package catalogue import ( "strings" "testing" ) // The node's tool runtime (novox/hq ADR 0175, to-be 38): where the runtime module is assigned, a // machine is sent every assigned module's tools bundle as an archive, and the runtime's own process // loading them. Where it is not, the machine is sent exactly what it was sent before. var bundleDigest = "sha256:" + strings.Repeat("b", 64) // aToolsModule is a module whose tools come as a compiled bundle and nothing else — the shape every // module takes once its tool container goes (to-be 38 WP4). func aToolsModule(t *testing.T, name string, entrypoints ...string) Manifest { t.Helper() m := Manifest{Module: name, Version: "1", Tools: []string{"status"}, Build: &Build{Artifacts: []Artifact{ {Name: "tools", Kind: ArtifactBundle, Language: "typescript", Entrypoints: entrypoints}, }}} resolved, err := m.Resolve([]Built{{Name: "tools", Kind: ArtifactBundle, Reference: ArtifactStoreScheme + name + "/tools/blobs/" + bundleDigest, Digest: bundleDigest}}) if err != nil { t.Fatal(err) } return resolved } // theRuntime is the runtime module as the catalogue holds it: its own bundle, run rather than // loaded, and its broker secret to receive the node's credential in. func theRuntime(t *testing.T) Manifest { t.Helper() m := Manifest{Module: RuntimeModule, Version: "1", OwnSecrets: OwnSecrets{"broker": {Path: "/var/lib/mesh/" + RuntimeModule + "/broker"}}, Build: &Build{Artifacts: []Artifact{{Name: "runtime", Kind: ArtifactBundle, Language: "typescript"}}}} resolved, err := m.Resolve([]Built{{Name: "runtime", Kind: ArtifactBundle, Reference: ArtifactStoreScheme + RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}}) if err != nil { t.Fatal(err) } return resolved } func TestABuildsBundlesAreCarriedOnTheResolvedManifest(t *testing.T) { m := aToolsModule(t, "nftables", "tools/index.js") if len(m.Bundles) != 1 { t.Fatalf("the resolved manifest carries %d bundle(s), not the one the build made", len(m.Bundles)) } b := m.Bundles[0] if b.Name != "tools" || b.Digest != bundleDigest || b.Language != "typescript" || b.Source != ArtifactStoreScheme+"nftables/tools/blobs/"+bundleDigest || len(b.Entrypoints) != 1 || b.Entrypoints[0] != "tools/index.js" { t.Errorf("the bundle is carried as %+v", b) } // A repository manifest may not write what the build derives. raw := `{"module":"x","version":"1","build":{"artifacts":[{"name":"t","kind":"bundle","language":"typescript"}]},` + `"bundles":[{"name":"t","source":"s","digest":"` + bundleDigest + `"}]}` if _, err := ParseManifest([]byte(raw)); err == nil || !strings.Contains(err.Error(), "bundles") { t.Errorf("a manifest stating its build's output by hand was accepted: %v", err) } } func TestEveryToolsBundleIsDeliveredWhereTheRuntimeRuns(t *testing.T) { store := Rendering{ArtifactStore: "anchor.internal:5101", Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}}} nftables := aToolsModule(t, "nftables", "tools/index.js") zsh := aToolsModule(t, "zsh", "tools/index.js", "tools/more.js") t.Run("with the runtime, one archive per tools bundle", func(t *testing.T) { r := Resolution{Node: "anchor", Modules: []Manifest{nftables, zsh, theRuntime(t)}} out, err := r.Declaration(store) if err != nil { t.Fatal(err) } archive := fileNamed(out, "nftables."+BundleID("tools")) if archive == nil { t.Fatalf("nftables' tools bundle was not delivered: %v", ids(out)) } if archive["type"] != "archive" || archive["digest"] != bundleDigest || archive["path"] != BundleRoot+"/nftables/tools" { t.Errorf("delivered as %v", archive) } if archive["source"] != "http://anchor.internal:5101/v2/nftables/tools/blobs/"+bundleDigest { t.Errorf("fetched from %v, not through the store as this network reaches it", archive["source"]) } if fileNamed(out, "zsh."+BundleID("tools")) == nil { t.Errorf("zsh's tools bundle was not delivered: %v", ids(out)) } // The runtime's own bundle is run, not loaded: its process delivers it, not an archive. if fileNamed(out, RuntimeModule+"."+BundleID("runtime")) != nil { t.Error("the runtime's own bundle was delivered as an archive beside its process") } }) t.Run("without the runtime, nothing changes", func(t *testing.T) { r := Resolution{Node: "anchor", Modules: []Manifest{nftables, zsh}} out, err := r.Declaration(store) if err != nil { t.Fatal(err) } for _, id := range ids(out) { if strings.Contains(id, BundleID("")) { t.Errorf("%s was delivered to a machine running no runtime to load it", id) } } }) } func ids(out []map[string]any) []string { var names []string for _, r := range out { names = append(names, r["id"].(string)) } return names }