Record the inputs of a build's source fingerprint in its build record (issue 1215)
This commit is contained in:
@@ -9,6 +9,8 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// What a build says it was made from, as files (novox/hq ADR 0267), and what a build compiling a Go
|
||||
@@ -202,3 +204,20 @@ func TestAGoBundleSaysItsClosureAndAnUntoldImageNothing(t *testing.T) {
|
||||
t.Fatalf("an image compiling nothing it was told of said a build source: %+v", got.Sources)
|
||||
}
|
||||
}
|
||||
|
||||
// 1215/AC1 (compiled here, run by the merge check; the unit level has 1215/AC1 over the same inputs): a build's
|
||||
// result carries every input of its fingerprint, and the SDK function over them gives it again.
|
||||
func TestABuildSaysTheInputsItsFingerprintWasComputedFrom(t *testing.T) {
|
||||
got, _, _ := buildTheProxy(t, aSharedRepository("one", "1"), false)
|
||||
if got.SourceInputs == nil {
|
||||
t.Fatal("the result carries no source inputs")
|
||||
}
|
||||
in := *got.SourceInputs
|
||||
if in.Module != "route-proxy" || in.Tree == "" || in.Contexts["server"] == "" {
|
||||
t.Errorf("the inputs miss the module, its tree or the server's context: %+v", in)
|
||||
}
|
||||
fp, none := delivery.SourceFingerprint(in)
|
||||
if fp == "" || none != "" || fp != got.Source {
|
||||
t.Errorf("the inputs give %q (%q); the build recorded %q", fp, none, got.Source)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/novox/mesh-controller/internal/catalogue"
|
||||
)
|
||||
|
||||
@@ -91,6 +92,10 @@ type Result struct {
|
||||
// (novox/hq issue 280).
|
||||
Source string
|
||||
|
||||
// SourceInputs are what Source was computed from (novox/hq ADR 0437 §5, issue 1215), set also where the source
|
||||
// does not pin the build, so the reason is visible: with them delivery.SourceFingerprint gives Source again.
|
||||
SourceInputs *delivery.SourceInputs
|
||||
|
||||
// ReuseKey is the build's reuse key (novox/hq ADR 0350 §1): its source fingerprint's parts with the builder's
|
||||
// version, and ReuseParts those parts; empty, with NoKey saying why, where the source does not pin the build.
|
||||
ReuseKey string
|
||||
@@ -491,7 +496,7 @@ func build(ctx context.Context, run Runner, publish Publisher,
|
||||
// The reuse key (novox/hq ADR 0350 §1): what a check keeps its build under, and what a walk takes one by.
|
||||
key := keyOf(src, manifest, Version)
|
||||
return Result{Manifest: resolved, Commit: commit, Built: built,
|
||||
Against: against(within, manifest, stoodOn), Read: readBy(manifest), Source: fingerprint,
|
||||
Against: against(within, manifest, stoodOn), Read: readBy(manifest), Source: fingerprint, SourceInputs: resultInputs(src),
|
||||
Trunk: trunk, OnTrunk: onTrunk, Branches: branches, Sources: sources,
|
||||
ReuseKey: key.Key, ReuseParts: key.Parts, NoKey: key.Why, Reused: reused, Why: builtWhy,
|
||||
Predefined: listed}, nil
|
||||
|
||||
@@ -278,6 +278,15 @@ func (s *sourceInputs) inputs() delivery.SourceInputs {
|
||||
}
|
||||
}
|
||||
|
||||
// resultInputs are the inputs a build's result records; nil for a build with no inputs to speak of.
|
||||
func resultInputs(s *sourceInputs) *delivery.SourceInputs {
|
||||
if s == nil {
|
||||
return nil
|
||||
}
|
||||
in := s.inputs()
|
||||
return &in
|
||||
}
|
||||
|
||||
// fingerprint is the build's source fingerprint, or empty when the source does not pin the build.
|
||||
func (s *sourceInputs) fingerprint() string {
|
||||
if s == nil {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
@@ -81,3 +82,44 @@ func TestTheBuilderFingerprintsWithTheSDKFunction(t *testing.T) {
|
||||
t.Errorf("the SDK function was given %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 1215/AC1: the inputs a result records, read back, give the fingerprint the build recorded (the live builds of
|
||||
// 1214/AC1), through encoding/json as the build record keeps them.
|
||||
func TestTheInputsAResultRecordsGiveItsFingerprintAgain(t *testing.T) {
|
||||
s := &sourceInputs{module: "build-agent", tree: "11c0b4f90cae0d6e4ef9cc48cbfdf9f6dbe52bca",
|
||||
bases: []string{
|
||||
"sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc",
|
||||
"sha256:8ac98ca534ac3f51e1f420a1dd2c15e74c75cfa0f23f3ad27eb5d7236c349a0c",
|
||||
},
|
||||
contexts: map[string]string{"server": "narrow:d51ca744fd11af59d616c858f76c27b3e58e16e11fbdc0c720eb9d7d19d55fac"}}
|
||||
raw, err := json.Marshal(resultInputs(s))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var back delivery.SourceInputs
|
||||
if err := json.Unmarshal(raw, &back); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "src1:e846efe73716b78e80860baaac96153de2cb76551325753838ca245d2160a3d7"
|
||||
if fp, none := delivery.SourceFingerprint(back); fp != want || none != "" {
|
||||
t.Errorf("the inputs read back give %q (%q), want %q", fp, none, want)
|
||||
}
|
||||
}
|
||||
|
||||
// 1215/AC3: a build that gets no fingerprint still has its inputs recorded, so the reason is visible.
|
||||
func TestABuildWithNoFingerprintStillSaysItsInputs(t *testing.T) {
|
||||
s := newSourceInputs("app")
|
||||
s.tree = "aaaa"
|
||||
s.notPinned("it resolves packages from the mesh's registry at build time")
|
||||
in := resultInputs(s)
|
||||
if in == nil || in.Module != "app" || in.Tree != "aaaa" || in.Unpinned == "" {
|
||||
t.Fatalf("the inputs of an unpinned build: %+v", in)
|
||||
}
|
||||
if fp, none := delivery.SourceFingerprint(*in); fp != "" || none != in.Unpinned {
|
||||
t.Errorf("the inputs give %q (%q)", fp, none)
|
||||
}
|
||||
noTree := resultInputs(newSourceInputs("app"))
|
||||
if noTree == nil || noTree.Module != "app" || noTree.Tree != "" {
|
||||
t.Errorf("the inputs of a build with no tree: %+v", noTree)
|
||||
}
|
||||
}
|
||||
|
||||
+16
-13
@@ -10,6 +10,8 @@ package chain
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// Ref names a build and the commit it was made from: one end of a change.
|
||||
@@ -97,19 +99,20 @@ type Build struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
Manifest json.RawMessage `json:"manifest,omitempty"`
|
||||
// ManifestDigest names the manifest in the manifests bucket (ManifestKey); a record that has it keeps no Manifest.
|
||||
ManifestDigest string `json:"manifest-digest,omitempty"`
|
||||
Against []string `json:"against,omitempty"`
|
||||
Mirrored []string `json:"mirrored,omitempty"`
|
||||
Read []ReadRepository `json:"read,omitempty"`
|
||||
Sources []Source `json:"sources,omitempty"`
|
||||
SourceFingerprint string `json:"source-fingerprint,omitempty"`
|
||||
Failed string `json:"failed,omitempty"`
|
||||
Made []Artifact `json:"made,omitempty"`
|
||||
Asked time.Time `json:"asked,omitzero"`
|
||||
At time.Time `json:"at,omitzero"`
|
||||
ReuseKey string `json:"reuse-key,omitempty"`
|
||||
Published *Published `json:"published,omitempty"`
|
||||
Request *Request `json:"request,omitempty"`
|
||||
ManifestDigest string `json:"manifest-digest,omitempty"`
|
||||
Against []string `json:"against,omitempty"`
|
||||
Mirrored []string `json:"mirrored,omitempty"`
|
||||
Read []ReadRepository `json:"read,omitempty"`
|
||||
Sources []Source `json:"sources,omitempty"`
|
||||
SourceFingerprint string `json:"source-fingerprint,omitempty"`
|
||||
SourceInputs *delivery.SourceInputs `json:"source-inputs,omitempty"`
|
||||
Failed string `json:"failed,omitempty"`
|
||||
Made []Artifact `json:"made,omitempty"`
|
||||
Asked time.Time `json:"asked,omitzero"`
|
||||
At time.Time `json:"at,omitzero"`
|
||||
ReuseKey string `json:"reuse-key,omitempty"`
|
||||
Published *Published `json:"published,omitempty"`
|
||||
Request *Request `json:"request,omitempty"`
|
||||
}
|
||||
|
||||
// Commit is one repository's commit a rollout carries.
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/novox/mesh-controller/internal/chain"
|
||||
)
|
||||
|
||||
@@ -51,6 +52,8 @@ type Build struct {
|
||||
// SourceFingerprint is what the build was made from, hashed, as its builder said it (novox/hq
|
||||
// issue 280); empty from a builder that predates it, or where the source does not pin the build.
|
||||
SourceFingerprint string
|
||||
// SourceInputs are what it was computed from (novox/hq ADR 0437 §5); nil from a build recorded before they were.
|
||||
SourceInputs *delivery.SourceInputs
|
||||
// Failed is the builder's own words, empty when it worked.
|
||||
Failed string
|
||||
Made []Artifact
|
||||
|
||||
@@ -23,7 +23,7 @@ import (
|
||||
// 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,
|
||||
Path: b.Path, Against: b.Against, Mirrored: b.Mirrored, SourceFingerprint: b.SourceFingerprint, SourceInputs: b.SourceInputs,
|
||||
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)
|
||||
@@ -46,7 +46,7 @@ func toChainBuild(b Build) chain.Build {
|
||||
// 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,
|
||||
Manifest: []byte(c.Manifest), ManifestDigest: c.ManifestDigest, Against: c.Against, Mirrored: c.Mirrored, SourceFingerprint: c.SourceFingerprint, SourceInputs: c.SourceInputs,
|
||||
Failed: c.Failed, Asked: c.Asked, At: c.At, ReuseKey: c.ReuseKey, Published: c.Published}
|
||||
if len(b.Manifest) == 0 {
|
||||
b.Manifest = nil
|
||||
|
||||
@@ -5,9 +5,11 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
"github.com/novox/mesh-controller/internal/chain"
|
||||
@@ -129,3 +131,55 @@ func TestTheBusBeingAwayIsNotNowLikeTheStoreBeingAway(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1215/AC1 and AC4: the inputs of a build's fingerprint are kept in the build's record, through the bucket's bytes, unchanged.
|
||||
func TestABuildKeepsTheInputsOfItsSourceFingerprint(t *testing.T) {
|
||||
in := &delivery.SourceInputs{Module: "notes", Tree: "t1", Bases: []string{"r/b@sha256:aa"},
|
||||
Contexts: map[string]string{"server": "narrow:ff"}, Toolchains: map[string]string{"code": "go sha256:cc 0123"}}
|
||||
fp, _ := delivery.SourceFingerprint(*in)
|
||||
b := Build{ID: "build-1", Module: "notes", Commit: "c1", SourceFingerprint: fp, SourceInputs: in}
|
||||
raw, err := json.Marshal(toChainBuild(b))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var back chain.Build
|
||||
if err := json.Unmarshal(raw, &back); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := fromChainBuild(back)
|
||||
if !reflect.DeepEqual(got.SourceInputs, in) {
|
||||
t.Fatalf("the inputs came back as %+v, not %+v", got.SourceInputs, in)
|
||||
}
|
||||
if again, _ := delivery.SourceFingerprint(*got.SourceInputs); again != got.SourceFingerprint {
|
||||
t.Errorf("the inputs read back give %q, the record says %q", again, got.SourceFingerprint)
|
||||
}
|
||||
}
|
||||
|
||||
// 1215/AC3: a build with no fingerprint keeps its inputs, so the reason is visible.
|
||||
func TestABuildWithNoFingerprintKeepsItsInputs(t *testing.T) {
|
||||
in := &delivery.SourceInputs{Module: "notes", Tree: "t1", Unpinned: "it resolves packages when built"}
|
||||
raw, _ := json.Marshal(toChainBuild(Build{ID: "build-1", Module: "notes", SourceInputs: in}))
|
||||
var back chain.Build
|
||||
if err := json.Unmarshal(raw, &back); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := fromChainBuild(back); got.SourceFingerprint != "" || !reflect.DeepEqual(got.SourceInputs, in) {
|
||||
t.Errorf("read back as %q with %+v", got.SourceFingerprint, got.SourceInputs)
|
||||
}
|
||||
}
|
||||
|
||||
// 1215/AC2: a record written before the inputs were kept reads as before, with none and no error.
|
||||
func TestABuildRecordFromBeforeTheInputsReadsAsBefore(t *testing.T) {
|
||||
var back chain.Build
|
||||
old := `{"id":"build-1","module":"notes","commit":"c1","source-fingerprint":"src1:aa","reuse-key":"rk"}`
|
||||
if err := json.Unmarshal([]byte(old), &back); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := fromChainBuild(back)
|
||||
if got.SourceInputs != nil || got.SourceFingerprint != "src1:aa" || got.ReuseKey != "rk" {
|
||||
t.Errorf("an old record reads as %+v", got)
|
||||
}
|
||||
if raw, _ := json.Marshal(toChainBuild(got)); strings.Contains(string(raw), "source-inputs") {
|
||||
t.Errorf("an old record is written with inputs: %s", raw)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// Asking a machine to build a module, and hearing what came out.
|
||||
@@ -410,6 +412,9 @@ type BuildResult struct {
|
||||
// byte-reproducible. Empty from a builder that predates it, or where the source does not pin the
|
||||
// build; then only identical artifacts make a rebuild no move.
|
||||
SourceFingerprint string `json:"source-fingerprint,omitempty"`
|
||||
// SourceInputs are what SourceFingerprint was computed from (novox/hq ADR 0437 §5, issue 1215), as encoding/json
|
||||
// writes the SDK's type; also set where the source does not pin the build.
|
||||
SourceInputs *delivery.SourceInputs `json:"source-inputs,omitempty"`
|
||||
|
||||
// Delivery is the how-manyth time the queue handed this ask out (BuildStart.Delivery): more than 1
|
||||
// says the outcome is from a run started again after an earlier taker left it (novox/hq issue 594).
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package link
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// 1215/AC4: the builder's result on the wire carries the inputs, under source-inputs, and reads back unchanged.
|
||||
func TestABuildResultCarriesTheSourceInputs(t *testing.T) {
|
||||
in := &delivery.SourceInputs{Module: "m", Tree: "t", Bases: []string{"x@sha256:a"},
|
||||
Contexts: map[string]string{"c": "narrow:1"}, Toolchains: map[string]string{"c": "go sha256:b 01"}, Unpinned: "why"}
|
||||
raw, err := json.Marshal(BuildResult{ID: "build-1", SourceInputs: in})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var wire map[string]json.RawMessage
|
||||
if err := json.Unmarshal(raw, &wire); err != nil || wire["source-inputs"] == nil {
|
||||
t.Fatalf("the result on the wire has no source-inputs: %s", raw)
|
||||
}
|
||||
var back BuildResult
|
||||
if err := json.Unmarshal(raw, &back); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(back.SourceInputs, in) {
|
||||
t.Errorf("read back as %+v, not %+v", back.SourceInputs, in)
|
||||
}
|
||||
raw, _ = json.Marshal(BuildResult{ID: "build-2"})
|
||||
wire = nil
|
||||
if err := json.Unmarshal(raw, &wire); err != nil || wire["source-inputs"] != nil {
|
||||
t.Errorf("a result with none writes source-inputs: %s", raw)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user