Generalize interface to allow processing of any events, not just Gitea events.
254 lines
6.6 KiB
Go
254 lines
6.6 KiB
Go
package common
|
|
|
|
/*
|
|
* This file is part of Autogits.
|
|
*
|
|
* Copyright © 2024 SUSE LLC
|
|
*
|
|
* Autogits is free software: you can redistribute it and/or modify it under
|
|
* the terms of the GNU General Public License as published by the Free Software
|
|
* Foundation, either version 2 of the License, or (at your option) any later
|
|
* version.
|
|
*
|
|
* Autogits is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
|
* PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with
|
|
* Foobar. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/tailscale/hujson"
|
|
)
|
|
|
|
//go:generate mockgen -source=config.go -destination=mock/config.go -typed
|
|
|
|
const (
|
|
ProjectConfigFile = "workflow.config"
|
|
StagingConfigFile = "staging.config"
|
|
)
|
|
|
|
type ConfigFile struct {
|
|
GitProjectNames []string
|
|
}
|
|
|
|
type ReviewGroup struct {
|
|
Name string
|
|
Reviewers []string
|
|
}
|
|
|
|
type QAConfig struct {
|
|
Name string
|
|
Origin string
|
|
}
|
|
|
|
type AutogitConfig struct {
|
|
Workflows []string // [pr, direct, test]
|
|
Organization string
|
|
GitProjectName string // Organization/GitProjectName.git is PrjGit
|
|
Branch string // branch name of PkgGit that aligns with PrjGit submodules
|
|
Reviewers []string // only used by `pr` workflow
|
|
ReviewGroups []ReviewGroup
|
|
Committers []string // group in addition to Reviewers and Maintainers that can order the bot around, mostly as helper for factory-maintainers
|
|
Subdirs []string // list of directories to sort submodules into. Needed b/c _manifest cannot list non-existent directories
|
|
|
|
ManualMergeOnly bool // only merge with "Merge OK" comment by Project Maintainers and/or Package Maintainers and/or reviewers
|
|
ManualMergeProject bool // require merge of ProjectGit PRs with "Merge OK" by ProjectMaintainers and/or reviewers
|
|
}
|
|
|
|
type AutogitConfigs []*AutogitConfig
|
|
|
|
func ReadConfig(reader io.Reader) (*ConfigFile, error) {
|
|
data, err := io.ReadAll(reader)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error reading config data: %w", err)
|
|
}
|
|
|
|
config := ConfigFile{}
|
|
data, err = hujson.Standardize(data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to parse json: %w", err)
|
|
}
|
|
if err := json.Unmarshal(data, &config.GitProjectNames); err != nil {
|
|
return nil, fmt.Errorf("Error parsing Git Project paths: %w", err)
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
func ReadConfigFile(filename string) (*ConfigFile, error) {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Cannot open config file for reading. err: %w", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
return ReadConfig(file)
|
|
}
|
|
|
|
type GiteaFileContentAndRepoFetcher interface {
|
|
GiteaFileContentReader
|
|
GiteaRepoFetcher
|
|
}
|
|
|
|
func UnmarshalWorkflowConfig(data []byte) (*AutogitConfig, error) {
|
|
var config AutogitConfig
|
|
data, err := hujson.Standardize(data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to parse json: %w", err)
|
|
}
|
|
if err := json.Unmarshal(data, &config); err != nil {
|
|
return nil, fmt.Errorf("Error parsing workflow config file: %s: %w", string(data), err)
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
func ReadWorkflowConfig(gitea GiteaFileContentAndRepoFetcher, git_project string) (*AutogitConfig, error) {
|
|
hash := strings.Split(git_project, "#")
|
|
branch := ""
|
|
if len(hash) > 1 {
|
|
branch = hash[1]
|
|
}
|
|
|
|
a := strings.Split(hash[0], "/")
|
|
prjGitRepo := DefaultGitPrj
|
|
switch len(a) {
|
|
case 1:
|
|
case 2:
|
|
prjGitRepo = a[1]
|
|
default:
|
|
return nil, fmt.Errorf("Missing org/repo in projectgit: %s", git_project)
|
|
}
|
|
|
|
data, _, err := gitea.GetRepositoryFileContent(a[0], prjGitRepo, branch, ProjectConfigFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Error fetching 'workflow.config' for %s/%s#%s: %w", a[0], prjGitRepo, branch, err)
|
|
}
|
|
|
|
config, err := UnmarshalWorkflowConfig(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(config.Organization) < 1 {
|
|
config.Organization = a[0]
|
|
}
|
|
config.GitProjectName = a[0] + "/" + prjGitRepo
|
|
if len(branch) == 0 {
|
|
if r, err := gitea.GetRepository(a[0], prjGitRepo); err == nil {
|
|
branch = r.DefaultBranch
|
|
} else {
|
|
return nil, fmt.Errorf("Failed to read workflow config in %s: %w", git_project, err)
|
|
}
|
|
}
|
|
config.GitProjectName = config.GitProjectName + "#" + branch
|
|
return config, nil
|
|
}
|
|
|
|
func ResolveWorkflowConfigs(gitea GiteaFileContentAndRepoFetcher, config *ConfigFile) (AutogitConfigs, error) {
|
|
configs := make([]*AutogitConfig, 0, len(config.GitProjectNames))
|
|
for _, git_project := range config.GitProjectNames {
|
|
c, err := ReadWorkflowConfig(gitea, git_project)
|
|
if err != nil {
|
|
// can't sync, so ignore for now
|
|
log.Println(err)
|
|
} else {
|
|
configs = append(configs, c)
|
|
}
|
|
}
|
|
|
|
return configs, nil
|
|
}
|
|
|
|
func (configs AutogitConfigs) GetPrjGitConfig(org, repo, branch string) *AutogitConfig {
|
|
prjgit := org + "/" + repo + "#" + branch
|
|
for _, c := range configs {
|
|
if c.GitProjectName == prjgit {
|
|
return c
|
|
}
|
|
if c.Organization == org && c.Branch == branch {
|
|
return c
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (config *AutogitConfig) GetReviewGroupMembers(reviewer string) ([]string, error) {
|
|
for _, g := range config.ReviewGroups {
|
|
if g.Name == reviewer {
|
|
return g.Reviewers, nil
|
|
}
|
|
}
|
|
|
|
return nil, errors.New("User " + reviewer + " not found as group reviewer for " + config.GitProjectName)
|
|
}
|
|
|
|
func (config *AutogitConfig) GetPrjGit() (string, string, string) {
|
|
org := config.Organization
|
|
repo := DefaultGitPrj
|
|
branch := "master"
|
|
|
|
a := strings.Split(config.GitProjectName, "/")
|
|
if len(a[0]) > 0 {
|
|
repo = strings.TrimSpace(a[0])
|
|
}
|
|
if len(a) == 2 {
|
|
if a[0] = strings.TrimSpace(a[0]); len(a[0]) > 0 {
|
|
org = a[0]
|
|
}
|
|
repo = strings.TrimSpace(a[1])
|
|
}
|
|
b := strings.Split(repo, "#")
|
|
if len(b) == 2 {
|
|
if b[0] = strings.TrimSpace(b[0]); len(b[0]) > 0 {
|
|
repo = b[0]
|
|
} else {
|
|
repo = DefaultGitPrj
|
|
}
|
|
if b[1] = strings.TrimSpace(b[1]); len(b[1]) > 0 {
|
|
branch = strings.TrimSpace(b[1])
|
|
}
|
|
}
|
|
|
|
return org, repo, branch
|
|
}
|
|
|
|
func (config *AutogitConfig) GetRemoteBranch() string {
|
|
return "origin_" + config.Branch
|
|
}
|
|
|
|
type StagingConfig struct {
|
|
ObsProject string
|
|
RebuildAll bool
|
|
CleanupDelay int // cleanup delay, in hours, for unmerged closed PRs (def: 48)
|
|
|
|
// if set, then only use pull request numbers as unique identifiers
|
|
StagingProject string
|
|
QA []QAConfig
|
|
}
|
|
|
|
func ParseStagingConfig(data []byte) (*StagingConfig, error) {
|
|
var staging StagingConfig
|
|
data, err := hujson.Standardize(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
staging.CleanupDelay = 48
|
|
if err := json.Unmarshal(data, &staging); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &staging, nil
|
|
}
|