package inventory import ( "context" "encoding/json" "time" ) // What has been built. // // A build result was answered to whoever asked and kept nowhere, so "when did this last build", // "why did it fail" and "which machine built what is running" had no answer. Failures are recorded // too: one that leaves no trace is indistinguishable from a build nobody asked for, and the // difference is the whole of whether somebody should be looking at something. // Build is one attempt, whichever way it went. type Build struct { ID string Repository string Ref string // Module is empty for a build that failed before it knew what it was building. Module string Commit string // On is the machine that did it. On string // Failed is the builder's own words, empty when it worked. Failed string Made []Artifact At time.Time } // Artifact is one thing a build published. type Artifact struct { Name string `json:"name"` Kind string `json:"kind"` Reference string `json:"reference"` } // Worked reports whether this build produced something. func (b Build) Worked() bool { return b.Failed == "" } // RecordBuild keeps what a builder said. // // Idempotent on the correlation id, because a result can arrive twice: once as the answer to // whoever asked and once on the exchange when nobody was. Recording both would show one build as // two, and which of the two is real is not a question anybody could answer afterwards. func (i *Inventory) RecordBuild(ctx context.Context, b Build) error { made, err := json.Marshal(b.Made) if err != nil { return err } var module *string if b.Module != "" { module = &b.Module } _, err = i.store.Pool().Exec(ctx, `insert into build (id, repository, ref, module, commit_hash, built_on, failed, made) values ($1, $2, $3, $4, $5, $6, $7, $8) on conflict (id) do nothing`, b.ID, b.Repository, b.Ref, module, b.Commit, b.On, b.Failed, made) return err } // Builds is what has happened lately, newest first. // // For one module when named, or across the mesh when not. Both are asked: *what happened just // now* after something goes wrong, and *what has happened to this* when deciding whether to // trust it. func (i *Inventory) Builds(ctx context.Context, module string, limit int) ([]Build, error) { if limit <= 0 { limit = 20 } query := `select id, repository, ref, coalesce(module,''), commit_hash, built_on, failed, made, at from build order by at desc limit $1` args := []any{limit} if module != "" { query = `select id, repository, ref, coalesce(module,''), commit_hash, built_on, failed, made, at from build where module = $2 order by at desc limit $1` args = append(args, module) } rows, err := i.store.Pool().Query(ctx, query, args...) if err != nil { return nil, err } defer rows.Close() var out []Build for rows.Next() { var b Build var made []byte if err := rows.Scan(&b.ID, &b.Repository, &b.Ref, &b.Module, &b.Commit, &b.On, &b.Failed, &made, &b.At); err != nil { return nil, err } if err := json.Unmarshal(made, &b.Made); err != nil { return nil, err } out = append(out, b) } return out, rows.Err() }