sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/hold/hold.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package hold contains a plugin which will allow users to label their 18 // own pull requests as not ready or ready for merge. The submit queue 19 // will honor the label to ensure pull requests do not merge when it is 20 // applied. 21 package hold 22 23 import ( 24 "fmt" 25 "regexp" 26 27 "github.com/sirupsen/logrus" 28 29 "sigs.k8s.io/prow/pkg/config" 30 "sigs.k8s.io/prow/pkg/github" 31 "sigs.k8s.io/prow/pkg/labels" 32 "sigs.k8s.io/prow/pkg/pluginhelp" 33 "sigs.k8s.io/prow/pkg/plugins" 34 ) 35 36 const ( 37 // PluginName defines this plugin's registered name. 38 PluginName = "hold" 39 ) 40 41 var ( 42 labelRe = regexp.MustCompile(`(?mi)^/hold(\s.*)?$`) 43 labelCancelRe = regexp.MustCompile(`(?mi)^/(remove-hold|hold\s+cancel|unhold)\s*$`) 44 ) 45 46 type hasLabelFunc func(label string, issueLabels []github.Label) bool 47 48 func init() { 49 plugins.RegisterGenericCommentHandler(PluginName, handleGenericComment, helpProvider) 50 } 51 52 func helpProvider(config *plugins.Configuration, _ []config.OrgRepo) (*pluginhelp.PluginHelp, error) { 53 // The Config field is omitted because this plugin is not configurable. 54 pluginHelp := &pluginhelp.PluginHelp{ 55 Description: "The hold plugin allows anyone to add or remove the '" + labels.Hold + "' Label from a pull request in order to temporarily prevent the PR from merging without withholding approval.", 56 } 57 pluginHelp.AddCommand(pluginhelp.Command{ 58 Usage: "/[remove-][un]hold [cancel]", 59 Description: "Adds or removes the `" + labels.Hold + "` Label which is used to indicate that the PR should not be automatically merged.", 60 Featured: false, 61 WhoCanUse: "Anyone can use the /hold command to add or remove the '" + labels.Hold + "' Label.", 62 Examples: []string{"/hold", "/hold cancel", "/unhold", "/remove-hold"}, 63 }) 64 return pluginHelp, nil 65 } 66 67 type githubClient interface { 68 AddLabel(owner, repo string, number int, label string) error 69 RemoveLabel(owner, repo string, number int, label string) error 70 GetIssueLabels(org, repo string, number int) ([]github.Label, error) 71 } 72 73 func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error { 74 hasLabel := func(label string, labels []github.Label) bool { 75 return github.HasLabel(label, labels) 76 } 77 return handle(pc.GitHubClient, pc.Logger, &e, hasLabel) 78 } 79 80 // handle drives the pull request to the desired state. If any user adds 81 // a /hold directive, we want to add a label if one does not already exist. 82 // If they add /hold cancel, we want to remove the label if it exists. 83 func handle(gc githubClient, log *logrus.Entry, e *github.GenericCommentEvent, f hasLabelFunc) error { 84 if !e.IsPR { 85 return nil 86 } 87 if e.Action != github.GenericCommentActionCreated { 88 return nil 89 } 90 needsLabel := false 91 if labelCancelRe.MatchString(e.Body) { 92 needsLabel = false 93 } else if labelRe.MatchString(e.Body) { 94 needsLabel = true 95 } else { 96 return nil 97 } 98 99 org := e.Repo.Owner.Login 100 repo := e.Repo.Name 101 issueLabels, err := gc.GetIssueLabels(org, repo, e.Number) 102 if err != nil { 103 return fmt.Errorf("failed to get the labels on %s/%s#%d: %w", org, repo, e.Number, err) 104 } 105 106 hasLabel := f(labels.Hold, issueLabels) 107 if hasLabel && !needsLabel { 108 log.Infof("Removing %q Label for %s/%s#%d", labels.Hold, org, repo, e.Number) 109 return gc.RemoveLabel(org, repo, e.Number, labels.Hold) 110 } else if !hasLabel && needsLabel { 111 log.Infof("Adding %q Label for %s/%s#%d", labels.Hold, org, repo, e.Number) 112 return gc.AddLabel(org, repo, e.Number, labels.Hold) 113 } 114 return nil 115 }