| @@ -30,6 +30,11 @@ type Build struct { |
| 30 | // Trusted is false for a merge request head fetched from another |
30 | // Trusted is false for a merge request head fetched from another |
| 31 | // repository: its steps run without the target's secrets. |
31 | // repository: its steps run without the target's secrets. |
| 32 | Trusted bool |
32 | Trusted bool |
| |
33 | // FailedStep is the 1-based step a failed build stopped at, 0 when it |
| |
34 | // stopped before its first step or did not fail. FailedReason is the |
| |
35 | // runner's one line: "exit 1", "build timed out after 45m0s". |
| |
36 | FailedStep int |
| |
37 | FailedReason string |
| 33 | } |
38 | } |
| 34 | |
39 | |
| 35 | // MaxBuildLog caps a build's stored log; appends past it are dropped. |
40 | // MaxBuildLog caps a build's stored log; appends past it are dropped. |
| @@ -65,14 +70,16 @@ func (s *Store) CreateBuild(repoID int64, job, sha, ref, stepsJSON, image, tree |
| 65 | } |
70 | } |
| 66 | |
71 | |
| 67 | const buildSelect = ` |
72 | const buildSelect = ` |
| 68 | SELECT id, repo_id, number, job, sha, ref, steps, image, tree, status, created_at, started_at, finished_at, log_closed_at, trusted |
73 | SELECT id, repo_id, number, job, sha, ref, steps, image, tree, status, created_at, started_at, finished_at, log_closed_at, trusted, |
| |
74 | failed_step, failed_reason |
| 69 | FROM builds` |
75 | FROM builds` |
| 70 | |
76 | |
| 71 | func scanBuild(row interface{ Scan(...any) error }) (Build, error) { |
77 | func scanBuild(row interface{ Scan(...any) error }) (Build, error) { |
| 72 | var b Build |
78 | var b Build |
| 73 | var trusted int |
79 | var trusted int |
| 74 | err := row.Scan(&b.ID, &b.RepoID, &b.Number, &b.Job, &b.SHA, &b.Ref, &b.Steps, &b.Image, &b.Tree, |
80 | err := row.Scan(&b.ID, &b.RepoID, &b.Number, &b.Job, &b.SHA, &b.Ref, &b.Steps, &b.Image, &b.Tree, |
| 75 | &b.Status, &b.CreatedAt, &b.StartedAt, &b.FinishedAt, &b.LogClosedAt, &trusted) |
81 | &b.Status, &b.CreatedAt, &b.StartedAt, &b.FinishedAt, &b.LogClosedAt, &trusted, |
| |
82 | &b.FailedStep, &b.FailedReason) |
| 76 | b.Trusted = trusted != 0 |
83 | b.Trusted = trusted != 0 |
| 77 | return b, err |
84 | return b, err |
| 78 | } |
85 | } |
| @@ -263,6 +270,21 @@ func (s *Store) FinishBuild(id int64, status string) error { |
| 263 | return nil |
270 | return nil |
| 264 | } |
271 | } |
| 265 | |
272 | |
| |
273 | // SetBuildFailure records where a running build failed. The runner |
| |
274 | // reports it with the outcome; it is written first, so a reader woken |
| |
275 | // by the finish sees both. |
| |
276 | func (s *Store) SetBuildFailure(id int64, step int, reason string) error { |
| |
277 | res, err := s.DB.Exec(`UPDATE builds SET failed_step = ?, failed_reason = ? |
| |
278 | WHERE id = ? AND status = 'running'`, step, reason, id) |
| |
279 | if err != nil { |
| |
280 | return err |
| |
281 | } |
| |
282 | if n, _ := res.RowsAffected(); n == 0 { |
| |
283 | return ErrNotFound |
| |
284 | } |
| |
285 | return nil |
| |
286 | } |
| |
287 | |
| 266 | func (s *Store) BuildByID(id int64) (Build, error) { |
288 | func (s *Store) BuildByID(id int64) (Build, error) { |
| 267 | b, err := scanBuild(s.DB.QueryRow(buildSelect+" WHERE id = ?", id)) |
289 | b, err := scanBuild(s.DB.QueryRow(buildSelect+" WHERE id = ?", id)) |
| 268 | if errors.Is(err, sql.ErrNoRows) { |
290 | if errors.Is(err, sql.ErrNoRows) { |