diff --git a/copy/copy.go b/copy/copy.go index ac0e6f2fa2..66d06fc51b 100644 --- a/copy/copy.go +++ b/copy/copy.go @@ -133,6 +133,10 @@ type Options struct { // Invalid when copying a non-multi-architecture image. That will probably // change in the future. EnsureCompressionVariantsExist []OptionCompressionVariant + // ForceCompressionFormat ensures that the compression algorithm set in + // DestinationCtx.CompressionFormat is used exclusively, and blobs of other + // compression algorithms are not reused. + ForceCompressionFormat bool } // OptionCompressionVariant allows to supply information about @@ -269,8 +273,11 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, if len(options.EnsureCompressionVariantsExist) > 0 { return nil, fmt.Errorf("EnsureCompressionVariantsExist is not implemented when not creating a multi-architecture image") } + if (options.DestinationCtx == nil || options.DestinationCtx.CompressionFormat == nil) && options.ForceCompressionFormat { + return nil, fmt.Errorf("cannot use ForceCompressionFormat with undefined default compression format") + } // The simple case: just copy a single image. - single, err := c.copySingleImage(ctx, c.unparsedToplevel, nil, copySingleImageOptions{requireCompressionFormatMatch: false}) + single, err := c.copySingleImage(ctx, c.unparsedToplevel, nil, copySingleImageOptions{requireCompressionFormatMatch: c.options.ForceCompressionFormat}) if err != nil { return nil, err } @@ -279,6 +286,9 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, if len(options.EnsureCompressionVariantsExist) > 0 { return nil, fmt.Errorf("EnsureCompressionVariantsExist is not implemented when not creating a multi-architecture image") } + if (options.DestinationCtx == nil || options.DestinationCtx.CompressionFormat == nil) && options.ForceCompressionFormat { + return nil, fmt.Errorf("cannot use ForceCompressionFormat with undefined default compression format") + } // This is a manifest list, and we weren't asked to copy multiple images. Choose a single image that // matches the current system to copy, and copy it. mfest, manifestType, err := c.unparsedToplevel.Manifest(ctx) @@ -295,7 +305,7 @@ func Image(ctx context.Context, policyContext *signature.PolicyContext, destRef, } logrus.Debugf("Source is a manifest list; copying (only) instance %s for current system", instanceDigest) unparsedInstance := image.UnparsedInstance(rawSource, &instanceDigest) - single, err := c.copySingleImage(ctx, unparsedInstance, nil, copySingleImageOptions{requireCompressionFormatMatch: false}) + single, err := c.copySingleImage(ctx, unparsedInstance, nil, copySingleImageOptions{requireCompressionFormatMatch: c.options.ForceCompressionFormat}) if err != nil { return nil, fmt.Errorf("copying system image from manifest list: %w", err) } diff --git a/copy/multiple.go b/copy/multiple.go index 34f2129d69..fb0996b568 100644 --- a/copy/multiple.go +++ b/copy/multiple.go @@ -29,8 +29,9 @@ const ( ) type instanceCopy struct { - op instanceCopyKind - sourceDigest digest.Digest + op instanceCopyKind + sourceDigest digest.Digest + requireCompressionFormatMatch bool // Fields which can be used by callers when operation // is `instanceCopyClone` @@ -122,9 +123,13 @@ func prepareInstanceCopies(list internalManifest.List, instanceDigests []digest. if err != nil { return res, fmt.Errorf("getting details for instance %s: %w", instanceDigest, err) } + if (options.DestinationCtx == nil || options.DestinationCtx.CompressionFormat == nil) && options.ForceCompressionFormat { + return res, fmt.Errorf("cannot use ForceCompressionFormat with undefined default compression") + } res = append(res, instanceCopy{ - op: instanceCopyCopy, - sourceDigest: instanceDigest, + op: instanceCopyCopy, + sourceDigest: instanceDigest, + requireCompressionFormatMatch: options.ForceCompressionFormat, }) platform := platformV1ToPlatformComparable(instanceDetails.ReadOnly.Platform) compressionList := compressionsByPlatform[platform] @@ -230,7 +235,7 @@ func (c *copier) copyMultipleImages(ctx context.Context) (copiedManifest []byte, logrus.Debugf("Copying instance %s (%d/%d)", instance.sourceDigest, i+1, len(instanceCopyList)) c.Printf("Copying image %s (%d/%d)\n", instance.sourceDigest, i+1, len(instanceCopyList)) unparsedInstance := image.UnparsedInstance(c.rawSource, &instanceCopyList[i].sourceDigest) - updated, err := c.copySingleImage(ctx, unparsedInstance, &instanceCopyList[i].sourceDigest, copySingleImageOptions{requireCompressionFormatMatch: false}) + updated, err := c.copySingleImage(ctx, unparsedInstance, &instanceCopyList[i].sourceDigest, copySingleImageOptions{requireCompressionFormatMatch: instance.requireCompressionFormatMatch}) if err != nil { return nil, fmt.Errorf("copying image %d/%d from manifest list: %w", i+1, len(instanceCopyList), err) } diff --git a/copy/multiple_test.go b/copy/multiple_test.go index 223dd274dc..b5f61e5b87 100644 --- a/copy/multiple_test.go +++ b/copy/multiple_test.go @@ -32,7 +32,7 @@ func TestPrepareCopyInstancesforInstanceCopyCopy(t *testing.T) { for _, instance := range sourceInstances { compare = append(compare, instanceCopy{op: instanceCopyCopy, - sourceDigest: instance}) + sourceDigest: instance, requireCompressionFormatMatch: false}) } assert.Equal(t, instancesToCopy, compare) @@ -42,6 +42,9 @@ func TestPrepareCopyInstancesforInstanceCopyCopy(t *testing.T) { compare = []instanceCopy{{op: instanceCopyCopy, sourceDigest: sourceInstances[1]}} assert.Equal(t, instancesToCopy, compare) + + _, err = prepareInstanceCopies(list, sourceInstances, &Options{Instances: []digest.Digest{sourceInstances[1]}, ImageListSelection: CopySpecificImages, ForceCompressionFormat: true}) + require.EqualError(t, err, "cannot use ForceCompressionFormat with undefined default compression") } // Test `instanceCopyClone` cases.