浏览代码

c8d/list: Ignore attestation manifests

Attestation manifests have an OCI image media type, which makes them
being listed like they were a separate platform supported by the image.

Don't use `images.Platforms` and walk the manifest list ourselves
looking for all manifests that are an actual image manifest.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Paweł Gronowski 2 年之前
父节点
当前提交
92e38b6e27
共有 1 个文件被更改,包括 111 次插入27 次删除
  1. 111 27
      daemon/containerd/image_list.go

+ 111 - 27
daemon/containerd/image_list.go

@@ -2,18 +2,21 @@ package containerd
 
 
 import (
 import (
 	"context"
 	"context"
+	"encoding/json"
 
 
 	"github.com/containerd/containerd"
 	"github.com/containerd/containerd"
 	"github.com/containerd/containerd/content"
 	"github.com/containerd/containerd/content"
 	cerrdefs "github.com/containerd/containerd/errdefs"
 	cerrdefs "github.com/containerd/containerd/errdefs"
 	"github.com/containerd/containerd/images"
 	"github.com/containerd/containerd/images"
-	cplatforms "github.com/containerd/containerd/platforms"
+	"github.com/containerd/containerd/platforms"
 	"github.com/docker/distribution/reference"
 	"github.com/docker/distribution/reference"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/filters"
 	"github.com/docker/docker/api/types/filters"
+	"github.com/moby/buildkit/util/attestation"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/go-digest"
 	"github.com/opencontainers/image-spec/identity"
 	"github.com/opencontainers/image-spec/identity"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
 	v1 "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/pkg/errors"
 	"github.com/sirupsen/logrus"
 	"github.com/sirupsen/logrus"
 )
 )
 
 
@@ -77,29 +80,77 @@ func (i *ImageService) Images(ctx context.Context, opts types.ImageListOptions)
 			continue
 			continue
 		}
 		}
 
 
-		platforms, err := images.Platforms(ctx, contentStore, img.Target)
-		if err != nil {
-			return nil, err
-		}
-
-		for _, platform := range platforms {
-			image, chainIDs, err := i.singlePlatformImage(ctx, contentStore, img, platform)
-			if err != nil {
-				return nil, err
-			}
-			if image == nil {
-				continue
+		err := images.Walk(ctx, images.HandlerFunc(func(ctx context.Context, desc v1.Descriptor) ([]v1.Descriptor, error) {
+			if images.IsIndexType(desc.MediaType) {
+				return images.Children(ctx, contentStore, desc)
 			}
 			}
 
 
-			summaries = append(summaries, image)
+			if images.IsManifestType(desc.MediaType) {
+				// Ignore buildkit attestation manifests
+				// https://github.com/moby/buildkit/blob/v0.11.4/docs/attestations/attestation-storage.md
+				// This would have also been caught by the isImageManifest call below, but it requires
+				// an additional content read and deserialization of Manifest.
+				if _, has := desc.Annotations[attestation.DockerAnnotationReferenceType]; has {
+					return nil, nil
+				}
+
+				mfst, err := images.Manifest(ctx, contentStore, desc, platforms.All)
+				if err != nil {
+					if cerrdefs.IsNotFound(err) {
+						return nil, nil
+					}
+					return nil, err
+				}
+
+				if !isImageManifest(mfst) {
+					return nil, nil
+				}
+
+				platform, err := getManifestPlatform(ctx, contentStore, desc, mfst.Config)
+				if err != nil {
+					if cerrdefs.IsNotFound(err) {
+						return nil, nil
+					}
+					return nil, err
+				}
+
+				pm := platforms.OnlyStrict(platform)
+				available, _, _, missing, err := images.Check(ctx, contentStore, img.Target, pm)
+				if err != nil {
+					logrus.WithFields(logrus.Fields{
+						logrus.ErrorKey: err,
+						"platform":      platform,
+						"image":         img.Target,
+					}).Warn("checking availability of platform content failed")
+					return nil, nil
+				}
+				if !available || len(missing) > 0 {
+					return nil, nil
+				}
 
 
-			if opts.SharedSize {
-				root = append(root, &chainIDs)
-				for _, id := range chainIDs {
-					layers[id] = layers[id] + 1
+				c8dImage := containerd.NewImageWithPlatform(i.client, img, pm)
+				image, chainIDs, err := i.singlePlatformImage(ctx, contentStore, c8dImage)
+				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
+					}
 				}
 				}
 			}
 			}
+
+			return nil, nil
+		}), img.Target)
+
+		if err != nil {
+			return nil, err
 		}
 		}
+
 	}
 	}
 
 
 	if opts.SharedSize {
 	if opts.SharedSize {
@@ -115,15 +166,7 @@ func (i *ImageService) Images(ctx context.Context, opts types.ImageListOptions)
 	return summaries, nil
 	return summaries, nil
 }
 }
 
 
-func (i *ImageService) singlePlatformImage(ctx context.Context, contentStore content.Store, img images.Image, platform v1.Platform) (*types.ImageSummary, []digest.Digest, error) {
-	platformMatcher := cplatforms.OnlyStrict(platform)
-	available, _, _, missing, err := images.Check(ctx, contentStore, img.Target, platformMatcher)
-	if !available || err != nil || len(missing) > 0 {
-		return nil, nil, nil
-	}
-
-	image := containerd.NewImageWithPlatform(i.client, img, platformMatcher)
-
+func (i *ImageService) singlePlatformImage(ctx context.Context, contentStore content.Store, image containerd.Image) (*types.ImageSummary, []digest.Digest, error) {
 	diffIDs, err := image.RootFS(ctx)
 	diffIDs, err := image.RootFS(ctx)
 	if err != nil {
 	if err != nil {
 		return nil, nil, err
 		return nil, nil, err
@@ -303,3 +346,44 @@ func computeSharedSize(chainIDs []digest.Digest, layers map[digest.Digest]int, s
 	}
 	}
 	return sharedSize, nil
 	return sharedSize, nil
 }
 }
+
+// getManifestPlatform returns a platform specified by the manifest descriptor
+// or reads it from its config.
+func getManifestPlatform(ctx context.Context, store content.Provider, manifestDesc, configDesc v1.Descriptor) (v1.Platform, error) {
+	var platform v1.Platform
+	if manifestDesc.Platform != nil {
+		platform = *manifestDesc.Platform
+	} else {
+		// Config is technically a v1.Image, but it has the same member as v1.Platform
+		// which makes the v1.Platform a subset of Image so we can unmarshal directly.
+		if err := readConfig(ctx, store, configDesc, &platform); err != nil {
+			return platform, err
+		}
+	}
+	return platforms.Normalize(platform), nil
+}
+
+// isImageManifests returns true if the manifest has any layer that is a known image layer.
+// Some manifests use the image media type for compatibility, even if they are not a real image.
+func isImageManifest(mfst v1.Manifest) bool {
+	for _, l := range mfst.Layers {
+		if images.IsLayerType(l.MediaType) {
+			return true
+		}
+	}
+	return false
+}
+
+// readConfig reads content pointed by the descriptor and unmarshals it into a specified output.
+func readConfig(ctx context.Context, store content.Provider, desc v1.Descriptor, out interface{}) error {
+	data, err := content.ReadBlob(ctx, store, desc)
+	if err != nil {
+		return errors.Wrapf(err, "failed to read config content")
+	}
+	err = json.Unmarshal(data, out)
+	if err != nil {
+		return errors.Wrapf(err, "could not deserialize image config")
+	}
+
+	return nil
+}