From 3312b8251545f1179bdc2d481019216f32875119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Wed, 20 Mar 2024 12:32:18 +0100 Subject: [PATCH] c8d/list: Add a test case for images sharing a top layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paweł Gronowski --- integration/image/list_test.go | 35 +++++++++++++++ internal/testutils/specialimage/multilayer.go | 45 ++++++++++++------- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/integration/image/list_test.go b/integration/image/list_test.go index b653802491..54d725315f 100644 --- a/integration/image/list_test.go +++ b/integration/image/list_test.go @@ -10,10 +10,14 @@ import ( "github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/image" "github.com/docker/docker/integration/internal/container" + "github.com/docker/docker/internal/testutils/specialimage" "github.com/docker/docker/testutil" + "github.com/docker/docker/testutil/daemon" "github.com/google/go-cmp/cmp/cmpopts" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" + "gotest.tools/v3/skip" ) // Regression : #38171 @@ -192,3 +196,34 @@ func TestAPIImagesFilters(t *testing.T) { }) } } + +// Verify that the size calculation operates on ChainIDs and not DiffIDs. +// This test calls an image list with two images that share one, top layer. +func TestAPIImagesListSizeShared(t *testing.T) { + skip.If(t, testEnv.DaemonInfo.OSType != "linux") + + ctx := setupTest(t) + + daemon := daemon.New(t) + daemon.Start(t) + defer daemon.Stop(t) + + client := daemon.NewClientT(t) + + specialimage.Load(ctx, t, client, func(dir string) (*ocispec.Index, error) { + return specialimage.MultiLayerCustom(dir, "multilayer:latest", []specialimage.SingleFileLayer{ + {Name: "bar", Content: []byte("2")}, + {Name: "foo", Content: []byte("1")}, + }) + }) + + specialimage.Load(ctx, t, client, func(dir string) (*ocispec.Index, error) { + return specialimage.MultiLayerCustom(dir, "multilayer2:latest", []specialimage.SingleFileLayer{ + {Name: "asdf", Content: []byte("3")}, + {Name: "foo", Content: []byte("1")}, + }) + }) + + _, err := client.ImageList(ctx, image.ListOptions{SharedSize: true}) + assert.NilError(t, err) +} diff --git a/internal/testutils/specialimage/multilayer.go b/internal/testutils/specialimage/multilayer.go index ee20380084..b7352d019d 100644 --- a/internal/testutils/specialimage/multilayer.go +++ b/internal/testutils/specialimage/multilayer.go @@ -16,20 +16,32 @@ import ( ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) -func MultiLayer(dir string) (*ocispec.Index, error) { - const imageRef = "multilayer:latest" +type SingleFileLayer struct { + Name string + Content []byte +} - layer1Desc, err := writeLayerWithOneFile(dir, "foo", []byte("1")) - if err != nil { - return nil, err - } - layer2Desc, err := writeLayerWithOneFile(dir, "bar", []byte("2")) - if err != nil { - return nil, err - } - layer3Desc, err := writeLayerWithOneFile(dir, "hello", []byte("world")) - if err != nil { - return nil, err +func MultiLayer(dir string) (*ocispec.Index, error) { + return MultiLayerCustom(dir, "multilayer:latest", []SingleFileLayer{ + {Name: "foo", Content: []byte("1")}, + {Name: "bar", Content: []byte("2")}, + {Name: "hello", Content: []byte("world")}, + }) +} + +func MultiLayerCustom(dir string, imageRef string, layers []SingleFileLayer) (*ocispec.Index, error) { + var layerDescs []ocispec.Descriptor + var layerDgsts []digest.Digest + var layerBlobs []string + for _, layer := range layers { + layerDesc, err := writeLayerWithOneFile(dir, layer.Name, layer.Content) + if err != nil { + return nil, err + } + + layerDescs = append(layerDescs, layerDesc) + layerDgsts = append(layerDgsts, layerDesc.Digest) + layerBlobs = append(layerBlobs, blobPath(layerDesc)) } configDesc, err := writeJsonBlob(dir, ocispec.MediaTypeImageConfig, ocispec.Image{ @@ -39,7 +51,7 @@ func MultiLayer(dir string) (*ocispec.Index, error) { }, RootFS: ocispec.RootFS{ Type: "layers", - DiffIDs: []digest.Digest{layer1Desc.Digest, layer2Desc.Digest, layer3Desc.Digest}, + DiffIDs: layerDgsts, }, }) if err != nil { @@ -49,14 +61,14 @@ func MultiLayer(dir string) (*ocispec.Index, error) { manifest := ocispec.Manifest{ MediaType: ocispec.MediaTypeImageManifest, Config: configDesc, - Layers: []ocispec.Descriptor{layer1Desc, layer2Desc, layer3Desc}, + Layers: layerDescs, } legacyManifests := []manifestItem{ { Config: blobPath(configDesc), RepoTags: []string{imageRef}, - Layers: []string{blobPath(layer1Desc), blobPath(layer2Desc), blobPath(layer3Desc)}, + Layers: layerBlobs, }, } @@ -128,6 +140,7 @@ func writeLayerWithOneFile(dir string, filename string, content []byte) (ocispec if err != nil { return ocispec.Descriptor{}, err } + defer rd.Close() return writeBlob(dir, ocispec.MediaTypeImageLayer, rd) }