-
Notifications
You must be signed in to change notification settings - Fork 0
/
labelMutex.go
200 lines (181 loc) · 5.4 KB
/
labelMutex.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
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
"github.com/google/go-github/v55/github"
"github.com/hashicorp/go-multierror"
"github.com/wolfeidau/dynalock"
)
var (
lockedSuffix = "locked"
)
type issuesService interface {
AddLabelsToIssue(ctx context.Context, owner string, repo string, number int, labels []string) ([]*github.Label, *github.Response, error)
RemoveLabelForIssue(ctx context.Context, owner string, repo string, number int, label string) (*github.Response, error)
}
type pullRequestService interface {
List(ctx context.Context, owner string, repo string, opts *github.PullRequestListOptions) ([]*github.PullRequest, *github.Response, error)
}
// LabelMutex is a GitHub action that applies a label to exactly one pull request in your repository
type LabelMutex struct {
issuesClient issuesService
pullRequestsClient pullRequestService
context context.Context
uriLocker URILocker
event []byte
eventName string
label string
action string
pr *github.PullRequest
locked bool
unlocked bool
htmlURL string
}
func (lm *LabelMutex) output() map[string]string {
output := make(map[string]string)
if lm.locked {
output["locked"] = "true"
} else {
output["locked"] = "false"
}
if lm.unlocked {
output["unlocked"] = "true"
} else {
output["unlocked"] = "false"
}
if lm.htmlURL != "" {
output["html_url"] = lm.htmlURL
}
return output
}
func (lm *LabelMutex) process() error {
if lm.eventName == "pull_request" {
return lm.processPR()
}
return lm.processOther()
}
func (lm *LabelMutex) processOther() error {
value, err := lm.uriLocker.Read()
if err == dynalock.ErrKeyNotFound {
lm.locked = false
lm.unlocked = true
}
if value == "" {
lm.locked = false
lm.unlocked = true
} else {
lm.htmlURL = value
lm.locked = true
lm.unlocked = false
}
return nil
}
func (lm *LabelMutex) processPR() error {
var resultErr *multierror.Error
var pr github.PullRequestEvent
err := json.Unmarshal(lm.event, &pr)
if err != nil {
return err
}
lm.pr = pr.GetPullRequest()
lm.action = pr.GetAction()
var hasLockRequestLabel bool
var hasLockConfirmedLabel bool
for _, label := range lm.pr.Labels {
if lm.label == label.GetName() {
hasLockRequestLabel = true
}
if fmt.Sprintf("%s:%s", lm.label, lockedSuffix) == label.GetName() {
hasLockConfirmedLabel = true
}
}
var removedLabelName string
var lockLabelRemoved bool
if lm.action == "unlabeled" {
removedLabelName = pr.GetLabel().GetName()
if removedLabelName == lm.label {
lockLabelRemoved = true
}
}
lockValue := lm.pr.GetHTMLURL()
if lm.pr.GetState() != "open" || lockLabelRemoved {
log.Printf("Unlocking '%s' ...\n", lm.label)
existing, err := lm.uriLocker.Unlock(lockValue)
if err == nil || existing == "" {
log.Println("Unlocked!")
lm.locked = false
lm.unlocked = true
}
if existing == "" {
if lm.action == "unlabeled" || lm.action == "closed" {
lm.locked = false
lm.unlocked = true
log.Printf("Lock '%s' was already unlocked ...\n", lm.label)
} else {
resultErr = multierror.Append(resultErr, err)
}
} else {
lm.locked = true
lm.unlocked = false
log.Printf("Lock '%s' currently claimed by %s ...\n", lm.label, existing)
lm.htmlURL = existing
}
resp, err := lm.issuesClient.RemoveLabelForIssue(lm.context, lm.pr.GetBase().Repo.Owner.GetLogin(), lm.pr.GetBase().Repo.GetName(), lm.pr.GetNumber(), lm.label)
if resp.Response.StatusCode != http.StatusNotFound && err != nil {
resultErr = multierror.Append(resultErr, err)
}
resp, err = lm.issuesClient.RemoveLabelForIssue(lm.context, lm.pr.GetBase().Repo.Owner.GetLogin(), lm.pr.GetBase().Repo.GetName(), lm.pr.GetNumber(), fmt.Sprintf("%s:%s", lm.label, lockedSuffix))
if resp.Response.StatusCode != http.StatusNotFound && err != nil {
resultErr = multierror.Append(resultErr, err)
}
return resultErr.ErrorOrNil()
}
if hasLockRequestLabel && hasLockConfirmedLabel {
log.Printf("Lock '%s' should already be claimed by %s, confirming ...\n", lm.label, lockValue)
// double check
success, existingValue, lockErr := lm.uriLocker.Lock(lockValue)
if success {
lm.locked = true
lm.htmlURL = lockValue
log.Printf("Weird, the lock should have already been ours!")
return nil
}
if existingValue == lockValue {
lm.locked = true
lm.htmlURL = lockValue
return nil
}
return lockErr
}
if hasLockRequestLabel && !hasLockConfirmedLabel {
log.Printf("Lock '%s' requested but not confirmed, trying to lock with %s ...\n", lm.label, lockValue)
success, existingValue, lockErr := lm.uriLocker.Lock(lockValue)
if lockErr != nil {
return lockErr
}
if success {
log.Printf("Lock '%s' obtained\n", lm.label)
lm.locked = true
lm.htmlURL = lockValue
labelsToAdd := []string{fmt.Sprintf("%s:%s", lm.label, lockedSuffix)}
_, _, err := lm.issuesClient.AddLabelsToIssue(lm.context, lm.pr.GetBase().Repo.Owner.GetLogin(), lm.pr.GetBase().Repo.GetName(), lm.pr.GetNumber(), labelsToAdd)
if err != nil {
return err
}
return nil
}
if existingValue != "" {
log.Printf("Lock '%s' claimed by %s\n", lm.label, existingValue)
lm.locked = true
lm.htmlURL = existingValue
return nil
}
return errors.New("Unknown error")
}
log.Printf("Label '%s' not present, doing nothing\n", lm.label)
return resultErr.ErrorOrNil()
}