package main /* * 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 . */ import ( "flag" "net/url" "os" "regexp" "slices" "strconv" "time" "src.opensuse.org/autogits/common" "src.opensuse.org/autogits/common/gitea-generated/models" ) const ( AppName = "workflow-pr" ) var GitAuthor string var GitEmail string var ListPROnly bool var PRID int64 var CurrentUser *models.User var GitHandler common.GitHandlerGenerator var Gitea common.Gitea func main() { flag.StringVar(&GitAuthor, "git-author", "AutoGits PR Review Bot", "Git commit author") flag.StringVar(&GitEmail, "git-email", "amajer+devel-git@suse.de", "Git commit email") workflowConfig := flag.String("config", "", "Repository and workflow definition file") giteaUrl := flag.String("gitea-url", "https://src.opensuse.org", "Gitea instance") rabbitUrl := flag.String("url", "amqps://rabbit.opensuse.org", "URL for RabbitMQ instance") debugMode := flag.Bool("debug", false, "Extra debugging information") checkOnStart := flag.Bool("check-on-start", false, "Check all repositories for consistency on start, without delays") checkIntervalHours := flag.Float64("check-interval", 5, "Check interval (+-random delay) for repositories for consitency, in hours") flag.BoolVar(&ListPROnly, "list-prs-only", false, "Only lists PRs without acting on them") flag.Int64Var(&PRID, "id", -1, "Process only the specific ID and ignore the rest. Use for debugging") basePath := flag.String("repo-path", "", "Repository path. Default is temporary directory") pr := flag.String("only-pr", "", "Only specific PR to process. For debugging") flag.BoolVar(&common.IsDryRun, "dry", false, "Dry mode. Do not push changes to remote repo.") flag.Parse() common.SetLoggingLevel(common.LogLevelInfo) if *debugMode { common.SetLoggingLevel(common.LogLevelDebug) } if err := common.RequireGiteaSecretToken(); err != nil { common.LogError("No Gitea secrets:", err) return } if err := common.RequireRabbitSecrets(); err != nil { common.LogError("No RabbitMQ secret:", err) return } common.LogDebug("Parsing config:", *workflowConfig) if len(*workflowConfig) == 0 { common.LogError("No configuratio file specified. Aborting") return } Gitea = common.AllocateGiteaTransport(*giteaUrl) config, err := common.ReadConfigFile(*workflowConfig) if err != nil { common.LogError("Cannot read config files:", err) return } configs, err := common.ResolveWorkflowConfigs(Gitea, config) if err != nil { common.LogError("Cannot resolve config files:", err) return } for _, c := range configs { common.LogDebug(*c) } req := new(RequestProcessor) req.configuredRepos = make(map[string][]*common.AutogitConfig) if len(*basePath) == 0 { *basePath, err = os.MkdirTemp(os.TempDir(), AppName) common.PanicOnError(err) defer os.RemoveAll(*basePath) } GitHandler, err = common.AllocateGitWorkTree(*basePath, GitAuthor, GitEmail) common.PanicOnError(err) orgs := make([]string, 0, 1) for _, c := range configs { if slices.Contains(c.Workflows, "pr") { common.LogDebug(" + adding org:", c.Organization, " branch:", c.Branch, " prjgit:", c.GitProjectName) configs := req.configuredRepos[c.Organization] if configs == nil { configs = make([]*common.AutogitConfig, 0, 1) } configs = append(configs, c) req.configuredRepos[c.Organization] = configs orgs = append(orgs, c.Organization) // add project git organization, if different if prjOrg, _, _ := c.GetPrjGit(); prjOrg != c.Organization { orgs = append(orgs, prjOrg) req.configuredRepos[prjOrg] = configs } } } if CurrentUser, err = Gitea.GetCurrentUser(); err != nil { common.LogError("Failed to fetch current gitea user:", err) return } common.LogInfo("Running with token from", CurrentUser.UserName) if *pr != "" { rx := regexp.MustCompile("^([a-zA-Z0-9_\\.-]+)/([a-zA-Z0-9_\\.-]+)[!#]([0-9]+)$") if data := rx.FindStringSubmatch(*pr); data == nil { common.LogError("Cannot parse PR line to process", *pr) } else { org := data[1] repo := data[2] num, err := strconv.ParseInt(data[3], 10, 64) common.LogInfo("Processing:", org, "/", repo, "#", num) common.PanicOnError(err) pr, err := Gitea.GetPullRequest(org, repo, num) if err != nil { common.LogError("Cannot fetch PR", err) return } if err = ProcesPullRequest(pr, configs); err != nil { common.LogError("processor returned error", err) } } return } checker := CreateDefaultStateChecker(*checkOnStart, req, Gitea, time.Duration(*checkIntervalHours)*time.Hour) go checker.ConsistencyCheckProcess() listenDefs := &common.RabbitMQGiteaEventsProcessor{ Orgs: orgs, Handlers: map[string]common.RequestProcessor{ common.RequestType_PR: req, common.RequestType_PRSync: req, common.RequestType_PRReviewAccepted: req, common.RequestType_PRReviewRejected: req, common.RequestType_IssueComment: req, }, } listenDefs.Connection().RabbitURL, _ = url.Parse(*rabbitUrl) common.PanicOnError(common.ProcessRabbitMQEvents(listenDefs)) }