github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/prow/plugins/label/label.go (about) 1 /* 2 Copyright 2016 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 label 18 19 import ( 20 "fmt" 21 "regexp" 22 "strings" 23 24 "github.com/sirupsen/logrus" 25 26 "k8s.io/test-infra/prow/github" 27 "k8s.io/test-infra/prow/pluginhelp" 28 "k8s.io/test-infra/prow/plugins" 29 ) 30 31 const pluginName = "label" 32 33 var ( 34 labelRegex = regexp.MustCompile(`(?m)^/(area|committee|kind|language|priority|sig|triage|wg)\s*(.*)$`) 35 removeLabelRegex = regexp.MustCompile(`(?m)^/remove-(area|committee|kind|language|priority|sig|triage|wg)\s*(.*)$`) 36 customLabelRegex = regexp.MustCompile(`(?m)^/label\s*(.*)$`) 37 customRemoveLabelRegex = regexp.MustCompile(`(?m)^/remove-label\s*(.*)$`) 38 nonExistentLabelOnIssue = "Those labels are not set on the issue: `%v`" 39 ) 40 41 func init() { 42 plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment, helpProvider) 43 } 44 45 func helpProvider(config *plugins.Configuration, enabledRepos []string) (*pluginhelp.PluginHelp, error) { 46 // The Config field is omitted because this plugin is not configurable. 47 pluginHelp := &pluginhelp.PluginHelp{ 48 Description: "The label plugin provides commands that add or remove certain types of labels. Labels of the following types can be manipulated: 'area/*', 'committee/*', 'kind/*', 'language/*', 'priority/*', 'sig/*', 'triage/*', and 'wg/*'. More labels can be configured to be used via the /label command.", 49 } 50 pluginHelp.AddCommand(pluginhelp.Command{ 51 Usage: "/[remove-](area|committee|kind|language|priority|sig|triage|wg|label) <target>", 52 Description: "Applies or removes a label from one of the recognized types of labels.", 53 Featured: false, 54 WhoCanUse: "Anyone can trigger this command on a PR.", 55 Examples: []string{"/kind bug", "/remove-area prow", "/sig testing", "/language zh"}, 56 }) 57 return pluginHelp, nil 58 } 59 60 func handleGenericComment(pc plugins.Agent, e github.GenericCommentEvent) error { 61 var labels []string 62 if pc.PluginConfig.Label != nil { 63 labels = pc.PluginConfig.Label.AdditionalLabels 64 } 65 return handle(pc.GitHubClient, pc.Logger, labels, &e) 66 } 67 68 type githubClient interface { 69 CreateComment(owner, repo string, number int, comment string) error 70 AddLabel(owner, repo string, number int, label string) error 71 RemoveLabel(owner, repo string, number int, label string) error 72 GetRepoLabels(owner, repo string) ([]github.Label, error) 73 GetIssueLabels(org, repo string, number int) ([]github.Label, error) 74 } 75 76 // Get Labels from Regexp matches 77 func getLabelsFromREMatches(matches [][]string) (labels []string) { 78 for _, match := range matches { 79 for _, label := range strings.Split(match[0], " ")[1:] { 80 label = strings.ToLower(match[1] + "/" + strings.TrimSpace(label)) 81 labels = append(labels, label) 82 } 83 } 84 return 85 } 86 87 // getLabelsFromGenericMatches returns label matches with extra labels if those 88 // have been configured in the plugin config. 89 func getLabelsFromGenericMatches(matches [][]string, additionalLabels []string) []string { 90 if len(additionalLabels) == 0 { 91 return nil 92 } 93 var labels []string 94 for _, match := range matches { 95 parts := strings.Split(match[0], " ") 96 if ((parts[0] != "/label") && (parts[0] != "/remove-label")) || len(parts) != 2 { 97 continue 98 } 99 for _, l := range additionalLabels { 100 if l == parts[1] { 101 labels = append(labels, parts[1]) 102 } 103 } 104 } 105 return labels 106 } 107 108 func handle(gc githubClient, log *logrus.Entry, additionalLabels []string, e *github.GenericCommentEvent) error { 109 labelMatches := labelRegex.FindAllStringSubmatch(e.Body, -1) 110 removeLabelMatches := removeLabelRegex.FindAllStringSubmatch(e.Body, -1) 111 customLabelMatches := customLabelRegex.FindAllStringSubmatch(e.Body, -1) 112 customRemoveLabelMatches := customRemoveLabelRegex.FindAllStringSubmatch(e.Body, -1) 113 if len(labelMatches) == 0 && len(removeLabelMatches) == 0 && len(customLabelMatches) == 0 && len(customRemoveLabelMatches) == 0 { 114 return nil 115 } 116 117 org := e.Repo.Owner.Login 118 repo := e.Repo.Name 119 120 repoLabels, err := gc.GetRepoLabels(org, repo) 121 if err != nil { 122 return err 123 } 124 labels, err := gc.GetIssueLabels(org, repo, e.Number) 125 if err != nil { 126 return err 127 } 128 129 RepoLabelsExisting := map[string]string{} 130 for _, l := range repoLabels { 131 RepoLabelsExisting[strings.ToLower(l.Name)] = l.Name 132 } 133 var ( 134 nonexistent []string 135 noSuchLabelsOnIssue []string 136 labelsToAdd []string 137 labelsToRemove []string 138 ) 139 140 // Get labels to add and labels to remove from regexp matches 141 labelsToAdd = append(getLabelsFromREMatches(labelMatches), getLabelsFromGenericMatches(customLabelMatches, additionalLabels)...) 142 labelsToRemove = append(getLabelsFromREMatches(removeLabelMatches), getLabelsFromGenericMatches(customRemoveLabelMatches, additionalLabels)...) 143 144 // Add labels 145 for _, labelToAdd := range labelsToAdd { 146 if github.HasLabel(labelToAdd, labels) { 147 continue 148 } 149 150 if _, ok := RepoLabelsExisting[labelToAdd]; !ok { 151 nonexistent = append(nonexistent, labelToAdd) 152 continue 153 } 154 155 if err := gc.AddLabel(org, repo, e.Number, RepoLabelsExisting[labelToAdd]); err != nil { 156 log.WithError(err).Errorf("Github failed to add the following label: %s", labelToAdd) 157 } 158 } 159 160 // Remove labels 161 for _, labelToRemove := range labelsToRemove { 162 if !github.HasLabel(labelToRemove, labels) { 163 noSuchLabelsOnIssue = append(noSuchLabelsOnIssue, labelToRemove) 164 continue 165 } 166 167 if _, ok := RepoLabelsExisting[labelToRemove]; !ok { 168 nonexistent = append(nonexistent, labelToRemove) 169 continue 170 } 171 172 if err := gc.RemoveLabel(org, repo, e.Number, labelToRemove); err != nil { 173 log.WithError(err).Errorf("Github failed to remove the following label: %s", labelToRemove) 174 } 175 } 176 177 //TODO(grodrigues3): Once labels are standardized, make this reply with a comment. 178 if len(nonexistent) > 0 { 179 log.Infof("Nonexistent labels: %v", nonexistent) 180 } 181 182 // Tried to remove Labels that were not present on the Issue 183 if len(noSuchLabelsOnIssue) > 0 { 184 msg := fmt.Sprintf(nonExistentLabelOnIssue, strings.Join(noSuchLabelsOnIssue, ", ")) 185 return gc.CreateComment(org, repo, e.Number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, msg)) 186 } 187 188 return nil 189 }