Stage a container image the way a binary is staged

The container deployment's answer to downloading a release: pull the image
the operator named, then ask it what it is. That last part is the point of
the phase, exactly as it is for a binary - the tag, the registry and the
repository name are all assumptions about someone else's publishing
process, and the image's own answer is the only thing that settles what
arrived.

The image is never derived from the running container by swapping its tag.
That derivation is wrong for a digest-pinned image, wrong for a mirror and
wrong for a fork, and being wrong here means pulling the wrong software
into a mail server. It is named in full or the phase refuses.

What comes back is the image's ID rather than the tag it arrived under. A
tag can move between staging and cutover -- that is the whole reason latest
is a hazard -- and running the tag later would run something other than
what was verified here.

VersionFromOutput is exported from preflight so both paths parse a version
identically. Two copies of that regex could disagree about what they
staged, which is a difference nobody would look for.

One caveat recorded rather than hidden: asking an image its version means
running it with --version, which assumes its entrypoint is the server and
passes flags through. That has not been confirmed against a published
Stalwart image, there being none to hand. If the assumption is wrong this
fails loudly with the image's own output rather than staging something
unverified, and the fallback tries the binary by name before giving up.

SkipPull is for a host that loaded the image from a tarball, where a pull
cannot work and its failure would say nothing useful.
This commit is contained in:
2026-08-28 17:20:53 -07:00
parent f34cd95454
commit 0a6b3ad173
3 changed files with 380 additions and 0 deletions
+14
View File
@@ -63,6 +63,20 @@ func cmp(a, b int) int {
// automates - see ARCHITECTURE.md §1/§4.1.
var minSupportedSource = semver{0, 15, 0}
// VersionFromOutput extracts a semver from whatever a Stalwart build
// printed when asked for its version. Exported because the same question
// gets asked of a container image (internal/stage), where the command is
// `docker run <image> --version` rather than the binary directly - and the
// answer has to be parsed identically or the two paths could disagree
// about what they staged.
func VersionFromOutput(out string) (string, error) {
v, err := parseSemver(out)
if err != nil {
return "", err
}
return v.String(), nil
}
// DetectVersion runs the installed binary's --version flag and extracts a
// semver from its output.
func DetectVersion(ctx context.Context, binaryPath string) (string, error) {