package main import ( "errors" "fmt" "math/rand" "path" "runtime/debug" "strings" "time" "src.opensuse.org/autogits/common" "src.opensuse.org/autogits/common/gitea-generated/models" "src.opensuse.org/autogits/workflow-pr/interfaces" ) type DefaultStateChecker struct { exitCheckLoop bool checkOnStart bool checkInterval time.Duration processor *RequestProcessor i interfaces.StateChecker } func CreateDefaultStateChecker(checkOnStart bool, processor *RequestProcessor, gitea common.Gitea, interval time.Duration) *DefaultStateChecker { var s = &DefaultStateChecker{ checkInterval: interval, checkOnStart: checkOnStart, processor: processor, } s.i = s return s } func pullRequestToEventState(state models.StateType) string { switch state { case "open": return "opened" } return string(state) } func (s *DefaultStateChecker) ProcessPR(pr *models.PullRequest, config *common.AutogitConfig) error { return ProcesPullRequest(pr, common.AutogitConfigs{config}) } func PrjGitSubmoduleCheck(config *common.AutogitConfig, git common.Git, repo string, submodules map[string]string) (prsToProcess []*interfaces.PRToProcess, err error) { nextSubmodule: for sub, commitID := range submodules { common.LogDebug(" + checking", sub, commitID) submoduleName := sub if n := strings.LastIndex(sub, "/"); n != -1 { submoduleName = sub[n+1:] } // check if open PR have PR against project branch := config.Branch if branch == "" { repo, err := Gitea.GetRepository(config.Organization, submoduleName) if err != nil || repo == nil { common.LogError("Cannot fetch repository:", config.Organization, "/", submoduleName, "... submodule skipped.") continue } branch = repo.DefaultBranch } prsToProcess = append(prsToProcess, &interfaces.PRToProcess{ Org: config.Organization, Repo: submoduleName, Branch: branch, }) // check if the commited changes are syned with branches commits, err := Gitea.GetRecentCommits(config.Organization, submoduleName, branch, 10) if err != nil { return nil, 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 ... common.LogError(" -", 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(repo, "submodule", "update", "--init", "--filter", "blob:none", "--", sub) subDir := path.Join(repo, sub) newCommits := common.SplitStringNoEmpty(git.GitExecWithOutputOrPanic(subDir, "rev-list", "^origin/"+branch, commitID), "\n") if len(newCommits) >= 1 { common.LogDebug(" - updating branch", branch, "to new head", commitID, " - len:", len(newCommits)) git.GitExecOrPanic(subDir, "checkout", "-B", branch, commitID) url := git.GitExecWithOutputOrPanic(subDir, "remote", "get-url", "origin", "--push") sshUrl, err := common.TranslateHttpsToSshUrl(strings.TrimSpace(url)) if err != nil { return prsToProcess, 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", branch) } } return prsToProcess, nil } func (s *DefaultStateChecker) VerifyProjectState(config *common.AutogitConfig) ([]*interfaces.PRToProcess, error) { defer func() { if r := recover(); r != nil { common.LogError("panic caught") if err, ok := r.(error); !ok { common.LogError(err) } common.LogError(string(debug.Stack())) } }() prsToProcess := []*interfaces.PRToProcess{} prjGitOrg, prjGitRepo, prjGitBranch := config.GetPrjGit() common.LogInfo(" checking", prjGitOrg+"/"+prjGitRepo+"#"+prjGitBranch) git, err := GitHandler.CreateGitHandler(config.Organization) common.LogDebug("Git Path:", git.GetPath()) if err != nil { return nil, fmt.Errorf("Cannot create git handler: %w", err) } defer git.Close() repo, err := Gitea.CreateRepositoryIfNotExist(git, prjGitOrg, prjGitRepo) if err != nil { return nil, fmt.Errorf("Error fetching or creating '%s/%s#%s' -- aborting verifyProjectState(). Err: %w", prjGitBranch, prjGitRepo, prjGitBranch, err) } _, err = git.GitClone(prjGitRepo, prjGitBranch, repo.SSHURL) common.PanicOnError(err) prsToProcess = append(prsToProcess, &interfaces.PRToProcess{ Org: prjGitOrg, Repo: prjGitRepo, Branch: prjGitBranch, }) submodules, err := git.GitSubmoduleList(prjGitRepo, "HEAD") // forward any package-gits referred by the project git, but don't go back return PrjGitSubmoduleCheck(config, git, prjGitRepo, submodules) } func (s *DefaultStateChecker) CheckRepos() error { defer func() { if r := recover(); r != nil { common.LogError("panic caught") if err, ok := r.(error); !ok { common.LogError(err) } common.LogError(string(debug.Stack())) } }() errorList := make([]error, 0, 10) for org, configs := range s.processor.configuredRepos { for _, config := range configs { if s.checkInterval > 0 { sleepInterval := (s.checkInterval - s.checkInterval/2) + time.Duration(rand.Int63n(int64(s.checkInterval))) common.LogInfo(" - sleep interval", sleepInterval, "until next check") time.Sleep(sleepInterval) } common.LogInfo(" ++ starting verification, org:", org, "config:", config.GitProjectName) prs, err := s.i.VerifyProjectState(config) if err != nil { common.LogError(" *** verification failed, org:", org, err) errorList = append(errorList, err) } for _, pr := range prs { prs, err := Gitea.GetRecentPullRequests(pr.Org, pr.Repo, pr.Branch) if err != nil { return fmt.Errorf("Error fetching pull requests for %s/%s#%s. Err: %w", pr.Org, pr.Repo, pr.Branch, err) } if len(prs) > 0 { common.LogDebug(fmt.Sprintf("%s/%s#%s", pr.Org, pr.Repo, pr.Branch), " - # of PRs to check:", len(prs)) } for _, pr := range prs { s.ProcessPR(pr, config) } } common.LogInfo(" ++ verification complete, org:", org, "config:", config.GitProjectName) } } return errors.Join(errorList...) } func (s *DefaultStateChecker) ConsistencyCheckProcess() error { if s.checkOnStart { savedCheckInterval := s.checkInterval s.checkInterval = 0 common.LogInfo("== Startup consistency check begin...") s.i.CheckRepos() common.LogInfo("== Startup consistency check done...") s.checkInterval = savedCheckInterval } for { if s.exitCheckLoop { break } s.i.CheckRepos() } return nil }