From bddd892e9108a7944df8fca9fde821bb97616bab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Gronowski?= Date: Wed, 14 Feb 2024 16:55:44 +0100 Subject: [PATCH] c8d: Adjust "image list" to return only a single item for each image store entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will return a single entry for each name/value pair, and for now all the "image specific" metadata (labels, config, size) should be either "default platform" or "first platform we have locally" (which then matches the logic for commands like `docker image inspect`, etc) with everything else (just ID, maybe?) coming from the manifest list/index. That leaves room for the longer-term implementation to add new fields to describe the _other_ images that are part of the manifest list/index. Co-authored-by: Tianon Gravi Signed-off-by: Paweł Gronowski --- daemon/containerd/image_list.go | 60 +++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 7 deletions(-) diff --git a/daemon/containerd/image_list.go b/daemon/containerd/image_list.go index bc4761acc6499..8271a9cc8a6fc 100644 --- a/daemon/containerd/image_list.go +++ b/daemon/containerd/image_list.go @@ -11,6 +11,7 @@ import ( cerrdefs "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/labels" + cplatforms "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/snapshots" "github.com/containerd/log" "github.com/distribution/reference" @@ -21,6 +22,7 @@ import ( "github.com/docker/docker/container" "github.com/docker/docker/errdefs" "github.com/docker/docker/image" + dockerspec "github.com/moby/docker-image-spec/specs-go/v1" "github.com/opencontainers/go-digest" "github.com/opencontainers/image-spec/identity" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -123,6 +125,9 @@ func (i *ImageService) Images(ctx context.Context, opts imagetypes.ListOptions) } } + // TODO: Allow platform override? + platformMatcher := matchAllWithPreference(cplatforms.Default()) + for _, img := range imgs { isDangling := isDanglingImage(img) @@ -154,7 +159,14 @@ func (i *ImageService) Images(ctx context.Context, opts imagetypes.ListOptions) allContainers = i.containers.List() } + type tempImage struct { + img *ImageManifest + indexPlatform *ocispec.Platform + dockerImage *dockerspec.DockerOCIImage + } + for _, img := range uniqueImages { + var presentImages []tempImage err := i.walkImageManifests(ctx, img, func(img *ImageManifest) error { if isPseudo, err := img.IsPseudoImage(ctx); isPseudo || err != nil { return err @@ -174,26 +186,60 @@ func (i *ImageService) Images(ctx context.Context, opts imagetypes.ListOptions) return nil } - image, chainIDs, err := i.singlePlatformImage(ctx, contentStore, tagsByDigest[img.RealTarget.Digest], img, opts, allContainers) + conf, err := img.Config(ctx) if err != nil { return err } - summaries = append(summaries, image) + var dockerImage dockerspec.DockerOCIImage + if err := readConfig(ctx, contentStore, conf, &dockerImage); err != nil { + return err + } + + presentImages = append(presentImages, tempImage{ + img: img, + indexPlatform: img.Target().Platform, + dockerImage: &dockerImage, + }) + return nil + }) + if err != nil { + return nil, err + } + + if len(presentImages) == 0 { + // TODO we should probably show *something* for images we've pulled + // but are 100% shallow or an empty manifest list/index + // ("tianon/scratch:index" is an empty example image index and + // "tianon/scratch:list" is an empty example manifest list) + continue + } - if opts.SharedSize { - root = append(root, &chainIDs) - for _, id := range chainIDs { - layers[id] = layers[id] + 1 + sort.SliceStable(presentImages, func(i, j int) bool { + platformFromIndexOrConfig := func(idx int) ocispec.Platform { + if presentImages[i].indexPlatform != nil { + return *presentImages[i].indexPlatform } + return presentImages[i].dockerImage.Platform } - return nil + return platformMatcher.Less(platformFromIndexOrConfig(i), platformFromIndexOrConfig(j)) }) + + best := presentImages[0].img + image, chainIDs, err := i.singlePlatformImage(ctx, contentStore, tagsByDigest[best.RealTarget.Digest], best, opts, allContainers) if err != nil { return nil, err } + summaries = append(summaries, image) + + if opts.SharedSize { + root = append(root, &chainIDs) + for _, id := range chainIDs { + layers[id] = layers[id] + 1 + } + } } if opts.SharedSize {