-
Notifications
You must be signed in to change notification settings - Fork 24
/
conveyor.go
221 lines (183 loc) · 4.9 KB
/
conveyor.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
package conveyor
import (
"io"
"strings"
"github.com/jmoiron/sqlx"
"github.com/remind101/conveyor/builder"
"github.com/remind101/conveyor/logs"
"golang.org/x/net/context"
"code.google.com/p/go-uuid/uuid"
)
// newID returns a new unique identifier.
var newID = uuid.New
// Conveyor provides the primary api for triggering builds.
type Conveyor struct {
// BuildQueue is used to enqueue a build.
BuildQueue
// Logger is the log storage backend to read and write logs for builds.
Logger logs.Logger
GitHub GitHubAPI
db *sqlx.DB
}
// New returns a new Conveyor instance.
func New(db *sqlx.DB) *Conveyor {
return &Conveyor{db: db}
}
// BuildRequest is provided when triggering a new build.
type BuildRequest struct {
// Repository is the repo to build. This is always required.
Repository string
// Sha is the git commit to build. If this is not provided, and a Branch
// is provided, the sha will be auto-resolved.
Sha string
// Branch is the name of the branch that this build relates to.
Branch string
// Set to true to disable the layer cache. The zero value is to enable
// caching.
NoCache bool
}
// Build enqueues a build to run.
func (c *Conveyor) Build(ctx context.Context, req BuildRequest) (*Build, error) {
// A branch is provied with no sha. Use the GitHub API to resolve the
// branch to the sha of HEAD of the branch.
if req.Sha == "" && req.Branch != "" {
owner, repo := splitRepo(req.Repository)
sha, err := c.GitHub.ResolveBranch(ctx, owner, repo, req.Branch)
if err != nil {
return nil, err
}
req.Sha = sha
}
tx, err := c.db.Beginx()
if err != nil {
return nil, err
}
b := &Build{
Repository: req.Repository,
Sha: req.Sha,
Branch: req.Branch,
}
if err := buildsCreate(tx, b); err != nil {
tx.Rollback()
return b, err
}
// Commit before we push the build into the queue. We need to do this
// because it's possible that two inflight transactions will get
// commited and one will raise an error.
if err := tx.Commit(); err != nil {
return b, err
}
return b, c.BuildQueue.Push(ctx, builder.BuildOptions{
ID: b.ID,
Repository: req.Repository,
Sha: req.Sha,
Branch: req.Branch,
NoCache: req.NoCache,
})
}
// FindBuild finds a build by its identity.
func (c *Conveyor) FindBuild(ctx context.Context, buildIdentity string) (*Build, error) {
tx, err := c.db.Beginx()
if err != nil {
return nil, err
}
var find func(*sqlx.Tx, string) (*Build, error)
switch strings.Contains(buildIdentity, "@") {
case true:
find = buildsFindByRepoSha
default:
find = buildsFindByID
}
b, err := find(tx, buildIdentity)
if err != nil {
tx.Rollback()
return b, err
}
return b, tx.Commit()
}
// FindArtifact finds an artifact by its identity.
func (c *Conveyor) FindArtifact(ctx context.Context, artifactIdentity string) (*Artifact, error) {
tx, err := c.db.Beginx()
if err != nil {
return nil, err
}
var find func(*sqlx.Tx, string) (*Artifact, error)
switch strings.Contains(artifactIdentity, "@") {
case true:
find = artifactsFindByRepoSha
default:
find = artifactsFindByID
}
a, err := find(tx, artifactIdentity)
if err != nil {
tx.Rollback()
return a, err
}
return a, tx.Commit()
}
// Writer returns an io.Writer to write logs for the build.
func (c *Conveyor) Writer(ctx context.Context, buildID string) (io.Writer, error) {
return c.Logger.Create(buildID)
}
// Logs returns an io.Reader to read logs for the build.
func (c *Conveyor) Logs(ctx context.Context, buildID string) (io.Reader, error) {
return c.Logger.Open(buildID)
}
// BuildStarted marks the build as started.
func (c *Conveyor) BuildStarted(ctx context.Context, buildID string) error {
tx, err := c.db.Beginx()
if err != nil {
return err
}
if err := buildsUpdateState(tx, buildID, StateBuilding); err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
// BuildComplete marks a build as successful and adds the image as an artifact.
func (c *Conveyor) BuildComplete(ctx context.Context, buildID, image string) error {
tx, err := c.db.Beginx()
if err != nil {
return err
}
if err := buildsUpdateState(tx, buildID, StateSucceeded); err != nil {
tx.Rollback()
return err
}
if err := artifactsCreate(tx, &Artifact{
BuildID: buildID,
Image: image,
}); err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
// BuildFailed marks the build as failed.
func (c *Conveyor) BuildFailed(ctx context.Context, buildID string, err error) error {
tx, err := c.db.Beginx()
if err != nil {
return err
}
if err := buildsUpdateState(tx, buildID, StateFailed); err != nil {
tx.Rollback()
return err
}
return tx.Commit()
}
func insert(tx *sqlx.Tx, sql string, v interface{}, returns ...interface{}) error {
rows, err := tx.NamedQuery(sql, v)
if err != nil {
return err
}
defer rows.Close()
if rows.Next() {
for _, r := range returns {
rows.Scan(r)
}
} else {
panic("expected id to be returned")
}
return nil
}