One name per thing, per the HQ glossary: the module/container/image/binary/repo becomes mesh-controller, the seat the-controller, and the store+broker pair the foundation (embedded base bundles, default template and example lock renamed with their go:embed directives). No behaviour change — a pure vocabulary rename. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
322 lines
12 KiB
Go
322 lines
12 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-host/internal/declaration"
|
|
)
|
|
|
|
// Each test names the decision it defends (novox/hq ADR 0017).
|
|
|
|
const (
|
|
held = "sha256:1111111111111111111111111111111111111111111111111111111111111111"
|
|
otherHeld = "sha256:2222222222222222222222222222222222222222222222222222222222222222"
|
|
)
|
|
|
|
// theRealBundle is this repository's own foundation example, used rather than a fixture.
|
|
//
|
|
// A fixture would agree with whatever this code does. The example is what an installer is actually
|
|
// pointed at, it names the control plane twice, and it is the file that changes when the foundation
|
|
// changes — so a rewrite that stops working on it is a rewrite that has stopped working.
|
|
func theRealBundle(t *testing.T) []byte {
|
|
t.Helper()
|
|
raw, err := os.ReadFile("../../examples/foundation-first-node.lock")
|
|
if err != nil {
|
|
t.Fatalf("reading the foundation example: %v", err)
|
|
}
|
|
return raw
|
|
}
|
|
|
|
func TestTheControlPlaneIsNamedByTheImageThisMachineHolds(t *testing.T) {
|
|
out, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !out.Changed {
|
|
t.Error("the rewrite reported nothing changed, and the template named a registry image")
|
|
}
|
|
control, err := controlPlaneIn(out.Declaration)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if control.Image != held {
|
|
t.Errorf("the control plane is %q, want %q", control.Image, held)
|
|
}
|
|
}
|
|
|
|
// **Every place the bundle names that image, not only the container.**
|
|
//
|
|
// The foundation names the control plane's image twice: the container that runs `serve`, and the
|
|
// action that runs `migrate` to create the contexts' schemas. Rewriting only the container leaves
|
|
// the migration pointing at an image no registry serves, and the apply dies in the middle — after
|
|
// the store is up and before the broker. This is the test that would have caught that.
|
|
func TestEveryPlaceTheBundleNamesTheControlPlaneIsRewritten(t *testing.T) {
|
|
template := theRealBundle(t)
|
|
|
|
out, err := Rewrite(template, held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out.Places < 2 {
|
|
t.Fatalf("the control plane's image was found in %d place(s); the foundation names it in "+
|
|
"the container AND in the migration action", out.Places)
|
|
}
|
|
if remaining := strings.Count(string(out.Bundle), out.Was); remaining != 0 {
|
|
t.Errorf("the produced bundle still names %q in %d place(s)", out.Was, remaining)
|
|
}
|
|
if got := strings.Count(string(out.Bundle), held); got != out.Places {
|
|
t.Errorf("the produced bundle names the held image %d time(s), and %d were replaced",
|
|
got, out.Places)
|
|
}
|
|
}
|
|
|
|
// Third-party images are somebody else's, at somebody else's registry, and the installer has no
|
|
// business touching them (novox/hq ADR 0006).
|
|
func TestPostgresAndTheBrokerAreLeftExactlyAsTheyWere(t *testing.T) {
|
|
template := theRealBundle(t)
|
|
|
|
out, err := Rewrite(template, held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
produced := containerImages(out.Declaration)
|
|
for _, id := range []string{"store", "broker"} {
|
|
image, named := produced[id]
|
|
if !named {
|
|
t.Fatalf("the foundation example no longer declares a %q container", id)
|
|
}
|
|
// Compared against the template's own text rather than against an expectation written
|
|
// here: what is being defended is "unchanged", and the template is the only thing that
|
|
// knows what it said.
|
|
if !strings.Contains(string(template), `"image": "`+image+`"`) {
|
|
t.Errorf("%s is now %q, which the template does not say", id, image)
|
|
}
|
|
if strings.HasPrefix(image, "sha256:") {
|
|
t.Errorf("%s was rewritten to an image this machine holds, and nothing holds it", id)
|
|
}
|
|
}
|
|
|
|
// And the claim in the report is the same claim, so a person reading it is reading evidence.
|
|
if len(out.Kept) != 2 {
|
|
t.Errorf("the rewrite reports %d untouched image(s): %v", len(out.Kept), out.Kept)
|
|
}
|
|
}
|
|
|
|
// A bundle with no control plane raises a store and a broker and no mesh. Refused, because
|
|
// applying it would succeed and leave a machine that looks bootstrapped.
|
|
func TestABundleThatNamesNoControlPlaneIsRefused(t *testing.T) {
|
|
template := []byte(`{"declaration":1,"resources":[
|
|
{"id":"store","type":"container","name":"mesh-store","image":"postgres@sha256:` +
|
|
strings.Repeat("7", 64) + `"}
|
|
]}`)
|
|
|
|
_, err := Rewrite(template, held)
|
|
if err == nil {
|
|
t.Fatal("a bundle with no control plane was rewritten and would have been applied")
|
|
}
|
|
// The refusal has to be actionable: it says what the bundle DID declare, so somebody can see
|
|
// they pointed it at the wrong file or misspelled the id.
|
|
if !strings.Contains(err.Error(), ControlPlaneID) || !strings.Contains(err.Error(), "store") {
|
|
t.Errorf("the refusal names neither what was wanted nor what was there: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAControlPlaneThatIsNotAContainerIsRefused(t *testing.T) {
|
|
template := []byte(`{"declaration":1,"resources":[
|
|
{"id":"control-plane","type":"package","package":"mesh-controller"}
|
|
]}`)
|
|
if _, err := Rewrite(template, held); err == nil {
|
|
t.Fatal("a control plane declared as a package was accepted, and a package has no image")
|
|
}
|
|
}
|
|
|
|
// Idempotence. This program is run over and over while somebody gets a machine working, and the
|
|
// second run must be able to say the bundle already names this image rather than reporting a
|
|
// rewrite it did not perform.
|
|
func TestRewritingABundleThatAlreadyNamesTheImageChangesNothing(t *testing.T) {
|
|
first, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
second, err := Rewrite(first.Bundle, held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if second.Changed {
|
|
t.Error("re-running the rewrite reported a change, and the image was already the one held")
|
|
}
|
|
if string(second.Bundle) != string(first.Bundle) {
|
|
t.Error("re-running the rewrite produced different bytes")
|
|
}
|
|
if second.Was != held {
|
|
t.Errorf("the second run reports it replaced %q; it replaced nothing", second.Was)
|
|
}
|
|
}
|
|
|
|
// A DIFFERENT image, though, must move — the ordinary case of a new control plane being installed
|
|
// over an old one. "Already correct" must not be the same code path as "already ran".
|
|
func TestANewImageReplacesAnOlderHeldOne(t *testing.T) {
|
|
first, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := Rewrite(first.Bundle, otherHeld)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !second.Changed || second.Was != held || second.Now != otherHeld {
|
|
t.Errorf("a new image did not replace the old one: changed=%v was=%q now=%q",
|
|
second.Changed, second.Was, second.Now)
|
|
}
|
|
}
|
|
|
|
// The produced bundle is meant to be READ. Re-serialising a parsed declaration would drop every
|
|
// comment in the template, and the foundation example is mostly comments — each one recording why a
|
|
// resource is the way it is, several of them paid for in the lab.
|
|
func TestTheProducedBundleKeepsTheTemplatesComments(t *testing.T) {
|
|
template := theRealBundle(t)
|
|
out, err := Rewrite(template, held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
const remembered = "NAMED VOLUME"
|
|
if !strings.Contains(string(out.Bundle), remembered) {
|
|
t.Errorf("the produced bundle lost the template's comments; %q is gone", remembered)
|
|
}
|
|
}
|
|
|
|
// The installer must refuse its own bad input in its own words, rather than writing a bundle and
|
|
// letting the host refuse a declaration somebody did not write.
|
|
func TestSomethingThatIsNotAnImageIdIsRefused(t *testing.T) {
|
|
for _, bad := range []string{
|
|
"",
|
|
"mesh-controller:latest",
|
|
"sha256:abc",
|
|
"sha256:" + strings.Repeat("1", 63),
|
|
"sha256:" + strings.Repeat("g", 64),
|
|
"mesh-controller@sha256:" + strings.Repeat("1", 64),
|
|
} {
|
|
if _, err := Rewrite(theRealBundle(t), bad); err == nil {
|
|
t.Errorf("image id %q was accepted", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The address every enrolment token will carry is reported and never invented. It differs per
|
|
// machine and it is silently fatal when wrong: a node enrols against a dead address and nothing
|
|
// says so until it fails to come back.
|
|
func TestTheAddressNodesWillDialIsReportedAndNotRewritten(t *testing.T) {
|
|
template := theRealBundle(t)
|
|
out, err := Rewrite(template, held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out.BrokerAddress == "" {
|
|
t.Fatal("the foundation example no longer says what address enrolling nodes will dial")
|
|
}
|
|
if !strings.Contains(string(out.Bundle), out.BrokerAddress) {
|
|
t.Errorf("the produced bundle no longer carries %q — it was rewritten, and nothing here "+
|
|
"knows this machine's address", out.BrokerAddress)
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------------------------
|
|
// The rename, which is what makes genesis a pivot rather than a handover (novox/hq ADR 0067).
|
|
// ---------------------------------------------------------------------------------------------
|
|
|
|
// **This is the test that dissolves the blocker.** The foundation raises a control plane and a
|
|
// module later declares one; if both are called `mesh-controller` then for one moment two owners hold
|
|
// one container, and the host — which tracks what it owns — has no way to stop owning something
|
|
// without destroying it. Nothing here invents such a mechanism. The foundation's container is
|
|
// called `temp-mesh-controller` instead, and there are simply two containers.
|
|
func TestTheFoundationsControlPlaneMovesOutOfTheModulesWay(t *testing.T) {
|
|
out, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !out.Renamed {
|
|
t.Error("the rewrite reported nothing renamed, and the template named it mesh-controller")
|
|
}
|
|
if out.TempName != "temp-mesh-controller" {
|
|
t.Errorf("the foundation's control plane is called %q", out.TempName)
|
|
}
|
|
control, err := controlPlaneIn(out.Declaration)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if control.Name != out.TempName {
|
|
t.Errorf("the produced bundle calls it %q, want %q", control.Name, out.TempName)
|
|
}
|
|
// And the plain name is free, which is the whole point: it belongs to the module now.
|
|
for _, name := range containerNames(out.Declaration) {
|
|
if name == ControlPlaneModule {
|
|
t.Errorf("the produced bundle still declares a container called %q, which the module "+
|
|
"will also declare", ControlPlaneModule)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The image reference contains the string `mesh-controller` too, and it is not a container name. A
|
|
// substitution that caught it would produce `…/temp-mesh-controller@sha256:…`, which no registry
|
|
// serves — and it would be found inside a pull rather than here.
|
|
func TestTheImageReferenceIsNotMistakenForTheContainerName(t *testing.T) {
|
|
out, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if strings.Contains(string(out.Bundle), TempPrefix+"mesh-controller@") ||
|
|
strings.Contains(string(out.Bundle), "/"+TempPrefix+"mesh-controller") {
|
|
t.Error("the rename reached inside an image reference")
|
|
}
|
|
}
|
|
|
|
// Everything else keeps the name the foundation gave it. The store and the broker are containers
|
|
// too, and a rename that moved them would leave a machine whose foundation the host cannot find.
|
|
func TestRenamingTheControlPlaneLeavesEveryOtherContainerAlone(t *testing.T) {
|
|
before, err := declaration.ParseFileTrusted(theRealBundle(t))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
out, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
was, now := containerNames(before), containerNames(out.Declaration)
|
|
for id, name := range was {
|
|
if id == ControlPlaneID {
|
|
continue
|
|
}
|
|
if now[id] != name {
|
|
t.Errorf("%s was renamed from %q to %q", id, name, now[id])
|
|
}
|
|
}
|
|
}
|
|
|
|
// A re-run against a bundle this installer produced renames nothing and says so. The installer is
|
|
// run over and over while somebody gets a machine working, and a step that could not tell "already
|
|
// done" from "just done" makes the second run indistinguishable from the first.
|
|
func TestRewritingABundleThisAlreadyProducedRenamesNothing(t *testing.T) {
|
|
first, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
second, err := Rewrite(first.Bundle, held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if second.Renamed {
|
|
t.Error("a bundle already naming temp-mesh-controller was renamed again")
|
|
}
|
|
if second.TempName != first.TempName {
|
|
t.Errorf("the second pass calls it %q and the first called it %q",
|
|
second.TempName, first.TempName)
|
|
}
|
|
if string(second.Bundle) != string(first.Bundle) {
|
|
t.Error("rewriting a produced bundle changed it")
|
|
}
|
|
}
|