mesh-bootstrap: the installer, and the two things its first real run found #8

Merged
jschoubben merged 9 commits from fix/bootstrap-first-run into main 2026-09-11 19:56:57 +00:00
3 changed files with 50 additions and 3 deletions
Showing only changes of commit 7e3481f025 - Show all commits
+10 -2
View File
@@ -75,6 +75,14 @@ host:
# hold, and the tag is the only name that survives the transfer. Saving by id produces an archive
# with no tags at all, which the installer refuses; caught here instead, in front of the person who
# can fix it.
#
# BOOTSTRAP_OUT is where the binary is written, and it exists because something other than a person
# now builds this: the lab rebuilds every artifact it runs from source before a raise, into paths it
# chose (mesh-lab's src/rebuild.ts, and novox/hq 04-ISSUES/005 for why it does that at all). A
# caller that could not say where the output goes would have to copy it afterwards, which is one
# more step to forget.
BOOTSTRAP_OUT ?= mesh-bootstrap
bootstrap:
@test -n "$(IMAGE)" || { echo "IMAGE= is required; an installer carrying no control-plane image cannot raise a mesh"; exit 1; }
@case "$(IMAGE)" in sha256:*) echo "IMAGE=$(IMAGE) is an image id. The installer identifies the carried image by its tag, because an id is the digest of a configuration that a runtime rewrites as it loads. Pass a name:tag"; exit 1;; esac
@@ -82,11 +90,11 @@ bootstrap:
@test -n "$$(docker image inspect --format '{{len .RepoTags}}' "$(IMAGE)" | grep -v '^0$$')" || { echo "$(IMAGE) has no repository tag, so the saved archive would carry no name the installer can ask a runtime about. Tag it first: docker tag $(IMAGE) mesh-control:<version>"; exit 1; }
@cp internal/image/control-plane.tar internal/image/control-plane.tar.placeholder
@docker save --output internal/image/control-plane.tar "$(IMAGE)"
@CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION)" -o mesh-bootstrap ./cmd/mesh-bootstrap; \
@CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION)" -o "$(BOOTSTRAP_OUT)" ./cmd/mesh-bootstrap; \
status=$$?; \
mv internal/image/control-plane.tar.placeholder internal/image/control-plane.tar; \
exit $$status
@echo "built mesh-bootstrap carrying $(IMAGE)"
@echo "built $(BOOTSTRAP_OUT) carrying $(IMAGE)"
clean:
rm -f mesh-host mesh-bootstrap
+15 -1
View File
@@ -104,6 +104,15 @@ func Preflight(ctx context.Context, o Options, d Deps, say func(string)) ([]byte
// reason. Everything else is somebody else's image at somebody else's registry, and a machine
// that cannot reach it fails inside a pull, which reports a network error where a person
// reads a missing image.
//
// "The mesh's own image is skipped" used to mean "skipped if the template happened to name it
// in a way that needs no registry", and that is not the same sentence. A template names the
// control plane by SOMETHING — the reference is a slot, and step 3 replaces whatever is in it
// with the id of the image this installer carries. Whatever the slot held is therefore never
// pulled, never fetched, and never reached; requiring it to be reachable refuses a correct
// install because of a string that is about to be thrown away. Found on the first real run: the
// lab's template still carried `192.0.2.250:5000/mesh-control@…`, the address of a registry that
// no longer exists, and preflight timed out dialling it.
for _, host := range registriesIn(parsed) {
dialing, cancel := context.WithTimeout(ctx, o.Timeout)
err := d.Dial(dialing, host)
@@ -171,12 +180,17 @@ func containerRuntimeDetector(run Runner) profile.Detector {
// registriesIn is every host the bundle's images would be fetched from, without duplicates and in
// the order they appear.
//
// The control plane's own resource is excluded by identity rather than by the shape of what it
// names. Its image reference is a slot the installer overwrites with the id of the image it
// carries, so no registry ever serves it — and a template that filled that slot with a registry
// this machine cannot reach is not a machine with a network problem.
func registriesIn(d *declaration.Declaration) []string {
var hosts []string
seen := map[string]bool{}
for _, r := range d.Resources {
container, ok := r.(*declaration.Container)
if !ok {
if !ok || container.Identity() == ControlPlaneID {
continue
}
host, served := registryOf(container.Image)
+25
View File
@@ -100,6 +100,31 @@ func TestOnlyTheRegistriesTheBundleNamesAreAskedAbout(t *testing.T) {
}
}
// And the control plane's slot is excluded whatever is in it.
//
// Found on the first real run of this installer. A template names the control plane by SOMETHING
// and step 3 replaces it with the id of the carried image, so whatever was there is never pulled —
// but preflight was reading that slot like any other and dialling it. The lab's template still
// carried the address of a registry the lab no longer raises, so a correct install timed out in
// preflight against a machine with a perfectly good network.
func TestTheControlPlanesOwnRegistryIsNeverAskedAbout(t *testing.T) {
parsed, err := declaration.ParseFileTrusted([]byte(`{"declaration":1,"resources":[
{"id":"store","type":"container","name":"mesh-store","image":"postgres@sha256:` +
strings.Repeat("7", 64) + `"},
{"id":"control-plane","type":"container","name":"mesh-control","image":"192.0.2.250:5000/mesh-control@sha256:` +
strings.Repeat("8", 64) + `"}
]}`))
if err != nil {
t.Fatal(err)
}
got := registriesIn(parsed)
if len(got) != 1 || got[0] != DefaultRegistry {
t.Fatalf("asked about %v; the control plane's own reference is about to be replaced and "+
"must not be reached for", got)
}
}
func TestWhereAnImageWouldBeFetchedFrom(t *testing.T) {
// The container runtime's own rule: the part before the first slash is a registry host if it
// has a dot, a port, or is localhost. Getting this wrong means dialling a hostname that is