mirror of
https://github.com/moby/moby.git
synced 2026-01-11 18:51:37 +00:00
Signed-off-by: Matthieu MOREL <matthieu.morel35@gmail.com> Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
278 lines
8.1 KiB
Go
278 lines
8.1 KiB
Go
package containerd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
containerd "github.com/containerd/containerd/v2/client"
|
|
"github.com/containerd/containerd/v2/core/content"
|
|
c8dimages "github.com/containerd/containerd/v2/core/images"
|
|
"github.com/containerd/containerd/v2/core/snapshots"
|
|
cerrdefs "github.com/containerd/errdefs"
|
|
"github.com/containerd/log"
|
|
"github.com/containerd/platforms"
|
|
"github.com/docker/docker/errdefs"
|
|
"github.com/moby/buildkit/util/attestation"
|
|
"github.com/opencontainers/image-spec/identity"
|
|
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 c8dimages.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 c8dimages.IsManifestType(desc.MediaType) {
|
|
return handleManifest(ctx, desc)
|
|
}
|
|
|
|
if c8dimages.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 c8dimages.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 c8dimages.IsManifestType(desc.MediaType) {
|
|
return handleManifest(ctx, desc)
|
|
}
|
|
|
|
if c8dimages.IsIndexType(desc.MediaType) {
|
|
return c8dimages.Walk(ctx, c8dimages.HandlerFunc(
|
|
func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
|
err := handleManifest(ctx, desc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
descs, err := c8dimages.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 c8dimages.Image, manifestDesc ocispec.Descriptor) (*ImageManifest, error) {
|
|
if !c8dimages.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() c8dimages.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 c8dimages.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 := c8dimages.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 (im *ImageManifest) ImagePlatform(ctx context.Context) (ocispec.Platform, error) {
|
|
target := im.Target()
|
|
if target.Platform != nil {
|
|
return *target.Platform, nil
|
|
}
|
|
|
|
var out ocispec.Platform
|
|
err := im.ReadConfig(ctx, &out)
|
|
return out, err
|
|
}
|
|
|
|
// ReadConfig gets the image config and unmarshals it into the provided struct.
|
|
// The provided struct should be a pointer to the config struct or its subset.
|
|
func (im *ImageManifest) ReadConfig(ctx context.Context, outConfig interface{}) error {
|
|
configDesc, err := im.Config(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return readJSON(ctx, im.ContentStore(), configDesc, outConfig)
|
|
}
|
|
|
|
// PresentContentSize returns the size of the image's content that is present in the content store.
|
|
func (im *ImageManifest) PresentContentSize(ctx context.Context) (int64, error) {
|
|
cs := im.ContentStore()
|
|
var size int64
|
|
err := c8dimages.Walk(ctx, presentChildrenHandler(cs, func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
|
size += desc.Size
|
|
return nil, nil
|
|
}), im.Target())
|
|
return size, err
|
|
}
|
|
|
|
// SnapshotUsage returns the disk usage of the image's snapshots.
|
|
func (im *ImageManifest) SnapshotUsage(ctx context.Context, snapshotter snapshots.Snapshotter) (snapshots.Usage, error) {
|
|
diffIDs, err := im.RootFS(ctx)
|
|
if err != nil {
|
|
return snapshots.Usage{}, errors.Wrapf(err, "failed to get rootfs of image %s", im.Name())
|
|
}
|
|
|
|
imageSnapshotID := identity.ChainID(diffIDs).String()
|
|
unpackedUsage, err := calculateSnapshotTotalUsage(ctx, snapshotter, imageSnapshotID)
|
|
if err != nil {
|
|
if cerrdefs.IsNotFound(err) {
|
|
return snapshots.Usage{Size: 0}, nil
|
|
}
|
|
log.G(ctx).WithError(err).WithFields(log.Fields{
|
|
"image": im.Name(),
|
|
"target": im.Target(),
|
|
"snapshotID": imageSnapshotID,
|
|
}).Warn("failed to calculate snapshot usage of image")
|
|
|
|
return snapshots.Usage{}, errors.Wrapf(err, "failed to calculate snapshot usage of image %s", im.Name())
|
|
}
|
|
return unpackedUsage, nil
|
|
}
|