github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/cherrypickunapproved/cherrypick-unapproved.go (about)

     1  /*
     2  Copyright 2018 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 cherrypickunapproved adds the `do-not-merge/cherry-pick-not-approved`
    18  // label to PRs against a release branch which do not have the
    19  // `cherry-pick-approved` label.
    20  package cherrypickunapproved
    21  
    22  import (
    23  	"fmt"
    24  	"regexp"
    25  	"strings"
    26  
    27  	"github.com/sirupsen/logrus"
    28  
    29  	"k8s.io/test-infra/prow/github"
    30  	"k8s.io/test-infra/prow/labels"
    31  	"k8s.io/test-infra/prow/pluginhelp"
    32  	"k8s.io/test-infra/prow/plugins"
    33  )
    34  
    35  const (
    36  	// PluginName defines this plugin's registered name.
    37  	PluginName = "cherry-pick-unapproved"
    38  )
    39  
    40  func init() {
    41  	plugins.RegisterPullRequestHandler(PluginName, handlePullRequest, helpProvider)
    42  }
    43  
    44  func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) {
    45  	// Only the 'Config' and Description' fields are necessary because this
    46  	// plugin does not react to any commands.
    47  	pluginHelp := &pluginhelp.PluginHelp{
    48  		Description: "Label PRs against a release branch which do not have the `cherry-pick-approved` label with the `do-not-merge/cherry-pick-not-approved` label.",
    49  		Config: map[string]string{
    50  			"": fmt.Sprintf(
    51  				"The cherry-pick-unapproved plugin treats PRs against branch names satisfying the regular expression `%s` as cherry-pick PRs and adds the following comment:\n%s",
    52  				config.CherryPickUnapproved.BranchRegexp,
    53  				config.CherryPickUnapproved.Comment,
    54  			),
    55  		},
    56  	}
    57  	return pluginHelp, nil
    58  }
    59  
    60  type githubClient interface {
    61  	CreateComment(owner, repo string, number int, comment string) error
    62  	AddLabel(owner, repo string, number int, label string) error
    63  	RemoveLabel(owner, repo string, number int, label string) error
    64  	GetIssueLabels(org, repo string, number int) ([]github.Label, error)
    65  }
    66  
    67  type commentPruner interface {
    68  	PruneComments(shouldPrune func(github.IssueComment) bool)
    69  }
    70  
    71  func handlePullRequest(pc plugins.Agent, pr github.PullRequestEvent) error {
    72  	cp, err := pc.CommentPruner()
    73  	if err != nil {
    74  		return err
    75  	}
    76  	return handlePR(
    77  		pc.GitHubClient, pc.Logger, &pr, cp,
    78  		pc.PluginConfig.CherryPickUnapproved.BranchRe, pc.PluginConfig.CherryPickUnapproved.Comment,
    79  	)
    80  }
    81  
    82  func handlePR(gc githubClient, log *logrus.Entry, pr *github.PullRequestEvent, cp commentPruner, branchRe *regexp.Regexp, commentBody string) error {
    83  	// Only consider the events that indicate opening of the PR and
    84  	// when the cpApproved and cpUnapproved labels are added or removed
    85  	cpLabelUpdated := (pr.Action == github.PullRequestActionLabeled || pr.Action == github.PullRequestActionUnlabeled) &&
    86  		(pr.Label.Name == labels.CpApproved || pr.Label.Name == labels.CpUnapproved)
    87  	if pr.Action != github.PullRequestActionOpened && pr.Action != github.PullRequestActionReopened && !cpLabelUpdated {
    88  		return nil
    89  	}
    90  
    91  	var (
    92  		org    = pr.Repo.Owner.Login
    93  		repo   = pr.Repo.Name
    94  		branch = pr.PullRequest.Base.Ref
    95  	)
    96  
    97  	// if the branch doesn't match against the branch names allowed for cherry-picks,
    98  	// don't do anything
    99  	if !branchRe.MatchString(branch) {
   100  		return nil
   101  	}
   102  
   103  	issueLabels, err := gc.GetIssueLabels(org, repo, pr.Number)
   104  	if err != nil {
   105  		return err
   106  	}
   107  	hasCherryPickApprovedLabel := github.HasLabel(labels.CpApproved, issueLabels)
   108  	hasCherryPickUnapprovedLabel := github.HasLabel(labels.CpUnapproved, issueLabels)
   109  
   110  	// if it has the approved label,
   111  	// remove the unapproved label (if it exists) and
   112  	// remove any comments left by this plugin
   113  	if hasCherryPickApprovedLabel {
   114  		if hasCherryPickUnapprovedLabel {
   115  			if err := gc.RemoveLabel(org, repo, pr.Number, labels.CpUnapproved); err != nil {
   116  				log.WithError(err).Errorf("Github failed to remove the following label: %s", labels.CpUnapproved)
   117  			}
   118  		}
   119  		cp.PruneComments(func(comment github.IssueComment) bool {
   120  			return strings.Contains(comment.Body, commentBody)
   121  		})
   122  		return nil
   123  	}
   124  
   125  	// if it already has the unapproved label, we are done here
   126  	if hasCherryPickUnapprovedLabel {
   127  		return nil
   128  	}
   129  
   130  	// only add the label and comment if none of the approved and unapproved labels are present
   131  	if err := gc.AddLabel(org, repo, pr.Number, labels.CpUnapproved); err != nil {
   132  		log.WithError(err).Errorf("Github failed to add the following label: %s", labels.CpUnapproved)
   133  	}
   134  
   135  	formattedComment := plugins.FormatSimpleResponse(pr.PullRequest.User.Login, commentBody)
   136  	if err := gc.CreateComment(org, repo, pr.Number, formattedComment); err != nil {
   137  		log.WithError(err).Errorf("Failed to comment %q", formattedComment)
   138  	}
   139  
   140  	return nil
   141  }