github.com/moby/docker@v26.1.3+incompatible/daemon/containerd/image_manifest.go (about)

     1  package containerd
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  
     7  	"github.com/containerd/containerd"
     8  	"github.com/containerd/containerd/content"
     9  	"github.com/containerd/containerd/images"
    10  	containerdimages "github.com/containerd/containerd/images"
    11  	"github.com/containerd/containerd/platforms"
    12  	"github.com/docker/docker/errdefs"
    13  	"github.com/moby/buildkit/util/attestation"
    14  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  var (
    19  	errNotManifestOrIndex = errdefs.InvalidParameter(errors.New("descriptor is neither a manifest or index"))
    20  	errNotManifest        = errdefs.InvalidParameter(errors.New("descriptor isn't a manifest"))
    21  )
    22  
    23  // walkImageManifests calls the handler for each locally present manifest in
    24  // the image. The image implements the containerd.Image interface, but all
    25  // operations act on the specific manifest instead of the index.
    26  func (i *ImageService) walkImageManifests(ctx context.Context, img containerdimages.Image, handler func(img *ImageManifest) error) error {
    27  	desc := img.Target
    28  
    29  	handleManifest := func(ctx context.Context, d ocispec.Descriptor) error {
    30  		platformImg, err := i.NewImageManifest(ctx, img, d)
    31  		if err != nil {
    32  			if err == errNotManifest {
    33  				return nil
    34  			}
    35  			return err
    36  		}
    37  		return handler(platformImg)
    38  	}
    39  
    40  	if containerdimages.IsManifestType(desc.MediaType) {
    41  		return handleManifest(ctx, desc)
    42  	}
    43  
    44  	if containerdimages.IsIndexType(desc.MediaType) {
    45  		return i.walkPresentChildren(ctx, desc, handleManifest)
    46  	}
    47  
    48  	return errors.Wrapf(errNotManifestOrIndex, "error walking manifest for %s", img.Name)
    49  }
    50  
    51  type ImageManifest struct {
    52  	containerd.Image
    53  
    54  	// Parent of the manifest (index/manifest list)
    55  	RealTarget ocispec.Descriptor
    56  
    57  	manifest *ocispec.Manifest
    58  }
    59  
    60  func (i *ImageService) NewImageManifest(ctx context.Context, img containerdimages.Image, manifestDesc ocispec.Descriptor) (*ImageManifest, error) {
    61  	if !containerdimages.IsManifestType(manifestDesc.MediaType) {
    62  		return nil, errNotManifest
    63  	}
    64  
    65  	parent := img.Target
    66  	img.Target = manifestDesc
    67  
    68  	c8dImg := containerd.NewImageWithPlatform(i.client, img, platforms.All)
    69  	return &ImageManifest{
    70  		Image:      c8dImg,
    71  		RealTarget: parent,
    72  	}, nil
    73  }
    74  
    75  func (im *ImageManifest) Metadata() containerdimages.Image {
    76  	md := im.Image.Metadata()
    77  	md.Target = im.RealTarget
    78  	return md
    79  }
    80  
    81  // IsPseudoImage returns false if the manifest has no layers or any of its layers is a known image layer.
    82  // Some manifests use the image media type for compatibility, even if they are not a real image.
    83  func (im *ImageManifest) IsPseudoImage(ctx context.Context) (bool, error) {
    84  	desc := im.Target()
    85  
    86  	// Quick check for buildkit attestation manifests
    87  	// https://github.com/moby/buildkit/blob/v0.11.4/docs/attestations/attestation-storage.md
    88  	// This would have also been caught by the layer check below, but it requires
    89  	// an additional content read and deserialization of Manifest.
    90  	if _, has := desc.Annotations[attestation.DockerAnnotationReferenceType]; has {
    91  		return true, nil
    92  	}
    93  
    94  	mfst, err := im.Manifest(ctx)
    95  	if err != nil {
    96  		return true, err
    97  	}
    98  	if len(mfst.Layers) == 0 {
    99  		return false, nil
   100  	}
   101  	for _, l := range mfst.Layers {
   102  		if images.IsLayerType(l.MediaType) {
   103  			return false, nil
   104  		}
   105  	}
   106  	return true, nil
   107  }
   108  
   109  func (im *ImageManifest) Manifest(ctx context.Context) (ocispec.Manifest, error) {
   110  	if im.manifest != nil {
   111  		return *im.manifest, nil
   112  	}
   113  
   114  	mfst, err := readManifest(ctx, im.ContentStore(), im.Target())
   115  	if err != nil {
   116  		return ocispec.Manifest{}, err
   117  	}
   118  
   119  	im.manifest = &mfst
   120  	return mfst, nil
   121  }
   122  
   123  func (im *ImageManifest) CheckContentAvailable(ctx context.Context) (bool, error) {
   124  	// The target is already a platform-specific manifest, so no need to match platform.
   125  	pm := platforms.All
   126  
   127  	available, _, _, missing, err := containerdimages.Check(ctx, im.ContentStore(), im.Target(), pm)
   128  	if err != nil {
   129  		return false, err
   130  	}
   131  
   132  	if !available || len(missing) > 0 {
   133  		return false, nil
   134  	}
   135  
   136  	return true, nil
   137  }
   138  
   139  func readManifest(ctx context.Context, store content.Provider, desc ocispec.Descriptor) (ocispec.Manifest, error) {
   140  	p, err := content.ReadBlob(ctx, store, desc)
   141  	if err != nil {
   142  		return ocispec.Manifest{}, err
   143  	}
   144  
   145  	var mfst ocispec.Manifest
   146  	if err := json.Unmarshal(p, &mfst); err != nil {
   147  		return ocispec.Manifest{}, err
   148  	}
   149  
   150  	return mfst, nil
   151  }