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" "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" ) var ( errNotManifestOrIndex = errdefs.InvalidParameter(errors.New("descriptor is neither a manifest or index")) errNotManifest = errdefs.InvalidParameter(errors.New("descriptor isn't a manifest")) ) // 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 { 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 i.walkPresentChildren(ctx, desc, handleManifest) } return errNotManifestOrIndex } 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, errNotManifest } parent := img.Target img.Target = manifestDesc c8dImg := containerd.NewImageWithPlatform(i.client, img, platforms.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 := platforms.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 }