194 lines
6.9 KiB
Go
194 lines
6.9 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"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) {
|
|
c, err := i.theChain(ctx)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return i.chainSourceRows(ctx, c, module)
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// **Across the switch to the new form** (novox/hq ADR 0453 §6, ADR 0499; R4e A9): a build of the new form (`src2:`, taken
|
|
// from the builder's key) continues the run of the build before it of the old form (`src1:` or none, build-agent's)
|
|
// when every artifact of the two has the same digest: the fingerprints cannot be compared, the programs can, and equal
|
|
// digests are no move. Past it the run goes on by the old 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].failedGate {
|
|
break
|
|
}
|
|
if builds[j].fingerprint != fp {
|
|
if !newForm(fp) || newForm(builds[j].fingerprint) || !sameDigests(builds[at].made, builds[j].made) {
|
|
break
|
|
}
|
|
fp = builds[j].fingerprint
|
|
}
|
|
at = j
|
|
}
|
|
return at
|
|
}
|
|
|
|
// newForm says a source fingerprint is of the new form (ADR 0453 §6).
|
|
func newForm(fp string) bool { return strings.HasPrefix(fp, "src2:") }
|
|
|
|
// sameDigests says two builds' artifacts, as recorded (JSON of []Artifact), are the same programs: the same names, each
|
|
// with the same digest, wherever each was pushed. A build naming an artifact with no digest is never the same.
|
|
func sameDigests(a, b []byte) bool {
|
|
digests := func(raw []byte) map[string]string {
|
|
var made []Artifact
|
|
if json.Unmarshal(raw, &made) != nil || len(made) == 0 {
|
|
return nil
|
|
}
|
|
out := map[string]string{}
|
|
for _, m := range made {
|
|
d := digestIn(m.Reference)
|
|
if d == "" {
|
|
return nil
|
|
}
|
|
out[m.Name] = d
|
|
}
|
|
return out
|
|
}
|
|
da, db := digests(a), digests(b)
|
|
if da == nil || db == nil || len(da) != len(db) {
|
|
return false
|
|
}
|
|
for name, d := range da {
|
|
if db[name] != d {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// 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, err := i.theChain(ctx)
|
|
if err != nil {
|
|
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) {
|
|
c, err := i.theChain(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Read from the commit pointers, which carry each commit's source fingerprint: no record is read (issue 1121).
|
|
return i.chainSourceFingerprints(ctx, c)
|
|
}
|
|
|
|
// BuildSourceFingerprints is SourceFingerprints for one module: commit → fingerprint.
|
|
func (i *Inventory) BuildSourceFingerprints(ctx context.Context, module string) (map[string]string, error) {
|
|
c, err := i.theChain(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// This module's commit pointers only (issue 1121).
|
|
return i.chainBuildSourceFingerprints(ctx, c, module)
|
|
}
|
|
|
|
// 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) {
|
|
c, err := i.theChain(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return i.chainHeldMade(ctx, c)
|
|
}
|
|
|
|
// 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
|
|
}
|