InstallStore turns the mesh-store the foundation raised at genesis into the postgres module, adopted in place: it verifies the module's server names the same container and the same image the foundation is running (fail-fast on a drift, rather than tearing down the mesh's store), then registers, builds the provisioner, and carries the superuser in via secret accept — the mesh cannot invent a credential that already made the databases (mirroring the control plane's store-connection delivery, control.go). pinImage generalised to any module for reuse. Issue 051 (WBS 3.1). One server holds the controller's contexts and every module's database. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
276 lines
13 KiB
Go
276 lines
13 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
// Step 9 is where the control plane stops being a special case. These tests defend the two things
|
|
// that could go wrong quietly: pinning it to the wrong image, and delivering it store connections
|
|
// the mesh invented rather than the ones the foundation actually made.
|
|
|
|
// theControlPlaneModule is the catalogue's manifest, trimmed to what this installer reads.
|
|
//
|
|
// A fixture rather than the file itself, unlike the foundation example the rewrite tests use: the
|
|
// catalogue is a different repository on a different branch, and a test that read it would pass or
|
|
// fail according to what somebody else had checked out. What it must stay faithful to is the
|
|
// SHAPE — the placeholder digest, the own-secret per context, the mount from the machine's path to
|
|
// the container's, and the environment file that fills what is not a path.
|
|
const theControlPlaneModule = `{
|
|
"module": "mesh-controller",
|
|
"version": "1",
|
|
"slug": "control",
|
|
"capabilities": ["container-runtime"],
|
|
"claims": [{"name": "the-controller", "scope": "mesh"}],
|
|
"own-secrets": {
|
|
"inventory": "/var/lib/mesh/mesh-controller/inventory",
|
|
"identity": "/var/lib/mesh/mesh-controller/identity",
|
|
"licences": "/var/lib/mesh/mesh-controller/licences",
|
|
"broker": "/var/lib/mesh/mesh-controller/broker",
|
|
"broker-management": "/var/lib/mesh/mesh-controller/broker-management"
|
|
},
|
|
"resources": [
|
|
{"id": "mesh-state", "type": "directory", "path": "/var/lib/mesh/mesh-controller", "mode": "0700"},
|
|
{"id": "broker-env", "type": "file", "path": "/var/lib/mesh/mesh-controller/broker.env",
|
|
"mode": "0600",
|
|
"content": "MESH_BROKER_AMQP=${secret:broker}\nMESH_BROKER_MANAGEMENT=${secret:broker-management}\nMESH_BROKER_ADDRESS=${machine:at}:5671\n"},
|
|
{"id": "server", "type": "container", "name": "mesh-controller",
|
|
"image": "mesh-controller@` + placeholderDigest + `",
|
|
"network": "host", "args": ["serve"],
|
|
"env-file": ["/var/lib/mesh/mesh-controller/broker.env"],
|
|
"env": {
|
|
"MESH_STORE_INVENTORY_FILE": "/run/secrets/inventory",
|
|
"MESH_STORE_IDENTITY_FILE": "/run/secrets/identity",
|
|
"MESH_STORE_LICENCES_FILE": "/run/secrets/licences",
|
|
"MESH_BROKER_CERTIFICATE": "/broker-tls/tls.crt"
|
|
},
|
|
"volumes": [
|
|
"mesh-broker-tls:/broker-tls:ro",
|
|
"/var/lib/mesh/mesh-controller/inventory:/run/secrets/inventory:ro",
|
|
"/var/lib/mesh/mesh-controller/identity:/run/secrets/identity:ro",
|
|
"/var/lib/mesh/mesh-controller/licences:/run/secrets/licences:ro"
|
|
]}
|
|
]
|
|
}`
|
|
|
|
const pushedReference = "127.0.0.1:5000/mesh-controller@sha256:" +
|
|
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
|
|
|
|
// **The whole reference moves, not only the digest.** The manifest's placeholder names a
|
|
// repository too, and replacing sixty-four zeros inside it would leave `mesh-controller@sha256:…`
|
|
// with no registry in front — which a runtime would go to the internet for, and this mesh's
|
|
// control plane exists in no public registry by design.
|
|
func TestTheControlPlaneIsPinnedToWhatThisMeshsRegistryAssigned(t *testing.T) {
|
|
pinned, places, err := pinImage([]byte(theControlPlaneModule), pushedReference, "mesh-controller")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if places != 1 {
|
|
t.Errorf("the placeholder was found in %d place(s)", places)
|
|
}
|
|
if !strings.Contains(string(pinned), `"image": "`+pushedReference+`"`) {
|
|
t.Errorf("the manifest does not name the pushed image:\n%s", pinned)
|
|
}
|
|
if strings.Contains(string(pinned), `"mesh-controller@sha256:`) {
|
|
t.Errorf("the digest was replaced and the manifest's own repository name was left in "+
|
|
"front of it, so nothing says which registry serves it:\n%s", pinned)
|
|
}
|
|
}
|
|
|
|
// A manifest already naming a real digest was pinned by somebody else, to some other build.
|
|
// Registering it would install a control plane that is not the image this machine just published,
|
|
// which is the one thing this step exists to guarantee.
|
|
func TestAManifestAlreadyPinnedByHandIsRefused(t *testing.T) {
|
|
already := strings.Replace(theControlPlaneModule, placeholderDigest,
|
|
"sha256:"+strings.Repeat("9", 64), 1)
|
|
if _, _, err := pinImage([]byte(already), pushedReference, "mesh-controller"); err == nil {
|
|
t.Fatal("a manifest already pinned to some other image was accepted")
|
|
}
|
|
}
|
|
|
|
// Every placeholder moves. A manifest naming its image in a second resource — a runtime container
|
|
// beside the application's, which the catalogue's converted modules routinely carry — would
|
|
// otherwise be left half pinned, and fail inside an apply rather than here.
|
|
func TestEveryPlaceTheManifestNamesTheImageIsPinned(t *testing.T) {
|
|
twice := strings.Replace(theControlPlaneModule,
|
|
`{"id": "mesh-state", "type": "directory", "path": "/var/lib/mesh/mesh-controller", "mode": "0700"},`,
|
|
`{"id": "mesh-state", "type": "directory", "path": "/var/lib/mesh/mesh-controller", "mode": "0700"},
|
|
{"id": "migrate", "type": "container", "name": "mesh-controller-migrate", "run-once": true,
|
|
"image": "mesh-controller@`+placeholderDigest+`", "args": ["migrate"]},`, 1)
|
|
|
|
pinned, places, err := pinImage([]byte(twice), pushedReference, "mesh-controller")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if places != 2 {
|
|
t.Errorf("the placeholder was found in %d place(s), and the manifest names it twice", places)
|
|
}
|
|
if strings.Contains(string(pinned), placeholderDigest) {
|
|
t.Error("a placeholder survived the pinning")
|
|
}
|
|
}
|
|
|
|
// **The connections are the foundation's, and they are read out of the bundle that made them.**
|
|
// The mesh cannot invent them: they are the credentials the foundation created the databases with,
|
|
// and thirty-two random bytes in their place would leave the control plane unable to open a single
|
|
// context. The pairing is read from the manifest so that whatever the catalogue calls these
|
|
// secrets is what is delivered.
|
|
func TestTheStoreConnectionsComeFromTheBundleThatMadeThem(t *testing.T) {
|
|
wanted, err := secretsByVariableIn([]byte(theControlPlaneModule))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// **Through the mount.** The manifest keeps its secrets under /var/lib and the container reads
|
|
// them at /run/secrets. Matching on the path alone would find nothing and refuse a correct
|
|
// manifest, which is exactly the ordinary case in the catalogue.
|
|
for variable, secret := range map[string]string{
|
|
"MESH_STORE_INVENTORY": "inventory",
|
|
"MESH_STORE_IDENTITY": "identity",
|
|
"MESH_STORE_LICENCES": "licences",
|
|
"MESH_BROKER_AMQP": "broker",
|
|
"MESH_BROKER_MANAGEMENT": "broker-management",
|
|
} {
|
|
if wanted[variable] != secret {
|
|
t.Errorf("%s would be accepted as %q, want %q", variable, wanted[variable], secret)
|
|
}
|
|
}
|
|
// And what the manifest fills from the machine rather than from a secret is left alone.
|
|
if _, claimed := wanted["MESH_BROKER_ADDRESS"]; claimed {
|
|
t.Error("the address the mesh composes from the machine was treated as a secret")
|
|
}
|
|
|
|
// The values are the foundation's own, taken from the produced bundle rather than composed.
|
|
rewritten, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
runtime := &asked{answer: aMeshThatAgrees(nil)}
|
|
control := controlPlane{container: "temp-mesh-controller", run: runtime.run, timeout: time.Second}
|
|
|
|
delivered, err := deliverStores(context.Background(), Options{Node: "anchor"}, control,
|
|
[]byte(theControlPlaneModule), rewritten.Declaration, func(string) {})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// Three stores and both halves of the broker: everything the foundation made and nothing else.
|
|
if len(delivered) != 5 {
|
|
t.Fatalf("%d values were delivered, and the foundation names five: %v",
|
|
len(delivered), delivered)
|
|
}
|
|
for _, secret := range delivered {
|
|
if !runtime.ran("secret accept anchor mesh-controller " + secret + " --from") {
|
|
t.Errorf("%s was not accepted through `secret accept`: %v", secret, runtime.commands)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A secret the foundation did not make is left for the mesh to make, and said so. Every other
|
|
// secret in a mesh is one the mesh made; `secret accept` is only for what predates the mesh.
|
|
func TestASecretTheFoundationNeverMadeIsLeftToTheMesh(t *testing.T) {
|
|
extra := strings.Replace(theControlPlaneModule,
|
|
`"broker": "/var/lib/mesh/mesh-controller/broker",`,
|
|
`"broker": "/var/lib/mesh/mesh-controller/broker",
|
|
"something-new": "/var/lib/mesh/mesh-controller/something-new",`, 1)
|
|
extra = strings.Replace(extra,
|
|
`"content": "MESH_BROKER_AMQP=${secret:broker}\n`,
|
|
`"content": "MESH_SOMETHING_NEW=${secret:something-new}\nMESH_BROKER_AMQP=${secret:broker}\n`, 1)
|
|
|
|
rewritten, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
runtime := &asked{answer: aMeshThatAgrees(nil)}
|
|
control := controlPlane{container: "temp-mesh-controller", run: runtime.run, timeout: time.Second}
|
|
|
|
var said []string
|
|
delivered, err := deliverStores(context.Background(), Options{Node: "anchor"}, control,
|
|
[]byte(extra), rewritten.Declaration, func(line string) { said = append(said, line) })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, secret := range delivered {
|
|
if secret == "something-new" {
|
|
t.Error("a value the foundation never made was accepted as though it had")
|
|
}
|
|
}
|
|
if !strings.Contains(strings.Join(said, "\n"), "the mesh will make something-new") {
|
|
t.Errorf("nothing was said about the secret the mesh has to make: %v", said)
|
|
}
|
|
}
|
|
|
|
// A manifest whose container reads a file no own-secret writes is refused. The mesh would seal
|
|
// nothing there and the control plane would find an empty file where a connection string has to
|
|
// be — which presents as a control plane that will not start, three steps from the cause.
|
|
func TestAConnectionFileNothingWritesIsRefused(t *testing.T) {
|
|
mismatched := strings.Replace(theControlPlaneModule,
|
|
`"inventory": "/var/lib/mesh/mesh-controller/inventory",`,
|
|
`"inventory": "/var/lib/mesh/mesh-controller/somewhere-else",`, 1)
|
|
|
|
_, err := secretsByVariableIn([]byte(mismatched))
|
|
if err == nil {
|
|
t.Fatal("a manifest whose two ends do not meet was accepted")
|
|
}
|
|
if !strings.Contains(err.Error(), "own-secret") {
|
|
t.Errorf("the refusal does not say which half is missing: %v", err)
|
|
}
|
|
}
|
|
|
|
// A manifest asking for no store connections at all describes a control plane that can open
|
|
// nothing, and the refusal says what shape the installer delivers into — because the manifest is
|
|
// written in another repository and this is where the two have to agree.
|
|
func TestAManifestWantingNoStoresIsRefusedWithTheShapeItShouldHave(t *testing.T) {
|
|
rewritten, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
runtime := &asked{answer: aMeshThatAgrees(nil)}
|
|
control := controlPlane{container: "temp-mesh-controller", run: runtime.run, timeout: time.Second}
|
|
|
|
bare := `{"module":"mesh-controller","version":"1","resources":[
|
|
{"id":"container","type":"container","name":"mesh-controller",
|
|
"image":"mesh-controller@` + placeholderDigest + `"}]}`
|
|
|
|
_, err = deliverStores(context.Background(), Options{Node: "anchor"}, control,
|
|
[]byte(bare), rewritten.Declaration, func(string) {})
|
|
if err == nil {
|
|
t.Fatal("a control plane that can open nothing was accepted")
|
|
}
|
|
if !strings.Contains(err.Error(), storeVariablePrefix+"<CONTEXT>"+storeFileSuffix) {
|
|
t.Errorf("the refusal does not say what shape is expected: %v", err)
|
|
}
|
|
}
|
|
|
|
// The permanent control plane is asked a question, not merely looked at — the same question the
|
|
// temporary one was asked at step 5, and for the same reason: `status` opens all three stores, so
|
|
// a reply proves the sealed connections it was given are the ones the foundation made.
|
|
func TestThePermanentControlPlaneIsAskedTheSameQuestion(t *testing.T) {
|
|
runtime := &asked{answer: aMeshThatAgrees(map[string]string{
|
|
"module list": "",
|
|
"exec mesh-controller /mesh-controller": "1 node, 0 waiting\n",
|
|
})}
|
|
control := controlPlane{container: "temp-mesh-controller", run: runtime.run, timeout: time.Second}
|
|
rewritten, err := Rewrite(theRealBundle(t), held)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
out, err := InstallControlPlane(context.Background(),
|
|
installing(t, catalogueWith(t, ControlPlaneModule, theControlPlaneModule)),
|
|
Deps{Run: runtime.run}, control, rewritten.Declaration, pushedReference, func(string) {})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if out.Answered != "1 node, 0 waiting" {
|
|
t.Errorf("the permanent control plane's reply is reported as %q", out.Answered)
|
|
}
|
|
if !runtime.ran("docker exec mesh-controller " + controlPlaneBinary + " status") {
|
|
t.Errorf("the permanent control plane was never asked anything: %v", runtime.commands)
|
|
}
|
|
// And the module was registered with the digest, not with the placeholder.
|
|
if !runtime.ran("module add /mesh-controller-module.json") {
|
|
t.Errorf("the module was never registered: %v", runtime.commands)
|
|
}
|
|
}
|