Skip to content

Commit a193465

Browse files
committed
Defer error from referencing a non-exisiting context from the config file to 1st API call
When a user removes a context from the store without using the CLI (by removing the context directory directly in the store), it can trigger an issue where the CLI does not work at all (even for changing the current context) anymore without editing the config file by hand. With Docker Desktop and WSL 2, we now link the wsl 2 distros context store to the windows one, so that a context created from Windows can be used in WSL 2 and vice-versa. However we keep the CLI config files independant (at least for now), so this issue can be triggered quite often. This commit fixes that by changing the way we handle context missing error: if the resolved context name comes from the config file and refers a missing context, the error is reported when the API is actually called instead of at the CLI initialization. Thus, local-only commands continue to work (shuch as `context use`, `context ls`,...) Signed-off-by: Simon Ferquel <simon.ferquel@docker.com>
1 parent af2c31c commit a193465

1 file changed

Lines changed: 35 additions & 15 deletions

File tree

cli/command/cli.go

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"io"
66
"io/ioutil"
7+
"net"
78
"os"
89
"path/filepath"
910
"runtime"
@@ -38,6 +39,16 @@ import (
3839
"github.com/theupdateframework/notary/passphrase"
3940
)
4041

42+
type contextNameSource int
43+
44+
const (
45+
contextFallbackToDockerHost contextNameSource = iota
46+
contextFromFlag
47+
contextFromEnv
48+
contextFromConfigFile
49+
contextDefault
50+
)
51+
4152
// Streams is an interface which exposes the standard input and output streams
4253
type Streams interface {
4354
In() *streams.In
@@ -251,12 +262,25 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions, ops ...Initialize
251262
return ResolveDefaultContext(opts.Common, cli.ConfigFile(), cli.contextStoreConfig, cli.Err())
252263
},
253264
}
254-
cli.currentContext, err = resolveContextName(opts.Common, cli.configFile, cli.contextStore)
265+
var contextSource contextNameSource
266+
cli.currentContext, contextSource, err = resolveContextName(opts.Common, cli.configFile)
255267
if err != nil {
256268
return err
257269
}
258270
cli.dockerEndpoint, err = resolveDockerEndpoint(cli.contextStore, cli.currentContext)
259-
if err != nil {
271+
if store.IsErrContextDoesNotExist(err) && contextSource == contextFromConfigFile {
272+
if cli.client == nil {
273+
// populate a client that always fails with "context not found"
274+
// this is to defer the context not found error until API is really used to allow
275+
// operations like `docker context use` or `docker context ls`
276+
cli.client, err = client.NewClientWithOpts(client.WithDialContext(func(ctx context.Context, network, addr string) (net.Conn, error) {
277+
return nil, errors.Errorf("Current context %q is not found on the file system, please check your config file at %s", cli.configFile.CurrentContext, cli.configFile.Filename)
278+
}))
279+
if err != nil {
280+
return err
281+
}
282+
}
283+
} else if err != nil {
260284
return errors.Wrap(err, "unable to resolve docker endpoint")
261285
}
262286

@@ -292,7 +316,7 @@ func NewAPIClientFromFlags(opts *cliflags.CommonOptions, configFile *configfile.
292316
return ResolveDefaultContext(opts, configFile, storeConfig, ioutil.Discard)
293317
},
294318
}
295-
contextName, err := resolveContextName(opts, configFile, store)
319+
contextName, _, err := resolveContextName(opts, configFile)
296320
if err != nil {
297321
return nil, err
298322
}
@@ -530,30 +554,26 @@ func UserAgent() string {
530554
// - if DOCKER_CONTEXT is set, use this value
531555
// - if Config file has a globally set "CurrentContext", use this value
532556
// - fallbacks to default HOST, uses TLS config from flags/env vars
533-
func resolveContextName(opts *cliflags.CommonOptions, config *configfile.ConfigFile, contextstore store.Reader) (string, error) {
557+
func resolveContextName(opts *cliflags.CommonOptions, config *configfile.ConfigFile) (string, contextNameSource, error) {
534558
if opts.Context != "" && len(opts.Hosts) > 0 {
535-
return "", errors.New("Conflicting options: either specify --host or --context, not both")
559+
return "", contextDefault, errors.New("Conflicting options: either specify --host or --context, not both")
536560
}
537561
if opts.Context != "" {
538-
return opts.Context, nil
562+
return opts.Context, contextFromFlag, nil
539563
}
540564
if len(opts.Hosts) > 0 {
541-
return DefaultContextName, nil
565+
return DefaultContextName, contextFallbackToDockerHost, nil
542566
}
543567
if _, present := os.LookupEnv("DOCKER_HOST"); present {
544-
return DefaultContextName, nil
568+
return DefaultContextName, contextFallbackToDockerHost, nil
545569
}
546570
if ctxName, ok := os.LookupEnv("DOCKER_CONTEXT"); ok {
547-
return ctxName, nil
571+
return ctxName, contextFromEnv, nil
548572
}
549573
if config != nil && config.CurrentContext != "" {
550-
_, err := contextstore.GetMetadata(config.CurrentContext)
551-
if store.IsErrContextDoesNotExist(err) {
552-
return "", errors.Errorf("Current context %q is not found on the file system, please check your config file at %s", config.CurrentContext, config.Filename)
553-
}
554-
return config.CurrentContext, err
574+
return config.CurrentContext, contextFromConfigFile, nil
555575
}
556-
return DefaultContextName, nil
576+
return DefaultContextName, contextDefault, nil
557577
}
558578

559579
var defaultStoreEndpoints = []store.NamedTypeGetter{

0 commit comments

Comments
 (0)