Browse code

Add support for DOCKER_CONFIG/--config to specific config file dir

Carry #11675

Aside from what #11675 says, to me a key usecase for this is to support
more than one Docker cli running at the same time but each may have its
own set of config files.

Signed-off-by: Doug Davis <dug@us.ibm.com>

Doug Davis authored on 2015/04/29 00:00:18
Showing 9 changed files
... ...
@@ -7,13 +7,11 @@ import (
7 7
 	"fmt"
8 8
 	"io"
9 9
 	"net/http"
10
-	"path/filepath"
11 10
 	"reflect"
12 11
 	"strings"
13 12
 	"text/template"
14 13
 
15 14
 	"github.com/docker/docker/cliconfig"
16
-	"github.com/docker/docker/pkg/homedir"
17 15
 	flag "github.com/docker/docker/pkg/mflag"
18 16
 	"github.com/docker/docker/pkg/sockets"
19 17
 	"github.com/docker/docker/pkg/term"
... ...
@@ -212,7 +210,7 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a
212 212
 	}
213 213
 	sockets.ConfigureTCPTransport(tr, proto, addr)
214 214
 
215
-	configFile, e := cliconfig.Load(filepath.Join(homedir.Get(), ".docker"))
215
+	configFile, e := cliconfig.Load(cliconfig.ConfigDir())
216 216
 	if e != nil {
217 217
 		fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e)
218 218
 	}
... ...
@@ -25,9 +25,24 @@ const (
25 25
 )
26 26
 
27 27
 var (
28
+	configDir            = os.Getenv("DOCKER_CONFIG")
28 29
 	ErrConfigFileMissing = errors.New("The Auth config file is missing")
29 30
 )
30 31
 
32
+func init() {
33
+	if configDir == "" {
34
+		configDir = filepath.Join(homedir.Get(), ".docker")
35
+	}
36
+}
37
+
38
+func ConfigDir() string {
39
+	return configDir
40
+}
41
+
42
+func SetConfigDir(dir string) {
43
+	configDir = dir
44
+}
45
+
31 46
 // Registry Auth Info
