diff --git a/cmd/skopeo/utils.go b/cmd/skopeo/utils.go index 21d3e9b2..c4d2f702 100644 --- a/cmd/skopeo/utils.go +++ b/cmd/skopeo/utils.go @@ -91,6 +91,8 @@ type dockerImageOptions struct { deprecatedTLSVerify *deprecatedTLSVerifyOption // May be shared across several imageOptions instances, or nil. authFilePath optionalString // Path to a */containers/auth.json (prefixed version to override shared image option). credsOption optionalString // username[:password] for accessing a registry + userName optionalString // username for accessing a registry + password optionalString // password for accessing a registry registryToken optionalString // token to be used directly as a Bearer token when accessing the 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:) @@ -122,6 +124,8 @@ func dockerImageFlags(global *globalOptions, shared *sharedImageOptions, depreca fs.Var(newOptionalStringValue(&flags.authFilePath), flagPrefix+"authfile", "path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json") } fs.Var(newOptionalStringValue(&flags.credsOption), flagPrefix+"creds", "Use `USERNAME[:PASSWORD]` for accessing the registry") + fs.Var(newOptionalStringValue(&flags.userName), flagPrefix+"username", "Username for accessing the registry") + fs.Var(newOptionalStringValue(&flags.password), flagPrefix+"password", "Password for accessing the registry") if credsOptionAlias != "" { // This is horribly ugly, but we need to support the old option forms of (skopeo copy) for compatibility. // Don't add any more cases like this. @@ -180,12 +184,30 @@ func (opts *imageOptions) newSystemContext() (*types.SystemContext, error) { if opts.credsOption.present && opts.noCreds { return nil, errors.New("creds and no-creds cannot be specified at the same time") } + if opts.userName.present && opts.noCreds { + return nil, errors.New("username and no-creds cannot be specified at the same time") + } + if opts.credsOption.present && opts.userName.present { + return nil, errors.New("creds and username cannot be specified at the same time") + } + // if any of username or password is present, then both are expected to be present + if opts.userName.present != opts.password.present { + if opts.userName.present { + return nil, errors.New("password must be specified when username is specified") + } + return nil, errors.New("username must be specified when password is specified") + } if opts.credsOption.present { var err error ctx.DockerAuthConfig, err = getDockerAuth(opts.credsOption.value) if err != nil { return nil, err } + } else if opts.userName.present { + ctx.DockerAuthConfig = &types.DockerAuthConfig{ + Username: opts.userName.value, + Password: opts.password.value, + } } if opts.registryToken.present { ctx.DockerBearerRegistryToken = opts.registryToken.value diff --git a/cmd/skopeo/utils_test.go b/cmd/skopeo/utils_test.go index a6076b94..6c5fada3 100644 --- a/cmd/skopeo/utils_test.go +++ b/cmd/skopeo/utils_test.go @@ -197,6 +197,54 @@ func TestImageDestOptionsNewSystemContext(t *testing.T) { assert.Error(t, err) } +// TestImageOptionsUsernamePassword verifies that using the username and password +// options works as expected +func TestImageOptionsUsernamePassword(t *testing.T) { + for _, command := range []struct { + commandArgs []string + expectedAuthConfig *types.DockerAuthConfig // data to expect, or nil if an error is expected + }{ + // Set only username/password (without --creds), expected to pass + { + commandArgs: []string{"--dest-username", "foo", "--dest-password", "bar"}, + expectedAuthConfig: &types.DockerAuthConfig{Username: "foo", Password: "bar"}, + }, + // no username but set password, expect error + { + commandArgs: []string{"--dest-password", "foo"}, + expectedAuthConfig: nil, + }, + // set username but no password. expected to fail (we currently don't allow a user without password) + { + commandArgs: []string{"--dest-username", "bar"}, + expectedAuthConfig: nil, + }, + // set username with --creds, expected to fail + { + commandArgs: []string{"--dest-username", "bar", "--dest-creds", "hello:world", "--dest-password", "foo"}, + expectedAuthConfig: nil, + }, + // set username with --no-creds, expected to fail + { + commandArgs: []string{"--dest-username", "bar", "--dest-no-creds", "--dest-password", "foo"}, + expectedAuthConfig: nil, + }, + } { + opts := fakeImageDestOptions(t, "dest-", true, []string{}, command.commandArgs) + // parse the command options + res, err := opts.newSystemContext() + if command.expectedAuthConfig == nil { + assert.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, &types.SystemContext{ + DockerRegistryUserAgent: defaultUserAgent, + DockerAuthConfig: command.expectedAuthConfig, + }, res) + } + } +} + func TestTLSVerifyFlags(t *testing.T) { type systemContextOpts interface { // Either *imageOptions or *imageDestOptions newSystemContext() (*types.SystemContext, error) diff --git a/completions/bash/skopeo b/completions/bash/skopeo index 70681e1c..f5a28918 100644 --- a/completions/bash/skopeo +++ b/completions/bash/skopeo @@ -51,6 +51,10 @@ _skopeo_copy() { --dest-daemon-host --src-registry-token --dest-registry-token + --src-username + --src-password + --dest-username + --dest-password " local boolean_options=" @@ -87,6 +91,10 @@ _skopeo_sync() { --src-cert-dir --src-creds --src-registry-token + --src-username + --src-password + --dest-username + --dest-password " local boolean_options=" @@ -116,6 +124,8 @@ _skopeo_inspect() { --format --retry-times --registry-token + --username + --password " local boolean_options=" --config @@ -163,6 +173,8 @@ _skopeo_delete() { --creds --cert-dir --registry-token + --username + --password " local boolean_options=" --tls-verify @@ -183,6 +195,8 @@ _skopeo_layers() { --creds --cert-dir --registry-token + --username + --password " local boolean_options=" --tls-verify @@ -197,6 +211,8 @@ _skopeo_list_repository_tags() { --creds --cert-dir --registry-token + --username + --password " local boolean_options=" diff --git a/docs/skopeo-copy.1.md b/docs/skopeo-copy.1.md index c7f62946..807a6c6c 100644 --- a/docs/skopeo-copy.1.md +++ b/docs/skopeo-copy.1.md @@ -164,6 +164,22 @@ Bearer token for accessing the destination registry. The number of times to retry. Retry wait time will be exponentially increased based on the number of failed attempts. +**--src-username** + +The username to access the source registry. + +**--src-password** + +The password to access the source registry. + +**--dest-username** + +The username to access the destination registry. + +**--dest-password** + +The password to access the destination registry. + ## EXAMPLES To just copy an image from one registry to another: diff --git a/docs/skopeo-delete.1.md b/docs/skopeo-delete.1.md index 71d7a5d3..eca121dd 100644 --- a/docs/skopeo-delete.1.md +++ b/docs/skopeo-delete.1.md @@ -64,6 +64,14 @@ Directory to use to share blobs across OCI repositories. Require HTTPS and verify certificates when talking to the container registry or daemon. Default to registry.conf setting. +**--username** + +The username to access the registry. + +**--password** + +The password to access the registry. + ## EXAMPLES Mark image example/pause for deletion from the registry.example.com registry: diff --git a/docs/skopeo-inspect.1.md b/docs/skopeo-inspect.1.md index 0ceb117b..bfd84653 100644 --- a/docs/skopeo-inspect.1.md +++ b/docs/skopeo-inspect.1.md @@ -69,6 +69,14 @@ Directory to use to share blobs across OCI repositories. Require HTTPS and verify certificates when talking to the container registry or daemon. Default to registry.conf setting. +**--username** + +The username to access the registry. + +**--password** + +The password to access the registry. + ## EXAMPLES To review information for the image fedora from the docker.io registry: diff --git a/docs/skopeo-list-tags.1.md b/docs/skopeo-list-tags.1.md index 83499aaf..db189ce5 100644 --- a/docs/skopeo-list-tags.1.md +++ b/docs/skopeo-list-tags.1.md @@ -43,6 +43,14 @@ The number of times to retry. Retry wait time will be exponentially increased ba Require HTTPS and verify certificates when talking to the container registry or daemon. Default to registry.conf setting. +**--username** + +The username to access the registry. + +**--password** + +The password to access the registry. + ## REPOSITORY NAMES Repository names are transport-specific references as each transport may have its own concept of a "repository" and "tags". Currently, only the Docker transport is supported. diff --git a/docs/skopeo-sync.1.md b/docs/skopeo-sync.1.md index 720097cc..83e348ac 100644 --- a/docs/skopeo-sync.1.md +++ b/docs/skopeo-sync.1.md @@ -91,6 +91,22 @@ Print usage statement. **--keep-going** If any errors occur during copying of images, those errors are logged and the process continues syncing rest of the images and finally fails at the end. +**--src-username** + +The username to access the source registry. + +**--src-password** + +The password to access the source registry. + +**--dest-username** + +The username to access the destination registry. + +**--dest-password** + +The password to access the destination registry. + ## EXAMPLES ### Synchronizing to a local directory