@@ -5,8 +5,10 @@ import (
5
5
"fmt"
6
6
"io"
7
7
"os"
8
+ "runtime"
8
9
"strings"
9
10
11
+ "github.com/containerd/containerd/platforms"
10
12
commonFlag "github.com/containers/common/pkg/flag"
11
13
"github.com/containers/common/pkg/retry"
12
14
"github.com/containers/image/v5/copy"
@@ -19,6 +21,8 @@ import (
19
21
"github.com/containers/image/v5/transports/alltransports"
20
22
encconfig "github.com/containers/ocicrypt/config"
21
23
enchelpers "github.com/containers/ocicrypt/helpers"
24
+ "github.com/opencontainers/go-digest"
25
+ imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
22
26
"github.com/spf13/cobra"
23
27
)
24
28
@@ -99,24 +103,122 @@ See skopeo(1) section "IMAGE NAMES" for the expected format
99
103
}
100
104
101
105
// parseMultiArch parses the list processing selection
102
- // It returns the copy.ImageListSelection to use with image.Copy option
103
- func parseMultiArch (multiArch string ) (copy.ImageListSelection , error ) {
104
- switch multiArch {
105
- case "system" :
106
- return copy .CopySystemImage , nil
107
- case "all" :
108
- return copy .CopyAllImages , nil
109
- // There is no CopyNoImages value in copy.ImageListSelection, but because we
110
- // don't provide an option to select a set of images to copy, we can use
111
- // CopySpecificImages.
112
- case "index-only" :
113
- return copy .CopySpecificImages , nil
114
- // We don't expose CopySpecificImages other than index-only above, because
115
- // we currently don't provide an option to choose the images to copy. That
116
- // could be added in the future.
106
+ // It returns the copy.ImageListSelection, instance list, and platform list to use in image.Copy option
107
+ func parseMultiArch (globalOptions * globalOptions , multiArch string ) (copy.ImageListSelection , []digest.Digest , []imgspecv1.Platform , error ) {
108
+ switch {
109
+ case multiArch == "system" :
110
+ return copy .CopySystemImage , nil , nil , nil
111
+ case multiArch == "all" :
112
+ return copy .CopyAllImages , nil , nil , nil
113
+ case multiArch == "index-only" :
114
+ // There is no CopyNoImages value in copy.ImageListSelection per se, but
115
+ // we can get the desired effect by using CopySpecificImages.
116
+ return copy .CopySpecificImages , nil , nil , nil
117
+ case strings .HasPrefix (multiArch , "sparse:" ):
118
+ sparseArg := strings .TrimPrefix (multiArch , "sparse:" )
119
+ platformList , instanceList , err := parseMultiArchSparse (globalOptions , sparseArg )
120
+ if err != nil {
121
+ return copy .CopySpecificImages , nil , nil , err
122
+ }
123
+ return copy .CopySpecificImages , instanceList , platformList , nil
117
124
default :
118
- return copy .CopySystemImage , fmt .Errorf ("unknown multi-arch option %q. Choose one of the supported options: 'system', 'all', or 'index-only'" , multiArch )
125
+ return copy .CopySystemImage , nil , nil , fmt .Errorf ("unknown multi-arch option %q. Choose one of the supported options: 'system', 'sparse:...', 'all', or 'index-only'" , multiArch )
126
+ }
127
+ }
128
+
129
+ func parseMultiArchSparse (globalOptions * globalOptions , sparseArg string ) ([]imgspecv1.Platform , []digest.Digest , error ) {
130
+ var platformList []imgspecv1.Platform
131
+ var instanceList []digest.Digest
132
+ remainder := "," + sparseArg
133
+ for remainder != "" {
134
+ parseArg := func (argStart string , argEnd string , fn func (argVal string ) (bool , error )) (bool , error ) {
135
+ if newRemainder , isArg := strings .CutPrefix (remainder , "," + argStart ); isArg {
136
+ if ! isArg {
137
+ return false , nil
138
+ }
139
+ var argSpec string
140
+ if argEnd != "" {
141
+ var foundArgEnd bool
142
+ argSpec , newRemainder , foundArgEnd = strings .Cut (newRemainder , argEnd )
143
+ if ! foundArgEnd {
144
+ return false , fmt .Errorf ("--multi-arch=sparse:%s: end of argument marker %s not found" , argStart , argEnd )
145
+ }
146
+ }
147
+ handled , err := fn (argSpec )
148
+ if err != nil {
149
+ return false , err
150
+ }
151
+ if handled {
152
+ remainder = newRemainder
153
+ return true , nil
154
+ }
155
+ return false , nil
156
+ }
157
+ return false , nil
158
+ }
159
+ if isSystem , err := parseArg ("system" , "" , func (string ) (bool , error ) {
160
+ systemPlatform := imgspecv1.Platform {
161
+ OS : globalOptions .overrideOS ,
162
+ Architecture : globalOptions .overrideArch ,
163
+ Variant : globalOptions .overrideVariant ,
164
+ }
165
+ platformList = append (platformList , systemPlatform )
166
+ return true , nil
167
+ }); err != nil {
168
+ return nil , nil , err
169
+ } else if isSystem {
170
+ continue
171
+ }
172
+ if isDigest , err := parseArg ("digest=[" , "]" , func (digestSpecList string ) (bool , error ) {
173
+ for _ , digestSpec := range strings .Split (digestSpecList , "," ) {
174
+ instanceDigest , err := digest .Parse (digestSpec )
175
+ if err != nil {
176
+ return false , fmt .Errorf ("while parsing instance digest %q: %w" , digestSpec , err )
177
+ }
178
+ instanceList = append (instanceList , instanceDigest )
179
+ }
180
+ return true , nil
181
+ }); err != nil {
182
+ return nil , nil , err
183
+ } else if isDigest {
184
+ continue
185
+ }
186
+ if isArch , err := parseArg ("arch=[" , "]" , func (archSpecList string ) (bool , error ) {
187
+ wantedOS := runtime .GOOS
188
+ if globalOptions .overrideOS != "" {
189
+ wantedOS = globalOptions .overrideOS
190
+ }
191
+ for _ , archSpec := range strings .Split (archSpecList , "," ) {
192
+ p := strings .SplitN (archSpec , "/" , 2 )
193
+ if len (p ) > 1 {
194
+ platformList = append (platformList , imgspecv1.Platform {OS : wantedOS , Architecture : p [0 ], Variant : p [1 ]})
195
+ } else {
196
+ platformList = append (platformList , imgspecv1.Platform {OS : wantedOS , Architecture : p [0 ]})
197
+ }
198
+ }
199
+ return true , nil
200
+ }); err != nil {
201
+ return nil , nil , err
202
+ } else if isArch {
203
+ continue
204
+ }
205
+ if isPlatform , err := parseArg ("platform=[" , "]" , func (platformSpecList string ) (bool , error ) {
206
+ for _ , platformSpec := range strings .Split (platformSpecList , "," ) {
207
+ p , err := platforms .Parse (platformSpec )
208
+ if err != nil {
209
+ return false , fmt .Errorf ("while parsing platform specifier %q: %w" , platformSpec , err )
210
+ }
211
+ platformList = append (platformList , p )
212
+ }
213
+ return true , nil
214
+ }); err != nil {
215
+ return nil , nil , err
216
+ } else if isPlatform {
217
+ continue
218
+ }
219
+ return nil , nil , fmt .Errorf ("--multi-arch=sparse: unrecognized value %q" , strings .TrimPrefix (remainder , "," ))
119
220
}
221
+ return platformList , instanceList , nil
120
222
}
121
223
122
224
func (opts * copyOptions ) run (args []string , stdout io.Writer ) (retErr error ) {
@@ -186,11 +288,13 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) (retErr error) {
186
288
}
187
289
188
290
imageListSelection := copy .CopySystemImage
291
+ var instanceDigests []digest.Digest
292
+ var instancePlatforms []imgspecv1.Platform
189
293
if opts .multiArch .Present () && opts .all {
190
294
return fmt .Errorf ("Cannot use --all and --multi-arch flags together" )
191
295
}
192
296
if opts .multiArch .Present () {
193
- imageListSelection , err = parseMultiArch (opts .multiArch .Value ())
297
+ imageListSelection , instanceDigests , instancePlatforms , err = parseMultiArch (opts . global , opts .multiArch .Value ())
194
298
if err != nil {
195
299
return err
196
300
}
@@ -296,6 +400,8 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) (retErr error) {
296
400
DestinationCtx : destinationCtx ,
297
401
ForceManifestMIMEType : manifestType ,
298
402
ImageListSelection : imageListSelection ,
403
+ Instances : instanceDigests ,
404
+ InstancePlatforms : instancePlatforms ,
299
405
PreserveDigests : opts .preserveDigests ,
300
406
OciDecryptConfig : decConfig ,
301
407
OciEncryptLayers : encLayers ,
0 commit comments