package bootstrap import ( "context" "encoding/json" "os" "path/filepath" "strings" "testing" ) // imageFormManifest is a controller from before issue 213: it builds an image and runs a container. const imageFormManifest = `{"module":"mesh-controller","version":"1","resources":[ {"id":"server","type":"container","name":"mesh-controller","network":"host","artifact":"server"}], "build":{"artifacts":[{"name":"server","kind":"image","from":"Dockerfile"}]}}` // processFormManifest is the controller's manifest as novox/mesh-controller declares it after issue // 213: a Go bundle the host runs as a process, replacing the container it ran as. const processFormManifest = `{ "module": "mesh-controller", "version": "1", "slug": "control", "prepares": true, "claims": [{"name": "mesh-controller", "scope": "mesh"}], "accesses": [{"path": "/var/lib/mesh-broker-tls", "mode": "read"}], "own-secrets": {"inventory": "${dir:mesh-state}/inventory", "bus": "${dir:mesh-state}/bus"}, "secrets-owner": "mesh-controller", "tools": ["status"], "resources": [ {"id": "account", "type": "user", "name": "mesh-controller", "shell": "/usr/bin/nologin", "home": "/var/lib/mesh-controller"}, {"id": "mesh-state", "type": "directory", "mode": "0700", "place": "mesh", "owner": "mesh-controller"}, {"id": "controller", "type": "process", "name": "mesh-controller", "artifact": "controller", "run": ["./mesh-controller", "serve"], "user": "mesh-controller", "env": {"MESH_BROKER_CERTIFICATE": "/var/lib/mesh-broker-tls/tls.crt", "MESH_STORE_INVENTORY_FILE": "${dir:mesh-state}/inventory", "MESH_STORE_INVENTORY_PORT": "${seat:mesh-store:5432}", "MESH_BUS_NATS_FILE": "${dir:mesh-state}/bus"}, "replaces": ["server"]} ], "build": {"artifacts": [{"name": "controller", "kind": "bundle", "language": "go", "system": "arch", "from": "cmd/mesh-controller", "binary": "mesh-controller"}]} }` // aRepository answers the carried builder's git as a clone of a repository holding this manifest and // a Dockerfile, and hands every other command to then. func aRepository(t *testing.T, manifest string, then func(string, []string) (string, error)) func(string, []string) (string, error) { t.Helper() return func(name string, args []string) (string, error) { if name == "docker" && len(args) > 5 && args[0] == "run" && args[4] == "--entrypoint" && args[5] == "git" { host := strings.TrimSuffix(args[3], ":/ws") joined := strings.Join(args, " ") switch { case strings.Contains(joined, " clone "): src := filepath.Join(host, "src") if err := os.MkdirAll(src, 0o755); err != nil { return "", err } if err := os.WriteFile(filepath.Join(src, "module.json"), []byte(manifest), 0o644); err != nil { return "", err } return "", os.WriteFile(filepath.Join(src, "Dockerfile"), []byte("FROM scratch\n"), 0o644) case strings.Contains(joined, "rev-parse"): return "a1b2c3d4e5f6\n", nil } return "", nil } return then(name, args) } } // novox/hq issue 223: a controller declared as a process is raised at genesis as a container built // from its own Dockerfile, not by the builder — which has no Go toolchain at genesis and would refuse. func TestAProcessFormControllerIsBuiltFromItsDockerfileAndRaisedAsAContainer(t *testing.T) { runtime := &asked{answer: aRepository(t, processFormManifest, func(name string, args []string) (string, error) { if name == "docker" && args[0] == "build" { return builtImage + "\n", nil } t.Fatalf("genesis ran %s %v; a process-form controller is built from its Dockerfile alone", name, args) return "", nil })} built, err := BuildControlPlane(context.Background(), runtime.run, "mesh-builder:test", Source{Repository: "https://example.invalid/mesh-controller.git", Ref: "a1b2c3d4"}, false, func(string) {}) if err != nil { t.Fatal(err) } if built.Image != builtImage || built.Commit != "a1b2c3d4e5f6" { t.Errorf("built %q from %q", built.Image, built.Commit) } if runtime.ran("mesh-builder:test build") { t.Error("the builder was asked to build a controller it cannot build at genesis") } var m map[string]any if err := json.Unmarshal(built.Manifest, &m); err != nil { t.Fatalf("the genesis manifest is not JSON: %v", err) } if _, has := m["prepares"]; has { t.Error("the genesis manifest prepares its state; the temporary controller already did, and a pinned image is nothing the controller can derive a step from") } if _, has := m["build"]; has { t.Error("the genesis manifest still says how it is built; it is handed over resolved") } var container map[string]any for _, raw := range m["resources"].([]any) { r := raw.(map[string]any) if r["type"] == "process" { t.Errorf("the genesis manifest still runs the process: %v", r) } if r["type"] == "container" { container = r } } if container == nil { t.Fatal("the genesis manifest runs no container") } for key, want := range map[string]any{"id": "server", "name": "mesh-controller", "image": builtImage, "network": "host"} { if container[key] != want { t.Errorf("the genesis container's %s is %v, not %v", key, container[key], want) } } if _, has := container["user"]; has { t.Error("the genesis container says a user; the host's container has no such field, the image's USER is who it runs as") } if m["secrets-owner"] != "65534:65534" { t.Errorf("the secrets belong to %v, which the container cannot read as", m["secrets-owner"]) } volumes, _ := json.Marshal(container["volumes"]) for _, want := range []string{"${dir:mesh-state}/inventory:${dir:mesh-state}/inventory:ro", "/var/lib/mesh-broker-tls/tls.crt:/var/lib/mesh-broker-tls/tls.crt:ro"} { if !strings.Contains(string(volumes), want) { t.Errorf("the container does not mount %s: %s", want, volumes) } } if strings.Contains(string(volumes), "seat:") { t.Errorf("a word that is not a path was mounted: %s", volumes) } // And it is what step 9 installs: pinned, its container found, its stores delivered from its // environment — the same path an image-form controller takes. pinned, _, err := pinImage(built.Manifest, built.Image, "registry.internal:5000/mesh-controller@sha256:"+strings.Repeat("e", 64), ControlPlaneModule) if err != nil { t.Fatal(err) } if id := controlPlaneResourceIn(pinned); id != "server" { t.Errorf("step 9 finds the controller's container as %q", id) } wanted, err := secretsByVariableIn(pinned) if err != nil { t.Fatalf("step 9 cannot deliver the stores into the genesis container: %v", err) } if wanted["MESH_STORE_INVENTORY"] != "inventory" { t.Errorf("step 9 delivers %v", wanted) } } // An older controller — an image and a container — is still built by the builder, as before. func TestAnImageFormControllerIsStillBuiltByTheBuilder(t *testing.T) { manifest := `{"module":"mesh-controller","version":"1","resources":[` + `{"id":"server","type":"container","name":"mesh-controller","image":"` + builtImage + `"}]}` runtime := &asked{answer: aRepository(t, imageFormManifest, func(name string, args []string) (string, error) { if name == "docker" && args[0] == "build" { t.Fatal("an image-form controller was built from its Dockerfile rather than by the builder") } return `{"module":"mesh-controller","commit":"a1b2c3d4","manifest":` + manifest + `,"made":[{"name":"server","kind":"image","reference":"` + builtImage + `"}]}`, nil })} built, err := BuildControlPlane(context.Background(), runtime.run, "mesh-builder:test", Source{Repository: "https://example.invalid/mesh-controller.git", Ref: "a1b2c3d4"}, false, func(string) {}) if err != nil { t.Fatal(err) } if string(built.Manifest) != manifest || !runtime.ran("mesh-builder:test build") { t.Errorf("the image form did not go through the builder: %s", built.Manifest) } } func TestAProcessFormNamingNoOneReplacementIsRefused(t *testing.T) { for _, bad := range []string{`"replaces": []`, `"replaces": ["a", "b"]`} { raw := strings.Replace(processFormManifest, `"replaces": ["server"]`, bad, 1) if _, err := processFormOf([]byte(raw)); err == nil { t.Errorf("a process saying %s was accepted; genesis would not know what to name its container", bad) } } } // theStepNineSecretsOf is what step 9 delivers for a process-form controller manifest: its genesis // container made, pinned, and its secrets read from that container's environment. func theStepNineSecretsOf(t *testing.T, manifest []byte) (map[string]string, error) { t.Helper() form, err := processFormOf(manifest) if err != nil || !form.Found { t.Fatalf("the manifest has no process form: %v", err) } genesis, err := genesisForm(manifest, form, builtImage) if err != nil { t.Fatal(err) } pinned, _, err := pinImage(genesis, builtImage, "registry.internal:5000/mesh-controller@sha256:"+strings.Repeat("e", 64), ControlPlaneModule) if err != nil { t.Fatal(err) } return secretsByVariableIn(pinned) } // **A file the controller's manifest writes itself is not a secret** (novox/hq issue 597). The merge // window's settings reach the controller in a file its manifest declares from its settings // (MESH_MERGE_WINDOW_FILE, ADR 0276); genesis refused it as a secret nobody writes, and stopped at // step 11 of 24. func TestAFileTheControllersManifestWritesIsNotASecret(t *testing.T) { manifest := strings.Replace(processFormManifest, `{"id": "controller", "type": "process",`, `{"id": "merge-window", "type": "file", "path": "${dir:mesh-state}/merge-window.json", "mode": "0600", "owner": "mesh-controller", "content": "{\"merge-window\": \"${setting:merge-window}\"}\n"}, {"id": "controller", "type": "process",`, 1) manifest = strings.Replace(manifest, `"MESH_BUS_NATS_FILE": "${dir:mesh-state}/bus"}`, `"MESH_BUS_NATS_FILE": "${dir:mesh-state}/bus", "MESH_MERGE_WINDOW_FILE": "${dir:mesh-state}/merge-window.json"}`, 1) if !strings.Contains(manifest, "MESH_MERGE_WINDOW_FILE") || !strings.Contains(manifest, `"id": "merge-window"`) { t.Fatal("the fixture was not changed") } wanted, err := theStepNineSecretsOf(t, []byte(manifest)) if err != nil { t.Fatalf("step 9 refuses a file the manifest writes itself: %v", err) } if _, asked := wanted["MESH_MERGE_WINDOW"]; asked { t.Errorf("the merge window's settings are delivered as a secret: %v", wanted) } if wanted["MESH_STORE_INVENTORY"] != "inventory" || wanted["MESH_BUS_NATS"] != "bus" { t.Errorf("step 9 delivers %v; the store's and the bus's secrets are still wanted", wanted) } } // And a `…_FILE` that neither an own-secret nor a file of the module writes is still refused. func TestAFileNothingOfTheControllerWritesIsStillRefused(t *testing.T) { manifest := strings.Replace(processFormManifest, `"MESH_BUS_NATS_FILE": "${dir:mesh-state}/bus"}`, `"MESH_BUS_NATS_FILE": "${dir:mesh-state}/bus", "MESH_MERGE_WINDOW_FILE": "${dir:mesh-state}/merge-window.json"}`, 1) if _, err := theStepNineSecretsOf(t, []byte(manifest)); err == nil || !strings.Contains(err.Error(), "MESH_MERGE_WINDOW_FILE") { t.Fatalf("a file nothing writes was accepted, or refused without its name: %v", err) } } // The controller's own manifest, as the mesh runs it, passes step 9 (issue 597): in a merge check the // node-engine's check has the controller cloned beside it (MESH_CHECK_BESIDE), where it must be; else a // checkout beside this one; else it skips aloud. func TestTheControllersOwnManifestPassesStepNine(t *testing.T) { var manifest []byte if root := os.Getenv("MESH_CHECK_BESIDE"); root != "" { raw, err := os.ReadFile(filepath.Join(root, "mesh-controller", "module.json")) if err != nil { t.Fatalf("the controller is not beside this check in MESH_CHECK_BESIDE=%s: %v", root, err) } manifest = raw } else if raw, err := os.ReadFile(filepath.Join("..", "..", "..", "mesh-controller", "module.json")); err == nil { manifest = raw } else { t.Skip("NOT JUDGED: no controller beside this checkout and no MESH_CHECK_BESIDE; the merge check judges it") } wanted, err := theStepNineSecretsOf(t, manifest) if err != nil { t.Fatalf("genesis's step 9 refuses the controller the mesh runs: %v", err) } if wanted["MESH_STORE_INVENTORY"] != "inventory" { t.Errorf("step 9 delivers %v", wanted) } }