2016-12-25 14:31:52 -05:00
package config
import (
2017-06-12 14:36:49 -04:00
"fmt"
2016-12-25 14:31:52 -05:00
"io"
"os"
2024-06-24 05:41:53 -04:00
"os/user"
2016-12-25 14:31:52 -05:00
"path/filepath"
2024-06-24 05:41:53 -04:00
"runtime"
2019-03-07 09:28:42 -05:00
"strings"
2019-10-24 10:11:04 -04:00
"sync"
2016-12-25 14:31:52 -05:00
2017-04-17 18:07:56 -04:00
"github.com/docker/cli/cli/config/configfile"
2017-06-12 14:36:49 -04:00
"github.com/docker/cli/cli/config/credentials"
2017-10-15 15:39:56 -04:00
"github.com/docker/cli/cli/config/types"
2017-03-09 13:23:45 -05:00
"github.com/pkg/errors"
2016-12-25 14:31:52 -05:00
)
const (
2023-06-28 07:19:39 -04:00
// 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.
2016-12-25 14:31:52 -05:00
ConfigFileName = "config.json"
configFileDir = ".docker"
2018-12-17 05:27:07 -05:00
contextsDir = "contexts"
2016-12-25 14:31:52 -05:00
)
var (
2020-07-16 06:01:54 -04:00
initConfigDir = new ( sync . Once )
2019-10-24 10:11:04 -04:00
configDir string
2016-12-25 14:31:52 -05:00
)
2023-06-28 06:40:38 -04:00
// 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 )
}
2024-06-24 05:41:53 -04:00
// getHomeDir returns the home directory of the current user with the help of
// environment variables depending on the target operating system.
// Returned path should be used with "path/filepath" to form new paths.
//
// On non-Windows platforms, it falls back to nss lookups, if the home
// directory cannot be obtained from environment-variables.
//
// If linking statically with cgo enabled against glibc, ensure the
// osusergo build tag is used.
//
// If needing to do nss lookups, do not disable cgo or set osusergo.
//
// getHomeDir is a copy of [pkg/homedir.Get] to prevent adding docker/docker
// as dependency for consumers that only need to read the config-file.
//
// [pkg/homedir.Get]: https://pkg.go.dev/github.com/docker/docker@v26.1.4+incompatible/pkg/homedir#Get
func getHomeDir ( ) string {
home , _ := os . UserHomeDir ( )
if home == "" && runtime . GOOS != "windows" {
if u , err := user . Current ( ) ; err == nil {
return u . HomeDir
}
}
return home
}
2016-12-25 14:31:52 -05:00
// Dir returns the directory the configuration file is stored in
func Dir ( ) string {
2023-05-10 04:46:48 -04:00
initConfigDir . Do ( func ( ) {
2023-06-28 07:19:39 -04:00
configDir = os . Getenv ( EnvOverrideConfigDir )
2023-05-10 04:46:48 -04:00
if configDir == "" {
2024-06-24 05:41:53 -04:00
configDir = filepath . Join ( getHomeDir ( ) , configFileDir )
2023-05-10 04:46:48 -04:00
}
} )
2016-12-25 14:31:52 -05:00
return configDir
}
2018-12-17 05:27:07 -05:00
// ContextStoreDir returns the directory the docker contexts are stored in
func ContextStoreDir ( ) string {
return filepath . Join ( Dir ( ) , contextsDir )
}
2016-12-25 14:31:52 -05:00
// SetDir sets the directory the configuration file is stored in
func SetDir ( dir string ) {
2023-06-28 06:40:38 -04:00
// trigger the sync.Once to synchronise with Dir()
initConfigDir . Do ( func ( ) { } )
2019-03-07 09:28:42 -05:00
configDir = filepath . Clean ( dir )
2016-12-25 14:31:52 -05:00
}
2019-01-10 10:49:06 -05:00
// Path returns the path to a file relative to the config dir
2019-03-07 09:28:42 -05:00
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
2019-01-10 10:49:06 -05:00
}
2016-12-25 14:31:52 -05:00
// LoadFromReader is a convenience function that creates a ConfigFile object from
2024-05-16 10:16:20 -04:00
// a reader. It returns an error if configData is malformed.
2016-12-25 14:31:52 -05:00
func LoadFromReader ( configData io . Reader ) ( * configfile . ConfigFile , error ) {
configFile := configfile . ConfigFile {
AuthConfigs : make ( map [ string ] types . AuthConfig ) ,
}
err := configFile . LoadFromReader ( configData )
return & configFile , err
}
2024-05-16 08:19:09 -04:00
// 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.
2024-05-16 10:16:20 -04:00
//
// 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.
2016-12-25 14:31:52 -05:00
func Load ( configDir string ) ( * configfile . ConfigFile , error ) {
if configDir == "" {
configDir = Dir ( )
}
2023-05-10 04:46:48 -04:00
return load ( configDir )
}
2016-12-25 14:31:52 -05:00
2023-05-10 04:46:48 -04:00
func load ( configDir string ) ( * configfile . ConfigFile , error ) {
2017-06-22 12:03:58 -04:00
filename := filepath . Join ( configDir , ConfigFileName )
2017-06-27 10:31:38 -04:00
configFile := configfile . New ( filename )
2016-12-25 14:31:52 -05:00
2023-05-10 04:46:48 -04:00
file , err := os . Open ( filename )
if err != nil {
if os . IsNotExist ( err ) {
2024-05-16 10:16:20 -04:00
// It is OK for no configuration file to be present, in which
// case we return a default struct.
2023-05-10 04:46:48 -04:00
return configFile , nil
2016-12-25 14:31:52 -05:00
}
2024-05-16 10:16:20 -04:00
// Any other error happening when failing to read the file must be returned.
return configFile , errors . Wrap ( err , "loading config file" )
2016-12-25 14:31:52 -05:00
}
2023-05-10 04:46:48 -04:00
defer file . Close ( )
err = configFile . LoadFromReader ( file )
if err != nil {
2024-10-21 12:15:49 -04:00
err = errors . Wrapf ( err , "parsing config file (%s)" , filename )
2016-12-25 14:31:52 -05:00
}
2023-05-10 04:46:48 -04:00
return configFile , err
2016-12-25 14:31:52 -05:00
}
2017-06-12 14:36:49 -04:00
// LoadDefaultConfigFile attempts to load the default config file and returns
2024-05-16 08:19:09 -04:00
// 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.
2017-06-21 17:20:49 -04:00
func LoadDefaultConfigFile ( stderr io . Writer ) * configfile . ConfigFile {
2023-05-10 04:46:48 -04:00
configFile , err := load ( Dir ( ) )
2017-06-21 17:20:49 -04:00
if err != nil {
2024-05-16 10:16:20 -04:00
// 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 )
2020-07-16 06:01:54 -04:00
}
2017-06-12 14:36:49 -04:00
if ! configFile . ContainsAuth ( ) {
2017-06-21 16:47:06 -04:00
configFile . CredentialsStore = credentials . DetectDefaultStore ( configFile . CredentialsStore )
2017-06-12 14:36:49 -04:00
}
return configFile
}