diff --git a/internal/builder/builder.go b/internal/builder/builder.go index 4fdfc42..5fcbf54 100644 --- a/internal/builder/builder.go +++ b/internal/builder/builder.go @@ -1171,6 +1171,9 @@ func readBy(manifest catalogue.Manifest) []catalogue.ArtifactContext { // binaryName is what a compiled bundle's executable is called: what the artifact says, or the name of // the package it is built from, which is what a compiler would have chosen anyway. func binaryName(a catalogue.Artifact) string { + if name := catalogue.BinaryOf(a); name != "" { + return name + } if name := strings.TrimSpace(a.Binary); name != "" { return name } diff --git a/internal/catalogue/build.go b/internal/catalogue/build.go index 88f77ab..1bc31c6 100644 --- a/internal/catalogue/build.go +++ b/internal/catalogue/build.go @@ -2,6 +2,7 @@ package catalogue import ( "fmt" + "path" "sort" "strings" ) @@ -96,6 +97,7 @@ func (m Manifest) Resolve(built []Built) (Manifest, error) { Name: a.Name, Source: Recorded(made.Reference), Digest: made.Digest, Language: a.Language, Entrypoints: append([]string(nil), a.Entrypoints...), Loads: loads, Env: copyWords(a.Env), Launchers: copyWords(made.Launchers), + Binary: BinaryOf(a), }) } sort.Slice(out.Bundles, func(i, j int) bool { return out.Bundles[i].Name < out.Bundles[j].Name }) @@ -417,3 +419,20 @@ func copyWords(in map[string]string) map[string]string { } return out } + +// BinaryOf is what a bundle compiled to a binary is called once built: what the artifact names, else +// the package it is built from, else the artifact's own name (novox/hq 04-ISSUES/142). Empty for a +// language that does not compile to one. The builder writes the binary under this name, and the +// composer runs it by it, so both ask here. +func BinaryOf(a Artifact) string { + if !compilesToABinary(a.Language) { + return "" + } + if name := strings.TrimSpace(a.Binary); name != "" { + return name + } + if from := strings.Trim(a.From, "./"); from != "" { + return path.Base(from) + } + return a.Name +} diff --git a/internal/catalogue/manifest.go b/internal/catalogue/manifest.go index 8acd381..01686d5 100644 --- a/internal/catalogue/manifest.go +++ b/internal/catalogue/manifest.go @@ -607,6 +607,9 @@ type Bundle struct { // Launchers are the executables the build wrote beside its entrypoints, by entrypoint (novox/hq // ADR 0193). A bundle built before them has none, and is served as it was built. Launchers map[string]string `json:"launchers,omitempty"` + // Binary is the executable a bundle compiled to a binary is, at its root (novox/hq ADR 0193): + // what runs it, where an interpreted bundle names an interpreter and an entrypoint. + Binary string `json:"binary,omitempty"` } // Build says how to produce this module's artifacts from its source. diff --git a/internal/catalogue/runtime.go b/internal/catalogue/runtime.go index 4a33cc7..65d326b 100644 --- a/internal/catalogue/runtime.go +++ b/internal/catalogue/runtime.go @@ -129,14 +129,22 @@ func (r Resolution) runtimeProcess(with Rendering) (map[string]any, error) { RuntimeModule, r.Node, len(runtime.Bundles)) } bundle := runtime.Bundles[0] - if len(bundle.Entrypoints) != 1 { - return nil, fmt.Errorf( - "%s's bundle %q names %d entrypoint(s); the runtime is run from one, so the module "+ - "declares exactly one (novox/hq to-be 38)", RuntimeModule, bundle.Name, len(bundle.Entrypoints)) - } - interpreter, err := interpreterFor(bundle.Language) - if err != nil { - return nil, err + // What runs it (novox/hq ADR 0193): a runtime compiled to a binary runs itself, from its own + // unpacked bundle; an interpreted one is its language's interpreter and its one entrypoint. + var run []any + if bundle.Binary != "" { + run = []any{"./" + bundle.Binary} + } else { + if len(bundle.Entrypoints) != 1 { + return nil, fmt.Errorf( + "%s's bundle %q names %d entrypoint(s); the runtime is run from one, so the module "+ + "declares exactly one (novox/hq to-be 38)", RuntimeModule, bundle.Name, len(bundle.Entrypoints)) + } + interpreter, err := interpreterFor(bundle.Language) + if err != nil { + return nil, err + } + run = []any{interpreter, bundle.Entrypoints[0]} } credential, declared := runtime.OwnSecrets["broker"] if !declared { @@ -198,7 +206,7 @@ func (r Resolution) runtimeProcess(with Rendering) (map[string]any, error) { process := map[string]any{ "id": RuntimeModule + "." + RuntimeProcessID(), "type": "process", "name": RuntimeModule, "source": bundle.Source, "digest": bundle.Digest, - "run": []any{interpreter, bundle.Entrypoints[0]}, + "run": run, "env": env, "restart-on": toAny(restartOn), } diff --git a/internal/catalogue/runtime_test.go b/internal/catalogue/runtime_test.go index 0411124..fa4dc43 100644 --- a/internal/catalogue/runtime_test.go +++ b/internal/catalogue/runtime_test.go @@ -362,3 +362,30 @@ func TestTheRuntimeStartsTheLauncherWhereTheBuildWroteOne(t *testing.T) { 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"]) + } +}