diff --git a/libimage/copier.go b/libimage/copier.go index 5f277a69e..b622ef5e3 100644 --- a/libimage/copier.go +++ b/libimage/copier.go @@ -49,6 +49,10 @@ type CopyOptions struct { CompressionFormat *compression.Algorithm // CompressionLevel specifies what compression level is used CompressionLevel *int + // ForceCompressionFormat ensures that the compression algorithm set in + // CompressionFormat is used exclusively, and blobs of other compression + // algorithms are not reused. + ForceCompressionFormat bool // containers-auth.json(5) file to use when authenticating against // container registries. @@ -294,6 +298,7 @@ func (r *Runtime) newCopier(options *CopyOptions) (*copier, error) { c.imageCopyOptions.ProgressInterval = time.Second } + c.imageCopyOptions.ForceCompressionFormat = options.ForceCompressionFormat c.imageCopyOptions.ForceManifestMIMEType = options.ManifestMIMEType c.imageCopyOptions.SourceCtx = c.systemContext c.imageCopyOptions.DestinationCtx = c.systemContext diff --git a/libimage/push_test.go b/libimage/push_test.go index dadcd1eb4..67ebedc33 100644 --- a/libimage/push_test.go +++ b/libimage/push_test.go @@ -3,9 +3,12 @@ package libimage import ( "context" "os" + "path/filepath" "testing" "github.com/containers/common/pkg/config" + "github.com/containers/image/v5/pkg/compression" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -96,3 +99,70 @@ func TestPushOtherPlatform(t *testing.T) { _, err = runtime.Push(ctx, "docker.io/library/alpine:latest", "docker-archive:"+tmp.Name(), pushOptions) require.NoError(t, err) } + +func TestPushWithForceCompression(t *testing.T) { + runtime, cleanup := testNewRuntime(t) + defer cleanup() + ctx := context.Background() + + // Prefetch alpine. + pullOptions := &PullOptions{} + pullOptions.Writer = os.Stdout + pullOptions.Architecture = "arm64" + pulledImages, err := runtime.Pull(ctx, "docker.io/library/alpine:latest", config.PullPolicyAlways, pullOptions) + require.NoError(t, err) + require.Len(t, pulledImages, 1) + + data, err := pulledImages[0].Inspect(ctx, nil) + require.NoError(t, err) + require.Equal(t, "arm64", data.Architecture) + + pushOptions := &PushOptions{} + pushOptions.CompressionFormat = &compression.Zstd + pushOptions.Writer = os.Stdout + tmp := t.TempDir() + _, err = runtime.Push(ctx, "docker.io/library/alpine:latest", "oci:"+tmp, pushOptions) + require.NoError(t, err) + + // blobs from first push + entries, err := os.ReadDir(filepath.Join(tmp, "blobs", "sha256")) + require.NoError(t, err) + blobsFirstPush := []string{} + for _, e := range entries { + blobsFirstPush = append(blobsFirstPush, e.Name()) + } + + _, err = runtime.Push(ctx, "docker.io/library/alpine:latest", "oci:"+tmp, pushOptions) + require.NoError(t, err) + + // blobs from second push + entries, err = os.ReadDir(filepath.Join(tmp, "blobs", "sha256")) + require.NoError(t, err) + blobsSecondPush := []string{} + for _, e := range entries { + blobsSecondPush = append(blobsSecondPush, e.Name()) + } + + // All blobs of first push should be equivalent to blobs of + // second push since same compression was used + assert.Equal(t, blobsSecondPush, blobsFirstPush) + + pushOptions = &PushOptions{} + pushOptions.CompressionFormat = &compression.Gzip + pushOptions.ForceCompressionFormat = true + pushOptions.Writer = os.Stdout + + _, err = runtime.Push(ctx, "docker.io/library/alpine:latest", "oci:"+tmp, pushOptions) + require.NoError(t, err) + + entries, err = os.ReadDir(filepath.Join(tmp, "blobs", "sha256")) + require.NoError(t, err) + blobsThirdPush := []string{} + for _, e := range entries { + blobsThirdPush = append(blobsThirdPush, e.Name()) + } + // Blobs of third push should not be equivalent to blobs of + // second and first push since different compression was used + // and ForceCompressionFormat is `true` + assert.NotEqual(t, blobsThirdPush, blobsFirstPush) +}