From 4489ddd8a5caa0181575620c56e912032582374b Mon Sep 17 00:00:00 2001 From: Daniel Strobusch <1847260+dastrobu@users.noreply.github.com> Date: Fri, 10 Jan 2020 12:41:43 +0100 Subject: [PATCH] add specific authfile options to copy (and sync) command. With additional prefixed flags for authfiles, it is possible to override the shared authfile flag to use different authfiles for src and dest registries. This is an important feature if the two registries have the same domain (but different paths) and require separate credentials. Closes #773. Signed-off-by: Daniel Strobusch <1847260+dastrobu@users.noreply.github.com> --- .gitignore | 3 +++ cmd/skopeo/utils.go | 20 +++++++++++++++-- cmd/skopeo/utils_test.go | 47 +++++++++++++++++++++++++++++++++++++++- completions/bash/skopeo | 2 ++ docs/skopeo-copy.1.md | 8 +++++++ docs/skopeo-sync.1.md | 8 +++++++ 6 files changed, 85 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 06713a69..54743cdc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ *.1 /layers-* /skopeo + +# ignore JetBrains IDEs (GoLand) config folder +.idea \ No newline at end of file diff --git a/cmd/skopeo/utils.go b/cmd/skopeo/utils.go index f66cfc35..c9c15415 100644 --- a/cmd/skopeo/utils.go +++ b/cmd/skopeo/utils.go @@ -56,6 +56,7 @@ func sharedImageFlags() ([]cli.Flag, *sharedImageOptions) { type dockerImageOptions struct { global *globalOptions // May be shared across several imageOptions instances. shared *sharedImageOptions // May be shared across several imageOptions instances. + authFilePath optionalString // Path to a */containers/auth.json (prefixed version to override shared image option). credsOption optionalString // username[:password] for accessing a registry dockerCertPath string // A directory using Docker-like *.{crt,cert,key} files for connecting to a registry or a daemon tlsVerify optionalBool // Require HTTPS and verify certificates (for docker: and docker-daemon:) @@ -87,7 +88,18 @@ func dockerImageFlags(global *globalOptions, shared *sharedImageOptions, flagPre credsOptionExtra += "," + credsOptionAlias } - return []cli.Flag{ + var flags []cli.Flag + if flagPrefix != "" { + // the non-prefixed flag is handled by a shared flag. + flags = append(flags, + cli.GenericFlag{ + Name: flagPrefix + "authfile", + Usage: "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json", + Value: newOptionalStringValue(&opts.authFilePath), + }, + ) + } + flags = append(flags, cli.GenericFlag{ Name: flagPrefix + "creds" + credsOptionExtra, Usage: "Use `USERNAME[:PASSWORD]` for accessing the registry", @@ -108,7 +120,8 @@ func dockerImageFlags(global *globalOptions, shared *sharedImageOptions, flagPre Usage: "Access the registry anonymously", Destination: &opts.noCreds, }, - }, &opts + ) + return flags, &opts } // imageFlags prepares a collection of CLI flags writing into imageOptions, and the managed imageOptions structure. @@ -143,6 +156,9 @@ func (opts *imageOptions) newSystemContext() (*types.SystemContext, error) { DockerDaemonCertPath: opts.dockerCertPath, SystemRegistriesConfPath: opts.global.registriesConfPath, } + if opts.dockerImageOptions.authFilePath.present { + ctx.AuthFilePath = opts.dockerImageOptions.authFilePath.value + } if opts.tlsVerify.present { ctx.DockerDaemonInsecureSkipTLSVerify = !opts.tlsVerify.value } diff --git a/cmd/skopeo/utils_test.go b/cmd/skopeo/utils_test.go index e4c8a77a..0fa4d145 100644 --- a/cmd/skopeo/utils_test.go +++ b/cmd/skopeo/utils_test.go @@ -54,6 +54,7 @@ func TestImageOptionsNewSystemContext(t *testing.T) { "--override-os", "overridden-os", }, []string{ "--authfile", "/srv/authfile", + "--dest-authfile", "/srv/dest-authfile", "--dest-cert-dir", "/srv/cert-dir", "--dest-shared-blob-dir", "/srv/shared-blob-dir", "--dest-daemon-host", "daemon-host.example.com", @@ -64,7 +65,7 @@ func TestImageOptionsNewSystemContext(t *testing.T) { require.NoError(t, err) assert.Equal(t, &types.SystemContext{ RegistriesDirPath: "/srv/registries.d", - AuthFilePath: "/srv/authfile", + AuthFilePath: "/srv/dest-authfile", ArchitectureChoice: "overridden-arch", OSChoice: "overridden-os", OCISharedBlobDirPath: "/srv/shared-blob-dir", @@ -180,3 +181,47 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) { _, err = opts.newSystemContext() assert.Error(t, err) } + +// since there is a shared authfile image option and a non-shared (prefixed) one, make sure the override logic +// works correctly. +func TestImageOptionsAuthfileOverride(t *testing.T) { + + for _, testCase := range []struct { + flagPrefix string + cmdFlags []string + expectedAuthfilePath string + }{ + // if there is no prefix, only authfile is allowed. + {"", + []string{ + "--authfile", "/srv/authfile", + }, "/srv/authfile"}, + // if authfile and dest-authfile is provided, dest-authfile wins + {"dest-", + []string{ + "--authfile", "/srv/authfile", + "--dest-authfile", "/srv/dest-authfile", + }, "/srv/dest-authfile", + }, + // if only the shared authfile is provided, authfile must be present in system context + {"dest-", + []string{ + "--authfile", "/srv/authfile", + }, "/srv/authfile", + }, + // if only the dest authfile is provided, dest-authfile must be present in system context + {"dest-", + []string{ + "--dest-authfile", "/srv/dest-authfile", + }, "/srv/dest-authfile", + }, + } { + opts := fakeImageOptions(t, testCase.flagPrefix, []string{}, testCase.cmdFlags) + res, err := opts.newSystemContext() + require.NoError(t, err) + + assert.Equal(t, &types.SystemContext{ + AuthFilePath: testCase.expectedAuthfilePath, + }, res) + } +} diff --git a/completions/bash/skopeo b/completions/bash/skopeo index c5e664c2..4797408e 100644 --- a/completions/bash/skopeo +++ b/completions/bash/skopeo @@ -37,6 +37,8 @@ _skopeo_supported_transports() { _skopeo_copy() { local options_with_args=" --authfile + --src-authfile + --dest-authfile --format -f --sign-by --src-creds --screds diff --git a/docs/skopeo-copy.1.md b/docs/skopeo-copy.1.md index 5efd7441..1d4653ea 100644 --- a/docs/skopeo-copy.1.md +++ b/docs/skopeo-copy.1.md @@ -28,6 +28,14 @@ the images in the list, and the list itself. Path of the authentication file. Default is ${XDG_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. +**--src-authfile** _path_ + +Path of the authentication file for the source registry. Uses path given by `--authfile`, if not provided. + +**--dest-authfile** _path_ + +Path of the authentication file for the destination registry. Uses path given by `--authfile`, if not provided. + **--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/docs/skopeo-sync.1.md b/docs/skopeo-sync.1.md index d26bfe66..ddeb8eef 100644 --- a/docs/skopeo-sync.1.md +++ b/docs/skopeo-sync.1.md @@ -37,6 +37,14 @@ name can be stored at _destination_. Path of the authentication file. Default is ${XDG\_RUNTIME\_DIR}/containers/auth.json, which is set using `podman login`. If the authorization state is not found there, $HOME/.docker/config.json is checked, which is set using `docker login`. +**--src-authfile** _path_ + +Path of the authentication file for the source registry. Uses path given by `--authfile`, if not provided. + +**--dest-authfile** _path_ + +Path of the authentication file for the destination registry. Uses path given by `--authfile`, if not provided. + **--src** _transport_ Transport for the source repository. **--dest** _transport_ Destination transport.