From a32be320cbfe92e7a7e58183449126e070758ec4 Mon Sep 17 00:00:00 2001 From: Colin Walters Date: Tue, 27 Apr 2021 16:24:45 -0400 Subject: [PATCH] copy: Add --digestfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `podman push` gained this a while ago, and we want it here for the same reason. Motivated by closing a race condition in ostree-rs-ext: https://github.com/ostreedev/ostree-rs-ext/blob/17a991050c5bf371633695b0bbd5cdbb3d717bca/lib/src/container/export.rs#L85 Co-authored-by: Miloslav Trmač --- cmd/skopeo/copy.go | 19 +++++++++++++++++-- docs/skopeo-copy.1.md | 4 ++++ integration/copy_test.go | 15 +++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/cmd/skopeo/copy.go b/cmd/skopeo/copy.go index 4a261bac..a7105b00 100644 --- a/cmd/skopeo/copy.go +++ b/cmd/skopeo/copy.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "io" + "io/ioutil" "strings" "github.com/containers/common/pkg/retry" @@ -27,6 +28,7 @@ type copyOptions struct { additionalTags []string // For docker-archive: destinations, in addition to the name:tag specified as destination, also add these removeSignatures bool // Do not copy signatures from the source image signByFingerprint string // Sign the image using a GPG key with the specified fingerprint + digestFile string // Write digest to this file format 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 @@ -69,6 +71,7 @@ See skopeo(1) section "IMAGE NAMES" for the expected format flags.BoolVarP(&opts.all, "all", "a", false, "Copy all images if SOURCE-IMAGE is a list") 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") flags.VarP(newOptionalStringValue(&opts.format), "format", "f", `MANIFEST TYPE (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source)`) flags.StringSliceVar(&opts.encryptionKeys, "encryption-key", []string{}, "*Experimental* key with the encryption protocol to use needed to encrypt the image (e.g. jwe:/path/to/key.pem)") flags.IntSliceVar(&opts.encryptLayer, "encrypt-layer", []int{}, "*Experimental* the 0-indexed layer indices, with support for negative indexing (e.g. 0 is the first layer, -1 is the last layer)") @@ -184,7 +187,7 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error { } return retry.RetryIfNecessary(ctx, func() error { - _, err = copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{ + manifestBytes, err := copy.Image(ctx, policyContext, destRef, srcRef, ©.Options{ RemoveSignatures: opts.removeSignatures, SignBy: opts.signByFingerprint, ReportWriter: stdout, @@ -196,6 +199,18 @@ func (opts *copyOptions) run(args []string, stdout io.Writer) error { OciEncryptLayers: encLayers, OciEncryptConfig: encConfig, }) - return err + if err != nil { + return err + } + if opts.digestFile != "" { + manifestDigest, err := manifest.Digest(manifestBytes) + if err != nil { + return err + } + if err = ioutil.WriteFile(opts.digestFile, []byte(manifestDigest.String()), 0644); err != nil { + return fmt.Errorf("Failed to write digest to file %q: %w", opts.digestFile, err) + } + } + return nil }, opts.retryOpts) } diff --git a/docs/skopeo-copy.1.md b/docs/skopeo-copy.1.md index baee7df4..c294c76f 100644 --- a/docs/skopeo-copy.1.md +++ b/docs/skopeo-copy.1.md @@ -42,6 +42,10 @@ Path of the authentication file for the source registry. Uses path given by `--a Path of the authentication file for the destination registry. Uses path given by `--authfile`, if not provided. +**--digestfile** _path_ + +After copying the image, write the digest of the resulting image to the file. + **--format, -f** _manifest-type_ Manifest type (oci, v2s1, or v2s2) to use when saving image to directory using the 'dir:' transport (default is manifest type of source) **--quiet, -q** suppress output information when copying images diff --git a/integration/copy_test.go b/integration/copy_test.go index b86b3289..2b336955 100644 --- a/integration/copy_test.go +++ b/integration/copy_test.go @@ -246,6 +246,21 @@ func (s *CopySuite) TestCopyWithManifestListDigest(c *check.C) { c.Assert(out, check.Equals, "") } +func (s *CopySuite) TestCopyWithDigestfileOutput(c *check.C) { + tempdir, err := ioutil.TempDir("", "tempdir") + c.Assert(err, check.IsNil) + defer os.RemoveAll(tempdir) + dir1, err := ioutil.TempDir("", "copy-manifest-list-digest-dir") + c.Assert(err, check.IsNil) + defer os.RemoveAll(dir1) + digestOutPath := filepath.Join(tempdir, "digest.txt") + assertSkopeoSucceeds(c, "", "copy", "--digestfile="+digestOutPath, knownListImage, "dir:"+dir1) + readDigest, err := ioutil.ReadFile(digestOutPath) + c.Assert(err, check.IsNil) + _, err = digest.Parse(string(readDigest)) + c.Assert(err, check.IsNil) +} + func (s *CopySuite) TestCopyWithManifestListStorageDigest(c *check.C) { storage, err := ioutil.TempDir("", "copy-manifest-list-storage-digest") c.Assert(err, check.IsNil)