github.com/abayer/test-infra@v0.0.5/prow/plugins/lifecycle/lifecycle.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 lifecycle
    18  
    19  import (
    20  	"regexp"
    21  
    22  	"github.com/sirupsen/logrus"
    23  
    24  	"k8s.io/test-infra/prow/github"
    25  	"k8s.io/test-infra/prow/pluginhelp"
    26  	"k8s.io/test-infra/prow/plugins"
    27  )
    28  
    29  var (
    30  	lifecycleActiveLabel = "lifecycle/active"
    31  	lifecycleFrozenLabel = "lifecycle/frozen"
    32  	lifecycleStaleLabel  = "lifecycle/stale"
    33  	lifecycleRottenLabel = "lifecycle/rotten"
    34  	lifecycleLabels      = []string{lifecycleActiveLabel, lifecycleFrozenLabel, lifecycleStaleLabel, lifecycleRottenLabel}
    35  	lifecycleRe          = regexp.MustCompile(`(?mi)^/(remove-)?lifecycle (active|frozen|stale|rotten)\s*$`)
    36  )
    37  
    38  func init() {
    39  	plugins.RegisterGenericCommentHandler("lifecycle", lifecycleHandleGenericComment, help)
    40  }
    41  
    42  func help(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) {
    43  	pluginHelp := &pluginhelp.PluginHelp{
    44  		Description: "Close, reopen, flag and/or unflag an issue or PR as frozen/stale/rotten",
    45  	}
    46  	pluginHelp.AddCommand(pluginhelp.Command{
    47  		Usage:       "/close",
    48  		Description: "Closes an issue or PR.",
    49  		Featured:    false,
    50  		WhoCanUse:   "Authors and assignees can triggers this command.",
    51  		Examples:    []string{"/close"},
    52  	})
    53  	pluginHelp.AddCommand(pluginhelp.Command{
    54  		Usage:       "/reopen",
    55  		Description: "Reopens an issue or PR",
    56  		Featured:    false,
    57  		WhoCanUse:   "Authors and assignees can trigger this command.",
    58  		Examples:    []string{"/reopen"},
    59  	})
    60  	pluginHelp.AddCommand(pluginhelp.Command{
    61  		Usage:       "/[remove-]lifecycle <frozen|stale|rotten>",
    62  		Description: "Flags an issue or PR as frozen/stale/rotten",
    63  		Featured:    false,
    64  		WhoCanUse:   "Anyone can trigger this command.",
    65  		Examples:    []string{"/lifecycle frozen", "/remove-lifecycle stale"},
    66  	})
    67  	return pluginHelp, nil
    68  }
    69  
    70  type lifecycleClient interface {
    71  	AddLabel(owner, repo string, number int, label string) error
    72  	RemoveLabel(owner, repo string, number int, label string) error
    73  	GetIssueLabels(org, repo string, number int) ([]github.Label, error)
    74  }
    75  
    76  func lifecycleHandleGenericComment(pc plugins.PluginClient, e github.GenericCommentEvent) error {
    77  	gc := pc.GitHubClient
    78  	log := pc.Logger
    79  	if err := handleReopen(gc, log, &e); err != nil {
    80  		return err
    81  	}
    82  	if err := handleClose(gc, log, &e); err != nil {
    83  		return err
    84  	}
    85  	return handle(gc, log, &e)
    86  }
    87  
    88  func handle(gc lifecycleClient, log *logrus.Entry, e *github.GenericCommentEvent) error {
    89  	// Only consider new comments.
    90  	if e.Action != github.GenericCommentActionCreated {
    91  		return nil
    92  	}
    93  
    94  	for _, mat := range lifecycleRe.FindAllStringSubmatch(e.Body, -1) {
    95  		if err := handleOne(gc, log, e, mat); err != nil {
    96  			return err
    97  		}
    98  	}
    99  	return nil
   100  }
   101  
   102  func handleOne(gc lifecycleClient, log *logrus.Entry, e *github.GenericCommentEvent, mat []string) error {
   103  	org := e.Repo.Owner.Login
   104  	repo := e.Repo.Name
   105  	number := e.Number
   106  
   107  	remove := mat[1] != ""
   108  	cmd := mat[2]
   109  	lbl := "lifecycle/" + cmd
   110  
   111  	// Let's start simple and allow anyone to add/remove frozen, stale, rotten labels.
   112  	// Adjust if we find evidence of the community abusing these labels.
   113  	labels, err := gc.GetIssueLabels(org, repo, number)
   114  	if err != nil {
   115  		log.WithError(err).Errorf("Failed to get labels.")
   116  	}
   117  
   118  	// If the label exists and we asked for it to be removed, remove it.
   119  	if github.HasLabel(lbl, labels) && remove {
   120  		return gc.RemoveLabel(org, repo, number, lbl)
   121  	}
   122  
   123  	// If the label does not exist and we asked for it to be added,
   124  	// remove other existing lifecycle labels and add it.
   125  	if !github.HasLabel(lbl, labels) && !remove {
   126  		for _, label := range lifecycleLabels {
   127  			if label != lbl && github.HasLabel(label, labels) {
   128  				if err := gc.RemoveLabel(org, repo, number, label); err != nil {
   129  					log.WithError(err).Errorf("Github failed to remove the following label: %s", label)
   130  				}
   131  			}
   132  		}
   133  
   134  		if err := gc.AddLabel(org, repo, number, lbl); err != nil {
   135  			log.WithError(err).Errorf("Github failed to add the following label: %s", lbl)
   136  		}
   137  	}
   138  
   139  	return nil
   140  }