Skip to content

Commit ed0e734

Browse files
authored
Merge pull request #2468 from dmcgowan/set-platform-on-unpack
Update client Image to have configurable platform
2 parents 4249f44 + 3a916a0 commit ed0e734

7 files changed

Lines changed: 151 additions & 60 deletions

File tree

client.go

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import (
4747
leasesproxy "github.com/containerd/containerd/leases/proxy"
4848
"github.com/containerd/containerd/namespaces"
4949
"github.com/containerd/containerd/pkg/dialer"
50+
"github.com/containerd/containerd/platforms"
5051
"github.com/containerd/containerd/plugin"
5152
"github.com/containerd/containerd/remotes"
5253
"github.com/containerd/containerd/remotes/docker"
@@ -290,98 +291,137 @@ func defaultRemoteContext() *RemoteContext {
290291
}
291292
}
292293

294+
// Fetch downloads the provided content into containerd's content store
295+
// and returns a non-platform specific image reference
296+
func (c *Client) Fetch(ctx context.Context, ref string, opts ...RemoteOpt) (images.Image, error) {
297+
fetchCtx := defaultRemoteContext()
298+
for _, o := range opts {
299+
if err := o(c, fetchCtx); err != nil {
300+
return images.Image{}, err
301+
}
302+
}
303+
304+
if fetchCtx.Unpack {
305+
return images.Image{}, errors.New("unpack on fetch not supported, try pull")
306+
}
307+
308+
ctx, done, err := c.WithLease(ctx)
309+
if err != nil {
310+
return images.Image{}, err
311+
}
312+
defer done(ctx)
313+
314+
return c.fetch(ctx, fetchCtx, ref)
315+
}
316+
293317
// Pull downloads the provided content into containerd's content store
318+
// and returns a platform specific image object
294319
func (c *Client) Pull(ctx context.Context, ref string, opts ...RemoteOpt) (Image, error) {
295320
pullCtx := defaultRemoteContext()
296321
for _, o := range opts {
297322
if err := o(c, pullCtx); err != nil {
298323
return nil, err
299324
}
300325
}
301-
store := c.ContentStore()
326+
327+
if len(pullCtx.Platforms) > 1 {
328+
return nil, errors.New("cannot pull multiplatform image locally, try Fetch")
329+
} else if len(pullCtx.Platforms) == 0 {
330+
pullCtx.Platforms = []string{platforms.Default()}
331+
}
302332

303333
ctx, done, err := c.WithLease(ctx)
304334
if err != nil {
305335
return nil, err
306336
}
307337
defer done(ctx)
308338

309-
name, desc, err := pullCtx.Resolver.Resolve(ctx, ref)
339+
img, err := c.fetch(ctx, pullCtx, ref)
310340
if err != nil {
311-
return nil, errors.Wrapf(err, "failed to resolve reference %q", ref)
341+
return nil, err
312342
}
313343

314-
fetcher, err := pullCtx.Resolver.Fetcher(ctx, name)
344+
i := NewImageWithPlatform(c, img, pullCtx.Platforms[0])
345+
346+
if pullCtx.Unpack {
347+
if err := i.Unpack(ctx, pullCtx.Snapshotter); err != nil {
348+
return nil, errors.Wrapf(err, "failed to unpack image on snapshotter %s", pullCtx.Snapshotter)
349+
}
350+
}
351+
352+
return i, nil
353+
}
354+
355+
func (c *Client) fetch(ctx context.Context, rCtx *RemoteContext, ref string) (images.Image, error) {
356+
store := c.ContentStore()
357+
name, desc, err := rCtx.Resolver.Resolve(ctx, ref)
315358
if err != nil {
316-
return nil, errors.Wrapf(err, "failed to get fetcher for %q", name)
359+
return images.Image{}, errors.Wrapf(err, "failed to resolve reference %q", ref)
360+
}
361+
362+
fetcher, err := rCtx.Resolver.Fetcher(ctx, name)
363+
if err != nil {
364+
return images.Image{}, errors.Wrapf(err, "failed to get fetcher for %q", name)
317365
}
318366

319367
var (
320368
schema1Converter *schema1.Converter
321369
handler images.Handler
322370
)
323-
if desc.MediaType == images.MediaTypeDockerSchema1Manifest && pullCtx.ConvertSchema1 {
371+
if desc.MediaType == images.MediaTypeDockerSchema1Manifest && rCtx.ConvertSchema1 {
324372
schema1Converter = schema1.NewConverter(store, fetcher)
325-
handler = images.Handlers(append(pullCtx.BaseHandlers, schema1Converter)...)
373+
handler = images.Handlers(append(rCtx.BaseHandlers, schema1Converter)...)
326374
} else {
327375
// Get all the children for a descriptor
328376
childrenHandler := images.ChildrenHandler(store)
329377
// Set any children labels for that content
330378
childrenHandler = images.SetChildrenLabels(store, childrenHandler)
331379
// Filter children by platforms
332-
childrenHandler = images.FilterPlatforms(childrenHandler, pullCtx.Platforms...)
380+
childrenHandler = images.FilterPlatforms(childrenHandler, rCtx.Platforms...)
333381

334-
handler = images.Handlers(append(pullCtx.BaseHandlers,
382+
handler = images.Handlers(append(rCtx.BaseHandlers,
335383
remotes.FetchHandler(store, fetcher),
336384
childrenHandler,
337385
)...)
338386
}
339387

340388
if err := images.Dispatch(ctx, handler, desc); err != nil {
341-
return nil, err
389+
return images.Image{}, err
342390
}
343391
if schema1Converter != nil {
344392
desc, err = schema1Converter.Convert(ctx)
345393
if err != nil {
346-
return nil, err
394+
return images.Image{}, err
347395
}
348396
}
349397

350-
img := &image{
351-
client: c,
352-
i: images.Image{
353-
Name: name,
354-
Target: desc,
355-
Labels: pullCtx.Labels,
356-
},
357-
}
358-
359-
if pullCtx.Unpack {
360-
if err := img.Unpack(ctx, pullCtx.Snapshotter); err != nil {
361-
return nil, errors.Wrapf(err, "failed to unpack image on snapshotter %s", pullCtx.Snapshotter)
362-
}
398+
img := images.Image{
399+
Name: name,
400+
Target: desc,
401+
Labels: rCtx.Labels,
363402
}
364403

365404
is := c.ImageService()
366405
for {
367-
if created, err := is.Create(ctx, img.i); err != nil {
406+
if created, err := is.Create(ctx, img); err != nil {
368407
if !errdefs.IsAlreadyExists(err) {
369-
return nil, err
408+
return images.Image{}, err
370409
}
371410

372-
updated, err := is.Update(ctx, img.i)
411+
updated, err := is.Update(ctx, img)
373412
if err != nil {
374413
// if image was removed, try create again
375414
if errdefs.IsNotFound(err) {
376415
continue
377416
}
378-
return nil, err
417+
return images.Image{}, err
379418
}
380419

381-
img.i = updated
420+
img = updated
382421
} else {
383-
img.i = created
422+
img = created
384423
}
424+
385425
return img, nil
386426
}
387427
}

client_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func TestMain(m *testing.M) {
111111
}).Info("running tests against containerd")
112112

113113
// pull a seed image
114-
if _, err = client.Pull(ctx, testImage, WithPullUnpack, WithPlatform(platforms.Default())); err != nil {
114+
if _, err = client.Pull(ctx, testImage, WithPullUnpack); err != nil {
115115
ctrd.Stop()
116116
ctrd.Wait()
117117
fmt.Fprintf(os.Stderr, "%s: %s\n", err, buf.String())
@@ -201,11 +201,11 @@ func TestImagePullAllPlatforms(t *testing.T) {
201201
defer cancel()
202202

203203
cs := client.ContentStore()
204-
img, err := client.Pull(ctx, testImage)
204+
img, err := client.Fetch(ctx, testImage)
205205
if err != nil {
206206
t.Fatal(err)
207207
}
208-
index := img.Target()
208+
index := img.Target
209209
manifests, err := images.Children(ctx, cs, index)
210210
if err != nil {
211211
t.Fatal(err)
@@ -249,12 +249,12 @@ func TestImagePullSomePlatforms(t *testing.T) {
249249
opts = append(opts, WithPlatform(platform))
250250
}
251251

252-
img, err := client.Pull(ctx, "docker.io/library/busybox:latest", opts...)
252+
img, err := client.Fetch(ctx, "docker.io/library/busybox:latest", opts...)
253253
if err != nil {
254254
t.Fatal(err)
255255
}
256256

257-
index := img.Target()
257+
index := img.Target
258258
manifests, err := images.Children(ctx, cs, index)
259259
if err != nil {
260260
t.Fatal(err)

cmd/ctr/commands/content/fetch.go

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import (
3232
"github.com/containerd/containerd/images"
3333
"github.com/containerd/containerd/log"
3434
"github.com/containerd/containerd/pkg/progress"
35+
"github.com/containerd/containerd/platforms"
3536
"github.com/containerd/containerd/remotes"
3637
digest "github.com/opencontainers/go-digest"
3738
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
@@ -56,7 +57,16 @@ not use this implementation as a guide. The end goal should be having metadata,
5657
content and snapshots ready for a direct use via the 'ctr run'.
5758
5859
Most of this is experimental and there are few leaps to make this work.`,
59-
Flags: append(commands.RegistryFlags, commands.LabelFlag),
60+
Flags: append(commands.RegistryFlags, commands.LabelFlag,
61+
cli.StringSliceFlag{
62+
Name: "platform",
63+
Usage: "Pull content from a specific platform",
64+
},
65+
cli.BoolFlag{
66+
Name: "all-platforms",
67+
Usage: "pull content from all platforms",
68+
},
69+
),
6070
Action: func(clicontext *cli.Context) error {
6171
var (
6272
ref = clicontext.Args().First()
@@ -73,10 +83,10 @@ Most of this is experimental and there are few leaps to make this work.`,
7383
}
7484

7585
// Fetch loads all resources into the content store and returns the image
76-
func Fetch(ctx context.Context, client *containerd.Client, ref string, cliContext *cli.Context) (containerd.Image, error) {
86+
func Fetch(ctx context.Context, client *containerd.Client, ref string, cliContext *cli.Context) (images.Image, error) {
7787
resolver, err := commands.GetResolver(ctx, cliContext)
7888
if err != nil {
79-
return nil, err
89+
return images.Image{}, err
8090
}
8191

8292
ongoing := newJobs(ref)
@@ -109,15 +119,19 @@ func Fetch(ctx context.Context, client *containerd.Client, ref string, cliContex
109119
}
110120

111121
if !cliContext.Bool("all-platforms") {
112-
for _, platform := range cliContext.StringSlice("platform") {
122+
p := cliContext.StringSlice("platform")
123+
if len(p) == 0 {
124+
p = append(p, platforms.Default())
125+
}
126+
for _, platform := range p {
113127
opts = append(opts, containerd.WithPlatform(platform))
114128
}
115129
}
116130

117-
img, err := client.Pull(pctx, ref, opts...)
131+
img, err := client.Fetch(pctx, ref, opts...)
118132
stopProgress()
119133
if err != nil {
120-
return nil, err
134+
return images.Image{}, err
121135
}
122136

123137
<-progress

cmd/ctr/commands/images/pull.go

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@ package images
1919
import (
2020
"fmt"
2121

22+
"github.com/containerd/containerd"
2223
"github.com/containerd/containerd/cmd/ctr/commands"
2324
"github.com/containerd/containerd/cmd/ctr/commands/content"
25+
"github.com/containerd/containerd/images"
2426
"github.com/containerd/containerd/log"
2527
"github.com/containerd/containerd/platforms"
28+
"github.com/pkg/errors"
2629
"github.com/urfave/cli"
2730
)
2831

@@ -43,7 +46,7 @@ command. As part of this process, we do the following:
4346
cli.StringSliceFlag{
4447
Name: "platform",
4548
Usage: "Pull content from a specific platform",
46-
Value: &cli.StringSlice{platforms.Default()},
49+
Value: &cli.StringSlice{},
4750
},
4851
cli.BoolFlag{
4952
Name: "all-platforms",
@@ -78,11 +81,34 @@ command. As part of this process, we do the following:
7881
log.G(ctx).WithField("image", ref).Debug("unpacking")
7982

8083
// TODO: Show unpack status
81-
fmt.Printf("unpacking %s...\n", img.Target().Digest)
82-
err = img.Unpack(ctx, context.String("snapshotter"))
83-
if err == nil {
84-
fmt.Println("done")
84+
85+
var p []string
86+
if context.Bool("all-platforms") {
87+
all, err := images.Platforms(ctx, client.ContentStore(), img.Target)
88+
if err != nil {
89+
return errors.Wrap(err, "unable to resolve image platforms")
90+
}
91+
p = make([]string, len(all))
92+
for i := range all {
93+
p[i] = platforms.Format(all[i])
94+
}
95+
} else {
96+
p = context.StringSlice("platform")
97+
}
98+
if len(p) == 0 {
99+
p = append(p, platforms.Default())
85100
}
86-
return err
101+
102+
for _, platform := range p {
103+
fmt.Printf("unpacking %s %s...\n", platform, img.Target.Digest)
104+
i := containerd.NewImageWithPlatform(client, img, platform)
105+
err = i.Unpack(ctx, context.String("snapshotter"))
106+
if err != nil {
107+
return err
108+
}
109+
}
110+
111+
fmt.Println("done")
112+
return nil
87113
},
88114
}

export_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ func TestOCIExport(t *testing.T) {
4040
}
4141
defer client.Close()
4242

43-
pulled, err := client.Pull(ctx, testImage)
43+
pulled, err := client.Fetch(ctx, testImage)
4444
if err != nil {
4545
t.Fatal(err)
4646
}
47-
exportedStream, err := client.Export(ctx, &oci.V1Exporter{}, pulled.Target())
47+
exportedStream, err := client.Export(ctx, &oci.V1Exporter{}, pulled.Target)
4848
if err != nil {
4949
t.Fatal(err)
5050
}

0 commit comments

Comments
 (0)