This commit adds support for the oauth [device-code](https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow)
login flow when authenticating against the official registry.
This is achieved by adding `cli/internal/oauth`, which contains code to manage
interacting with the Docker OAuth tenant (`login.docker.com`), including launching
the device-code flow, refreshing access using the refresh-token, and logging out.
The `OAuthManager` introduced here is also made available through the `command.Cli`
interface method `OAuthManager()`.
In order to maintain compatibility with any clients manually accessing
the credentials through `~/.docker/config.json` or via credential
helpers, the added `OAuthManager` uses the retrieved access token to
automatically generate a PAT with Hub, and store that in the
credentials.
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
This test was just incorrect (and testing incorrect
behavior): it was checking that `docker run` exited with a `context
canceled` error after signalling the CLI/cancelling the command's
context, but this was incorrect (and was fixed in
991b1303da - which was when this test
started failing).
However, since this test assertion was happening inside of a goroutine,
it would sometimes pass if this assertion didn't get to run before the
test suite terminated. It was flaky because sometimes this assertion
inside the goroutine did get to execute, but after the test finished
execution, which is a big no-no.
As an aside, assertions inside goroutines are generally bad, and `govet`
even has a linter for this (but it only catches `t.Fatal` and `t.FailNow`
calls and not `assert.Xx`.
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
Such as with `docker run`, if a user CTRL-Cs while attached to a
container, we should forward the signal and wait for the exit from
`ContainerWait`, instead of just returning.
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
In 3f0d90a2a9 we introduced a global
signal handler and made sure all the contexts passed into command
execution get (appropriately) cancelled when we get a SIGINT.
Due to that change, and how we use this context during `docker attach`,
we started to return the context cancelation error when a user signals
the running `docker attach`.
Since this is the intended behavior, we shouldn't return an error, so
this commit adds checks to ignore this specific error in this case.
Also adds a regression test.
Signed-off-by: Laura Brehm <laurabrehm@hey.com>
This environment variable allows for setting additional headers
to be sent by the client. Headers set through this environment
variable are added to headers set through the config-file (through
the HttpHeaders field).
This environment variable can be used in situations where headers
must be set for a specific invocation of the CLI, but should not
be set by default, and therefore cannot be set in the config-file.
WARNING: If both config and environment-variable are set, the environment
variable currently overrides all headers set in the configuration file.
This behavior may change in a future update, as we are considering the
environment variable to be appending to existing headers (and to only
override headers with the same name).
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
The fileStore itself is aware that it's insecure, so we can make it
responsible for printing the warning. It's not "perfect", as we use
`os.Stderr` unconditionally (not `dockerCli.Err()`), but probably won't
make a difference in _most_ cases.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
- Add an empty line before the warning to separate it from the command's output
- Use the `/go/` redirect URL that we have available.
- Put quotes around the filename used for storage.
- Use present tense for the message, as the message is printed while saving.
- User "credentials" instead of "password" for consistency with "credentials-store"
Before:
docker login myregistry.example.com
Username: thajeztah
Password:
WARNING! Your password will be stored unencrypted in /root/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credential-stores
Login Succeeded
After:
docker login myregistry.example.com
Username: thajeztah
Password:
WARNING! Your credentials are stored unencrypted in '/root/.docker/config.json'.
Configure a credential helper to remove this warning. See
https://docs.docker.com/go/credential-store/
Login Succeeded
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
If we fail to save credentials, make sure that the error about saving
doesn't get lost in the warning about credentials being stored unencrypted.
Also discard errors about printing the warning, as those would be unlikely,
and if they would occur, probably would fail to be printed as well.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
The `reportError` utility was present because cli.StatusError would print
the error decorated with `Status: <error-message>, Code: <exit-code>`.
That was not desirable in many cases as it would mess-up the output. To
prevent this, the CLI had code to check for an empty `Status` (error message)
in which case the error would be "ignored" (and only used for the exit-status),
and the `reportError` utility would be used to manually print a custom error
message before returning the error.
Now that bca2090061 fixed the output format
of `cli.StatusError`, and 3dd6fc365d and
350a0b68a9 no longer discard these error,
we can get rid of this utility, and just set the error-message for
the status-error.
This patch:
- Introduces a `withHelp` which takes care of decorating errors with
a "Run --help" hint for the user.
- Introduces a `toStatusError` utility that detects certain errors in
the container to assign a corresponding exit-code (these error-codes
can be used to distinguish "client" errors from "container" errors).
- Removes the `reportError` utility, and removes code that manually
printed errors before returning.
Behavior is mostly unmodified, with the exception of some slight reformatting
of the errors:
- `withHelp` adds a `docker:` prefix to the error, to indicate the error
is produced by the `docker` command. This prefix was already present
in most cases.
- The "--help" hint is slightly updated ("Run 'docker run --help' for
more information" instead of "See 'docker run --help'"), to make it
more clear that it's a "call to action".
- An empty is added before the "--help" hint to separate it better from
the error-message.
Before this patch:
$ docker run --pull=invalid-option alpine
docker: invalid pull option: 'invalid-option': must be one of "always", "missing" or "never".
See 'docker run --help'.
$ echo $?
125
$ docker run --rm alpine nosuchcommand
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "nosuchcommand": executable file not found in $PATH: unknown.
$ echo $?
127
With this patch:
$ docker run --pull=invalid-option alpine
docker: invalid pull option: 'invalid-option': must be one of "always", "missing" or "never"
Run 'docker run --help' for more information
$ echo $?
125
$ docker run --rm alpine nosuchcommand
docker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "nosuchcommand": executable file not found in $PATH: unknown.
Run 'docker run --help' for more information
$ echo $?
127
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
With this patch:
docker run --volumes-from amazing_nobel
amazing_cannon boring_wozniak determined_banzai
elegant_solomon reverent_booth amazing_nobel
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
"docker run" and "docker create" are mostly identical, so we can copy
the same completion functions,
We could possibly create a utility for this (similar to `addFlags()` which
configures both commands with the flags they share). I considered combining
his with `addFlags()`, but that utility is also used in various tests, in
which we don't need this feature, so keeping that for a future exercise.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
It's an alias for cobra.FixedCompletions but takes a variadic list
of strings, so that it's not needed to construct an array for this.
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
EnvVarNames offers completion for environment-variable names. This
completion can be used for "--env" and "--build-arg" flags, which
allow obtaining the value of the given environment-variable if present
in the local environment, so we only should complete the names of the
environment variables, and not their value. This also prevents the
completion script from printing values of environment variables
containing sensitive values.
For example;
export MY_VAR=hello
docker run --rm --env MY_VAR alpine printenv MY_VAR
hello
Before this patch:
docker run --env GO
GO111MODULE=auto GOLANG_VERSION=1.21.12 GOPATH=/go GOTOOLCHAIN=local
With this patch:
docker run --env GO<tab>
GO111MODULE GOLANG_VERSION GOPATH GOTOOLCHAIN
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This is just a convenience function to allow defining completion to
use the default (complete with filenames and directories).
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Improve the output for these validation errors:
- Removes the short command description from the output. This information
does not provide much useful help, and distracts from the error message.
- Reduces punctuation, and
- Prefixes the error message with the binary / root-command name
(usually `docker:`) to be consistent with other similar errors.
- Adds an empty line between the error-message and the "call to action"
(`Run 'docker volume --help'...` in the example below). This helps
separating the error message and "usage" from the call-to-action.
Before this patch:
$ docker volume ls one two three
"docker volume ls" accepts no arguments.
See 'docker volume ls --help'.
Usage: docker volume ls [OPTIONS]
List volumes
$ docker volume create one two three
"docker volume create" requires at most 1 argument.
See 'docker volume create --help'.
Usage: docker volume create [OPTIONS] [VOLUME]
Create a volume
With this patch:
$ docker volume ls one two three
docker: 'docker volume ls' accepts no arguments
Usage: docker volume ls [OPTIONS]
Run 'docker volume ls --help' for more information
$ docker voludocker volume create one two three
docker: 'docker volume create' requires at most 1 argument
Usage: docker volume create [OPTIONS] [VOLUME]
SRun 'docker volume create --help' for more information
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This error didn't do a great job at formatting. If a StatusError was
produced without a Status message, it would print a very non-informative
error, with information missing.
Let's update the output:
- If a status-message is provided; print just that (after all the
status code is something that can be found from the shell, e.g.
through `echo $?` in Bash).
- If no status-message is provided: print a message more similar to
Go's `exec.ExecError`, which uses `os.rocessState.String()` (see [1]).
Before this patch, an error without custom status would print:
Status: , Code: 2
After this patch:
exit status 2
In situations where a custom error-message is provided, the error-message
is print as-is, whereas before this patch, the message got combined with
the `Status:` and `Code:`, which resulted in some odd output.
Before this patch:
docker volume --no-such-flag
Status: unknown flag: --no-such-flag
See 'docker volume --help'.
Usage: docker volume COMMAND
Manage volumes
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove unused local volumes
rm Remove one or more volumes
update Update a volume (cluster volumes only)
Run 'docker volume COMMAND --help' for more information on a command.
, Code: 125
With this patch, the error is shown as-is;
docker volume --no-such-flag
unknown flag: --no-such-flag
See 'docker volume --help'.
Usage: docker volume COMMAND
Manage volumes
Commands:
create Create a volume
inspect Display detailed information on one or more volumes
ls List volumes
prune Remove unused local volumes
rm Remove one or more volumes
update Update a volume (cluster volumes only)
Run 'docker volume COMMAND --help' for more information on a command.
While the exit-code is no longer printed, it's still properly handled;
echo $?
125
[1]: 82c14346d8/src/os/exec_posix.go (L107-L135)
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Trying to make the logic slightly clearer, and adding a custom
message for the skip,
Before this:
=== RUN TestSplitCpArg/absolute_path_with_drive
cp_test.go:184: tc.os == "windows" && runtime.GOOS != "windows" || tc.os == "linux" && runtime.GOOS == "windows"
After this:
=== RUN TestSplitCpArg/absolute_path_with_drive
cp_test.go:184: skipping windows test on non-windows platform
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Introduce a (non-exported) ipamOptions that collects all options for
creating a network.IPAM, so that this utility is more atomic (potentially
even could be moved to a separate package and exported).
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This makes a quick pass through our tests;
Discard output/err
----------------------------------------------
Many tests were testing for error-conditions, but didn't discard output.
This produced a lot of noise when running the tests, and made it hard
to discover if there were actual failures, or if the output was expected.
For example:
=== RUN TestConfigCreateErrors
Error: "create" requires exactly 2 arguments.
See 'create --help'.
Usage: create [OPTIONS] CONFIG file|- [flags]
Create a config from a file or STDIN
Error: "create" requires exactly 2 arguments.
See 'create --help'.
Usage: create [OPTIONS] CONFIG file|- [flags]
Create a config from a file or STDIN
Error: error creating config
--- PASS: TestConfigCreateErrors (0.00s)
And after discarding output:
=== RUN TestConfigCreateErrors
--- PASS: TestConfigCreateErrors (0.00s)
Use sub-tests where possible
----------------------------------------------
Some tests were already set-up to use test-tables, and even had a usable
name (or in some cases "error" to check for). Change them to actual sub-
tests. Same test as above, but now with sub-tests and output discarded:
=== RUN TestConfigCreateErrors
=== RUN TestConfigCreateErrors/requires_exactly_2_arguments
=== RUN TestConfigCreateErrors/requires_exactly_2_arguments#01
=== RUN TestConfigCreateErrors/error_creating_config
--- PASS: TestConfigCreateErrors (0.00s)
--- PASS: TestConfigCreateErrors/requires_exactly_2_arguments (0.00s)
--- PASS: TestConfigCreateErrors/requires_exactly_2_arguments#01 (0.00s)
--- PASS: TestConfigCreateErrors/error_creating_config (0.00s)
PASS
It's not perfect in all cases (in the above, there's duplicate "expected"
errors, but Go conveniently adds "#01" for the duplicate). There's probably
also various tests I missed that could still use the same changes applied;
we can improve these in follow-ups.
Set cmd.Args to prevent test-failures
----------------------------------------------
When running tests from my IDE, it compiles the tests before running,
then executes the compiled binary to run the tests. Cobra doesn't like
that, because in that situation `os.Args` is taken as argument for the
command that's executed. The command that's tested now sees the test-
flags as arguments (`-test.v -test.run ..`), which causes various tests
to fail ("Command XYZ does not accept arguments").
# compile the tests:
go test -c -o foo.test
# execute the test:
./foo.test -test.v -test.run TestFoo
=== RUN TestFoo
Error: "foo" accepts no arguments.
The Cobra maintainers ran into the same situation, and for their own
use have added a special case to ignore `os.Args` in these cases;
https://github.com/spf13/cobra/blob/v1.8.1/command.go#L1078-L1083
args := c.args
// Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155
if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
args = os.Args[1:]
}
Unfortunately, that exception is too specific (only checks for `cobra.test`),
so doesn't automatically fix the issue for other test-binaries. They did
provide a `cmd.SetArgs()` utility for this purpose
https://github.com/spf13/cobra/blob/v1.8.1/command.go#L276-L280
// SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden
// particularly useful when testing.
func (c *Command) SetArgs(a []string) {
c.args = a
}
And the fix is to explicitly set the command's args to an empty slice to
prevent Cobra from falling back to using `os.Args[1:]` as arguments.
cmd := newSomeThingCommand()
cmd.SetArgs([]string{})
Some tests already take this issue into account, and I updated some tests
for this, but there's likely many other ones that can use the same treatment.
Perhaps the Cobra maintainers would accept a contribution to make their
condition less specific and to look for binaries ending with a `.test`
suffix (which is what compiled binaries usually are named as).
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>