v0.351.0 (unreleased): old -rc controller images and old infra images are deleted (decision 56 amended, decision 285)
gates / gates (push) Successful in 31s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-11-09 10:23:06 +01:00
1 parent 64b99b74e3
commit 27360d6abe
10 files changed
+471 -31

No files matched your search

@@ -28,7 +28,11 @@ import (
// of the running version; the PREVIOUS — the self-update's own record (update-state.json previous_image of a swap
// that ended on the running version), or, when there is no such record or its image is gone, the highest version
// below the running one (logged as "by version order"); every version ABOVE the running one (a pulled target the
// swap has not taken yet); every tag that is not a plain X.Y.Z (latest, -rc) — left alone, logged. A candidate is an
// swap has not taken yet); every tag that is not a version at all (latest) — left alone, logged. A test build's tag
// (X.Y.Z-rc.N) IS a version since v0.351.0 (decision 56, amended 2026-11-09): util.ParseVersionPre orders it below its release, so an old rc is a
// candidate like an old release — 25 of them (2.2 GB) sat on demo-hp on 2026-11-09 because the rule left every -rc
// alone. The previous by version order prefers a release; a test build is the previous only when no release is below
// the running one. A box that RUNS a test build is judged too (it was skipped). A candidate is an
// untagged (<none>) controller image or one whose every tag is a version below the previous. Deleted by exact
// name/ID, never forced, never by prune; an ID that also carries another repository's name is left alone. NOTHING is
// deleted while the controller is swapping itself (selfUpdatingNow). Registry tags are never touched.
@@ -45,7 +49,7 @@ type ControllerImageRecord struct {
func (m *Manager) RetainControllerImages(rec ControllerImageRecord) ([]string, error) {
imageRetentionMu.Lock()
defer imageRetentionMu.Unlock()
running, err := util.ParseVersion(rec.Running)
running, err := util.ParseVersionPre(rec.Running)
if err != nil || rec.Repo == "" {
m.logger.Printf("[INFO] [stacks] controller image retention: skipped — running version %q is not a release (or no repository)", rec.Running)
return nil, nil
@@ -82,15 +86,27 @@ func (m *Manager) RetainControllerImages(rec ControllerImageRecord) ([]string, e
}
}
if prevTag == "" {
var best *util.Version
// A release first: a box on 0.350.0 holding 0.349.0 and 0.350.0-rc.3 keeps 0.349.0 for a hand roll-back.
var best, bestPre *util.Version
for _, im := range imgs {
if im.Repo != repo {
continue
}
if v, err := util.ParseVersion(im.Tag); err == nil && v.Compare(running) < 0 && (best == nil || v.Compare(*best) > 0) {
vv := v
best = &vv
v, err := util.ParseVersionPre(im.Tag)
if err != nil || v.Compare(running) >= 0 {
continue
}
vv := v
if v.Pre == "" {
if best == nil || v.Compare(*best) > 0 {
best = &vv
}
} else if bestPre == nil || v.Compare(*bestPre) > 0 {
bestPre = &vv
}
}
if best == nil {
best = bestPre
}
if best != nil {
prevTag, prevHow = best.Raw, "by version order (no swap record names one present)"
@@ -98,7 +114,7 @@ func (m *Manager) RetainControllerImages(rec ControllerImageRecord) ([]string, e
}
var prev *util.Version
if prevTag != "" {
if v, err := util.ParseVersion(prevTag); err == nil {
if v, err := util.ParseVersionPre(prevTag); err == nil {
prev = &v
}
}
@@ -140,10 +156,10 @@ func (m *Manager) RetainControllerImages(rec ControllerImageRecord) ([]string, e
continue
}
names = append(names, im.Repo+":"+im.Tag)
v, err := util.ParseVersion(im.Tag)
v, err := util.ParseVersionPre(im.Tag)
switch {
case err != nil:
deletable, odd = false, im.Tag // latest, -rc: not ours to judge
deletable, odd = false, im.Tag // latest: not a version, not ours to judge
case v.Compare(running) >= 0:
deletable = false // the running one, or a pulled target not swapped to yet
case prev == nil || v.Compare(*prev) >= 0: