|  | package verifiers | 
|  |  | 
|  | import ( | 
|  | "context" | 
|  | "fmt" | 
|  | "net/http" | 
|  | "time" | 
|  |  | 
|  | "go.skia.org/infra/go/allowed" | 
|  | "go.skia.org/infra/go/gerrit" | 
|  | "go.skia.org/infra/go/git" | 
|  | "go.skia.org/infra/go/skerr" | 
|  | "go.skia.org/infra/go/sklog" | 
|  | "go.skia.org/infra/skcq/go/codereview" | 
|  | "go.skia.org/infra/skcq/go/config" | 
|  | "go.skia.org/infra/skcq/go/types" | 
|  | ) | 
|  |  | 
|  | var ( | 
|  | // vmTimeNowFunc allows tests to mock out time.Now() for testing. | 
|  | vmTimeNowFunc = time.Now | 
|  | ) | 
|  |  | 
|  | // SkCQVerifiersManager implements VerifiersManager. | 
|  | type SkCQVerifiersManager struct { | 
|  | throttlerManager      types.ThrottlerManager | 
|  | httpClient            *http.Client | 
|  | criaClient            *http.Client | 
|  | cr                    codereview.CodeReview | 
|  | canModifyCfgsOnTheFly allowed.Allow | 
|  | // Allow lists will be cached here so that they are not continuously | 
|  | // newly instantiated. | 
|  | allowlistCache map[string]allowed.Allow | 
|  | } | 
|  |  | 
|  | // NewSkCQVerifiersManager returns an instance of SkCQVerifiersManager. | 
|  | func NewSkCQVerifiersManager(throttlerManager types.ThrottlerManager, httpClient, criaClient *http.Client, cr codereview.CodeReview, canModifyCfgsOnTheFly allowed.Allow) *SkCQVerifiersManager { | 
|  | return &SkCQVerifiersManager{ | 
|  | throttlerManager: throttlerManager, | 
|  | httpClient:       httpClient, | 
|  | criaClient:       criaClient, | 
|  | cr:               cr, | 
|  | allowlistCache:   map[string]allowed.Allow{}, | 
|  | } | 
|  | } | 
|  |  | 
|  | // GetVerifiers implements the VerifierManager interface. | 
|  | func (vm *SkCQVerifiersManager) GetVerifiers(ctx context.Context, cfg *config.SkCQCfg, ci *gerrit.ChangeInfo, isSubmittedTogetherChange bool, configReader config.ConfigReader) ([]types.Verifier, []string, error) { | 
|  | // Instantiate the 2 slices that will be populated and returned. | 
|  | clVerifiers := []types.Verifier{} | 
|  | togetherChanges := []*gerrit.ChangeInfo{} | 
|  |  | 
|  | // Get footers map to pass into the different verifiers that need them. | 
|  | commitMsg, err := vm.cr.GetCommitMessage(ctx, ci.Issue) | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Could not get commit message of %d", ci.Issue) | 
|  | } | 
|  | footersMap := git.GetFootersMap(commitMsg) | 
|  |  | 
|  | // Check if the change is a CQ run (vs dry-run) first. This is done because | 
|  | // if a change has both CQ+2 and CQ+1 votes then we want to consider the CQ+2 | 
|  | // vote first. | 
|  | if isSubmittedTogetherChange || vm.cr.IsCQ(ctx, ci) { | 
|  | // Do not need to run these verifiers if it is a submitted together change. | 
|  | // This is done because checking if the CQ+2 triggerer is a committer | 
|  | // was already done in the original change. | 
|  | // Also, we do not need to look for the submitted together changes for | 
|  | // this submitted together change. It is unecessary and would probably | 
|  | // cause an infinite-loop of some kind. | 
|  | if !isSubmittedTogetherChange { | 
|  | // Get committer list from the cache, or set it if it does not exist. | 
|  | committerList, ok := vm.allowlistCache[cfg.CommitterList] | 
|  | if !ok { | 
|  | committerList, err = allowed.NewAllowedFromChromeInfraAuth(vm.criaClient, cfg.CommitterList) | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Could not create an allowed from %s", cfg.CommitterList) | 
|  | } | 
|  | vm.allowlistCache[cfg.CommitterList] = committerList | 
|  | } | 
|  |  | 
|  | // Verify all the submitted together changes (if any exist). | 
|  | togetherChanges, err = vm.cr.GetSubmittedTogether(ctx, ci) | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Error when getting submitted together chagnes for SubmittedTogetherVerifier") | 
|  | } | 
|  | if len(togetherChanges) > 0 { | 
|  | togetherChangesVerifier, err := NewSubmittedTogetherVerifier(ctx, vm, togetherChanges, vm.httpClient, vm.cr, ci, footersMap, vm.canModifyCfgsOnTheFly) | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Error when creating SubmittedTogetherVerifier") | 
|  | } | 
|  | clVerifiers = append(clVerifiers, togetherChangesVerifier) | 
|  | } | 
|  | } | 
|  |  | 
|  | // Verify that the change does not have "Commit: false". | 
|  | commitFooterVerifier, err := NewCommitFooterVerifier(footersMap) | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Error when creating CommitFooterVerifier") | 
|  | } | 
|  | clVerifiers = append(clVerifiers, commitFooterVerifier) | 
|  |  | 
|  | // Verify that the change is not WIP. | 
|  | wipVerifier, err := NewWIPVerifier() | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Error when creating WIPVerifier") | 
|  | } | 
|  | clVerifiers = append(clVerifiers, wipVerifier) | 
|  |  | 
|  | // Verify the change is submittable. | 
|  | submittableVerifier, err := NewSubmittableVerifier() | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Error when creating SubmittableVerifier") | 
|  | } | 
|  | clVerifiers = append(clVerifiers, submittableVerifier) | 
|  |  | 
|  | if cfg.TreeStatusURL != "" { | 
|  | // Verify that the tree is open. | 
|  | treeStatusVerifier, err := NewTreeStatusVerifier(vm.httpClient, cfg.TreeStatusURL, footersMap) | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Error when creating TreeStatusVerifier") | 
|  | } | 
|  | clVerifiers = append(clVerifiers, treeStatusVerifier) | 
|  | } | 
|  |  | 
|  | // Verify that the change can be submitted without throttling. | 
|  | throttlerVerifier, err := NewThrottlerVerifier(cfg.ThrottlerCfg, vm.throttlerManager) | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Error when creating ThrottlerVerifier") | 
|  | } | 
|  | clVerifiers = append(clVerifiers, throttlerVerifier) | 
|  |  | 
|  | } | 
|  |  | 
|  | // Verifiers common to both dry runs and CQ. | 
|  |  | 
|  | if cfg.TasksJSONPath != "" { | 
|  | // Verify that try jobs ran. | 
|  | tasksCfg, err := configReader.GetTasksCfg(ctx, cfg.TasksJSONPath) | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Error getting tasks cfg") | 
|  | } | 
|  | tryJobsVerifier, err := NewTryJobsVerifier(vm.httpClient, vm.cr, tasksCfg, footersMap, cfg.VisibilityType) | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Error when creating TryJobsVerifier") | 
|  | } | 
|  | clVerifiers = append(clVerifiers, tryJobsVerifier) | 
|  | } | 
|  |  | 
|  | if cfg.AuthorsPath != "" { | 
|  | // Verify that the author of the change is specified in the AUTHORS file. | 
|  | authorsFileContent, err := configReader.GetAuthorsFileContents(ctx, cfg.AuthorsPath) | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Error getting AUTHORS file") | 
|  | } | 
|  | authorsVerifier, err := NewAuthorsVerifier(vm.cr, authorsFileContent) | 
|  | if err != nil { | 
|  | return nil, nil, skerr.Wrapf(err, "Error when creating AuthorsVerifier") | 
|  | } | 
|  | clVerifiers = append(clVerifiers, authorsVerifier) | 
|  | } | 
|  |  | 
|  | togetherChangeIDs := []string{} | 
|  | for _, t := range togetherChanges { | 
|  | togetherChangeIDs = append(togetherChangeIDs, fmt.Sprintf("%d", t.Issue)) | 
|  | } | 
|  | return clVerifiers, togetherChangeIDs, nil | 
|  | } | 
|  |  | 
|  | // RunVerifiers implements the VerifierManager interface. | 
|  | func (vm *SkCQVerifiersManager) RunVerifiers(ctx context.Context, ci *gerrit.ChangeInfo, verifiers []types.Verifier, startTime int64) []*types.VerifierStatus { | 
|  | verifierStatuses := []*types.VerifierStatus{} | 
|  | for _, v := range verifiers { | 
|  | status := &types.VerifierStatus{ | 
|  | Name:    v.Name(), | 
|  | StartTs: startTime, | 
|  | } | 
|  | verifierState, reason, err := v.Verify(ctx, ci, startTime) | 
|  | if err != nil { | 
|  | // Always consider errors from verify as transient errors so that they | 
|  | // are retried by SkCQ. Always log them so that alerts appear due to | 
|  | // error rate alerts. | 
|  | errMsg := fmt.Sprintf("%s: Hopefully a transient error: %s", v.Name(), err) | 
|  | sklog.Errorf("[%d] %s", ci.Issue, errMsg) | 
|  | status.State = types.VerifierWaitingState | 
|  | status.Reason = errMsg | 
|  | } else { | 
|  | status.State = verifierState | 
|  | status.Reason = reason | 
|  | switch verifierState { | 
|  | case types.VerifierSuccessState: | 
|  | status.StopTs = vmTimeNowFunc().Unix() | 
|  | case types.VerifierFailureState: | 
|  | status.StopTs = vmTimeNowFunc().Unix() | 
|  | } | 
|  | } | 
|  | verifierStatuses = append(verifierStatuses, status) | 
|  | } | 
|  | return verifierStatuses | 
|  | } |