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 (`@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.` 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.` 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 `.>` 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.` 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 `.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 } // ErrNotLaidOut is a read of the builds before the controller's start has laid them out under their modules (issue 1121): // refused loudly, never answered from keys that are not there. var ErrNotLaidOut = errors.New("the builds are not laid out under their modules yet; the controller's start does it and says so") // LayBuildsOut lays the builds out under their modules once and writes the marker key (chain.LayoutMarkerKey) that lets them // be read. It needs the chain attached and the lease held (the chain's writes ask), and is idempotent: with the marker it // does nothing, and a run that stopped half way is finished by the next. The old keys stay (ADR 0408 §4). func (i *Inventory) LayBuildsOut(ctx context.Context, say func(string, ...any)) error { c := i.attached() if c == nil { return ErrNoChain } if _, found, err := c.Builds.GetRaw(ctx, chain.LayoutMarkerKey); err != nil { return err } else if found { i.markLaidOut() return nil } report, err := i.layoutBuilds(ctx, c, say) if err != nil { return err } body, _ := json.Marshal(map[string]any{"records": report.Records, "at": time.Now().UTC()}) if _, err := c.Builds.CreateRaw(ctx, chain.LayoutMarkerKey, body); err != nil && !errors.Is(err, chain.ErrExists) { return fmt.Errorf("the builds are laid out and the marker is not written: %w", err) } i.markLaidOut() return nil } // layoutBuilds lays the builds of the bucket out under their modules: each record of a key that is its id alone is written // under `.` (a request, and a build that knew no module, under `req.`), 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 record already under its new key is moved: a run again (the start before this one laid it out) neither reads its // manifest nor writes it, and **no pointer is set back**: they are only made where missing. own := chain.BuildKey(rec.Module, rec.ID) if rec.State == chain.BuildAsked { own = chain.RequestKey(rec.ID) } if _, there, err := c.Builds.GetRaw(ctx, own); err != nil { return r, err } else if there { r.Present++ if rec.State != chain.BuildAsked { if wrote, err := i.pointTo(ctx, c, chain.IDKey(rec.ID), idPointer(rec.Module), false); err != nil { return r, err } else if wrote { r.Written++ } } if rec.State != chain.BuildAsked && rec.Module != "" { byModule[rec.Module] = append(byModule[rec.Module], rec) } continue } // 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, false) 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 }