github.com/abayer/test-infra@v0.0.5/prow/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  	"k8s.io/test-infra/prow/github"
    30  	"k8s.io/test-infra/prow/pluginhelp"
    31  	"k8s.io/test-infra/prow/plugins"
    32  )
    33  
    34  const pluginName = "hold"
    35  
    36  var (
    37  	label         = "do-not-merge/hold"
    38  	labelRe       = regexp.MustCompile(`(?mi)^/hold\s*$`)
    39  	labelCancelRe = regexp.MustCompile(`(?mi)^/hold cancel\s*$`)
    40  )
    41  
    42  type hasLabelFunc func(label string, issueLabels []github.Label) bool
    43  
    44  func init() {
    45  	plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider)
    46  }
    47  
    48  func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) {
    49  	// The Config field is omitted because this plugin is not configurable.
    50  	pluginHelp := &pluginhelp.PluginHelp{
    51  		Description: "The hold plugin allows anyone to add or remove the '" + label + "' label from a pull request in order to temporarily prevent the PR from merging without withholding approval.",
    52  	}
    53  	pluginHelp.AddCommand(pluginhelp.Command{
    54  		Usage:       "/hold [cancel]",
    55  		Description: "Adds or removes the `" + label + "` label which is used to indicate that the PR should not be automatically merged.",
    56  		Featured:    false,
    57  		WhoCanUse:   "Anyone can use the /hold command to add or remove the '" + label + "' label.",
    58  		Examples:    []string{"/hold", "/hold cancel"},
    59  	})
    60  	return pluginHelp, nil
    61  }
    62  
    63  type githubClient interface {
    64  	AddLabel(owner, repo string, number int, label string) error
    65  	RemoveLabel(owner, repo string, number int, label string) error
    66  	GetIssueLabels(org, repo string, number int) ([]github.Label, error)
    67  }
    68  
    69  func handleGenericComment(pc plugins.PluginClient, e github.GenericCommentEvent) error {
    70  	hasLabel := func(label string, labels []github.Label) bool {
    71  		return github.HasLabel(label, labels)
    72  	}
    73  	return handle(pc.GitHubClient, pc.Logger, &e, hasLabel)
    74  }
    75  
    76  // handle drives the pull request to the desired state. If any user adds
    77  // a /hold directive, we want to add a label if one does not already exist.
    78  // If they add /hold cancel, we want to remove the label if it exists.
    79  func handle(gc githubClient, log *logrus.Entry, e *github.GenericCommentEvent, f hasLabelFunc) error {
    80  	if e.Action != github.GenericCommentActionCreated {
    81  		return nil
    82  	}
    83  	needsLabel := false
    84  	if labelRe.MatchString(e.Body) {
    85  		needsLabel = true
    86  	} else if labelCancelRe.MatchString(e.Body) {
    87  		needsLabel = false
    88  	} else {
    89  		return nil
    90  	}
    91  
    92  	org := e.Repo.Owner.Login
    93  	repo := e.Repo.Name
    94  	labels, err := gc.GetIssueLabels(org, repo, e.Number)
    95  	if err != nil {
    96  		return fmt.Errorf("failed to get the labels on %s/%s#%d: %v", org, repo, e.Number, err)
    97  	}
    98  
    99  	hasLabel := f(label, labels)
   100  	if hasLabel && !needsLabel {
   101  		log.Info("Removing %q label for %s/%s#%d", label, org, repo, e.Number)
   102  		return gc.RemoveLabel(org, repo, e.Number, label)
   103  	} else if !hasLabel && needsLabel {
   104  		log.Info("Adding %q label for %s/%s#%d", label, org, repo, e.Number)
   105  		return gc.AddLabel(org, repo, e.Number, label)
   106  	}
   107  	return nil
   108  }