One name per thing, per the HQ glossary: the module/container/image/binary/repo becomes mesh-controller, the seat the-controller, and the store+broker pair the foundation (embedded base bundles, default template and example lock renamed with their go:embed directives). No behaviour change — a pure vocabulary rename. Claude-Session: https://claude.ai/code/session_01D6qtiYU3P9jk3pnAXyAFyx
161 lines
6.5 KiB
Go
161 lines
6.5 KiB
Go
package image
|
|
|
|
import (
|
|
"archive/tar"
|
|
"bytes"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// Each test names the decision it defends (novox/hq ADR 0017).
|
|
|
|
// savedImage builds what `docker save` produces, as far as this package reads it.
|
|
func savedImage(t *testing.T, files map[string]string) []byte {
|
|
t.Helper()
|
|
var buffer bytes.Buffer
|
|
writer := tar.NewWriter(&buffer)
|
|
for name, content := range files {
|
|
header := &tar.Header{Name: name, Mode: 0o644, Size: int64(len(content))}
|
|
if err := writer.WriteHeader(header); err != nil {
|
|
t.Fatalf("building the fixture: %v", err)
|
|
}
|
|
if _, err := writer.Write([]byte(content)); err != nil {
|
|
t.Fatalf("building the fixture: %v", err)
|
|
}
|
|
}
|
|
if err := writer.Close(); err != nil {
|
|
t.Fatalf("building the fixture: %v", err)
|
|
}
|
|
return buffer.Bytes()
|
|
}
|
|
|
|
func manifest(t *testing.T, config string, tags ...string) string {
|
|
t.Helper()
|
|
raw, err := json.Marshal([]manifestEntry{{Config: config, RepoTags: tags}})
|
|
if err != nil {
|
|
t.Fatalf("building the fixture: %v", err)
|
|
}
|
|
return string(raw)
|
|
}
|
|
|
|
// The archive's own id is read from the FILE, in both layouts `docker save` has used.
|
|
//
|
|
// **This test used to say that reading it here was what made the load idempotent — that knowing
|
|
// the id in advance let the installer ask "do you already hold exactly this". That was wrong.** An
|
|
// id is the digest of the image's configuration document, and a runtime rewrites that document as
|
|
// it loads, so this is a fact about the archive and not a prediction about any machine. What the
|
|
// installer asks a runtime by is the TAG, and what a bundle names is the answer the runtime gives
|
|
// back (`internal/bootstrap`.Load).
|
|
//
|
|
// It is still read and still checked, because it is what says which build somebody embedded — and
|
|
// because printing it beside the runtime's answer is how a person sees that the two differ.
|
|
func TestTheArchivesOwnIdIsReadFromTheSavedFile(t *testing.T) {
|
|
digest := strings.Repeat("a", 64)
|
|
// Both layouts `docker save` has used. The older one names the config `<digest>.json`; the OCI
|
|
// one names it `blobs/sha256/<digest>`. They carry the same sixty-four characters, and a
|
|
// reader that understood only one would work until somebody upgraded their runtime.
|
|
for _, config := range []string{digest + ".json", "blobs/sha256/" + digest} {
|
|
saved := savedImage(t, map[string]string{"manifest.json": manifest(t, config)})
|
|
id, err := ArchiveID(saved)
|
|
if err != nil {
|
|
t.Fatalf("config %q: %v", config, err)
|
|
}
|
|
if id != "sha256:"+digest {
|
|
t.Errorf("config %q gave id %q, want sha256:%s", config, id, digest)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The tag is read, and it is not decoration: it is the name the installer asks a runtime by,
|
|
// because it is the one thing that survives `docker save` and `docker load` unchanged. The id
|
|
// does not.
|
|
func TestTheSavedTagsAreRead(t *testing.T) {
|
|
saved := savedImage(t, map[string]string{
|
|
"manifest.json": manifest(t, strings.Repeat("b", 64)+".json", "mesh-controller:v1"),
|
|
})
|
|
got := Tags(saved)
|
|
if len(got) != 1 || got[0] != "mesh-controller:v1" {
|
|
t.Errorf("tags = %v, want [mesh-controller:v1]", got)
|
|
}
|
|
}
|
|
|
|
// A tar that is not a saved image is refused with what is wrong, not with a nil id.
|
|
//
|
|
// Nothing here reaches a bundle any more, but this is still the earliest moment somebody can be
|
|
// told they embedded the wrong file — and the alternative is finding out at the load, from a
|
|
// container runtime, in a sentence about a tar rather than about what was built.
|
|
func TestSomethingThatIsNotASavedImageIsRefused(t *testing.T) {
|
|
notAnImage := savedImage(t, map[string]string{"hello": "world"})
|
|
if _, err := ArchiveID(notAnImage); err == nil {
|
|
t.Error("a tar with no manifest.json was accepted as a saved image")
|
|
} else if !strings.Contains(err.Error(), "docker save") {
|
|
t.Errorf("the refusal does not say what to embed instead: %v", err)
|
|
}
|
|
|
|
if _, err := ArchiveID([]byte("this is not a tar at all")); err == nil {
|
|
t.Error("bytes that are not a tar were accepted")
|
|
}
|
|
}
|
|
|
|
// Exactly one image. A bootstrap that chose between several would be the thing that guesses which
|
|
// one is the control plane, and it would guess right until the day somebody saved two.
|
|
func TestATarHoldingSeveralImagesIsRefused(t *testing.T) {
|
|
entries, err := json.Marshal([]manifestEntry{
|
|
{Config: strings.Repeat("a", 64) + ".json"},
|
|
{Config: strings.Repeat("b", 64) + ".json"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
saved := savedImage(t, map[string]string{"manifest.json": string(entries)})
|
|
if _, err := ArchiveID(saved); err == nil {
|
|
t.Error("a tar holding two images was accepted")
|
|
}
|
|
}
|
|
|
|
// An id is a digest or it is nothing. A truncated one names several images, and which one ran
|
|
// would be whichever the runtime matched first — the same reasoning `internal/declaration` gives
|
|
// for refusing a short image reference.
|
|
func TestAConfigThatIsNotADigestIsRefused(t *testing.T) {
|
|
for _, config := range []string{"config.json", "abc.json", "blobs/sha256/" + strings.Repeat("a", 63)} {
|
|
saved := savedImage(t, map[string]string{"manifest.json": manifest(t, config)})
|
|
if _, err := ArchiveID(saved); err == nil {
|
|
t.Errorf("config %q was accepted and is not a digest", config)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The committed placeholder must read as "carries nothing", so an installer built from a plain
|
|
// checkout says so in preflight rather than getting a machine as far as a running store and
|
|
// stopping. This is the same guarantee `internal/bundle` makes about a lock file of only comments.
|
|
func TestAnInstallerBuiltFromAPlainCheckoutCarriesNothing(t *testing.T) {
|
|
if !IsEmpty() {
|
|
// Not a failure of this checkout: `make bootstrap` embeds a real image and puts the
|
|
// placeholder back, so a real image here means a build was interrupted.
|
|
t.Skip("this checkout has a saved image embedded, so there is no placeholder to check")
|
|
}
|
|
if _, err := Saved(); err == nil {
|
|
t.Fatal("an installer carrying only the placeholder reported it carries an image")
|
|
}
|
|
}
|
|
|
|
// And "empty" is decided by whether the bytes could be loaded, not by matching the placeholder's
|
|
// text. A truncated or corrupted embed is equally unloadable and equally worth refusing early.
|
|
func TestEmptyMeansUnloadableRatherThanEqualToThePlaceholder(t *testing.T) {
|
|
restore := saved
|
|
defer func() { saved = restore }()
|
|
|
|
saved = []byte("half a tar, cut off")
|
|
if !IsEmpty() {
|
|
t.Error("bytes that are not a tar were reported as a carried image")
|
|
}
|
|
|
|
saved = savedImage(t, map[string]string{
|
|
"manifest.json": manifest(t, strings.Repeat("c", 64)+".json"),
|
|
})
|
|
if IsEmpty() {
|
|
t.Error("a real saved image was reported as no image at all")
|
|
}
|
|
}
|