diff --git a/cmd/skopeo/copy.go b/cmd/skopeo/copy.go index 3ffce4db..b61d2a9e 100644 --- a/cmd/skopeo/copy.go +++ b/cmd/skopeo/copy.go @@ -32,6 +32,7 @@ type copyOptions struct { format commonFlag.OptionalString // Force conversion of the image to a specified format quiet bool // Suppress output information when copying images all bool // Copy all of the images if the source is a list + multiArch commonFlag.OptionalString // How to handle multi architecture images encryptLayer []int // The list of layers to encrypt encryptionKeys []string // Keys needed to encrypt the image decryptionKeys []string // Keys needed to decrypt the image @@ -72,6 +73,7 @@ See skopeo(1) section "IMAGE NAMES" for the expected format flags.StringSliceVar(&opts.additionalTags, "additional-tag", []string{}, "additional tags (supports docker-archive)") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Suppress output information when copying images") flags.BoolVarP(&opts.all, "all", "a", false, "Copy all images if SOURCE-IMAGE is a list") + flags.Var(commonFlag.NewOptionalStringValue(&opts.multiArch), "multi-arch", `How to handle multi-architecture images (system, all, or index-only)`) flags.BoolVar(&opts.removeSignatures, "remove-signatures", false, "Do not copy signatures from SOURCE-IMAGE") flags.StringVar(&opts.signByFingerprint, "sign-by", "", "Sign the image using a GPG key with the specified `FINGERPRINT`") 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 return cmd } +// parseMultiArch parses the list processing selection +// It returns the copy.ImageListSelection to use with image.Copy option +func parseMultiArch(multiArch string) (copy.ImageListSelection, error) { + switch multiArch { + case "system": + return copy.CopySystemImage, nil + case "all": + return copy.CopyAllImages, nil + // There is no CopyNoImages value in copy.ImageListSelection, but because we + // don't provide an option to select a set of images to copy, we can use + // CopySpecificImages. + case "index-only": + return copy.CopySpecificImages, nil + // We don't expose CopySpecificImages other than index-only above, because + // we currently don't provide an option to choose the images to copy. That + // could be added in the future. + default: + return copy.CopySystemImage, fmt.Errorf("unknown multi-arch option %q. Choose one of the supported options: 'system', 'all', or 'index-only'", multiArch) + } +} + func (opts *copyOptions) run(args []string, stdout io.Writer) error { if len(args) != 2 { return errorShouldDisplayUsage{errors.New("Exactly two arguments expected")} @@ -143,7 +166,17 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error { if opts.quiet { stdout = nil } + imageListSelection := copy.CopySystemImage + if opts.multiArch.Present() && opts.all { + return fmt.Errorf("Cannot use --all and --multi-arch flags together") + } + if opts.multiArch.Present() { + imageListSelection, err = parseMultiArch(opts.multiArch.Value()) + if err != nil { + return err + } + } if opts.all { imageListSelection = copy.CopyAllImages } diff --git a/completions/bash/skopeo b/completions/bash/skopeo index ad3d8d29..26140547 100644 --- a/completions/bash/skopeo +++ b/completions/bash/skopeo @@ -40,6 +40,7 @@ _skopeo_copy() { --src-authfile --dest-authfile --format -f + --multi-arch --sign-by --src-creds --screds --src-cert-dir diff --git a/docs/skopeo-copy.1.md b/docs/skopeo-copy.1.md index e9c21592..d24ca7cf 100644 --- a/docs/skopeo-copy.1.md +++ b/docs/skopeo-copy.1.md @@ -66,6 +66,17 @@ MANIFEST TYPE (oci, v2s1, or v2s2) to use in the destination (default is manifes Print usage statement +**--multi-arch** + +Control what is copied if _source-image_ refers to a multi-architecture image. Default is system. + +Options: +- system: Copy only the image that matches the system architecture +- all: Copy the full multi-architecture image +- index-only: Copy only the index + +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. + **--quiet**, **-q** Suppress output information when copying images. diff --git a/integration/copy_test.go b/integration/copy_test.go index 2c21ae56..a8f0171f 100644 --- a/integration/copy_test.go +++ b/integration/copy_test.go @@ -123,10 +123,10 @@ func (s *CopySuite) TestCopyAllWithManifestListRoundTrip(c *check.C) { dir2, err := ioutil.TempDir("", "copy-all-manifest-list-dir") c.Assert(err, check.IsNil) defer os.RemoveAll(dir2) - assertSkopeoSucceeds(c, "", "copy", "--all", knownListImage, "oci:"+oci1) - assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci1, "dir:"+dir1) - assertSkopeoSucceeds(c, "", "copy", "--all", "dir:"+dir1, "oci:"+oci2) - assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci2, "dir:"+dir2) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", knownListImage, "oci:"+oci1) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "dir:"+dir1, "oci:"+oci2) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci2, "dir:"+dir2) assertDirImagesAreEqual(c, dir1, dir2) out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2) c.Assert(out, check.Equals, "") @@ -145,15 +145,30 @@ func (s *CopySuite) TestCopyAllWithManifestListConverge(c *check.C) { dir2, err := ioutil.TempDir("", "copy-all-manifest-list-dir") c.Assert(err, check.IsNil) defer os.RemoveAll(dir2) - assertSkopeoSucceeds(c, "", "copy", "--all", knownListImage, "oci:"+oci1) - assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci1, "dir:"+dir1) - assertSkopeoSucceeds(c, "", "copy", "--all", "--format", "oci", knownListImage, "dir:"+dir2) - assertSkopeoSucceeds(c, "", "copy", "--all", "dir:"+dir2, "oci:"+oci2) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", knownListImage, "oci:"+oci1) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "--format", "oci", knownListImage, "dir:"+dir2) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "dir:"+dir2, "oci:"+oci2) assertDirImagesAreEqual(c, dir1, dir2) out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2) c.Assert(out, check.Equals, "") } +func (s *CopySuite) TestCopyNoneWithManifestList(c *check.C) { + dir1, err := ioutil.TempDir("", "copy-all-manifest-list-dir") + c.Assert(err, check.IsNil) + defer os.RemoveAll(dir1) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=index-only", knownListImage, "dir:"+dir1) + + manifestPath := filepath.Join(dir1, "manifest.json") + readManifest, err := ioutil.ReadFile(manifestPath) + c.Assert(err, check.IsNil) + mimeType := manifest.GuessMIMEType(readManifest) + c.Assert(mimeType, check.Equals, "application/vnd.docker.distribution.manifest.list.v2+json") + out := combinedOutputOfCommand(c, "ls", "-1", dir1) + c.Assert(out, check.Equals, "manifest.json\nversion\n") +} + func (s *CopySuite) TestCopyWithManifestListConverge(c *check.C) { oci1, err := ioutil.TempDir("", "copy-all-manifest-list-oci") c.Assert(err, check.IsNil) @@ -168,9 +183,9 @@ func (s *CopySuite) TestCopyWithManifestListConverge(c *check.C) { c.Assert(err, check.IsNil) defer os.RemoveAll(dir2) assertSkopeoSucceeds(c, "", "copy", knownListImage, "oci:"+oci1) - assertSkopeoSucceeds(c, "", "copy", "--all", "oci:"+oci1, "dir:"+dir1) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "oci:"+oci1, "dir:"+dir1) assertSkopeoSucceeds(c, "", "copy", "--format", "oci", knownListImage, "dir:"+dir2) - assertSkopeoSucceeds(c, "", "copy", "--all", "dir:"+dir2, "oci:"+oci2) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", "dir:"+dir2, "oci:"+oci2) assertDirImagesAreEqual(c, dir1, dir2) out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2) c.Assert(out, check.Equals, "") @@ -181,7 +196,7 @@ func (s *CopySuite) TestCopyAllWithManifestListStorageFails(c *check.C) { c.Assert(err, check.IsNil) defer os.RemoveAll(storage) storage = fmt.Sprintf("[vfs@%s/root+%s/runroot]", storage, storage) - assertSkopeoFails(c, `.*destination transport .* does not support copying multiple images as a group.*`, "copy", "--all", knownListImage, "containers-storage:"+storage+"test") + assertSkopeoFails(c, `.*destination transport .* does not support copying multiple images as a group.*`, "copy", "--multi-arch=all", knownListImage, "containers-storage:"+storage+"test") } func (s *CopySuite) TestCopyWithManifestListStorage(c *check.C) { @@ -239,7 +254,7 @@ func (s *CopySuite) TestCopyWithManifestListDigest(c *check.C) { c.Assert(err, check.IsNil) digest := manifestDigest.String() assertSkopeoSucceeds(c, "", "copy", knownListImage+"@"+digest, "dir:"+dir1) - assertSkopeoSucceeds(c, "", "copy", "--all", knownListImage+"@"+digest, "dir:"+dir2) + assertSkopeoSucceeds(c, "", "copy", "--multi-arch=all", knownListImage+"@"+digest, "dir:"+dir2) assertSkopeoSucceeds(c, "", "copy", "dir:"+dir1, "oci:"+oci1) assertSkopeoSucceeds(c, "", "copy", "dir:"+dir2, "oci:"+oci2) out := combinedOutputOfCommand(c, "diff", "-urN", oci1, oci2)