diff --git a/workflow-pr/main.go b/workflow-pr/main.go index 2f495c0..e78214d 100644 --- a/workflow-pr/main.go +++ b/workflow-pr/main.go @@ -22,10 +22,8 @@ import ( "flag" "fmt" "log" - "math/rand" "path" "slices" - "strings" "time" "src.opensuse.org/autogits/common" @@ -70,148 +68,6 @@ var DebugMode bool var checkOnStart bool var checkInterval time.Duration -func verifyProjectState(processor *RequestProcessor, git *common.GitHandler, orgName string, config *common.AutogitConfig, configs []*common.AutogitConfig) error { - org := common.Organization{ - Username: orgName, - } - repo, err := processor.gitea.CreateRepositoryIfNotExist(git, org, config.GitProjectName) - if err != nil { - return fmt.Errorf("Error fetching or creating '%s/%s' -- aborting verifyProjectState(). Err: %w", orgName, config.GitProjectName, err) - } - - common.PanicOnError(git.GitExec("", "clone", "--depth", "1", repo.SSHURL, config.GitProjectName)) - log.Println("getting submodule list") - submodules, err := git.GitSubmoduleList(config.GitProjectName, "HEAD") - -nextSubmodule: - for sub, commitID := range submodules { - log.Println(" + checking", sub, commitID) - submoduleName := sub - if n := strings.LastIndex(sub, "/"); n != -1 { - submoduleName = sub[n+1:] - } - - // check if open PR have PR against project - prs, err := processor.gitea.GetRecentPullRequests(config.Organization, submoduleName) - if err != nil { - return fmt.Errorf("Error fetching pull requests for %s/%s. Err: %w", config.Organization, submoduleName, err) - } - - if DebugMode { - log.Println(" - # of PRs to check:", len(prs)) - } - - for _, pr := range prs { - var event common.PullRequestWebhookEvent - - event.Pull_Request = common.PullRequestFromModel(pr) - event.Action = string(pr.State) - event.Number = int(pr.Index) - event.Repository = common.RepositoryFromModel(pr.Base.Repo) - event.Sender = *common.UserFromModel(pr.User) - event.Requested_reviewer = nil - - g := &common.GitHandlerImpl{} - git, err := g.CreateGitHandler(GitAuthor, GitEmail, AppName) - if err != nil { - return fmt.Errorf("Error allocating GitHandler. Err: %w", err) - } - if !DebugMode { - defer git.Close() - } - - switch pr.State { - case "open": - processor.Opened.Process(&event, git, config) - case "closed": - processor.Closed.Process(&event, git, config) - default: - return fmt.Errorf("Unhandled pull request state: '%s'. %s/%s/%d", pr.State, config.Organization, submoduleName, pr.Index) - } - } - - // check if the commited changes are syned with branches - commits, err := processor.gitea.GetRecentCommits(config.Organization, submoduleName, config.Branch, 10) - if err != nil { - return fmt.Errorf("Error fetching recent commits for %s/%s. Err: %w", config.Organization, submoduleName, err) - } - - for idx, commit := range commits { - if commit.SHA == commitID { - if idx != 0 { - // commit in past ... - log.Println(" W -", submoduleName, " is behind the branch by", idx, "This should not happen in PR workflow alone") - } - continue nextSubmodule - } - } - - // not found in past, check if we should advance the branch label ... pull the submodule - git.GitExecOrPanic(config.GitProjectName, "submodule", "update", "--init", "--filter", "blob:none", "--", sub) - subDir := path.Join(config.GitProjectName, sub) - newCommits := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(subDir, "rev-list", "^origin/"+config.Branch, commitID), "\n") - - if len(newCommits) >= 1 { - if DebugMode { - log.Println(" - updating branch", config.Branch, "to new head", commitID, " - len:", len(newCommits)) - } - git.GitExecOrPanic(subDir, "checkout", "-B", config.Branch, commitID) - url := git.GitExecWithOutputOrPanic(subDir, "remote", "get-url", "origin", "--push") - sshUrl, err := common.TranslateHttpsToSshUrl(strings.TrimSpace(url)) - if err != nil { - return fmt.Errorf("Cannot traslate HTTPS git URL to SSH_URL. %w", err) - } - git.GitExecOrPanic(subDir, "remote", "set-url", "origin", "--push", sshUrl) - git.GitExecOrPanic(subDir, "push", "origin", config.Branch) - } - } - - // forward any package-gits referred by the project git, but don't go back - return nil -} - -func checkRepos(processor *RequestProcessor) { - for org, configs := range processor.configuredRepos { - for _, config := range configs { - if checkInterval > 0 { - sleepInterval := checkInterval - checkInterval/2 + time.Duration(rand.Int63n(int64(checkInterval))) - log.Println(" - sleep interval", sleepInterval, "until next check") - time.Sleep(sleepInterval) - } - - log.Printf(" ++ starting verification, org: `%s` config: `%s`\n", org, config.GitProjectName) - g := &common.GitHandlerImpl{} - git, err := g.CreateGitHandler(GitAuthor, GitEmail, AppName) - if err != nil { - log.Println("Faield to allocate GitHandler:", err) - return - } - if !DebugMode { - defer git.Close() - } - if err := verifyProjectState(processor, git, org, config, configs); err != nil { - log.Printf(" *** verification failed, org: `%s`, err: %#v\n", org, err) - } - log.Printf(" ++ verification complete, org: `%s` config: `%s`\n", org, config.GitProjectName) - } - } -} - -func consistencyCheckProcess(processor *RequestProcessor) { - if checkOnStart { - savedCheckInterval := checkInterval - checkInterval = 0 - log.Println("== Startup consistency check begin...") - checkRepos(processor) - log.Println("== Startup consistency check done...") - checkInterval = savedCheckInterval - } - - for { - checkRepos(processor) - } -} - func main() { if err := common.RequireGiteaSecretToken(); err != nil { log.Fatal(err) @@ -259,19 +115,23 @@ func main() { } } + + + checker := &DefaultStateChecker { + gitea: common.AllocateGiteaTransport(*giteaHost), + git: &common.GitHandlerImpl{}, + } req.Synced = &PullRequestSynced{ - gitea: req.gitea, + gitea: checker.gitea, } req.Opened = &PullRequestOpened{ - gitea: req.gitea, + gitea: checker.gitea, } req.Closed = &PullRequestClosed{ - gitea: req.gitea, + gitea: checker.gitea, } - req.gitea = common.AllocateGiteaTransport(*giteaHost) - - go consistencyCheckProcess(req) + go checker.consistencyCheckProcess(req) var defs common.ListenDefinitions diff --git a/workflow-pr/pr.go b/workflow-pr/pr.go index 4dd5c2f..a14384c 100644 --- a/workflow-pr/pr.go +++ b/workflow-pr/pr.go @@ -16,7 +16,6 @@ type RequestProcessor struct { Opened, Synced, Closed PullRequestProcessor configuredRepos map[string][]*common.AutogitConfig - gitea common.Gitea git common.GitHandlerGenerator } diff --git a/workflow-pr/repo_check.go b/workflow-pr/repo_check.go new file mode 100644 index 0000000..510899c --- /dev/null +++ b/workflow-pr/repo_check.go @@ -0,0 +1,162 @@ +package main + +import ( + "fmt" + "log" + "math/rand" + "path" + "strings" + "time" + + "src.opensuse.org/autogits/common" +) + +type StateChecker interface { +} + +type DefaultStateChecker struct { + processor *RequestProcessor + + gitea common.Gitea + git common.GitHandlerGenerator +} + +func (s *DefaultStateChecker) verifyProjectState(processor *RequestProcessor, git *common.GitHandler, orgName string, config *common.AutogitConfig, configs []*common.AutogitConfig) error { + org := common.Organization{ + Username: orgName, + } + repo, err := s.gitea.CreateRepositoryIfNotExist(git, org, config.GitProjectName) + if err != nil { + return fmt.Errorf("Error fetching or creating '%s/%s' -- aborting verifyProjectState(). Err: %w", orgName, config.GitProjectName, err) + } + + common.PanicOnError(git.GitExec("", "clone", "--depth", "1", repo.SSHURL, config.GitProjectName)) + log.Println("getting submodule list") + submodules, err := git.GitSubmoduleList(config.GitProjectName, "HEAD") + +nextSubmodule: + for sub, commitID := range submodules { + log.Println(" + checking", sub, commitID) + submoduleName := sub + if n := strings.LastIndex(sub, "/"); n != -1 { + submoduleName = sub[n+1:] + } + + // check if open PR have PR against project + prs, err := s.gitea.GetRecentPullRequests(config.Organization, submoduleName) + if err != nil { + return fmt.Errorf("Error fetching pull requests for %s/%s. Err: %w", config.Organization, submoduleName, err) + } + + if DebugMode { + log.Println(" - # of PRs to check:", len(prs)) + } + + for _, pr := range prs { + var event common.PullRequestWebhookEvent + + event.Pull_Request = common.PullRequestFromModel(pr) + event.Action = string(pr.State) + event.Number = int(pr.Index) + event.Repository = common.RepositoryFromModel(pr.Base.Repo) + event.Sender = *common.UserFromModel(pr.User) + event.Requested_reviewer = nil + + git, err := s.git.CreateGitHandler(GitAuthor, GitEmail, AppName) + if err != nil { + return fmt.Errorf("Error allocating GitHandler. Err: %w", err) + } + if !DebugMode { + defer git.Close() + } + + switch pr.State { + case "open": + s.processor.Opened.Process(&event, git, config) + case "closed": + s.processor.Closed.Process(&event, git, config) + default: + return fmt.Errorf("Unhandled pull request state: '%s'. %s/%s/%d", pr.State, config.Organization, submoduleName, pr.Index) + } + } + + // check if the commited changes are syned with branches + commits, err := s.gitea.GetRecentCommits(config.Organization, submoduleName, config.Branch, 10) + if err != nil { + return fmt.Errorf("Error fetching recent commits for %s/%s. Err: %w", config.Organization, submoduleName, err) + } + + for idx, commit := range commits { + if commit.SHA == commitID { + if idx != 0 { + // commit in past ... + log.Println(" W -", submoduleName, " is behind the branch by", idx, "This should not happen in PR workflow alone") + } + continue nextSubmodule + } + } + + // not found in past, check if we should advance the branch label ... pull the submodule + git.GitExecOrPanic(config.GitProjectName, "submodule", "update", "--init", "--filter", "blob:none", "--", sub) + subDir := path.Join(config.GitProjectName, sub) + newCommits := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(subDir, "rev-list", "^origin/"+config.Branch, commitID), "\n") + + if len(newCommits) >= 1 { + if DebugMode { + log.Println(" - updating branch", config.Branch, "to new head", commitID, " - len:", len(newCommits)) + } + git.GitExecOrPanic(subDir, "checkout", "-B", config.Branch, commitID) + url := git.GitExecWithOutputOrPanic(subDir, "remote", "get-url", "origin", "--push") + sshUrl, err := common.TranslateHttpsToSshUrl(strings.TrimSpace(url)) + if err != nil { + return fmt.Errorf("Cannot traslate HTTPS git URL to SSH_URL. %w", err) + } + git.GitExecOrPanic(subDir, "remote", "set-url", "origin", "--push", sshUrl) + git.GitExecOrPanic(subDir, "push", "origin", config.Branch) + } + } + + // forward any package-gits referred by the project git, but don't go back + return nil +} + +func (s *DefaultStateChecker) checkRepos(processor *RequestProcessor) { + for org, configs := range processor.configuredRepos { + for _, config := range configs { + if checkInterval > 0 { + sleepInterval := checkInterval - checkInterval/2 + time.Duration(rand.Int63n(int64(checkInterval))) + log.Println(" - sleep interval", sleepInterval, "until next check") + time.Sleep(sleepInterval) + } + + log.Printf(" ++ starting verification, org: `%s` config: `%s`\n", org, config.GitProjectName) + git, err := s.git.CreateGitHandler(GitAuthor, GitEmail, AppName) + if err != nil { + log.Println("Faield to allocate GitHandler:", err) + return + } + if !DebugMode { + defer git.Close() + } + if err := s.verifyProjectState(processor, git, org, config, configs); err != nil { + log.Printf(" *** verification failed, org: `%s`, err: %#v\n", org, err) + } + log.Printf(" ++ verification complete, org: `%s` config: `%s`\n", org, config.GitProjectName) + } + } +} + +func (s *DefaultStateChecker) consistencyCheckProcess(processor *RequestProcessor) { + if checkOnStart { + savedCheckInterval := checkInterval + checkInterval = 0 + log.Println("== Startup consistency check begin...") + s.checkRepos(processor) + log.Println("== Startup consistency check done...") + checkInterval = savedCheckInterval + } + + for { + s.checkRepos(processor) + } +}