diff --git a/cli/flags/options.go b/cli/flags/options.go index f24992f5bc..309cb4616f 100644 --- a/cli/flags/options.go +++ b/cli/flags/options.go @@ -1,6 +1,7 @@ package flags import ( + "errors" "fmt" "os" "path/filepath" @@ -53,6 +54,39 @@ var ( dockerTLS = os.Getenv(EnvEnableTLS) != "" ) +// hostVar is used for the '--host' / '-H' flag to set [ClientOptions.Hosts]. +// The [ClientOptions.Hosts] field is a slice because it was originally shared +// with the daemon config. However, the CLI only allows for a single host to +// be specified. +// +// hostVar presents itself as a "string", but stores the value in a string +// slice. It produces an error when trying to set multiple values, matching +// the check in [getServerHost]. +// +// [getServerHost]: https://github.com/docker/cli/blob/7eab668982645def1cd46fe1b60894cba6fd17a4/cli/command/cli.go#L542-L551 +type hostVar struct { + dst *[]string + set bool +} + +func (h *hostVar) String() string { + if h.dst == nil || len(*h.dst) == 0 { + return "" + } + return (*h.dst)[0] +} + +func (h *hostVar) Set(s string) error { + if h.set { + return errors.New("specify only one -H") + } + *h.dst = []string{s} + h.set = true + return nil +} + +func (*hostVar) Type() string { return "string" } + // ClientOptions are the options used to configure the client cli. type ClientOptions struct { Debug bool @@ -95,7 +129,7 @@ func (o *ClientOptions) InstallFlags(flags *pflag.FlagSet) { // TODO(thaJeztah): show the default host. // TODO(thaJeztah): this should be a string, not an "array" as we only allow a single host. - flags.StringArrayVarP(&o.Hosts, "host", "H", nil, "Daemon socket to connect to") + flags.VarP(&hostVar{dst: &o.Hosts}, "host", "H", "Daemon socket to connect to") flags.StringVarP(&o.Context, "context", "c", "", `Name of the context to use to connect to the daemon (overrides `+client.EnvOverrideHost+` env var and default context set with "docker context use")`) } diff --git a/docs/reference/commandline/docker.md b/docs/reference/commandline/docker.md index 00ab5cce62..03dbddc8cc 100644 --- a/docs/reference/commandline/docker.md +++ b/docs/reference/commandline/docker.md @@ -69,18 +69,18 @@ The base command for the Docker CLI. ### Options -| Name | Type | Default | Description | -|:---------------------------------|:--------------|:-------------------------|:--------------------------------------------------------------------------------------------------------------------------------------| -| `--config` | `string` | `/root/.docker` | Location of client config files | -| `-c`, `--context` | `string` | | Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with `docker context use`) | -| `-D`, `--debug` | `bool` | | Enable debug mode | -| [`-H`](#host), [`--host`](#host) | `stringArray` | | Daemon socket to connect to | -| `-l`, `--log-level` | `string` | `info` | Set the logging level (`debug`, `info`, `warn`, `error`, `fatal`) | -| `--tls` | `bool` | | Use TLS; implied by --tlsverify | -| `--tlscacert` | `string` | `/root/.docker/ca.pem` | Trust certs signed only by this CA | -| `--tlscert` | `string` | `/root/.docker/cert.pem` | Path to TLS certificate file | -| `--tlskey` | `string` | `/root/.docker/key.pem` | Path to TLS key file | -| `--tlsverify` | `bool` | | Use TLS and verify the remote | +| Name | Type | Default | Description | +|:---------------------------------|:---------|:-------------------------|:--------------------------------------------------------------------------------------------------------------------------------------| +| `--config` | `string` | `/root/.docker` | Location of client config files | +| `-c`, `--context` | `string` | | Name of the context to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with `docker context use`) | +| `-D`, `--debug` | `bool` | | Enable debug mode | +| [`-H`](#host), [`--host`](#host) | `string` | | Daemon socket to connect to | +| `-l`, `--log-level` | `string` | `info` | Set the logging level (`debug`, `info`, `warn`, `error`, `fatal`) | +| `--tls` | `bool` | | Use TLS; implied by --tlsverify | +| `--tlscacert` | `string` | `/root/.docker/ca.pem` | Trust certs signed only by this CA | +| `--tlscert` | `string` | `/root/.docker/cert.pem` | Path to TLS certificate file | +| `--tlskey` | `string` | `/root/.docker/key.pem` | Path to TLS key file | +| `--tlsverify` | `bool` | | Use TLS and verify the remote | diff --git a/e2e/cli-plugins/flags_test.go b/e2e/cli-plugins/flags_test.go index 69fe6d6ad3..0cb24ba248 100644 --- a/e2e/cli-plugins/flags_test.go +++ b/e2e/cli-plugins/flags_test.go @@ -1,6 +1,7 @@ package cliplugins import ( + "fmt" "os" "testing" @@ -91,9 +92,9 @@ func TestGlobalArgsOnlyParsedOnce(t *testing.T) { // This is checking the precondition wrt -H mentioned in the function comment name: "fails-if-H-used-twice", args: []string{"-H", dh, "-H", dh, "version", "-f", "{{.Client.Version}}"}, - expectedExitCode: 1, + expectedExitCode: 125, expectedOut: icmd.None, - expectedErr: "Specify only one -H", + expectedErr: fmt.Sprintf(`invalid argument %q for "-H, --host" flag: specify only one -H`, dh), }, { name: "builtin",