Skip to content

Commit c3f6595

Browse files
authored
Merge pull request #1511 from Jamstah/copy-sparse-manifest
Add an option to allow copying image indexes alone
2 parents 1d24e65 + d940154 commit c3f6595

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)