Compute the source fingerprint with mesh-sdk's one function (issue 1214)
This commit is contained in:
+21
-27
@@ -10,6 +10,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"github.com/novox/mesh-controller/internal/catalogue"
|
||||
)
|
||||
|
||||
@@ -36,7 +37,7 @@ import (
|
||||
// (novox/hq ADR 0097 refuses the unpinned bases; what a RUN step downloads is not seen here).
|
||||
|
||||
// sourcePrefix names the fingerprint's form, so a later form is never compared equal to this one.
|
||||
const sourcePrefix = "src1:"
|
||||
const sourcePrefix = delivery.SourcePrefix
|
||||
|
||||
// sourceInputs collects what one build was made from.
|
||||
type sourceInputs struct {
|
||||
@@ -262,40 +263,33 @@ func (s *sourceInputs) notPinned(why string) {
|
||||
}
|
||||
}
|
||||
|
||||
// sourceFingerprint is the one function the controller's builder and the indexer both call (novox/hq ADR 0437 §5);
|
||||
// a variable only so a test can see it called.
|
||||
var sourceFingerprint = delivery.SourceFingerprint
|
||||
|
||||
// inputs are what the build's fingerprint is of, as the SDK names them.
|
||||
func (s *sourceInputs) inputs() delivery.SourceInputs {
|
||||
if s == nil {
|
||||
return delivery.SourceInputs{}
|
||||
}
|
||||
return delivery.SourceInputs{
|
||||
Module: s.module, Tree: s.tree, Bases: s.bases,
|
||||
Contexts: s.contexts, Toolchains: s.toolchains, Unpinned: s.unpinned,
|
||||
}
|
||||
}
|
||||
|
||||
// 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 || s.unpinned != "" || s.tree == "" {
|
||||
if s == nil {
|
||||
return ""
|
||||
}
|
||||
var lines []string
|
||||
lines = append(lines, "module "+s.module, "tree "+s.tree)
|
||||
bases := map[string]bool{}
|
||||
for _, b := range s.bases {
|
||||
bases[referenceDigest(b)] = true
|
||||
}
|
||||
for b := range bases {
|
||||
lines = append(lines, "base "+b)
|
||||
}
|
||||
for a, t := range s.contexts {
|
||||
lines = append(lines, "context "+a+" "+t)
|
||||
}
|
||||
for a, t := range s.toolchains {
|
||||
lines = append(lines, "toolchain "+a+" "+t)
|
||||
}
|
||||
// The module and its tree first, the rest in a fixed order.
|
||||
sort.Strings(lines[2:])
|
||||
sum := sha256.Sum256([]byte(strings.Join(lines, "\n")))
|
||||
return sourcePrefix + hex.EncodeToString(sum[:])
|
||||
fp, _ := sourceFingerprint(s.inputs())
|
||||
return fp
|
||||
}
|
||||
|
||||
// referenceDigest is a reference's digest — `sha256:…` — so the registry address it was copied into does not
|
||||
// enter the fingerprint; the reference itself when it carries none.
|
||||
func referenceDigest(reference string) string {
|
||||
if _, digest, pinned := strings.Cut(reference, "@"); pinned && digest != "" {
|
||||
return digest
|
||||
}
|
||||
return reference
|
||||
}
|
||||
func referenceDigest(reference string) string { return delivery.ReferenceDigest(reference) }
|
||||
|
||||
// gitTree is the git tree of a directory of a clone at its checked-out commit: the whole tree for an
|
||||
// empty path.
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package builder
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
)
|
||||
|
||||
// 1214/AC1: the inputs of three builds the live builder recorded a source fingerprint for, copied from mesh-sdk
|
||||
// go/delivery/fingerprint_test.go (TestFingerprintLiveBuilds), where each value is captured, never written by hand.
|
||||
func TestTheBuildersFingerprintEqualsWhatTheLiveBuilderRecorded(t *testing.T) {
|
||||
const ts = "typescript sha256:4b81682df5665c1f461d3f720a3a0fd7593f4aa71e4fc272dbfdc4bbd235b489 20125bd590041690"
|
||||
for _, c := range []struct {
|
||||
name string
|
||||
in sourceInputs
|
||||
recorded string
|
||||
}{
|
||||
{"mailu", sourceInputs{
|
||||
module: "mailu",
|
||||
tree: "cfc6fa8ff5fc1e220132e51ade653b9d3f387b3e",
|
||||
bases: []string{"sha256:25f3cfeaceca14921366af4d1240b56457ef46273bdb508c7b0e8f469f6fd228"},
|
||||
toolchains: map[string]string{"code": ts},
|
||||
}, "src1:539f2e64000d436c572ef53b40e524012455945f8c5a1c6bb173ee0707e88ef4"},
|
||||
{"radarr", sourceInputs{
|
||||
module: "radarr",
|
||||
tree: "42b9c918b5af75cba2bbc75845012e7f64e16df9",
|
||||
toolchains: map[string]string{
|
||||
"code": ts,
|
||||
"tools-go": "go sha256:2054591ca034b68323d87ec887ba34f56819cb3364034360c0777da4b314dd54 0995d85edc9a6a23",
|
||||
},
|
||||
}, "src1:b3c150d39c395b99dd68974a48490e9a42467aa5d486d0c604d6ef1e8e9c910a"},
|
||||
{"build-agent", sourceInputs{
|
||||
module: "build-agent",
|
||||
tree: "11c0b4f90cae0d6e4ef9cc48cbfdf9f6dbe52bca",
|
||||
bases: []string{
|
||||
"sha256:d9e853e87e55526f6b2917df91a2115c36dd7c696a35be12163d44e6e2a4b6bc",
|
||||
"sha256:8ac98ca534ac3f51e1f420a1dd2c15e74c75cfa0f23f3ad27eb5d7236c349a0c",
|
||||
},
|
||||
contexts: map[string]string{"server": "narrow:d51ca744fd11af59d616c858f76c27b3e58e16e11fbdc0c720eb9d7d19d55fac"},
|
||||
}, "src1:e846efe73716b78e80860baaac96153de2cb76551325753838ca245d2160a3d7"},
|
||||
} {
|
||||
if got := c.in.fingerprint(); got != c.recorded {
|
||||
t.Errorf("%s: fingerprint = %q, the live builder recorded %q", c.name, got, c.recorded)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1214/AC2: a build with no tree, or one that resolves packages when built, gets no fingerprint.
|
||||
func TestABuildWithoutATreeOrUnpinnedGetsNoFingerprint(t *testing.T) {
|
||||
if (&sourceInputs{module: "app"}).fingerprint() != "" {
|
||||
t.Error("a build with no tree has a fingerprint")
|
||||
}
|
||||
s := &sourceInputs{module: "app", tree: "aaaa"}
|
||||
s.notPinned("it resolves packages from the mesh's registry at build time")
|
||||
if got := s.fingerprint(); got != "" {
|
||||
t.Errorf("an unpinned build has a fingerprint: %s", got)
|
||||
}
|
||||
if got := orNoTree(""); got != "its tree was not named" {
|
||||
t.Errorf("the reason for no tree = %q", got)
|
||||
}
|
||||
if got := orNoTree(s.unpinned); got != s.unpinned {
|
||||
t.Errorf("the reason for unpinned = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// 1214/AC3: the builder has no hash of its own: its fingerprint is what delivery.SourceFingerprint says, whatever
|
||||
// that is (a stand-in proves it is called), and the reuse key does not move with it.
|
||||
func TestTheBuilderFingerprintsWithTheSDKFunction(t *testing.T) {
|
||||
was := sourceFingerprint
|
||||
t.Cleanup(func() { sourceFingerprint = was })
|
||||
var got delivery.SourceInputs
|
||||
sourceFingerprint = func(in delivery.SourceInputs) (string, string) {
|
||||
got = in
|
||||
return "src1:stand-in", ""
|
||||
}
|
||||
s := &sourceInputs{module: "m", tree: "t", bases: []string{"x@sha256:a"}, contexts: map[string]string{"c": "ct"}, toolchains: map[string]string{"c": "go"}}
|
||||
if fp := s.fingerprint(); fp != "src1:stand-in" {
|
||||
t.Fatalf("fingerprint = %q, not the SDK function's", fp)
|
||||
}
|
||||
if got.Module != "m" || got.Tree != "t" || len(got.Bases) != 1 || got.Contexts["c"] != "ct" || got.Toolchains["c"] != "go" {
|
||||
t.Errorf("the SDK function was given %+v", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user