|
@@ -6,6 +6,7 @@ import (
|
|
|
|
|
|
"github.com/containerd/containerd"
|
|
|
"github.com/containerd/containerd/content"
|
|
|
+ cerrdefs "github.com/containerd/containerd/errdefs"
|
|
|
"github.com/containerd/containerd/images"
|
|
|
containerdimages "github.com/containerd/containerd/images"
|
|
|
"github.com/containerd/containerd/platforms"
|
|
@@ -48,6 +49,51 @@ func (i *ImageService) walkImageManifests(ctx context.Context, img containerdima
|
|
|
return errNotManifestOrIndex
|
|
|
}
|
|
|
|
|
|
+// 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.
|
|
|
+// The image implements the containerd.Image interface, but all operations act
|
|
|
+// on the specific manifest instead of the index.
|
|
|
+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
|
|
|
+ }
|
|
|
+
|
|
|
+ c, err := containerdimages.Children(ctx, i.content, desc)
|
|
|
+ if err != nil {
|
|
|
+ if cerrdefs.IsNotFound(err) {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ return c, nil
|
|
|
+ }), desc)
|
|
|
+ }
|
|
|
+
|
|
|
+ return errNotManifestOrIndex
|
|
|
+}
|
|
|
+
|
|
|
type ImageManifest struct {
|
|
|
containerd.Image
|
|
|
|
|
@@ -78,21 +124,31 @@ func (im *ImageManifest) Metadata() containerdimages.Image {
|
|
|
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 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 {
|
|
|
+ if im.IsAttestation() {
|
|
|
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", desc.Digest))
|
|
|
+ }
|
|
|
return true, err
|
|
|
}
|
|
|
if len(mfst.Layers) == 0 {
|
|
@@ -149,3 +205,21 @@ func readManifest(ctx context.Context, store content.Provider, desc ocispec.Desc
|
|
|
|
|
|
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
|
|
|
+}
|