mesh/merge-gate head c79591531575 merged into trunk aa02e7bf4e78 — pass: builds build-agent, mesh-controller → ace, g14, novox, shanks; no bus step; every …
mesh/repo-check head c79591531575 merged into trunk aa02e7bf4e78 — pass: its merge-check.sh passed
mesh/delivery delivered
Milestone 5 moved 8,599 build records onto the bus under bare build ids, each carrying its module's whole manifest. Every build lookup then read the whole bucket, and a delivery plan's send did that per module per machine while it held the plans and the machine's declaration: the send of build-agent never finished, and every other act and the status waited behind it. Builds now live under <module>.build.<id> (requests under req.<id>, id.<id> names a build's module), with pointer keys newest, worked, held, commit.<sha> and source.<reuse key> that move only to a newer build and are read as hints, repaired when wrong. Manifests are kept once in the same bucket under manifest.sha256-<hex>, so no grant the running bus lacks is needed. The old keys are laid out once at start, idempotently, and left unread. The calls a plan look makes on the silent clocks while it holds the plans are bounded, since the vendored kv.Keys ignores its context.
221 lines
7.5 KiB
Go
221 lines
7.5 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"github.com/novox/mesh-controller/internal/chain"
|
|
)
|
|
|
|
// A build whose source is unchanged is never a move (novox/hq issue 280).
|
|
//
|
|
// The builder says what each build was made from, hashed (its source fingerprint). Two successful
|
|
// builds of a module with one fingerprint are one build, whatever digests they made — an image is not
|
|
// byte-reproducible, and reading a rebuild's new image digest as a new build made a merge that never
|
|
// touched the bus demand a planned bus upgrade before anything could be sent to the machine running
|
|
// it. So:
|
|
//
|
|
// - a module's builds, newest request first, fall into runs of one fingerprint; **the oldest build of
|
|
// the newest run stands for the run**: its artifacts are the ones the mesh registers and hands
|
|
// every module that stands on it (Held), so a rebuild of an unchanged source changes nothing any
|
|
// machine is sent, and nothing standing on it moves either;
|
|
// - a build that failed its gate ends a run: what was put back is never what a later build stands for;
|
|
// - an empty fingerprint — a builder that predates it, a source that does not pin its build — is a
|
|
// run of its own, as every build was before.
|
|
|
|
// sourceRow is one successful build of a module, as the runs are read.
|
|
type sourceRow struct {
|
|
id, commit, fingerprint string
|
|
made []byte
|
|
manifest []byte
|
|
manifestDigest string
|
|
failedGate bool
|
|
}
|
|
|
|
// sourceRows is every successful build of each module (of one module, when named), newest request
|
|
// first.
|
|
func (i *Inventory) sourceRows(ctx context.Context, module string) (map[string][]sourceRow, []string, error) {
|
|
if c, on, err := i.ChainReads(ctx, KindBuilds); err != nil {
|
|
return nil, nil, err
|
|
} else if on {
|
|
return i.chainSourceRows(ctx, c, module)
|
|
}
|
|
query := `select b.module, b.id, b.commit_hash, b.source_fingerprint, b.made, b.manifest,
|
|
coalesce(g.verdict = 'failed', false)
|
|
from build b left join build_gate g on g.build = b.id
|
|
where b.module is not null and b.module <> '' and b.failed = ''`
|
|
args := []any{}
|
|
if module != "" {
|
|
query += ` and b.module = $1`
|
|
args = append(args, module)
|
|
}
|
|
rows, err := i.store.Pool().Query(ctx, query+` order by b.module, coalesce(b.asked, b.at) desc, b.at desc`, args...)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
defer rows.Close()
|
|
out := map[string][]sourceRow{}
|
|
var order []string
|
|
for rows.Next() {
|
|
var m string
|
|
var r sourceRow
|
|
if err := rows.Scan(&m, &r.id, &r.commit, &r.fingerprint, &r.made, &r.manifest, &r.failedGate); err != nil {
|
|
return nil, nil, err
|
|
}
|
|
if _, seen := out[m]; !seen {
|
|
order = append(order, m)
|
|
}
|
|
out[m] = append(out[m], r)
|
|
}
|
|
return out, order, rows.Err()
|
|
}
|
|
|
|
// standing is, of a module's builds newest first, the index of the build that stands for the build at
|
|
// `from`: the oldest of the unbroken run of builds behind it with its source fingerprint.
|
|
func standing(builds []sourceRow, from int) int {
|
|
at := from
|
|
fp := builds[from].fingerprint
|
|
if fp == "" || builds[from].failedGate {
|
|
return at
|
|
}
|
|
for j := from + 1; j < len(builds); j++ {
|
|
if builds[j].fingerprint != fp || builds[j].failedGate {
|
|
break
|
|
}
|
|
at = j
|
|
}
|
|
return at
|
|
}
|
|
|
|
// StandingBuild is the build that stands for a module's build (novox/hq issue 280): the oldest build of
|
|
// the unbroken run of builds with its source fingerprint, at or before it — itself when its fingerprint
|
|
// is empty or it starts the run. Answers its id and the manifest it was recorded with; empty when the
|
|
// build is not a successful build of the module on record.
|
|
func (i *Inventory) StandingBuild(ctx context.Context, module, build string) (string, []byte, error) {
|
|
all, _, err := i.sourceRows(ctx, module)
|
|
if err != nil {
|
|
return "", nil, err
|
|
}
|
|
builds := all[module]
|
|
for k, b := range builds {
|
|
if b.id == build {
|
|
s := builds[standing(builds, k)]
|
|
if len(s.manifest) == 0 && s.manifestDigest != "" {
|
|
// The manifest is kept apart from the record (issue 1121): one Get.
|
|
c, on, err := i.ChainReads(ctx, KindBuilds)
|
|
if err != nil || !on {
|
|
return "", nil, err
|
|
}
|
|
raw, err := manifestOfRecord(ctx, c, chain.Build{ID: s.id, ManifestDigest: s.manifestDigest})
|
|
return s.id, raw, err
|
|
}
|
|
return s.id, s.manifest, nil
|
|
}
|
|
}
|
|
return "", nil, nil
|
|
}
|
|
|
|
// SourceFingerprints is, per module, per commit, the source fingerprint of the newest successful build
|
|
// from it that has one: module → commit → fingerprint. A commit whose builds carry none is absent.
|
|
func (i *Inventory) SourceFingerprints(ctx context.Context) (map[string]map[string]string, error) {
|
|
if c, on, err := i.ChainReads(ctx, KindBuilds); err != nil {
|
|
return nil, err
|
|
} else if on {
|
|
// Read from the commit pointers, which carry each commit's source fingerprint: no record is read (issue 1121).
|
|
return i.chainSourceFingerprints(ctx, c)
|
|
}
|
|
all, _, err := i.sourceRows(ctx, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := map[string]map[string]string{}
|
|
for m, builds := range all {
|
|
for _, b := range builds {
|
|
if b.fingerprint == "" || b.commit == "" {
|
|
continue
|
|
}
|
|
if out[m] == nil {
|
|
out[m] = map[string]string{}
|
|
}
|
|
if _, seen := out[m][b.commit]; !seen {
|
|
out[m][b.commit] = b.fingerprint
|
|
}
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// BuildSourceFingerprints is SourceFingerprints for one module: commit → fingerprint.
|
|
func (i *Inventory) BuildSourceFingerprints(ctx context.Context, module string) (map[string]string, error) {
|
|
if c, on, err := i.ChainReads(ctx, KindBuilds); err != nil {
|
|
return nil, err
|
|
} else if on {
|
|
// This module's commit pointers only (issue 1121).
|
|
return i.chainBuildSourceFingerprints(ctx, c, module)
|
|
}
|
|
all, err := i.SourceFingerprints(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if all[module] == nil {
|
|
return map[string]string{}, nil
|
|
}
|
|
return all[module], nil
|
|
}
|
|
|
|
// heldMade is, per module, what the build standing for its newest successful build made — what Held
|
|
// answers (novox/hq issue 280).
|
|
func (i *Inventory) heldMade(ctx context.Context) (map[string][]Artifact, error) {
|
|
if c, on, err := i.ChainReads(ctx, KindBuilds); err != nil {
|
|
return nil, err
|
|
} else if on {
|
|
return i.chainHeldMade(ctx, c)
|
|
}
|
|
all, _, err := i.sourceRows(ctx, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := map[string][]Artifact{}
|
|
for m, builds := range all {
|
|
if len(builds) == 0 {
|
|
continue
|
|
}
|
|
var made []Artifact
|
|
if err := json.Unmarshal(builds[standing(builds, 0)].made, &made); err != nil {
|
|
// Skipped rather than fatal, as Held always did: the build that needs it says it is missing.
|
|
continue
|
|
}
|
|
out[m] = made
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// SameSourceCommits is the commits of the builds in a build's run (novox/hq issue 280): the build and
|
|
// every build behind it back to the one standing for it, all made from one source. Empty when the
|
|
// build stands for itself — no earlier build was made from its source — so a commit alone never says
|
|
// two builds are one: a dependent rebuilt because its base moved keeps its commit and is a move.
|
|
func (i *Inventory) SameSourceCommits(ctx context.Context, module, build string) (map[string]bool, error) {
|
|
all, _, err := i.sourceRows(ctx, module)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
builds := all[module]
|
|
for k, b := range builds {
|
|
if b.id != build {
|
|
continue
|
|
}
|
|
s := standing(builds, k)
|
|
if s == k {
|
|
return nil, nil
|
|
}
|
|
out := map[string]bool{}
|
|
for j := k; j <= s; j++ {
|
|
if builds[j].commit != "" {
|
|
out[builds[j].commit] = true
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
return nil, nil
|
|
}
|