Files
jschoubben 5dd439df50 inspect by kind, not the ambiguous bare form — a same-named network stops a container from ever being found
docker inspect <name> resolves across every object kind, not just
containers. A module regularly names a network the same as the
container that joins it (keycloak does this today, ordinarily) — so
when the container does not exist yet but the same-named network
already does, the bare form answers with the network's JSON instead
of reporting the container absent, and the template these callers use
(.State.Running) fails to execute against it entirely.

Live on novox tonight: minio's LB container, named the same as its
network ("minio"), could never be created — every apply crashed on
"the container runtime could not say whether minio is here", stuck
since first push, because the check itself never got a clean answer.

Fixed at every call site asking a container's state by name
(containerState, inspectFound, NamesFree, raiseGiteaServer,
containerRunning) by scoping to `docker container inspect`, matching
the type-scoped form this codebase already uses correctly for
networks, volumes and images elsewhere. Also scoped the one image
inspect that was still bare (publish.go), for the same reason.

mesh-host runs as a host-level service (nox-mesh-host.service), not a
Docker module — merging this does not redeploy it. The live novox
failure persists until the service itself is rebuilt and updated.
2026-09-24 19:50:16 +02:00

131 lines
4.8 KiB
Go

package bootstrap
import (
"context"
"errors"
"os"
"path/filepath"
"strings"
"testing"
"github.com/novox/mesh-host/internal/apply"
"github.com/novox/mesh-host/internal/declaration"
"github.com/novox/mesh-host/internal/store"
)
// Defends phase three's premise (phase3.go): the store genesis raised is adopted by the postgres
// module IN PLACE — same name, same image, same spec — so the applier reconciles it and never
// recreates the mesh's memory with the temporary control plane connected to it.
//
// The host folds a mounted file's content into the container's spec (novox/hq 04-ISSUES/103), so
// this now depends on a byte: the superuser file genesis writes and mounts must be the same bytes
// the module later declares. The module's value is what `secret accept` took — the operator's
// file with its line ending removed and nothing else (mesh-control, asSupplied). Reproduced before
// it was fixed: genesis wrote `value\n`, the module wrote `value`, and the store was recreated
// during install.
// labelled is a runtime that keeps the spec label the host gives a container and hands it back.
type labelled struct {
spec map[string]string
created []string
removed []string
}
func (l *labelled) run(_ context.Context, _ string, args ...string) (string, error) {
switch args[0] {
case "info":
return "27.0\n", nil
case "container":
spec, ok := l.spec[args[len(args)-1]]
if !ok {
return "", errors.New("no such container")
}
return "true\t" + spec + "\n", nil
case "rm":
l.removed = append(l.removed, args[len(args)-1])
delete(l.spec, args[len(args)-1])
case "run":
var name, spec string
for i, a := range args {
if a == "--name" {
name = args[i+1]
}
if a == "--label" && strings.HasPrefix(args[i+1], "mesh-host.spec=") {
spec = strings.TrimPrefix(args[i+1], "mesh-host.spec=")
}
}
l.spec[name] = spec
l.created = append(l.created, name)
return "made\n", nil
}
return "", nil
}
func TestTheStoreGenesisRaisedIsAdoptedInPlaceNotRecreated(t *testing.T) {
dir := t.TempDir()
secret := filepath.Join(dir, "superuser.secret")
// The bytes genesis really writes — the code path, not a fixture that agrees with it.
if _, made, err := keptOrMade(secret, false); err != nil || !made {
t.Fatalf("genesis did not make the superuser secret: made=%v err=%v", made, err)
}
onDisk, err := os.ReadFile(secret)
if err != nil {
t.Fatal(err)
}
image := "docker.io/library/postgres@sha256:" + strings.Repeat("ab", 32)
mounts := `"volumes":["mesh-store-data:/var/lib/postgresql/data","` + secret + `:` + storeSuperuserMount + `:ro"]`
// The foundation's store, as the produced bundle raises it (RewriteRoot): the file mounted,
// declared by nothing — genesis wrote it before there was a declaration to name it.
raise, err := declaration.ParseTrusted([]byte(`{"declaration":1,"resources":[
{"id":"` + StoreID + `","type":"container","name":"mesh-store","image":"` + image + `",
"env":{"POSTGRES_PASSWORD_FILE":"` + storeSuperuserMount + `"},` + mounts + `}
]}`))
if err != nil {
t.Fatal(err)
}
runtime := &labelled{spec: map[string]string{}}
_, known, err := apply.Apply(context.Background(), arch(t), raise, store.State{}, store.OriginCarried,
runtime.run, nil, nil)
if err != nil {
t.Fatalf("raising the foundation's store: %v", err)
}
if len(runtime.created) != 1 {
t.Fatalf("the store was not raised once: %v", runtime.created)
}
// The postgres module's declaration of the same store: the superuser file as `secret accept`
// took it in — its line ending removed and nothing else — then the same container.
accepted := strings.TrimRight(string(onDisk), "\r\n")
adopt, err := declaration.ParseTrusted([]byte(`{"declaration":1,"resources":[
{"id":"postgres.superuser","type":"file","path":"` + secret + `","content":"` + accepted + `","mode":"0600"},
{"id":"postgres.server","type":"container","name":"mesh-store","image":"` + image + `",
"env":{"POSTGRES_PASSWORD_FILE":"` + storeSuperuserMount + `"},` + mounts + `}
]}`))
if err != nil {
t.Fatal(err)
}
runtime.created, runtime.removed = nil, nil
report, _, err := apply.Apply(context.Background(), arch(t), adopt, known, store.OriginDeclared,
runtime.run, nil, nil)
if err != nil {
t.Fatalf("adopting the store: %v", err)
}
if len(runtime.removed) > 0 || len(runtime.created) > 0 {
t.Fatalf("the module's declaration recreated the store genesis raised (removed %v, created %v): "+
"the file genesis mounted and the file the module declares are not the same bytes",
runtime.removed, runtime.created)
}
for _, o := range report.Outcomes {
if o.ID == "postgres.server" && o.Action != "unchanged" {
t.Errorf("the store was not adopted in place: %+v", o)
}
if o.ID == "postgres.superuser" && o.Action != "unchanged" {
t.Errorf("the module rewrote the superuser file genesis wrote: %+v", o)
}
}
}