Merge pull request #47597 from vvoland/c8d-list-fix-shared-size
c8d/list: Fix shared size calculation
diff --git a/daemon/containerd/image_list.go b/daemon/containerd/image_list.go
index 600260a..80b2409 100644
--- a/daemon/containerd/image_list.go
+++ b/daemon/containerd/image_list.go
@@ -255,11 +255,13 @@
target := img.Target()
- chainIDs, err := img.RootFS(ctx)
+ diffIDs, err := img.RootFS(ctx)
if err != nil {
return err
}
+ chainIDs := identity.ChainIDs(diffIDs)
+
ts, _, err := i.singlePlatformSize(ctx, img)
if err != nil {
return err
@@ -650,6 +652,11 @@
}
size, err := sizeFn(chainID)
if err != nil {
+ // Several images might share the same layer and neither of them
+ // might be unpacked (for example if it's a non-host platform).
+ if cerrdefs.IsNotFound(err) {
+ continue
+ }
return 0, err
}
sharedSize += size
diff --git a/integration/image/list_test.go b/integration/image/list_test.go
index b653802..54d7253 100644
--- a/integration/image/list_test.go
+++ b/integration/image/list_test.go
@@ -10,10 +10,14 @@
"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 @@
})
}
}
+
+// 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 ee20380..b7352d0 100644
--- a/internal/testutils/specialimage/multilayer.go
+++ b/internal/testutils/specialimage/multilayer.go
@@ -16,20 +16,32 @@
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 @@
},
RootFS: ocispec.RootFS{
Type: "layers",
- DiffIDs: []digest.Digest{layer1Desc.Digest, layer2Desc.Digest, layer3Desc.Digest},
+ DiffIDs: layerDgsts,
},
})
if err != nil {
@@ -49,14 +61,14 @@
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 @@
if err != nil {
return ocispec.Descriptor{}, err
}
+ defer rd.Close()
return writeBlob(dir, ocispec.MediaTypeImageLayer, rd)
}