mirror of
https://github.com/containers/skopeo.git
synced 2025-04-27 11:01:18 +00:00
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:
parent
e0f0869151
commit
c233a6dcb1
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
26
systemtest/080-sync.bats
Normal 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
|
Loading…
Reference in New Issue
Block a user