From 57dde8e9777f9d902594b45ab6cd71e51f4e47a8 Mon Sep 17 00:00:00 2001 From: Daniel J Walsh Date: Thu, 7 Jan 2021 11:43:28 -0500 Subject: [PATCH] Make buildah push support pushing manifests lists and digests Currently manifests just look like images in container storage. It is surprising to the user when they go to push the images that they end up failing, and have to use the buildah manifest push. This patch causes buildah push to failover to buildah manifest push if the image is a manifest. Signed-off-by: Daniel J Walsh --- cmd/buildah/manifest.go | 20 +++++++++++--------- cmd/buildah/push.go | 11 +++++++++++ contrib/completions/bash/buildah | 4 +++- docs/buildah-manifest-push.md | 8 ++++---- docs/buildah-push.md | 13 +++++++++++-- pkg/cli/common.go | 2 ++ tests/lists.bats | 8 ++++++++ 7 files changed, 50 insertions(+), 16 deletions(-) diff --git a/cmd/buildah/manifest.go b/cmd/buildah/manifest.go index bf9013c7d38..52beffb188d 100644 --- a/cmd/buildah/manifest.go +++ b/cmd/buildah/manifest.go @@ -17,6 +17,8 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/image/v5/types" + "github.com/containers/storage" digest "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" @@ -39,10 +41,6 @@ type manifestAnnotateOpts = struct { features, osFeatures, annotations []string } type manifestInspectOpts = struct{} -type manifestPushOpts = struct { - purge, quiet, all, tlsVerify, removeSignatures bool - authfile, certDir, creds, digestfile, format, signaturePolicy, signBy string -} func init() { var ( @@ -58,7 +56,7 @@ func init() { manifestRemoveOpts manifestRemoveOpts manifestAnnotateOpts manifestAnnotateOpts manifestInspectOpts manifestInspectOpts - manifestPushOpts manifestPushOpts + manifestPushOpts pushOptions ) manifestCommand := &cobra.Command{ Use: "manifest", @@ -188,7 +186,7 @@ func init() { } manifestPushCommand.SetUsageTemplate(UsageTemplate()) flags = manifestPushCommand.Flags() - flags.BoolVar(&manifestPushOpts.purge, "purge", false, "remove the manifest list if push succeeds") + flags.BoolVar(&manifestPushOpts.rm, "rm", false, "remove the manifest list if push succeeds") flags.BoolVar(&manifestPushOpts.all, "all", false, "also push the images in the list") flags.StringVar(&manifestPushOpts.authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&manifestPushOpts.certDir, "cert-dir", "", "use certificates at the specified path to access the registry") @@ -203,6 +201,7 @@ func init() { } flags.BoolVar(&manifestPushOpts.tlsVerify, "tls-verify", true, "require HTTPS and verify certificates when accessing the registry. TLS verification cannot be used when talking to an insecure registry.") flags.BoolVarP(&manifestPushOpts.quiet, "quiet", "q", false, "don't output progress information when pushing lists") + flags.SetNormalizeFunc(cli.AliasFlags) manifestCommand.AddCommand(manifestPushCommand) } @@ -630,7 +629,7 @@ func manifestInspectCmd(c *cobra.Command, args []string, opts manifestInspectOpt return nil } -func manifestPushCmd(c *cobra.Command, args []string, opts manifestPushOpts) error { +func manifestPushCmd(c *cobra.Command, args []string, opts pushOptions) error { if err := auth.CheckAuthFile(opts.authfile); err != nil { return err } @@ -659,12 +658,15 @@ func manifestPushCmd(c *cobra.Command, args []string, opts manifestPushOpts) err if err != nil { return err } - systemContext, err := parse.SystemContextFromOptions(c) if err != nil { return errors.Wrapf(err, "error building system context") } + return manifestPush(systemContext, store, listImageSpec, destSpec, opts) +} + +func manifestPush(systemContext *types.SystemContext, store storage.Store, listImageSpec, destSpec string, opts pushOptions) error { _, listImage, err := util.FindImage(store, "", systemContext, listImageSpec) if err != nil { return err @@ -712,7 +714,7 @@ func manifestPushCmd(c *cobra.Command, args []string, opts manifestPushOpts) err _, digest, err := list.Push(ctx, dest, options) - if err == nil && opts.purge { + if err == nil && opts.rm { _, err = store.DeleteImage(listImage.ID, true) } diff --git a/cmd/buildah/push.go b/cmd/buildah/push.go index d913cc223f0..3e0853095c1 100644 --- a/cmd/buildah/push.go +++ b/cmd/buildah/push.go @@ -15,6 +15,7 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/transports" "github.com/containers/image/v5/transports/alltransports" + "github.com/containers/storage" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -22,6 +23,7 @@ import ( ) type pushOptions struct { + all bool authfile string blobCache string certDir string @@ -29,6 +31,7 @@ type pushOptions struct { digestfile string disableCompression bool format string + rm bool quiet bool removeSignatures bool signaturePolicy string @@ -68,6 +71,7 @@ func init() { flags := pushCommand.Flags() flags.SetInterspersed(false) + flags.BoolVar(&opts.all, "all", false, "push all of the images referenced by the manifest list") flags.StringVar(&opts.authfile, "authfile", auth.GetDefaultAuthFile(), "path of the authentication file. Use REGISTRY_AUTH_FILE environment variable to override") flags.StringVar(&opts.blobCache, "blob-cache", "", "assume image blobs in the specified directory will be available for pushing") flags.StringVar(&opts.certDir, "cert-dir", "", "use certificates at the specified path to access the registry") @@ -76,6 +80,7 @@ func init() { flags.BoolVarP(&opts.disableCompression, "disable-compression", "D", false, "don't compress layers") flags.StringVarP(&opts.format, "format", "f", "", "manifest type (oci, v2s1, or v2s2) to use when saving image using the 'dir:' transport (default is manifest type of source)") flags.BoolVarP(&opts.quiet, "quiet", "q", false, "don't output progress information when pushing images") + flags.BoolVar(&opts.rm, "rm", false, "remove the manifest list if push succeeds") flags.BoolVarP(&opts.removeSignatures, "remove-signatures", "", false, "don't copy signatures when pushing image") flags.StringVar(&opts.signBy, "sign-by", "", "sign the image using a GPG key with the specified `FINGERPRINT`") flags.StringVar(&opts.signaturePolicy, "signature-policy", "", "`pathname` of signature policy file (not usually used)") @@ -195,6 +200,12 @@ func pushCmd(c *cobra.Command, args []string, iopts pushOptions) error { ref, digest, err := buildah.Push(getContext(), src, dest, options) if err != nil { + if errors.Cause(err) != storage.ErrImageUnknown { + // Image might be a manifest so attempt a manifest push + if manifestsErr := manifestPush(systemContext, store, src, destSpec, iopts); manifestsErr == nil { + return nil + } + } return util.GetFailureCause(err, errors.Wrapf(err, "error pushing image %q to %q", src, destSpec)) } if ref != nil { diff --git a/contrib/completions/bash/buildah b/contrib/completions/bash/buildah index 291abb78b5e..2bd73780834 100644 --- a/contrib/completions/bash/buildah +++ b/contrib/completions/bash/buildah @@ -632,12 +632,14 @@ return 1 _buildah_push() { local boolean_options=" + --all --help -h --disable-compression -D --quiet -q + --rm --tls-verify --remove-signatures " @@ -868,7 +870,7 @@ return 1 --digestfile --format -f - --purge + --rm --sign-by " diff --git a/docs/buildah-manifest-push.md b/docs/buildah-manifest-push.md index f46366c7d46..b5ae42b7b40 100644 --- a/docs/buildah-manifest-push.md +++ b/docs/buildah-manifest-push.md @@ -47,10 +47,6 @@ After copying the image, write the digest of the resulting image to the file. Manifest list type (oci or v2s2) to use when pushing the list (default is oci). -**--purge** - -Delete the manifest list or image index from local storage if pushing succeeds. - **--quiet**, **-q** Don't output progress information when pushing lists. @@ -59,6 +55,10 @@ Don't output progress information when pushing lists. Don't copy signatures when pushing images. +**--rm** + +Delete the manifest list or image index from local storage if pushing succeeds. + **--sign-by** *fingerprint* Sign the pushed images using the GPG key that matches the specified fingerprint. diff --git a/docs/buildah-push.md b/docs/buildah-push.md index 51df0a81dd1..6fd587ab56c 100644 --- a/docs/buildah-push.md +++ b/docs/buildah-push.md @@ -1,7 +1,7 @@ # buildah-push "1" "June 2017" "buildah" ## NAME -buildah\-push - Push an image from local storage to elsewhere. +buildah\-push - Push an image, manifest list or image index from local storage to elsewhere. ## SYNOPSIS **buildah push** [*options*] *image* [*destination*] @@ -42,6 +42,11 @@ If the transport part of DESTINATION is omitted, "docker://" is assumed. ## OPTIONS +**--all** + +If specified image is a manifest list or image index, push the images in addition to +the list or index itself. + **--authfile** *path* Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `buildah login`. @@ -86,6 +91,10 @@ When writing the output image, suppress progress output. Don't copy signatures when pushing images. +**--rm** + +When pushing a the manifest list or image index, delete them from local storage if pushing succeeds. + **--sign-by** *fingerprint* Sign the pushed image using the GPG key that matches the specified fingerprint. @@ -164,4 +173,4 @@ registries.conf is the configuration file which specifies which container regist Signature policy file. This defines the trust policy for container images. Controls which container registries can be used for image, and whether or not the tool should trust the images. ## SEE ALSO -buildah(1), buildah-login(1), containers-policy.json(5), docker-login(1), containers-registries.conf(5) +buildah(1), buildah-login(1), containers-policy.json(5), docker-login(1), containers-registries.conf(5), buildah-manifest(1) diff --git a/pkg/cli/common.go b/pkg/cli/common.go index 5b36ead9ed1..baeeca7b0c3 100644 --- a/pkg/cli/common.go +++ b/pkg/cli/common.go @@ -400,6 +400,8 @@ func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName { name = "arch" case "override-os": name = "os" + case "purge": + name = "rm" } return pflag.NormalizedName(name) } diff --git a/tests/lists.bats b/tests/lists.bats index 843b9d89623..b7bbce5a14e 100644 --- a/tests/lists.bats +++ b/tests/lists.bats @@ -103,6 +103,14 @@ IMAGE_LIST_S390X_INSTANCE_DIGEST=sha256:882a20ee0df7399a445285361d38b711c299ca09 run_buildah 125 manifest inspect foo } +@test "manifest-push-rm" { + run_buildah manifest create foo + run_buildah manifest add --arch=arm64 foo ${IMAGE_LIST} + run_buildah manifest inspect foo + run_buildah manifest push --signature-policy ${TESTSDIR}/policy.json --rm foo dir:${TESTDIR}/pushed + run_buildah 125 manifest inspect foo +} + @test "manifest-push should fail with nonexistent authfile" { run_buildah manifest create foo run_buildah manifest add --arch=arm64 foo ${IMAGE_LIST}