diff --git a/cli/command/cli.go b/cli/command/cli.go index ca329f5d6280..392b65a67569 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -32,6 +32,23 @@ import ( const defaultInitTimeout = 2 * time.Second +// Reasons to show what determined active context +var ContextReasons = struct { + ExplicitContextFlag string + HostFlag string + HostEnv string + ContextEnv string + ConfigFile string + Default string +}{ + ExplicitContextFlag: "from --context flag", + HostFlag: "from --host flag", + HostEnv: "context disabled because DOCKER_HOST environment variable is set", + ContextEnv: "DOCKER_CONTEXT environment variable set", + ConfigFile: "Current context from docker config file", + Default: "from default context", +} + // Streams is an interface which exposes the standard input and output streams type Streams interface { In() *streams.In @@ -53,6 +70,7 @@ type Cli interface { BuildKitEnabled() (bool, error) ContextStore() store.Store CurrentContext() string + CurrentContextReason() string DockerEndpoint() docker.Endpoint TelemetryClient } @@ -62,22 +80,23 @@ type Cli interface { // constructor to make sure they are properly initialized with defaults // set. type DockerCli struct { - configFile *configfile.ConfigFile - options *cliflags.ClientOptions - in *streams.In - out *streams.Out - err *streams.Out - client client.APIClient - serverInfo ServerInfo - contentTrust bool - contextStore store.Store - currentContext string - init sync.Once - initErr error - dockerEndpoint docker.Endpoint - contextStoreConfig *store.Config - initTimeout time.Duration - res telemetryResource + configFile *configfile.ConfigFile + options *cliflags.ClientOptions + in *streams.In + out *streams.Out + err *streams.Out + client client.APIClient + serverInfo ServerInfo + contentTrust bool + contextStore store.Store + currentContext string + currentContextReason string + init sync.Once + initErr error + dockerEndpoint docker.Endpoint + contextStoreConfig *store.Config + initTimeout time.Duration + res telemetryResource // baseCtx is the base context used for internal operations. In the future // this may be replaced by explicitly passing a context to functions that @@ -264,7 +283,7 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...CLIOption) cli.options = opts cli.configFile = config.LoadDefaultConfigFile(cli.err) - cli.currentContext = resolveContextName(cli.options, cli.configFile) + cli.currentContext, cli.currentContextReason = resolveContextName(cli.options, cli.configFile) cli.contextStore = &ContextStoreWithDefault{ Store: store.New(config.ContextStoreDir(), *cli.contextStoreConfig), Resolver: func() (*DefaultContext, error) { @@ -308,7 +327,8 @@ func NewAPIClientFromFlags(opts *cliflags.ClientOptions, configFile *configfile. return ResolveDefaultContext(opts, storeConfig) }, } - endpoint, err := resolveDockerEndpoint(contextStore, resolveContextName(opts, configFile)) + ctxName, _ := resolveContextName(opts, configFile) + endpoint, err := resolveDockerEndpoint(contextStore, ctxName) if err != nil { return nil, fmt.Errorf("unable to resolve docker endpoint: %w", err) } @@ -448,30 +468,34 @@ func (cli *DockerCli) CurrentContext() string { return cli.currentContext } +func (cli *DockerCli) CurrentContextReason() string { + return cli.currentContextReason +} + // 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, cfg *configfile.ConfigFile) string { +func resolveContextName(opts *cliflags.ClientOptions, cfg *configfile.ConfigFile) (string, string) { if opts != nil && opts.Context != "" { - return opts.Context + return opts.Context, ContextReasons.ExplicitContextFlag } if opts != nil && len(opts.Hosts) > 0 { - return DefaultContextName + return DefaultContextName, ContextReasons.HostFlag } if os.Getenv(client.EnvOverrideHost) != "" { - return DefaultContextName + return DefaultContextName, ContextReasons.HostEnv } if ctxName := os.Getenv(EnvOverrideContext); ctxName != "" { - return ctxName + return ctxName, ContextReasons.ContextEnv } if cfg != nil && cfg.CurrentContext != "" { // We don't validate if this context exists: errors may occur when trying to use it. - return cfg.CurrentContext + return cfg.CurrentContext, ContextReasons.ConfigFile } - return DefaultContextName + return DefaultContextName, ContextReasons.Default } // DockerEndpoint returns the current docker endpoint diff --git a/cli/command/system/info.go b/cli/command/system/info.go index 0fcca3d95ef9..bd4c6caf5b6f 100644 --- a/cli/command/system/info.go +++ b/cli/command/system/info.go @@ -107,6 +107,8 @@ func runInfo(ctx context.Context, cmd *cobra.Command, dockerCli command.Cli, opt if opts.format == "" { info.UserName = dockerCli.ConfigFile().AuthConfigs[registry.IndexServer].Username info.ClientInfo.APIVersion = dockerCli.CurrentVersion() + info.ClientInfo.Docker_Host = dockerCli.DockerEndpoint().EndpointMeta.Host + info.ClientInfo.Context_Reason = dockerCli.CurrentContextReason() return errors.Join(prettyPrintInfo(dockerCli, info), serverConnErr) } @@ -218,8 +220,9 @@ func prettyPrintInfo(streams command.Streams, info dockerInfo) error { func prettyPrintClientInfo(streams command.Streams, info clientInfo) { fprintlnNonEmpty(streams.Out(), " Version: ", info.Version) - fprintln(streams.Out(), " Context: ", info.Context) + fprintln(streams.Out(), " Context:", info.Context+" ("+info.Context_Reason+")") fprintln(streams.Out(), " Debug Mode:", info.Debug) + fprintln(streams.Out(), " Docker Host:", info.Docker_Host) if len(info.Plugins) > 0 { fprintln(streams.Out(), " Plugins:") diff --git a/cli/command/system/version.go b/cli/command/system/version.go index 5f5937c59a31..b0bff2b6d61d 100644 --- a/cli/command/system/version.go +++ b/cli/command/system/version.go @@ -31,7 +31,8 @@ Client:{{if ne .Platform nil}}{{if ne .Platform.Name ""}} {{.Platform.Name}}{{en Git commit: {{.GitCommit}} Built: {{.BuildTime}} OS/Arch: {{.Os}}/{{.Arch}} - Context: {{.Context}} + Context: {{.Context}} ({{.Context_Reason}}) + Docker Host: {{.Docker_Host}} {{- end}} {{- if ne .Server nil}}{{with .Server}} @@ -82,6 +83,8 @@ type clientVersion struct { Arch string `json:"Arch,omitempty"` BuildTime string `json:"BuildTime,omitempty"` Context string `json:"Context"` + Context_Reason string `json:"Context_Reason"` + Docker_Host string `json:"Docker_Host"` } // newClientVersion constructs a new clientVersion. If a dockerCLI is @@ -104,6 +107,8 @@ func newClientVersion(contextName string, dockerCli command.Cli) clientVersion { } if dockerCli != nil { v.APIVersion = dockerCli.CurrentVersion() + v.Context_Reason = dockerCli.CurrentContextReason() + v.Docker_Host = dockerCli.DockerEndpoint().EndpointMeta.Host } return v }