The only complete written-down copy of how a mesh is stood up was an integration test in the lab. That is why every bootstrap gap kept being found late: an install procedure that lives as a test fixture is exercised by whoever writes tests, never by whoever installs. This is that procedure. A separate binary, not a mesh-host subcommand. mesh-host says of itself that it connects to nothing and listens on nothing and that what it applies comes from a file, and that sentence is what makes an always-running root daemon auditable. An installer loads images and interrogates a control plane. Same tier, different program. The control plane's image is carried, not built and not fetched. The forge that holds its source runs on the mesh, so a bootstrap that had to fetch it would need a mesh in order to raise one. Embedding breaks that cycle the way the carried bundle breaks "copy it onto a machine and run it". The image id is read out of the saved tar before the runtime is asked anything, which is what makes the load idempotent: the installer can ask whether the machine already holds exactly this. Five steps, each idempotent and each saying whether it found or changed something, because this is run over and over by somebody getting a machine working. It stops at a running substrate with a control plane that replies — enrolment, the module catalogue and assignment are the next stage and are deliberately absent. Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
123 lines
4.7 KiB
Go
123 lines
4.7 KiB
Go
package bootstrap
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/novox/mesh-host/internal/image"
|
|
)
|
|
|
|
// Loaded is the control plane's image on this machine.
|
|
type Loaded struct {
|
|
// ID is what the bundle will name the image by: sha256 of its own configuration.
|
|
ID string
|
|
// Tags is what it was called when it was saved. For a person, never for the bundle.
|
|
Tags []string
|
|
// Held is true when the machine already had it and nothing was loaded.
|
|
Held bool
|
|
}
|
|
|
|
// Load puts the carried control-plane image into this machine's container runtime.
|
|
//
|
|
// **Idempotent by asking first, which is possible because the id is a fact about the file.** The
|
|
// image id is read out of the saved tar (see `internal/image`.ID) before the runtime is asked
|
|
// anything, so this can ask "do you already hold exactly this image" — and on the second, third
|
|
// and tenth run of the installer the answer is yes and nothing is loaded. A load that scraped the
|
|
// id out of what `docker load` printed could only know that after loading, so it would load every
|
|
// time and report the same thing either way.
|
|
//
|
|
// It reads back (novox/hq ADR 0018). A load that reported success and left nothing there is a
|
|
// failure, not a convergence, and the apply would then meet a bundle naming an image the machine
|
|
// does not hold — which fails correctly but two steps too late.
|
|
func Load(ctx context.Context, run Runner, dryRun bool, say func(string)) (Loaded, error) {
|
|
saved, err := image.Saved()
|
|
if err != nil {
|
|
return Loaded{}, err
|
|
}
|
|
return loadImage(ctx, run, saved, dryRun, say)
|
|
}
|
|
|
|
// loadImage is Load with the carried bytes handed in, so the whole path can be tested against a
|
|
// saved image a test builds rather than against whatever a particular build embedded.
|
|
func loadImage(ctx context.Context, run Runner, saved []byte, dryRun bool, say func(string)) (Loaded, error) {
|
|
id, err := image.ID(saved)
|
|
if err != nil {
|
|
return Loaded{}, err
|
|
}
|
|
loaded := Loaded{ID: id, Tags: image.Tags(saved)}
|
|
|
|
if held, err := holdsImage(ctx, run, id); err != nil {
|
|
return loaded, err
|
|
} else if held {
|
|
loaded.Held = true
|
|
say(" already held " + id + " — nothing loaded")
|
|
return loaded, nil
|
|
}
|
|
|
|
if dryRun {
|
|
say(fmt.Sprintf(" would load %s (%d bytes)", id, len(saved)))
|
|
return loaded, nil
|
|
}
|
|
|
|
// Through a file rather than through stdin: the runner this repository shares runs a command
|
|
// and captures its output, and giving it a second mouth for one caller would change every
|
|
// applier's contract for the sake of one step (internal/apply's Runner).
|
|
tarball, err := os.CreateTemp("", "mesh-control-*.tar")
|
|
if err != nil {
|
|
return loaded, fmt.Errorf("nowhere to put the carried image while loading it: %w", err)
|
|
}
|
|
defer os.Remove(tarball.Name())
|
|
|
|
if _, err := tarball.Write(saved); err != nil {
|
|
tarball.Close()
|
|
return loaded, fmt.Errorf("cannot write the carried image to %s: %w", tarball.Name(), err)
|
|
}
|
|
if err := tarball.Close(); err != nil {
|
|
return loaded, fmt.Errorf("cannot finish writing %s: %w", tarball.Name(), err)
|
|
}
|
|
|
|
if _, err := run(ctx, "docker", "load", "--input", tarball.Name()); err != nil {
|
|
return loaded, fmt.Errorf(
|
|
"the container runtime would not load the carried control-plane image: %w", err)
|
|
}
|
|
|
|
// Read back. This is what makes "loaded" a fact rather than an intention.
|
|
held, err := holdsImage(ctx, run, id)
|
|
if err != nil {
|
|
return loaded, err
|
|
}
|
|
if !held {
|
|
return loaded, fmt.Errorf(
|
|
"the load reported success and this machine does not hold %s.\n"+
|
|
"The bundle names the control plane by that id and nothing serves it, so the "+
|
|
"apply would refuse. Check what `docker load` actually took", id)
|
|
}
|
|
say(" loaded " + id)
|
|
return loaded, nil
|
|
}
|
|
|
|
// holdsImage asks the runtime whether this exact image is present.
|
|
//
|
|
// It asks for the id back rather than reading the exit code, because an image inspected by id and
|
|
// an image inspected by a tag that happens to point somewhere else are the same successful
|
|
// command. What is wanted is "this one", and the answer says which one.
|
|
func holdsImage(ctx context.Context, run Runner, id string) (bool, error) {
|
|
out, err := run(ctx, "docker", "image", "inspect", "--format", "{{.Id}}", id)
|
|
if err != nil {
|
|
// Absent is an answer, not a failure. Every other reason the runtime might refuse looks
|
|
// the same from here — which is why preflight proves the runtime answers before this runs,
|
|
// rather than this trying to tell the two apart from an exit code.
|
|
return false, nil
|
|
}
|
|
got := strings.TrimSpace(out)
|
|
if got != id {
|
|
return false, fmt.Errorf(
|
|
"asked for image %s, the runtime answered %q. An image id is the digest of the "+
|
|
"image's own configuration, so these are two different images and the bundle "+
|
|
"would name the wrong one", id, got)
|
|
}
|
|
return true, nil
|
|
}
|