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	"io"
22	"os"
23	"time"
24
25	"github.com/containerd/containerd"
26	"github.com/containerd/containerd/cmd/ctr/commands"
27	"github.com/containerd/containerd/images/archive"
28	"github.com/containerd/containerd/log"
29	"github.com/urfave/cli"
30)
31
32var importCommand = cli.Command{
33	Name:      "import",
34	Usage:     "import images",
35	ArgsUsage: "[flags] <in>",
36	Description: `Import images from a tar stream.
37Implemented formats:
38- oci.v1
39- docker.v1.1
40- docker.v1.2
41
42
43For OCI v1, you may need to specify --base-name because an OCI archive may
44contain only partial image references (tags without the base image name).
45If no base image name is provided, a name will be generated as "import-%{yyyy-MM-dd}".
46
47e.g.
48  $ ctr images import --base-name foo/bar foobar.tar
49
50If foobar.tar contains an OCI ref named "latest" and anonymous ref "sha256:deadbeef", the command will create
51"foo/bar:latest" and "foo/bar@sha256:deadbeef" images in the containerd store.
52`,
53	Flags: append([]cli.Flag{
54		cli.StringFlag{
55			Name:  "base-name",
56			Value: "",
57			Usage: "base image name for added images, when provided only images with this name prefix are imported",
58		},
59		cli.BoolFlag{
60			Name:  "digests",
61			Usage: "whether to create digest images (default: false)",
62		},
63		cli.StringFlag{
64			Name:  "index-name",
65			Usage: "image name to keep index as, by default index is discarded",
66		},
67		cli.BoolFlag{
68			Name:  "all-platforms",
69			Usage: "imports content for all platforms, false by default",
70		},
71		cli.BoolFlag{
72			Name:  "no-unpack",
73			Usage: "skip unpacking the images, false by default",
74		},
75		cli.BoolFlag{
76			Name:  "compress-blobs",
77			Usage: "compress uncompressed blobs when creating manifest (Docker format only)",
78		},
79	}, commands.SnapshotterFlags...),
80
81	Action: func(context *cli.Context) error {
82		var (
83			in   = context.Args().First()
84			opts []containerd.ImportOpt
85		)
86
87		prefix := context.String("base-name")
88		if prefix == "" {
89			prefix = fmt.Sprintf("import-%s", time.Now().Format("2006-01-02"))
90			opts = append(opts, containerd.WithImageRefTranslator(archive.AddRefPrefix(prefix)))
91		} else {
92			// When provided, filter out references which do not match
93			opts = append(opts, containerd.WithImageRefTranslator(archive.FilterRefPrefix(prefix)))
94		}
95
96		if context.Bool("digests") {
97			opts = append(opts, containerd.WithDigestRef(archive.DigestTranslator(prefix)))
98		}
99
100		if idxName := context.String("index-name"); idxName != "" {
101			opts = append(opts, containerd.WithIndexName(idxName))
102		}
103
104		if context.Bool("compress-blobs") {
105			opts = append(opts, containerd.WithImportCompression())
106		}
107
108		opts = append(opts, containerd.WithAllPlatforms(context.Bool("all-platforms")))
109
110		client, ctx, cancel, err := commands.NewClient(context)
111		if err != nil {
112			return err
113		}
114		defer cancel()
115
116		var r io.ReadCloser
117		if in == "-" {
118			r = os.Stdin
119		} else {
120			r, err = os.Open(in)
121			if err != nil {
122				return err
123			}
124		}
125		imgs, err := client.Import(ctx, r, opts...)
126		closeErr := r.Close()
127		if err != nil {
128			return err
129		}
130		if closeErr != nil {
131			return closeErr
132		}
133
134		if !context.Bool("no-unpack") {
135			log.G(ctx).Debugf("unpacking %d images", len(imgs))
136
137			for _, img := range imgs {
138				// TODO: Allow configuration of the platform
139				image := containerd.NewImage(client, img)
140
141				// TODO: Show unpack status
142				fmt.Printf("unpacking %s (%s)...", img.Name, img.Target.Digest)
143				err = image.Unpack(ctx, context.String("snapshotter"))
144				if err != nil {
145					return err
146				}
147				fmt.Println("done")
148			}
149		}
150		return nil
151	},
152}
153