@@ -21,36 +21,33 @@ import (
2121 "os"
2222
2323 "github.com/containerd/containerd/cmd/ctr/commands"
24- "github.com/containerd/containerd/images/oci"
25- "github.com/containerd/containerd/reference"
26- digest "github.com/opencontainers/go-digest"
24+ "github.com/containerd/containerd/images/archive"
25+ "github.com/containerd/containerd/platforms"
2726 ocispec "github.com/opencontainers/image-spec/specs-go/v1"
2827 "github.com/pkg/errors"
2928 "github.com/urfave/cli"
3029)
3130
3231var exportCommand = cli.Command {
3332 Name : "export" ,
34- Usage : "export an image" ,
35- ArgsUsage : "[flags] <out> <image>" ,
36- Description : `Export an image to a tar stream.
37- Currently, only OCI format is supported.
33+ Usage : "export images" ,
34+ ArgsUsage : "[flags] <out> <image> ..." ,
35+ Description : `Export images to an OCI tar archive.
36+
37+ Tar output is formatted as an OCI archive, a Docker manifest is provided for the platform.
38+ Use '--skip-manifest-json' to avoid including the Docker manifest.json file.
39+ Use '--platform' to define the output platform.
40+ When '--all-platforms' is given all images in a manifest list must be available.
3841` ,
3942 Flags : []cli.Flag {
40- // TODO(AkihiroSuda): make this map[string]string as in moby/moby#33355?
41- cli.StringFlag {
42- Name : "oci-ref-name" ,
43- Value : "" ,
44- Usage : "override org.opencontainers.image.ref.name annotation" ,
45- },
46- cli.StringFlag {
47- Name : "manifest" ,
48- Usage : "digest of manifest" ,
43+ cli.BoolFlag {
44+ Name : "skip-manifest-json" ,
45+ Usage : "do not add Docker compatible manifest.json to archive" ,
4946 },
50- cli.StringFlag {
51- Name : "manifest-type " ,
52- Usage : "media type of manifest digest " ,
53- Value : ocispec . MediaTypeImageManifest ,
47+ cli.StringSliceFlag {
48+ Name : "platform " ,
49+ Usage : "Pull content from a specific platform " ,
50+ Value : & cli. StringSlice {} ,
5451 },
5552 cli.BoolFlag {
5653 Name : "all-platforms" ,
@@ -59,43 +56,47 @@ Currently, only OCI format is supported.
5956 },
6057 Action : func (context * cli.Context ) error {
6158 var (
62- out = context .Args ().First ()
63- local = context .Args ().Get ( 1 )
64- desc ocispec. Descriptor
59+ out = context .Args ().First ()
60+ images = context .Args ().Tail ( )
61+ exportOpts = []archive. ExportOpt {}
6562 )
66- if out == "" || local == "" {
63+ if out == "" || len ( images ) == 0 {
6764 return errors .New ("please provide both an output filename and an image reference to export" )
6865 }
66+
67+ if pss := context .StringSlice ("platform" ); len (pss ) > 0 {
68+ var all []ocispec.Platform
69+ for _ , ps := range pss {
70+ p , err := platforms .Parse (ps )
71+ if err != nil {
72+ return errors .Wrapf (err , "invalid platform %q" , ps )
73+ }
74+ all = append (all , p )
75+ }
76+ exportOpts = append (exportOpts , archive .WithPlatform (platforms .Ordered (all ... )))
77+ } else {
78+ exportOpts = append (exportOpts , archive .WithPlatform (platforms .Default ()))
79+ }
80+
81+ if context .Bool ("all-platforms" ) {
82+ exportOpts = append (exportOpts , archive .WithAllPlatforms ())
83+ }
84+
85+ if context .Bool ("skip-manifest-json" ) {
86+ exportOpts = append (exportOpts , archive .WithSkipDockerManifest ())
87+ }
88+
6989 client , ctx , cancel , err := commands .NewClient (context )
7090 if err != nil {
7191 return err
7292 }
7393 defer cancel ()
74- if manifest := context .String ("manifest" ); manifest != "" {
75- desc .Digest , err = digest .Parse (manifest )
76- if err != nil {
77- return errors .Wrap (err , "invalid manifest digest" )
78- }
79- desc .MediaType = context .String ("manifest-type" )
80- } else {
81- img , err := client .ImageService ().Get (ctx , local )
82- if err != nil {
83- return errors .Wrap (err , "unable to resolve image to manifest" )
84- }
85- desc = img .Target
86- }
8794
88- if desc .Annotations == nil {
89- desc .Annotations = make (map [string ]string )
90- }
91- if s , ok := desc .Annotations [ocispec .AnnotationRefName ]; ! ok || s == "" {
92- if ociRefName := determineOCIRefName (local ); ociRefName != "" {
93- desc .Annotations [ocispec .AnnotationRefName ] = ociRefName
94- }
95- if ociRefName := context .String ("oci-ref-name" ); ociRefName != "" {
96- desc .Annotations [ocispec .AnnotationRefName ] = ociRefName
97- }
95+ is := client .ImageService ()
96+ for _ , img := range images {
97+ exportOpts = append (exportOpts , archive .WithImage (is , img ))
9898 }
99+
99100 var w io.WriteCloser
100101 if out == "-" {
101102 w = os .Stdout
@@ -105,32 +106,8 @@ Currently, only OCI format is supported.
105106 return nil
106107 }
107108 }
109+ defer w .Close ()
108110
109- var (
110- exportOpts []oci.V1ExporterOpt
111- )
112-
113- exportOpts = append (exportOpts , oci .WithAllPlatforms (context .Bool ("all-platforms" )))
114-
115- r , err := client .Export (ctx , desc , exportOpts ... )
116- if err != nil {
117- return err
118- }
119- if _ , err := io .Copy (w , r ); err != nil {
120- return err
121- }
122- if err := w .Close (); err != nil {
123- return err
124- }
125- return r .Close ()
111+ return client .Export (ctx , w , exportOpts ... )
126112 },
127113}
128-
129- func determineOCIRefName (local string ) string {
130- refspec , err := reference .Parse (local )
131- if err != nil {
132- return ""
133- }
134- tag , _ := reference .SplitObject (refspec .Object )
135- return tag
136- }
0 commit comments