github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/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  	"fmt"
    21  	"regexp"
    22  
    23  	"github.com/sirupsen/logrus"
    24  
    25  	"sigs.k8s.io/prow/pkg/config"
    26  	"sigs.k8s.io/prow/pkg/github"
    27  	"sigs.k8s.io/prow/pkg/labels"
    28  	"sigs.k8s.io/prow/pkg/pluginhelp"
    29  	"sigs.k8s.io/prow/pkg/plugins"
    30  )
    31  
    32  var (
    33  	lifecycleLabels = []string{labels.LifecycleActive, labels.LifecycleFrozen, labels.LifecycleStale, labels.LifecycleRotten}
    34  	lifecycleRe     = regexp.MustCompile(`(?mi)^/(remove-)?lifecycle (active|frozen|stale|rotten)\s*$`)
    35  )
    36  
    37  func init() {
    38  	plugins.RegisterGenericCommentHandler("lifecycle", lifecycleHandleGenericComment, help)
    39  }
    40  
    41  func help(config *plugins.Configuration, _ []config.OrgRepo) (*pluginhelp.PluginHelp, error) {
    42  	pluginHelp := &pluginhelp.PluginHelp{
    43  		Description: "Close, reopen, flag and/or unflag an issue or PR as frozen/stale/rotten",
    44  	}
    45  	pluginHelp.AddCommand(pluginhelp.Command{
    46  		Usage:       "/close [not-planned]",
    47  		Description: "Closes an issue or PR.",
    48  		Featured:    false,
    49  		WhoCanUse:   "Authors and collaborators on the repository can trigger this command.",
    50  		Examples:    []string{"/close", "/close not-planned"},
    51  	})
    52  	pluginHelp.AddCommand(pluginhelp.Command{
    53  		Usage:       "/reopen",
    54  		Description: "Reopens an issue or PR",
    55  		Featured:    false,
    56  		WhoCanUse:   "Authors and collaborators on the repository can trigger this command.",
    57  		Examples:    []string{"/reopen"},
    58  	})
    59  	pluginHelp.AddCommand(pluginhelp.Command{
    60  		Usage:       "/[remove-]lifecycle <frozen|stale|rotten>",
    61  		Description: "Flags an issue or PR as frozen/stale/rotten",
    62  		Featured:    false,
    63  		WhoCanUse:   "Anyone can trigger this command.",
    64  		Examples:    []string{"/lifecycle frozen", "/remove-lifecycle stale"},
    65  	})
    66  	return pluginHelp, nil
    67  }
    68  
    69  type lifecycleClient interface {
    70  	AddLabel(owner, repo string, number int, label string) error
    71  	RemoveLabel(owner, repo string, number int, label string) error
    72  	GetIssueLabels(org, repo string, number int) ([]github.Label, error)
    73  	CreateComment(owner, repo string, number int, comment string) error
    74  }
    75  
    76  func lifecycleHandleGenericComment(pc plugins.Agent, 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  	user := e.User.Login
   107  
   108  	remove := mat[1] != ""
   109  	cmd := mat[2]
   110  	lbl := "lifecycle/" + cmd
   111  
   112  	// Don't allow adding lifecycle/frozen label to PRs
   113  	if e.IsPR && lbl == labels.LifecycleFrozen && !remove {
   114  		return gc.CreateComment(org, repo, number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, user, fmt.Sprintf("The `%s` label cannot be applied to Pull Requests.", labels.LifecycleFrozen)))
   115  	}
   116  
   117  	// Let's start simple and allow anyone to add/remove frozen, stale, rotten labels.
   118  	// Adjust if we find evidence of the community abusing these labels.
   119  	labels, err := gc.GetIssueLabels(org, repo, number)
   120  	if err != nil {
   121  		log.WithError(err).Errorf("Failed to get labels.")
   122  	}
   123  
   124  	// If the label exists and we asked for it to be removed, remove it.
   125  	if github.HasLabel(lbl, labels) && remove {
   126  		return gc.RemoveLabel(org, repo, number, lbl)
   127  	}
   128  
   129  	// If the label does not exist and we asked for it to be added,
   130  	// remove other existing lifecycle labels and add it.
   131  	if !github.HasLabel(lbl, labels) && !remove {
   132  		for _, label := range lifecycleLabels {
   133  			if label != lbl && github.HasLabel(label, labels) {
   134  				if err := gc.RemoveLabel(org, repo, number, label); err != nil {
   135  					log.WithError(err).Errorf("GitHub failed to remove the following label: %s", label)
   136  				}
   137  			}
   138  		}
   139  
   140  		if err := gc.AddLabel(org, repo, number, lbl); err != nil {
   141  			log.WithError(err).Errorf("GitHub failed to add the following label: %s", lbl)
   142  		}
   143  	}
   144  
   145  	return nil
   146  }