either branch for project config must be defined in the config, OR it can be fetched as default branch from Gitea. If neither happens, it's best not to do any guessing here
267 lines
7.0 KiB
Go
267 lines
7.0 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
|
|
Silent bool // will not request reviews from group members
|
|
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) GetReviewGroup(reviewer string) (*ReviewGroup, error) {
|
|
for _, g := range config.ReviewGroups {
|
|
if g.Name == reviewer {
|
|
return g, 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 := ""
|
|
|
|
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])
|
|
}
|
|
}
|
|
|
|
if len(branch) == 0 {
|
|
panic("branch for project is undefined. Should not happend." + org + "/" + repo)
|
|
}
|
|
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
|
|
}
|