forked from evergreen-ci/evergreen
/
github.go
249 lines (213 loc) · 7.74 KB
/
github.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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
package patch
import (
"fmt"
"strings"
"time"
"github.com/evergreen-ci/evergreen"
"github.com/evergreen-ci/evergreen/db"
"github.com/evergreen-ci/evergreen/util"
"github.com/google/go-github/github"
"github.com/mongodb/anser/bsonutil"
"github.com/pkg/errors"
"go.mongodb.org/mongo-driver/bson"
mgobson "gopkg.in/mgo.v2/bson"
)
const (
// IntentCollection is the database collection that stores patch intents.
IntentCollection = "patch_intents"
// GithubIntentType represents patch intents created for GitHub.
GithubIntentType = "github"
// GithubAlias is a special alias to specify default variants and tasks for GitHub pull requests.
GithubAlias = "__github"
)
// githubIntent represents an intent to create a patch build as a result of a
// PullRequestEvent webhook. These intents are processed asynchronously by an
// amboy queue.
type githubIntent struct {
// TODO: migrate/remove all documents to use the MsgID as the _id
// ID is created by the driver and has no special meaning to the application.
DocumentID string `bson:"_id"`
// MsgId is a GUID provided by Github (X-Github-Delivery) for the event.
MsgID string `bson:"msg_id"`
// BaseRepoName is the full repository name, ex: mongodb/mongo, that
// this PR will be merged into
BaseRepoName string `bson:"base_repo_name"`
// BaseBranch is the branch that this pull request was opened against
BaseBranch string `bson:"base_branch"`
// HeadRepoName is the full repository name that contains the changes
// to be merged
HeadRepoName string `bson:"head_repo_name"`
// PRNumber is the pull request number in GitHub.
PRNumber int `bson:"pr_number"`
// User is the login username of the Github user that created the pull request
User string `bson:"user"`
// UID is the PR author's Github UID
UID int `bson:"author_uid"`
// HeadHash is the head hash of the diff, i.e. hash of the most recent
// commit.
HeadHash string `bson:"head_hash"`
// Title is the title of the Github PR
Title string `bson:"Title"`
// PushedAt was the time the Github Head Repository was pushed to
PushedAt time.Time `bson:"pushed_at"`
// CreatedAt is the time that this intent was stored in the database
CreatedAt time.Time `bson:"created_at"`
// Processed indicates whether a patch intent has been processed by the amboy queue.
Processed bool `bson:"processed"`
// ProcessedAt is the time that this intent was processed
ProcessedAt time.Time `bson:"processed_at"`
// IntentType indicates the type of the patch intent, e.g. GithubIntentType
IntentType string `bson:"intent_type"`
}
// BSON fields for the patches
// nolint
var (
documentIDKey = bsonutil.MustHaveTag(githubIntent{}, "DocumentID")
msgIDKey = bsonutil.MustHaveTag(githubIntent{}, "MsgID")
createdAtKey = bsonutil.MustHaveTag(githubIntent{}, "CreatedAt")
baseRepoNameKey = bsonutil.MustHaveTag(githubIntent{}, "BaseRepoName")
baseBranchKey = bsonutil.MustHaveTag(githubIntent{}, "BaseBranch")
headRepoNameKey = bsonutil.MustHaveTag(githubIntent{}, "HeadRepoName")
prNumberKey = bsonutil.MustHaveTag(githubIntent{}, "PRNumber")
userKey = bsonutil.MustHaveTag(githubIntent{}, "User")
uidKey = bsonutil.MustHaveTag(githubIntent{}, "UID")
headHashKey = bsonutil.MustHaveTag(githubIntent{}, "HeadHash")
processedKey = bsonutil.MustHaveTag(githubIntent{}, "Processed")
processedAtKey = bsonutil.MustHaveTag(githubIntent{}, "ProcessedAt")
intentTypeKey = bsonutil.MustHaveTag(githubIntent{}, "IntentType")
)
// NewGithubIntent creates an Intent from a google/go-github PullRequestEvent,
// or returns an error if the some part of the struct is invalid
func NewGithubIntent(msgDeliveryID string, pr *github.PullRequest) (Intent, error) {
if pr == nil ||
pr.Base == nil || pr.Base.Repo == nil ||
pr.Head == nil || pr.Head.Repo == nil || pr.Head.Repo.PushedAt == nil ||
pr.User == nil {
return nil, errors.New("incomplete PR")
}
if msgDeliveryID == "" {
return nil, errors.New("Unique msg id cannot be empty")
}
if len(strings.Split(pr.Base.Repo.GetFullName(), "/")) != 2 {
return nil, errors.New("Base repo name is invalid (expected [owner]/[repo])")
}
if pr.Base.GetRef() == "" {
return nil, errors.New("Base ref is empty")
}
if len(strings.Split(pr.Head.Repo.GetFullName(), "/")) != 2 {
return nil, errors.New("Head repo name is invalid (expected [owner]/[repo])")
}
if pr.GetNumber() == 0 {
return nil, errors.New("PR number must not be 0")
}
if pr.User.GetLogin() == "" || pr.User.GetID() == 0 {
return nil, errors.New("Github sender missing login name or uid")
}
if pr.Head.GetSHA() == "" {
return nil, errors.New("Head hash must not be empty")
}
if pr.GetTitle() == "" {
return nil, errors.New("PR title must not be empty")
}
if util.IsZeroTime(pr.Head.Repo.PushedAt.Time) {
return nil, errors.New("pushed at time not set")
}
return &githubIntent{
DocumentID: msgDeliveryID,
MsgID: msgDeliveryID,
BaseRepoName: pr.Base.Repo.GetFullName(),
BaseBranch: pr.Base.GetRef(),
HeadRepoName: pr.Head.Repo.GetFullName(),
PRNumber: pr.GetNumber(),
User: pr.User.GetLogin(),
UID: pr.User.GetID(),
HeadHash: pr.Head.GetSHA(),
Title: pr.GetTitle(),
IntentType: GithubIntentType,
PushedAt: pr.Head.Repo.PushedAt.Time.UTC(),
}, nil
}
// SetProcessed should be called by an amboy queue after creating a patch from an intent.
func (g *githubIntent) SetProcessed() error {
g.Processed = true
g.ProcessedAt = time.Now().UTC().Round(time.Millisecond)
return updateOneIntent(
bson.M{documentIDKey: g.DocumentID},
bson.M{"$set": bson.M{
processedKey: g.Processed,
processedAtKey: g.ProcessedAt,
}},
)
}
// updateOne updates one patch intent.
func updateOneIntent(query interface{}, update interface{}) error {
return db.Update(
IntentCollection,
query,
update,
)
}
// IsProcessed returns whether a patch exists for this intent.
func (g *githubIntent) IsProcessed() bool {
return g.Processed
}
// GetType returns the patch intent, e.g., GithubIntentType.
func (g *githubIntent) GetType() string {
return g.IntentType
}
// Insert inserts a patch intent in the database.
func (g *githubIntent) Insert() error {
g.CreatedAt = time.Now().UTC().Round(time.Millisecond)
err := db.Insert(IntentCollection, g)
if err != nil {
g.CreatedAt = time.Time{}
return err
}
return nil
}
func (g *githubIntent) ID() string {
return g.MsgID
}
func (g *githubIntent) ShouldFinalizePatch() bool {
return true
}
func (g *githubIntent) RequesterIdentity() string {
return evergreen.GithubPRRequester
}
// FindUnprocessedGithubIntents finds all patch intents that have not yet been processed.
func FindUnprocessedGithubIntents() ([]*githubIntent, error) {
var intents []*githubIntent
err := db.FindAllQ(IntentCollection, db.Query(bson.M{processedKey: false, intentTypeKey: GithubIntentType}), &intents)
if err != nil {
return []*githubIntent{}, err
}
return intents, nil
}
func (g *githubIntent) NewPatch() *Patch {
baseRepo := strings.Split(g.BaseRepoName, "/")
headRepo := strings.Split(g.HeadRepoName, "/")
pullURL := fmt.Sprintf("https://github.com/%s/pull/%d", g.BaseRepoName, g.PRNumber)
patchDoc := &Patch{
Id: mgobson.NewObjectId(),
Alias: GithubAlias,
Description: fmt.Sprintf("'%s' pull request #%d by %s: %s (%s)", g.BaseRepoName, g.PRNumber, g.User, g.Title, pullURL),
Author: evergreen.GithubPatchUser,
Status: evergreen.PatchCreated,
CreateTime: g.PushedAt,
GithubPatchData: GithubPatch{
PRNumber: g.PRNumber,
BaseOwner: baseRepo[0],
BaseRepo: baseRepo[1],
BaseBranch: g.BaseBranch,
HeadOwner: headRepo[0],
HeadRepo: headRepo[1],
HeadHash: g.HeadHash,
Author: g.User,
AuthorUID: g.UID,
},
}
return patchDoc
}
func (g *githubIntent) GetAlias() string {
return GithubAlias
}