forked from kubernetes/test-infra
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathassign.go
198 lines (173 loc) · 6.42 KB
/
assign.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package assign
import (
"fmt"
"regexp"
"strings"
"github.com/sirupsen/logrus"
"k8s.io/test-infra/prow/github"
"k8s.io/test-infra/prow/plugins"
)
const pluginName = "assign"
var (
assignRe = regexp.MustCompile(`(?mi)^/(un)?assign(( @?[-\w]+?)*)\s*$`)
ccRe = regexp.MustCompile(`(?mi)^/(un)?cc(( +@?[-\w]+?)*)\s*$`)
)
func init() {
plugins.RegisterGenericCommentHandler(pluginName, handleGenericComment)
}
type githubClient interface {
AssignIssue(owner, repo string, number int, logins []string) error
UnassignIssue(owner, repo string, number int, logins []string) error
RequestReview(org, repo string, number int, logins []string) error
UnrequestReview(org, repo string, number int, logins []string) error
CreateComment(owner, repo string, number int, comment string) error
}
func handleGenericComment(pc plugins.PluginClient, e github.GenericCommentEvent) error {
if e.Action != github.GenericCommentActionCreated {
return nil
}
err := handle(newAssignHandler(e, pc.GitHubClient, pc.Logger))
if e.IsPR {
err = combineErrors(err, handle(newReviewHandler(e, pc.GitHubClient, pc.Logger)))
}
return err
}
func parseLogins(text string) []string {
var parts []string
for _, p := range strings.Split(text, " ") {
t := strings.Trim(p, "@ ")
if t == "" {
continue
}
parts = append(parts, t)
}
return parts
}
func combineErrors(err1, err2 error) error {
if err1 != nil && err2 != nil {
return fmt.Errorf("two errors: 1) %v 2) %v", err1, err2)
} else if err1 != nil {
return err1
} else {
return err2
}
}
// handle is the generic handler for the assign plugin. It uses the handler's regexp and affectedLogins
// functions to identify the users to add and/or remove and then passes the appropriate users to the
// handler's add and remove functions. If add fails to add some of the users, a response comment is
// created where the body of the response is generated by the handler's addFailureResponse function.
func handle(h *handler) error {
e := h.event
org := e.Repo.Owner.Login
repo := e.Repo.Name
matches := h.regexp.FindAllStringSubmatch(e.Body, -1)
if matches == nil {
return nil
}
users := make(map[string]bool)
for _, re := range matches {
add := re[1] != "un" // un<cmd> == !add
if re[2] == "" {
users[e.User.Login] = add
} else {
for _, login := range parseLogins(re[2]) {
users[login] = add
}
}
}
var toAdd, toRemove []string
for login, add := range users {
if add {
toAdd = append(toAdd, login)
} else {
toRemove = append(toRemove, login)
}
}
if len(toRemove) > 0 {
h.log.Printf("Removing %s from %s/%s#%d: %v", h.userType, org, repo, e.Number, toRemove)
if err := h.remove(org, repo, e.Number, toRemove); err != nil {
return err
}
}
if len(toAdd) > 0 {
h.log.Printf("Adding %s to %s/%s#%d: %v", h.userType, org, repo, e.Number, toAdd)
if err := h.add(org, repo, e.Number, toAdd); err != nil {
if mu, ok := err.(github.MissingUsers); ok {
msg := h.addFailureResponse(mu)
if len(msg) == 0 {
return nil
}
if err := h.gc.CreateComment(org, repo, e.Number, plugins.FormatResponseRaw(e.Body, e.HTMLURL, e.User.Login, msg)); err != nil {
return fmt.Errorf("comment err: %v", err)
}
return nil
}
return err
}
}
return nil
}
// handler is a struct that contains data about a github event and provides functions to help handle it.
type handler struct {
// addFailureResponse generates the body of a response comment in the event that the add function fails.
addFailureResponse func(mu github.MissingUsers) string
// remove is the function that is called on the affected logins for a command prefixed with 'un'.
remove func(org, repo string, number int, users []string) error
// add is the function that is called on the affected logins for a command with no 'un' prefix.
add func(org, repo string, number int, users []string) error
// event is a pointer to the github.GenericCommentEvent struct that triggered the handler.
event *github.GenericCommentEvent
// regexp is the regular expression describing the command. It must have an optional 'un' prefix
// as the first subgroup and the arguments to the command as the second subgroup.
regexp *regexp.Regexp
// gc is the githubClient to use for creating response comments in the event of a failure.
gc githubClient
// log is a logrus.Entry used to record actions the handler takes.
log *logrus.Entry
// userType is a string that represents the type of users affected by this handler. (e.g. 'assignees')
userType string
}
func newAssignHandler(e github.GenericCommentEvent, gc githubClient, log *logrus.Entry) *handler {
org := e.Repo.Owner.Login
addFailureResponse := func(mu github.MissingUsers) string {
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)
}
return &handler{
addFailureResponse: addFailureResponse,
remove: gc.UnassignIssue,
add: gc.AssignIssue,
event: &e,
regexp: assignRe,
gc: gc,
log: log,
userType: "assignee(s)",
}
}
func newReviewHandler(e github.GenericCommentEvent, gc githubClient, log *logrus.Entry) *handler {
org := e.Repo.Owner.Login
addFailureResponse := func(mu github.MissingUsers) string {
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)
}
return &handler{
addFailureResponse: addFailureResponse,
remove: gc.UnrequestReview,
add: gc.RequestReview,
event: &e,
regexp: ccRe,
gc: gc,
log: log,
userType: "reviewer(s)",
}
}