Add dry-run mode to skopeo-sync

Taking over #1459 to drive it to completion.

Signed-off-by: Ted Wexler <twexler@bloomberg.net>
Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh 2022-03-28 08:40:12 -04:00
parent e0f0869151
commit c233a6dcb1
No known key found for this signature in database
GPG Key ID: A2DF901DABE2C028
4 changed files with 60 additions and 15 deletions

View File

@ -42,6 +42,7 @@ type syncOptions struct {
destination string // Destination registry name destination string // Destination registry name
scoped bool // When true, namespace copied images at destination using the source repository 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 all bool // Copy all of the images if an image in the source is a list
dryRun bool // Don't actually copy anything, just output what it would have done
preserveDigests bool // Preserve digests during sync preserveDigests bool // Preserve digests during sync
keepGoing bool // Whether or not to abort the sync if there are any errors during syncing the images keepGoing bool // Whether or not to abort the sync if there are any errors during syncing the images
} }
@ -110,6 +111,7 @@ See skopeo-sync(1) for details.
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")
flags.BoolVarP(&opts.all, "all", "a", false, "Copy all images if SOURCE-IMAGE is a list") flags.BoolVarP(&opts.all, "all", "a", false, "Copy all images if SOURCE-IMAGE is a list")
flags.BoolVar(&opts.dryRun, "dry-run", false, "Run without actually copying data")
flags.BoolVar(&opts.preserveDigests, "preserve-digests", false, "Preserve digests of images and lists") flags.BoolVar(&opts.preserveDigests, "preserve-digests", false, "Preserve digests of images and lists")
flags.BoolVarP(&opts.keepGoing, "keep-going", "", false, "Do not abort the sync if any image copy fails") flags.BoolVarP(&opts.keepGoing, "keep-going", "", false, "Do not abort the sync if any image copy fails")
flags.AddFlagSet(&sharedFlags) flags.AddFlagSet(&sharedFlags)
@ -597,6 +599,10 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) (retErr error) {
} }
errorsPresent := false errorsPresent := false
imagesNumber := 0 imagesNumber := 0
if opts.dryRun {
logrus.Warn("Running in dry-run mode")
}
for _, srcRepo := range srcRepoList { for _, srcRepo := range srcRepoList {
options.SourceCtx = srcRepo.Context options.SourceCtx = srcRepo.Context
for counter, ref := range srcRepo.ImageRefs { for counter, ref := range srcRepo.ImageRefs {
@ -623,29 +629,37 @@ func (opts *syncOptions) run(args []string, stdout io.Writer) (retErr error) {
return err return err
} }
logrus.WithFields(logrus.Fields{ fromToFields := logrus.Fields{
"from": transports.ImageName(ref), "from": transports.ImageName(ref),
"to": transports.ImageName(destRef), "to": transports.ImageName(destRef),
}).Infof("Copying image ref %d/%d", counter+1, len(srcRepo.ImageRefs)) }
if opts.dryRun {
if err = retry.RetryIfNecessary(ctx, func() error { logrus.WithFields(fromToFields).Infof("Would have copied image ref %d/%d", counter+1, len(srcRepo.ImageRefs))
_, err = copy.Image(ctx, policyContext, destRef, ref, &options) } else {
return err logrus.WithFields(fromToFields).Infof("Copying image ref %d/%d", counter+1, len(srcRepo.ImageRefs))
}, opts.retryOpts); err != nil { if err = retry.RetryIfNecessary(ctx, func() error {
if !opts.keepGoing { _, err = copy.Image(ctx, policyContext, destRef, ref, &options)
return errors.Wrapf(err, "Error copying ref %q", transports.ImageName(ref)) return err
}, opts.retryOpts); err != nil {
if !opts.keepGoing {
return errors.Wrapf(err, "Error copying ref %q", transports.ImageName(ref))
}
// log the error, keep a note that there was a failure and move on to the next
// image ref
errorsPresent = true
logrus.WithError(err).Errorf("Error copying ref %q", transports.ImageName(ref))
continue
} }
// log the error, keep a note that there was a failure and move on to the next
// image ref
errorsPresent = true
logrus.WithError(err).Errorf("Error copying ref %q", transports.ImageName(ref))
continue
} }
imagesNumber++ imagesNumber++
} }
} }
logrus.Infof("Synced %d images from %d sources", imagesNumber, len(srcRepoList)) if opts.dryRun {
logrus.Infof("Would have synced %d images from %d sources", imagesNumber, len(srcRepoList))
} else {
logrus.Infof("Synced %d images from %d sources", imagesNumber, len(srcRepoList))
}
if !errorsPresent { if !errorsPresent {
return nil return nil
} }

View File

@ -106,6 +106,7 @@ _skopeo_sync() {
--all --all
--dest-no-creds --dest-no-creds
--dest-tls-verify --dest-tls-verify
--dry-run
--remove-signatures --remove-signatures
--scoped --scoped
--src-no-creds --src-no-creds

View File

@ -50,6 +50,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. Path of the authentication file for the destination registry. Uses path given by `--authfile`, if not provided.
**--dry-run**
Run the sync without actually copying data to the destination.
**--src**, **-s** _transport_ Transport for the source repository. **--src**, **-s** _transport_ Transport for the source repository.
**--dest**, **-d** _transport_ Destination transport. **--dest**, **-d** _transport_ Destination transport.

26
systemtest/080-sync.bats Normal file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env bats
#
# Sync tests
#
load helpers
function setup() {
standard_setup
}
@test "sync: --dry-run" {
local remote_image=quay.io/libpod/busybox:latest
local dir=$TESTDIR/dir
run_skopeo sync --dry-run --src docker --dest dir --scoped $remote_image $dir
expect_output --substring "Would have copied image"
expect_output --substring "from=\"docker://${remote_image}\" to=\"dir:${dir}/${remote_image}\""
expect_output --substring "Would have synced 1 images from 1 sources"
}
teardown() {
standard_teardown
}
# vim: filetype=sh