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:
+33
-16
@@ -82,20 +82,33 @@ type manifestEntry struct {
|
||||
RepoTags []string `json:"RepoTags"`
|
||||
}
|
||||
|
||||
// ID is the image id the runtime will give this image once it is loaded, read out of the tar.
|
||||
// ArchiveID is what this archive calls the image it holds. **It is not necessarily the id the
|
||||
// runtime will assign when the archive is loaded, and it must never be what a bundle names.**
|
||||
//
|
||||
// **Read here rather than parsed out of what `docker load` prints.** The load prints a sentence
|
||||
// for a person — `Loaded image: name:tag` or `Loaded image ID: sha256:…`, depending on whether the
|
||||
// image was saved with a tag — and a program that scraped it would be depending on which of those
|
||||
// a particular runtime version chose. The id is a fact about the file, available before the
|
||||
// runtime is asked anything, which is also what makes the load idempotent: the installer can ask
|
||||
// whether the machine already holds THIS image before loading it.
|
||||
// This was called ID, and its comment said it was the id the runtime would give the image once
|
||||
// loaded. That was wrong, and wrong in the worst available way: right on the machine the image
|
||||
// was saved on, and wrong on the machine it was carried to. An image id is the sha256 of the
|
||||
// image's *configuration document*, and a runtime rewrites that document as it loads — a newer
|
||||
// Docker saves in one format and an older one stores it in another. Same layers, same program,
|
||||
// different name. Measured on a live raise:
|
||||
//
|
||||
// An image id is the sha256 of the image's configuration document (novox/hq ADR 0006, and see
|
||||
// `internal/declaration`'s checkImage). `manifest.json` names that document by its digest — as
|
||||
// `<64hex>.json` in the older layout and `blobs/sha256/<64hex>` in the OCI one — so both forms
|
||||
// reduce to the same sixty-four characters.
|
||||
func ID(saved []byte) (string, error) {
|
||||
// saved on the workstation sha256:b86bb81ca2f9691f24f4725f50962d1e49c98c5ffe211113241243d42d18ceea
|
||||
// loaded on the machine sha256:2dc219046c73702fc640317f0342a28ec962ef1e9ef547b2f02861c508ca78fb
|
||||
//
|
||||
// A bundle naming this id would then name 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 stop at a pull that cannot succeed. The id a bundle names is read
|
||||
// back from the runtime after the load (`internal/bootstrap`.Load), which is the only place it is
|
||||
// a fact rather than a prediction.
|
||||
//
|
||||
// What it is still good for is a statement about the FILE — which build somebody embedded — and
|
||||
// as a hint printed beside the runtime's answer when the two differ, so a person can see that
|
||||
// they did.
|
||||
//
|
||||
// `manifest.json` names the configuration document by its digest — as `<64hex>.json` in the older
|
||||
// layout and `blobs/sha256/<64hex>` in the OCI one — so both forms reduce to the same sixty-four
|
||||
// characters.
|
||||
func ArchiveID(saved []byte) (string, error) {
|
||||
manifest, err := fileFromTar(saved, "manifest.json")
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -125,11 +138,15 @@ func ID(saved []byte) (string, error) {
|
||||
return "sha256:" + digest, nil
|
||||
}
|
||||
|
||||
// Tags is what the saved image was called when it was saved, for a person reading a report.
|
||||
// Tags is what the saved image was called when it was saved.
|
||||
//
|
||||
// Decoration, and said so: the installer names the image by its id everywhere it matters, because
|
||||
// a tag is exactly what a pinned bundle may not rely on (novox/hq ADR 0006). This is here so a
|
||||
// report can say which build somebody embedded, which is otherwise sixty-four characters of hex.
|
||||
// **Not decoration any more, and this comment used to say it was.** A tag is exactly what a pinned
|
||||
// bundle may not rely on (novox/hq ADR 0006) and none of this ever reaches a bundle — but the tag
|
||||
// is how the installer ASKS the runtime what id it assigned, because the id itself is not
|
||||
// knowable beforehand (see ArchiveID). It is the one name that survives `docker save` and
|
||||
// `docker load` unchanged, which is precisely what the id does not.
|
||||
//
|
||||
// It also still says which build somebody embedded, which is otherwise sixty-four hex characters.
|
||||
func Tags(saved []byte) []string {
|
||||
manifest, err := fileFromTar(saved, "manifest.json")
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user