diff --git a/cli/command/cli.go b/cli/command/cli.go index b5c1ccd996..f27d43a5f7 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -28,7 +28,6 @@ import ( "github.com/docker/docker/api/types/registry" "github.com/docker/docker/api/types/swarm" "github.com/docker/docker/client" - "github.com/docker/docker/errdefs" "github.com/docker/go-connections/tlsconfig" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -212,6 +211,9 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...Initialize if opts.Debug { debug.Enable() } + if opts.Context != "" && len(opts.Hosts) > 0 { + return errors.New("conflicting options: either specify --host or --context, not both") + } cli.loadConfigFile() @@ -222,10 +224,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...Initialize return ResolveDefaultContext(opts, cli.contextStoreConfig) }, } - cli.currentContext, err = resolveContextName(opts, cli.configFile, cli.contextStore) - if err != nil { - return err - } + cli.currentContext = resolveContextName(opts, cli.configFile) cli.dockerEndpoint, err = resolveDockerEndpoint(cli.contextStore, cli.currentContext) if err != nil { return errors.Wrap(err, "unable to resolve docker endpoint") @@ -243,6 +242,10 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...Initialize // NewAPIClientFromFlags creates a new APIClient from command line flags func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile.ConfigFile) (client.APIClient, error) { + if opts.Context != "" && len(opts.Hosts) > 0 { + return nil, errors.New("conflicting options: either specify --host or --context, not both") + } + storeConfig := DefaultContextStoreConfig() contextStore := &ContextStoreWithDefault{ Store: store.New(config.ContextStoreDir(), storeConfig), @@ -250,11 +253,7 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile. return ResolveDefaultContext(opts, storeConfig) }, } - contextName, err := resolveContextName(opts, configFile, contextStore) - if err != nil { - return nil, err - } - endpoint, err := resolveDockerEndpoint(contextStore, contextName) + endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile)) if err != nil { return nil, errors.Wrap(err, "unable to resolve docker endpoint") } @@ -363,11 +362,63 @@ func (cli *DockerCli) ContextStore() store.Store { return cli.contextStore } -// CurrentContext returns the current context name +// CurrentContext returns the current context name, based on flags, +// environment variables and the cli configuration file, in the following +// order of preference: +// +// 1. The "--context" command-line option. +// 2. The "DOCKER_CONTEXT" environment variable. +// 3. The current context as configured through the in "currentContext" +// field in the CLI configuration file ("~/.docker/config.json"). +// 4. If no context is configured, use the "default" context. +// +// # Fallbacks for backward-compatibility +// +// To preserve backward-compatibility with the "pre-contexts" behavior, +// the "default" context is used if: +// +// - The "--host" option is set +// - The "DOCKER_HOST" ([DefaultContextName]) environment variable is set +// to a non-empty value. +// +// In these cases, the default context is used, which uses the host as +// specified in "DOCKER_HOST", and TLS config from flags/env vars. +// +// Setting both the "--context" and "--host" flags is ambiguous and results +// in an error when the cli is started. +// +// CurrentContext does not validate if the given context exists or if it's +// valid; errors may occur when trying to use it. func (cli *DockerCli) CurrentContext() string { return cli.currentContext } +// CurrentContext returns the current context name, based on flags, +// environment variables and the cli configuration file. It does not +// validate if the given context exists or if it's valid; errors may +// occur when trying to use it. +// +// Refer to [DockerCli.CurrentContext] above for further details. +func resolveContextName(opts *cliflags.ClientOptions, config *configfile.ConfigFile) string { + if opts != nil && opts.Context != "" { + return opts.Context + } + if opts != nil && len(opts.Hosts) > 0 { + return DefaultContextName + } + if os.Getenv(client.EnvOverrideHost) != "" { + return DefaultContextName + } + if ctxName := os.Getenv("DOCKER_CONTEXT"); ctxName != "" { + return ctxName + } + if config != nil && config.CurrentContext != "" { + // We don't validate if this context exists: errors may occur when trying to use it. + return config.CurrentContext + } + return DefaultContextName +} + // DockerEndpoint returns the current docker endpoint func (cli *DockerCli) DockerEndpoint() docker.Endpoint { return cli.dockerEndpoint @@ -437,40 +488,6 @@ func UserAgent() string { return "Docker-Client/" + version.Version + " (" + runtime.GOOS + ")" } -// resolveContextName resolves the current context name with the following rules: -// - setting both --context and --host flags is ambiguous -// - if --context is set, use this value -// - if --host flag or DOCKER_HOST (client.EnvOverrideHost) is set, fallbacks to use the same logic as before context-store was added -// for backward compatibility with existing scripts -// - if DOCKER_CONTEXT is set, use this value -// - if Config file has a globally set "CurrentContext", use this value -// - fallbacks to default HOST, uses TLS config from flags/env vars -func resolveContextName(opts *cliflags.ClientOptions, config *configfile.ConfigFile, contextstore store.Reader) (string, error) { - if opts.Context != "" && len(opts.Hosts) > 0 { - return "", errors.New("Conflicting options: either specify --host or --context, not both") - } - if opts.Context != "" { - return opts.Context, nil - } - if len(opts.Hosts) > 0 { - return DefaultContextName, nil - } - if os.Getenv(client.EnvOverrideHost) != "" { - return DefaultContextName, nil - } - if ctxName := os.Getenv("DOCKER_CONTEXT"); ctxName != "" { - return ctxName, nil - } - if config != nil && config.CurrentContext != "" { - _, err := contextstore.GetMetadata(config.CurrentContext) - if errdefs.IsNotFound(err) { - return "", errors.Errorf("current context %q is not found on the file system, please check your config file at %s", config.CurrentContext, config.Filename) - } - return config.CurrentContext, err - } - return DefaultContextName, nil -} - var defaultStoreEndpoints = []store.NamedTypeGetter{ store.EndpointTypeGetter(docker.DockerEndpoint, func() interface{} { return &docker.EndpointMeta{} }), } diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index 9f4cc99ae3..dcd9103fb6 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -200,7 +200,8 @@ func TestInitializeFromClientHangs(t *testing.T) { go func() { cli := &DockerCli{client: apiClient, initTimeout: time.Millisecond} - cli.Initialize(flags.NewClientOptions()) + err := cli.Initialize(flags.NewClientOptions()) + assert.Check(t, err) close(initializedCh) }()