mirror of
https://github.com/containers/skopeo.git
synced 2025-08-11 19:32:04 +00:00
Merge pull request #1263 from Freakin/sync-manifest-format
Added format parameter to sync command
This commit is contained in:
commit
2eb35e7af9
@ -9,14 +9,11 @@ import (
|
|||||||
"github.com/containers/common/pkg/retry"
|
"github.com/containers/common/pkg/retry"
|
||||||
"github.com/containers/image/v5/copy"
|
"github.com/containers/image/v5/copy"
|
||||||
"github.com/containers/image/v5/docker/reference"
|
"github.com/containers/image/v5/docker/reference"
|
||||||
"github.com/containers/image/v5/manifest"
|
|
||||||
"github.com/containers/image/v5/transports"
|
"github.com/containers/image/v5/transports"
|
||||||
"github.com/containers/image/v5/transports/alltransports"
|
"github.com/containers/image/v5/transports/alltransports"
|
||||||
"github.com/spf13/cobra"
|
|
||||||
|
|
||||||
encconfig "github.com/containers/ocicrypt/config"
|
encconfig "github.com/containers/ocicrypt/config"
|
||||||
enchelpers "github.com/containers/ocicrypt/helpers"
|
enchelpers "github.com/containers/ocicrypt/helpers"
|
||||||
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
type copyOptions struct {
|
type copyOptions struct {
|
||||||
@ -112,15 +109,9 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
|
|||||||
|
|
||||||
var manifestType string
|
var manifestType string
|
||||||
if opts.format.present {
|
if opts.format.present {
|
||||||
switch opts.format.value {
|
manifestType, err = parseManifestFormat(opts.format.value)
|
||||||
case "oci":
|
if err != nil {
|
||||||
manifestType = imgspecv1.MediaTypeImageManifest
|
return err
|
||||||
case "v2s1":
|
|
||||||
manifestType = manifest.DockerV2Schema1SignedMediaType
|
|
||||||
case "v2s2":
|
|
||||||
manifestType = manifest.DockerV2Schema2MediaType
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("unknown format %q. Choose one of the supported formats: 'oci', 'v2s1', or 'v2s2'", opts.format.value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,12 +31,13 @@ type syncOptions struct {
|
|||||||
srcImage *imageOptions // Source image options
|
srcImage *imageOptions // Source image options
|
||||||
destImage *imageDestOptions // Destination image options
|
destImage *imageDestOptions // Destination image options
|
||||||
retryOpts *retry.RetryOptions
|
retryOpts *retry.RetryOptions
|
||||||
removeSignatures bool // Do not copy signatures from the source image
|
removeSignatures bool // Do not copy signatures from the source image
|
||||||
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
|
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
|
||||||
source string // Source repository name
|
format optionalString // Force conversion of the image to a specified format
|
||||||
destination string // Destination registry name
|
source string // Source repository name
|
||||||
scoped bool // When true, namespace copied images at destination using the source repository name
|
destination string // Destination registry name
|
||||||
all bool // Copy all of the images if an image in the source is a list
|
scoped bool // When true, namespace copied images at destination using the source repository name
|
||||||
|
all bool // Copy all of the images if an image in the source is a list
|
||||||
}
|
}
|
||||||
|
|
||||||
// repoDescriptor contains information of a single repository used as a sync source.
|
// repoDescriptor contains information of a single repository used as a sync source.
|
||||||
@ -95,6 +96,7 @@ See skopeo-sync(1) for details.
|
|||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
flags.BoolVar(&opts.removeSignatures, "remove-signatures", false, "Do not copy signatures from SOURCE images")
|
flags.BoolVar(&opts.removeSignatures, "remove-signatures", false, "Do not copy signatures from SOURCE images")
|
||||||
flags.StringVar(&opts.signByFingerprint, "sign-by", "", "Sign the image using a GPG key with the specified `FINGERPRINT`")
|
flags.StringVar(&opts.signByFingerprint, "sign-by", "", "Sign the image using a GPG key with the specified `FINGERPRINT`")
|
||||||
|
flags.VarP(newOptionalStringValue(&opts.format), "format", "f", `MANIFEST TYPE (oci, v2s1, or v2s2) to use when syncing image(s) to a destination (default is manifest type of source)`)
|
||||||
flags.StringVarP(&opts.source, "src", "s", "", "SOURCE transport type")
|
flags.StringVarP(&opts.source, "src", "s", "", "SOURCE transport type")
|
||||||
flags.StringVarP(&opts.destination, "dest", "d", "", "DESTINATION transport type")
|
flags.StringVarP(&opts.destination, "dest", "d", "", "DESTINATION transport type")
|
||||||
flags.BoolVar(&opts.scoped, "scoped", false, "Images at DESTINATION are prefix using the full source image path as scope")
|
flags.BoolVar(&opts.scoped, "scoped", false, "Images at DESTINATION are prefix using the full source image path as scope")
|
||||||
@ -536,6 +538,14 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var manifestType string
|
||||||
|
if opts.format.present {
|
||||||
|
manifestType, err = parseManifestFormat(opts.format.value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ctx, cancel := opts.global.commandTimeoutContext()
|
ctx, cancel := opts.global.commandTimeoutContext()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@ -562,6 +572,7 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) error {
|
|||||||
DestinationCtx: destinationCtx,
|
DestinationCtx: destinationCtx,
|
||||||
ImageListSelection: imageListSelection,
|
ImageListSelection: imageListSelection,
|
||||||
OptimizeDestinationImageAlreadyExists: true,
|
OptimizeDestinationImageAlreadyExists: true,
|
||||||
|
ForceManifestMIMEType: manifestType,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, srcRepo := range srcRepoList {
|
for _, srcRepo := range srcRepoList {
|
||||||
|
@ -2,14 +2,17 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/common/pkg/retry"
|
"github.com/containers/common/pkg/retry"
|
||||||
|
"github.com/containers/image/v5/manifest"
|
||||||
"github.com/containers/image/v5/pkg/compression"
|
"github.com/containers/image/v5/pkg/compression"
|
||||||
"github.com/containers/image/v5/transports/alltransports"
|
"github.com/containers/image/v5/transports/alltransports"
|
||||||
"github.com/containers/image/v5/types"
|
"github.com/containers/image/v5/types"
|
||||||
|
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@ -246,6 +249,21 @@ func parseImageSource(ctx context.Context, opts *imageOptions, name string) (typ
|
|||||||
return ref.NewImageSource(ctx, sys)
|
return ref.NewImageSource(ctx, sys)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseManifestFormat parses format parameter for copy and sync command.
|
||||||
|
// It returns string value to use as manifest MIME type
|
||||||
|
func parseManifestFormat(manifestFormat string) (string, error) {
|
||||||
|
switch manifestFormat {
|
||||||
|
case "oci":
|
||||||
|
return imgspecv1.MediaTypeImageManifest, nil
|
||||||
|
case "v2s1":
|
||||||
|
return manifest.DockerV2Schema1SignedMediaType, nil
|
||||||
|
case "v2s2":
|
||||||
|
return manifest.DockerV2Schema2MediaType, nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unknown format %q. Choose one of the supported formats: 'oci', 'v2s1', or 'v2s2'", manifestFormat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// usageTemplate returns the usage template for skopeo commands
|
// usageTemplate returns the usage template for skopeo commands
|
||||||
// This blocks the displaying of the global options. The main skopeo
|
// This blocks the displaying of the global options. The main skopeo
|
||||||
// command should not use this.
|
// command should not use this.
|
||||||
|
@ -4,7 +4,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/containers/image/v5/manifest"
|
||||||
"github.com/containers/image/v5/types"
|
"github.com/containers/image/v5/types"
|
||||||
|
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -203,6 +205,38 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) {
|
|||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseManifestFormat(t *testing.T) {
|
||||||
|
for _, testCase := range []struct {
|
||||||
|
formatParam string
|
||||||
|
expectedManifestType string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{"oci",
|
||||||
|
imgspecv1.MediaTypeImageManifest,
|
||||||
|
false},
|
||||||
|
{"v2s1",
|
||||||
|
manifest.DockerV2Schema1SignedMediaType,
|
||||||
|
false},
|
||||||
|
{"v2s2",
|
||||||
|
manifest.DockerV2Schema2MediaType,
|
||||||
|
false},
|
||||||
|
{"",
|
||||||
|
"",
|
||||||
|
true},
|
||||||
|
{"badValue",
|
||||||
|
"",
|
||||||
|
true},
|
||||||
|
} {
|
||||||
|
manifestType, err := parseManifestFormat(testCase.formatParam)
|
||||||
|
if testCase.expectErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
assert.Equal(t, manifestType, testCase.expectedManifestType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// since there is a shared authfile image option and a non-shared (prefixed) one, make sure the override logic
|
// since there is a shared authfile image option and a non-shared (prefixed) one, make sure the override logic
|
||||||
// works correctly.
|
// works correctly.
|
||||||
func TestImageOptionsAuthfileOverride(t *testing.T) {
|
func TestImageOptionsAuthfileOverride(t *testing.T) {
|
||||||
|
@ -70,6 +70,42 @@ _skopeo_copy() {
|
|||||||
_complete_ "$options_with_args" "$boolean_options" "$transports"
|
_complete_ "$options_with_args" "$boolean_options" "$transports"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_skopeo_sync() {
|
||||||
|
local options_with_args="
|
||||||
|
--authfile
|
||||||
|
--dest
|
||||||
|
--dest-authfile
|
||||||
|
--dest-cert-
|
||||||
|
--dest-creds
|
||||||
|
--dest-registry-token string
|
||||||
|
--format
|
||||||
|
--retry-times
|
||||||
|
--sign-by
|
||||||
|
--src
|
||||||
|
--src-authfile
|
||||||
|
--src-cert-dir
|
||||||
|
--src-creds
|
||||||
|
--src-registry-token
|
||||||
|
"
|
||||||
|
|
||||||
|
local boolean_options="
|
||||||
|
--all
|
||||||
|
--dest-no-creds
|
||||||
|
--dest-tls-verify
|
||||||
|
--remove-signatures
|
||||||
|
--scoped
|
||||||
|
--src-no-creds
|
||||||
|
--src-tls-verify
|
||||||
|
"
|
||||||
|
|
||||||
|
local transports
|
||||||
|
transports="
|
||||||
|
$(_skopeo_supported_transports "${FUNCNAME//"_skopeo_"/}")
|
||||||
|
"
|
||||||
|
|
||||||
|
_complete_ "$options_with_args" "$boolean_options" "$transports"
|
||||||
|
}
|
||||||
|
|
||||||
_skopeo_inspect() {
|
_skopeo_inspect() {
|
||||||
local options_with_args="
|
local options_with_args="
|
||||||
--authfile
|
--authfile
|
||||||
@ -260,7 +296,7 @@ _cli_bash_autocomplete() {
|
|||||||
local counter=1
|
local counter=1
|
||||||
while [ $counter -lt "$cword" ]; do
|
while [ $counter -lt "$cword" ]; do
|
||||||
case "${words[$counter]}" in
|
case "${words[$counter]}" in
|
||||||
skopeo|copy|inspect|delete|manifest-digest|standalone-sign|standalone-verify|help|h|list-repository-tags)
|
skopeo|copy|sync|inspect|delete|manifest-digest|standalone-sign|standalone-verify|help|h|list-repository-tags)
|
||||||
command="${words[$counter]//-/_}"
|
command="${words[$counter]//-/_}"
|
||||||
cpos=$counter
|
cpos=$counter
|
||||||
(( cpos++ ))
|
(( cpos++ ))
|
||||||
|
@ -54,6 +54,8 @@ Path of the authentication file for the destination registry. Uses path given by
|
|||||||
|
|
||||||
**--dest** _transport_ Destination transport.
|
**--dest** _transport_ Destination transport.
|
||||||
|
|
||||||
|
**--format, -f** _manifest-type_ Manifest Type (oci, v2s1, or v2s2) to use when syncing image(s) to a destination (default is manifest type of source).
|
||||||
|
|
||||||
**--scoped** Prefix images with the source image path, so that multiple images with the same name can be stored at _destination_.
|
**--scoped** Prefix images with the source image path, so that multiple images with the same name can be stored at _destination_.
|
||||||
|
|
||||||
**--remove-signatures** Do not copy signatures, if any, from _source-image_. This is necessary when copying a signed image to a destination which does not support signatures.
|
**--remove-signatures** Do not copy signatures, if any, from _source-image_. This is necessary when copying a signed image to a destination which does not support signatures.
|
||||||
|
@ -1251,14 +1251,6 @@ func (s *CopySuite) testCopySchemaConversionRegistries(c *check.C, schema1Regist
|
|||||||
verifyManifestMIMEType(c, destDir, manifest.DockerV2Schema1SignedMediaType)
|
verifyManifestMIMEType(c, destDir, manifest.DockerV2Schema1SignedMediaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify manifest in a dir: image at dir is expectedMIMEType.
|
|
||||||
func verifyManifestMIMEType(c *check.C, dir string, expectedMIMEType string) {
|
|
||||||
manifestBlob, err := ioutil.ReadFile(filepath.Join(dir, "manifest.json"))
|
|
||||||
c.Assert(err, check.IsNil)
|
|
||||||
mimeType := manifest.GuessMIMEType(manifestBlob)
|
|
||||||
c.Assert(mimeType, check.Equals, expectedMIMEType)
|
|
||||||
}
|
|
||||||
|
|
||||||
const regConfFixture = "./fixtures/registries.conf"
|
const regConfFixture = "./fixtures/registries.conf"
|
||||||
|
|
||||||
func (s *SkopeoSuite) TestSuccessCopySrcWithMirror(c *check.C) {
|
func (s *SkopeoSuite) TestSuccessCopySrcWithMirror(c *check.C) {
|
||||||
|
@ -12,8 +12,10 @@ import (
|
|||||||
|
|
||||||
"github.com/containers/image/v5/docker"
|
"github.com/containers/image/v5/docker"
|
||||||
"github.com/containers/image/v5/docker/reference"
|
"github.com/containers/image/v5/docker/reference"
|
||||||
|
"github.com/containers/image/v5/manifest"
|
||||||
"github.com/containers/image/v5/types"
|
"github.com/containers/image/v5/types"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
|
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -472,6 +474,26 @@ func (s *SyncSuite) TestYamlTLSVerify(c *check.C) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SyncSuite) TestSyncManifestOutput(c *check.C) {
|
||||||
|
tmpDir, err := ioutil.TempDir("", "sync-manifest-output")
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
|
||||||
|
destDir1 := filepath.Join(tmpDir, "dest1")
|
||||||
|
destDir2 := filepath.Join(tmpDir, "dest2")
|
||||||
|
destDir3 := filepath.Join(tmpDir, "dest3")
|
||||||
|
|
||||||
|
//Split image:tag path from image URI for manifest comparison
|
||||||
|
imageDir := pullableTaggedImage[strings.LastIndex(pullableTaggedImage, "/")+1:]
|
||||||
|
|
||||||
|
assertSkopeoSucceeds(c, "", "sync", "--format=oci", "--all", "--src", "docker", "--dest", "dir", pullableTaggedImage, destDir1)
|
||||||
|
verifyManifestMIMEType(c, filepath.Join(destDir1, imageDir), imgspecv1.MediaTypeImageManifest)
|
||||||
|
assertSkopeoSucceeds(c, "", "sync", "--format=v2s2", "--all", "--src", "docker", "--dest", "dir", pullableTaggedImage, destDir2)
|
||||||
|
verifyManifestMIMEType(c, filepath.Join(destDir2, imageDir), manifest.DockerV2Schema2MediaType)
|
||||||
|
assertSkopeoSucceeds(c, "", "sync", "--format=v2s1", "--all", "--src", "docker", "--dest", "dir", pullableTaggedImage, destDir3)
|
||||||
|
verifyManifestMIMEType(c, filepath.Join(destDir3, imageDir), manifest.DockerV2Schema1SignedMediaType)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SyncSuite) TestDocker2DockerTagged(c *check.C) {
|
func (s *SyncSuite) TestDocker2DockerTagged(c *check.C) {
|
||||||
const localRegURL = "docker://" + v2DockerRegistryURL + "/"
|
const localRegURL = "docker://" + v2DockerRegistryURL + "/"
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/containers/image/v5/manifest"
|
||||||
"github.com/go-check/check"
|
"github.com/go-check/check"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -200,3 +201,11 @@ func runDecompressDirs(c *check.C, regexp string, args ...string) {
|
|||||||
c.Assert(string(out), check.Matches, "(?s)"+regexp) // (?s) : '.' will also match newlines
|
c.Assert(string(out), check.Matches, "(?s)"+regexp) // (?s) : '.' will also match newlines
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Verify manifest in a dir: image at dir is expectedMIMEType.
|
||||||
|
func verifyManifestMIMEType(c *check.C, dir string, expectedMIMEType string) {
|
||||||
|
manifestBlob, err := ioutil.ReadFile(filepath.Join(dir, "manifest.json"))
|
||||||
|
c.Assert(err, check.IsNil)
|
||||||
|
mimeType := manifest.GuessMIMEType(manifestBlob)
|
||||||
|
c.Assert(mimeType, check.Equals, expectedMIMEType)
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user