| @@ -30,6 +30,11 @@ type Build struct { |
| 30 | 30 | // Trusted is false for a merge request head fetched from another |
| 31 | 31 | // repository: its steps run without the target's secrets. |
| 32 | 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 | 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 | 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 | 75 | FROM builds` |
| 70 | 76 | |
| 71 | 77 | func scanBuild(row interface{ Scan(...any) error }) (Build, error) { |
| 72 | 78 | var b Build |
| 73 | 79 | var trusted int |
| 74 | 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 | 83 | b.Trusted = trusted != 0 |
| 77 | 84 | return b, err |
| 78 | 85 | } |
| @@ -263,6 +270,21 @@ func (s *Store) FinishBuild(id int64, status string) error { |
| 263 | 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 | 288 | func (s *Store) BuildByID(id int64) (Build, error) { |
| 267 | 289 | b, err := scanBuild(s.DB.QueryRow(buildSelect+" WHERE id = ?", id)) |
| 268 | 290 | if errors.Is(err, sql.ErrNoRows) { |