Files
mesh-controller/internal/inventory/chain_builds.go
T
jschoubben c795915315
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
Keep builds on the bus under their module, so no lookup reads the bucket whole (issue 1121, ADR 0427)
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.
2026-10-18 13:32:31 +02:00

1253 lines
41 KiB
Go

package inventory
import (
"bytes"
"context"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"slices"
"sort"
"strings"
"time"
"github.com/novox/mesh-controller/internal/chain"
)
// Builds on the bus (novox/hq ADR 0408, issue 1090): once the kind `builds` is moved (chain.go) every build is a key of
// the builds bucket, under its id, and every read below is made from the bucket's records. The tables are read when the
// kind is not moved, by the methods these stand beside.
// toChainBuild is a build as the bucket keeps it.
func toChainBuild(b Build) chain.Build {
c := chain.Build{ID: b.ID, Repository: b.Repository, Ref: b.Ref, Module: b.Module, Commit: b.Commit, On: b.On,
Path: b.Path, Against: b.Against, Mirrored: b.Mirrored, SourceFingerprint: b.SourceFingerprint,
ManifestDigest: b.ManifestDigest, Failed: b.Failed, Asked: b.Asked.UTC(), At: b.At.UTC(), ReuseKey: b.ReuseKey, Published: b.Published}
if len(b.Manifest) > 0 {
c.Manifest = json.RawMessage(b.Manifest)
}
for _, r := range b.Read {
c.Read = append(c.Read, chain.ReadRepository{Repository: r.Repository, Ref: r.Ref, Paths: r.Paths, Own: r.Own})
}
for _, s := range b.Sources {
c.Sources = append(c.Sources, chain.Source{Repository: s.Repository, Ref: s.Ref, Paths: s.Paths})
}
for _, a := range b.Made {
c.Made = append(c.Made, chain.Artifact{Name: a.Name, Kind: a.Kind, Reference: a.Reference})
}
if c.Published == nil {
c.Published = publishedOf(b.Made)
}
return c
}
// fromChainBuild is a build as the mesh reads it.
func fromChainBuild(c chain.Build) Build {
b := Build{ID: c.ID, Repository: c.Repository, Ref: c.Ref, Module: c.Module, Commit: c.Commit, On: c.On, Path: c.Path,
Manifest: []byte(c.Manifest), ManifestDigest: c.ManifestDigest, Against: c.Against, Mirrored: c.Mirrored, SourceFingerprint: c.SourceFingerprint,
Failed: c.Failed, Asked: c.Asked, At: c.At, ReuseKey: c.ReuseKey, Published: c.Published}
if len(b.Manifest) == 0 {
b.Manifest = nil
}
for _, r := range c.Read {
b.Read = append(b.Read, ReadRepository{Repository: r.Repository, Ref: r.Ref, Paths: r.Paths, Own: r.Own})
}
for _, s := range c.Sources {
b.Sources = append(b.Sources, BuildSource{Repository: s.Repository, Ref: s.Ref, Paths: s.Paths})
}
for _, a := range c.Made {
b.Made = append(b.Made, Artifact{Name: a.Name, Kind: a.Kind, Reference: a.Reference})
}
return b
}
// publishedOf is what a build published, read from what it made: a package by name and version, and an artifact by the
// digest of the first thing it made that has one (`<reference>@sha256:…`). Nil for a build that published nothing.
func publishedOf(made []Artifact) *chain.Published {
var out chain.Published
for _, a := range made {
switch {
case a.Kind == "package":
if name, version, ok := strings.Cut(a.Reference, "@"); ok && name != "" && version != "" && out.Package == nil {
out.Package = &chain.Package{Name: name, Version: version}
}
case out.Artifact == "":
if _, digest, ok := strings.Cut(a.Reference, "@"); ok && strings.HasPrefix(digest, "sha256:") {
out.Artifact = digest
}
}
}
if out.Artifact == "" && out.Package == nil {
return nil
}
return &out
}
// legacyBuildKey is the key a build had before the layout under its module (issue 1121): its id alone. The keys stay (ADR
// 0408 §4) and nothing reads them once the kind builds-layout is moved.
func legacyBuildKey(id string) string { return id }
// chainRecordBuild keeps a build under its module and id. **Idempotent on the id**, as the table's insert was: a result
// arrives twice, and the first record stands. A request nobody has answered is the record `req.<id>` in state asked (issue
// 1091); the outcome is written under its module and keeps the request it answers, and the request's record then says it was
// answered. The build's manifest goes to the manifests bucket once, and the record names its digest (issue 1121).
func (i *Inventory) chainRecordBuild(ctx context.Context, c *chain.Chain, b Build) error {
if b.At.IsZero() {
b.At = time.Now().UTC()
}
if slices.Contains(chain.ReservedModules, b.Module) {
return fmt.Errorf("build %s is not kept on the bus: %q is a name the bucket keeps for itself, not a module's", b.ID, b.Module)
}
digest, err := i.keepManifest(ctx, c, b.Manifest)
if err != nil {
return fmt.Errorf("build %s is not kept on the bus: its manifest: %w", b.ID, err)
}
key, reqKey := chain.BuildKey(b.Module, b.ID), chain.RequestKey(b.ID)
for try := 0; try < casTries; try++ {
next := toChainBuild(b)
if digest != "" {
next.Manifest, next.ManifestDigest = nil, digest
}
cur, found, err := c.Builds.Get(ctx, key)
if err != nil {
return fmt.Errorf("build %s is not kept on the bus: %w", b.ID, err)
}
var asked chain.Kept[chain.Build]
var askedFound bool
if key != reqKey {
if asked, askedFound, err = c.Builds.Get(ctx, reqKey); err != nil {
return fmt.Errorf("build %s is not kept on the bus: %w", b.ID, err)
}
}
switch {
case !found && askedFound:
next.Request = asked.Value.Request
_, err = c.Builds.Create(ctx, key, next)
if errors.Is(err, chain.ErrExists) {
continue
}
case !found:
_, err = c.Builds.Create(ctx, key, next)
if errors.Is(err, chain.ErrExists) {
continue
}
case key == reqKey && cur.Value.State == chain.BuildAsked:
next.Request = cur.Value.Request
_, err = c.Builds.Update(ctx, key, next, cur.Revision)
if errors.Is(err, chain.ErrMoved) {
continue
}
default:
// An outcome stands: the first record is the record. Its id pointer is made if a crash left it out.
if _, err := i.pointTo(ctx, c, chain.IDKey(b.ID), idPointer(b.Module), false); err != nil {
return err
}
return i.RecordMirrored(ctx, b.ID, "", b.Mirrored)
}
if err != nil {
return fmt.Errorf("build %s is not kept on the bus: %w", b.ID, err)
}
if err := i.pointAt(ctx, c, next, chainFingerprint(fromChainBuild(next))); err != nil {
return err
}
if askedFound && asked.Value.State == chain.BuildAsked {
i.answerRequest(ctx, c, reqKey, next)
}
return i.RecordMirrored(ctx, b.ID, "", b.Mirrored)
}
return fmt.Errorf("build %s is not kept on the bus: it kept moving under the write", b.ID)
}
// idPointer is what `id.<id>` holds: the build's module, or the request prefix for a build that knew none.
func idPointer(module string) []byte {
if module == "" {
return []byte(chain.RequestPrefix)
}
return []byte(module)
}
// pointTo makes a pointer key hold value, by compare-and-set: made when it is absent, and, when overwrite, changed when it
// holds something else.
func (i *Inventory) pointTo(ctx context.Context, c *chain.Chain, key string, value []byte, overwrite bool) (bool, error) {
for try := 0; try < casTries; try++ {
e, found, err := c.Builds.GetRaw(ctx, key)
if err != nil {
return false, fmt.Errorf("%s is not read on the bus: %w", key, err)
}
switch {
case !found:
_, err = c.Builds.CreateRaw(ctx, key, value)
if errors.Is(err, chain.ErrExists) {
continue
}
case !overwrite || bytes.Equal(e.Value, value):
return false, nil
default:
_, err = c.Builds.UpdateRaw(ctx, key, value, e.Revision)
if errors.Is(err, chain.ErrMoved) {
continue
}
}
if err != nil {
return false, fmt.Errorf("%s is not kept on the bus: %w", key, err)
}
return true, nil
}
return false, fmt.Errorf("%s kept moving under the write", key)
}
// pointAt moves the pointers a build written now is the answer to: its id's module, the module's newest, and for a success
// the module's worked, held, commit and source (issue 1121).
func (i *Inventory) pointAt(ctx context.Context, c *chain.Chain, rec chain.Build, fingerprint string) error {
if _, err := i.pointTo(ctx, c, chain.IDKey(rec.ID), idPointer(rec.Module), false); err != nil {
return err
}
if rec.Module == "" {
return nil
}
if _, err := i.pointIfNewer(ctx, c, chain.NewestKey(rec.Module), []byte(rec.ID), rec); err != nil {
return err
}
if rec.Failed != "" {
return nil
}
// Held: the run of builds made from one source stands on its first. The previous success is read before it is replaced;
// a build older than it, finishing late, moves neither.
hold, older := true, false
if prev, found, err := c.Builds.GetRaw(ctx, chain.WorkedKey(rec.Module)); err != nil {
return err
} else if found {
if old, found, err := c.Builds.Get(ctx, chain.BuildKey(rec.Module, pointedID(prev.Value))); err != nil {
return err
} else if found {
older = !buildNewer(rec, old.Value)
hold = rec.SourceFingerprint == "" || old.Value.SourceFingerprint != rec.SourceFingerprint
}
}
if !older {
if _, err := i.pointIfNewer(ctx, c, chain.WorkedKey(rec.Module), []byte(rec.ID), rec); err != nil {
return err
}
if _, found, err := c.Builds.GetRaw(ctx, chain.HeldKey(rec.Module)); err != nil {
return err
} else if hold || !found {
if _, err := i.pointIfNewer(ctx, c, chain.HeldKey(rec.Module), []byte(rec.ID), rec); err != nil {
return err
}
}
}
if rec.Commit != "" {
body, err := json.Marshal(chain.CommitPointer{Build: rec.ID, Source: rec.SourceFingerprint, Fingerprint: fingerprint})
if err != nil {
return err
}
if _, err := i.pointIfNewer(ctx, c, chain.CommitKey(rec.Module, rec.Commit), body, rec); err != nil {
return err
}
}
if rec.ReuseKey != "" {
_, err := i.pointIfNewer(ctx, c, chain.SourceKey(rec.Module, chain.SpellReuse(rec.ReuseKey)), []byte(rec.ID), rec)
return err
}
return nil
}
// buildNewer is whether a is a newer build than b in the order every "what a module currently is" question uses: the newest
// request first, then the newest record, then the id (newestRequestFirst).
func buildNewer(a, b chain.Build) bool {
x, y := fromChainBuild(a).AskedOrAt(), fromChainBuild(b).AskedOrAt()
if !x.Equal(y) {
return x.After(y)
}
if !a.At.Equal(b.At) {
return a.At.After(b.At)
}
return a.ID > b.ID
}
// pointIfNewer makes a pointer name rec by compare-and-set on the pointer's own revision, unless the build it names now is
// newer: **a pointer follows the newest build, not the last write** (issue 1121), so an older build that finishes late
// leaves it alone.
func (i *Inventory) pointIfNewer(ctx context.Context, c *chain.Chain, key string, value []byte, rec chain.Build) (bool, error) {
for try := 0; try < casTries; try++ {
e, found, err := c.Builds.GetRaw(ctx, key)
if err != nil {
return false, fmt.Errorf("%s is not read on the bus: %w", key, err)
}
if found {
if bytes.Equal(e.Value, value) {
return false, nil
}
cur, has, err := c.Builds.Get(ctx, chain.BuildKey(rec.Module, pointedID(e.Value)))
if err != nil {
return false, err
}
if has && cur.Value.State == "" && !buildNewer(rec, cur.Value) {
return false, nil
}
_, err = c.Builds.UpdateRaw(ctx, key, value, e.Revision)
if errors.Is(err, chain.ErrMoved) {
continue
}
if err != nil {
return false, fmt.Errorf("%s is not kept on the bus: %w", key, err)
}
return true, nil
}
_, err = c.Builds.CreateRaw(ctx, key, value)
if errors.Is(err, chain.ErrExists) {
continue
}
if err != nil {
return false, fmt.Errorf("%s is not kept on the bus: %w", key, err)
}
return true, nil
}
return false, fmt.Errorf("%s kept moving under the write", key)
}
// answerRequest says on the request's record that a build answered it. Said and not failed on: the outcome stands, and the
// request is read as unheard until a later write makes this one.
func (i *Inventory) answerRequest(ctx context.Context, c *chain.Chain, reqKey string, out chain.Build) {
for try := 0; try < casTries; try++ {
cur, found, err := c.Builds.Get(ctx, reqKey)
if err != nil || !found || cur.Value.State != chain.BuildAsked {
return
}
cur.Value.State, cur.Value.Module, cur.Value.Failed, cur.Value.At = chain.BuildAnswered, out.Module, out.Failed, out.At
if _, err := c.Builds.Update(ctx, reqKey, cur.Value, cur.Revision); !errors.Is(err, chain.ErrMoved) {
return
}
}
}
// manifestBytes is a manifest as the bucket keeps it: its canonical form, nil when the build kept none.
func manifestBytes(raw []byte) []byte {
if t := bytes.TrimSpace(raw); len(t) == 0 || string(t) == "null" {
return nil
}
return canonicalJSON(raw)
}
// keepManifest keeps a manifest in the manifests bucket under the digest of its canonical bytes, once, and answers the
// digest; empty when the build kept none or the chain keeps manifests in the record.
func (i *Inventory) keepManifest(ctx context.Context, c *chain.Chain, raw []byte) (string, error) {
body := manifestBytes(raw)
if body == nil || !c.Kept {
return "", nil
}
key := chain.ManifestKey(body)
if _, err := c.Manifests.CreateRaw(ctx, key, body); err != nil && !errors.Is(err, chain.ErrExists) {
return "", err
}
return key, nil
}
// manifestOfRecord is the manifest a record names, or keeps.
func manifestOfRecord(ctx context.Context, c *chain.Chain, rec chain.Build) ([]byte, error) {
if rec.ManifestDigest == "" {
return []byte(rec.Manifest), nil
}
e, found, err := c.Manifests.GetRaw(ctx, rec.ManifestDigest)
if err != nil {
return nil, fmt.Errorf("the manifest %s of build %s cannot be read: %w", rec.ManifestDigest, rec.ID, err)
}
if !found {
return nil, fmt.Errorf("build %s names the manifest %s and the bus does not hold it", rec.ID, rec.ManifestDigest)
}
return e.Value, nil
}
// casTries is how often a write that lost a compare-and-set reads the key again and tries once more.
const casTries = 8
// buildsIn is the records of the builds bucket under the keys a filter matches, read in one pass of a watch the server
// filters; the pointers among them are left out.
func buildsIn(ctx context.Context, c *chain.Chain, filter string) ([]chain.Kept[chain.Build], error) {
raw, err := c.Builds.FilteredRaw(ctx, filter)
if err != nil {
return nil, fmt.Errorf("the builds cannot be read from the bus: %w", err)
}
out := make([]chain.Kept[chain.Build], 0, len(raw))
for key, e := range raw {
if !chain.IsRecordKey(key) && !strings.HasPrefix(key, chain.RequestPrefix+".") {
continue
}
k, err := chain.DecodeKept[chain.Build](e)
if err != nil {
return nil, err
}
out = append(out, k)
}
return out, nil
}
func outcomesOf(kept []chain.Kept[chain.Build]) []Build {
out := make([]Build, 0, len(kept))
for _, k := range kept {
// A request nobody has answered is no build: it has no outcome to read.
if k.Value.State == chain.BuildAsked || k.Value.State == chain.BuildAnswered {
continue
}
out = append(out, fromChainBuild(k.Value))
}
return out
}
// chainBuilds is every build of every module, whole, in no order: the modules named by their `newest` pointers, each read by
// its own keys (the manifests kept in the same bucket are never in a filter), and the builds that knew no module.
func chainBuilds(ctx context.Context, c *chain.Chain) ([]Build, error) {
pointers, err := c.Builds.FilteredRaw(ctx, "*.newest")
if err != nil {
return nil, fmt.Errorf("the builds cannot be read from the bus: %w", err)
}
var out []Build
for key := range pointers {
module := chain.ModuleOf(key)
if slices.Contains(chain.ReservedModules, module) {
continue
}
builds, err := chainModuleBuilds(ctx, c, module)
if err != nil {
return nil, err
}
out = append(out, builds...)
}
kept, err := buildsIn(ctx, c, chain.RequestPrefix+".*")
if err != nil {
return nil, err
}
return append(out, outcomesOf(kept)...), nil
}
// chainModuleBuilds is every build of one module, in no order: the keys `<module>.>` and no other.
func chainModuleBuilds(ctx context.Context, c *chain.Chain, module string) ([]Build, error) {
kept, err := buildsIn(ctx, c, chain.ModuleFilter(module))
if err != nil {
return nil, err
}
return outcomesOf(kept), nil
}
// chainBuildsOf is the builds of a module, or of every module when none is named.
func chainBuildsOf(ctx context.Context, c *chain.Chain, module string) ([]Build, error) {
if module == "" {
return chainBuilds(ctx, c)
}
return chainModuleBuilds(ctx, c, module)
}
// pointedBuilds is, per module, the build a family of pointers (`*.newest`, `*.worked`, `*.held`) names, each read by one
// Get and checked: a survey of every module reads one record of each and not their histories.
func (i *Inventory) pointedBuilds(ctx context.Context, c *chain.Chain, filter string) (map[string]chain.Build, error) {
raw, err := c.Builds.FilteredRaw(ctx, filter)
if err != nil {
return nil, fmt.Errorf("the builds cannot be read from the bus: %w", err)
}
worked := strings.HasSuffix(filter, ".worked")
out := make(map[string]chain.Build, len(raw))
for key := range raw {
module := chain.ModuleOf(key)
if slices.Contains(chain.ReservedModules, module) {
continue
}
rec, found, err := i.followPointer(ctx, c, module, key, func(b chain.Build) bool { return !worked || b.Failed == "" })
if err != nil {
return nil, err
}
if found {
out[module] = rec
}
}
return out, nil
}
// pointedID is the build id a pointer holds: a commit pointer's build, or the id itself.
func pointedID(value []byte) string {
if len(value) > 0 && value[0] == '{' {
var p chain.CommitPointer
if json.Unmarshal(value, &p) == nil {
return p.Build
}
}
return string(value)
}
// followPointer reads the record a pointer of a module names, and checks it: it is the module's, it is an outcome, and ok
// agrees. **A pointer is a hint** (issue 1121): when it is missing or the record is not what it was for, the module's records
// are read, the pointers made again and said, and the pointer is followed once more.
func (i *Inventory) followPointer(ctx context.Context, c *chain.Chain, module, pointer string, ok func(chain.Build) bool) (chain.Build, bool, error) {
for pass := 0; pass < 2; pass++ {
e, found, err := c.Builds.GetRaw(ctx, pointer)
if err != nil {
return chain.Build{}, false, err
}
if found {
k, has, err := c.Builds.Get(ctx, chain.BuildKey(module, pointedID(e.Value)))
if err != nil {
return chain.Build{}, false, err
}
if has && k.Value.Module == module && k.Value.State == "" && ok(k.Value) {
return k.Value, true, nil
}
}
if pass == 1 {
break
}
if err := i.repairPointers(ctx, c, module, pointer); err != nil {
return chain.Build{}, false, err
}
}
return chain.Build{}, false, nil
}
// repairPointers reads a module's records and makes its pointers what they say, saying each it changed.
func (i *Inventory) repairPointers(ctx context.Context, c *chain.Chain, module, why string) error {
kept, err := buildsIn(ctx, c, chain.ModuleFilter(module))
if err != nil {
return err
}
var recs []chain.Build
for _, k := range kept {
if k.Value.State == "" {
recs = append(recs, k.Value)
}
}
points, err := pointsOf(module, recs)
if err != nil {
return err
}
for _, p := range points {
wrote, err := i.pointTo(ctx, c, p.key, p.value, true)
if err != nil {
return err
}
if wrote {
fmt.Printf("builds: the pointer %s was missing or wrong (a lookup of %s) and is repaired from the records of %s\n", p.key, why, module)
}
}
return nil
}
type pointer struct {
key string
value []byte
}
// pointsOf are the pointers of a module as its records say: the newest, the newest success, the held, and per commit and per
// reuse key the newest success. The records are in no order.
func pointsOf(module string, recs []chain.Build) ([]pointer, error) {
if len(recs) == 0 {
return nil, nil
}
sort.SliceStable(recs, func(a, b int) bool { return buildNewer(recs[b], recs[a]) })
points := []pointer{{chain.NewestKey(module), []byte(recs[len(recs)-1].ID)}}
var lastWorked, held *chain.Build
commits, sources := map[string]chain.Build{}, map[string]chain.Build{}
for k := range recs {
rec := recs[k]
if rec.Failed != "" {
continue
}
if held == nil || rec.SourceFingerprint == "" || lastWorked == nil || lastWorked.SourceFingerprint != rec.SourceFingerprint {
held = &recs[k]
}
lastWorked = &recs[k]
if rec.Commit != "" {
commits[rec.Commit] = rec
}
if rec.ReuseKey != "" {
sources[rec.ReuseKey] = rec
}
}
if lastWorked != nil {
points = append(points, pointer{chain.WorkedKey(module), []byte(lastWorked.ID)}, pointer{chain.HeldKey(module), []byte(held.ID)})
}
for commit, rec := range commits {
body, err := json.Marshal(chain.CommitPointer{Build: rec.ID, Source: rec.SourceFingerprint, Fingerprint: chainFingerprint(fromChainBuild(rec))})
if err != nil {
return nil, err
}
points = append(points, pointer{chain.CommitKey(module, commit), body})
}
for key, rec := range sources {
points = append(points, pointer{chain.SourceKey(module, chain.SpellReuse(key)), []byte(rec.ID)})
}
return points, nil
}
// newestRequestOrder sorts builds as newestRequestFirst does: the newest request first, then the newest record.
func newestRequestOrder(builds []Build) {
sort.SliceStable(builds, func(a, b int) bool {
x, y := builds[a].AskedOrAt(), builds[b].AskedOrAt()
if !x.Equal(y) {
return x.After(y)
}
if !builds[a].At.Equal(builds[b].At) {
return builds[a].At.After(builds[b].At)
}
return builds[a].ID > builds[b].ID
})
}
// byModule sorts builds by module, then newest request first.
func byModule(builds []Build) {
newestRequestOrder(builds)
sort.SliceStable(builds, func(a, b int) bool { return builds[a].Module < builds[b].Module })
}
// worked is the builds that made something for a module: succeeded, and knew what they built.
func worked(all []Build) []Build {
var out []Build
for _, b := range all {
if b.Failed == "" && b.Module != "" {
out = append(out, b)
}
}
return out
}
func (i *Inventory) chainBuildList(ctx context.Context, c *chain.Chain, module string, limit int) ([]Build, error) {
all, err := chainBuildsOf(ctx, c, module)
if err != nil {
return nil, err
}
var out []Build
for _, b := range all {
if module == "" || b.Module == module {
out = append(out, Build{ID: b.ID, Repository: b.Repository, Ref: b.Ref, Module: b.Module, Commit: b.Commit,
On: b.On, Failed: b.Failed, Made: b.Made, Asked: b.Asked, At: b.At, ReuseKey: b.ReuseKey, Published: b.Published})
}
}
sort.SliceStable(out, func(a, b int) bool {
if !out[a].At.Equal(out[b].At) {
return out[a].At.After(out[b].At)
}
return out[a].ID > out[b].ID
})
if len(out) > limit {
out = out[:limit]
}
return out, nil
}
// chainRecordOf is the record of a build known by its id alone: the pointer `id.<id>` names its module, and the record is
// one Get more. A request no outcome has answered is no build.
func (i *Inventory) chainRecordOf(ctx context.Context, c *chain.Chain, id string) (chain.Build, bool, error) {
ptr, found, err := c.Builds.GetRaw(ctx, chain.IDKey(id))
if err != nil {
return chain.Build{}, false, err
}
module := ""
if found && string(ptr.Value) != chain.RequestPrefix {
module = string(ptr.Value)
}
k, ok, err := c.Builds.Get(ctx, chain.BuildKey(module, id))
if err != nil || !ok || k.Value.State == chain.BuildAsked || k.Value.State == chain.BuildAnswered {
return chain.Build{}, false, err
}
return k.Value, true, nil
}
func (i *Inventory) chainBuildByID(ctx context.Context, c *chain.Chain, id string) (Build, bool, error) {
rec, ok, err := i.chainRecordOf(ctx, c, id)
if err != nil || !ok {
return Build{}, false, err
}
b := fromChainBuild(rec)
return Build{ID: b.ID, Repository: b.Repository, Ref: b.Ref, Module: b.Module, Commit: b.Commit, On: b.On,
Failed: b.Failed, Made: b.Made, Path: b.Path, Asked: b.Asked, At: b.At}, true, nil
}
func (i *Inventory) chainBuiltAgainst(ctx context.Context, c *chain.Chain) (map[string][]string, error) {
worked, err := i.pointedBuilds(ctx, c, "*.worked")
if err != nil {
return nil, err
}
out := map[string][]string{}
for module, b := range worked {
if len(b.Against) > 0 {
out[module] = b.Against
}
}
return out, nil
}
func (i *Inventory) chainReadRepositories(ctx context.Context, c *chain.Chain) (map[string][]ReadRepository, error) {
// The newest build of each module whatever its outcome: a failed one newer than the newest that worked leaves that
// one's build source stale. Each is one record read through its pointer.
type newestOf struct {
at time.Time
failed bool
}
tried, err := i.pointedBuilds(ctx, c, "*.newest")
if err != nil {
return nil, err
}
newest := map[string]newestOf{}
for module, rec := range tried {
b := fromChainBuild(rec)
newest[module] = newestOf{b.AskedOrAt(), b.Failed != ""}
}
workedRecs, err := i.pointedBuilds(ctx, c, "*.worked")
if err != nil {
return nil, err
}
ok := make([]Build, 0, len(workedRecs))
for _, rec := range workedRecs {
ok = append(ok, fromChainBuild(rec))
}
byModule(ok)
owns, err := i.moduleSources(ctx)
if err != nil {
return nil, err
}
read := map[string][]ReadRepository{}
seen := map[string]bool{}
for _, b := range ok {
if seen[b.Module] {
continue
}
seen[b.Module] = true
built := b.AskedOrAt()
of := append([]ReadRepository(nil), b.Read...)
sources := b.Sources
whole := ""
if n, known := newest[b.Module]; known && n.failed && n.at.After(built) && len(sources) > 0 {
sources, whole = nil, "a newer build of it failed"
}
of = WithBuildSources(of, sources)
if own, has := owns[b.Module]; has {
of = OwnRepositoryAsOwn(of, own)
}
if whole != "" {
of = append(of, ReadRepository{Own: true, Whole: whole})
}
if len(of) > 0 {
for k := range of {
of[k].Built, of[k].Looked = built, newest[b.Module].at
}
read[b.Module] = of
}
}
return read, nil
}
// moduleSources is the repository each registered module is registered from.
func (i *Inventory) moduleSources(ctx context.Context) (map[string]string, error) {
rows, err := i.store.Pool().Query(ctx, `select name, source from module where source is not null`)
if err != nil {
return nil, err
}
defer rows.Close()
out := map[string]string{}
for rows.Next() {
var name, source string
if err := rows.Scan(&name, &source); err != nil {
return nil, err
}
out[name] = source
}
return out, rows.Err()
}
func (i *Inventory) chainAnnounceable(ctx context.Context, c *chain.Chain) ([]Build, error) {
all, err := chainBuilds(ctx, c)
if err != nil {
return nil, err
}
var ok []Build
for _, b := range worked(all) {
if b.Commit != "" {
ok = append(ok, b)
}
}
newestRequestOrder(ok)
type key struct{ module, commit string }
seen := map[key]bool{}
var out []Build
for _, b := range ok {
k := key{b.Module, b.Commit}
if seen[k] {
continue
}
seen[k] = true
out = append(out, Build{ID: b.ID, Repository: b.Repository, Ref: b.Ref, Module: b.Module, Commit: b.Commit,
On: b.On, Failed: b.Failed, Made: b.Made, Path: b.Path, Manifest: b.Manifest, ManifestDigest: b.ManifestDigest,
Against: b.Against, At: b.At})
}
sort.SliceStable(out, func(a, b int) bool { return out[a].At.Before(out[b].At) })
if err := fillManifests(ctx, c, out); err != nil {
return nil, err
}
return out, nil
}
// fillManifests reads the manifests the builds name, each digest once.
func fillManifests(ctx context.Context, c *chain.Chain, builds []Build) error {
seen := map[string][]byte{}
for k := range builds {
d := builds[k].ManifestDigest
if d == "" || len(builds[k].Manifest) > 0 {
continue
}
raw, ok := seen[d]
if !ok {
var err error
if raw, err = manifestOfRecord(ctx, c, chain.Build{ID: builds[k].ID, ManifestDigest: d}); err != nil {
return err
}
seen[d] = raw
}
builds[k].Manifest = raw
}
return nil
}
// failedAtAGate is the builds that failed their gate. The gate's verdicts stay in their table (issue 1097).
func (i *Inventory) failedAtAGate(ctx context.Context) (map[string]bool, error) {
rows, err := i.store.Pool().Query(ctx, `select build from build_gate where verdict = 'failed'`)
if err != nil {
return nil, err
}
defer rows.Close()
out := map[string]bool{}
for rows.Next() {
var id string
if err := rows.Scan(&id); err != nil {
return nil, err
}
out[id] = true
}
return out, rows.Err()
}
func (i *Inventory) chainSourceRows(ctx context.Context, c *chain.Chain, module string) (map[string][]sourceRow, []string, error) {
all, err := chainBuildsOf(ctx, c, module)
if err != nil {
return nil, nil, err
}
failed, err := i.failedAtAGate(ctx)
if err != nil {
return nil, nil, err
}
ok := worked(all)
byModule(ok)
out := map[string][]sourceRow{}
var order []string
for _, b := range ok {
if module != "" && b.Module != module {
continue
}
made, err := json.Marshal(b.Made)
if err != nil {
return nil, nil, err
}
if _, seen := out[b.Module]; !seen {
order = append(order, b.Module)
}
out[b.Module] = append(out[b.Module], sourceRow{id: b.ID, commit: b.Commit, fingerprint: b.SourceFingerprint,
made: made, manifest: b.Manifest, manifestDigest: b.ManifestDigest, failedGate: failed[b.ID]})
}
return out, order, nil
}
// canonical is a JSON document encoded the one way (objects' keys sorted, compact), so the same content hashes alike
// however the bucket's record spells it.
func canonicalJSON(raw []byte) []byte {
if len(bytes.TrimSpace(raw)) == 0 {
return nil
}
out, err := canonical(raw)
if err != nil {
return raw
}
return out
}
// chainFingerprint is what a build would put on a machine, hashed: its artifacts and its manifest, the manifest by its
// digest (the digest of its canonical bytes, which the manifests bucket keys it by).
func chainFingerprint(b Build) string {
made, _ := json.Marshal(b.Made)
digest := b.ManifestDigest
if digest == "" {
if body := manifestBytes(b.Manifest); body != nil {
digest = chain.ManifestKey(body)
}
}
sum := sha256.Sum256(append(append(canonicalJSON(made), 0), digest...))
return hex.EncodeToString(sum[:])
}
// commitPointers are, per commit of a module (every module when none is named), the pointer to the newest successful build
// from it: a read of the keys `<module>.commit.*` and no record.
func commitPointers(ctx context.Context, c *chain.Chain, module string) (map[string]map[string]chain.CommitPointer, error) {
filter := "*.commit.*"
if module != "" {
filter = module + ".commit.*"
}
raw, err := c.Builds.FilteredRaw(ctx, filter)
if err != nil {
return nil, fmt.Errorf("the builds cannot be read from the bus: %w", err)
}
out := map[string]map[string]chain.CommitPointer{}
for key, e := range raw {
var p chain.CommitPointer
if err := json.Unmarshal(e.Value, &p); err != nil {
return nil, fmt.Errorf("the pointer %s is not readable: %w", key, err)
}
m, commit, _ := strings.Cut(key, ".commit.")
if out[m] == nil {
out[m] = map[string]chain.CommitPointer{}
}
out[m][commit] = p
}
return out, nil
}
func (i *Inventory) chainBuildFingerprints(ctx context.Context, c *chain.Chain, module string) (map[string]string, error) {
pointers, err := commitPointers(ctx, c, module)
if err != nil {
return nil, err
}
out := map[string]string{}
for commit, p := range pointers[module] {
out[commit] = p.Fingerprint
}
return out, nil
}
func (i *Inventory) chainFingerprints(ctx context.Context, c *chain.Chain) (map[string]map[string]string, error) {
pointers, err := commitPointers(ctx, c, "")
if err != nil {
return nil, err
}
out := map[string]map[string]string{}
for m, commits := range pointers {
out[m] = map[string]string{}
for commit, p := range commits {
out[m][commit] = p.Fingerprint
}
}
return out, nil
}
// chainBuildSourceFingerprints is chainSourceFingerprints for one module: commit → fingerprint, from its commit pointers.
func (i *Inventory) chainBuildSourceFingerprints(ctx context.Context, c *chain.Chain, module string) (map[string]string, error) {
pointers, err := commitPointers(ctx, c, module)
if err != nil {
return nil, err
}
out := map[string]string{}
for commit, p := range pointers[module] {
if p.Source != "" {
out[commit] = p.Source
}
}
return out, nil
}
// chainSourceFingerprints is, per module and commit, the source fingerprint of the newest successful build from it that has
// one, read from the commit pointers.
func (i *Inventory) chainSourceFingerprints(ctx context.Context, c *chain.Chain) (map[string]map[string]string, error) {
pointers, err := commitPointers(ctx, c, "")
if err != nil {
return nil, err
}
out := map[string]map[string]string{}
for m, commits := range pointers {
for commit, p := range commits {
if p.Source == "" {
continue
}
if out[m] == nil {
out[m] = map[string]string{}
}
out[m][commit] = p.Source
}
}
return out, nil
}
// sortByRecorded is newest record first, as `order by at desc`.
func sortByRecorded(builds []Build) {
sort.SliceStable(builds, func(a, b int) bool {
if !builds[a].At.Equal(builds[b].At) {
return builds[a].At.After(builds[b].At)
}
return builds[a].ID > builds[b].ID
})
}
// chainBuildOf is the id of the newest successful build of a module from a commit: its commit pointer, and the record it names.
func (i *Inventory) chainBuildOf(ctx context.Context, c *chain.Chain, module, commit string) (string, error) {
rec, found, err := i.followPointer(ctx, c, module, chain.CommitKey(module, commit),
func(b chain.Build) bool { return b.Failed == "" && b.Commit == commit })
if err != nil || !found {
return "", err
}
return rec.ID, nil
}
// chainBuildOfSource is the id of the newest successful build of a module with a reuse key (ADR 0350 §1): its source
// pointer, and the record it names. What a reuse asks.
func (i *Inventory) chainBuildOfSource(ctx context.Context, c *chain.Chain, module, reuseKey string) (string, error) {
rec, found, err := i.followPointer(ctx, c, module, chain.SourceKey(module, chain.SpellReuse(reuseKey)),
func(b chain.Build) bool { return b.Failed == "" && b.ReuseKey == reuseKey })
if err != nil || !found {
return "", err
}
return rec.ID, nil
}
// chainManifestBuild is the newest successful build of a module from a commit (or one it abbreviates) that kept a manifest.
// A whole commit is its pointer and the record; an abbreviation reads the module's builds.
func (i *Inventory) chainManifestBuild(ctx context.Context, c *chain.Chain, module, commit string) (string, []byte, bool, error) {
kept := func(b chain.Build) bool { return b.ManifestDigest != "" || manifestBytes(b.Manifest) != nil }
if rec, found, err := i.followPointer(ctx, c, module, chain.CommitKey(module, commit),
func(b chain.Build) bool { return b.Failed == "" && b.Commit == commit }); err != nil {
return "", nil, false, err
} else if found && kept(rec) {
raw, err := manifestOfRecord(ctx, c, rec)
return rec.ID, raw, err == nil, err
}
all, err := chainModuleBuilds(ctx, c, module)
if err != nil {
return "", nil, false, err
}
var ok []Build
for _, b := range all {
if b.Failed == "" && (b.ManifestDigest != "" || manifestBytes(b.Manifest) != nil) &&
(b.Commit == commit || strings.HasPrefix(b.Commit, commit)) {
ok = append(ok, b)
}
}
sortByRecorded(ok)
if len(ok) == 0 {
return "", nil, false, nil
}
raw, err := manifestOfRecord(ctx, c, chain.Build{ID: ok[0].ID, Manifest: ok[0].Manifest, ManifestDigest: ok[0].ManifestDigest})
return ok[0].ID, raw, err == nil, err
}
// chainManifestOf is the manifest a build was recorded with.
func (i *Inventory) chainManifestOf(ctx context.Context, c *chain.Chain, id string) ([]byte, error) {
rec, ok, err := i.chainRecordOf(ctx, c, id)
if err != nil || !ok {
return nil, err
}
return manifestOfRecord(ctx, c, rec)
}
// chainRecentMade is the artifacts and bases of the KeptBuilds newest successful builds of each registered module.
func (i *Inventory) chainRecentMade(ctx context.Context, c *chain.Chain) ([]Build, error) {
all, err := chainBuilds(ctx, c)
if err != nil {
return nil, err
}
rows, err := i.store.Pool().Query(ctx, `select name from module`)
if err != nil {
return nil, err
}
registered := map[string]bool{}
for rows.Next() {
var name string
if err := rows.Scan(&name); err != nil {
rows.Close()
return nil, err
}
registered[name] = true
}
rows.Close()
if err := rows.Err(); err != nil {
return nil, err
}
var ok []Build
for _, b := range worked(all) {
if registered[b.Module] {
ok = append(ok, b)
}
}
sortByRecorded(ok)
back := map[string]int{}
var out []Build
for _, b := range ok {
back[b.Module]++
if back[b.Module] <= KeptBuilds {
out = append(out, b)
}
}
return out, nil
}
// chainEveryReferenceMade is every successful build's artifacts, oldest build first.
func (i *Inventory) chainEveryReferenceMade(ctx context.Context, c *chain.Chain) ([]Build, error) {
all, err := chainBuilds(ctx, c)
if err != nil {
return nil, err
}
ok := worked(all)
sort.SliceStable(ok, func(a, b int) bool {
if !ok[a].At.Equal(ok[b].At) {
return ok[a].At.Before(ok[b].At)
}
return ok[a].ID < ok[b].ID
})
return ok, nil
}
// chainHeldMade is, per module, what the build standing for its newest successful build made. Read from the pointers: the
// module's `worked` (its newest success) and `held` (the first build of the run of one source that it stands on), one record
// of each. A build that failed its gate ends a run (issue 280), and the pointer cannot say where; then the module's builds
// are read.
func (i *Inventory) chainHeldMade(ctx context.Context, c *chain.Chain) (map[string][]Artifact, error) {
worked, err := i.pointedBuilds(ctx, c, "*.worked")
if err != nil {
return nil, err
}
held, err := i.pointedBuilds(ctx, c, "*.held")
if err != nil {
return nil, err
}
failed, err := i.failedAtAGate(ctx)
if err != nil {
return nil, err
}
out := map[string][]Artifact{}
for module, w := range worked {
stand := w
if h, ok := held[module]; ok && !failed[w.ID] {
stand = h
}
if failed[stand.ID] && stand.ID != w.ID {
rows, _, err := i.chainSourceRows(ctx, c, module)
if err != nil {
return nil, err
}
if builds := rows[module]; len(builds) > 0 {
var made []Artifact
if err := json.Unmarshal(builds[standing(builds, 0)].made, &made); err == nil {
out[module] = made
}
}
continue
}
out[module] = fromChainBuild(stand).Made
}
return out, nil
}
// LayoutReport is what the layout of the builds bucket did.
type LayoutReport struct {
// Records is the keys of before it read, Written the keys this run made (records, pointers, manifests) and Present the
// records already under their new key.
Records, Written, Present int
}
// KindBuildsLayout is the kind chain_moved names the layout of the builds bucket under their modules by (issue 1121).
const KindBuildsLayout = "builds-layout"
// layoutBuilds lays the builds of the bucket out under their modules: each record of a key that is its id alone is written
// under `<module>.<id>` (a request, and a build that knew no module, under `req.<id>`), its manifest moved to the manifests
// bucket, and the pointers made. **Idempotent**: a key that exists is already moved, and a pointer that holds what it should
// is not written. The old keys stay (ADR 0408 §4) and are not read again.
func (i *Inventory) layoutBuilds(ctx context.Context, c *chain.Chain, say func(string, ...any)) (LayoutReport, error) {
if say == nil {
say = func(string, ...any) {}
}
var r LayoutReport
legacy, err := c.Builds.FilteredRaw(ctx, "*")
if err != nil {
return r, fmt.Errorf("the builds of before the layout cannot be read: %w", err)
}
ids := make([]string, 0, len(legacy))
for key := range legacy {
ids = append(ids, key)
}
sort.Strings(ids)
byModule := map[string][]chain.Build{}
manifestsDone := map[string]bool{}
for _, key := range ids {
k, err := chain.DecodeKept[chain.Build](legacy[key])
if err != nil {
return r, err
}
rec := k.Value
r.Records++
if r.Records%1000 == 0 {
say("laying the builds out: %d of %d records, %d keys written so far", r.Records, len(ids), r.Written)
}
// A manifest already written in this run is not written again.
digest := ""
if body := manifestBytes([]byte(rec.Manifest)); body != nil && c.Kept {
digest = chain.ManifestKey(body)
if !manifestsDone[digest] {
if _, err := c.Manifests.CreateRaw(ctx, digest, body); err == nil {
r.Written++
} else if !errors.Is(err, chain.ErrExists) {
return r, fmt.Errorf("the manifest of %s is not kept: %w", key, err)
}
manifestsDone[digest] = true
}
}
if digest != "" {
rec.Manifest, rec.ManifestDigest = nil, digest
}
target := chain.BuildKey(rec.Module, rec.ID)
if rec.State == chain.BuildAsked {
target = chain.RequestKey(rec.ID)
}
switch _, err := c.Builds.Create(ctx, target, rec); {
case err == nil:
r.Written++
case errors.Is(err, chain.ErrExists):
r.Present++
default:
return r, fmt.Errorf("%s is not written under %s: %w", key, target, err)
}
if rec.State != chain.BuildAsked {
wrote, err := i.pointTo(ctx, c, chain.IDKey(rec.ID), idPointer(rec.Module), false)
if err != nil {
return r, err
}
if wrote {
r.Written++
}
}
if rec.State != chain.BuildAsked && rec.Module != "" && rec.Request != nil {
// The request beside its outcome, so the requests are read from their own prefix.
ans := chain.Build{ID: rec.ID, State: chain.BuildAnswered, Repository: rec.Repository, Ref: rec.Ref, Path: rec.Path,
Asked: rec.Asked, Request: rec.Request, Module: rec.Module, Failed: rec.Failed, At: rec.At}
switch _, err := c.Builds.Create(ctx, chain.RequestKey(rec.ID), ans); {
case err == nil:
r.Written++
case errors.Is(err, chain.ErrExists):
default:
return r, err
}
}
if rec.State != chain.BuildAsked && rec.Module != "" {
byModule[rec.Module] = append(byModule[rec.Module], rec)
}
}
for module, recs := range byModule {
points, err := pointsOf(module, recs)
if err != nil {
return r, err
}
for _, p := range points {
wrote, err := i.pointTo(ctx, c, p.key, p.value, true)
if err != nil {
return r, err
}
if wrote {
r.Written++
}
}
}
say("laid out the builds under their modules: %d records read, %d keys written, %d records already there", r.Records, r.Written, r.Present)
return r, nil
}