diff --git a/cmd/ctr/utils.go b/cmd/ctr/utils.go index 32e8f3327..da1747bca 100644 --- a/cmd/ctr/utils.go +++ b/cmd/ctr/utils.go @@ -30,6 +30,7 @@ import ( "github.com/containerd/containerd/content" "github.com/containerd/containerd/images" "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes/docker" "github.com/containerd/containerd/rootfs" @@ -205,7 +206,7 @@ func getImageLayers(ctx gocontext.Context, image images.Image, cs content.Store) return nil, errors.Wrap(err, "failed to unmarshal manifest") } - diffIDs, err := image.RootFS(ctx, cs) + diffIDs, err := image.RootFS(ctx, cs, platforms.Format(platforms.Default())) if err != nil { return nil, errors.Wrap(err, "failed to resolve rootfs") } diff --git a/container_opts.go b/container_opts.go index 008adfca2..10b566e18 100644 --- a/container_opts.go +++ b/container_opts.go @@ -5,6 +5,7 @@ import ( "github.com/containerd/containerd/containers" "github.com/containerd/containerd/errdefs" + "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/typeurl" "github.com/gogo/protobuf/types" "github.com/opencontainers/image-spec/identity" @@ -79,7 +80,7 @@ func WithSnapshot(id string) NewContainerOpts { // root filesystem in read-write mode func WithNewSnapshot(id string, i Image) NewContainerOpts { return func(ctx context.Context, client *Client, c *containers.Container) error { - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Format(platforms.Default())) if err != nil { return err } @@ -108,7 +109,7 @@ func WithSnapshotCleanup(ctx context.Context, client *Client, c containers.Conta // root filesystem in read-only mode func WithNewSnapshotView(id string, i Image) NewContainerOpts { return func(ctx context.Context, client *Client, c *containers.Container) error { - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Format(platforms.Default())) if err != nil { return err } diff --git a/container_opts_unix.go b/container_opts_unix.go index 2e06b3a1c..8108f4b83 100644 --- a/container_opts_unix.go +++ b/container_opts_unix.go @@ -12,6 +12,7 @@ import ( "github.com/containerd/containerd/content" "github.com/containerd/containerd/errdefs" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" "github.com/gogo/protobuf/proto" protobuf "github.com/gogo/protobuf/types" digest "github.com/opencontainers/go-digest" @@ -38,7 +39,7 @@ func WithCheckpoint(desc v1.Descriptor, snapshotKey string) NewContainerOpts { fk := m rw = &fk case images.MediaTypeDockerSchema2Manifest: - config, err := images.Config(ctx, store, m) + config, err := images.Config(ctx, store, m, platforms.Format(platforms.Default())) if err != nil { return err } diff --git a/image.go b/image.go index bf725cba6..88f9b2e0f 100644 --- a/image.go +++ b/image.go @@ -2,10 +2,9 @@ package containerd import ( "context" - "encoding/json" - "github.com/containerd/containerd/content" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" "github.com/containerd/containerd/rootfs" digest "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" @@ -46,7 +45,7 @@ func (i *image) Target() ocispec.Descriptor { func (i *image) RootFS(ctx context.Context) ([]digest.Digest, error) { provider := i.client.ContentStore() - return i.i.RootFS(ctx, provider) + return i.i.RootFS(ctx, provider, platforms.Format(platforms.Default())) } func (i *image) Size(ctx context.Context) (int64, error) { @@ -56,11 +55,11 @@ func (i *image) Size(ctx context.Context) (int64, error) { func (i *image) Config(ctx context.Context) (ocispec.Descriptor, error) { provider := i.client.ContentStore() - return i.i.Config(ctx, provider) + return i.i.Config(ctx, provider, platforms.Format(platforms.Default())) } func (i *image) Unpack(ctx context.Context, snapshotterName string) error { - layers, err := i.getLayers(ctx) + layers, err := i.getLayers(ctx, platforms.Format(platforms.Default())) if err != nil { return err } @@ -98,19 +97,15 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string) error { return nil } -func (i *image) getLayers(ctx context.Context) ([]rootfs.Layer, error) { +func (i *image) getLayers(ctx context.Context, platform string) ([]rootfs.Layer, error) { cs := i.client.ContentStore() - // TODO: Support manifest list - p, err := content.ReadBlob(ctx, cs, i.i.Target.Digest) + manifest, err := images.Manifest(ctx, cs, i.i.Target, platform) if err != nil { - return nil, errors.Wrapf(err, "failed to read manifest blob") + return nil, errors.Wrap(err, "") } - var manifest ocispec.Manifest - if err := json.Unmarshal(p, &manifest); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal manifest") - } - diffIDs, err := i.i.RootFS(ctx, cs) + + diffIDs, err := i.i.RootFS(ctx, cs, platform) if err != nil { return nil, errors.Wrap(err, "failed to resolve rootfs") } diff --git a/images/image.go b/images/image.go index 652475cf4..674cf0874 100644 --- a/images/image.go +++ b/images/image.go @@ -6,6 +6,7 @@ import ( "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" @@ -40,16 +41,16 @@ type Store interface { // // 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) (ocispec.Descriptor, error) { - return Config(ctx, provider, image.Target) +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) ([]digest.Digest, error) { - desc, err := image.Config(ctx, provider) +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 } @@ -68,17 +69,23 @@ func (image *Image) Size(ctx context.Context, provider content.Provider) (int64, }), ChildrenHandler(provider)), image.Target) } -// 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) (ocispec.Descriptor, error) { - var configDesc ocispec.Descriptor - return configDesc, Walk(ctx, HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) { +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, image.Digest) + p, err := content.ReadBlob(ctx, provider, desc.Digest) if err != nil { return nil, err } @@ -88,14 +95,80 @@ func Config(ctx context.Context, provider content.Provider, image ocispec.Descri return nil, err } - configDesc = manifest.Config + 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 - default: - return nil, errors.New("could not resolve config") - } + case MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex: + p, err := content.ReadBlob(ctx, provider, desc.Digest) + if err != nil { + return nil, err + } - }), image) + 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. diff --git a/spec_opts_unix.go b/spec_opts_unix.go index ed2f038f0..b529cfb87 100644 --- a/spec_opts_unix.go +++ b/spec_opts_unix.go @@ -19,6 +19,7 @@ import ( "github.com/containerd/containerd/fs" "github.com/containerd/containerd/images" "github.com/containerd/containerd/namespaces" + "github.com/containerd/containerd/platforms" "github.com/opencontainers/image-spec/identity" "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runc/libcontainer/user" @@ -72,7 +73,7 @@ func WithImageConfig(i Image) SpecOpts { image = i.(*image) store = client.ContentStore() ) - ic, err := image.i.Config(ctx, store) + ic, err := image.i.Config(ctx, store, platforms.Format(platforms.Default())) if err != nil { return err } @@ -235,7 +236,7 @@ func WithRemappedSnapshotView(id string, i Image, uid, gid uint32) NewContainerO func withRemappedSnapshotBase(id string, i Image, uid, gid uint32, readonly bool) NewContainerOpts { return func(ctx context.Context, client *Client, c *containers.Container) error { - diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore()) + diffIDs, err := i.(*image).i.RootFS(ctx, client.ContentStore(), platforms.Format(platforms.Default())) if err != nil { return err } diff --git a/spec_opts_windows.go b/spec_opts_windows.go index 33ee74e3f..33aba1a9c 100644 --- a/spec_opts_windows.go +++ b/spec_opts_windows.go @@ -10,6 +10,7 @@ import ( "github.com/containerd/containerd/containers" "github.com/containerd/containerd/content" "github.com/containerd/containerd/images" + "github.com/containerd/containerd/platforms" "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/runtime-spec/specs-go" ) @@ -20,7 +21,7 @@ func WithImageConfig(i Image) SpecOpts { image = i.(*image) store = client.ContentStore() ) - ic, err := image.i.Config(ctx, store) + ic, err := image.i.Config(ctx, store, platforms.Format(platforms.Default())) if err != nil { return err }