Skip to content

Commit a5f9613

Browse files
authored
Merge pull request #3927 from katiewasnothere/snapshotter_check
add check that snapshotter supports the image platform when unpacking
2 parents 318e34b + f8992f4 commit a5f9613

7 files changed

Lines changed: 150 additions & 6 deletions

File tree

client.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
snapshotsapi "github.com/containerd/containerd/api/services/snapshots/v1"
4040
"github.com/containerd/containerd/api/services/tasks/v1"
4141
versionservice "github.com/containerd/containerd/api/services/version/v1"
42+
apitypes "github.com/containerd/containerd/api/types"
4243
"github.com/containerd/containerd/containers"
4344
"github.com/containerd/containerd/content"
4445
contentproxy "github.com/containerd/containerd/content/proxy"
@@ -782,3 +783,33 @@ func CheckRuntime(current, expected string) bool {
782783
}
783784
return true
784785
}
786+
787+
func (c *Client) GetSnapshotterSupportedPlatforms(ctx context.Context, snapshotterName string) (platforms.MatchComparer, error) {
788+
filters := []string{fmt.Sprintf("type==%s, id==%s", plugin.SnapshotPlugin, snapshotterName)}
789+
in := c.IntrospectionService()
790+
791+
resp, err := in.Plugins(ctx, filters)
792+
if err != nil {
793+
return nil, err
794+
}
795+
796+
if len(resp.Plugins) <= 0 {
797+
return nil, fmt.Errorf("inspection service could not find snapshotter %s plugin", snapshotterName)
798+
}
799+
800+
sn := resp.Plugins[0]
801+
snPlatforms := toPlatforms(sn.Platforms)
802+
return platforms.Any(snPlatforms...), nil
803+
}
804+
805+
func toPlatforms(pt []apitypes.Platform) []ocispec.Platform {
806+
platforms := make([]ocispec.Platform, len(pt))
807+
for i, p := range pt {
808+
platforms[i] = ocispec.Platform{
809+
Architecture: p.Architecture,
810+
OS: p.OS,
811+
Variant: p.Variant,
812+
}
813+
}
814+
return platforms
815+
}

client_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ func TestMain(m *testing.M) {
137137
}).Info("running tests against containerd")
138138

