add optional check that snapshotter supports the image platform when unpacking
Signed-off-by: Kathryn Baldauf <kabaldau@microsoft.com>
This commit is contained in:
parent
1e624fa3de
commit
f8992f451c
31
client.go
31
client.go
@ -39,6 +39,7 @@ import (
|
|||||||
snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1"
|
snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1"
|
||||||
"github.com/containerd/containerd/api/services/tasks/v1"
|
"github.com/containerd/containerd/api/services/tasks/v1"
|
||||||
versionservice "github.com/containerd/containerd/api/services/version/v1"
|
versionservice "github.com/containerd/containerd/api/services/version/v1"
|
||||||
|
apitypes "github.com/containerd/containerd/api/types"
|
||||||
"github.com/containerd/containerd/containers"
|
"github.com/containerd/containerd/containers"
|
||||||
"github.com/containerd/containerd/content"
|
"github.com/containerd/containerd/content"
|
||||||
contentproxy "github.com/containerd/containerd/content/proxy"
|
contentproxy "github.com/containerd/containerd/content/proxy"
|
||||||
@ -782,3 +783,33 @@ func CheckRuntime(current, expected string) bool {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetSnapshotterSupportedPlatforms(ctx context.Context, snapshotterName string) (platforms.MatchComparer, error) {
|
||||||
|
filters := []string{fmt.Sprintf("type==%s, id==%s", plugin.SnapshotPlugin, snapshotterName)}
|
||||||
|
in := c.IntrospectionService()
|
||||||
|
|
||||||
|
resp, err := in.Plugins(ctx, filters)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Plugins) <= 0 {
|
||||||
|
return nil, fmt.Errorf("inspection service could not find snapshotter %s plugin", snapshotterName)
|
||||||
|
}
|
||||||
|
|
||||||
|
sn := resp.Plugins[0]
|
||||||
|
snPlatforms := toPlatforms(sn.Platforms)
|
||||||
|
return platforms.Any(snPlatforms...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func toPlatforms(pt []apitypes.Platform) []ocispec.Platform {
|
||||||
|
platforms := make([]ocispec.Platform, len(pt))
|
||||||
|
for i, p := range pt {
|
||||||
|
platforms[i] = ocispec.Platform{
|
||||||
|
Architecture: p.Architecture,
|
||||||
|
OS: p.OS,
|
||||||
|
Variant: p.Variant,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return platforms
|
||||||
|
}
|
||||||
|
@ -137,7 +137,7 @@ func TestMain(m *testing.M) {
|
|||||||
}).Info("running tests against containerd")
|
}).Info("running tests against containerd")
|
||||||
|
|
||||||
// pull a seed image
|
// pull a seed image
|
||||||
log.G(ctx).Info("start to pull seed image")
|
log.G(ctx).WithField("image", testImage).Info("start to pull seed image")
|
||||||
if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil {
|
if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
|
fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
|
||||||
ctrd.Kill()
|
ctrd.Kill()
|
||||||
|
67
image.go
67
image.go
@ -18,6 +18,7 @@ package containerd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@ -281,11 +282,22 @@ type UnpackConfig struct {
|
|||||||
ApplyOpts []diff.ApplyOpt
|
ApplyOpts []diff.ApplyOpt
|
||||||
// SnapshotOpts for configuring a snapshotter
|
// SnapshotOpts for configuring a snapshotter
|
||||||
SnapshotOpts []snapshots.Opt
|
SnapshotOpts []snapshots.Opt
|
||||||
|
// CheckPlatformSupported is whether to validate that a snapshotter
|
||||||
|
// supports an image's platform before unpacking
|
||||||
|
CheckPlatformSupported bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnpackOpt provides configuration for unpack
|
// UnpackOpt provides configuration for unpack
|
||||||
type UnpackOpt func(context.Context, *UnpackConfig) error
|
type UnpackOpt func(context.Context, *UnpackConfig) error
|
||||||
|
|
||||||
|
// WithSnapshotterPlatformCheck sets `CheckPlatformSupported` on the UnpackConfig
|
||||||
|
func WithSnapshotterPlatformCheck() UnpackOpt {
|
||||||
|
return func(ctx context.Context, uc *UnpackConfig) error {
|
||||||
|
uc.CheckPlatformSupported = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...UnpackOpt) error {
|
func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...UnpackOpt) error {
|
||||||
ctx, done, err := i.client.WithLease(ctx)
|
ctx, done, err := i.client.WithLease(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -300,7 +312,12 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...Unpa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
layers, err := i.getLayers(ctx, i.platform)
|
manifest, err := i.getManifest(ctx, i.platform)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
layers, err := i.getLayers(ctx, i.platform, manifest)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -320,6 +337,12 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...Unpa
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if config.CheckPlatformSupported {
|
||||||
|
if err := i.checkSnapshotterSupport(ctx, snapshotterName, manifest); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for _, layer := range layers {
|
for _, layer := range layers {
|
||||||
unpacked, err = rootfs.ApplyLayerWithOpts(ctx, layer, chain, sn, a, config.SnapshotOpts, config.ApplyOpts)
|
unpacked, err = rootfs.ApplyLayerWithOpts(ctx, layer, chain, sn, a, config.SnapshotOpts, config.ApplyOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -361,14 +384,17 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...Unpa
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer) ([]rootfs.Layer, error) {
|
func (i *image) getManifest(ctx context.Context, platform platforms.MatchComparer) (ocispec.Manifest, error) {
|
||||||
cs := i.client.ContentStore()
|
cs := i.ContentStore()
|
||||||
|
|
||||||
manifest, err := images.Manifest(ctx, cs, i.i.Target, platform)
|
manifest, err := images.Manifest(ctx, cs, i.i.Target, platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return ocispec.Manifest{}, err
|
||||||
}
|
}
|
||||||
|
return manifest, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer, manifest ocispec.Manifest) ([]rootfs.Layer, error) {
|
||||||
|
cs := i.ContentStore()
|
||||||
diffIDs, err := i.i.RootFS(ctx, cs, platform)
|
diffIDs, err := i.i.RootFS(ctx, cs, platform)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to resolve rootfs")
|
return nil, errors.Wrap(err, "failed to resolve rootfs")
|
||||||
@ -388,6 +414,37 @@ func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer)
|
|||||||
return layers, nil
|
return layers, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *image) getManifestPlatform(ctx context.Context, manifest ocispec.Manifest) (ocispec.Platform, error) {
|
||||||
|
cs := i.ContentStore()
|
||||||
|
p, err := content.ReadBlob(ctx, cs, manifest.Config)
|
||||||
|
if err != nil {
|
||||||
|
return ocispec.Platform{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var image ocispec.Image
|
||||||
|
if err := json.Unmarshal(p, &image); err != nil {
|
||||||
|
return ocispec.Platform{}, err
|
||||||
|
}
|
||||||
|
return platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *image) checkSnapshotterSupport(ctx context.Context, snapshotterName string, manifest ocispec.Manifest) error {
|
||||||
|
snapshotterPlatformMatcher, err := i.client.GetSnapshotterSupportedPlatforms(ctx, snapshotterName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
manifestPlatform, err := i.getManifestPlatform(ctx, manifest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if snapshotterPlatformMatcher.Match(manifestPlatform) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return fmt.Errorf("snapshotter %s does not support platform %s for image %s", snapshotterName, manifestPlatform, manifest.Config.Digest)
|
||||||
|
}
|
||||||
|
|
||||||
func (i *image) ContentStore() content.Store {
|
func (i *image) ContentStore() content.Store {
|
||||||
return i.client.ContentStore()
|
return i.client.ContentStore()
|
||||||
}
|
}
|
||||||
|
@ -81,6 +81,9 @@ func TestImageIsUnpacked(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestImagePullWithDistSourceLabel(t *testing.T) {
|
func TestImagePullWithDistSourceLabel(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
var (
|
var (
|
||||||
source = "docker.io"
|
source = "docker.io"
|
||||||
repoName = "library/busybox"
|
repoName = "library/busybox"
|
||||||
@ -233,3 +236,39 @@ func TestImageUsage(t *testing.T) {
|
|||||||
t.Fatalf("Expected actual usage with snapshots to be greater: %d <= %d", s, s3)
|
t.Fatalf("Expected actual usage with snapshots to be greater: %d <= %d", s, s3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImageSupportedBySnapshotter_Error(t *testing.T) {
|
||||||
|
var unsupportedImage string
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
unsupportedImage = "docker.io/library/busybox:latest"
|
||||||
|
} else {
|
||||||
|
unsupportedImage = "mcr.microsoft.com/windows/nanoserver:1809"
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := testContext(t)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
client, err := newClient(t, address)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
err = client.ImageService().Delete(ctx, unsupportedImage)
|
||||||
|
if err != nil && !errdefs.IsNotFound(err) {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = client.Pull(ctx, unsupportedImage,
|
||||||
|
WithSchema1Conversion,
|
||||||
|
WithPlatform(platforms.DefaultString()),
|
||||||
|
WithPullSnapshotter(DefaultSnapshotter),
|
||||||
|
WithPullUnpack,
|
||||||
|
WithUnpackOpts([]UnpackOpt{WithSnapshotterPlatformCheck()}),
|
||||||
|
)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("expected unpacking %s for snapshotter %s to fail", unsupportedImage, DefaultSnapshotter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
api "github.com/containerd/containerd/api/services/introspection/v1"
|
api "github.com/containerd/containerd/api/services/introspection/v1"
|
||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
|
"github.com/containerd/containerd/log"
|
||||||
ptypes "github.com/gogo/protobuf/types"
|
ptypes "github.com/gogo/protobuf/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -40,6 +41,7 @@ func NewIntrospectionServiceFromClient(c api.IntrospectionClient) Service {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *introspectionRemote) Plugins(ctx context.Context, filters []string) (*api.PluginsResponse, error) {
|
func (i *introspectionRemote) Plugins(ctx context.Context, filters []string) (*api.PluginsResponse, error) {
|
||||||
|
log.G(ctx).WithField("filters", filters).Info("remote introspection plugin filters")
|
||||||
resp, err := i.client.Plugins(ctx, &api.PluginsRequest{
|
resp, err := i.client.Plugins(ctx, &api.PluginsRequest{
|
||||||
Filters: filters,
|
Filters: filters,
|
||||||
})
|
})
|
||||||
|
@ -32,10 +32,12 @@ import (
|
|||||||
"github.com/containerd/containerd/errdefs"
|
"github.com/containerd/containerd/errdefs"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/mount"
|
"github.com/containerd/containerd/mount"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
"github.com/containerd/containerd/snapshots"
|
"github.com/containerd/containerd/snapshots"
|
||||||
"github.com/containerd/containerd/snapshots/storage"
|
"github.com/containerd/containerd/snapshots/storage"
|
||||||
"github.com/containerd/continuity/fs"
|
"github.com/containerd/continuity/fs"
|
||||||
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -44,6 +46,7 @@ func init() {
|
|||||||
Type: plugin.SnapshotPlugin,
|
Type: plugin.SnapshotPlugin,
|
||||||
ID: "windows",
|
ID: "windows",
|
||||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||||
|
ic.Meta.Platforms = []ocispec.Platform{platforms.DefaultSpec()}
|
||||||
return NewSnapshotter(ic.Root)
|
return NewSnapshotter(ic.Root)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
12
unpacker.go
12
unpacker.go
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/containerd/containerd/images"
|
"github.com/containerd/containerd/images"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
"github.com/containerd/containerd/mount"
|
"github.com/containerd/containerd/mount"
|
||||||
|
"github.com/containerd/containerd/platforms"
|
||||||
"github.com/containerd/containerd/snapshots"
|
"github.com/containerd/containerd/snapshots"
|
||||||
"github.com/opencontainers/go-digest"
|
"github.com/opencontainers/go-digest"
|
||||||
"github.com/opencontainers/image-spec/identity"
|
"github.com/opencontainers/image-spec/identity"
|
||||||
@ -93,6 +94,17 @@ func (u *unpacker) unpack(
|
|||||||
return errors.Errorf("number of layers and diffIDs don't match: %d != %d", len(layers), len(diffIDs))
|
return errors.Errorf("number of layers and diffIDs don't match: %d != %d", len(layers), len(diffIDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if u.config.CheckPlatformSupported {
|
||||||
|
imgPlatform := platforms.Normalize(ocispec.Platform{OS: i.OS, Architecture: i.Architecture})
|
||||||
|
snapshotterPlatformMatcher, err := u.c.GetSnapshotterSupportedPlatforms(ctx, u.snapshotter)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to find supported platforms for snapshotter %s", u.snapshotter)
|
||||||
|
}
|
||||||
|
if !snapshotterPlatformMatcher.Match(imgPlatform) {
|
||||||
|
return fmt.Errorf("snapshotter %s does not support platform %s for image %s", u.snapshotter, imgPlatform, config.Digest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
sn = u.c.SnapshotService(u.snapshotter)
|
sn = u.c.SnapshotService(u.snapshotter)
|
||||||
a = u.c.DiffService()
|
a = u.c.DiffService()
|
||||||
|
Loading…
Reference in New Issue
Block a user