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 }