139139
// pull a seed image
140-
log.G(ctx).Info("start to pull seed image")
140+
log.G(ctx).WithField("image", testImage).Info("start to pull seed image")
141141
if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil {
142142
fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
143143
ctrd.Kill()

image.go

Lines changed: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package containerd
1818

1919
import (
2020
"context"
21+
"encoding/json"
2122
"fmt"
2223
"strings"
2324
"sync/atomic"
@@ -281,11 +282,22 @@ type UnpackConfig struct {
281282
ApplyOpts []diff.ApplyOpt
282283
// SnapshotOpts for configuring a snapshotter
283284
SnapshotOpts []snapshots.Opt
285+
// CheckPlatformSupported is whether to validate that a snapshotter
286+
// supports an image's platform before unpacking
287+
CheckPlatformSupported bool
284288
}
285289

286290
// UnpackOpt provides configuration for unpack
287291
type UnpackOpt func(context.Context, *UnpackConfig) error
288292

293+
// WithSnapshotterPlatformCheck sets `CheckPlatformSupported` on the UnpackConfig
294+
func WithSnapshotterPlatformCheck() UnpackOpt {
295+
return func(ctx context.Context, uc *UnpackConfig) error {
296+
uc.CheckPlatformSupported = true
297+
return nil
298+
}
299+
}
300+
289301
func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...UnpackOpt) error {
290302
ctx, done, err := i.client.WithLease(ctx)
291303
if err != nil {
@@ -300,7 +312,12 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...Unpa
300312
}
301313
}
302314

303-
layers, err := i.getLayers(ctx, i.platform)
315+
manifest, err := i.getManifest(ctx, i.platform)
316+
if err != nil {
317+
return err
318+
}
319+
320+
layers, err := i.getLayers(ctx, i.platform, manifest)
304321
if err != nil {
305322
return err
306323
}
@@ -320,6 +337,12 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...Unpa
320337
if err != nil {
321338
return err
322339
}
340+
if config.CheckPlatformSupported {
341+
if err := i.checkSnapshotterSupport(ctx, snapshotterName, manifest); err != nil {
342+
return err
343+
}
344+
}
345+
323346
for _, layer := range layers {
324347
unpacked, err = rootfs.ApplyLayerWithOpts(ctx, layer, chain, sn, a, config.SnapshotOpts, config.ApplyOpts)
325348
if err != nil {
@@ -361,14 +384,17 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string, opts ...Unpa
361384
return err
362385
}
363386

364-
func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer) ([]rootfs.Layer, error) {
365-
cs := i.client.ContentStore()
366-
387+
func (i *image) getManifest(ctx context.Context, platform platforms.MatchComparer) (ocispec.Manifest, error) {
388+
cs := i.ContentStore()
367389
manifest, err := images.Manifest(ctx, cs, i.i.Target, platform)
368390
if err != nil {
369-
return nil, err
391+
return ocispec.Manifest{}, err
370392
}
393+
return manifest, nil
394+
}
371395

396+
func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer, manifest ocispec.Manifest) ([]rootfs.Layer, error) {
397+
cs := i.ContentStore()
372398
diffIDs, err := i.i.RootFS(ctx, cs, platform)
373399
if err != nil {
374400
return nil, errors.Wrap(err, "failed to resolve rootfs")
@@ -388,6 +414,37 @@ func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer)
388414
return layers, nil
389415
}
390416

417+
func (i *image) getManifestPlatform(ctx context.Context, manifest ocispec.Manifest) (ocispec.Platform, error) {
418+
cs := i.ContentStore()
419+
p, err := content.ReadBlob(ctx, cs, manifest.Config)
420+
if err != nil {
421+
return ocispec.Platform{}, err
422+
}
423+
424+
var image ocispec.Image
425+
if err := json.Unmarshal(p, &image); err != nil {
426+
return ocispec.Platform{}, err
427+
}
428+
return platforms.Normalize(ocispec.Platform{OS: image.OS, Architecture: image.Architecture}), nil
429+
}
430+
431+
func (i *image) checkSnapshotterSupport(ctx context.Context, snapshotterName string, manifest ocispec.Manifest) error {
432+
snapshotterPlatformMatcher, err := i.client.GetSnapshotterSupportedPlatforms(ctx, snapshotterName)
433+
if err != nil {
434+
return err
435+
}
436+
437+
manifestPlatform, err := i.getManifestPlatform(ctx, manifest)
438+
if err != nil {
439+
return err
440+
}
441+
442+
if snapshotterPlatformMatcher.Match(manifestPlatform) {
443+
return nil
444+
}
445+
return fmt.Errorf("snapshotter %s does not support platform %s for image %s", snapshotterName, manifestPlatform, manifest.Config.Digest)
446+
}
447+
391448
func (i *image) ContentStore() content.Store {
392449
return i.client.ContentStore()
393450
}

image_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ func TestImageIsUnpacked(t *testing.T) {
8181
}
8282

8383
func TestImagePullWithDistSourceLabel(t *testing.T) {
84+
if runtime.GOOS == "windows" {
85+
t.Skip()
86+
}
8487
var (
8588
source = "docker.io"
8689
repoName = "library/busybox"
@@ -233,3 +236,39 @@ func TestImageUsage(t *testing.T) {
233236
t.Fatalf("Expected actual usage with snapshots to be greater: %d <= %d", s, s3)
234237
}
235238
}
239+
240+
func TestImageSupportedBySnapshotter_Error(t *testing.T) {
241+
var unsupportedImage string
242+
if runtime.GOOS == "windows" {
243+
unsupportedImage = "docker.io/library/busybox:latest"
244+
} else {
245+
unsupportedImage = "mcr.microsoft.com/windows/nanoserver:1809"
246+
}
247+
248+
ctx, cancel := testContext(t)
249+
defer cancel()
250+
251+
client, err := newClient(t, address)
252+
if err != nil {
253+
t.Fatal(err)
254+
}
255+
defer client.Close()
256+
257+
// Cleanup
258+
err = client.ImageService().Delete(ctx, unsupportedImage)
259+
if err != nil && !errdefs.IsNotFound(err) {
260+
t.Fatal(err)
261+
}
262+
263+
_, err = client.Pull(ctx, unsupportedImage,
264+
WithSchema1Conversion,
265+
WithPlatform(platforms.DefaultString()),
266+
WithPullSnapshotter(DefaultSnapshotter),
267+
WithPullUnpack,
268+
WithUnpackOpts([]UnpackOpt{WithSnapshotterPlatformCheck()}),
269+
)
270+
271+
if err == nil {
272+
t.Fatalf("expected unpacking %s for snapshotter %s to fail", unsupportedImage, DefaultSnapshotter)
273+
}
274+
}

services/introspection/introspection.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121

2222
api "github.com/containerd/containerd/api/services/introspection/v1"
2323
"github.com/containerd/containerd/errdefs"
24+
"github.com/containerd/containerd/log"
2425
ptypes "github.com/gogo/protobuf/types"
2526
)
2627

@@ -40,6 +41,7 @@ func NewIntrospectionServiceFromClient(c api.IntrospectionClient) Service {
4041
}
4142

4243
func (i *introspectionRemote) Plugins(ctx context.Context, filters []string) (*api.PluginsResponse, error) {
44+
log.G(ctx).WithField("filters", filters).Info("remote introspection plugin filters")
4345
resp, err := i.client.Plugins(ctx, &api.PluginsRequest{
4446
Filters: filters,
4547
})

snapshots/windows/windows.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ import (
3232
"github.com/containerd/containerd/errdefs"
3333
"github.com/containerd/containerd/log"
3434
"github.com/containerd/containerd/mount"
35+
"github.com/containerd/containerd/platforms"
3536
"github.com/containerd/containerd/plugin"
3637
"github.com/containerd/containerd/snapshots"
3738
"github.com/containerd/containerd/snapshots/storage"
3839
"github.com/containerd/continuity/fs"
40+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
3941
"github.com/pkg/errors"
4042
)
4143

@@ -44,6 +46,7 @@ func init() {
4446
Type: plugin.SnapshotPlugin,
4547
ID: "windows",
4648
InitFn: func(ic *plugin.InitContext) (interface{}, error) {
49+
ic.Meta.Platforms = []ocispec.Platform{platforms.DefaultSpec()}
4750
return NewSnapshotter(ic.Root)
4851
},
4952
})

unpacker.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/containerd/containerd/images"
3232
"github.com/containerd/containerd/log"
3333
"github.com/containerd/containerd/mount"
34+
"github.com/containerd/containerd/platforms"
3435
"github.com/containerd/containerd/snapshots"
3536
"github.com/opencontainers/go-digest"
3637
"github.com/opencontainers/image-spec/identity"
@@ -93,6 +94,17 @@ func (u *unpacker) unpack(
9394
return errors.Errorf("number of layers and diffIDs don't match: %d != %d", len(layers), len(diffIDs))
9495
}
9596

97+
if u.config.CheckPlatformSupported {
98+
imgPlatform := platforms.Normalize(ocispec.Platform{OS: i.OS, Architecture: i.Architecture})
99+
snapshotterPlatformMatcher, err := u.c.GetSnapshotterSupportedPlatforms(ctx, u.snapshotter)
100+
if err != nil {
101+
return errors.Wrapf(err, "failed to find supported platforms for snapshotter %s", u.snapshotter)
102+
}
103+
if !snapshotterPlatformMatcher.Match(imgPlatform) {
104+
return fmt.Errorf("snapshotter %s does not support platform %s for image %s", u.snapshotter, imgPlatform, config.Digest)
105+
}
106+
}
107+
96108
var (
97109
sn = u.c.SnapshotService(u.snapshotter)
98110
a = u.c.DiffService()

0 commit comments

Comments
 (0)