BL13: build.ended names the node that keeps a kept build in kept-on
A build the build node kept, which answers its digest and no reference, is done; its end carries kept-on once for the build. Builds: design 47 G §671, §379, §271, §526 Claude-Session: https://claude.ai/code/session_01JG73KEq4zoDfM9WtYpcQgZ
This commit is contained in:
@@ -12,7 +12,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/containerimageregistryclient/reference"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogkey"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/containerimageregistry"
|
||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/ociartifactregistry"
|
||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds/forms"
|
||||
@@ -63,13 +63,13 @@ const (
|
||||
)
|
||||
|
||||
// BuilderBuildKey is the key, in BuilderBuildsBucket, of a module's build at a fingerprint: `<module>.<hex>`, the hex
|
||||
// being the fingerprint's key part (meshcatalogclient.KeyPart), since a bus key may not hold ':'. A module holding a dot or a
|
||||
// being the fingerprint's key part (meshcatalogkey.KeyPart), since a bus key may not hold ':'. A module holding a dot or a
|
||||
// character a key refuses, and a fingerprint not of the new form, are refused, naming them.
|
||||
func BuilderBuildKey(module, fp string) (string, error) {
|
||||
if err := keys.CheckName("module", module); err != nil {
|
||||
return "", err
|
||||
}
|
||||
part, err := meshcatalogclient.KeyPart(fp)
|
||||
part, err := meshcatalogkey.KeyPart(fp)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -85,7 +85,7 @@ func ParseBuilderBuildKey(key string) (module, fp string, err error) {
|
||||
if err := keys.CheckName("module", module); err != nil {
|
||||
return "", "", fmt.Errorf("builds: %q is not a builder build key: %w", key, err)
|
||||
}
|
||||
if fp, err = meshcatalogclient.ParseKeyPart(part); err != nil {
|
||||
if fp, err = meshcatalogkey.ParseKeyPart(part); err != nil {
|
||||
return "", "", fmt.Errorf("builds: %q is not a builder build key: %w", key, err)
|
||||
}
|
||||
return module, fp, nil
|
||||
@@ -237,9 +237,16 @@ type BuilderBuild struct {
|
||||
// node-engine, beside the ones its manifest needs (novox/hq ADR 0618 §1). A reader that does not know the field
|
||||
// reads the build as needing none.
|
||||
Needs []string `json:"needs,omitempty"`
|
||||
// KeptOn is, on a done key only, the build node that keeps the build instead of storing it through the seat
|
||||
// oci-artifact-registry (design 47 G §671): every artifact of the build is kept there, each with its digest in
|
||||
// Artifacts. A build not kept names none; the builder carries it onto the build's end (BuildLogEntry.KeptOn).
|
||||
KeptOn string `json:"kept-on,omitempty"`
|
||||
}
|
||||
|
||||
func (b BuilderBuild) check() error {
|
||||
if b.KeptOn != "" && b.State != BuildDone {
|
||||
return fmt.Errorf("builds: a build is kept on a done key alone, this one is %s", b.State)
|
||||
}
|
||||
if err := trunk.CheckImports(b.Imports); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ type BuildLogEntry struct {
|
||||
Manifest string `json:"manifest,omitempty"`
|
||||
Against []string `json:"against,omitempty"`
|
||||
Made []BuiltArtifact `json:"made,omitempty"`
|
||||
// KeptOn is, at the end of a kept build, the build node that keeps it, once for the build (design 47 G §671): every
|
||||
// artifact in Made is kept there, with its digest. A build not kept carries none.
|
||||
KeptOn string `json:"kept-on,omitempty"`
|
||||
// Imports are, at a build's end, the mesh packages (import paths) the built programs import, their tests left out,
|
||||
// sorted and each once, as the build module answered them (BuildAnswer.Imports; novox/hq ADR 0593 §2): the indexer
|
||||
// keeps them on the module (KeptInputs2.Imports) when the build's fingerprint is the module's.
|
||||
|
||||
@@ -82,3 +82,31 @@ func outOfStepFilesABuildAnswersReachTheLog(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Design 47 G §671: a build the build node kept instead of storing it, which answers its digest and no reference, is done,
|
||||
// and its end in the log names the node that keeps it once; a build not kept names none.
|
||||
func aKeptBuildNamesItsNodeInItsEnd(t *testing.T) {
|
||||
r := newRig(t, goBundle)
|
||||
r.asks.Answer = func(a buildstep.StepAsk) buildstep.Step {
|
||||
step := r.answer(a)
|
||||
var answer buildstep.BuildAnswer
|
||||
if err := json.Unmarshal(step.Result, &answer); err == nil && a.Language == "go" && len(answer.Artifacts) > 0 {
|
||||
for i := range answer.Artifacts {
|
||||
answer.Artifacts[i].Reference = ""
|
||||
}
|
||||
answer.Kept = strings.TrimPrefix(answer.Artifacts[0].Digest, "sha256:")
|
||||
step.Result, _ = json.Marshal(answer)
|
||||
}
|
||||
return step
|
||||
}
|
||||
var logged []builds.BuildLogEntry
|
||||
var mu sync.Mutex
|
||||
r.b.Node, r.b.Log = "novox", func(e builds.BuildLogEntry) { mu.Lock(); logged = append(logged, e); mu.Unlock() }
|
||||
v := r.consider(t, "modules/m")
|
||||
if v.State != builds.BuildDone || v.KeptOn == "" {
|
||||
t.Fatalf("done key %+v, want it done and kept on its node", v)
|
||||
}
|
||||
if len(logged) != 2 || logged[1].KeptOn != v.KeptOn || logged[0].KeptOn != "" {
|
||||
t.Fatalf("log %+v, want kept-on %q on the end alone", logged, v.KeptOn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -651,6 +651,7 @@ type lane struct {
|
||||
digests map[string]string // artifact -> digest
|
||||
references map[string]string // artifact -> the reference an image was pushed to (a trunk build only)
|
||||
imports []string // the mesh packages the lane's programs import, as its build module answered them
|
||||
kept string // the digest of the artifact the build node kept instead of storing it (BuildAnswer.Kept), else empty
|
||||
outOfStep []string // the generated files its build module's compare found out of step, passed or failed
|
||||
err error
|
||||
}
|
||||
@@ -763,7 +764,7 @@ func (b *Builder) build(ctx context.Context, module string, m meshcatalog.Module
|
||||
continue
|
||||
}
|
||||
r := l.references[t.Artifact]
|
||||
if r == "" && stored(t.Kind) {
|
||||
if r == "" && stored(t.Kind) && (l.kept == "" || strings.TrimPrefix(d, "sha256:") != l.kept) {
|
||||
// A bundle, an archive or an upstream copy is done only when its push was answered with where it is (ADR 0498
|
||||
// clause 1); a candidate's included (clause 3).
|
||||
return failed("%s%s on %s: the build answered no reference, so its push to the artifact store was not answered", builds.NotStored, t.Artifact, l.node)
|
||||
@@ -796,6 +797,11 @@ func (b *Builder) build(ctx context.Context, module string, m meshcatalog.Module
|
||||
}
|
||||
done := builds.BuilderBuild{State: builds.BuildDone, By: b.Claimer.By, Manifest: m.Manifest, Step: step, Artifacts: artifacts, Toolchains: used,
|
||||
Imports: importsOf(lanes), Needs: forms.Current.Needs(artifacts)} // the forms it is written in (ADR 0618 §1)
|
||||
for _, l := range lanes {
|
||||
if l.kept != "" {
|
||||
done.KeptOn = l.node // design 47 G §671: the build node that keeps the build
|
||||
}
|
||||
}
|
||||
if _, err := builds.EncodeBuilderBuild(done); err != nil {
|
||||
// A reference the build module answered that does not name the artifact and its digest is no reference.
|
||||
return failed("%s%v", builds.NotStored, err)
|
||||
@@ -1008,6 +1014,7 @@ func (b *Builder) runLane(ctx context.Context, l *lane, repository string, m mes
|
||||
return
|
||||
}
|
||||
l.imports = answer.Imports
|
||||
l.kept = answer.Kept
|
||||
built := map[string]sdkstep.AskBuilt{}
|
||||
for _, a := range answer.Artifacts {
|
||||
built[a.Artifact] = a
|
||||
@@ -1092,6 +1099,7 @@ func LogEnded(module string, m meshcatalog.ModulesByModuleValue, ref, by string,
|
||||
e.Against = append([]string(nil), m.Inputs2.Bases...)
|
||||
}
|
||||
e.Made = append([]builds.BuiltArtifact(nil), result.Artifacts...)
|
||||
e.KeptOn = result.KeptOn
|
||||
e.Imports = append([]string(nil), result.Imports...) // the indexer keeps them on the module (novox/hq ADR 0593 §2)
|
||||
e.Error, e.Step = result.Error, result.Step // a failed build's reason, not only its outcome
|
||||
return e
|
||||
|
||||
@@ -230,6 +230,7 @@ var (
|
||||
|
||||
func TestABundleAndAnArchiveAreBuiltOnOneNodeAndDone(t *testing.T) { // 1607/AC7, ADR 0478 clauses 1, 4
|
||||
t.Run("the imports a build answers reach the done key and the log", importsABuildAnswersReachTheDoneKeyAndTheLog)
|
||||
t.Run("a kept build names its node in its end", aKeptBuildNamesItsNodeInItsEnd)
|
||||
t.Run("the out-of-step files a build answers reach the log", outOfStepFilesABuildAnswersReachTheLog)
|
||||
t.Run("ADR 0624: a done key keeps each form a counted node reads", aDoneKeyKeepsEachFormACountedNodeReads)
|
||||
r := newRig(t, goBundle, archiveA, tsBundle)
|
||||
|
||||
Reference in New Issue
Block a user