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"
|
||||
"github.com/containerd/containerd/api/services/tasks/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/content"
|
||||
contentproxy "github.com/containerd/containerd/content/proxy"
|
||||
@ -782,3 +783,33 @@ func CheckRuntime(current, expected string) bool {
|
||||
}
|
||||
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")
|
||||
|
||||
// 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 {
|
||||
fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
|
||||
ctrd.Kill()
|
||||
|
67
image.go
67
image.go
@ -18,6 +18,7 @@ package containerd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@ -281,11 +282,22 @@ type UnpackConfig struct {
|
||||
ApplyOpts []diff.ApplyOpt
|
||||
// SnapshotOpts for configuring a snapshotter
|
||||
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
|
||||
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 {
|
||||
ctx, done, err := i.client.WithLease(ctx)
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
@ -320,6 +337,12 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...Unpa
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config.CheckPlatformSupported {
|
||||
if err := i.checkSnapshotterSupport(ctx, snapshotterName, manifest); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, layer := range layers {
|
||||
unpacked, err = rootfs.ApplyLayerWithOpts(ctx, layer, chain, sn, a, config.SnapshotOpts, config.ApplyOpts)
|
||||
if err != nil {
|
||||
@ -361,14 +384,17 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...Unpa
|
||||
return err
|
||||
}
|
||||
|
||||
func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer) ([]rootfs.Layer, error) {
|
||||
cs := i.client.ContentStore()
|
||||
|
||||
func (i *image) getManifest(ctx context.Context, platform platforms.MatchComparer) (ocispec.Manifest, error) {
|
||||
cs := i.ContentStore()
|
||||
manifest, err := images.Manifest(ctx, cs, i.i.Target, platform)
|
||||
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)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return i.client.ContentStore()
|
||||
}
|
||||
|
@ -81,6 +81,9 @@ func TestImageIsUnpacked(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestImagePullWithDistSourceLabel(t *testing.T) {
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip()
|
||||
}
|
||||
var (
|
||||
source = "docker.io"
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/log"
|
||||
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) {
|
||||
log.G(ctx).WithField("filters", filters).Info("remote introspection plugin filters")
|
||||
resp, err := i.client.Plugins(ctx, &api.PluginsRequest{
|
||||
Filters: filters,
|
||||
})
|
||||
|
@ -32,10 +32,12 @@ import (
|
||||
"github.com/containerd/containerd/errdefs"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/containerd/plugin"
|
||||
"github.com/containerd/containerd/snapshots"
|
||||
"github.com/containerd/containerd/snapshots/storage"
|
||||
"github.com/containerd/continuity/fs"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -44,6 +46,7 @@ func init() {
|
||||
Type: plugin.SnapshotPlugin,
|
||||
ID: "windows",
|
||||
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
|
||||
ic.Meta.Platforms = []ocispec.Platform{platforms.DefaultSpec()}
|
||||
return NewSnapshotter(ic.Root)
|
||||
},
|
||||
})
|
||||
|
12
unpacker.go
12
unpacker.go
@ -31,6 +31,7 @@ import (
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/log"
|
||||
"github.com/containerd/containerd/mount"
|
||||
"github.com/containerd/containerd/platforms"
|
||||
"github.com/containerd/containerd/snapshots"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"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))
|
||||
}
|
||||
|
||||
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 (
|
||||
sn = u.c.SnapshotService(u.snapshotter)
|
||||
a = u.c.DiffService()
|
||||
|
Loading…
Reference in New Issue
Block a user