-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtrigger.go
More file actions
361 lines (294 loc) · 8.92 KB
/
Copy pathtrigger.go
File metadata and controls
361 lines (294 loc) · 8.92 KB
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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
package build
import (
"bytes"
"database/sql"
"database/sql/driver"
"encoding/json"
"strings"
"time"
"djinn-ci.com/database"
"djinn-ci.com/errors"
"github.com/andrewpillar/query"
"github.com/jmoiron/sqlx"
)
type TriggerType uint8
type triggerData map[string]string
// Trigger is the type that represents what triggered a build.
type Trigger struct {
ID int64 `db:"id"`
BuildID int64 `db:"build_id"`
ProviderID sql.NullInt64 `db:"provider_id"`
RepoID sql.NullInt64 `db:"repo_id"`
Type TriggerType `db:"type"`
Comment string `db:"comment"`
Data triggerData `db:"data"`
CreatedAt time.Time `db:"created_at"`
Build *Build `db:"-" gob:"-"`
}
// TriggerStore is the type for creating and modifying Trigger models in the
// database.
type TriggerStore struct {
database.Store
Build *Build
}
//go:generate stringer -type TriggerType -linecomment
const (
// There are three different trigger types for a build trigger,
// Manual - for when a build was manually submitted for either via the API
// or UI.
//
// Push - for when a build was triggered via a commit hook.
// Pull - for when a build was triggered via a pull-request hook.
// Schedule - for when a build was triggered via a cron.
Manual TriggerType = iota // manual
Push // push
Pull // pull
Schedule // schedule
)
var (
_ database.Model = (*Trigger)(nil)
_ database.Binder = (*TriggerStore)(nil)
_ database.Loader = (*TriggerStore)(nil)
_ sql.Scanner = (*triggerData)(nil)
_ driver.Valuer = (*triggerData)(nil)
_ sql.Scanner = (*TriggerType)(nil)
_ driver.Valuer = (*TriggerType)(nil)
triggerTable = "build_triggers"
triggersMap = map[string]TriggerType{
"manual": Manual,
"push": Push,
"pull": Pull,
"schedule": Schedule,
}
)
// NewTriggerStore returns a new TriggerStore for querying the build_triggers
// table. Each database passed to this function will be bound to the returned
// TriggerStore.
func NewTriggerStore(db *sqlx.DB, mm ...database.Model) *TriggerStore {
s := &TriggerStore{
Store: database.Store{DB: db},
}
s.Bind(mm...)
return s
}
// NewTriggerData returns an empty set of data for a build trigger.
func NewTriggerData() triggerData { return triggerData(make(map[string]string)) }
// TriggerModel is called along with database.ModelSlice to convert the given slice of
// Trigger models to a slice of database.Model interfaces.
func TriggerModel(tt []*Trigger) func(int) database.Model {
return func(i int) database.Model {
return tt[i]
}
}
func (t *TriggerType) Scan(val interface{}) error {
b, err := database.Scan(val)
if err != nil {
return errors.Err(err)
}
if len(b) == 0 {
(*t) = TriggerType(0)
return nil
}
return errors.Err(t.UnmarshalText(b))
}
func (t *TriggerType) UnmarshalText(b []byte) error {
var ok bool
s := string(b)
(*t), ok = triggersMap[s]
if !ok {
return errors.New("unknown trigger " + s)
}
return nil
}
func (t TriggerType) Value() (driver.Value, error) { return driver.Value(t.String()), nil }
func (d *triggerData) Scan(val interface{}) error {
b, err := database.Scan(val)
if err != nil {
return errors.Err(err)
}
if len(b) == 0 {
return nil
}
buf := bytes.NewBuffer(b)
dec := json.NewDecoder(buf)
return errors.Err(dec.Decode(d))
}
func (d *triggerData) Set(key, val string) {
if (*d) == nil {
(*d) = make(map[string]string)
}
(*d)[key] = val
}
func (d *triggerData) String() string {
var buf bytes.Buffer
json.NewEncoder(&buf).Encode(d)
return buf.String()[:buf.Len()-1]
}
func (d triggerData) Value() (driver.Value, error) { return driver.Value(d.String()), nil }
// Bind implements the database.Binder interface. This will only bind the models
// if they are pointers to a Build model.
func (t *Trigger) Bind(mm ...database.Model) {
for _, m := range mm {
switch v := m.(type) {
case *Build:
t.Build = v
}
}
}
func (t *Trigger) SetPrimary(i int64) { t.ID = i }
func (t Trigger) Primary() (string, int64) { return "id", t.ID }
// IsZero implements the database.Model interface.
func (t *Trigger) IsZero() bool {
return t == nil || t.ID == 0 &&
t.BuildID == 0 &&
t.Type == TriggerType(0) &&
t.Comment == "" &&
len(t.Data) == 0 &&
t.CreatedAt == time.Time{}
}
// JSON implements the database.Model interface. This will return a map with
// the current Trigger's values under each key.
func (t *Trigger) JSON(_ string) map[string]interface{} {
return map[string]interface{}{
"type": t.Type.String(),
"comment": t.Comment,
"data": t.Data,
}
}
// Endpoint is a stub to fulfill the database.Model interface. It returns an empty
// string.
func (*Trigger) Endpoint(_ ...string) string { return "" }
// Values implements the database.Model interface. This will return a map with
// the following values, build_id, provider_id, type, comment, and data.
func (t Trigger) Values() map[string]interface{} {
return map[string]interface{}{
"build_id": t.BuildID,
"provider_id": t.ProviderID,
"repo_id": t.RepoID,
"type": t.Type,
"comment": t.Comment,
"data": t.Data,
}
}
// CommentBody parses the trigger comment to get the body of the comment. This
// will typically return the lines of the comment that appear after the first
// newline character that is found. If there is no newline character, and the
// trigger comment itself is less than 72 characters in length, then nothing
// is returned. The first 72 characters are summed to be the title of the
// comment.
func (t Trigger) CommentBody() string {
i := strings.Index(t.Comment, "\n")
if i == -1 {
if len(t.Comment) <= 72 {
return ""
}
i = 72
}
body := strings.TrimSpace(t.Comment[i:])
if strings.TrimSpace(t.Comment[:i]) != "" && body != "" {
return "..." + body
}
return body
}
// CommentTitle parses the trigger comment to get the title of the comment.
// This treats the first line of the trigger comment as the title. If that
// first line is longer than 72 characters, then only the first 72 characters
// will be returned.
func (t Trigger) CommentTitle() string {
i := strings.Index(t.Comment, "\n")
if i == -1 {
if len(t.Comment) <= 72 {
return t.Comment
}
i = 72
}
title := strings.TrimSpace(t.Comment[:i])
if strings.TrimSpace(t.Comment[i:]) != "" {
return title + "..."
}
return title
}
// String returns a formatted string of the trigger itself, this will detail the
// user who submitted it, if not nil, and format the comment into the title and
// body.
func (t Trigger) String() string {
buf := bytes.Buffer{}
var username, email string
if t.Build != nil && t.Build.User != nil {
username = t.Build.User.Username
email = t.Build.User.Email
}
switch t.Type {
case Manual:
buf.WriteString("Submitted by " + username + "<" + email + ">\n")
case Push:
buf.WriteString("Committed " + t.Data["sha"][:7] + " to " + t.Data["ref"] + "\n")
case Pull:
buf.WriteString(strings.Title(t.Data["action"]) + " pull request to " + t.Data["ref"] + "\n")
}
if t.Comment != "" {
buf.WriteString("\n" + t.CommentTitle() + "\n\n")
buf.WriteString(t.CommentBody() + "\n")
}
return buf.String()
}
// Bind implements the database.Binder interface. This will only bind the models
// if they are pointers to a Build model.
func (s *TriggerStore) Bind(mm ...database.Model) {
for _, m := range mm {
switch v := m.(type) {
case *Build:
s.Build = v
}
}
}
func (s *TriggerStore) Create(tt ...*Trigger) error {
mm := database.ModelSlice(len(tt), TriggerModel(tt))
return errors.Err(s.Store.Create(triggerTable, mm...))
}
func (s TriggerStore) Get(opts ...query.Option) (*Trigger, error) {
t := &Trigger{
Build: s.Build,
}
opts = append([]query.Option{
database.Where(s.Build, "build_id"),
}, opts...)
err := s.Store.Get(t, triggerTable, opts...)
if err == sql.ErrNoRows {
err = nil
}
return t, errors.Err(err)
}
// All returns a slice of Trigger models, applying each query.Option that is
// given. The database.Where option is used on the Build bound database to limit the
// query to those relations.
func (s TriggerStore) All(opts ...query.Option) ([]*Trigger, error) {
tt := make([]*Trigger, 0)
opts = append([]query.Option{
database.Where(s.Build, "build_id"),
}, opts...)
err := s.Store.All(&tt, triggerTable, opts...)
if err == sql.ErrNoRows {
err = nil
}
for _, t := range tt {
t.Build = s.Build
}
return tt, errors.Err(err)
}
// Load loads in a slice of Trigger models where the given key is in the list of
// given vals. Each database is loaded individually via a call to the given load
// callback. This method calls StageStore.All under the hood, so any bound
// models will impact the models being loaded.
func (s TriggerStore) Load(key string, vals []interface{}, load database.LoaderFunc) error {
tt, err := s.All(query.Where(key, "IN", database.List(vals...)))
if err != nil {
return errors.Err(err)
}
for i := range vals {
for _, t := range tt {
load(i, t)
}
}
return nil
}