| 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" |
| cerrdefs "github.com/containerd/errdefs" |
| "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. |
| 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 errors.Wrapf(errNotManifestOrIndex, "error walking manifest for %s", img.Name) |
| } |
| |
| // 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. |
| 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 |
| } |
| |
| descs, err := containerdimages.Children(ctx, i.content, desc) |
| if err != nil { |
| if cerrdefs.IsNotFound(err) { |
| return nil, nil |
| } |
| return nil, err |
| } |
| return descs, nil |
| }), desc) |
| } |
| |
| return errNotManifestOrIndex |
| } |
| |
| // ImageManifest implements the containerd.Image interface, but all operations |
| // act on the specific manifest instead of the index as opposed to the struct |
| // returned by containerd.NewImageWithPlatform. |
| 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 |
| } |
| |
| 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 when any of the below is true: |
| // - The manifest has no layers |
| // - None of its layers is a known image layer. |
| // - The manifest has unknown/unknown platform. |
| // |
| // 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) { |
| if im.IsAttestation() { |
| return true, nil |
| } |
| |
| plat := im.Target().Platform |
| if plat != nil { |
| if plat.OS == "unknown" && plat.Architecture == "unknown" { |
| 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", im.Target().Digest)) |
| } |
| 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 |
| } |
| |
| // 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 |
| } |