The `QuotedString` option was added in [moby@e4c1f07] and [moby@abe32de] to work around a regression in Docker 1.13 that caused `docker-machine` to fail. `docker-machine` produced instructions on how to set up a cli to connect to the Machine it produced. These instructions used quotes around the paths for TLS certificates, but with an `=` for the flag's values instead of a space; due to this the shell would not handle stripping quotes, so the CLI would now get the value including quotes. Preserving quotes in such cases is expected (and standard behavior), but versions of Docker before 1.13 used a custom "mflag" package for flag parsing, and that package contained custom handling for quotes (added in [moby@0e9c40e]). For other flags, this problem could be solved by the user, but as these instructions were produced by `docker-machine`'s `config` command, an exception was made for the `--tls-xxx` flags. From [moby-29761]: > The flag trimming behaviour is really unusual, and I would say unexpected. > I think removing it is generally the right idea. Since we have one very > common case where it's necessary for backwards compatibility we need to > add a special case, but I don't think we should apply that case to every > flag. The `QuotedString` implementation has various limitations, as it doesn't follow the same handling of quotes as a shell would. Given that Docker Machine reached EOL a long time ago and other options, such as `docker context`, have been added to configure the CLI to connect to a specific host (with corresponding TLS configuration), we should remove the special handling for these flags, as it's inconsitent with all other flags, and not worth maintaining for a tool that no longer exists. This patch deprecates the `QuotedString` option and removes its use. A temporary, non-exported copy is added, but will be removed in the next release. [moby-29761]: https://github.com/moby/moby/issues/29761#issuecomment-270211265 [moby@e4c1f07]:e4c1f07729[moby@abe32de]:abe32de6b4[moby@0e9c40e]:0e9c40eb82[moby@c79a169]:c79a169a35Signed-off-by: Sebastiaan van Stijn <github@gone.nl> (cherry picked from commit187a942a88) Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
179 lines
6.0 KiB
Go
179 lines
6.0 KiB
Go
package flags
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/docker/cli/cli/config"
|
|
"github.com/docker/cli/opts"
|
|
"github.com/docker/docker/client"
|
|
"github.com/docker/go-connections/tlsconfig"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/spf13/pflag"
|
|
)
|
|
|
|
const (
|
|
// EnvEnableTLS is the name of the environment variable that can be used
|
|
// to enable TLS for client connections. When set to a non-empty value, TLS
|
|
// is enabled for API connections using TCP. For backward-compatibility, this
|
|
// environment-variable can only be used to enable TLS, not to disable.
|
|
//
|
|
// Note that TLS is always enabled implicitly if the "--tls-verify" option
|
|
// or "DOCKER_TLS_VERIFY" ([github.com/docker/docker/client.EnvTLSVerify])
|
|
// env var is set to, which could be to either enable or disable TLS certification
|
|
// validation. In both cases, TLS is enabled but, depending on the setting,
|
|
// with verification disabled.
|
|
EnvEnableTLS = "DOCKER_TLS"
|
|
|
|
// DefaultCaFile is the default filename for the CA pem file
|
|
DefaultCaFile = "ca.pem"
|
|
// DefaultKeyFile is the default filename for the key pem file
|
|
DefaultKeyFile = "key.pem"
|
|
// DefaultCertFile is the default filename for the cert pem file
|
|
DefaultCertFile = "cert.pem"
|
|
// FlagTLSVerify is the flag name for the TLS verification option
|
|
FlagTLSVerify = "tlsverify"
|
|
// FormatHelp describes the --format flag behavior for list commands
|
|
FormatHelp = `Format output using a custom template:
|
|
'table': Print output in table format with column headers (default)
|
|
'table TEMPLATE': Print output in table format using the given Go template
|
|
'json': Print in JSON format
|
|
'TEMPLATE': Print output using the given Go template.
|
|
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates`
|
|
// InspectFormatHelp describes the --format flag behavior for inspect commands
|
|
InspectFormatHelp = `Format output using a custom template:
|
|
'json': Print in JSON format
|
|
'TEMPLATE': Print output using the given Go template.
|
|
Refer to https://docs.docker.com/go/formatting/ for more information about formatting output with templates`
|
|
)
|
|
|
|
var (
|
|
dockerCertPath = os.Getenv(client.EnvOverrideCertPath)
|
|
dockerTLSVerify = os.Getenv(client.EnvTLSVerify) != ""
|
|
dockerTLS = os.Getenv(EnvEnableTLS) != ""
|
|
)
|
|
|
|
// ClientOptions are the options used to configure the client cli.
|
|
type ClientOptions struct {
|
|
Debug bool
|
|
Hosts []string
|
|
LogLevel string
|
|
TLS bool
|
|
TLSVerify bool
|
|
TLSOptions *tlsconfig.Options
|
|
Context string
|
|
ConfigDir string
|
|
}
|
|
|
|
// NewClientOptions returns a new ClientOptions.
|
|
func NewClientOptions() *ClientOptions {
|
|
return &ClientOptions{}
|
|
}
|
|
|
|
// InstallFlags adds flags for the common options on the FlagSet
|
|
func (o *ClientOptions) InstallFlags(flags *pflag.FlagSet) {
|
|
configDir := config.Dir()
|
|
if dockerCertPath == "" {
|
|
dockerCertPath = configDir
|
|
}
|
|
|
|
flags.StringVar(&o.ConfigDir, "config", configDir, "Location of client config files")
|
|
flags.BoolVarP(&o.Debug, "debug", "D", false, "Enable debug mode")
|
|
flags.StringVarP(&o.LogLevel, "log-level", "l", "info", `Set the logging level ("debug", "info", "warn", "error", "fatal")`)
|
|
flags.BoolVar(&o.TLS, "tls", dockerTLS, "Use TLS; implied by --tlsverify")
|
|
flags.BoolVar(&o.TLSVerify, FlagTLSVerify, dockerTLSVerify, "Use TLS and verify the remote")
|
|
|
|
o.TLSOptions = &tlsconfig.Options{
|
|
CAFile: filepath.Join(dockerCertPath, DefaultCaFile),
|
|
CertFile: filepath.Join(dockerCertPath, DefaultCertFile),
|
|
KeyFile: filepath.Join(dockerCertPath, DefaultKeyFile),
|
|
}
|
|
tlsOptions := o.TLSOptions
|
|
flags.Var("edString{&tlsOptions.CAFile}, "tlscacert", "Trust certs signed only by this CA")
|
|
flags.Var("edString{&tlsOptions.CertFile}, "tlscert", "Path to TLS certificate file")
|
|
flags.Var("edString{&tlsOptions.KeyFile}, "tlskey", "Path to TLS key file")
|
|
|
|
// opts.ValidateHost is not used here, so as to allow connection helpers
|
|
hostOpt := opts.NewNamedListOptsRef("hosts", &o.Hosts, nil)
|
|
flags.VarP(hostOpt, "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")`)
|
|
}
|
|
|
|
// SetDefaultOptions sets default values for options after flag parsing is
|
|
// complete
|
|
func (o *ClientOptions) SetDefaultOptions(flags *pflag.FlagSet) {
|
|
// Regardless of whether the user sets it to true or false, if they
|
|
// specify --tlsverify at all then we need to turn on TLS
|
|
// TLSVerify can be true even if not set due to DOCKER_TLS_VERIFY env var, so we need
|
|
// to check that here as well
|
|
if flags.Changed(FlagTLSVerify) || o.TLSVerify {
|
|
o.TLS = true
|
|
}
|
|
|
|
if !o.TLS {
|
|
o.TLSOptions = nil
|
|
} else {
|
|
tlsOptions := o.TLSOptions
|
|
tlsOptions.InsecureSkipVerify = !o.TLSVerify
|
|
|
|
// Reset CertFile and KeyFile to empty string if the user did not specify
|
|
// the respective flags and the respective default files were not found.
|
|
if !flags.Changed("tlscert") {
|
|
if _, err := os.Stat(tlsOptions.CertFile); os.IsNotExist(err) {
|
|
tlsOptions.CertFile = ""
|
|
}
|
|
}
|
|
if !flags.Changed("tlskey") {
|
|
if _, err := os.Stat(tlsOptions.KeyFile); os.IsNotExist(err) {
|
|
tlsOptions.KeyFile = ""
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetLogLevel sets the logrus logging level
|
|
func SetLogLevel(logLevel string) {
|
|
if logLevel != "" {
|
|
lvl, err := logrus.ParseLevel(logLevel)
|
|
if err != nil {
|
|
_, _ = fmt.Fprintln(os.Stderr, "Unable to parse logging level:", logLevel)
|
|
os.Exit(1)
|
|
}
|
|
logrus.SetLevel(lvl)
|
|
} else {
|
|
logrus.SetLevel(logrus.InfoLevel)
|
|
}
|
|
}
|
|
|
|
type quotedString struct {
|
|
value *string
|
|
}
|
|
|
|
func (s *quotedString) Set(val string) error {
|
|
*s.value = trimQuotes(val)
|
|
return nil
|
|
}
|
|
|
|
func (*quotedString) Type() string {
|
|
return "string"
|
|
}
|
|
|
|
func (s *quotedString) String() string {
|
|
return *s.value
|
|
}
|
|
|
|
func trimQuotes(value string) string {
|
|
if len(value) < 2 {
|
|
return value
|
|
}
|
|
lastIndex := len(value) - 1
|
|
for _, char := range []byte{'\'', '"'} {
|
|
if value[0] == char && value[lastIndex] == char {
|
|
return value[1:lastIndex]
|
|
}
|
|
}
|
|
return value
|
|
}
|