sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/plugins/sigmention/sigmention.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 sigmention recognize SIG '@' mentions and adds 'sig/*' and 'kind/*' labels as appropriate. 18 // SIG mentions are also reitierated by the bot if the user who made the mention is not a member in 19 // order for the mention to trigger a notification for the github team. 20 package sigmention 21 22 import ( 23 "fmt" 24 "regexp" 25 "strings" 26 27 "github.com/sirupsen/logrus" 28 "sigs.k8s.io/prow/pkg/config" 29 "sigs.k8s.io/prow/pkg/github" 30 "sigs.k8s.io/prow/pkg/labels" 31 "sigs.k8s.io/prow/pkg/pluginhelp" 32 "sigs.k8s.io/prow/pkg/plugins" 33 ) 34 35 const pluginName = "sigmention" 36 37 var ( 38 chatBack = "Reiterating the mentions to trigger a notification: \n%v\n" 39 40 kindMap = map[string]string{ 41 "bugs": labels.Bug, 42 "feature-requests": "kind/feature", 43 "api-reviews": "kind/api-change", 44 "proposals": "kind/design", 45 } 46 ) 47 48 type githubClient interface { 49 CreateComment(owner, repo string, number int, comment string) error 50 IsMember(org, user string) (bool, error) 51 AddLabel(owner, repo string, number int, label string) error 52 RemoveLabel(owner, repo string, number int, label string) error 53 GetRepoLabels(owner, repo string) ([]github.Label, error) 54 BotUserChecker() (func(candidate string) bool, error) 55 GetIssueLabels(org, repo string, number int) ([]github.Label, error) 56 } 57 58 func init() { 59 plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider) 60 } 61 62 func helpProvider(config *plugins.Configuration, _ []config.OrgRepo) (*pluginhelp.PluginHelp, error) { 63 yamlSnippet, err := plugins.CommentMap.GenYaml(&plugins.Configuration{ 64 SigMention: plugins.SigMention{ 65 Regexp: "(?m)@kubernetes/sig-([\\w-]*)-(misc|test-failures|bugs|feature-requests|proposals|pr-reviews|api-reviews)", 66 }, 67 }) 68 if err != nil { 69 logrus.WithError(err).Warnf("cannot generate comments for %s plugin", pluginName) 70 } 71 return &pluginhelp.PluginHelp{ 72 Description: `The sigmention plugin responds to SIG (Special Interest Group) GitHub team mentions like '@kubernetes/sig-testing-bugs'. The plugin responds in two ways: 73 <ol><li> The appropriate 'sig/*' and 'kind/*' labels are applied to the issue or pull request. In this case 'sig/testing' and 'kind/bug'.</li> 74 <li> If the user who mentioned the GitHub team is not a member of the organization that owns the repository the bot will create a comment that repeats the mention. This is necessary because non-member mentions do not trigger GitHub notifications.</li></ol>`, 75 Config: map[string]string{ 76 "": fmt.Sprintf("Labels added by the plugin are triggered by mentions of GitHub teams matching the following regexp:\n%s", config.SigMention.Regexp), 77 }, 78 Snippet: yamlSnippet, 79 }, 80 nil 81 } 82 83 func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error { 84 return handle(pc.GitHubClient, pc.Logger, &e, pc.PluginConfig.SigMention.Re) 85 } 86 87 func handle(gc githubClient, log *logrus.Entry, e *github.GenericCommentEvent, re *regexp.Regexp) error { 88 // Ignore bot comments and comments that aren't new. 89 botUserChecker, err := gc.BotUserChecker() 90 if err != nil { 91 return err 92 } 93 if botUserChecker(e.User.Login) { 94 return nil 95 } 96 if e.Action != github.GenericCommentActionCreated { 97 return nil 98 } 99 100 sigMatches := re.FindAllStringSubmatch(e.Body, -1) 101 if len(sigMatches) == 0 { 102 return nil 103 } 104 105 org := e.Repo.Owner.Login 106 repo := e.Repo.Name 107 108 labels, err := gc.GetIssueLabels(org, repo, e.Number) 109 if err != nil { 110 return err 111 } 112 repoLabels, err := gc.GetRepoLabels(org, repo) 113 if err != nil { 114 return err 115 } 116 RepoLabelsExisting := map[string]string{} 117 for _, l := range repoLabels { 118 RepoLabelsExisting[strings.ToLower(l.Name)] = l.Name 119 } 120 121 var nonexistent, toRepeat []string 122 for _, sigMatch := range sigMatches { 123 sigLabel := strings.ToLower("sig" + "/" + sigMatch[1]) 124 sigLabel, ok := RepoLabelsExisting[sigLabel] 125 if !ok { 126 nonexistent = append(nonexistent, "sig/"+sigMatch[1]) 127 continue 128 } 129 if !github.HasLabel(sigLabel, labels) { 130 if err := gc.AddLabel(org, repo, e.Number, sigLabel); err != nil { 131 log.WithError(err).Errorf("GitHub failed to add the following label: %s", sigLabel) 132 } 133 } 134 135 if len(sigMatch) > 2 { 136 if kindLabel, ok := kindMap[sigMatch[2]]; ok && !github.HasLabel(kindLabel, labels) { 137 if err := gc.AddLabel(org, repo, e.Number, kindLabel); err != nil { 138 log.WithError(err).Errorf("GitHub failed to add the following label: %s", kindLabel) 139 } 140 } 141 } 142 143 toRepeat = append(toRepeat, sigMatch[0]) 144 } 145 //TODO(grodrigues3): Once labels are standardized, make this reply with a comment. 146 if len(nonexistent) > 0 { 147 log.Infof("Nonexistent labels: %v", nonexistent) 148 } 149 150 isMember, err := gc.IsMember(org, e.User.Login) 151 if err != nil { 152 log.WithError(err).Errorf("Error from IsMember(%q of org %q).", e.User.Login, org) 153 } 154 if isMember || len(toRepeat) == 0 { 155 return nil 156 } 157 158 msg := fmt.Sprintf(chatBack, strings.Join(toRepeat, ", ")) 159 return gc.CreateComment(org, repo, e.Number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, msg)) 160 }