// Munge is the workhorse the will actually make updates to the PR func (LGTMAfterCommitMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel(lgtmLabel) { return } lastModified := obj.LastModifiedTime() lgtmTime := obj.LabelTime(lgtmLabel) if lastModified == nil || lgtmTime == nil { glog.Errorf("PR %d unable to determine lastModified or lgtmTime", *obj.Issue.Number) return } if lastModified.After(*lgtmTime) { glog.Infof("PR: %d lgtm:%s lastModified:%s", *obj.Issue.Number, lgtmTime.String(), lastModified.String()) body := fmt.Sprintf(lgtmRemovedBody, mungerutil.GetIssueUsers(obj.Issue).AllUsers().Mention().Join()) if err := obj.WriteComment(body); err != nil { return } obj.RemoveLabel(lgtmLabel) } }
// Munge is the workhorse the will actually make updates to the PR func (r *RebuildMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } comments, err := obj.ListComments() if err != nil { glog.Errorf("unexpected error getting comments: %v", err) } for ix := range comments { comment := comments[ix] // Skip all robot comments if r.robots.Has(*comment.User.Login) { glog.V(4).Infof("Skipping comment by robot %s: %s", *comment.User.Login, *comment.Body) continue } if isRebuildComment(comment) && rebuildCommentMissingIssueNumber(comment) { if err := obj.DeleteComment(comment); err != nil { glog.Errorf("Error deleting comment: %v", err) continue } body := fmt.Sprintf(rebuildFormat, *comment.User.Login) err := obj.WriteComment(body) if err != nil { glog.Errorf("unexpected error adding comment: %v", err) continue } } } }
// Munge is the workhorse the will actually make updates to the PR func (r *ReleaseNoteLabel) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if completedReleaseNoteProcess(obj) { r.ensureNoRelNoteNeededLabel(obj) return } if !r.prMustFollowRelNoteProcess(obj) { r.ensureNoRelNoteNeededLabel(obj) return } if !obj.HasLabel(releaseNoteLabelNeeded) { obj.AddLabel(releaseNoteLabelNeeded) } if !obj.HasLabel(lgtmLabel) { return } obj.WriteComment(releaseNoteBody) obj.RemoveLabel(lgtmLabel) }
// Munge is the workhorse the will actually make updates to the PR func (b *BlockPath) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if obj.HasLabel(doNotMergeLabel) { return } files, err := obj.ListFiles() if err != nil { return } for _, f := range files { if matchesAny(*f.Filename, b.blockRegexp) { if matchesAny(*f.Filename, b.doNotBlockRegexp) { continue } obj.WriteComment(blockPathBody) obj.AddLabels([]string{doNotMergeLabel}) return } } }
func (r *ReleaseNoteLabel) prMustFollowRelNoteProcess(obj *github.MungeObject) bool { if obj.IsForBranch("master") { return true } parents := getCherrypickParentPRs(obj, r.config) // if it has no parents it needs to follow the release note process if len(parents) == 0 { return true } for _, parent := range parents { // If the parent didn't set a release note, the CP must if !parent.HasLabel(releaseNote) && !parent.HasLabel(releaseNoteActionRequired) { if !obj.HasLabel(releaseNoteLabelNeeded) { obj.WriteComment(parentReleaseNoteBody) } return true } } // All of the parents set the releaseNote or releaseNoteActionRequired label, // so this cherrypick PR needs to do nothing. return false }
// Munge is the workhorse the will actually make updates to the PR func (b *BlockPath) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if obj.HasLabel(doNotMergeLabel) { return } commits, err := obj.GetCommits() if err != nil { return } for _, c := range commits { for _, f := range c.Files { if matchesAny(*f.Filename, b.blockRegexp) { if matchesAny(*f.Filename, b.doNotBlockRegexp) { continue } body := fmt.Sprintf(`Adding label:%s because PR changes docs prohibited to auto merge See http://kubernetes.io/editdocs/ for information about editing docs`, doNotMergeLabel) obj.WriteComment(body) obj.AddLabels([]string{doNotMergeLabel}) return } } } }
// Munge is the workhorse the will actually make updates to the PR func (StaleGreenCI) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel(lgtmLabel) { return } if mergeable, err := obj.IsMergeable(); !mergeable || err != nil { return } if !obj.IsStatusSuccess(requiredContexts) { return } for _, context := range requiredContexts { statusTime := obj.GetStatusTime(context) if statusTime == nil { glog.Errorf("%d: unable to determine time %q context was set", *obj.Issue.Number, context) return } if time.Since(*statusTime) > staleGreenCIHours*time.Hour { obj.WriteComment(greenMsgBody) err := obj.WaitForPending(requiredContexts) if err != nil { glog.Errorf("Failed waiting for PR to start testing: %v", err) } return } } }
// Munge is the workhorse the will actually make updates to the PR func (NagFlakeIssues) Munge(obj *mgh.MungeObject) { if obj.IsPR() || !obj.HasLabel("kind/flake") { return } comments, err := obj.ListComments() if err != nil { glog.Error(err) return } // Use the pinger to notify assignees: // - Set time period based on configuration (at the top of this file) // - Mention list of assignees as an argument // - Start the ping timer after the last HumanActor comment // How often should we ping period := findTimePeriod(obj.Issue.Labels) // Who are we pinging who := mungerutil.GetIssueUsers(obj.Issue).Assignees.Mention().Join() // When does the pinger start startDate := c.LastComment(comments, c.HumanActor(), obj.Issue.CreatedAt) // Get a notification if it's time to ping. notif := pinger.SetTimePeriod(period).PingNotification( comments, who, startDate, ) if notif != nil { obj.WriteComment(notif.String()) } }
// Munge is the workhorse the will actually make updates to the PR func (LGTMAfterCommitMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel("lgtm") { return } lastModified := obj.LastModifiedTime() lgtmTime := obj.LabelTime("lgtm") if lastModified == nil || lgtmTime == nil { glog.Errorf("PR %d unable to determine lastModified or lgtmTime", *obj.Issue.Number) return } if lastModified.After(*lgtmTime) { glog.Infof("PR: %d lgtm:%s lastModified:%s", *obj.Issue.Number, lgtmTime.String(), lastModified.String()) lgtmRemovedBody := "PR changed after LGTM, removing LGTM." if err := obj.WriteComment(lgtmRemovedBody); err != nil { return } obj.RemoveLabel("lgtm") } }
// Munge is the workhorse the will actually make updates to the PR func (PingCIMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel("lgtm") { return } mergeable, err := obj.IsMergeable() if err != nil { glog.V(2).Infof("Skipping %d - problem determining mergeability", *obj.Issue.Number) return } if !mergeable { glog.V(2).Infof("Skipping %d - not mergeable", *obj.Issue.Number) return } if state := obj.GetStatusState([]string{jenkinsCIContext, travisContext}); state != "incomplete" { glog.V(2).Info("Have %s status - skipping ping CI", jenkinsCIContext) return } state := obj.GetStatusState([]string{shippableContext, travisContext}) if state == "incomplete" { msg := "Continuous integration appears to have missed, closing and re-opening to trigger it" obj.WriteComment(msg) obj.ClosePR() time.Sleep(5 * time.Second) obj.OpenPR(10) } }
// Munge is the workhorse the will actually make updates to the PR func (StalePendingCI) Munge(obj *github.MungeObject) { requiredContexts := []string{jenkinsUnitContext, jenkinsE2EContext} if !obj.IsPR() { return } if !obj.HasLabel(lgtmLabel) { return } if mergeable, err := obj.IsMergeable(); !mergeable || err != nil { return } status := obj.GetStatusState(requiredContexts) if status != "pending" { return } for _, context := range requiredContexts { statusTime := obj.GetStatusTime(context) if statusTime == nil { glog.Errorf("%d: unable to determine time %q context was set", *obj.Issue.Number, context) return } if time.Since(*statusTime) > stalePendingCIHours*time.Hour { obj.WriteComment(pendingMsgBody) return } } }
// Munge is the workhorse the will actually make updates to the PR func (s *SizeMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } issue := obj.Issue s.getGeneratedFiles(obj) genFiles := *s.genFiles genPrefixes := *s.genPrefixes files, err := obj.ListFiles() if err != nil { return } adds := 0 dels := 0 for _, f := range files { skip := false for _, p := range genPrefixes { if strings.HasPrefix(*f.Filename, p) { skip = true break } } if skip { continue } if genFiles.Has(*f.Filename) { continue } if f.Additions != nil { adds += *f.Additions } if f.Deletions != nil { dels += *f.Deletions } } newSize := calculateSize(adds, dels) newLabel := labelSizePrefix + newSize existing := github.GetLabelsWithPrefix(issue.Labels, labelSizePrefix) needsUpdate := true for _, l := range existing { if l == newLabel { needsUpdate = false continue } obj.RemoveLabel(l) } if needsUpdate { obj.AddLabels([]string{newLabel}) body := fmt.Sprintf("Labelling this PR as %s", newLabel) obj.WriteComment(body) } }
func (sq *SubmitQueue) doGithubE2EAndMerge(obj *github.MungeObject) { err := obj.Refresh() if err != nil { glog.Errorf("%d: unknown err: %v", *obj.Issue.Number, err) sq.SetMergeStatus(obj, unknown) return } if !sq.validForMerge(obj) { return } if obj.HasLabel(e2eNotRequiredLabel) { obj.MergePR("submit-queue") sq.SetMergeStatus(obj, merged) return } if err := obj.WriteComment(verifySafeToMergeBody); err != nil { glog.Errorf("%d: unknown err: %v", *obj.Issue.Number, err) sq.SetMergeStatus(obj, unknown) return } // Wait for the build to start sq.SetMergeStatus(obj, ghE2EWaitingStart) err = obj.WaitForPending([]string{sq.E2EStatusContext, sq.UnitStatusContext}) if err != nil { s := fmt.Sprintf("Failed waiting for PR to start testing: %v", err) sq.SetMergeStatus(obj, s) return } // Wait for the status to go back to something other than pending sq.SetMergeStatus(obj, ghE2ERunning) err = obj.WaitForNotPending([]string{sq.E2EStatusContext, sq.UnitStatusContext}) if err != nil { s := fmt.Sprintf("Failed waiting for PR to finish testing: %v", err) sq.SetMergeStatus(obj, s) return } // Check if the thing we care about is success if ok := obj.IsStatusSuccess([]string{sq.E2EStatusContext, sq.UnitStatusContext}); !ok { sq.SetMergeStatus(obj, ghE2EFailed) return } if !sq.e2eStable() { sq.SetMergeStatus(obj, e2eFailure) return } obj.MergePR("submit-queue") sq.updateMergeRate() sq.SetMergeStatus(obj, merged) return }
// Munge is unused by this munger. func (cla *ClaMunger) Munge(obj *githubhelper.MungeObject) { if !obj.IsPR() { return } if obj.HasLabel(claHumanLabel) { return } status := obj.GetStatusState([]string{cla.CLAStatusContext}) // Check for pending status and exit. if status == contextPending { // do nothing and wait for state to be updated. return } if status == contextSuccess { if obj.HasLabel(cncfClaYesLabel) { // status is success and we've already applied 'cncf-cla: yes'. return } if obj.HasLabel(cncfClaNoLabel) { obj.RemoveLabel(cncfClaNoLabel) } obj.AddLabel(cncfClaYesLabel) return } // If we are here, that means that the context is failure/error. comments, err := obj.ListComments() if err != nil { glog.Error(err) return } who := mungerutil.GetIssueUsers(obj.Issue).Author.Mention().Join() // Get a notification if it's time to ping. notif := cla.pinger.PingNotification( comments, who, nil, ) if notif != nil { obj.WriteComment(notif.String()) } if obj.HasLabel(cncfClaNoLabel) { // status reported error/failure and we've already applied 'cncf-cla: no' label. return } if obj.HasLabel(cncfClaYesLabel) { obj.RemoveLabel(cncfClaYesLabel) } obj.AddLabel(cncfClaNoLabel) }
func closePullRequest(obj *github.MungeObject, inactiveFor time.Duration) { comment := findLatestWarningComment(obj) if comment != nil { obj.DeleteComment(comment) } obj.WriteComment(fmt.Sprintf(closingComment, durationToDays(inactiveFor))) obj.ClosePR() }
func handleFound(obj *github.MungeObject, gitMsg []byte, branch string) error { msg := string(gitMsg) o := strings.SplitN(msg, "\n", 2) sha := o[0] msg = fmt.Sprintf("Commit %s found in the %q branch appears to be this PR. Removing the %q label. If this s an error find help to get your PR picked.", sha, branch, cpCandidateLabel) obj.WriteComment(msg) obj.RemoveLabel(cpCandidateLabel) return nil }
// Munge is the workhorse the will actually make updates to the PR func (OkToTestMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel(lgtmLabel) { return } state := obj.GetStatusState(requiredContexts) if state == "incomplete" { glog.V(2).Infof("status is incomplete, adding ok to test") obj.WriteComment(okToTestBody) } }
func closePullRequest(obj *github.MungeObject, inactiveFor time.Duration) { mention := mungerutil.GetIssueUsers(obj.Issue).AllUsers().Mention().Join() if mention != "" { mention = "cc " + mention + "\n" } comment := findLatestWarningComment(obj) if comment != nil { obj.DeleteComment(comment) } obj.WriteComment(fmt.Sprintf(closingComment, durationToDays(inactiveFor), mention)) obj.ClosePR() }
func postWarningComment(obj *github.MungeObject, inactiveFor time.Duration, closeIn time.Duration) { mention := mungerutil.GetIssueUsers(obj.Issue).AllUsers().Mention().Join() if mention != "" { mention = "cc " + mention + "\n" } closeDate := time.Now().Add(closeIn).Format("Jan 2, 2006") obj.WriteComment(fmt.Sprintf( warningComment, durationToDays(inactiveFor), durationToDays(closeIn), closeDate, mention, )) }
// Munge is the workhorse the will actually make updates to the PR func (PickMustHaveMilestone) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel(cpCandidateLabel) { return } releaseMilestone := obj.ReleaseMilestone() hasLabel := obj.HasLabel(cpCandidateLabel) if hasLabel && releaseMilestone == "" { obj.WriteComment(pickMustHaveMilestoneBody) obj.RemoveLabel(cpCandidateLabel) } }
// Munge is the workhorse the will actually make updates to the PR func (OkToTestMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel("lgtm") { return } state := obj.GetStatusState([]string{"Jenkins GCE e2e"}) if state == "incomplete" { glog.V(2).Infof("status is incomplete, adding ok to test") msg := `@k8s-bot ok to test pr builder appears to be missing, activating due to 'lgtm' label.` obj.WriteComment(msg) } }
// Munge is the workhorse the will actually make updates to the PR func (PickMustHaveMilestone) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if !obj.HasLabel(cpCandidateLabel) { return } releaseMilestone := obj.ReleaseMilestone() hasLabel := obj.HasLabel(cpCandidateLabel) if hasLabel && releaseMilestone == "" { msg := fmt.Sprintf("Removing label `%s` because no release milestone was set. This is an invalid state and thus this PR is not being considered for cherry-pick to any release branch. Please add an appropriate release milestone and then re-add the label.", cpCandidateLabel) obj.WriteComment(msg) obj.RemoveLabel(cpCandidateLabel) } }
// Munge is unused by this munger. func (c *ClaMunger) Munge(obj *githubhelper.MungeObject) { if !obj.IsPR() { return } if obj.HasLabel(claHumanLabel) { return } status := obj.GetStatusState([]string{c.CLAStatusContext}) // Check for pending status and exit. if status == contextPending { // do nothing and wait for state to be updated. return } if status == contextSuccess { if obj.HasLabel(cncfClaYesLabel) { // status is success and we've already applied 'cncf-cla: yes'. return } if obj.HasLabel(cncfClaNoLabel) { obj.RemoveLabel(cncfClaNoLabel) } obj.AddLabel(cncfClaYesLabel) return } if obj.HasLabel(cncfClaNoLabel) { // status reported error/failure and we've already applied 'cncf-cla: no' label. return } // Write comment and then modify the labels. err := obj.WriteComment(fmt.Sprint(cncfclaNotFoundMessage, mungerutil.GetIssueUsers(obj.Issue).Author.Mention().Join())) if err != nil { return } if obj.HasLabel(cncfClaYesLabel) { obj.RemoveLabel(cncfClaYesLabel) } obj.AddLabel(cncfClaNoLabel) }
// Munge is the workhorse the will actually make updates to the PR func (LabelUnapprovedPicks) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if obj.IsForBranch("master") { return } if obj.HasLabel(cpApprovedLabel) { return } obj.AddLabel(doNotMergeLabel) msg := fmt.Sprintf("This PR is not for the master branch but does not have the `%s` label. Adding the `%s` label.", cpApprovedLabel, doNotMergeLabel) obj.WriteComment(msg) }
// updateIssue adds a comment about the item to the github object. func (s *IssueSyncer) updateIssue(obj *github.MungeObject, source IssueSource) error { body := source.Body(false) id := source.ID() if !strings.Contains(body, source.ID()) { // prevent making tons of duplicate comments panic(fmt.Errorf("Programmer error: %v does not contain %v!", body, id)) } glog.Infof("Updating issue %v with item %v", *obj.Issue.Number, source.ID()) if err := obj.WriteComment(body); err != nil { return err } p, err := source.Priority(obj) if err != nil { return err } return s.syncPriority(obj, p) }
// Munge is the workhorse the will actually make updates to the PR func (LabelUnapprovedPicks) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if obj.IsForBranch("master") { return } if obj.HasLabel(cpApprovedLabel) { return } if obj.HasLabel(doNotMergeLabel) { return } obj.AddLabel(doNotMergeLabel) obj.WriteComment(labelUnapprovedBody) }
// Munge is the workhorse the will actually make updates to the PR func (r *ReleaseNoteLabel) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } if releaseNoteAlreadyAdded(obj) { r.ensureNoRelNoteNeededLabel(obj) return } if !r.prMustFollowRelNoteProcess(obj) { r.ensureNoRelNoteNeededLabel(obj) return } labelToAdd := determineReleaseNoteLabel(obj) if labelToAdd != releaseNoteLabelNeeded { //going to apply some other release-note-label if obj.HasLabel(releaseNoteLabelNeeded) { obj.RemoveLabel(releaseNoteLabelNeeded) } obj.AddLabel(labelToAdd) return } if !obj.HasLabel(releaseNoteLabelNeeded) { obj.AddLabel(releaseNoteLabelNeeded) } if !obj.HasLabel(lgtmLabel) { return } if obj.HasLabel(doNotMergeLabel) { return } obj.WriteComment(releaseNoteBody) obj.AddLabel(doNotMergeLabel) }
// Munge is the workhorse the will actually make updates to the PR func (NeedsRebaseMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } mergeable, err := obj.IsMergeable() if err != nil { glog.V(2).Infof("Skipping %d - problem determining mergeable", *obj.Issue.Number) return } if mergeable && obj.HasLabel(needsRebase) { obj.RemoveLabel(needsRebase) } if !mergeable && !obj.HasLabel(needsRebase) { obj.AddLabels([]string{needsRebase}) body := "PR needs rebase" if err := obj.WriteComment(body); err != nil { return } } }
// Munge is the workhorse the will actually make updates to the PR func (PingCIMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } // This munger only runs on certain branches, since travis/CI only listens // on certain branches validBranch := false for _, b := range validBranches { if obj.IsForBranch(b) { validBranch = true break } } if !validBranch { return } if !obj.HasLabel(lgtmLabel) { return } mergeable, err := obj.IsMergeable() if err != nil { glog.V(2).Infof("ping CI skipping %d - problem determining mergeability", *obj.Issue.Number) return } if !mergeable { glog.V(2).Infof("ping CI skipping %d - not mergeable", *obj.Issue.Number) return } if state := obj.GetStatusState([]string{travisContext}); state == "incomplete" { msg := "Travis continuous integration appears to have missed, closing and re-opening to trigger it" obj.WriteComment(msg) obj.ClosePR() time.Sleep(5 * time.Second) obj.OpenPR(10) } }
// Munge is the workhorse the will actually make updates to the PR func (RebuildMunger) Munge(obj *github.MungeObject) { if !obj.IsPR() { return } comments, err := obj.ListComments(*obj.Issue.Number) if err != nil { glog.Errorf("unexpected error getting comments: %v", err) } for ix := range comments { comment := &comments[ix] // Skip all robot comments for _, robot := range []string{"k8s-bot", "k8s-merge-robot", "googlebot"} { if *comment.User.Login == robot { glog.V(4).Infof("Skipping comment by robot %s: %s", robot, *comment.Body) continue } } if isRebuildComment(comment) && rebuildCommentMissingIssueNumber(comment) { if err := obj.DeleteComment(comment); err != nil { glog.Errorf("Error deleting comment: %v", err) continue } body := fmt.Sprintf(`@%s an issue is required for any manual rebuild. Expecting comment of the form 'github issue: #<number>' [Open test flakes](https://github.com/kubernetes/kubernetes/issues?q=is%3Aissue%20label%3Akind%2Fflake)`, *comment.User.Login) err := obj.WriteComment(body) if err != nil { glog.Errorf("unexpected error adding comment: %v", err) continue } if obj.HasLabel("lgtm") { if err := obj.RemoveLabel("lgtm"); err != nil { glog.Errorf("unexpected error removing lgtm label: %v", err) } } } } }