// The release's own swap demand (zero-downtime deploys spec §4.2): how an // awake instance may move onto a build, as the build's shipper said. package release import ( "context" "errors" "fmt" "strings" ) // DemandHold is the one swap demand a release can make (`carlos deploy // --swap hold`, spec §9.1). const DemandHold = "hold" // NormalizeDemand is the demand a box acts on for a manifest's swap field. // "" is no demand. Anything else is DemandHold: "hold" itself, and any value // a newer shipper wrote that this binary does not know, because missing or // unverifiable policy data never permits an overlap (spec §4.2). func NormalizeDemand(swap string) string { if swap == "" { return "" } return DemandHold } // ErrStagedDigestMismatch is an adoption attempt's staging error (spec §4.2): // the staged executable is not the artifact the canonical manifest names for // this platform, so the manifest's demand does not describe the bytes that // would run. The attempt is refused, with no failure record against the // build. var ErrStagedDigestMismatch = errors.New("staged artifact is not the release's canonical artifact") // CanonicalDemand reads build version's own swap demand from its CANONICAL // manifest in the release store, and checks the staged executable at // stagedPath against that manifest's digest for platform (spec §4.2). // // - Match: the manifest's demand (NormalizeDemand), the staged digest, nil. // - Mismatch: "", the staged digest, an error wrapping // ErrStagedDigestMismatch. // - Cannot compare (the manifest is unreadable, it has no artifact for // platform, or the staged file cannot be read): DemandHold and a nil // error, because missing or unverifiable policy data never permits an // overlap. digest is the staged file's when it could be hashed, else "". // // It is for ADOPTION attempts only (causes release, install, retry). Recovery // checks a retained digest with VerifyStaged and never asks the store. func CanonicalDemand(ctx context.Context, s Store, acct, app, version, platform, stagedPath string) (demand string, digest string, err error) { digest, herr := FileSHA256(stagedPath) if herr != nil { return DemandHold, "", nil } m, _, merr := GetManifestRaw(ctx, s, acct, app, version) if merr != nil { return DemandHold, digest, nil } art, ok := m.Artifacts[platform] if !ok { return DemandHold, digest, nil } if !strings.EqualFold(art.SHA256, digest) { return "", digest, fmt.Errorf("%w: %s/%s staged at %s hashes to %s; the release names %s", ErrStagedDigestMismatch, app, version, stagedPath, digest, art.SHA256) } return NormalizeDemand(m.Swap), digest, nil }