@@ -43,26 +43,28 @@ import (
4343)
4444
4545const (
46- lgtmLabel = "lgtm"
47- e2eNotRequiredLabel = "e2e -not-required"
48- doNotMergeLabel = "do-not-merge"
49- claYesLabel = "cla: yes"
50- claHumanLabel = "cla: human-approved"
46+ lgtmLabel = "lgtm"
47+ retestNotRequiredLabel = "retest -not-required"
48+ doNotMergeLabel = "do-not-merge"
49+ claYesLabel = "cla: yes"
50+ claHumanLabel = "cla: human-approved"
5151
5252 jenkinsE2EContext = "Jenkins GCE e2e"
5353 jenkinsUnitContext = "Jenkins unit/integration"
54+ jenkinsNodeContext = "Jenkins GCE Node e2e "
5455 travisContext = "continuous-integration/travis-ci/pr"
5556 sqContext = "Submit Queue"
5657
57- e2eNotRequiredMergePriority = - 1 // used for e2eNotRequiredLabel
58- defaultMergePriority = 3 // when an issue is unlabeled
58+ retestNotRequiredMergePriority = - 1 // used for retestNotRequiredLabel
59+ defaultMergePriority = 3 // when an issue is unlabeled
5960
6061 githubE2EPollTime = 30 * time .Second
6162)
6263
6364var (
64- _ = fmt .Print
65- verifySafeToMergeBody = fmt .Sprintf ("@%s test this [submit-queue is verifying that this PR is safe to merge]" , jenkinsBotName )
65+ _ = fmt .Print
66+ // This MUST cause a RETEST of everything in the sq.RequiredRetestContexts
67+ retestBody = fmt .Sprintf ("@%s test this [submit-queue is verifying that this PR is safe to merge]" , jenkinsBotName )
6668)
6769
6870type submitStatus struct {
@@ -146,11 +148,12 @@ type SubmitQueue struct {
146148 FakeE2E bool
147149
148150 Committers string
149- E2EStatusContext string
150- UnitStatusContext string
151151 RequiredStatusContexts []string
152152 doNotMergeMilestones []string
153153
154+ RequiredRetestContexts []string
155+ retestBody string
156+
154157 sync.Mutex
155158 lastPRStatus map [string ]submitStatus
156159 prStatus map [string ]submitStatus // protected by sync.Mutex
@@ -281,6 +284,16 @@ func (sq *SubmitQueue) calcMergeRateWithTail() float64 {
281284 return calcMergeRate (sq .mergeRate , sq .lastMergeTime , now )
282285}
283286
287+ // Given a string slice with a single empty value this function will return a empty slice.
288+ // This is extremely useful for StringSlice flags, so the user can do --flag="" and instead
289+ // of getting []string{""} they will get []string{}
290+ func cleanStringSlice (in []string ) []string {
291+ if len (in ) == 1 && len (in [0 ]) == 0 {
292+ return []string {}
293+ }
294+ return in
295+ }
296+
284297// Initialize will initialize the munger
285298func (sq * SubmitQueue ) Initialize (config * github.Config , features * features.Features ) error {
286299 return sq .internalInitialize (config , features , "" )
@@ -292,6 +305,13 @@ func (sq *SubmitQueue) internalInitialize(config *github.Config, features *featu
292305 sq .Lock ()
293306 defer sq .Unlock ()
294307
308+ // Clean up all of our flags which we wish --flag="" to mean []string{}
309+ sq .JobNames = cleanStringSlice (sq .JobNames )
310+ sq .WeakStableJobNames = cleanStringSlice (sq .WeakStableJobNames )
311+ sq .RequiredStatusContexts = cleanStringSlice (sq .RequiredStatusContexts )
312+ sq .RequiredRetestContexts = cleanStringSlice (sq .RequiredRetestContexts )
313+ sq .doNotMergeMilestones = cleanStringSlice (sq .doNotMergeMilestones )
314+
295315 sq .githubConfig = config
296316
297317 if sq .FakeE2E {
@@ -322,7 +342,6 @@ func (sq *SubmitQueue) internalInitialize(config *github.Config, features *featu
322342 http .Handle ("/prs" , gziphandler .GzipHandler (http .HandlerFunc (sq .servePRs )))
323343 http .Handle ("/history" , gziphandler .GzipHandler (http .HandlerFunc (sq .serveHistory )))
324344 http .Handle ("/users" , gziphandler .GzipHandler (http .HandlerFunc (sq .serveUsers )))
325- http .Handle ("/github-e2e-queue" , gziphandler .GzipHandler (http .HandlerFunc (sq .serveGithubE2EStatus )))
326345 http .Handle ("/google-internal-ci" , gziphandler .GzipHandler (http .HandlerFunc (sq .serveGoogleInternalStatus )))
327346 http .Handle ("/merge-info" , gziphandler .GzipHandler (http .HandlerFunc (sq .serveMergeInfo )))
328347 http .Handle ("/priority-info" , gziphandler .GzipHandler (http .HandlerFunc (sq .servePriorityInfo )))
@@ -383,10 +402,11 @@ func (sq *SubmitQueue) AddFlags(cmd *cobra.Command, config *github.Config) {
383402 []string {},
384403 "Comma separated list of jobs in Jenkins to use for stability testing that needs only weak success" )
385404 cmd .Flags ().StringSliceVar (& sq .RequiredStatusContexts , "required-contexts" , []string {}, "Comma separate list of status contexts required for a PR to be considered ok to merge" )
386- cmd .Flags ().StringVar (& sq .E2EStatusContext , "e2e-status-context " , jenkinsE2EContext , "The name of the github status context for the e2e PR Builder " )
387- cmd .Flags ().StringVar (& sq .UnitStatusContext , "unit-status-context " , jenkinsUnitContext , "The name of the github status context for the unit PR Builder " )
405+ cmd .Flags ().StringSliceVar (& sq .RequiredRetestContexts , "required-retest-contexts " , [] string { jenkinsE2EContext , jenkinsUnitContext , jenkinsNodeContext }, "Comma separate list of statuses which will be retested and which must come back green after the `retest-body` comment is posted to a PR " )
406+ cmd .Flags ().StringVar (& sq .retestBody , "retest-body " , retestBody , "message which, when posted to the PR, will cause ALL `required-retest-contexts` to be re-tested " )
388407 cmd .Flags ().BoolVar (& sq .FakeE2E , "fake-e2e" , false , "Whether to use a fake for testing E2E stability." )
389408 cmd .Flags ().StringSliceVar (& sq .doNotMergeMilestones , "do-not-merge-milestones" , []string {}, "List of milestones which, when applied, will cause the PR to not be merged" )
409+ // If you create a StringSliceVar you may wish to check out 'cleanStringSliceVar()'
390410}
391411
392412// Hold the lock
@@ -513,8 +533,8 @@ func objToStatusPullRequest(obj *github.MungeObject) *statusPullRequest {
513533 if ! ok {
514534 var prio string
515535 p := priority (obj )
516- if p == e2eNotRequiredMergePriority {
517- prio = e2eNotRequiredLabel
536+ if p == retestNotRequiredMergePriority {
537+ prio = retestNotRequiredLabel
518538 } else {
519539 prio = fmt .Sprintf ("P%d" , p ) // store it a P1, P2, P3. Not just 1,2,3
520540 }
@@ -695,17 +715,6 @@ const (
695715 unmergeableMilestone = "Milestone is for a future release and cannot be merged"
696716)
697717
698- func (sq * SubmitQueue ) requiredStatusContexts (obj * github.MungeObject ) []string {
699- contexts := sq .RequiredStatusContexts
700- if len (sq .E2EStatusContext ) > 0 && ! obj .HasLabel (e2eNotRequiredLabel ) {
701- contexts = append (contexts , sq .E2EStatusContext )
702- }
703- if len (sq .UnitStatusContext ) > 0 {
704- contexts = append (contexts , sq .UnitStatusContext )
705- }
706- return contexts
707- }
708-
709718// validForMerge is the base logic about what PR can be automatically merged.
710719// PRs must pass this logic to be placed on the queue and they must pass this
711720// logic a second time to be retested/merged after they get to the top of
@@ -758,8 +767,11 @@ func (sq *SubmitQueue) validForMerge(obj *github.MungeObject) bool {
758767 }
759768
760769 // Validate the status information for this PR
761- contexts := sq .requiredStatusContexts (obj )
762- if ok := obj .IsStatusSuccess (contexts ); ! ok {
770+ if ok := obj .IsStatusSuccess (sq .RequiredStatusContexts ); ! ok {
771+ sq .SetMergeStatus (obj , ciFailure )
772+ return false
773+ }
774+ if ok := obj .IsStatusSuccess (sq .RequiredRetestContexts ); ! ok {
763775 sq .SetMergeStatus (obj , ciFailure )
764776 return false
765777 }
@@ -848,8 +860,8 @@ func (sq *SubmitQueue) cleanupOldE2E(obj *github.MungeObject, reason string) {
848860
849861func priority (obj * github.MungeObject ) int {
850862 // jump to the front of the queue if you don't need retested
851- if obj .HasLabel (e2eNotRequiredLabel ) {
852- return e2eNotRequiredMergePriority
863+ if obj .HasLabel (retestNotRequiredLabel ) {
864+ return retestNotRequiredMergePriority
853865 }
854866
855867 prio := obj .Priority ()
@@ -1009,7 +1021,7 @@ func (sq *SubmitQueue) doGithubE2EAndMerge(obj *github.MungeObject) bool {
10091021 return true
10101022 }
10111023
1012- if obj .HasLabel (e2eNotRequiredLabel ) {
1024+ if obj .HasLabel (retestNotRequiredLabel ) {
10131025 // Do not update mergeRate when we don't do e2e tests
10141026 sq .mergePullRequest (obj )
10151027 return true
@@ -1024,30 +1036,30 @@ func (sq *SubmitQueue) doGithubE2EAndMerge(obj *github.MungeObject) bool {
10241036 glog .Infof ("Skipping retest since head and base sha match previous attempt!" )
10251037 atomic .AddInt32 (& sq .retestsAvoided , 1 )
10261038 } else {
1027- if err := obj .WriteComment (verifySafeToMergeBody ); err != nil {
1039+ if err := obj .WriteComment (retestBody ); err != nil {
10281040 glog .Errorf ("%d: unknown err: %v" , * obj .Issue .Number , err )
10291041 sq .SetMergeStatus (obj , unknown )
10301042 return true
10311043 }
10321044
1033- // Wait for the build to start
1045+ // Wait for the retest to start
10341046 sq .SetMergeStatus (obj , ghE2EWaitingStart )
1035- err = obj .WaitForPending ([] string { sq .E2EStatusContext , sq . UnitStatusContext } )
1047+ err = obj .WaitForPending (sq .RequiredRetestContexts )
10361048 if err != nil {
10371049 sq .SetMergeStatus (obj , fmt .Sprintf ("Failed waiting for PR to start testing: %v" , err ))
10381050 return true
10391051 }
10401052
10411053 // Wait for the status to go back to something other than pending
10421054 sq .SetMergeStatus (obj , ghE2ERunning )
1043- err = obj .WaitForNotPending ([] string { sq .E2EStatusContext , sq . UnitStatusContext } )
1055+ err = obj .WaitForNotPending (sq .RequiredRetestContexts )
10441056 if err != nil {
10451057 sq .SetMergeStatus (obj , fmt .Sprintf ("Failed waiting for PR to finish testing: %v" , err ))
10461058 return true
10471059 }
10481060
10491061 // Check if the thing we care about is success
1050- if ok := obj .IsStatusSuccess ([] string { sq .E2EStatusContext , sq . UnitStatusContext } ); ! ok {
1062+ if ok := obj .IsStatusSuccess (sq .RequiredRetestContexts ); ! ok {
10511063 sq .SetMergeStatus (obj , ghE2EFailed )
10521064 return true
10531065 }
@@ -1131,23 +1143,16 @@ func (sq *SubmitQueue) serveMergeInfo(res http.ResponseWriter, req *http.Request
11311143 out .WriteString ("<ol>" )
11321144 out .WriteString (fmt .Sprintf ("<li>The PR must have the label %q or %q</li>" , claYesLabel , claHumanLabel ))
11331145 out .WriteString ("<li>The PR must be mergeable. aka cannot need a rebase</li>" )
1134- contexts := sq .RequiredStatusContexts
1135- exceptStr := ""
1136- if len (sq .E2EStatusContext ) > 0 {
1137- contexts = append (contexts , sq .E2EStatusContext )
1138- exceptStr = fmt .Sprintf ("Note: %q is not required if the PR has the %q label" , sq .E2EStatusContext , e2eNotRequiredLabel )
1139- }
1140- if len (sq .UnitStatusContext ) > 0 {
1141- contexts = append (contexts , sq .UnitStatusContext )
1142- }
1143- if len (contexts ) > 0 {
1146+ if len (sq .RequiredStatusContexts ) > 0 || len (sq .RequiredRetestContexts ) > 0 {
11441147 out .WriteString ("<li>All of the following github statuses must be green" )
11451148 out .WriteString ("<ul>" )
1146- for _ , context := range contexts {
1149+ for _ , context := range sq .RequiredStatusContexts {
1150+ out .WriteString (fmt .Sprintf ("<li>%s</li>" , context ))
1151+ }
1152+ for _ , context := range sq .RequiredRetestContexts {
11471153 out .WriteString (fmt .Sprintf ("<li>%s</li>" , context ))
11481154 }
11491155 out .WriteString ("</ul>" )
1150- out .WriteString (fmt .Sprintf ("%s</li>" , exceptStr ))
11511156 }
11521157 out .WriteString (fmt .Sprintf ("<li>The PR cannot have any of the following milestones: %q</li>" , sq .doNotMergeMilestones ))
11531158 out .WriteString (fmt .Sprintf (`<li>The PR must have the %q label</li>` , lgtmLabel ))
@@ -1157,8 +1162,15 @@ func (sq *SubmitQueue) serveMergeInfo(res http.ResponseWriter, req *http.Request
11571162 out .WriteString ("The PR can then be queued to re-test before merge. Once it reaches the top of the queue all of the above conditions must be true but so must the following:" )
11581163 out .WriteString ("<ol>" )
11591164 out .WriteString (fmt .Sprintf ("<li>All of the <a href=http://submit-queue.k8s.io/#/e2e>continuously running e2e tests</a> must be passing</li>" ))
1160- out .WriteString (fmt .Sprintf ("<li>The %s tests must pass a second time<br>" , sq .E2EStatusContext ))
1161- out .WriteString (fmt .Sprintf ("Note: The %s tests are not required if the %q label is present</li>" , sq .E2EStatusContext , e2eNotRequiredLabel ))
1165+ if len (sq .RequiredRetestContexts ) > 0 {
1166+ out .WriteString ("<li>All of the following tests must pass a second time" )
1167+ out .WriteString ("<ul>" )
1168+ for _ , context := range sq .RequiredRetestContexts {
1169+ out .WriteString (fmt .Sprintf ("<li>%s</li>" , context ))
1170+ }
1171+ out .WriteString ("</ul>" )
1172+ out .WriteString (fmt .Sprintf ("Unless the %q label is present</li>" , retestNotRequiredLabel ))
1173+ }
11621174 out .WriteString ("</ol>" )
11631175 out .WriteString ("And then the PR will be merged!!" )
11641176 res .Write (out .Bytes ())
@@ -1174,7 +1186,7 @@ func (sq *SubmitQueue) servePriorityInfo(res http.ResponseWriter, req *http.Requ
11741186 <li>Determined by a label of the form 'priority/pX'
11751187 <li>P0 -> P1 -> P2</li>
11761188 <li>A PR with no priority label is considered equal to a P3</li>
1177- <li>A PR with the '` + e2eNotRequiredLabel + `' label will come first, before even P0</li>
1189+ <li>A PR with the '` + retestNotRequiredLabel + `' label will come first, before even P0</li>
11781190 </ul>
11791191 </li>
11801192 <li>Release milestone due date
@@ -1192,7 +1204,7 @@ func (sq *SubmitQueue) isStaleComment(obj *github.MungeObject, comment githubapi
11921204 if ! mergeBotComment (comment ) {
11931205 return false
11941206 }
1195- if * comment .Body != verifySafeToMergeBody {
1207+ if * comment .Body != retestBody {
11961208 return false
11971209 }
11981210 stale := commentBeforeLastCI (obj , comment )
0 commit comments