Skip to content

Protect every repository of a referenced digest from image retention - #520

Merged
sjmiller609 merged 2 commits into
mainfrom
hypeship/retention-protect-by-digest
Oct 15, 2026
Merged

sjmiller609 merged 2 commits into
mainfrom
hypeship/retention-protect-by-digest

Conversation

@sjmiller609

@sjmiller609 sjmiller609 commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Converted image content lives once per digest and is shared by every repository that resolves to it. The retention sweep built its protected set from the repository plus digest named by each instance and snapshot, so an image of the same digest under a different repository looked unused.

That bites when a build's image is pushed to another registry and an instance is created from the pushed name. The instance protects <registry>/<repo>@sha256:…, but the original builds/<id>@sha256:… ages out of the allow window and gets deleted, which removes the shared content. The instance can then no longer restore from standby: failed to resolve image for restore: image not found.

The sweep now also keys protection by digest. Any ready image whose digest is referenced is skipped, and its retention state is cleared so the unused timer restarts if the reference later disappears. Images are otherwise treated as before.

Validation

  • go test -race ./lib/imageretention
  • New TestSweepProtectsAliasOfReferencedDigest seeds the same digest under builds/abc and a digest-form registry name, references only the registry name from an instance, and expires the builds/abc state. It fails on main and passes with the change.

Note

Medium Risk
Changes when cached images are deleted can affect restore/standby if protection logic is wrong; scope is limited to retention sweep and only expands the protected set.

Overview
Image retention now protects shared digest content across all repository aliases, fixing cases where a build image under builds/<id> was deleted while an instance still referenced the same digest under a pushed registry name.

The sweep builds a digest-level protected set from instance and snapshot references: any ready cached image whose digest is referenced is skipped for deletion, and stale unused state on alias repos (e.g. builds/abc when only ECR @sha256:… is referenced) is cleared.

Reference collection now includes both StoredMetadata.Image and ResolvedImage, so the digest pinned at instance create time stays protected when a mutable tag later points at different content.

README documents digest-wide protection. New tests cover alias protection and tag-move scenarios.

Reviewed by Cursor Bugbot for commit 2a1e29e. Bugbot is set up for automated code reviews on this repo. Configure here.

Follow-up in the second commit

Reference collection read only StoredMetadata.Image, the name the caller passed. Boot, start, and restore use ResolvedImage, the digest pinned at create time, so a tag that later moved left the pinned digest unprotected and the instance unable to restore. Both names are now collected for instances and snapshots. This only adds to the protected set. TestSweepProtectsResolvedDigestAfterTagMoves covers it and fails without the change.

Converted image content is shared by digest across repositories, but the
retention sweep only protected the repository named by an instance or
snapshot. When a build's image was pushed to another registry and an
instance was created from the pushed name, the original builds/<id> image
looked unused, and deleting it removed the shared content the instance
needed to restore from standby.
@sjmiller609
sjmiller609 requested a review from chruffins October 15, 2026 20:49

@chruffins chruffins left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

approving.

follow-up (pre-existing, not introduced by this PR, so not blocking): reference collection in lib/imageretention/controller.go (collectReferencedImageNames, ~lines 378-401) reads only StoredMetadata.Image. that field holds the caller's tag. boot, start, and restore use ResolvedImage, the digest pinned at create time (bootImageRef in lib/instances/create_image.go). if a tag moves to a new digest, the old digest and its build alias can still age out, and the instance then fails to restore. the README's "every repository whose image resolves to a referenced digest is kept" holds only for references that are collected.

suggested fix: collect ResolvedImage with a fallback to Image for both instances and snapshots, matching bootImageRef. that needs bootImageRef exported or an equivalent helper.

Reference collection read only the image name the caller passed, but
boot, start, and restore use the digest pinned at create time. If the
caller's tag later moved, the pinned digest aged out and the instance
could no longer restore. Collect both names.
@sjmiller609

Copy link
Copy Markdown
Collaborator Author

Took the follow-up here in 2a1e29e rather than leaving it: both Image and ResolvedImage are collected for instances and snapshots, which covers the bootImageRef precedence without exporting it, and only ever adds to the protected set. New test stores a tag in Image and an older digest in ResolvedImage, expires the older digest, and checks it survives the sweep.

@sjmiller609
sjmiller609 merged commit 54a3c79 into main Oct 15, 2026
10 checks passed
@sjmiller609
sjmiller609 deleted the hypeship/retention-protect-by-digest branch October 15, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants