460 lines
21 KiB
Go
460 lines
21 KiB
Go
package catalogue
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"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",
|
|
Entrypoints: []string{"src/main.js"}}}}}
|
|
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
|
|
}
|
|
|
|
// One process per machine runs the runtime from its own bundle, told what it serves and from where,
|
|
// where its credential is, and who the operator is — restarted when any of that changes.
|
|
func TestTheMachineRunsOneRuntimeLoadingEveryDeliveredBundle(t *testing.T) {
|
|
with := Rendering{ArtifactStore: "anchor.internal:5101",
|
|
Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}}}
|
|
nftables := aToolsModule(t, "nftables", "tools/index.js")
|
|
// A bundle carrying a daemon beside its tools says which files the runtime loads.
|
|
showcase := Manifest{Module: "showcase", Version: "1", Tools: []string{"greet"},
|
|
Build: &Build{Artifacts: []Artifact{{Name: "code", Kind: ArtifactBundle, Language: "typescript",
|
|
Entrypoints: []string{"daemon/index.js", "tools/index.js"}, Loads: []string{"tools/index.js"}}}}}
|
|
showcase, err := showcase.Resolve([]Built{{Name: "code", Kind: ArtifactBundle,
|
|
Reference: ArtifactStoreScheme + "showcase/code/blobs/" + bundleDigest, Digest: bundleDigest}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
r := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{nftables, showcase, theRuntime(t)}}
|
|
out, err := r.Declaration(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
process := fileNamed(out, RuntimeModule+"."+RuntimeProcessID())
|
|
if process == nil {
|
|
t.Fatalf("no runtime process was composed: %v", ids(out))
|
|
}
|
|
if process["type"] != "process" || process["name"] != RuntimeModule || process["digest"] != bundleDigest ||
|
|
process["source"] != "http://anchor.internal:5101/v2/"+RuntimeModule+"/runtime/blobs/"+bundleDigest {
|
|
t.Errorf("the runtime's process is %v", process)
|
|
}
|
|
if fmt.Sprint(process["run"]) != "[node src/main.js]" {
|
|
t.Errorf("the runtime is run as %v; its bundle's one entrypoint, by its language's interpreter", process["run"])
|
|
}
|
|
env := process["env"].(map[string]string)
|
|
if env[RuntimeToolModules] != "nftables="+BundleRoot+"/nftables/tools/tools/index.js,"+
|
|
"showcase="+BundleRoot+"/showcase/code/tools/index.js" {
|
|
t.Errorf("the runtime is told to serve %q: every loaded file, by module, and nothing a bundle runs", env[RuntimeToolModules])
|
|
}
|
|
if env[RuntimeBrokerFile] != "/var/lib/mesh/"+RuntimeModule+"/broker" {
|
|
t.Errorf("the runtime reads its credential at %q, not where the module's own secret is placed", env[RuntimeBrokerFile])
|
|
}
|
|
if env[RuntimeOperatorAccount] != "ops" || env[RuntimeOperatorHome] != "/home/ops" || process["user"] != "ops" {
|
|
t.Errorf("the operator is not handed to the runtime: %v as %v", env, process["user"])
|
|
}
|
|
// The credential the process reads belongs to the account it runs as, or it could not read it
|
|
// (to-be 38 WP3); other modules' secrets are left as their manifests say.
|
|
if credential := fileNamed(out, RuntimeModule+"."+NeedID("broker")); credential == nil || credential["owner"] != "ops" {
|
|
t.Errorf("the runtime's credential is not the account's to read: %v", credential)
|
|
}
|
|
restarts := fmt.Sprint(process["restart-on"])
|
|
for _, want := range []string{"nftables." + BundleID("tools"), "showcase." + BundleID("code"), RuntimeModule + "." + NeedID("broker")} {
|
|
if !strings.Contains(restarts, want) {
|
|
t.Errorf("the runtime is not restarted when %s changes: %s", want, restarts)
|
|
}
|
|
}
|
|
// After every bundle and the credential, so both exist before it starts.
|
|
names := ids(out)
|
|
if names[len(names)-1] != RuntimeModule+"."+RuntimeProcessID() {
|
|
t.Errorf("the runtime's process is not last: %v", names)
|
|
}
|
|
|
|
t.Run("a machine with no account runs it as root without the operator words", func(t *testing.T) {
|
|
out, err := Resolution{Node: "anchor", Modules: []Manifest{nftables, theRuntime(t)}}.Declaration(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
process := fileNamed(out, RuntimeModule+"."+RuntimeProcessID())
|
|
env := process["env"].(map[string]string)
|
|
if _, set := env[RuntimeOperatorAccount]; set {
|
|
t.Error("an operator account was named on a machine that has none")
|
|
}
|
|
if _, set := process["user"]; set {
|
|
t.Error("a user was set on a machine with no account")
|
|
}
|
|
if credential := fileNamed(out, RuntimeModule+"."+NeedID("broker")); credential == nil || credential["owner"] != nil {
|
|
t.Errorf("the runtime's credential was given an owner on a machine with no account: %v", credential)
|
|
}
|
|
})
|
|
|
|
t.Run("a runtime module built wrong is refused by name", func(t *testing.T) {
|
|
two := Manifest{Module: RuntimeModule, Version: "1", OwnSecrets: OwnSecrets{"broker": {Path: "/b"}},
|
|
Build: &Build{Artifacts: []Artifact{{Name: "runtime", Kind: ArtifactBundle, Language: "typescript",
|
|
Entrypoints: []string{"a.js", "b.js"}}}}}
|
|
resolved, err := two.Resolve([]Built{{Name: "runtime", Kind: ArtifactBundle,
|
|
Reference: ArtifactStoreScheme + "x/runtime/blobs/" + bundleDigest, Digest: bundleDigest}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_, err = Resolution{Node: "anchor", Modules: []Manifest{resolved}}.Declaration(with)
|
|
if err == nil || !strings.Contains(err.Error(), "entrypoint") {
|
|
t.Errorf("a runtime bundle with two entrypoints was composed: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
// novox/hq ADR 0192: a tools bundle says what it is given; the composer resolves it per machine as
|
|
// a container's environment, hands it to the runtime as the module's words, and makes what the
|
|
// words name readable by the account the runtime runs as.
|
|
func TestABundleIsGivenItsWordsResolvedForThisMachine(t *testing.T) {
|
|
with := Rendering{ArtifactStore: "anchor.internal:5101",
|
|
Needed: map[string]map[string]string{
|
|
RuntimeModule: {"broker": "sealed-credential"},
|
|
"dash": {"token": "sealed-token"},
|
|
}}
|
|
dash := Manifest{Module: "dash", Version: "1", Tools: []string{"status"},
|
|
Listens: []Listening{{Name: "web", Port: 3000, Protocol: "tcp"}},
|
|
OwnSecrets: OwnSecrets{"token": {Path: "${dir:mesh-state}/token"}},
|
|
Resources: []map[string]any{
|
|
{"id": "mesh-state", "type": "directory", "mode": "0700", "place": "mesh"},
|
|
{"id": "config", "type": "file", "path": "${dir:mesh-state}/config.json", "mode": "0600", "content": "{}\n"},
|
|
{"id": "unrelated", "type": "file", "path": "/etc/dash.conf", "content": "x\n"},
|
|
},
|
|
Build: &Build{Artifacts: []Artifact{{Name: "tools", Kind: ArtifactBundle, Language: "typescript",
|
|
Entrypoints: []string{"tools/index.js"},
|
|
Env: map[string]string{
|
|
"DASH_CONFIG_FILE": "${dir:mesh-state}/config.json",
|
|
"DASH_TOKEN_FILE": "${dir:mesh-state}/token",
|
|
"DASH_URL": "http://127.0.0.1:${port:3000}",
|
|
"DASH_ADMIN": "mesh-admin",
|
|
}}}}}
|
|
if problems := dash.Build.problems(dash.Module); len(problems) > 0 {
|
|
t.Fatalf("a bundle's words written with ${dir:…} and ${port:…} were refused: %v", problems)
|
|
}
|
|
dash, err := dash.Resolve([]Built{{Name: "tools", Kind: ArtifactBundle,
|
|
Reference: ArtifactStoreScheme + "dash/tools/blobs/" + bundleDigest, Digest: bundleDigest}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
other := aToolsModule(t, "nftables", "tools/index.js")
|
|
|
|
out, err := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{dash, other, theRuntime(t)}}.Declaration(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
dir := fileNamed(out, "dash.mesh-state")
|
|
if dir == nil {
|
|
t.Fatalf("no directory: %v", ids(out))
|
|
}
|
|
at := fmt.Sprint(dir["path"])
|
|
process := fileNamed(out, RuntimeModule+"."+RuntimeProcessID())
|
|
env := process["env"].(map[string]string)
|
|
var given map[string]map[string]string
|
|
if err := json.Unmarshal([]byte(env[RuntimeToolEnv]), &given); err != nil {
|
|
t.Fatalf("the runtime's %s is not JSON: %q", RuntimeToolEnv, env[RuntimeToolEnv])
|
|
}
|
|
want := map[string]string{
|
|
"DASH_CONFIG_FILE": at + "/config.json",
|
|
"DASH_TOKEN_FILE": at + "/token",
|
|
"DASH_URL": "http://127.0.0.1:3000",
|
|
"DASH_ADMIN": "mesh-admin",
|
|
}
|
|
if fmt.Sprint(given["dash"]) != fmt.Sprint(want) {
|
|
t.Errorf("dash is given %v, want %v", given["dash"], want)
|
|
}
|
|
if _, has := given["nftables"]; has {
|
|
t.Errorf("a module that declares no words was given some: %v", given)
|
|
}
|
|
// What the words name is the account's to read; nothing else of the module's is touched.
|
|
for _, id := range []string{"dash.mesh-state", "dash.config", "dash." + NeedID("token")} {
|
|
if r := fileNamed(out, id); r == nil || r["owner"] != "ops" {
|
|
t.Errorf("%s is not the account's to read: %v", id, r)
|
|
}
|
|
}
|
|
if r := fileNamed(out, "dash.unrelated"); r == nil || r["owner"] != nil {
|
|
t.Errorf("a file no word names was given an owner: %v", r)
|
|
}
|
|
// A file a word names restarts the runtime when it changes, as it restarted the tool container.
|
|
restarts := fmt.Sprint(process["restart-on"])
|
|
for _, want := range []string{"dash.config", "dash." + NeedID("token")} {
|
|
if !strings.Contains(restarts, want) {
|
|
t.Errorf("the runtime is not restarted when %s changes: %s", want, restarts)
|
|
}
|
|
}
|
|
if strings.Contains(restarts, "dash.unrelated") || strings.Contains(restarts, "dash.mesh-state") {
|
|
t.Errorf("the runtime restarts for something no word names as a file: %s", restarts)
|
|
}
|
|
|
|
t.Run("on a machine with no account the runtime is root and nothing is re-owned", func(t *testing.T) {
|
|
out, err := Resolution{Node: "anchor", Modules: []Manifest{dash, theRuntime(t)}}.Declaration(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if r := fileNamed(out, "dash.config"); r["owner"] != nil {
|
|
t.Errorf("re-owned with no account: %v", r)
|
|
}
|
|
})
|
|
|
|
t.Run("a change to a module's words changes the runtime's process", func(t *testing.T) {
|
|
changed := dash
|
|
changed.Bundles = append([]Bundle(nil), dash.Bundles...)
|
|
changed.Bundles[0].Env = map[string]string{"DASH_ADMIN": "somebody-else"}
|
|
out2, err := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{changed, theRuntime(t)}}.Declaration(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if fmt.Sprint(fileNamed(out2, RuntimeModule+"."+RuntimeProcessID())["env"]) == fmt.Sprint(env) {
|
|
t.Error("the runtime's process is the same after a module's words changed, so it would not restart")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestABundlesWordsAreRefusedWhenTheyAreNotPathsOrConstants(t *testing.T) {
|
|
m := Manifest{Module: "dash", Version: "1", Build: &Build{Artifacts: []Artifact{
|
|
{Name: "tools", Kind: ArtifactBundle, Language: "typescript", Entrypoints: []string{"tools/index.js"},
|
|
Env: map[string]string{"DASH_TOKEN": "${secret:token}", RuntimeBrokerFile: "/x", "DASH_PEER": "${bound:db:url}"}},
|
|
{Name: "runtime", Kind: ArtifactImage, From: "Dockerfile", Env: map[string]string{"X": "y"}},
|
|
}}}
|
|
said := strings.Join(m.Build.problems(m.Module), "\n")
|
|
for _, want := range []string{
|
|
`"tools" gives DASH_TOKEN the value "${secret:token}"`,
|
|
`"tools" gives DASH_PEER the value "${bound:db:url}"`,
|
|
`"tools" gives itself ` + RuntimeBrokerFile,
|
|
`"runtime" is a "image" and says what it is given`,
|
|
} {
|
|
if !strings.Contains(said, want) {
|
|
t.Errorf("not refused: %s\nsaid:\n%s", want, said)
|
|
}
|
|
}
|
|
}
|
|
|
|
// novox/hq ADR 0193: the runtime is told the launcher a build wrote, and the entrypoint itself for a
|
|
// build from before launchers — so the move needs no flag day.
|
|
func TestTheRuntimeStartsTheLauncherWhereTheBuildWroteOne(t *testing.T) {
|
|
with := Rendering{ArtifactStore: "anchor.internal:5101",
|
|
Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}}}
|
|
launched := Manifest{Module: "dash", Version: "1", Tools: []string{"status"},
|
|
Build: &Build{Artifacts: []Artifact{{Name: "tools", Kind: ArtifactBundle, Language: "typescript",
|
|
Entrypoints: []string{"tools/index.js"}}}}}
|
|
launched, err := launched.Resolve([]Built{{Name: "tools", Kind: ArtifactBundle,
|
|
Reference: ArtifactStoreScheme + "dash/tools/blobs/" + bundleDigest, Digest: bundleDigest,
|
|
Launchers: map[string]string{"tools/index.js": "tools/index.serve.mjs"}}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
older := aToolsModule(t, "nftables", "tools/index.js")
|
|
out, err := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{launched, older, theRuntime(t)}}.Declaration(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
env := fileNamed(out, RuntimeModule+"."+RuntimeProcessID())["env"].(map[string]string)
|
|
want := "dash=" + BundleRoot + "/dash/tools/tools/index.serve.mjs,nftables=" + BundleRoot + "/nftables/tools/tools/index.js"
|
|
if env[RuntimeToolModules] != want {
|
|
t.Errorf("the runtime is told %q, want %q", env[RuntimeToolModules], want)
|
|
}
|
|
}
|
|
|
|
// novox/hq ADR 0193: a runtime compiled to a binary runs itself from its own unpacked bundle.
|
|
func TestARuntimeCompiledToABinaryRunsItself(t *testing.T) {
|
|
with := Rendering{ArtifactStore: "anchor.internal:5101",
|
|
Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}}}
|
|
goRuntime := Manifest{Module: RuntimeModule, Version: "1",
|
|
OwnSecrets: OwnSecrets{"broker": {Path: "/var/lib/mesh/" + RuntimeModule + "/broker"}},
|
|
Build: &Build{Artifacts: []Artifact{{Name: "runtime", Kind: ArtifactBundle, Language: "go",
|
|
System: "arch", From: "cmd/node-tools"}}}}
|
|
goRuntime, err := goRuntime.Resolve([]Built{{Name: "runtime", Kind: ArtifactBundle,
|
|
Reference: ArtifactStoreScheme + RuntimeModule + "/runtime/blobs/" + bundleDigest, Digest: bundleDigest}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{aToolsModule(t, "nftables", "tools/index.js"), goRuntime}}.Declaration(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
process := fileNamed(out, RuntimeModule+"."+RuntimeProcessID())
|
|
if fmt.Sprint(process["run"]) != "[./node-tools]" {
|
|
t.Errorf("a Go runtime is run as %v, want its own binary", process["run"])
|
|
}
|
|
env := process["env"].(map[string]string)
|
|
if env[RuntimeToolModules] == "" || process["user"] != "ops" {
|
|
t.Errorf("the Go runtime is not told what to serve or whose it is: %v %v", env, process["user"])
|
|
}
|
|
}
|
|
|
|
// novox/hq 04-ISSUES/216: a bundle nothing loads, runs or unpacks is refused at registration; saying
|
|
// `loads`, listing `tools`, or a resource naming it admits it.
|
|
func TestABundleNothingDeliversIsRefused(t *testing.T) {
|
|
base := func() Manifest {
|
|
return Manifest{Module: "baserow", Version: "1", Build: &Build{Artifacts: []Artifact{
|
|
{Name: "tools", Kind: ArtifactBundle, Language: "typescript", Entrypoints: []string{"tools/index.js"}}}}}
|
|
}
|
|
if p := undeliveredBundles(base()); len(p) != 1 || !strings.Contains(p[0], "never reach a machine") {
|
|
t.Fatalf("a bundle nothing delivers was admitted: %v", p)
|
|
}
|
|
loads := base()
|
|
loads.Build.Artifacts[0].Loads = []string{"tools/index.js"}
|
|
tools := base()
|
|
tools.Tools = []string{"baserow_list_rows"}
|
|
run := base()
|
|
run.Resources = []map[string]any{{"id": "daemon", "type": "process", "artifact": "tools", "run": []any{"node", "tools/index.js"}}}
|
|
runtime := base()
|
|
runtime.Module = RuntimeModule
|
|
for name, m := range map[string]Manifest{"loads": loads, "tools": tools, "a process": run, "the runtime": runtime} {
|
|
if p := undeliveredBundles(m); len(p) != 0 {
|
|
t.Errorf("a bundle delivered by %s was refused: %v", name, p)
|
|
}
|
|
}
|
|
}
|
|
|
|
// novox/hq ADR 0193: a Go tools bundle is served — its binary is what the runtime starts, delivered
|
|
// like any tools bundle, named to the runtime where a TypeScript bundle names its launcher.
|
|
func TestAGoToolsBundleIsServedByItsBinary(t *testing.T) {
|
|
with := Rendering{ArtifactStore: "anchor.internal:5101",
|
|
Needed: map[string]map[string]string{RuntimeModule: {"broker": "sealed-credential"}}}
|
|
lamp := Manifest{Module: "lamp", Version: "1", Tools: []string{"on"},
|
|
Build: &Build{Artifacts: []Artifact{{Name: "tools", Kind: ArtifactBundle, Language: "go",
|
|
System: "arch", From: "cmd/lamp-tools"}}}}
|
|
if p := lamp.Build.problems("lamp"); len(p) != 0 {
|
|
t.Fatalf("a Go tools bundle was refused: %v", p)
|
|
}
|
|
lamp, err := lamp.Resolve([]Built{{Name: "tools", Kind: ArtifactBundle,
|
|
Reference: ArtifactStoreScheme + "lamp/tools/blobs/" + bundleDigest, Digest: bundleDigest}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if fmt.Sprint(lamp.Bundles[0].Loads) != "[lamp-tools]" {
|
|
t.Fatalf("the runtime loads %v from a Go bundle, want its binary", lamp.Bundles[0].Loads)
|
|
}
|
|
out, err := Resolution{Node: "anchor", Account: "ops", Modules: []Manifest{lamp, theRuntime(t)}}.Declaration(with)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if fileNamed(out, "lamp."+BundleID("tools")) == nil {
|
|
t.Errorf("the Go bundle is not delivered: %v", ids(out))
|
|
}
|
|
env := fileNamed(out, RuntimeModule+"."+RuntimeProcessID())["env"].(map[string]string)
|
|
if env[RuntimeToolModules] != "lamp="+BundlePath("lamp", "tools")+"/lamp-tools" {
|
|
t.Errorf("the runtime is told %q, want the binary", env[RuntimeToolModules])
|
|
}
|
|
|
|
// An artifact may say it explicitly; naming anything but the binary is refused.
|
|
said := Manifest{Module: "lamp", Version: "1", Build: &Build{Artifacts: []Artifact{{Name: "tools",
|
|
Kind: ArtifactBundle, Language: "go", System: "arch", Binary: "lamp", Loads: []string{"lamp"}}}}}
|
|
if p := said.Build.problems("lamp"); len(p) != 0 {
|
|
t.Errorf("loads naming the binary was refused: %v", p)
|
|
}
|
|
said.Build.Artifacts[0].Loads = []string{"tools/index.js"}
|
|
if p := said.Build.problems("lamp"); len(p) == 0 {
|
|
t.Error("a Go bundle loading a file it does not contain was admitted")
|
|
}
|
|
}
|