//go:build integration package main import ( "context" "encoding/json" "errors" "reflect" "strings" "testing" "git.novox.be/novox/mesh-sdk/go/catalogue" sdkdelivery "git.novox.be/novox/mesh-sdk/go/delivery" "github.com/novox/mesh-controller/internal/builderkeys" "github.com/novox/mesh-controller/internal/inventory" ) // withTrunkList stands in for the catalogue's trunk head: an empty list of predefined keys, readable, or not. func withTrunkList(t *testing.T, readable bool) { t.Helper() was := predefinedAtTrunk predefinedAtTrunk = func(context.Context, *inventory.Inventory) (predefinedHead, error) { if !readable { return predefinedHead{}, errors.New("the forge did not answer") } list, err := catalogue.ParsePredefined([]byte(`{}`)) return predefinedHead{List: list, Repository: "novox/mesh-catalog", Commit: "abcdef0123456789", Generation: 1 << 40}, err } t.Cleanup(func() { predefinedAtTrunk = was }) } // aKeyFor is a done key of module at fp, of one archive with a reference, and the indexer's trunk value naming it at // commit in repository/path. func aKeyFor(module, version, fp, repository, path, commit string) (keyBuckets, sdkdelivery.Module) { raw := `{"module":"` + module + `","version":"` + version + `","build":{"artifacts":[{"name":"a","kind":"archive","from":"files"}]},` + `"resources":[{"id":"a","type":"archive","path":"/opt/` + module + `","artifact":"a"}]}` digest := sdkdelivery.ManifestDigest([]byte(raw)) blob := shaOf(module + version) k := builderkeys.Key{Module: module, Fingerprint: fp, Found: true, Revision: 1, Build: sdkdelivery.BuilderBuild{State: sdkdelivery.BuildDone, By: "ace", Manifest: digest, Artifacts: []sdkdelivery.BuiltArtifact{{Name: "a", Kind: "archive", Digest: blob, Reference: "artifact-store://" + module + "/a/blobs/" + blob}}}} return keyBuckets{keys: map[string]builderkeys.Key{module: k}, manifests: map[string]string{digest: raw}}, sdkdelivery.Module{FP2: fp, Manifest: digest, Repository: repository, Commit: commit, Path: path} } func fpOf(c byte) string { return "src2:" + strings.Repeat(string(c), 64) } // A3, A4 (ADR 0498 §8, ADR 0501 §1-§3, §8): a done key of a trunk fingerprint records one build of today's layout, // whose artifacts are the key's, and registers the module under the same rules of who may register as before. func TestADoneKeyOfTheTrunkRegistersUnderTheSameRules(t *testing.T) { open := theCatalogue(t) // sudo from novox/mesh-catalog modules/sudo, mesh-host from novox/mesh-host ctx := t.Context() inv := open.inventory withTrunkList(t, true) // A known module from the repository and path the catalogue builds it from: registered from its key. keys, trunk := aKeyFor("sudo", "2", fpOf('a'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789") _, kept, err := takeInKey(ctx, inv, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}}) if err != nil { t.Fatalf("0501 §1: a done key of sudo's trunk fingerprint was not taken in: %v", err) } if shelf, _ := inv.Catalogue(ctx); shelf["sudo"].Version != "2" { t.Fatalf("0501 §1: sudo is %q", shelf["sudo"].Version) } got, found, err := inv.BuildByID(ctx, kept.ID) if err != nil || !found || len(got.Made) != 1 || got.Made[0].Reference != kept.Made[0].Reference { t.Fatalf("0498 §8: the recorded build is %+v (%v)", got, err) } if fps, err := inv.BuildSourceFingerprints(ctx, "sudo"); err != nil || fps["c0ffee0123456789"] != fpOf('a') { t.Fatalf("0498 §8: the recorded build's fingerprint is %v (%v), not the key's", fps, err) } // Recording the same key again is one record. if _, _, err := takeInKey(ctx, inv, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}}); err != nil { t.Fatal(err) } builds, _ := inv.Builds(ctx, "sudo", 20) n := 0 for _, b := range builds { if b.ID == kept.ID { n++ } } if n != 1 { t.Fatalf("0498 §8: one key taken in twice is %d records", n) } // A known module from another repository: refused, as a build of it was (ADR 0266). other, otherTrunk := aKeyFor("sudo", "evil", fpOf('b'), "agent/sudo", "modules/sudo", "d00d0123456789ab") if _, _, err := takeInKey(ctx, inv, other, keyTake{Module: "sudo", Trunk: otherTrunk, Ask: registrationAsk{Merge: otherTrunk.Commit}}); !errors.Is(err, errNotItsSource) { t.Fatalf("0501 §3: a known module from another repository was taken in: %v", err) } // A new module from a repository the catalogue builds from: registered. zram, zramTrunk := aKeyFor("zram", "1", fpOf('c'), "novox/mesh-catalog", "modules/zram", "c0ffee0123456789") if _, _, err := takeInKey(ctx, inv, zram, keyTake{Module: "zram", Trunk: zramTrunk, Ask: registrationAsk{Merge: zramTrunk.Commit}}); err != nil { t.Fatalf("0501 §3: a new module from a built repository was refused: %v", err) } // A new module from a repository nothing is built from, with no Approve: refused. tools, toolsTrunk := aKeyFor("agent-tools", "1", fpOf('d'), "agent/tools", "", "e0e0e0e0e0e0e0e0") if _, _, err := takeInKey(ctx, inv, tools, keyTake{Module: "agent-tools", Trunk: toolsTrunk, Ask: registrationAsk{Merge: toolsTrunk.Commit}}); !errors.Is(err, errNotItsSource) { t.Fatalf("0501 §3: a new repository's module was taken in with no warrant: %v", err) } // At the terminal the operator registers it (ADR 0501 §4). if _, _, err := takeInKey(ctx, inv, tools, keyTake{Module: "agent-tools", Trunk: toolsTrunk, Ask: registrationAsk{Terminal: true}}); err != nil { t.Fatalf("0501 §4: the terminal's registration was refused: %v", err) } } // ADR 0501 §2: a key only a candidate names registers nothing: the trunk value names another fingerprint, whose key is // not there, and the take-in waits. func TestAKeyOnlyACandidateNamesRegistersNothing(t *testing.T) { open := theCatalogue(t) ctx := t.Context() withTrunkList(t, true) keys, trunk := aKeyFor("sudo", "3", fpOf('e'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789") trunk.FP2 = fpOf('f') // the trunk names another fingerprint than the candidate's done key _, _, err := takeInKey(ctx, open.inventory, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}}) if !errors.Is(err, errKeyWaits) { t.Fatalf("0501 §2: %v", err) } if shelf, _ := open.inventory.Catalogue(ctx); shelf["sudo"].Version != "1" { t.Fatalf("0501 §2: a candidate's key registered sudo %q", shelf["sudo"].Version) } } // ADR 0501 §8: a take-in that cannot read the list of predefined keys at the catalogue's trunk head waits, says so and // registers nothing. func TestATakeInWithoutTheTrunksListWaits(t *testing.T) { open := theCatalogue(t) ctx := t.Context() withTrunkList(t, false) keys, trunk := aKeyFor("sudo", "4", fpOf('1'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789") _, _, err := takeInKey(ctx, open.inventory, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}}) if !errors.Is(err, errKeyWaits) || !strings.Contains(err.Error(), "predefined keys") { t.Fatalf("0501 §8: %v", err) } if shelf, _ := open.inventory.Catalogue(ctx); shelf["sudo"].Version != "1" { t.Fatalf("0501 §8: registered %q", shelf["sudo"].Version) } } // A3 (the lead's decision on Against and Read): a key's record keeps the collector's keep set as it was and its read // repositories are its manifest's, fresh at every key. func TestAKeysRecordKeepsTheKeepSetAndReadsItsManifest(t *testing.T) { open := theCatalogue(t) ctx := t.Context() inv := open.inventory withTrunkList(t, true) before, err := inv.KeepSet(ctx) if err != nil { t.Fatal(err) } raw := `{"module":"sudo","version":"5","build":{"artifacts":[{"name":"img","kind":"image","from":"Dockerfile",` + `"context":{"repository":"novox/elsewhere","seat":"git"}}]},"resources":[{"id":"run","type":"container","artifact":"img"}]}` digest := sdkdelivery.ManifestDigest([]byte(raw)) k := builderkeys.Key{Module: "sudo", Fingerprint: fpOf('2'), Found: true, Revision: 1, Build: sdkdelivery.BuilderBuild{State: sdkdelivery.BuildDone, By: "g14", Manifest: digest, Artifacts: []sdkdelivery.BuiltArtifact{{Name: "img", Kind: "image", Digest: shaOf("img5"), Reference: "artifact-store://sudo/img@" + shaOf("img5")}}}} keys := keyBuckets{keys: map[string]builderkeys.Key{"sudo": k}, manifests: map[string]string{digest: raw}} trunk := sdkdelivery.Module{FP2: fpOf('2'), Manifest: digest, Repository: "novox/mesh-catalog", Commit: "c0ffee0123456789", Path: "modules/sudo"} if _, _, err := takeInKey(ctx, inv, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}}); err != nil { t.Fatal(err) } after, err := inv.KeepSet(ctx) if err != nil { t.Fatal(err) } for reference, why := range before { if !reflect.DeepEqual(after[reference], why) && !strings.Contains(strings.Join(after[reference], ","), strings.Join(why, ",")) { t.Errorf("the keep set changed for %s: %v, was %v", reference, after[reference], why) } } read, err := inv.ReadRepositories(ctx) if err != nil { t.Fatal(err) } found := false for _, r := range read["sudo"] { if !r.Own && r.Repository == "novox/elsewhere" { found = true } } if !found { text, _ := json.Marshal(read["sudo"]) t.Fatalf("the key's record does not read its manifest's context: %s", text) } } // A9 (ADR 0453 §6, ADR 0499 consequences): at the switch, a key's build whose every artifact has the digest of the // build-agent build the mesh holds (`src1:`) is no move: it is registered with that build's artifacts, and a walk sends // nothing for it. func TestAKeyWithTheHeldBuildsDigestsIsNoMove(t *testing.T) { open := theCatalogue(t) ctx := t.Context() inv := open.inventory withTrunkList(t, true) keys, trunk := aKeyFor("sudo", "6", fpOf('6'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789") k := keys.keys["sudo"] held := inventory.Build{ID: "build-sudo-old", Module: "sudo", Repository: "novox/mesh-catalog", Path: "modules/sudo", Commit: "0ld0ld0ld0ld0ld0", SourceFingerprint: "src1:" + strings.Repeat("9", 64), Made: []inventory.Artifact{{Name: "a", Kind: "archive", Reference: k.Build.Artifacts[0].Reference}}} held.Manifest, _ = json.Marshal(catalogue.Manifest{Module: "sudo", Version: "held"}) if err := inv.RecordBuild(ctx, held); err != nil { t.Fatal(err) } _, kept, err := takeInKey(ctx, inv, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}}) if err != nil { t.Fatal(err) } same, err := inv.SameSourceCommits(ctx, "sudo", kept.ID) if err != nil || !same["0ld0ld0ld0ld0ld0"] { t.Fatalf("A9: the key's build does not stand on the held build of the same digests: %v %v", same, err) } if shelf, _ := inv.Catalogue(ctx); shelf["sudo"].Version != "held" { t.Fatalf("A9: sudo is registered as %q, not with the held build's artifacts", shelf["sudo"].Version) } } // A13 against the store: the switch's list reads the build the mesh holds of each module and the builder's key of its // trunk fingerprint, and names each module unmoved, moved or not known. func TestTheSwitchsListReadsTheHeldBuildsAndTheKeys(t *testing.T) { open := theCatalogue(t) ctx := t.Context() inv := open.inventory keys, trunk := aKeyFor("sudo", "1", fpOf('b'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789") k := keys.keys["sudo"] if err := inv.RecordBuild(ctx, inventory.Build{ID: "build-sudo-held", Module: "sudo", Repository: "novox/mesh-catalog", Commit: "c0ffee0123456789", Made: []inventory.Artifact{{Name: "a", Kind: "archive", Reference: k.Build.Artifacts[0].Reference}}}); err != nil { t.Fatal(err) } reads := planKeysOf{values: map[string]sdkdelivery.Module{"sudo@": trunk}, keys: map[string]builderkeys.Key{"sudo": k}} lines, err := switchList(ctx, inv, reads) if err != nil { t.Fatal(err) } got := map[string]string{} for _, l := range lines { got[l.Module] = l.Verdict } if got["sudo"] != switchUnmoved || got["mesh-host"] != switchUnknown { t.Fatalf("A13: the switch's list is %+v", lines) } } // Issue 1902: the take-in with the lookup of the catalogue's repository NOT stubbed - the catalogue's modules are registered // with a URL source and no seat, as every live row is - reads the list from the forge's wire alone and registers the module. func TestATakeInFindsTheCataloguesRepositoryInTheURLForm(t *testing.T) { open := theCatalogue(t) ctx := t.Context() was, wasForms := formsOfSources, askGitea formsOfSources = func(context.Context, *inventory.Inventory) *sourceForms { return aFormsOnTheForge() } t.Cleanup(func() { formsOfSources, askGitea = was, wasForms }) aForgeAnsweringTheCatalogue(t) if err := open.inventory.RegisterModule(ctx, catalogue.Manifest{Module: "catalogue-anchor", Version: "1"}, inventory.Source{Repository: "http://anchor.internal:20000/novox/mesh-catalog.git", Path: "modules/catalogue-anchor", Ref: "main", BuiltFrom: "c0", Head: "c0"}); err != nil { t.Fatal(err) } keys, trunk := aKeyFor("sudo", "6", fpOf('7'), "novox/mesh-catalog", "modules/sudo", "c0ffee0123456789") if _, _, err := takeInKey(ctx, open.inventory, keys, keyTake{Module: "sudo", Trunk: trunk, Ask: registrationAsk{Merge: trunk.Commit}}); err != nil { t.Fatalf("a key was not taken in with the real lookup of the catalogue's repository: %v", err) } }