32 47
 type AuthConfig struct {
33 48
 	Username      string `json:"username,omitempty"`
... ...
@@ -56,7 +71,7 @@ func NewConfigFile(fn string) *ConfigFile {
56 56
 // FIXME: use the internal golang config parser
57 57
 func Load(configDir string) (*ConfigFile, error) {
58 58
 	if configDir == "" {
59
-		configDir = filepath.Join(homedir.Get(), ".docker")
59
+		configDir = ConfigDir()
60 60
 	}
61 61
 
62 62
 	configFile := ConfigFile{
... ...
@@ -13,8 +13,8 @@ import (
13 13
 	"github.com/Sirupsen/logrus"
14 14
 	apiserver "github.com/docker/docker/api/server"
15 15
 	"github.com/docker/docker/autogen/dockerversion"
16
+	"github.com/docker/docker/cliconfig"
16 17
 	"github.com/docker/docker/daemon"
17
-	"github.com/docker/docker/pkg/homedir"
18 18
 	flag "github.com/docker/docker/pkg/mflag"
19 19
 	"github.com/docker/docker/pkg/pidfile"
20 20
 	"github.com/docker/docker/pkg/signal"
... ...
@@ -39,7 +39,7 @@ func init() {
39 39
 
40 40
 func migrateKey() (err error) {
41 41
 	// Migrate trust key if exists at ~/.docker/key.json and owned by current user
42
-	oldPath := filepath.Join(homedir.Get(), ".docker", defaultTrustKeyFile)
42
+	oldPath := filepath.Join(cliconfig.ConfigDir(), defaultTrustKeyFile)
43 43
 	newPath := filepath.Join(getDaemonConfDir(), defaultTrustKeyFile)
44 44
 	if _, statErr := os.Stat(newPath); os.IsNotExist(statErr) && currentUserIsOwner(oldPath) {
45 45
 		defer func() {
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/Sirupsen/logrus"
11 11
 	"github.com/docker/docker/api/client"
12 12
 	"github.com/docker/docker/autogen/dockerversion"
13
+	"github.com/docker/docker/cliconfig"
13 14
 	"github.com/docker/docker/opts"
14 15
 	flag "github.com/docker/docker/pkg/mflag"
15 16
 	"github.com/docker/docker/pkg/reexec"
... ...
@@ -43,6 +44,10 @@ func main() {
43 43
 		return
44 44
 	}
45 45
 
46
+	if *flConfigDir != "" {
47
+		cliconfig.SetConfigDir(*flConfigDir)
48
+	}
49
+
46 50
 	if *flLogLevel != "" {
47 51
 		lvl, err := logrus.ParseLevel(*flLogLevel)
48 52
 		if err != nil {
... ...
@@ -7,8 +7,8 @@ import (
7 7
 	"runtime"
8 8
 	"sort"
9 9
 
10
+	"github.com/docker/docker/cliconfig"
10 11
 	"github.com/docker/docker/opts"
11
-	"github.com/docker/docker/pkg/homedir"
12 12
 	flag "github.com/docker/docker/pkg/mflag"
13 13
 	"github.com/docker/docker/pkg/tlsconfig"
14 14
 )
... ...
@@ -73,19 +73,20 @@ var (
73 73
 
74 74
 func init() {
75 75
 	if dockerCertPath == "" {
76
-		dockerCertPath = filepath.Join(homedir.Get(), ".docker")
76
+		dockerCertPath = cliconfig.ConfigDir()
77 77
 	}
78 78
 }
79 79
 
80 80
 func getDaemonConfDir() string {
81 81
 	// TODO: update for Windows daemon
82 82
 	if runtime.GOOS == "windows" {
83
-		return filepath.Join(homedir.Get(), ".docker")
83
+		return cliconfig.ConfigDir()
84 84
 	}
85 85
 	return "/etc/docker"
86 86
 }
87 87
 
88 88
 var (
89
+	flConfigDir = flag.String([]string{"-config"}, cliconfig.ConfigDir(), "Location of client config files")
89 90
 	flVersion   = flag.Bool([]string{"v", "-version"}, false, "Print version information and quit")
90 91
 	flDaemon    = flag.Bool([]string{"d", "-daemon"}, false, "Enable daemon mode")
91 92
 	flDebug     = flag.Bool([]string{"D", "-debug"}, false, "Enable debug mode")
... ...
@@ -105,7 +106,7 @@ func setDefaultConfFlag(flag *string, def string) {
105 105
 		if *flDaemon {
106 106
 			*flag = filepath.Join(getDaemonConfDir(), def)
107 107
 		} else {
108
-			*flag = filepath.Join(homedir.Get(), ".docker", def)
108
+			*flag = filepath.Join(cliconfig.ConfigDir(), def)
109 109
 		}
110 110
 	}
111 111
 }
... ...
@@ -10,7 +10,7 @@ parent = "smn_cli"
10 10
 
11 11
 # Using the command line
12 12
 
13
-> **Note:** if you are using a remote Docker daemon, such as Boot2Docker, 
13
+> **Note:** If you are using a remote Docker daemon, such as Boot2Docker,
14 14
 > then _do not_ type the `sudo` before the `docker` commands shown in the
15 15
 > documentation's examples.
16 16
 
... ...
@@ -38,6 +38,7 @@ the [installation](/installation) instructions for your operating system.
38 38
 For easy reference, the following list of environment variables are supported
39 39
 by the `docker` command line:
40 40
 
41
+* `DOCKER_CONFIG` The location of your client configuration files.
41 42
 * `DOCKER_CERT_PATH` The location of your authentication keys.
42 43
 * `DOCKER_DRIVER` The graph driver to use.
43 44
 * `DOCKER_HOST` Daemon socket to connect to.
... ...
@@ -60,10 +61,21 @@ variables.
60 60
 
61 61
 ## Configuration files
62 62
 
63
-The Docker command line stores its configuration files in a directory called
64
-`.docker` within your `HOME` directory. Docker manages most of the files in
65
-`.docker` and you should not modify them. However, you *can modify* the
66
-`.docker/config.json` file to control certain aspects of how the `docker`
63
+By default, the Docker command line stores its configuration files in a
64
+directory called `.docker` within your `HOME` directory. However, you can
65
+specify a different location via the `DOCKER_CONFIG` environment variable
66
+or the `--config` command line option. If both are specified, then the
67
+`--config` option overrides the `DOCKER_CONFIG` environment variable.
68
+For example:
69
+
70
+    docker --config ~/testconfigs/ ps
71
+
72
+Instructs Docker to use the configuration files in your `~/testconfigs/`
73
+directory when running the `ps` command.
74
+
75
+Docker manages most of the files in the configuration directory
76
+and you should not modify them. However, you *can modify* the
77
+`config.json` file to control certain aspects of how the `docker`
67 78
 command behaves.
68 79
 
69 80
 Currently, you can modify the `docker` command behavior using environment
... ...
@@ -18,6 +18,7 @@ parent = "smn_cli"
18 18
       --api-cors-header=""                   Set CORS headers in the remote API
19 19
       -b, --bridge=""                        Attach containers to a network bridge
20 20
       --bip=""                               Specify network bridge IP
21
+      --config=~/.docker                     Location of client config files
21 22
       -D, --debug=false                      Enable debug mode
22 23
       -d, --daemon=false                     Enable daemon mode
23 24
       --default-gateway=""                   Container default gateway IPv4 address
... ...
@@ -64,3 +64,85 @@ func (s *DockerSuite) TestConfigHttpHeader(c *check.C) {
64 64
 		c.Fatalf("Missing/bad header: %q\nout:%v", headers, out)
65 65
 	}
66 66
 }
67
+
68
+func (s *DockerSuite) TestConfigDir(c *check.C) {
69
+	cDir, _ := ioutil.TempDir("", "fake-home")
70
+
71
+	// First make sure pointing to empty dir doesn't generate an error
72
+	cmd := exec.Command(dockerBinary, "--config", cDir, "ps")
73
+	out, rc, err := runCommandWithOutput(cmd)
74
+
75
+	if rc != 0 || err != nil {
76
+		c.Fatalf("ps1 didn't work:\nrc:%d\nout%s\nerr:%v", rc, out, err)
77
+	}
78
+
79
+	// Test with env var too
80
+	cmd = exec.Command(dockerBinary, "ps")
81
+	cmd.Env = append(os.Environ(), "DOCKER_CONFIG="+cDir)
82
+	out, rc, err = runCommandWithOutput(cmd)
83
+
84
+	if rc != 0 || err != nil {
85
+		c.Fatalf("ps2 didn't work:\nrc:%d\nout%s\nerr:%v", rc, out, err)
86
+	}
87
+
88
+	// Start a server so we can check to see if the config file was
89
+	// loaded properly
90
+	var headers map[string][]string
91
+
92
+	server := httptest.NewServer(http.HandlerFunc(
93
+		func(w http.ResponseWriter, r *http.Request) {
94
+			headers = r.Header
95
+		}))
96
+	defer server.Close()
97
+
98
+	// Create a dummy config file in our new config dir
99
+	data := `{
100
+		"HttpHeaders": { "MyHeader": "MyValue" }
101
+	}`
102
+
103
+	tmpCfg := filepath.Join(cDir, "config.json")
104
+	err = ioutil.WriteFile(tmpCfg, []byte(data), 0600)
105
+	if err != nil {
106
+		c.Fatalf("Err creating file(%s): %v", tmpCfg, err)
107
+	}
108
+
109
+	cmd = exec.Command(dockerBinary, "--config", cDir, "-H="+server.URL[7:], "ps")
110
+	out, _, _ = runCommandWithOutput(cmd)
111
+
112
+	if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" {
113
+		c.Fatalf("ps3 - Missing header: %q\nout:%v", headers, out)
114
+	}
115
+
116
+	// Reset headers and try again using env var this time
117
+	headers = map[string][]string{}
118
+	cmd = exec.Command(dockerBinary, "-H="+server.URL[7:], "ps")
119
+	cmd.Env = append(os.Environ(), "DOCKER_CONFIG="+cDir)
120
+	out, _, _ = runCommandWithOutput(cmd)
121
+
122
+	if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" {
123
+		c.Fatalf("ps4 - Missing header: %q\nout:%v", headers, out)
124
+	}
125
+
126
+	// Reset headers and make sure flag overrides the env var
127
+	headers = map[string][]string{}
128
+	cmd = exec.Command(dockerBinary, "--config", cDir, "-H="+server.URL[7:], "ps")
129
+	cmd.Env = append(os.Environ(), "DOCKER_CONFIG=MissingDir")
130
+	out, _, _ = runCommandWithOutput(cmd)
131
+
132
+	if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" {
133
+		c.Fatalf("ps5 - Missing header: %q\nout:%v", headers, out)
134
+	}
135
+
136
+	// Reset headers and make sure flag overrides the env var.
137
+	// Almost same as previous but make sure the "MissingDir" isn't
138
+	// ignore - we don't want to default back to the env var.
139
+	headers = map[string][]string{}
140
+	cmd = exec.Command(dockerBinary, "--config", "MissingDir", "-H="+server.URL[7:], "ps")
141
+	cmd.Env = append(os.Environ(), "DOCKER_CONFIG="+cDir)
142
+	out, _, _ = runCommandWithOutput(cmd)
143
+
144
+	if headers["Myheader"] != nil {
145
+		c.Fatalf("ps6 - Headers are there but shouldn't be: %q\nout:%v", headers, out)
146
+	}
147
+
148
+}
... ...
@@ -35,6 +35,9 @@ To see the man page for a command run **man docker <command>**.
35 35
 **--bip**=""
36 36
   Use the provided CIDR notation address for the dynamically created bridge (docker0); Mutually exclusive of \-b
37 37
 
38
+**--config**=""
39
+  Specifies the location of the Docker client configuration files. The default is '~/.docker'.
40
+
38 41
 **-D**, **--debug**=*true*|*false*
39 42
   Enable debug mode. Default is false.
40 43