Move image references to shared content storage - #449
Conversation
|
@cursor review |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 6b7d984. Configure here.
7aac5c0 to
e3cab9b
Compare
promoteImageToContent was implemented but never invoked, so ready per-repository images were never migrated into the digest-keyed content layout. Promote them at manager startup: hardlink the rootfs into shared content, repoint repository tags, and retire the legacy tree. Non-ready images are left untouched and failures only warn.
Tag symlinks now point relatively into the shared content directory, so assert on the resolved digest instead of the raw link target. Deleting a digest whose build is still in flight keeps the shared content and a re-import joins that build, so the recreate-race test must expect the same build id. Recovery writes metadata through the layout resolver, so the credentials scrub check reads the content metadata path. Reuse metadataStatus for content metadata instead of duplicating the parse.
New builds write rootfs and metadata under the shared content directory, so EnsureImageReady must copy from the resolved disk location instead of the legacy per-repository digest directory and create its tag symlink in the repository references layout.
27eb82b to
1302e8d
Compare
sjmiller609
left a comment
There was a problem hiding this comment.
Blocker
lib/images/storage.go:312-315— startup promotion deletes legacy rootfs paths which breaks existing snapshots. Try this to reproduce
Nits
-
lib/images/manager.go:288-294,617-630— when a second tag joins an in-progress shared-digest build, its pending claim is not recorded. If that tag already references an older digest, it remains there after completion because the shared metadata tracks only the first request’s tag. Track pending claims per(repository, tag)and test two existing tags joining one in-flight digest. -
lib/images/storage_refs.go:219-227— repository references share content-level metadata, including API resource tags. Two repositories requesting the same digest with different resource tags therefore expose the first pull’s tags. Persist reference-level fields separately, or merge them per repository reference, with cross-repository list/filter coverage.
validation notes
- steady-state single and 25-way snapshot fanout showed no clear storage-related performance regression, looks good 👍
- this deduplicates the final whole-image EROFS by manifest digest; it does not create per-layer EROFS blobs or make conversion incremental. We were discussing a possibility to do that here on slack, quoted below for OSS visibility:
the way hypeman stores images is not efficient for host storage. Because each downloaded image version is a full copy of the rootfs, then similar images duplicate their base layers on the exported hypeman erofs file. This matters in use cases where we want to host a lot of different but similar images at the same time (eg quickbox).
Potential solution: instead of exporting each image as one flattened EROFS file, export its OCI layers as content-addressed EROFS blobs. Store shared layers once across image versions, then compose them into the read-only root filesystem Hypeman mounts for each VM.
but I think addressing that or not is optional in this PR, as that seems potentially much more complicated so could be worth doing separately, at your discretion.
|
there's a PR further up the stack that will de-duplicate layers (and will need a decent amount of iteration b4 it's ready), but this will enable hypeman tag which is the next PR in the stack! |

summary
Move image storage from repository-local digest directories to digest-keyed shared content, while keeping existing image operations and legacy data readable during migration.
images/content/<digest>images/repositories/<repository>/<tag>This change does not deduplicate or compose OCI layers.
pre-existing flows
The existing image APIs continue to support the current pull/import, lookup, wait, list, and delete flows:
CreateImageandImportLocalImagestill reuse an existing digest, return in-progress status, enforce credential matching, and apply Docker-style last-pull-wins tag behavior.GetImage, listing, and disk-path resolution can read both the legacy repository-local layout and the new shared-content layout.DeleteImageremoves a tag without deleting content still referenced by another tag or an active pull. Explicit digest deletion still removes the digest content.new flows
data model and storage changes
The on-disk layout changes from repository-local image data to shared content with separate repository tag references:
Legacy paths remain readable during migration. Image metadata gains fields for tag ownership and stale-build protection:
requested_tagprevious_tag_digesttag_generationThese fields prevent an older asynchronous pull from overwriting a tag after a newer pull or delete. Disk accounting identifies hard-linked rootfs files so aliases count once.
UX and behavior changes
WaitForReadycan observe a tagged pull before its final symlink is installed, reducing races in the registry conversion flow.validation
GOCACHE=/tmp/hypeman-go-cache go test -race -run 'Test(DeleteImagePreservesCrossRepositoryContent|DeleteTagRemovesBothLayoutReferences|LegacyImageIsNotShadowedByContentMetadata|ListAllMetadataDeduplicatesDualLayouts|FailedLegacyImageUsesReadyContent|ReadyContentDoesNotFallBackToLegacyDisk|WriteMetadataUsesContentWhenLegacyDirectoryIsEmpty|ContentLayoutResolvesDiskByDigest|ListAllMetadataContentLayout|TotalReadyImageBytes|ImageMetadata|TagFollowsLastPull)' ./lib/images ./lib/paths -count=1GOCACHE=/tmp/hypeman-go-cache go test -run '^$' ./lib/images ./lib/pathsmkfs.erofs.