Merge pull request #1263 from Freakin/sync-manifest-format

Added format parameter to sync command
This commit is contained in:
Valentin Rothberg 2021-05-03 10:25:14 +02:00 committed by GitHub
commit 2eb35e7af9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 143 additions and 28 deletions

View File

@ -9,14 +9,11 @@ import (
"github.com/containers/common/pkg/retry"
"github.com/containers/image/v5/copy"
"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/alltransports"
"github.com/spf13/cobra"
encconfig "github.com/containers/ocicrypt/config"
enchelpers "github.com/containers/ocicrypt/helpers"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
)
type copyOptions struct {
@ -112,15 +109,9 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error {
var manifestType string
if opts.format.present {
switch opts.format.value {
case "oci":
manifestType = imgspecv1.MediaTypeImageManifest
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)
manifestType, err = parseManifestFormat(opts.format.value)
if err != nil {
return err
}
}

View File

@ -31,12 +31,13 @@ type syncOptions struct {
srcImage *imageOptions // Source image options
destImage *imageDestOptions // Destination image options
retryOpts *retry.RetryOptions
removeSignatures bool // Do not copy signatures from the source image
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
source string // Source repository name
destination string // Destination registry name
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
removeSignatures bool // Do not copy signatures from the source image
signByFingerprint string // Sign the image using a GPG key with the specified fingerprint
format optionalString // Force conversion of the image to a specified format
source string // Source repository name
destination string // Destination registry name
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.
@ -95,6 +96,7 @@ See skopeo-sync(1) for details.
flags := cmd.Flags()
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.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.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")
@ -536,6 +538,14 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) error {
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()
defer cancel()
@ -562,6 +572,7 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) error {
DestinationCtx: destinationCtx,
ImageListSelection: imageListSelection,
OptimizeDestinationImageAlreadyExists: true,
ForceManifestMIMEType: manifestType,
}
for _, srcRepo := range srcRepoList {

View File

@ -2,14 +2,17 @@ package main
import (
"context"
"fmt"
"io"
"os"
"strings"
"github.com/containers/common/pkg/retry"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/pkg/compression"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
@ -246,6 +249,21 @@ func parseImageSource(ctx context.Context, opts *imageOptions, name string) (typ
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
// This blocks the displaying of the global options. The main skopeo
// command should not use this.

View File

@ -4,7 +4,9 @@ import (
"os"
"testing"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -203,6 +205,38 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) {
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
// works correctly.
func TestImageOptionsAuthfileOverride(t *testing.T) {

View File

@ -70,6 +70,42 @@ _skopeo_copy() {
_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() {
local options_with_args="
--authfile
@ -260,7 +296,7 @@ _cli_bash_autocomplete() {
local counter=1
while [ $counter -lt "$cword" ]; do
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]//-/_}"
cpos=$counter
(( cpos++ ))

View File

@ -54,6 +54,8 @@ Path of the authentication file for the destination registry. Uses path given by
**--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_.
**--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.

View File

@ -1251,14 +1251,6 @@ func (s *CopySuite) testCopySchemaConversionRegistries(c *check.C, schema1Regist
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"
func (s *SkopeoSuite) TestSuccessCopySrcWithMirror(c *check.C) {

View File

@ -12,8 +12,10 @@ import (
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/types"
"github.com/go-check/check"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)
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) {
const localRegURL = "docker://" + v2DockerRegistryURL + "/"

View File

@ -10,6 +10,7 @@ import (
"strings"
"time"
"github.com/containers/image/v5/manifest"
"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
}
}
// 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)
}