Skip to content

Commit d940154

Browse files
committed
Add an option to allow copying image indexes alone
The new --multi-arch option allows the user to select between copying the image associated with the system platform, all images in the index, or just the index itself without attempting to copy the images. Signed-off-by: James Hewitt <[email protected]>
1 parent 1d24e65 commit d940154

File tree

4 files changed

+72
-12
lines changed

4 files changed

+72
-12
lines changed

cmd/skopeo/copy.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type copyOptions struct {
3232
format commonFlag.OptionalString // Force conversion of the image to a specified format
3333
quiet bool // Suppress output information when copying images
3434
all bool // Copy all of the images if the source is a list
35+
multiArch commonFlag.OptionalString // How to handle multi architecture images
3536
encryptLayer []int // The list of layers to encrypt
3637
encryptionKeys []string // Keys needed to encrypt the image
3738
decryptionKeys []string // Keys needed to decrypt the image
@@ -72,6 +73,7 @@ See skopeo(1) section "IMAGE NAMES" for the expected format
7273
flags.StringSliceVar(&opts.additionalTags, "additional-tag", []string{}, "additional tags (supports docker-archive)")
7374
flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress output information when copying images")
7475
flags.BoolVarP(&opts.all, "all", "a", false, "Copy all images if SOURCE-IMAGE is a list")
76+
flags.Var(commonFlag.NewOptionalStringValue(&opts.multiArch), "multi-arch", `How to handle multi-architecture images (system, all, or index-only)`)
7577
flags.BoolVar(&opts.removeSignatures, "remove-signatures", false, "Do not copy signatures from SOURCE-IMAGE")
7678
flags.StringVar(&opts.signByFingerprint, "sign-by", "", "Sign the image using a GPG key with the specified `FINGERPRINT`")
7779
flags.StringVar(&opts.digestFile, "digestfile", "", "Write the digest of the pushed image to the specified file")
@@ -82,6 +84,27 @@ See skopeo(1) section "IMAGE NAMES" for the expected format
8284
return cmd
8385
}
8486

87+
// parseMultiArch parses the list processing selection
88+
// It returns the copy.ImageListSelection to use with image.Copy option
89+
func parseMultiArch(multiArch string) (copy.ImageListSelection, error) {
90+
switch multiArch {
91+
case "system":
92+
return copy.CopySystemImage, nil
93+
case "all":
94+
return copy.CopyAllImages, nil
95+
// There is no CopyNoImages value in copy.ImageListSelection, but because we
96+
// don't provide an option to select a set of images to copy, we can use
97+
// CopySpecificImages.
98+
case "index-only":
99+
return copy.CopySpecificImages, nil
100+
// We don't expose CopySpecificImages other than index-only above, because
101+
// we currently don't provide an option to choose the images to copy. That
102+
// could be added in the future.
103+
default:
104+
return copy.CopySystemImage, fmt.Errorf("unknown multi-arch option %q. Choose one of the supported options: 'system', 'all', or 'index-only'", multiArch)
105+
}
106+
}
107+
85108
func (opts *copyOptions) run(args []string, stdout io.Writer) error {
86109
if len(args) != 2 {
87110
return errorShouldDisplayUsage{errors.New("Exactly two arguments expected")}
@@ -143,7 +166,17 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
143166
if opts.quiet {
144167
stdout = nil
145168
}
169+
146170
imageListSelection := copy.CopySystemImage
171+
if opts.multiArch.Present() && opts.all {
172+
return fmt.Errorf("Cannot use --all and --multi-arch flags together")
173+
}
174+
if opts.multiArch.Present() {
175+
imageListSelection, err = parseMultiArch(opts.multiArch.Value())
176+
if err != nil {
177+
return err
178+
}
179+
}
147180
if opts.all {
148181
imageListSelection = copy.CopyAllImages
149182
}

completions/bash/skopeo

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ _skopeo_copy() {
4040
--src-authfile
4141
--dest-authfile
4242
--format -f
43+
--multi-arch
4344
--sign-by
4445
--src-creds --screds
4546
--src-cert-dir

docs/skopeo-copy.1.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,17 @@ MANIFEST TYPE (oci, v2s1, or v2s2) to use in the destination (default is manifes
6666

6767
Print usage statement
6868

69+
**--multi-arch**
70+
71+
Control what is copied if _source-image_ refers to a multi-architecture image. Default is system.
72+
73+
Options:
74+
- system: Copy only the image that matches the system architecture
75+
- all: Copy the full multi-architecture image
76+
- index-only: Copy only the index
77+
78+
The index-only option usually fails unless the referenced per-architecture images are already present in the destination, or the target registry supports sparse indexes.
79+
6980
**--quiet**, **-q**
7081

7182
Suppress output information when copying images.

integration/copy_test.go

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,10 @@ func (s *CopySuite) TestCopyAllWithManifestListRoundTrip(c *check.C) {
123123
dir2, err := ioutil.TempDir("", "copy-all-manifest-list-dir")
124124
c.Assert(err, check.IsNil)
125125
defer os.RemoveAll(dir2)
126-
assertSkopeoSucceeds(c, "", "copy", "--all", knownListImage, "oci:"+oci1)
127-
assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci1, "dir:"+dir1)
128-
assertSkopeoSucceeds(c, "", "copy", "--all", "dir:"+dir1, "oci:"+oci2)
129-
assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci2, "dir:"+dir2)
126+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", knownListImage, "oci:"+oci1)
127+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1)
128+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "dir:"+dir1, "oci:"+oci2)
129+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci2, "dir:"+dir2)
130130
assertDirImagesAreEqual(c, dir1, dir2)
131131
out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)
132132
c.Assert(out, check.Equals, "")
@@ -145,15 +145,30 @@ func (s *CopySuite) TestCopyAllWithManifestListConverge(c *check.C) {
145145
dir2, err := ioutil.TempDir("", "copy-all-manifest-list-dir")
146146
c.Assert(err, check.IsNil)
147147
defer os.RemoveAll(dir2)
148-
assertSkopeoSucceeds(c, "", "copy", "--all", knownListImage, "oci:"+oci1)
149-
assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci1, "dir:"+dir1)
150-
assertSkopeoSucceeds(c, "", "copy", "--all", "--format", "oci", knownListImage, "dir:"+dir2)
151-
assertSkopeoSucceeds(c, "", "copy", "--all", "dir:"+dir2, "oci:"+oci2)
148+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", knownListImage, "oci:"+oci1)
149+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1)
150+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "--format", "oci", knownListImage, "dir:"+dir2)
151+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "dir:"+dir2, "oci:"+oci2)
152152
assertDirImagesAreEqual(c, dir1, dir2)
153153
out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)
154154
c.Assert(out, check.Equals, "")
155155
}
156156

