github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/prow/plugins/assign/assign.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 assign 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/plugins" 28 ) 29 30 const pluginName = "assign" 31 32 var ( 33 assignRe = regexp.MustCompile(`(?mi)^/(un)?assign(( @?[-\w]+?)*)\s*$`) 34 ccRe = regexp.MustCompile(`(?mi)^/(un)?cc(( +@?[-\w]+?)*)\s*$`) 35 ) 36 37 func init() { 38 plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment) 39 } 40 41 type githubClient interface { 42 AssignIssue(owner, repo string, number int, logins []string) error 43 UnassignIssue(owner, repo string, number int, logins []string) error 44 45 RequestReview(org, repo string, number int, logins []string) error 46 UnrequestReview(org, repo string, number int, logins []string) error 47 48 CreateComment(owner, repo string, number int, comment string) error 49 } 50 51 func handleGenericComment(pc plugins.PluginClient, e github.GenericCommentEvent) error { 52 if e.Action != github.GenericCommentActionCreated { 53 return nil 54 } 55 err := handle(newAssignHandler(e, pc.GitHubClient, pc.Logger)) 56 if e.IsPR { 57 err = combineErrors(err, handle(newReviewHandler(e, pc.GitHubClient, pc.Logger))) 58 } 59 return err 60 } 61 62 func parseLogins(text string) []string { 63 var parts []string 64 for _, p := range strings.Split(text, " ") { 65 t := strings.Trim(p, "@ ") 66 if t == "" { 67 continue 68 } 69 parts = append(parts, t) 70 } 71 return parts 72 } 73 74 func combineErrors(err1, err2 error) error { 75 if err1 != nil && err2 != nil { 76 return fmt.Errorf("two errors: 1) %v 2) %v", err1, err2) 77 } else if err1 != nil { 78 return err1 79 } else { 80 return err2 81 } 82 } 83 84 // handle is the generic handler for the assign plugin. It uses the handler's regexp and affectedLogins 85 // functions to identify the users to add and/or remove and then passes the appropriate users to the 86 // handler's add and remove functions. If add fails to add some of the users, a response comment is 87 // created where the body of the response is generated by the handler's addFailureResponse function. 88 func handle(h *handler) error { 89 e := h.event 90 org := e.Repo.Owner.Login 91 repo := e.Repo.Name 92 matches := h.regexp.FindAllStringSubmatch(e.Body, -1) 93 if matches == nil { 94 return nil 95 } 96 users := make(map[string]bool) 97 for _, re := range matches { 98 add := re[1] != "un" // un<cmd> == !add 99 if re[2] == "" { 100 users[e.User.Login] = add 101 } else { 102 for _, login := range parseLogins(re[2]) { 103 users[login] = add 104 } 105 } 106 } 107 var toAdd, toRemove []string 108 for login, add := range users { 109 if add { 110 toAdd = append(toAdd, login) 111 } else { 112 toRemove = append(toRemove, login) 113 } 114 } 115 116 if len(toRemove) > 0 { 117 h.log.Printf("Removing %s from %s/%s#%d: %v", h.userType, org, repo, e.Number, toRemove) 118 if err := h.remove(org, repo, e.Number, toRemove); err != nil { 119 return err 120 } 121 } 122 if len(toAdd) > 0 { 123 h.log.Printf("Adding %s to %s/%s#%d: %v", h.userType, org, repo, e.Number, toAdd) 124 if err := h.add(org, repo, e.Number, toAdd); err != nil { 125 if mu, ok := err.(github.MissingUsers); ok { 126 msg := h.addFailureResponse(mu) 127 if len(msg) == 0 { 128 return nil 129 } 130 if err := h.gc.CreateComment(org, repo, e.Number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, msg)); err != nil { 131 return fmt.Errorf("comment err: %v", err) 132 } 133 return nil 134 } 135 return err 136 } 137 } 138 return nil 139 } 140 141 // handler is a struct that contains data about a github event and provides functions to help handle it. 142 type handler struct { 143 // addFailureResponse generates the body of a response comment in the event that the add function fails. 144 addFailureResponse func(mu github.MissingUsers) string 145 // remove is the function that is called on the affected logins for a command prefixed with 'un'. 146 remove func(org, repo string, number int, users []string) error 147 // add is the function that is called on the affected logins for a command with no 'un' prefix. 148 add func(org, repo string, number int, users []string) error 149 150 // event is a pointer to the github.GenericCommentEvent struct that triggered the handler. 151 event *github.GenericCommentEvent 152 // regexp is the regular expression describing the command. It must have an optional 'un' prefix 153 // as the first subgroup and the arguments to the command as the second subgroup. 154 regexp *regexp.Regexp 155 // gc is the githubClient to use for creating response comments in the event of a failure. 156 gc githubClient 157 158 // log is a logrus.Entry used to record actions the handler takes. 159 log *logrus.Entry 160 // userType is a string that represents the type of users affected by this handler. (e.g. 'assignees') 161 userType string 162 } 163 164 func newAssignHandler(e github.GenericCommentEvent, gc githubClient, log *logrus.Entry) *handler { 165 org := e.Repo.Owner.Login 166 addFailureResponse := func(mu github.MissingUsers) string { 167 return fmt.Sprintf("GitHub didn't allow me to assign the following users: %s.\n\nNote that only [%s members](https://github.com/orgs/%s/people) can be assigned.", strings.Join(mu.Users, ", "), org, org) 168 } 169 170 return &handler{ 171 addFailureResponse: addFailureResponse, 172 remove: gc.UnassignIssue, 173 add: gc.AssignIssue, 174 event: &e, 175 regexp: assignRe, 176 gc: gc, 177 log: log, 178 userType: "assignee(s)", 179 } 180 } 181 182 func newReviewHandler(e github.GenericCommentEvent, gc githubClient, log *logrus.Entry) *handler { 183 org := e.Repo.Owner.Login 184 addFailureResponse := func(mu github.MissingUsers) string { 185 return fmt.Sprintf("GitHub didn't allow me to request PR reviews from the following users: %s.\n\nNote that only [%s members](https://github.com/orgs/%s/people) can review this PR, and authors cannot review their own PRs.", strings.Join(mu.Users, ", "), org, org) 186 } 187 188 return &handler{ 189 addFailureResponse: addFailureResponse, 190 remove: gc.UnrequestReview, 191 add: gc.RequestReview, 192 event: &e, 193 regexp: ccRe, 194 gc: gc, 195 log: log, 196 userType: "reviewer(s)", 197 } 198 }