Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions cli/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,24 @@ func load(configDir string) (*configfile.ConfigFile, error) {

file, err := os.Open(filename)
if err != nil {
// The config-directory must be a directory. If it is a regular file
// (for example, when DOCKER_CONFIG or --config points at the config
// file itself instead of the directory holding it), opening the file
// fails with a platform-specific error; ENOTDIR on unix, and a
// "not exist" error on Windows. Detect that situation here, so that
// we consistently report the actual problem instead of either
// silently ignoring it, or producing an error for a path that
// does not exist.
//
// The check must come before the [os.IsNotExist] branch below, as
// the Windows error satisfies it. The underlying error is not
// wrapped for the same reason: it would still satisfy
// [errors.Is](err, [fs.ErrNotExist]) on Windows, making this
// misconfiguration indistinguishable from "no config file present"
// for callers that check for it.
if fi, statErr := os.Stat(configDir); statErr == nil && !fi.IsDir() {
return configFile, fmt.Errorf("loading config file: config directory (%s) is not a directory", configDir)
}
if os.IsNotExist(err) {
// It is OK for no configuration file to be present, in which
// case we return a default struct.
Expand Down
28 changes: 28 additions & 0 deletions cli/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,34 @@ func TestMissingFile(t *testing.T) {
saveConfigAndValidateNewFormat(t, config, tmpHome)
}

// TestLoadConfigDirIsFile verifies that we produce an error if the
// config-directory is not a directory, which happens when DOCKER_CONFIG
// or --config is set to the config file itself instead of the directory
// containing it.
func TestLoadConfigDirIsFile(t *testing.T) {
cfgDir := filepath.Join(t.TempDir(), ConfigFileName)
err := os.WriteFile(cfgDir, []byte(`{}`), 0o644)
assert.NilError(t, err)

expected := fmt.Sprintf("loading config file: config directory (%s) is not a directory", cfgDir)

t.Run("Load", func(t *testing.T) {
_, err := Load(cfgDir)
assert.Check(t, is.Error(err, expected))
})

t.Run("LoadDefaultConfigFile", func(t *testing.T) {
oldDir := Dir()
SetDir(cfgDir)
t.Cleanup(func() { SetDir(oldDir) })

buffer := new(bytes.Buffer)
configFile := LoadDefaultConfigFile(buffer)
assert.Check(t, configFile != nil)
assert.Check(t, is.Contains(buffer.String(), "WARNING: Error "+expected))
})
}

// TestLoadDanglingSymlink verifies that we gracefully handle dangling symlinks.
//
// TODO(thaJeztah): consider whether we want dangling symlinks to be an error condition instead.
Expand Down
Loading