Skip to content

Commit d494e22

Browse files
committed
copy: add --multi-arch=sparse:...
Add a "sparse:..." value for the copy --multi-arch option, where the "..." is a comma-separated list of "system", "arch=[...]", "digest=[]", and "platform=[]" lists. Signed-off-by: Nalin Dahyabhai <[email protected]>
1 parent 9459680 commit d494e22

22 files changed

+2068
-20
lines changed

cmd/skopeo/copy.go

Lines changed: 123 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ import (
55
"fmt"
66
"io"
77
"os"
8+
"runtime"
89
"strings"
910

11+
"github.com/containerd/containerd/platforms"
1012
commonFlag "github.com/containers/common/pkg/flag"
1113
"github.com/containers/common/pkg/retry"
1214
"github.com/containers/image/v5/copy"
@@ -19,6 +21,8 @@ import (
1921
"github.com/containers/image/v5/transports/alltransports"
2022
encconfig "github.com/containers/ocicrypt/config"
2123
enchelpers "github.com/containers/ocicrypt/helpers"
24+
"github.com/opencontainers/go-digest"
25+
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
2226
"github.com/spf13/cobra"
2327
)
2428

@@ -99,24 +103,122 @@ See skopeo(1) section "IMAGE NAMES" for the expected format
99103
}
100104

101105
// 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
117124
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, ","))
119220
}
221+
return platformList, instanceList, nil
120222
}
121223

122224
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) {
186288
}
187289

188290
imageListSelection := copy.CopySystemImage
291+
var instanceDigests []digest.Digest
292+
var instancePlatforms []imgspecv1.Platform
189293
if opts.multiArch.Present() && opts.all {
190294
return fmt.Errorf("Cannot use --all and --multi-arch flags together")
191295
}
192296
if opts.multiArch.Present() {
193-
imageListSelection, err = parseMultiArch(opts.multiArch.Value())
297+
imageListSelection, instanceDigests, instancePlatforms, err = parseMultiArch(opts.global, opts.multiArch.Value())
194298
if err != nil {
195299
return err
196300
}
@@ -296,6 +400,8 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) (retErr error) {
296400
DestinationCtx: destinationCtx,
297401
ForceManifestMIMEType: manifestType,
298402
ImageListSelection: imageListSelection,
403+
Instances: instanceDigests,
404+
InstancePlatforms: instancePlatforms,
299405
PreserveDigests: opts.preserveDigests,
300406
OciDecryptConfig: decConfig,
301407
OciEncryptLayers: encLayers,

0 commit comments

Comments
 (0)