Selaa lähdekoodia

c8d: Add walkImageManifests and ImageManifest wrapper

The default implementation of the containerd.Image interface provided by
the containerd operates on the parent index/manifest list of the image
and the platform matcher.

This isn't convenient when a specific manifest is already known and it's
redundant to search the whole index for a manifest that matches the
given platform matcher. It can also result in a different manifest
picked up than expected when multiple manifests with the same platform
are present.

This introduces a walkImageManifests which walks the provided image and
calls a handler with a ImageManifest, which is a simple wrapper that
implements containerd.Image interfaces and performs all containerd.Image
operations against a platform specific manifest instead of the root
manifest list/index.

Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
(cherry picked from commit fabc1d5bef1dbe4a8779fef04cf3897a0c793372)
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Paweł Gronowski 2 vuotta sitten
vanhempi
commit
0139309fef
1 muutettua tiedostoa jossa 146 lisäystä ja 0 poistoa
  1. 146 0
      daemon/containerd/image_manifest.go

+ 146 - 0
daemon/containerd/image_manifest.go

@@ -0,0 +1,146 @@
+package containerd
+
+import (
+	"context"
+	"encoding/json"
+
+	"github.com/containerd/containerd"
+	"github.com/containerd/containerd/content"
+	"github.com/containerd/containerd/images"
+	containerdimages "github.com/containerd/containerd/images"
+	cplatforms "github.com/containerd/containerd/platforms"
+	"github.com/docker/docker/errdefs"
+	"github.com/moby/buildkit/util/attestation"
+	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
+	"github.com/pkg/errors"
+)
+
+// walkImageManifests calls the handler for each locally present manifest in
+// the image. The image implements the containerd.Image interface, but all
+// operations act on the specific manifest instead of the index.
+func (i *ImageService) walkImageManifests(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 {
+			return err
+		}
+		return handler(platformImg)
+	}
+
+	if containerdimages.IsIndexType(desc.MediaType) {
+		store := i.client.ContentStore()
+		return containerdimages.Walk(ctx, presentChildrenHandler(store, containerdimages.HandlerFunc(
+			func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
+				if containerdimages.IsManifestType(desc.MediaType) {
+					return nil, handleManifest(ctx, desc)
+				}
+				return nil, nil
+			})), desc)
+	}
+
+	return handleManifest(ctx, desc)
+}
+
+type ImageManifest struct {
+	containerd.Image
+
+	// Parent of the manifest (index/manifest list)
+	RealTarget ocispec.Descriptor
+
+	manifest *ocispec.Manifest
+}
+
+func (i *ImageService) NewImageManifest(ctx context.Context, img containerdimages.Image, manifestDesc ocispec.Descriptor) (*ImageManifest, error) {
+	if !containerdimages.IsManifestType(manifestDesc.MediaType) {
+		return nil, errdefs.InvalidParameter(errors.New("descriptor isn't a manifest"))
+	}
+
+	parent := img.Target
+	img.Target = manifestDesc
+
+	c8dImg := containerd.NewImageWithPlatform(i.client, img, cplatforms.All)
+	return &ImageManifest{
+		Image:      c8dImg,
+		RealTarget: parent,
+	}, nil
+}
+
+func (im *ImageManifest) Metadata() containerdimages.Image {
+	md := im.Image.Metadata()
+	md.Target = im.RealTarget
+	return md
+}
+
+// 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 {
+		return true, nil
+	}
+
+	mfst, err := im.Manifest(ctx)
+	if err != nil {
+		return true, err
+	}
+	if len(mfst.Layers) == 0 {
+		return false, nil
+	}
+	for _, l := range mfst.Layers {
+		if images.IsLayerType(l.MediaType) {
+			return false, nil
+		}
+	}
+	return true, nil
+}
+
+func (im *ImageManifest) Manifest(ctx context.Context) (ocispec.Manifest, error) {
+	if im.manifest != nil {
+		return *im.manifest, nil
+	}
+
+	mfst, err := readManifest(ctx, im.ContentStore(), im.Target())
+	if err != nil {
+		return ocispec.Manifest{}, err
+	}
+
+	im.manifest = &mfst
+	return mfst, nil
+}
+
+func (im *ImageManifest) CheckContentAvailable(ctx context.Context) (bool, error) {
+	// The target is already a platform-specific manifest, so no need to match platform.
+	pm := cplatforms.All
+
+	available, _, _, missing, err := containerdimages.Check(ctx, im.ContentStore(), im.Target(), pm)
+	if err != nil {
+		return false, err
+	}
+
+	if !available || len(missing) > 0 {
+		return false, nil
+	}
+
+	return true, nil
+}
+
+func readManifest(ctx context.Context, store content.Provider, desc ocispec.Descriptor) (ocispec.Manifest, error) {
+	p, err := content.ReadBlob(ctx, store, desc)
+	if err != nil {
+		return ocispec.Manifest{}, err
+	}
+
+	var mfst ocispec.Manifest
+	if err := json.Unmarshal(p, &mfst); err != nil {
+		return ocispec.Manifest{}, err
+	}
+
+	return mfst, nil
+}