-
Notifications
You must be signed in to change notification settings - Fork 75
copy: add option to strip sparse manifest lists #655
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Add the ability to strip non-copied instances from manifest lists when using CopySpecificImages. This implements the functionality originally proposed in containers/image#1707. When copying a subset of images from a multi-architecture manifest list using CopySpecificImages, the current behavior always produces a sparse manifest list - a manifest list that references platforms that weren't actually copied. While this maintains the original digest, many registries reject sparse manifests with "blob unknown to registry" errors. This commit adds a new SparseManifestListAction option that gives users control over this behavior: - KeepSparseManifestList (default): preserves existing behavior - StripSparseManifestList (new): removes non-copied instances The implementation includes: 1. New SparseManifestListAction enum in image/copy 2. New ListOpDelete operation in image/internal/manifest with support for both OCI1Index and Schema2List formats 3. Index-based deletion (not digest-based) to handle platform variants that share the same digest 4. Stripping logic in copyMultipleImages that activates when StripSparseManifestList is set 5. Comprehensive test coverage for deletion operations Based on original work by @bertbaron and @mtrmac in containers/image#1707, adapted for the container-libs monorepo structure. Relates to containers#227 Signed-off-by: Alex Guidi <[email protected]>
|
Packit jobs failed. @containers/packit-build please check. |
mtrmac
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Just a brief first look: generally the structure and choice of affected sub packages look correct.
This is impressively small, I was expecting a that a larger change to internal/manifest would be necessary.
| } | ||
|
|
||
| // Find which indices were skipped | ||
| var indicesToDelete []int |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(If the structure remains: I think this variable is unnecessary — we can do something like for range slices.Backwards(instanceDigests) { if !copiedDigests.Contains(…) { deleteEdits = append(…) } in one loop)
| return fmt.Errorf("Schema2List.EditInstances: invalid delete index %d (list has %d instances)", editInstance.DeleteIndex, len(list.Manifests)) | ||
| } | ||
| // Remove the element by appending slices before and after the target index | ||
| list.Manifests = append(list.Manifests[:editInstance.DeleteIndex], list.Manifests[editInstance.DeleteIndex+1:]...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
slices.Delete would be a bit better, (also in the other copy)
| } | ||
|
|
||
| // TestListDeleteInstances tests the ListOpDelete functionality for both OCI and Docker manifest formats. | ||
| func TestListDeleteInstances(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’d prefer this to be tested in the existing Test…EditInstances methods (but the two can share a helper if that is beneficial)
| // Remove skipped instances from the manifest list using EditInstances | ||
| if len(deleteEdits) > 0 { | ||
| logrus.Debugf("Removing %d instances from manifest list", len(deleteEdits)) | ||
| if err := updatedList.EditInstances(deleteEdits, false); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can only call EditInstances once, appending to existing instanceEdits.
| // Build a set of digests that were copied | ||
| copiedDigests := set.New[digest.Digest]() | ||
| for _, instance := range instanceCopyList { | ||
| copiedDigests.Add(instance.sourceDigest) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if the same digest is included multiple times in the index? That’s not very likely with per-platform images, but with CopySpecificImages, the caller can choose to copy one of the duplicates and not the other.
prepareInstanceCopies knows exactly which instances are copied and which are not, so I think it can also prepare some version of the deletion instructions.
Also, prepareInstanceCopies is intentionally a ~pure function, so that it is unit-testable. Unit tests for the logic here, ensuring we ask for deleting the correct instances, seem worthwhile.
|
|
||
| // StripSparseManifestList will strip missing images from the manifest | ||
| // list. When images are stripped the digest will differ from the original. | ||
| StripSparseManifestList |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| StripSparseManifestList | |
| StripSparseManifestList SparseManifestListAction |
IIRC, otherwise it doesn’t have the specialized type.
|
|
||
| // When only a subset of images of a list is copied, this action indicates if the manifest should be kept or stripped. | ||
| // See CopySpecificImages. | ||
| SparseManifestListAction SparseManifestListAction |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this would be better placed closer to the ImageListSelection field.
Add option to strip sparse manifest lists when copying specific images
This PR adds the ability to strip non-copied instances from manifest lists when using
CopySpecificImages, implementing the functionality proposed in containers/image#1707.Motivation
Relates to #227
When copying a subset of images from a multi-architecture manifest list using
CopySpecificImages, the current behavior always produces a sparse manifest list - a manifest list that references platforms that weren't actually copied. While this maintains the original digest, many registries reject sparse manifests with "blob unknown to registry" errors.This PR adds a new
SparseManifestListActionoption that gives users control over this behavior:KeepSparseManifestList(default, existing behavior) - keeps all references even for non-copied imagesStripSparseManifestList(new) - removes non-copied instances and generates a manifest list with only copied instancesThis is particularly important for use cases like selective mirroring where only specific architectures need to be copied to registries that don't support sparse manifests.
Changes
1. New
SparseManifestListActionoption inimage/copySparseManifestListActionenum with two values:KeepSparseManifestList(default): preserves existing behavior - keeps all manifest referencesStripSparseManifestList(new): removes non-copied instances from the manifest listOptionsstruct to give users explicit control2. New
ListOpDeleteoperation inimage/internal/manifestListEditwithListOpDeleteoperation andDeleteIndexfield to support removing instancesOCI1IndexandSchema2List3. Sparse manifest stripping in
copyMultipleImages(image/copy/multiple.go)SparseManifestListAction == StripSparseManifestListEditInstancesAPI withListOpDelete4. Comprehensive test coverage
TestListDeleteInstancescovering both OCI and Docker manifest formatsTesting
All existing tests pass. New tests verify:
Credits
This implementation is based on the original work by @bertbaron and @mtrmac in containers/image#1707, adapted for the container-libs monorepo structure with the following changes:
manifestpackage tointernal/manifest(per container-libs directory structure)EditInstancesAPI pattern used throughout the codebase