
Fixes pulling of multi-arch images by limiting the expansion of the index by filtering to the current default platform. Signed-off-by: Derek McGowan <derek@mcgstyle.net>
226 lines
6.7 KiB
Go
226 lines
6.7 KiB
Go
package images
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/content"
|
|
"github.com/containerd/containerd/errdefs"
|
|
"github.com/containerd/containerd/platforms"
|
|
digest "github.com/opencontainers/go-digest"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Image provides the model for how containerd views container images.
|
|
type Image struct {
|
|
Name string
|
|
Labels map[string]string
|
|
Target ocispec.Descriptor
|
|
CreatedAt, UpdatedAt time.Time
|
|
}
|
|
|
|
type Store interface {
|
|
Get(ctx context.Context, name string) (Image, error)
|
|
List(ctx context.Context, filters ...string) ([]Image, error)
|
|
Create(ctx context.Context, image Image) (Image, error)
|
|
|
|
// Update will replace the data in the store with the provided image. If
|
|
// one or more fieldpaths are provided, only those fields will be updated.
|
|
Update(ctx context.Context, image Image, fieldpaths ...string) (Image, error)
|
|
|
|
Delete(ctx context.Context, name string) error
|
|
}
|
|
|
|
// TODO(stevvooe): Many of these functions make strong platform assumptions,
|
|
// which are untrue in a lot of cases. More refactoring must be done here to
|
|
// make this work in all cases.
|
|
|
|
// Config resolves the image configuration descriptor.
|
|
//
|
|
// The caller can then use the descriptor to resolve and process the
|
|
// configuration of the image.
|
|
func (image *Image) Config(ctx context.Context, provider content.Provider, platform string) (ocispec.Descriptor, error) {
|
|
return Config(ctx, provider, image.Target, platform)
|
|
}
|
|
|
|
// RootFS returns the unpacked diffids that make up and images rootfs.
|
|
//
|
|
// These are used to verify that a set of layers unpacked to the expected
|
|
// values.
|
|
func (image *Image) RootFS(ctx context.Context, provider content.Provider, platform string) ([]digest.Digest, error) {
|
|
desc, err := image.Config(ctx, provider, platform)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return RootFS(ctx, provider, desc)
|
|
}
|
|
|
|
// Size returns the total size of an image's packed resources.
|
|
func (image *Image) Size(ctx context.Context, provider content.Provider, platform string) (int64, error) {
|
|
var size int64
|
|
return size, Walk(ctx, Handlers(HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
|
if desc.Size < 0 {
|
|
return nil, errors.Errorf("invalid size %v in %v (%v)", desc.Size, desc.Digest, desc.MediaType)
|
|
}
|
|
size += desc.Size
|
|
return nil, nil
|
|
}), ChildrenHandler(provider, platform)), image.Target)
|
|
}
|
|
|
|
func Manifest(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (ocispec.Manifest, error) {
|
|
var (
|
|
matcher platforms.Matcher
|
|
m *ocispec.Manifest
|
|
err error
|
|
)
|
|
if platform != "" {
|
|
matcher, err = platforms.Parse(platform)
|
|
if err != nil {
|
|
return ocispec.Manifest{}, err
|
|
}
|
|
}
|
|
|
|
if err := Walk(ctx, HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
|
switch desc.MediaType {
|
|
case MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
|
|
p, err := content.ReadBlob(ctx, provider, desc.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var manifest ocispec.Manifest
|
|
if err := json.Unmarshal(p, &manifest); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if platform != "" {
|
|
if desc.Platform != nil && !matcher.Match(*desc.Platform) {
|
|
return nil, nil
|
|
}
|
|
|
|
if desc.Platform == nil {
|
|
p, err := content.ReadBlob(ctx, provider, manifest.Config.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var image ocispec.Image
|
|
if err := json.Unmarshal(p, &image); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !matcher.Match(platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture})) {
|
|
return nil, nil
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
m = &manifest
|
|
|
|
return nil, nil
|
|
case MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
|
p, err := content.ReadBlob(ctx, provider, desc.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var idx ocispec.Index
|
|
if err := json.Unmarshal(p, &idx); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if platform == "" {
|
|
return idx.Manifests, nil
|
|
}
|
|
|
|
var descs []ocispec.Descriptor
|
|
for _, d := range idx.Manifests {
|
|
if d.Platform == nil || matcher.Match(*d.Platform) {
|
|
descs = append(descs, d)
|
|
}
|
|
}
|
|
|
|
return descs, nil
|
|
|
|
}
|
|
return nil, errors.New("could not resolve manifest")
|
|
}), image); err != nil {
|
|
return ocispec.Manifest{}, err
|
|
}
|
|
|
|
if m == nil {
|
|
return ocispec.Manifest{}, errors.Wrap(errdefs.ErrNotFound, "manifest not found")
|
|
}
|
|
|
|
return *m, nil
|
|
}
|
|
|
|
// Config resolves the image configuration descriptor using a content provided
|
|
// to resolve child resources on the image.
|
|
//
|
|
// The caller can then use the descriptor to resolve and process the
|
|
// configuration of the image.
|
|
func Config(ctx context.Context, provider content.Provider, image ocispec.Descriptor, platform string) (ocispec.Descriptor, error) {
|
|
manifest, err := Manifest(ctx, provider, image, platform)
|
|
if err != nil {
|
|
return ocispec.Descriptor{}, err
|
|
}
|
|
return manifest.Config, err
|
|
}
|
|
|
|
// Platforms returns one or more platforms supported by the image.
|
|
func Platforms(ctx context.Context, provider content.Provider, image ocispec.Descriptor) ([]ocispec.Platform, error) {
|
|
var platformSpecs []ocispec.Platform
|
|
return platformSpecs, Walk(ctx, Handlers(HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
|
if desc.Platform != nil {
|
|
platformSpecs = append(platformSpecs, *desc.Platform)
|
|
return nil, SkipDesc
|
|
}
|
|
|
|
switch desc.MediaType {
|
|
case MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig:
|
|
p, err := content.ReadBlob(ctx, provider, desc.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var image ocispec.Image
|
|
if err := json.Unmarshal(p, &image); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
platformSpecs = append(platformSpecs,
|
|
platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture}))
|
|
}
|
|
return nil, nil
|
|
}), ChildrenHandler(provider, "")), image)
|
|
}
|
|
|
|
// RootFS returns the unpacked diffids that make up and images rootfs.
|
|
//
|
|
// These are used to verify that a set of layers unpacked to the expected
|
|
// values.
|
|
func RootFS(ctx context.Context, provider content.Provider, configDesc ocispec.Descriptor) ([]digest.Digest, error) {
|
|
p, err := content.ReadBlob(ctx, provider, configDesc.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var config ocispec.Image
|
|
if err := json.Unmarshal(p, &config); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(stevvooe): Remove this bit when OCI structure uses correct type for
|
|
// rootfs.DiffIDs.
|
|
var diffIDs []digest.Digest
|
|
for _, diffID := range config.RootFS.DiffIDs {
|
|
diffIDs = append(diffIDs, digest.Digest(diffID))
|
|
}
|
|
|
|
return diffIDs, nil
|
|
}
|