-
Notifications
You must be signed in to change notification settings - Fork 115
/
assign.go
209 lines (184 loc) · 7.71 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
199
200
201
202
203
204
205
206
207
208
209
/*
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"
"strings"
"github.com/jenkins-x/go-scm/scm"
"github.com/jenkins-x/lighthouse/pkg/plugins"
"github.com/jenkins-x/lighthouse/pkg/scmprovider"
"github.com/sirupsen/logrus"
)
const pluginName = "assign"
var (
plugin = plugins.Plugin{
Description: "The assign plugin assigns or requests reviews from users. Specific users can be assigned with the command '/assign @user1' or have reviews requested of them with the command '/cc @user1'. If no user is specified the commands default to targeting the user who created the command. Assignments and requested reviews can be removed in the same way that they are added by prefixing the commands with 'un'.",
Commands: []plugins.Command{{
Prefix: "un",
Name: "cc|assign",
Arg: &plugins.CommandArg{
Pattern: `@?"?[-/\w]+"?(?:[ \t]+@?"?[-/\w]+"?)*`,
Optional: true,
},
Description: "Assigns an assignee to the PR or issue or requests a review from the user(s)",
Featured: true,
WhoCanUse: "Anyone can use the command, but the target user must be an org member, a repo collaborator, or should have previously commented on the issue or PR.",
Action: plugins.
Invoke(func(match plugins.CommandMatch, pc plugins.Agent, e scmprovider.GenericCommentEvent) error {
return handleGenericComment(match.Prefix != "un", match.Name, match.Arg, pc, e)
}).
When(plugins.Action(scm.ActionCreate)),
}},
}
)
func init() {
plugins.RegisterPlugin(pluginName, plugin)
}
type scmProviderClient 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, pr bool, comment string) error
QuoteAuthorForComment(string) string
}
func handleGenericComment(add bool, command string, arg string, pc plugins.Agent, e scmprovider.GenericCommentEvent) error {
err := handle(add, command, arg, newAssignHandler(e, pc.SCMProviderClient, pc.Logger))
if e.IsPR {
err = combineErrors(err, handle(add, command, arg, newReviewHandler(e, pc.SCMProviderClient, 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(add bool, command string, arg string, h *handler) error {
e := h.event
org := e.Repo.Namespace
repo := e.Repo.Name
if command != h.command {
return nil
}
users := make(map[string]bool)
if arg == "" {
users[e.Author.Login] = add
} else {
for _, login := range parseLogins(arg) {
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.(scmprovider.MissingUsers); ok {
msg := h.addFailureResponse(mu)
if len(msg) == 0 {
return nil
}
if err := h.spc.CreateComment(org, repo, e.Number, e.IsPR,
plugins.FormatResponseRaw(e.Body, e.Link, h.spc.QuoteAuthorForComment(e.Author.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 scmprovider.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 gitprovider.GenericCommentEvent struct that triggered the handler.
event *scmprovider.GenericCommentEvent
// command is the name of the command to be executed (assign or cc).
command string
// spc is the scmProviderClient to use for creating response comments in the event of a failure.
spc scmProviderClient
// 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 scmprovider.GenericCommentEvent, spc scmProviderClient, log *logrus.Entry) *handler {
org := e.Repo.Namespace
addFailureResponse := func(mu scmprovider.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), repo collaborators and people who have commented on this issue/PR can be assigned. Additionally, issues/PRs can only have 10 assignees at the same time.\nFor more information please see [the contributor guide](https://git.k8s.io/community/contributors/guide/#issue-assignment-in-github)", strings.Join(mu.Users, ", "), org, org)
}
return &handler{
addFailureResponse: addFailureResponse,
remove: spc.UnassignIssue,
add: spc.AssignIssue,
event: &e,
command: "assign",
spc: spc,
log: log,
userType: "assignee(s)",
}
}
func newReviewHandler(e scmprovider.GenericCommentEvent, spc scmProviderClient, log *logrus.Entry) *handler {
org := e.Repo.Namespace
addFailureResponse := func(mu scmprovider.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) and repo collaborators can review this PR, and authors cannot review their own PRs.", strings.Join(mu.Users, ", "), org, org)
}
return &handler{
addFailureResponse: addFailureResponse,
remove: spc.UnrequestReview,
add: spc.RequestReview,
event: &e,
command: "cc",
spc: spc,
log: log,
userType: "reviewer(s)",
}
}