forked from remind101/conveyor
-
Notifications
You must be signed in to change notification settings - Fork 0
/
builds.go
142 lines (126 loc) · 3.7 KB
/
builds.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
package conveyor
import (
"database/sql/driver"
"errors"
"fmt"
"strings"
"time"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
)
// ErrDuplicateBuild can be returned when we try to start a build for a sha that
// is already in a "pending" or "building" state. We want to ensure that we only
// have 1 concurrent build for a given sha.
//
// This is also enforced at the db level with the `unique_build` constraint.
var ErrDuplicateBuild = errors.New("a build for this sha is already pending or building")
// The database constraint that counts as an ErrDuplicateBuild.
const uniqueBuildConstraint = "unique_build"
// Build represents a build of a commit.
type Build struct {
// A unique identifier for this build.
ID string `db:"id"`
// Autogenerated sequence id.
Seq int64 `db:"seq"`
// The repository that this build relates to.
Repository string `db:"repository"`
// The branch that this build relates to.
Branch string `db:"branch"`
// The sha that this build relates to.
Sha string `db:"sha"`
// The current state of the build.
State BuildState `db:"state"`
// The time that this build was created.
CreatedAt time.Time `db:"created_at"`
// The time that the build was started.
StartedAt *time.Time `db:"started_at"`
// The time that the build was completed.
CompletedAt *time.Time `db:"completed_at"`
}
type BuildState int
const (
StatePending BuildState = iota
StateBuilding
StateFailed
StateSucceeded
)
func (s BuildState) String() string {
switch s {
case StatePending:
return "pending"
case StateBuilding:
return "building"
case StateFailed:
return "failed"
case StateSucceeded:
return "succeeded"
default:
panic(fmt.Sprintf("unknown build state: %v", s))
}
}
// Scan implements the sql.Scanner interface.
func (s *BuildState) Scan(src interface{}) error {
if v, ok := src.([]byte); ok {
switch string(v) {
case "pending":
*s = StatePending
case "building":
*s = StateBuilding
case "failed":
*s = StateFailed
case "succeeded":
*s = StateSucceeded
default:
return fmt.Errorf("unknown build state: %v", string(v))
}
}
return nil
}
// Value implements the driver.Value interface.
func (s BuildState) Value() (driver.Value, error) {
return driver.Value(s.String()), nil
}
// buildsCreate inserts a new build into the database.
func buildsCreate(tx *sqlx.Tx, b *Build) error {
const createBuildSQL = `INSERT INTO builds (repository, branch, sha, state) VALUES (:repository, :branch, :sha, :state) RETURNING id`
err := insert(tx, createBuildSQL, b, &b.ID)
if err, ok := err.(*pq.Error); ok {
if err.Constraint == uniqueBuildConstraint {
return ErrDuplicateBuild
}
}
return err
}
// buildsFindByID finds a build by ID.
func buildsFindByID(tx *sqlx.Tx, buildID string) (*Build, error) {
const findBuildSQL = `SELECT * FROM builds WHERE id = ? LIMIT 1`
var b Build
err := tx.Get(&b, tx.Rebind(findBuildSQL), buildID)
return &b, err
}
// buildsFindByRepoSha finds a build by repository and sha.
func buildsFindByRepoSha(tx *sqlx.Tx, repoSha string) (*Build, error) {
parts := strings.Split(repoSha, "@")
var sql = `SELECT * FROM builds
WHERE repository = ?
AND sha = ?
ORDER BY seq desc
LIMIT 1`
var b Build
err := tx.Get(&b, tx.Rebind(sql), parts[0], parts[1])
return &b, err
}
// buildsUpdateState changes the state of a build.
func buildsUpdateState(tx *sqlx.Tx, buildID string, state BuildState) error {
var sql string
switch state {
case StateBuilding:
sql = `UPDATE builds SET state = ?, started_at = ? WHERE id = ?`
case StateSucceeded, StateFailed:
sql = `UPDATE builds SET state = ?, completed_at = ? WHERE id = ?`
default:
panic(fmt.Sprintf("not implemented for %s", state))
}
_, err := tx.Exec(tx.Rebind(sql), state, time.Now(), buildID)
return err
}