When attempting to load a config-file that exists, but is not accessible for the current user, we should not discard the error. This patch makes sure that the error is returned by Load(), but does not yet change LoadDefaultConfigFile, as this requires a change in signature. Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
146 lines
4.8 KiB
Go
146 lines
4.8 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/docker/cli/cli/config/configfile"
|
|
"github.com/docker/cli/cli/config/credentials"
|
|
"github.com/docker/cli/cli/config/types"
|
|
"github.com/docker/docker/pkg/homedir"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
// EnvOverrideConfigDir is the name of the environment variable that can be
|
|
// used to override the location of the client configuration files (~/.docker).
|
|
//
|
|
// It takes priority over the default, but can be overridden by the "--config"
|
|
// command line option.
|
|
EnvOverrideConfigDir = "DOCKER_CONFIG"
|
|
|
|
// ConfigFileName is the name of the client configuration file inside the
|
|
// config-directory.
|
|
ConfigFileName = "config.json"
|
|
configFileDir = ".docker"
|
|
contextsDir = "contexts"
|
|
)
|
|
|
|
var (
|
|
initConfigDir = new(sync.Once)
|
|
configDir string
|
|
)
|
|
|
|
// resetConfigDir is used in testing to reset the "configDir" package variable
|
|
// and its sync.Once to force re-lookup between tests.
|
|
func resetConfigDir() {
|
|
configDir = ""
|
|
initConfigDir = new(sync.Once)
|
|
}
|
|
|
|
// Dir returns the directory the configuration file is stored in
|
|
func Dir() string {
|
|
initConfigDir.Do(func() {
|
|
configDir = os.Getenv(EnvOverrideConfigDir)
|
|
if configDir == "" {
|
|
configDir = filepath.Join(homedir.Get(), configFileDir)
|
|
}
|
|
})
|
|
return configDir
|
|
}
|
|
|
|
// ContextStoreDir returns the directory the docker contexts are stored in
|
|
func ContextStoreDir() string {
|
|
return filepath.Join(Dir(), contextsDir)
|
|
}
|
|
|
|
// SetDir sets the directory the configuration file is stored in
|
|
func SetDir(dir string) {
|
|
// trigger the sync.Once to synchronise with Dir()
|
|
initConfigDir.Do(func() {})
|
|
configDir = filepath.Clean(dir)
|
|
}
|
|
|
|
// Path returns the path to a file relative to the config dir
|
|
func Path(p ...string) (string, error) {
|
|
path := filepath.Join(append([]string{Dir()}, p...)...)
|
|
if !strings.HasPrefix(path, Dir()+string(filepath.Separator)) {
|
|
return "", errors.Errorf("path %q is outside of root config directory %q", path, Dir())
|
|
}
|
|
return path, nil
|
|
}
|
|
|
|
// LoadFromReader is a convenience function that creates a ConfigFile object from
|
|
// a reader. It returns an error if configData is malformed.
|
|
func LoadFromReader(configData io.Reader) (*configfile.ConfigFile, error) {
|
|
configFile := configfile.ConfigFile{
|
|
AuthConfigs: make(map[string]types.AuthConfig),
|
|
}
|
|
err := configFile.LoadFromReader(configData)
|
|
return &configFile, err
|
|
}
|
|
|
|
// Load reads the configuration file ([ConfigFileName]) from the given directory.
|
|
// If no directory is given, it uses the default [Dir]. A [*configfile.ConfigFile]
|
|
// is returned containing the contents of the configuration file, or a default
|
|
// struct if no configfile exists in the given location.
|
|
//
|
|
// Load returns an error if a configuration file exists in the given location,
|
|
// but cannot be read, or is malformed. Consumers must handle errors to prevent
|
|
// overwriting an existing configuration file.
|
|
func Load(configDir string) (*configfile.ConfigFile, error) {
|
|
if configDir == "" {
|
|
configDir = Dir()
|
|
}
|
|
return load(configDir)
|
|
}
|
|
|
|
func load(configDir string) (*configfile.ConfigFile, error) {
|
|
filename := filepath.Join(configDir, ConfigFileName)
|
|
configFile := configfile.New(filename)
|
|
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
// It is OK for no configuration file to be present, in which
|
|
// case we return a default struct.
|
|
return configFile, nil
|
|
}
|
|
// Any other error happening when failing to read the file must be returned.
|
|
return configFile, errors.Wrap(err, "loading config file")
|
|
}
|
|
defer file.Close()
|
|
err = configFile.LoadFromReader(file)
|
|
if err != nil {
|
|
err = errors.Wrapf(err, "loading config file: %s: ", filename)
|
|
}
|
|
return configFile, err
|
|
}
|
|
|
|
// LoadDefaultConfigFile attempts to load the default config file and returns
|
|
// a reference to the ConfigFile struct. If none is found or when failing to load
|
|
// the configuration file, it initializes a default ConfigFile struct. If no
|
|
// credentials-store is set in the configuration file, it attempts to discover
|
|
// the default store to use for the current platform.
|
|
//
|
|
// Important: LoadDefaultConfigFile prints a warning to stderr when failing to
|
|
// load the configuration file, but otherwise ignores errors. Consumers should
|
|
// consider using [Load] (and [credentials.DetectDefaultStore]) to detect errors
|
|
// when updating the configuration file, to prevent discarding a (malformed)
|
|
// configuration file.
|
|
func LoadDefaultConfigFile(stderr io.Writer) *configfile.ConfigFile {
|
|
configFile, err := load(Dir())
|
|
if err != nil {
|
|
// FIXME(thaJeztah): we should not proceed here to prevent overwriting existing (but malformed) config files; see https://github.com/docker/cli/issues/5075
|
|
_, _ = fmt.Fprintln(stderr, "WARNING: Error", err)
|
|
}
|
|
if !configFile.ContainsAuth() {
|
|
configFile.CredentialsStore = credentials.DetectDefaultStore(configFile.CredentialsStore)
|
|
}
|
|
return configFile
|
|
}
|