diff --git a/api/client/cli.go b/api/client/cli.go index a53a0ba3e5..4ab9cfeab0 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -7,13 +7,11 @@ import ( "fmt" "io" "net/http" - "path/filepath" "reflect" "strings" "text/template" "github.com/docker/docker/cliconfig" - "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/sockets" "github.com/docker/docker/pkg/term" @@ -212,7 +210,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a } sockets.ConfigureTCPTransport(tr, proto, addr) - configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker")) + configFile, e := cliconfig.Load(cliconfig.ConfigDir()) if e != nil { fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) } diff --git a/cliconfig/config.go b/cliconfig/config.go index 2a27589d20..10f24f3c8e 100644 --- a/cliconfig/config.go +++ b/cliconfig/config.go @@ -25,9 +25,24 @@ const ( ) var ( + configDir = os.Getenv("DOCKER_CONFIG") ErrConfigFileMissing = errors.New("The Auth config file is missing") ) +func init() { + if configDir == "" { + configDir = filepath.Join(homedir.Get(), ".docker") + } +} + +func ConfigDir() string { + return configDir +} + +func SetConfigDir(dir string) { + configDir = dir +} + // Registry Auth Info type AuthConfig struct { Username string `json:"username,omitempty"` @@ -56,7 +71,7 @@ func NewConfigFile(fn string) *ConfigFile { // FIXME: use the internal golang config parser func Load(configDir string) (*ConfigFile, error) { if configDir == "" { - configDir = filepath.Join(homedir.Get(), ".docker") + configDir = ConfigDir() } configFile := ConfigFile{ diff --git a/docker/daemon.go b/docker/daemon.go index 2b4125c959..5674bb160d 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -13,8 +13,8 @@ import ( "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/daemon" - "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/pidfile" "github.com/docker/docker/pkg/signal" @@ -39,7 +39,7 @@ func init() { func migrateKey() (err error) { // Migrate trust key if exists at ~/.docker/key.json and owned by current user - oldPath := filepath.Join(homedir.Get(), ".docker", defaultTrustKeyFile) + oldPath := filepath.Join(cliconfig.ConfigDir(), defaultTrustKeyFile) newPath := filepath.Join(getDaemonConfDir(), defaultTrustKeyFile) if _, statErr := os.Stat(newPath); os.IsNotExist(statErr) && currentUserIsOwner(oldPath) { defer func() { diff --git a/docker/docker.go b/docker/docker.go index 79bd6b2d47..d9f9c43a6a 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -10,6 +10,7 @@ import ( "github.com/Sirupsen/logrus" "github.com/docker/docker/api/client" "github.com/docker/docker/autogen/dockerversion" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/opts" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/reexec" @@ -43,6 +44,10 @@ func main() { return } + if *flConfigDir != "" { + cliconfig.SetConfigDir(*flConfigDir) + } + if *flLogLevel != "" { lvl, err := logrus.ParseLevel(*flLogLevel) if err != nil { diff --git a/docker/flags.go b/docker/flags.go index 969469c176..d8ecf97d43 100644 --- a/docker/flags.go +++ b/docker/flags.go @@ -7,8 +7,8 @@ import ( "runtime" "sort" + "github.com/docker/docker/cliconfig" "github.com/docker/docker/opts" - "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/tlsconfig" ) @@ -73,19 +73,20 @@ var ( func init() { if dockerCertPath == "" { - dockerCertPath = filepath.Join(homedir.Get(), ".docker") + dockerCertPath = cliconfig.ConfigDir() } } func getDaemonConfDir() string { // TODO: update for Windows daemon if runtime.GOOS == "windows" { - return filepath.Join(homedir.Get(), ".docker") + return cliconfig.ConfigDir() } return "/etc/docker" } var ( + flConfigDir = flag.String([]string{"-config"}, cliconfig.ConfigDir(), "Location of client config files") flVersion = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit") flDaemon = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode") flDebug = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode") @@ -105,7 +106,7 @@ func setDefaultConfFlag(flag *string, def string) { if *flDaemon { *flag = filepath.Join(getDaemonConfDir(), def) } else { - *flag = filepath.Join(homedir.Get(), ".docker", def) + *flag = filepath.Join(cliconfig.ConfigDir(), def) } } } diff --git a/docs/reference/commandline/cli.md b/docs/reference/commandline/cli.md index 0407c03fc0..69a6e3e31e 100644 --- a/docs/reference/commandline/cli.md +++ b/docs/reference/commandline/cli.md @@ -10,7 +10,7 @@ parent = "smn_cli" # Using the command line -> **Note:** if you are using a remote Docker daemon, such as Boot2Docker, +> **Note:** If you are using a remote Docker daemon, such as Boot2Docker, > then _do not_ type the `sudo` before the `docker` commands shown in the > documentation's examples. @@ -38,6 +38,7 @@ the [installation](/installation) instructions for your operating system. For easy reference, the following list of environment variables are supported by the `docker` command line: +* `DOCKER_CONFIG` The location of your client configuration files. * `DOCKER_CERT_PATH` The location of your authentication keys. * `DOCKER_DRIVER` The graph driver to use. * `DOCKER_HOST` Daemon socket to connect to. @@ -60,10 +61,21 @@ variables. ## Configuration files -The Docker command line stores its configuration files in a directory called -`.docker` within your `HOME` directory. Docker manages most of the files in -`.docker` and you should not modify them. However, you *can modify* the -`.docker/config.json` file to control certain aspects of how the `docker` +By default, the Docker command line stores its configuration files in a +directory called `.docker` within your `HOME` directory. However, you can +specify a different location via the `DOCKER_CONFIG` environment variable +or the `--config` command line option. If both are specified, then the +`--config` option overrides the `DOCKER_CONFIG` environment variable. +For example: + + docker --config ~/testconfigs/ ps + +Instructs Docker to use the configuration files in your `~/testconfigs/` +directory when running the `ps` command. + +Docker manages most of the files in the configuration directory +and you should not modify them. However, you *can modify* the +`config.json` file to control certain aspects of how the `docker` command behaves. Currently, you can modify the `docker` command behavior using environment diff --git a/docs/reference/commandline/daemon.md b/docs/reference/commandline/daemon.md index 84648e8306..668fbc3152 100644 --- a/docs/reference/commandline/daemon.md +++ b/docs/reference/commandline/daemon.md @@ -18,6 +18,7 @@ parent = "smn_cli" --api-cors-header="" Set CORS headers in the remote API -b, --bridge="" Attach containers to a network bridge --bip="" Specify network bridge IP + --config=~/.docker Location of client config files -D, --debug=false Enable debug mode -d, --daemon=false Enable daemon mode --default-gateway="" Container default gateway IPv4 address diff --git a/integration-cli/docker_cli_config_test.go b/integration-cli/docker_cli_config_test.go index 82c133b2f7..b3338c9953 100644 --- a/integration-cli/docker_cli_config_test.go +++ b/integration-cli/docker_cli_config_test.go @@ -64,3 +64,85 @@ func (s *DockerSuite) TestConfigHttpHeader(c *check.C) { c.Fatalf("Missing/bad header: %q\nout:%v", headers, out) } } + +func (s *DockerSuite) TestConfigDir(c *check.C) { + cDir, _ := ioutil.TempDir("", "fake-home") + + // First make sure pointing to empty dir doesn't generate an error + cmd := exec.Command(dockerBinary, "--config", cDir, "ps") + out, rc, err := runCommandWithOutput(cmd) + + if rc != 0 || err != nil { + c.Fatalf("ps1 didn't work:\nrc:%d\nout%s\nerr:%v", rc, out, err) + } + + // Test with env var too + cmd = exec.Command(dockerBinary, "ps") + cmd.Env = append(os.Environ(), "DOCKER_CONFIG="+cDir) + out, rc, err = runCommandWithOutput(cmd) + + if rc != 0 || err != nil { + c.Fatalf("ps2 didn't work:\nrc:%d\nout%s\nerr:%v", rc, out, err) + } + + // Start a server so we can check to see if the config file was + // loaded properly + var headers map[string][]string + + server := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + headers = r.Header + })) + defer server.Close() + + // Create a dummy config file in our new config dir + data := `{ + "HttpHeaders": { "MyHeader": "MyValue" } + }` + + tmpCfg := filepath.Join(cDir, "config.json") + err = ioutil.WriteFile(tmpCfg, []byte(data), 0600) + if err != nil { + c.Fatalf("Err creating file(%s): %v", tmpCfg, err) + } + + cmd = exec.Command(dockerBinary, "--config", cDir, "-H="+server.URL[7:], "ps") + out, _, _ = runCommandWithOutput(cmd) + + if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" { + c.Fatalf("ps3 - Missing header: %q\nout:%v", headers, out) + } + + // Reset headers and try again using env var this time + headers = map[string][]string{} + cmd = exec.Command(dockerBinary, "-H="+server.URL[7:], "ps") + cmd.Env = append(os.Environ(), "DOCKER_CONFIG="+cDir) + out, _, _ = runCommandWithOutput(cmd) + + if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" { + c.Fatalf("ps4 - Missing header: %q\nout:%v", headers, out) + } + + // Reset headers and make sure flag overrides the env var + headers = map[string][]string{} + cmd = exec.Command(dockerBinary, "--config", cDir, "-H="+server.URL[7:], "ps") + cmd.Env = append(os.Environ(), "DOCKER_CONFIG=MissingDir") + out, _, _ = runCommandWithOutput(cmd) + + if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" { + c.Fatalf("ps5 - Missing header: %q\nout:%v", headers, out) + } + + // Reset headers and make sure flag overrides the env var. + // Almost same as previous but make sure the "MissingDir" isn't + // ignore - we don't want to default back to the env var. + headers = map[string][]string{} + cmd = exec.Command(dockerBinary, "--config", "MissingDir", "-H="+server.URL[7:], "ps") + cmd.Env = append(os.Environ(), "DOCKER_CONFIG="+cDir) + out, _, _ = runCommandWithOutput(cmd) + + if headers["Myheader"] != nil { + c.Fatalf("ps6 - Headers are there but shouldn't be: %q\nout:%v", headers, out) + } + +} diff --git a/man/docker.1.md b/man/docker.1.md index 98135e50ff..25c799a0b5 100644 --- a/man/docker.1.md +++ b/man/docker.1.md @@ -35,6 +35,9 @@ To see the man page for a command run **man docker **. **--bip**="" Use the provided CIDR notation address for the dynamically created bridge (docker0); Mutually exclusive of \-b +**--config**="" + Specifies the location of the Docker client configuration files. The default is '~/.docker'. + **-D**, **--debug**=*true*|*false* Enable debug mode. Default is false.