Browse Source

c8d/image_manifest: Common bits

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Paweł Gronowski 1 year ago
parent
commit
eecb36e822
1 changed files with 79 additions and 5 deletions
  1. 79 5
      daemon/containerd/image_manifest.go

+ 79 - 5
daemon/containerd/image_manifest.go

@@ -6,6 +6,7 @@ import (
 
 	"github.com/containerd/containerd"
 	"github.com/containerd/containerd/content"
+	cerrdefs "github.com/containerd/containerd/errdefs"
 	"github.com/containerd/containerd/images"
 	containerdimages "github.com/containerd/containerd/images"
 	"github.com/containerd/containerd/platforms"
@@ -48,6 +49,51 @@ func (i *ImageService) walkImageManifests(ctx context.Context, img containerdima
 	return errNotManifestOrIndex
 }
 
+// walkReachableImageManifests calls the handler for each manifest in the
+// multiplatform image that can be reached from the given image.
+// The image might not be present locally, but its descriptor is known.
+// The image implements the containerd.Image interface, but all operations act
+// on the specific manifest instead of the index.
+func (i *ImageService) walkReachableImageManifests(ctx context.Context, img containerdimages.Image, handler func(img *ImageManifest) error) error {
+	desc := img.Target
+
+	handleManifest := func(ctx context.Context, d ocispec.Descriptor) error {
+		platformImg, err := i.NewImageManifest(ctx, img, d)
+		if err != nil {
+			if err == errNotManifest {
+				return nil
+			}
+			return err
+		}
+		return handler(platformImg)
+	}
+
+	if containerdimages.IsManifestType(desc.MediaType) {
+		return handleManifest(ctx, desc)
+	}
+
+	if containerdimages.IsIndexType(desc.MediaType) {
+		return containerdimages.Walk(ctx, containerdimages.HandlerFunc(
+			func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
+				err := handleManifest(ctx, desc)
+				if err != nil {
+					return nil, err
+				}
+
+				c, err := containerdimages.Children(ctx, i.content, desc)
+				if err != nil {
+					if cerrdefs.IsNotFound(err) {
+						return nil, nil
+					}
+					return nil, err
+				}
+				return c, nil
+			}), desc)
+	}
+
+	return errNotManifestOrIndex
+}
+
 type ImageManifest struct {
 	containerd.Image
 
@@ -78,21 +124,31 @@ func (im *ImageManifest) Metadata() containerdimages.Image {
 	return md
 }
 
+func (im *ImageManifest) IsAttestation() bool {
+	// Quick check for 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 layer check below, but it requires
+	// an additional content read and deserialization of Manifest.
+	if _, has := im.Target().Annotations[attestation.DockerAnnotationReferenceType]; has {
+		return true
+	}
+	return false
+}
+
 // IsPseudoImage returns false if the manifest has no layers or any of its layers is a known image layer.
 // Some manifests use the image media type for compatibility, even if they are not a real image.
 func (im *ImageManifest) IsPseudoImage(ctx context.Context) (bool, error) {
 	desc := im.Target()
 
-	// Quick check for 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 layer check below, but it requires
-	// an additional content read and deserialization of Manifest.
-	if _, has := desc.Annotations[attestation.DockerAnnotationReferenceType]; has {
+	if im.IsAttestation() {
 		return true, nil
 	}
 
 	mfst, err := im.Manifest(ctx)
 	if err != nil {
+		if cerrdefs.IsNotFound(err) {
+			return false, errdefs.NotFound(errors.Wrapf(err, "failed to read manifest %v", desc.Digest))
+		}
 		return true, err
 	}
 	if len(mfst.Layers) == 0 {
@@ -149,3 +205,21 @@ func readManifest(ctx context.Context, store content.Provider, desc ocispec.Desc
 
 	return mfst, nil
 }
+
+// ImagePlatform returns the platform of the image manifest.
+// If the manifest list doesn't have a platform filled, it will be read from the config.
+func (m *ImageManifest) ImagePlatform(ctx context.Context) (ocispec.Platform, error) {
+	target := m.Target()
+	if target.Platform != nil {
+		return *target.Platform, nil
+	}
+
+	configDesc, err := m.Config(ctx)
+	if err != nil {
+		return ocispec.Platform{}, err
+	}
+
+	var out ocispec.Platform
+	err = readConfig(ctx, m.ContentStore(), configDesc, &out)
+	return out, err
+}