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 }