Files
autogits/common/config.go
Adam Majer b091e0e98d common: panic if the config is unresoveable
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
2025-08-25 17:04:43 +02:00

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
}