From 1e7d880b561318aa2ad89d7583addad1904e5a36 Mon Sep 17 00:00:00 2001 From: umohnani8 Date: Tue, 12 Dec 2017 13:33:10 -0500 Subject: Add manifest type conversion to kpod push User can select from 3 manifest types: oci, v2s1, or v2s2 e.g kpod push --format v2s2 alpine dir:my-directory Added "compress" flag to enable compression when true Signed-off-by: umohnani8 Closes: #126 Approved by: rhatdan --- cmd/kpod/login.go | 2 +- cmd/kpod/logout.go | 2 +- cmd/kpod/push.go | 43 +++++++++++++++++++++++++++++--- completions/bash/kpod | 4 +-- docs/kpod-push.1.md | 32 +++++++++++++++++++++--- libpod/common/common.go | 20 ++++++++------- libpod/common/docker_registry_options.go | 3 ++- libpod/runtime_img.go | 16 +++++++----- test/kpod_push.bats | 16 ++++++++++++ 9 files changed, 110 insertions(+), 28 deletions(-) diff --git a/cmd/kpod/login.go b/cmd/kpod/login.go index df74a2fdf..8984d069c 100644 --- a/cmd/kpod/login.go +++ b/cmd/kpod/login.go @@ -56,7 +56,7 @@ func loginCmd(c *cli.Context) error { server = args[0] } - sc := common.GetSystemContext("", c.String("authfile")) + sc := common.GetSystemContext("", c.String("authfile"), false) // username of user logged in to server (if one exists) userFromAuthFile := config.GetUserLoggedIn(sc, server) diff --git a/cmd/kpod/logout.go b/cmd/kpod/logout.go index 9d1d52e32..cae8ddfb2 100644 --- a/cmd/kpod/logout.go +++ b/cmd/kpod/logout.go @@ -46,7 +46,7 @@ func logoutCmd(c *cli.Context) error { server = args[0] } - sc := common.GetSystemContext("", c.String("authfile")) + sc := common.GetSystemContext("", c.String("authfile"), false) if c.Bool("all") { if err := config.RemoveAllAuthentication(sc); err != nil { diff --git a/cmd/kpod/push.go b/cmd/kpod/push.go index 4f1218a08..d3d42e0ee 100644 --- a/cmd/kpod/push.go +++ b/cmd/kpod/push.go @@ -4,9 +4,12 @@ import ( "fmt" "io" "os" + "strings" + "github.com/containers/image/manifest" "github.com/containers/image/types" "github.com/containers/storage/pkg/archive" + imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/projectatomic/libpod/libpod" "github.com/projectatomic/libpod/libpod/common" @@ -29,6 +32,14 @@ var ( Name: "cert-dir", Usage: "`pathname` of a directory containing TLS certificates and keys", }, + cli.BoolFlag{ + Name: "compress", + Usage: "compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type as source)", + }, + cli.StringFlag{ + Name: "format, f", + Usage: "manifest type (oci, v2s1, or v2s2) to use when pushing an image using the 'dir:' transport (default is manifest type of source)", + }, cli.BoolTFlag{ Name: "tls-verify", Usage: "require HTTPS and verify certificates when contacting registries (default: true)", @@ -75,8 +86,16 @@ func pushCmd(c *cli.Context) error { if err := validateFlags(c, pushFlags); err != nil { return err } - srcName := c.Args().Get(0) - destName := c.Args().Get(1) + srcName := args[0] + destName := args[1] + + // --compress and --format can only be used for the "dir" transport + splitArg := strings.SplitN(destName, ":", 2) + if c.IsSet("compress") || c.IsSet("format") { + if splitArg[0] != libpod.DirTransport { + return errors.Errorf("--compress and --format can be set only when pushing to a directory using the 'dir' transport") + } + } registryCredsString := c.String("creds") certPath := c.String("cert-dir") @@ -112,6 +131,20 @@ func pushCmd(c *cli.Context) error { writer = os.Stdout } + var manifestType string + if c.IsSet("format") { + switch c.String("format") { + case "oci": + manifestType = imgspecv1.MediaTypeImageManifest + case "v2s1": + manifestType = manifest.DockerV2Schema1SignedMediaType + case "v2s2", "docker": + manifestType = manifest.DockerV2Schema2MediaType + default: + return fmt.Errorf("unknown format %q. Choose on of the supported formats: 'oci', 'v2s1', or 'v2s2'", c.String("format")) + } + } + options := libpod.CopyOptions{ Compression: archive.Uncompressed, SignaturePolicyPath: c.String("signature-policy"), @@ -124,8 +157,10 @@ func pushCmd(c *cli.Context) error { RemoveSignatures: removeSignatures, SignBy: signBy, }, - AuthFile: c.String("authfile"), - Writer: writer, + AuthFile: c.String("authfile"), + Writer: writer, + ManifestMIMEType: manifestType, + ForceCompress: c.Bool("compress"), } return runtime.PushImage(srcName, destName, options) diff --git a/completions/bash/kpod b/completions/bash/kpod index d403fed3e..417e9468d 100644 --- a/completions/bash/kpod +++ b/completions/bash/kpod @@ -921,8 +921,7 @@ _kpod_mount() { _kpod_push() { local boolean_options=" - --disable-compression - -D + --compress --quiet -q --remove-signatures @@ -931,6 +930,7 @@ _kpod_push() { local options_with_args=" --authfile + --format --cert-dir --creds --sign-by diff --git a/docs/kpod-push.1.md b/docs/kpod-push.1.md index a9d314cbe..211a5f517 100644 --- a/docs/kpod-push.1.md +++ b/docs/kpod-push.1.md @@ -9,8 +9,10 @@ kpod push - Push an image from local storage to elsewhere **kpod** **push** [*options* [...]] **imageID** [**destination**] ## DESCRIPTION -Pushes an image from local storage to a specified destination, decompressing -and recompressing layers as needed. +Pushes an image from local storage to a specified destination. +Push is mainly used to push images to registries, however **kpod push** +can be used to save images to tarballs and directories using the following +transports: **dir:**, **docker-archive:**, **docker-daemon:**, **oci-archive:**, and **ostree:**. ## imageID Image stored in local container/storage @@ -19,6 +21,8 @@ Image stored in local container/storage The DESTINATION is a location to store container images The Image "DESTINATION" uses a "transport":"details" format. + If a transport is not given, kpod push will attempt to push + to a registry. Multiple transports are supported: @@ -55,9 +59,15 @@ Credentials (USERNAME:PASSWORD) to use for authenticating to a registry Pathname of a directory containing TLS certificates and keys -**--disable-compression, -D** +**--compress** -Don't compress copies of filesystem layers which will be pushed +Compress tarball image layers when pushing to a directory using the 'dir' transport. (default is same compression type, compressed or uncompressed, as source) +Note: This flag can only be set when using the **dir** transport + +**--format, -f** + +Manifest Type (oci, v2s1, or v2s2) to use when pushing an image to a directory using the 'dir:' transport (default is manifest type of source) +Note: This flag can only be set when using the **dir** transport **--quiet, -q** @@ -113,5 +123,19 @@ Writing manifest to image destination Storing signatures ``` +This example pushes the rhel7 image to rhel7-dir with the "oci" manifest type +``` +# kpod push --format oci registry.access.redhat.com/rhel7 dir:rhel7-dir +Getting image source signatures +Copying blob sha256:9cadd93b16ff2a0c51ac967ea2abfadfac50cfa3af8b5bf983d89b8f8647f3e4 + 71.41 MB / 71.41 MB [======================================================] 9s +Copying blob sha256:4aa565ad8b7a87248163ce7dba1dd3894821aac97e846b932ff6b8ef9a8a508a + 1.21 KB / 1.21 KB [========================================================] 0s +Copying config sha256:f1b09a81455c351eaa484b61aacd048ab613c08e4c5d1da80c4c46301b03cf3b + 3.01 KB / 3.01 KB [========================================================] 0s +Writing manifest to image destination +Storing signatures +``` + ## SEE ALSO kpod(1), kpod-pull(1), crio(8), crio.conf(5), docker-login(1) diff --git a/libpod/common/common.go b/libpod/common/common.go index 8a7fbcd5e..6af3cd232 100644 --- a/libpod/common/common.go +++ b/libpod/common/common.go @@ -16,31 +16,33 @@ var ( ) // GetCopyOptions constructs a new containers/image/copy.Options{} struct from the given parameters -func GetCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDockerRegistry, destDockerRegistry *DockerRegistryOptions, signing SigningOptions, authFile string) *cp.Options { +func GetCopyOptions(reportWriter io.Writer, signaturePolicyPath string, srcDockerRegistry, destDockerRegistry *DockerRegistryOptions, signing SigningOptions, authFile, manifestType string, forceCompress bool) *cp.Options { if srcDockerRegistry == nil { srcDockerRegistry = &DockerRegistryOptions{} } if destDockerRegistry == nil { destDockerRegistry = &DockerRegistryOptions{} } - srcContext := srcDockerRegistry.GetSystemContext(signaturePolicyPath, authFile) - destContext := destDockerRegistry.GetSystemContext(signaturePolicyPath, authFile) + srcContext := srcDockerRegistry.GetSystemContext(signaturePolicyPath, authFile, forceCompress) + destContext := destDockerRegistry.GetSystemContext(signaturePolicyPath, authFile, forceCompress) return &cp.Options{ - RemoveSignatures: signing.RemoveSignatures, - SignBy: signing.SignBy, - ReportWriter: reportWriter, - SourceCtx: srcContext, - DestinationCtx: destContext, + RemoveSignatures: signing.RemoveSignatures, + SignBy: signing.SignBy, + ReportWriter: reportWriter, + SourceCtx: srcContext, + DestinationCtx: destContext, + ForceManifestMIMEType: manifestType, } } // GetSystemContext Constructs a new containers/image/types.SystemContext{} struct from the given signaturePolicy path -func GetSystemContext(signaturePolicyPath, authFilePath string) *types.SystemContext { +func GetSystemContext(signaturePolicyPath, authFilePath string, forceCompress bool) *types.SystemContext { sc := &types.SystemContext{} if signaturePolicyPath != "" { sc.SignaturePolicyPath = signaturePolicyPath } sc.AuthFilePath = authFilePath + sc.DirForceCompress = forceCompress return sc } diff --git a/libpod/common/docker_registry_options.go b/libpod/common/docker_registry_options.go index 24fa5c03e..f79ae0c54 100644 --- a/libpod/common/docker_registry_options.go +++ b/libpod/common/docker_registry_options.go @@ -22,13 +22,14 @@ type DockerRegistryOptions struct { // GetSystemContext constructs a new system context from the given signaturePolicy path and the // values in the DockerRegistryOptions -func (o DockerRegistryOptions) GetSystemContext(signaturePolicyPath, authFile string) *types.SystemContext { +func (o DockerRegistryOptions) GetSystemContext(signaturePolicyPath, authFile string, forceCompress bool) *types.SystemContext { sc := &types.SystemContext{ SignaturePolicyPath: signaturePolicyPath, DockerAuthConfig: o.DockerRegistryCreds, DockerCertPath: o.DockerCertPath, DockerInsecureSkipTLSVerify: o.DockerInsecureSkipTLSVerify, AuthFilePath: authFile, + DirForceCompress: forceCompress, } return sc } diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index d5da35c42..6107c2fdb 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -78,6 +78,10 @@ type CopyOptions struct { Reference string // ImageConfig is the Image spec for the image created when a tar archive is imported ImageConfig ociv1.Image + // ManifestMIMEType is the manifest type of the image when saving to a directory + ManifestMIMEType string + // ForceCompress compresses the image layers when saving to a directory using the dir transport if true + ForceCompress bool } // Image API @@ -643,7 +647,7 @@ func (r *Runtime) PullImage(imgName string, options CopyOptions) (string, error) signaturePolicyPath = options.SignaturePolicyPath } - sc := common.GetSystemContext(signaturePolicyPath, options.AuthFile) + sc := common.GetSystemContext(signaturePolicyPath, options.AuthFile, false) srcRef, err := alltransports.ParseImageName(imgName) if err != nil { @@ -664,7 +668,7 @@ func (r *Runtime) PullImage(imgName string, options CopyOptions) (string, error) } defer policyContext.Destroy() - copyOptions := common.GetCopyOptions(options.Writer, signaturePolicyPath, &options.DockerRegistryOptions, nil, options.SigningOptions, options.AuthFile) + copyOptions := common.GetCopyOptions(options.Writer, signaturePolicyPath, &options.DockerRegistryOptions, nil, options.SigningOptions, options.AuthFile, "", false) for _, imageInfo := range pullStructs { // Print the following statement only when pulling from a docker or atomic registry @@ -721,7 +725,7 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio signaturePolicyPath = options.SignaturePolicyPath } - sc := common.GetSystemContext(signaturePolicyPath, options.AuthFile) + sc := common.GetSystemContext(signaturePolicyPath, options.AuthFile, options.ForceCompress) policyContext, err := getPolicyContext(sc) if err != nil { @@ -735,7 +739,7 @@ func (r *Runtime) PushImage(source string, destination string, options CopyOptio return errors.Wrapf(err, "error getting source imageReference for %q", source) } - copyOptions := common.GetCopyOptions(options.Writer, signaturePolicyPath, nil, &options.DockerRegistryOptions, options.SigningOptions, options.AuthFile) + copyOptions := common.GetCopyOptions(options.Writer, signaturePolicyPath, nil, &options.DockerRegistryOptions, options.SigningOptions, options.AuthFile, options.ManifestMIMEType, options.ForceCompress) // Copy the image to the remote destination err = cp.Image(policyContext, dest, src, copyOptions) @@ -1004,7 +1008,7 @@ func (r *Runtime) ImportImage(path string, options CopyOptions) error { } var reference = options.Reference - sc := common.GetSystemContext("", "") + sc := common.GetSystemContext("", "", false) // if reference not given, get the image digest if reference == "" { @@ -1020,7 +1024,7 @@ func (r *Runtime) ImportImage(path string, options CopyOptions) error { } defer policyContext.Destroy() - copyOptions := common.GetCopyOptions(os.Stdout, "", nil, nil, common.SigningOptions{}, "") + copyOptions := common.GetCopyOptions(os.Stdout, "", nil, nil, common.SigningOptions{}, "", "", false) dest, err := is.Transport.ParseStoreReference(r.store, reference) if err != nil { diff --git a/test/kpod_push.bats b/test/kpod_push.bats index d9b1811cc..dee29a64c 100644 --- a/test/kpod_push.bats +++ b/test/kpod_push.bats @@ -84,3 +84,19 @@ function setup() { run ${KPOD_BINARY} $KPOD_OPTIONS rmi "$ALPINE" echo "$output" } + +@test "push with manifest type conversion" { + run bash -c "${KPOD_BINARY} $KPOD_OPTIONS push --format oci "${BB}" dir:my-dir" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "grep "application/vnd.oci.image.config.v1+json" my-dir/manifest.json" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "${KPOD_BINARY} $KPOD_OPTIONS push --compress --format v2s2 "${BB}" dir:my-dir" + echo "$output" + [ "$status" -eq 0 ] + run bash -c "grep "application/vnd.docker.distribution.manifest.v2+json" my-dir/manifest.json" + echo "$output" + [ "$status" -eq 0 ] + rm -rf my-dir +} -- cgit v1.2.3-54-g00ecf