1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17package images
18
19import (
20	"fmt"
21
22	"github.com/containerd/containerd"
23	"github.com/containerd/containerd/cmd/ctr/commands"
24	"github.com/containerd/containerd/cmd/ctr/commands/content"
25	"github.com/containerd/containerd/images"
26	"github.com/containerd/containerd/log"
27	"github.com/containerd/containerd/platforms"
28	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
29	"github.com/pkg/errors"
30	"github.com/urfave/cli"
31)
32
33var pullCommand = cli.Command{
34	Name:      "pull",
35	Usage:     "pull an image from a remote",
36	ArgsUsage: "[flags] <ref>",
37	Description: `Fetch and prepare an image for use in containerd.
38
39After pulling an image, it should be ready to use the same reference in a run
40command. As part of this process, we do the following:
41
421. Fetch all resources into containerd.
432. Prepare the snapshot filesystem with the pulled resources.
443. Register metadata for the image.
45`,
46	Flags: append(append(commands.RegistryFlags, append(commands.SnapshotterFlags, commands.LabelFlag)...),
47		cli.StringSliceFlag{
48			Name:  "platform",
49			Usage: "Pull content from a specific platform",
50			Value: &cli.StringSlice{},
51		},
52		cli.BoolFlag{
53			Name:  "all-platforms",
54			Usage: "pull content and metadata from all platforms",
55		},
56		cli.BoolFlag{
57			Name:  "all-metadata",
58			Usage: "Pull metadata for all platforms",
59		},
60	),
61	Action: func(context *cli.Context) error {
62		var (
63			ref = context.Args().First()
64		)
65		if ref == "" {
66			return fmt.Errorf("please provide an image reference to pull")
67		}
68
69		client, ctx, cancel, err := commands.NewClient(context)
70		if err != nil {
71			return err
72		}
73		defer cancel()
74
75		ctx, done, err := client.WithLease(ctx)
76		if err != nil {
77			return err
78		}
79		defer done(ctx)
80
81		config, err := content.NewFetchConfig(ctx, context)
82		if err != nil {
83			return err
84		}
85
86		img, err := content.Fetch(ctx, client, ref, config)
87		if err != nil {
88			return err
89		}
90
91		log.G(ctx).WithField("image", ref).Debug("unpacking")
92
93		// TODO: Show unpack status
94
95		var p []ocispec.Platform
96		if context.Bool("all-platforms") {
97			p, err = images.Platforms(ctx, client.ContentStore(), img.Target)
98			if err != nil {
99				return errors.Wrap(err, "unable to resolve image platforms")
100			}
101		} else {
102			for _, s := range context.StringSlice("platform") {
103				ps, err := platforms.Parse(s)
104				if err != nil {
105					return errors.Wrapf(err, "unable to parse platform %s", s)
106				}
107				p = append(p, ps)
108			}
109		}
110		if len(p) == 0 {
111			p = append(p, platforms.DefaultSpec())
112		}
113
114		for _, platform := range p {
115			fmt.Printf("unpacking %s %s...\n", platforms.Format(platform), img.Target.Digest)
116			i := containerd.NewImageWithPlatform(client, img, platforms.Only(platform))
117			err = i.Unpack(ctx, context.String("snapshotter"))
118			if err != nil {
119				return err
120			}
121		}
122
123		fmt.Println("done")
124		return nil
125	},
126}
127