github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/milestonestatus/milestonestatus.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 milestonestatus implements the `/status` command which allows members of the milestone
    18  // maintainers team to specify a `status/*` label to be applied to an Issue or PR.
    19  package milestonestatus
    20  
    21  import (
    22  	"fmt"
    23  	"regexp"
    24  	"strings"
    25  
    26  	"github.com/sirupsen/logrus"
    27  
    28  	"sigs.k8s.io/prow/pkg/config"
    29  	"sigs.k8s.io/prow/pkg/github"
    30  	"sigs.k8s.io/prow/pkg/pluginhelp"
    31  	"sigs.k8s.io/prow/pkg/plugins"
    32  )
    33  
    34  const pluginName = "milestonestatus"
    35  
    36  var (
    37  	statusRegex      = regexp.MustCompile(`(?m)^/status\s+(.+)$`)
    38  	mustBeAuthorized = "You must be a member of the [%s/%s](https://github.com/orgs/%s/teams/%s/members) GitHub team to add status labels. If you believe you should be able to issue the /status command, please contact your %s and have them propose you as an additional delegate for this responsibility."
    39  	milestoneTeamMsg = "The milestone maintainers team is the GitHub team %q"
    40  	statusMap        = map[string]string{
    41  		"approved-for-milestone": "status/approved-for-milestone",
    42  		"in-progress":            "status/in-progress",
    43  		"in-review":              "status/in-review",
    44  	}
    45  )
    46  
    47  type githubClient interface {
    48  	CreateComment(owner, repo string, number int, comment string) error
    49  	AddLabel(owner, repo string, number int, label string) error
    50  	ListTeamMembers(org string, id int, role string) ([]github.TeamMember, error)
    51  	ListTeamMembersBySlug(org, teamSlug, role string) ([]github.TeamMember, error)
    52  }
    53  
    54  func init() {
    55  	plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider)
    56  }
    57  
    58  func helpProvider(config *plugins.Configuration, enabledRepos []config.OrgRepo) (*pluginhelp.PluginHelp, error) {
    59  	msgForTeam := func(team plugins.Milestone) string {
    60  		return fmt.Sprintf(milestoneTeamMsg, team.MaintainersTeam)
    61  	}
    62  
    63  	pluginHelp := &pluginhelp.PluginHelp{
    64  		Description: "The milestonestatus plugin allows members of the milestone maintainers GitHub team to specify the 'status/*' label that should apply to a pull request.",
    65  		Config: func() map[string]string {
    66  			configMap := make(map[string]string)
    67  			for _, repo := range enabledRepos {
    68  				team, exists := config.RepoMilestone[repo.String()]
    69  				if exists {
    70  					configMap[repo.String()] = msgForTeam(team)
    71  				}
    72  			}
    73  			configMap[""] = msgForTeam(config.RepoMilestone[""])
    74  			return configMap
    75  		}(),
    76  	}
    77  	pluginHelp.AddCommand(pluginhelp.Command{
    78  		Usage:       "/status (approved-for-milestone|in-progress|in-review)",
    79  		Description: "Applies the 'status/' label to a PR.",
    80  		Featured:    false,
    81  		WhoCanUse:   "Members of the milestone maintainers GitHub team can use the '/status' command. This team is specified in the config by providing the GitHub team's ID.",
    82  		Examples:    []string{"/status approved-for-milestone", "/status in-progress", "/status in-review"},
    83  	})
    84  	return pluginHelp, nil
    85  }
    86  
    87  func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error {
    88  	return handle(pc.GitHubClient, pc.Logger, &e, pc.PluginConfig.RepoMilestone)
    89  }
    90  
    91  func handle(gc githubClient, log *logrus.Entry, e *github.GenericCommentEvent, repoMilestone map[string]plugins.Milestone) error {
    92  	if e.Action != github.GenericCommentActionCreated {
    93  		return nil
    94  	}
    95  
    96  	statusMatches := statusRegex.FindAllStringSubmatch(e.Body, -1)
    97  	if len(statusMatches) == 0 {
    98  		return nil
    99  	}
   100  
   101  	org := e.Repo.Owner.Login
   102  	repo := e.Repo.Name
   103  
   104  	milestone, exists := repoMilestone[fmt.Sprintf("%s/%s", org, repo)]
   105  	if !exists {
   106  		// fallback default
   107  		milestone = repoMilestone[""]
   108  	}
   109  
   110  	milestoneMaintainers, err := determineMaintainers(gc, milestone, org)
   111  	if err != nil {
   112  		return err
   113  	}
   114  	found := false
   115  	for _, person := range milestoneMaintainers {
   116  		login := strings.ToLower(e.User.Login)
   117  		if strings.ToLower(person.Login) == login {
   118  			found = true
   119  			break
   120  		}
   121  	}
   122  	if !found {
   123  		// not in the milestone maintainers team
   124  		msg := fmt.Sprintf(mustBeAuthorized, org, milestone.MaintainersTeam, org, milestone.MaintainersTeam, milestone.MaintainersFriendlyName)
   125  		return gc.CreateComment(org, repo, e.Number, msg)
   126  	}
   127  
   128  	for _, statusMatch := range statusMatches {
   129  		sLabel, validStatus := statusMap[strings.TrimSpace(statusMatch[1])]
   130  		if !validStatus {
   131  			continue
   132  		}
   133  		if err := gc.AddLabel(org, repo, e.Number, sLabel); err != nil {
   134  			log.WithError(err).Errorf("Error adding the label %q to %s/%s#%d.", sLabel, org, repo, e.Number)
   135  		}
   136  	}
   137  	return nil
   138  }
   139  
   140  func determineMaintainers(gc githubClient, milestone plugins.Milestone, org string) ([]github.TeamMember, error) {
   141  	if milestone.MaintainersTeam != "" {
   142  		return gc.ListTeamMembersBySlug(org, milestone.MaintainersTeam, github.RoleAll)
   143  	}
   144  	return gc.ListTeamMembers(org, milestone.MaintainersID, github.RoleAll)
   145  }