bootstrap: read the image id back from the runtime, never predict it

An image id does not survive `docker save` -> transfer -> `docker load`. The id is
the digest of the image's *configuration*, and a runtime rewrites that
configuration as it loads: a newer Docker saves in one format, an older one stores
it in another. Same layers, same program, different name. Measured on a live raise:

  saved on the workstation  sha256:b86bb81ca2f9691f24f4725f50962d1e49c98c5ffe211113241243d42d18ceea
  loaded on the machine     sha256:2dc219046c73702fc640317f0342a28ec962ef1e9ef547b2f02861c508ca78fb

`internal/image`.ID read the id out of the carried tar and its comment said that
was the id the runtime would assign. That is true on the machine the image was
built on and false on every machine it is carried to — which is every machine this
program exists for. The installer then either stopped at step 2 refusing the
runtime's answer, or would have written a bundle naming an image the machine does
not hold; and nothing serves an image named by the digest of its own configuration,
which is the whole point of naming one that way, so the apply would have died
inside a pull that cannot succeed. The lab hit this.

So the image is identified by its TAG, which is ordinary metadata the tar carries
through unchanged. The runtime is asked what that tag resolves to before the load
(already held, nothing to do) and again after (this is what the bundle names). The
tag never reaches the bundle — a pinned bundle may not rely on one, ADR 0006 — it
is how the id is obtained, not what is written down.

  - image.ID becomes image.ArchiveID, and says plainly that it is a fact about the
    file and not a prediction about any machine. It is kept for reports, and printed
    beside the runtime's answer whenever the two differ.
  - Idempotence is decided from what the runtime holds under the tag, not from a
    predicted id, which cannot answer the question at all here.
  - An untagged archive is refused, in preflight and again at the load: there would
    be no portable name to ask about, and the only thing left is scraping a sentence
    `docker load` writes for a person. `make bootstrap` refuses an id or an untagged
    image, so it is caught in front of whoever can fix it.
  - A dry run cannot know the id and says so rather than pretending. Run refuses to
    write a bundle carrying an unconfirmed id at all.

Tests: the injected Runner now answers with an id DIFFERING from the tar's, and the
runtime's answer is what must be used. The test that refused a differing id encoded
the mistake and is replaced by one refusing an answer that is not an id at all.

Claude-Session: https://claude.ai/code/session_01LrgweAeERJYBg88c5cKDzF
This commit is contained in:
2026-09-11 00:12:36 +02:00
parent 0a88dc1f9d
commit cb5e137297
7 changed files with 410 additions and 142 deletions
+18 -3
View File
@@ -36,12 +36,27 @@ func Preflight(ctx context.Context, o Options, d Deps, say func(string)) ([]byte
if err != nil {
return nil, err
}
carriedID, err := image.ID(saved)
//
// The id said here is the ARCHIVE's, and it is reported as such: it is a fact about the file
// and not about this machine. What this runtime will call the image once it holds it is the
// runtime's decision, made at the load, and asked for there (see Load).
carriedID, err := image.ArchiveID(saved)
if err != nil {
return nil, err
}
say(fmt.Sprintf(" control plane %s carried (%s)",
firstOr(image.Tags(saved), "untagged"), carriedID))
tag := firstOr(image.Tags(saved), "")
if tag == "" {
// Refused here as well as at the load, because preflight's whole job is to find at the
// start what would otherwise be found half way through — and this would be found after an
// image had been written to disk and handed to a container runtime.
return nil, fmt.Errorf(
"the carried control-plane image has no tag, and the installer identifies it by one: "+
"an image id is the digest of a configuration that a runtime rewrites as it loads, "+
"so the archive's id (%s) is not necessarily the id this machine would hold.\n"+
"Rebuild the installer with a tagged image: `make bootstrap IMAGE=<name>:<tag>`",
carriedID)
}
say(fmt.Sprintf(" control plane %s carried (the archive calls it %s)", tag, carriedID))
// 2. Is the template there, and is it a substrate?
template, err := os.ReadFile(o.Template)