@@ -21,36 +21,33 @@ import (
21
21
"os"
22
22
23
23
"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"
27
26
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
28
27
"github.com/pkg/errors"
29
28
"github.com/urfave/cli"
30
29
)
31
30
32
31
var exportCommand = cli.Command {
33
32
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.
38
41
` ,
39
42
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" ,
49
46
},
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 {} ,
54
51
},
55
52
cli.BoolFlag {
56
53
Name : "all-platforms" ,
@@ -59,43 +56,47 @@ Currently, only OCI format is supported.
59
56
},
60
57
Action : func (context * cli.Context ) error {
61
58
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 {}
65
62
)
66
- if out == "" || local == "" {
63
+ if out == "" || len ( images ) == 0 {
67
64
return errors .New ("please provide both an output filename and an image reference to export" )
68
65
}
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
+
69
89
client , ctx , cancel , err := commands .NewClient (context )
70
90
if err != nil {
71
91
return err
72
92
}
73
93
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
- }
87
94
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 ))
98
98
}
99
+
99
100
var w io.WriteCloser
100
101
if out == "-" {
101
102
w = os .Stdout
@@ -105,32 +106,8 @@ Currently, only OCI format is supported.
105
106
return nil
106
107
}
107
108
}
109
+ defer w .Close ()
108
110
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 ... )
126
112
},
127
113
}
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