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) } }