github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/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  }