157+
func (s *CopySuite) TestCopyNoneWithManifestList(c *check.C) {
158+
dir1, err := ioutil.TempDir("", "copy-all-manifest-list-dir")
159+
c.Assert(err, check.IsNil)
160+
defer os.RemoveAll(dir1)
161+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=index-only", knownListImage, "dir:"+dir1)
162+
163+
manifestPath := filepath.Join(dir1, "manifest.json")
164+
readManifest, err := ioutil.ReadFile(manifestPath)
165+
c.Assert(err, check.IsNil)
166+
mimeType := manifest.GuessMIMEType(readManifest)
167+
c.Assert(mimeType, check.Equals, "application/vnd.docker.distribution.manifest.list.v2+json")
168+
out := combinedOutputOfCommand(c, "ls", "-1", dir1)
169+
c.Assert(out, check.Equals, "manifest.json\nversion\n")
170+
}
171+
157172
func (s *CopySuite) TestCopyWithManifestListConverge(c *check.C) {
158173
oci1, err := ioutil.TempDir("", "copy-all-manifest-list-oci")
159174
c.Assert(err, check.IsNil)
@@ -168,9 +183,9 @@ func (s *CopySuite) TestCopyWithManifestListConverge(c *check.C) {
168183
c.Assert(err, check.IsNil)
169184
defer os.RemoveAll(dir2)
170185
assertSkopeoSucceeds(c, "", "copy", knownListImage, "oci:"+oci1)
171-
assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci1, "dir:"+dir1)
186+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1)
172187
assertSkopeoSucceeds(c, "", "copy", "--format", "oci", knownListImage, "dir:"+dir2)
173-
assertSkopeoSucceeds(c, "", "copy", "--all", "dir:"+dir2, "oci:"+oci2)
188+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "dir:"+dir2, "oci:"+oci2)
174189
assertDirImagesAreEqual(c, dir1, dir2)
175190
out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)
176191
c.Assert(out, check.Equals, "")
@@ -181,7 +196,7 @@ func (s *CopySuite) TestCopyAllWithManifestListStorageFails(c *check.C) {
181196
c.Assert(err, check.IsNil)
182197
defer os.RemoveAll(storage)
183198
storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage)
184-
assertSkopeoFails(c, `.*destination transport .* does not support copying multiple images as a group.*`, "copy", "--all", knownListImage, "containers-storage:"+storage+"test")
199+
assertSkopeoFails(c, `.*destination transport .* does not support copying multiple images as a group.*`, "copy", "--multi-arch=all", knownListImage, "containers-storage:"+storage+"test")
185200
}
186201

187202
func (s *CopySuite) TestCopyWithManifestListStorage(c *check.C) {
@@ -239,7 +254,7 @@ func (s *CopySuite) TestCopyWithManifestListDigest(c *check.C) {
239254
c.Assert(err, check.IsNil)
240255
digest := manifestDigest.String()
241256
assertSkopeoSucceeds(c, "", "copy", knownListImage+"@"+digest, "dir:"+dir1)
242-
assertSkopeoSucceeds(c, "", "copy", "--all", knownListImage+"@"+digest, "dir:"+dir2)
257+
assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", knownListImage+"@"+digest, "dir:"+dir2)
243258
assertSkopeoSucceeds(c, "", "copy", "dir:"+dir1, "oci:"+oci1)
244259
assertSkopeoSucceeds(c, "", "copy", "dir:"+dir2, "oci:"+oci2)
245260
out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)

0 commit comments

Comments
 (0)