From 18c9b6c6455f116ae59cde8544413b3d7d294a5e Mon Sep 17 00:00:00 2001 From: Doug Davis Date: Wed, 1 Apr 2015 15:39:37 -0700 Subject: [PATCH] Add .docker/config.json and support for HTTP Headers This PR does the following: - migrated ~/.dockerfg to ~/.docker/config.json. The data is migrated but the old file remains in case its needed - moves the auth json in that fie into an "auth" property so we can add new top-level properties w/o messing with the auth stuff - adds support for an HttpHeaders property in ~/.docker/config.json which adds these http headers to all msgs from the cli In a follow-on PR I'll move the config file process out from under "registry" since it not specific to that any more. I didn't do it here because I wanted the diff to be smaller so people can make sure I didn't break/miss any auth code during my edits. Signed-off-by: Doug Davis --- api/client/build.go | 4 +- api/client/cli.go | 15 ++- api/client/create.go | 3 - api/client/hijack.go | 7 ++ api/client/info.go | 3 +- api/client/login.go | 24 ++-- api/client/logout.go | 7 +- api/client/pull.go | 2 - api/client/push.go | 2 - api/client/search.go | 2 - api/client/utils.go | 9 +- builder/evaluator.go | 4 +- builder/internals.go | 4 +- builder/job.go | 2 +- docs/sources/reference/commandline/cli.md | 29 +++++ integration-cli/docker_cli_config_test.go | 58 ++++++++++ registry/auth.go | 105 ++++++++++++----- registry/auth_test.go | 26 +++-- registry/config_file_test.go | 135 ++++++++++++++++++++++ 19 files changed, 360 insertions(+), 81 deletions(-) create mode 100644 integration-cli/docker_cli_config_test.go create mode 100644 registry/config_file_test.go diff --git a/api/client/build.go b/api/client/build.go index 788319edf7..63cc63bc9e 100644 --- a/api/client/build.go +++ b/api/client/build.go @@ -286,10 +286,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error { v.Set("dockerfile", *dockerfileName) - cli.LoadConfigFile() - headers := http.Header(make(map[string][]string)) - buf, err := json.Marshal(cli.configFile) + buf, err := json.Marshal(cli.configFile.AuthConfigs) if err != nil { return err } diff --git a/api/client/cli.go b/api/client/cli.go index dfa5fe520f..3146b17b31 100644 --- a/api/client/cli.go +++ b/api/client/cli.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "os" + "path/filepath" "reflect" "strings" "text/template" @@ -120,14 +121,6 @@ func (cli *DockerCli) Subcmd(name, signature, description string, exitOnError bo return flags } -func (cli *DockerCli) LoadConfigFile() (err error) { - cli.configFile, err = registry.LoadConfig(homedir.Get()) - if err != nil { - fmt.Fprintf(cli.err, "WARNING: %s\n", err) - } - return err -} - func (cli *DockerCli) CheckTtyInput(attachStdin, ttyMode bool) error { // In order to attach to a container tty, input stream for the client must // be a tty itself: redirecting or piping the client standard input is @@ -184,9 +177,15 @@ func NewDockerCli(in io.ReadCloser, out, err io.Writer, keyFile string, proto, a tr.Dial = (&net.Dialer{Timeout: timeout}).Dial } + configFile, e := registry.LoadConfig(filepath.Join(homedir.Get(), ".docker")) + if e != nil { + fmt.Fprintf(err, "WARNING: Error loading config file:%v\n", e) + } + return &DockerCli{ proto: proto, addr: addr, + configFile: configFile, in: in, out: out, err: err, diff --git a/api/client/create.go b/api/client/create.go index bb84d5e463..d2987a67e4 100644 --- a/api/client/create.go +++ b/api/client/create.go @@ -37,9 +37,6 @@ func (cli *DockerCli) pullImageCustomOut(image string, out io.Writer) error { return err } - // Load the auth config file, to be able to pull the image - cli.LoadConfigFile() - // Resolve the Auth config relevant for this server authConfig := cli.configFile.ResolveAuthConfig(repoInfo.Index) buf, err := json.Marshal(authConfig) diff --git a/api/client/hijack.go b/api/client/hijack.go index 1635384168..5f4794a5e7 100644 --- a/api/client/hijack.go +++ b/api/client/hijack.go @@ -142,6 +142,13 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea if err != nil { return err } + + // Add CLI Config's HTTP Headers BEFORE we set the Docker headers + // then the user can't change OUR headers + for k, v := range cli.configFile.HttpHeaders { + req.Header.Set(k, v) + } + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.Header.Set("Content-Type", "text/plain") req.Header.Set("Connection", "Upgrade") diff --git a/api/client/info.go b/api/client/info.go index 6557888829..432ccac40f 100644 --- a/api/client/info.go +++ b/api/client/info.go @@ -68,8 +68,7 @@ func (cli *DockerCli) CmdInfo(args ...string) error { } if info.IndexServerAddress != "" { - cli.LoadConfigFile() - u := cli.configFile.Configs[info.IndexServerAddress].Username + u := cli.configFile.AuthConfigs[info.IndexServerAddress].Username if len(u) > 0 { fmt.Fprintf(cli.out, "Username: %v\n", u) fmt.Fprintf(cli.out, "Registry: %v\n", info.IndexServerAddress) diff --git a/api/client/login.go b/api/client/login.go index b24ef7df7e..e8e87fc5e3 100644 --- a/api/client/login.go +++ b/api/client/login.go @@ -6,11 +6,9 @@ import ( "fmt" "io" "os" - "path" "strings" "github.com/docker/docker/api/types" - "github.com/docker/docker/pkg/homedir" flag "github.com/docker/docker/pkg/mflag" "github.com/docker/docker/pkg/term" "github.com/docker/docker/registry" @@ -56,8 +54,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { return string(line) } - cli.LoadConfigFile() - authconfig, ok := cli.configFile.Configs[serverAddress] + authconfig, ok := cli.configFile.AuthConfigs[serverAddress] if !ok { authconfig = registry.AuthConfig{} } @@ -113,12 +110,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig.Password = password authconfig.Email = email authconfig.ServerAddress = serverAddress - cli.configFile.Configs[serverAddress] = authconfig + cli.configFile.AuthConfigs[serverAddress] = authconfig - stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress], nil) + stream, statusCode, err := cli.call("POST", "/auth", cli.configFile.AuthConfigs[serverAddress], nil) if statusCode == 401 { - delete(cli.configFile.Configs, serverAddress) - registry.SaveConfig(cli.configFile) + delete(cli.configFile.AuthConfigs, serverAddress) + if err2 := cli.configFile.Save(); err2 != nil { + fmt.Fprintf(cli.out, "WARNING: could not save config file: %v\n", err2) + } return err } if err != nil { @@ -127,12 +126,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error { var response types.AuthResponse if err := json.NewDecoder(stream).Decode(&response); err != nil { - cli.configFile, _ = registry.LoadConfig(homedir.Get()) + // Upon error, remove entry + delete(cli.configFile.AuthConfigs, serverAddress) return err } - registry.SaveConfig(cli.configFile) - fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s.\n", path.Join(homedir.Get(), registry.CONFIGFILE)) + if err := cli.configFile.Save(); err != nil { + return fmt.Errorf("Error saving config file: %v", err) + } + fmt.Fprintf(cli.out, "WARNING: login credentials saved in %s\n", cli.configFile.Filename()) if response.Status != "" { fmt.Fprintf(cli.out, "%s\n", response.Status) diff --git a/api/client/logout.go b/api/client/logout.go index 9282f22f0c..74d0c278fa 100644 --- a/api/client/logout.go +++ b/api/client/logout.go @@ -22,14 +22,13 @@ func (cli *DockerCli) CmdLogout(args ...string) error { serverAddress = cmd.Arg(0) } - cli.LoadConfigFile() - if _, ok := cli.configFile.Configs[serverAddress]; !ok { + if _, ok := cli.configFile.AuthConfigs[serverAddress]; !ok { fmt.Fprintf(cli.out, "Not logged in to %s\n", serverAddress) } else { fmt.Fprintf(cli.out, "Remove login credentials for %s\n", serverAddress) - delete(cli.configFile.Configs, serverAddress) + delete(cli.configFile.AuthConfigs, serverAddress) - if err := registry.SaveConfig(cli.configFile); err != nil { + if err := cli.configFile.Save(); err != nil { return fmt.Errorf("Failed to save docker config: %v", err) } } diff --git a/api/client/pull.go b/api/client/pull.go index a554e1f456..17abe4bb65 100644 --- a/api/client/pull.go +++ b/api/client/pull.go @@ -42,8 +42,6 @@ func (cli *DockerCli) CmdPull(args ...string) error { return err } - cli.LoadConfigFile() - _, _, err = cli.clientRequestAttemptLogin("POST", "/images/create?"+v.Encode(), nil, cli.out, repoInfo.Index, "pull") return err } diff --git a/api/client/push.go b/api/client/push.go index a31a04ed44..d4fc4c5c9e 100644 --- a/api/client/push.go +++ b/api/client/push.go @@ -20,8 +20,6 @@ func (cli *DockerCli) CmdPush(args ...string) error { name := cmd.Arg(0) - cli.LoadConfigFile() - remote, tag := parsers.ParseRepositoryTag(name) // Resolve the Repository name from fqn to RepositoryInfo diff --git a/api/client/search.go b/api/client/search.go index 5e0a22f014..2cff7708d3 100644 --- a/api/client/search.go +++ b/api/client/search.go @@ -44,8 +44,6 @@ func (cli *DockerCli) CmdSearch(args ...string) error { return err } - cli.LoadConfigFile() - rdr, _, err := cli.clientRequestAttemptLogin("GET", "/images/search?"+v.Encode(), nil, nil, repoInfo.Index, "search") if err != nil { return err diff --git a/api/client/utils.go b/api/client/utils.go index cf11fefe5b..026593d00d 100644 --- a/api/client/utils.go +++ b/api/client/utils.go @@ -65,6 +65,13 @@ func (cli *DockerCli) clientRequest(method, path string, in io.Reader, headers m if err != nil { return nil, "", -1, err } + + // Add CLI Config's HTTP Headers BEFORE we set the Docker headers + // then the user can't change OUR headers + for k, v := range cli.configFile.HttpHeaders { + req.Header.Set(k, v) + } + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.URL.Host = cli.addr req.URL.Scheme = cli.scheme @@ -299,7 +306,7 @@ func (cli *DockerCli) monitorTtySize(id string, isExec bool) error { sigchan := make(chan os.Signal, 1) gosignal.Notify(sigchan, signal.SIGWINCH) go func() { - for _ = range sigchan { + for range sigchan { cli.resizeTty(id, isExec) } }() diff --git a/builder/evaluator.go b/builder/evaluator.go index c159e51bf9..0eba4a6ebd 100644 --- a/builder/evaluator.go +++ b/builder/evaluator.go @@ -101,8 +101,8 @@ type Builder struct { // the final configs of the Dockerfile but dont want the layers disableCommit bool - AuthConfig *registry.AuthConfig - AuthConfigFile *registry.ConfigFile + AuthConfig *registry.AuthConfig + ConfigFile *registry.ConfigFile // Deprecated, original writer used for ImagePull. To be removed. OutOld io.Writer diff --git a/builder/internals.go b/builder/internals.go index bf47714ee3..c15cad4e5e 100644 --- a/builder/internals.go +++ b/builder/internals.go @@ -437,13 +437,13 @@ func (b *Builder) pullImage(name string) (*imagepkg.Image, error) { } pullRegistryAuth := b.AuthConfig - if len(b.AuthConfigFile.Configs) > 0 { + if len(b.ConfigFile.AuthConfigs) > 0 { // The request came with a full auth config file, we prefer to use that repoInfo, err := b.Daemon.RegistryService.ResolveRepository(remote) if err != nil { return nil, err } - resolvedAuth := b.AuthConfigFile.ResolveAuthConfig(repoInfo.Index) + resolvedAuth := b.ConfigFile.ResolveAuthConfig(repoInfo.Index) pullRegistryAuth = &resolvedAuth } diff --git a/builder/job.go b/builder/job.go index 60f9071191..930d7b7f16 100644 --- a/builder/job.go +++ b/builder/job.go @@ -150,7 +150,7 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) error { OutOld: job.Stdout, StreamFormatter: sf, AuthConfig: authConfig, - AuthConfigFile: configFile, + ConfigFile: configFile, dockerfileName: dockerfileName, cpuShares: cpuShares, cpuSetCpus: cpuSetCpus, diff --git a/docs/sources/reference/commandline/cli.md b/docs/sources/reference/commandline/cli.md index 607f670762..821de99e30 100644 --- a/docs/sources/reference/commandline/cli.md +++ b/docs/sources/reference/commandline/cli.md @@ -48,6 +48,35 @@ These Go environment variables are case-insensitive. See the [Go specification](http://golang.org/pkg/net/http/) for details on these 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` +command behaves. + +Currently, you can modify the `docker` command behavior using environment +variables or command-line options. You can also use options within +`config.json` to modify some of the same behavior. When using these +mechanisms, you must keep in mind the order of precedence among them. Command +line options override environment variables and environment variables override +properties you specify in a `config.json` file. + +The `config.json` file stores a JSON encoding of a single `HttpHeaders` +property. The property specifies a set of headers to include in all +messages sent from the Docker client to the daemon. Docker does not try to +interpret or understand these header; it simply puts them into the messages. +Docker does not allow these headers to change any headers it sets for itself. + +Following is a sample `config.json` file: + + { + "HttpHeaders: { + "MyHeader": "MyValue" + } + } + ## Help To list the help on any command just execute the command, followed by the `--help` option. diff --git a/integration-cli/docker_cli_config_test.go b/integration-cli/docker_cli_config_test.go new file mode 100644 index 0000000000..23ad700698 --- /dev/null +++ b/integration-cli/docker_cli_config_test.go @@ -0,0 +1,58 @@ +package main + +import ( + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/docker/docker/pkg/homedir" +) + +func TestConfigHttpHeader(t *testing.T) { + testRequires(t, UnixCli) // Can't set/unset HOME on windows right now + // We either need a level of Go that supports Unsetenv (for cases + // when HOME/USERPROFILE isn't set), or we need to be able to use + // os/user but user.Current() only works if we aren't statically compiling + + var headers map[string][]string + + server := httptest.NewServer(http.HandlerFunc( + func(w http.ResponseWriter, r *http.Request) { + headers = r.Header + })) + defer server.Close() + + homeKey := homedir.Key() + homeVal := homedir.Get() + tmpDir, _ := ioutil.TempDir("", "fake-home") + defer os.RemoveAll(tmpDir) + + dotDocker := filepath.Join(tmpDir, ".docker") + os.Mkdir(dotDocker, 0600) + tmpCfg := filepath.Join(dotDocker, "config.json") + + defer func() { os.Setenv(homeKey, homeVal) }() + os.Setenv(homeKey, tmpDir) + + data := `{ + "HttpHeaders": { "MyHeader": "MyValue" } + }` + + err := ioutil.WriteFile(tmpCfg, []byte(data), 0600) + if err != nil { + t.Fatalf("Err creating file(%s): %v", tmpCfg, err) + } + + cmd := exec.Command(dockerBinary, "-H="+server.URL[7:], "ps") + out, _, _ := runCommandWithOutput(cmd) + + if headers["Myheader"] == nil || headers["Myheader"][0] != "MyValue" { + t.Fatalf("Missing/bad header: %q\nout:%v", headers, out) + } + + logDone("config - add new http headers") +} diff --git a/registry/auth.go b/registry/auth.go index 51b781dd92..bccf58fc5a 100644 --- a/registry/auth.go +++ b/registry/auth.go @@ -8,24 +8,27 @@ import ( "io/ioutil" "net/http" "os" - "path" + "path/filepath" "strings" "sync" "time" "github.com/Sirupsen/logrus" + "github.com/docker/docker/pkg/homedir" "github.com/docker/docker/pkg/requestdecorator" ) const ( // Where we store the config file - CONFIGFILE = ".dockercfg" + CONFIGFILE = "config.json" + OLD_CONFIGFILE = ".dockercfg" ) var ( ErrConfigFileMissing = errors.New("The Auth config file is missing") ) +// Registry Auth Info type AuthConfig struct { Username string `json:"username,omitempty"` Password string `json:"password,omitempty"` @@ -34,9 +37,11 @@ type AuthConfig struct { ServerAddress string `json:"serveraddress,omitempty"` } +// ~/.docker/config.json file info type ConfigFile struct { - Configs map[string]AuthConfig `json:"configs,omitempty"` - rootPath string + AuthConfigs map[string]AuthConfig `json:"auths"` + HttpHeaders map[string]string `json:"HttpHeaders,omitempty"` + filename string // Note: not serialized - for internal use only } type RequestAuthorization struct { @@ -147,18 +152,58 @@ func decodeAuth(authStr string) (string, string, error) { // load up the auth config information and return values // FIXME: use the internal golang config parser -func LoadConfig(rootPath string) (*ConfigFile, error) { - configFile := ConfigFile{Configs: make(map[string]AuthConfig), rootPath: rootPath} - confFile := path.Join(rootPath, CONFIGFILE) +func LoadConfig(configDir string) (*ConfigFile, error) { + if configDir == "" { + configDir = filepath.Join(homedir.Get(), ".docker") + } + + configFile := ConfigFile{ + AuthConfigs: make(map[string]AuthConfig), + filename: filepath.Join(configDir, CONFIGFILE), + } + + // Try happy path first - latest config file + if _, err := os.Stat(configFile.filename); err == nil { + file, err := os.Open(configFile.filename) + if err != nil { + return &configFile, err + } + defer file.Close() + + if err := json.NewDecoder(file).Decode(&configFile); err != nil { + return &configFile, err + } + + for addr, ac := range configFile.AuthConfigs { + ac.Username, ac.Password, err = decodeAuth(ac.Auth) + if err != nil { + return &configFile, err + } + ac.Auth = "" + ac.ServerAddress = addr + configFile.AuthConfigs[addr] = ac + } + + return &configFile, nil + } else if !os.IsNotExist(err) { + // if file is there but we can't stat it for any reason other + // than it doesn't exist then stop + return &configFile, err + } + + // Can't find latest config file so check for the old one + confFile := filepath.Join(homedir.Get(), OLD_CONFIGFILE) + if _, err := os.Stat(confFile); err != nil { return &configFile, nil //missing file is not an error } + b, err := ioutil.ReadFile(confFile) if err != nil { return &configFile, err } - if err := json.Unmarshal(b, &configFile.Configs); err != nil { + if err := json.Unmarshal(b, &configFile.AuthConfigs); err != nil { arr := strings.Split(string(b), "\n") if len(arr) < 2 { return &configFile, fmt.Errorf("The Auth config file is empty") @@ -179,48 +224,52 @@ func LoadConfig(rootPath string) (*ConfigFile, error) { authConfig.Email = origEmail[1] authConfig.ServerAddress = IndexServerAddress() // *TODO: Switch to using IndexServerName() instead? - configFile.Configs[IndexServerAddress()] = authConfig + configFile.AuthConfigs[IndexServerAddress()] = authConfig } else { - for k, authConfig := range configFile.Configs { + for k, authConfig := range configFile.AuthConfigs { authConfig.Username, authConfig.Password, err = decodeAuth(authConfig.Auth) if err != nil { return &configFile, err } authConfig.Auth = "" authConfig.ServerAddress = k - configFile.Configs[k] = authConfig + configFile.AuthConfigs[k] = authConfig } } return &configFile, nil } -// save the auth config -func SaveConfig(configFile *ConfigFile) error { - confFile := path.Join(configFile.rootPath, CONFIGFILE) - if len(configFile.Configs) == 0 { - os.Remove(confFile) - return nil - } - - configs := make(map[string]AuthConfig, len(configFile.Configs)) - for k, authConfig := range configFile.Configs { +func (configFile *ConfigFile) Save() error { + // Encode sensitive data into a new/temp struct + tmpAuthConfigs := make(map[string]AuthConfig, len(configFile.AuthConfigs)) + for k, authConfig := range configFile.AuthConfigs { authCopy := authConfig authCopy.Auth = encodeAuth(&authCopy) authCopy.Username = "" authCopy.Password = "" authCopy.ServerAddress = "" - configs[k] = authCopy + tmpAuthConfigs[k] = authCopy } - b, err := json.MarshalIndent(configs, "", "\t") + saveAuthConfigs := configFile.AuthConfigs + configFile.AuthConfigs = tmpAuthConfigs + defer func() { configFile.AuthConfigs = saveAuthConfigs }() + + data, err := json.MarshalIndent(configFile, "", "\t") if err != nil { return err } - err = ioutil.WriteFile(confFile, b, 0600) + + if err := os.MkdirAll(filepath.Dir(configFile.filename), 0600); err != nil { + return err + } + + err = ioutil.WriteFile(configFile.filename, data, 0600) if err != nil { return err } + return nil } @@ -431,7 +480,7 @@ func tryV2TokenAuthLogin(authConfig *AuthConfig, params map[string]string, regis func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { configKey := index.GetAuthConfigKey() // First try the happy case - if c, found := config.Configs[configKey]; found || index.Official { + if c, found := config.AuthConfigs[configKey]; found || index.Official { return c } @@ -450,7 +499,7 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { // Maybe they have a legacy config file, we will iterate the keys converting // them to the new format and testing - for registry, config := range config.Configs { + for registry, config := range config.AuthConfigs { if configKey == convertToHostname(registry) { return config } @@ -459,3 +508,7 @@ func (config *ConfigFile) ResolveAuthConfig(index *IndexInfo) AuthConfig { // When all else fails, return an empty auth config return AuthConfig{} } + +func (config *ConfigFile) Filename() string { + return config.filename +} diff --git a/registry/auth_test.go b/registry/auth_test.go index 9cc299aabe..b07aa7dbc8 100644 --- a/registry/auth_test.go +++ b/registry/auth_test.go @@ -3,6 +3,7 @@ package registry import ( "io/ioutil" "os" + "path/filepath" "testing" ) @@ -31,13 +32,14 @@ func setupTempConfigFile() (*ConfigFile, error) { if err != nil { return nil, err } + root = filepath.Join(root, CONFIGFILE) configFile := &ConfigFile{ - rootPath: root, - Configs: make(map[string]AuthConfig), + AuthConfigs: make(map[string]AuthConfig), + filename: root, } for _, registry := range []string{"testIndex", IndexServerAddress()} { - configFile.Configs[registry] = AuthConfig{ + configFile.AuthConfigs[registry] = AuthConfig{ Username: "docker-user", Password: "docker-pass", Email: "docker@docker.io", @@ -52,14 +54,14 @@ func TestSameAuthDataPostSave(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.rootPath) + defer os.RemoveAll(configFile.filename) - err = SaveConfig(configFile) + err = configFile.Save() if err != nil { t.Fatal(err) } - authConfig := configFile.Configs["testIndex"] + authConfig := configFile.AuthConfigs["testIndex"] if authConfig.Username != "docker-user" { t.Fail() } @@ -79,9 +81,9 @@ func TestResolveAuthConfigIndexServer(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.rootPath) + defer os.RemoveAll(configFile.filename) - indexConfig := configFile.Configs[IndexServerAddress()] + indexConfig := configFile.AuthConfigs[IndexServerAddress()] officialIndex := &IndexInfo{ Official: true, @@ -102,7 +104,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) { if err != nil { t.Fatal(err) } - defer os.RemoveAll(configFile.rootPath) + defer os.RemoveAll(configFile.filename) registryAuth := AuthConfig{ Username: "foo-user", @@ -119,7 +121,7 @@ func TestResolveAuthConfigFullURL(t *testing.T) { Password: "baz-pass", Email: "baz@example.com", } - configFile.Configs[IndexServerAddress()] = officialAuth + configFile.AuthConfigs[IndexServerAddress()] = officialAuth expectedAuths := map[string]AuthConfig{ "registry.example.com": registryAuth, @@ -157,12 +159,12 @@ func TestResolveAuthConfigFullURL(t *testing.T) { Name: configKey, } for _, registry := range registries { - configFile.Configs[registry] = configured + configFile.AuthConfigs[registry] = configured resolved := configFile.ResolveAuthConfig(index) if resolved.Email != configured.Email { t.Errorf("%s -> %q != %q\n", registry, resolved.Email, configured.Email) } - delete(configFile.Configs, registry) + delete(configFile.AuthConfigs, registry) resolved = configFile.ResolveAuthConfig(index) if resolved.Email == configured.Email { t.Errorf("%s -> %q == %q\n", registry, resolved.Email, configured.Email) diff --git a/registry/config_file_test.go b/registry/config_file_test.go new file mode 100644 index 0000000000..9abb8ee95c --- /dev/null +++ b/registry/config_file_test.go @@ -0,0 +1,135 @@ +package registry + +import ( + "io/ioutil" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + + "github.com/docker/docker/pkg/homedir" +) + +func TestMissingFile(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on missing file: %q", err) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} + +func TestEmptyFile(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + fn := filepath.Join(tmpHome, CONFIGFILE) + ioutil.WriteFile(fn, []byte(""), 0600) + + _, err := LoadConfig(tmpHome) + if err == nil { + t.Fatalf("Was supposed to fail") + } +} + +func TestEmptyJson(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + fn := filepath.Join(tmpHome, CONFIGFILE) + ioutil.WriteFile(fn, []byte("{}"), 0600) + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on empty json file: %q", err) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} + +func TestOldJson(t *testing.T) { + if runtime.GOOS == "windows" { + return + } + + tmpHome, _ := ioutil.TempDir("", "config-test") + defer os.RemoveAll(tmpHome) + + homeKey := homedir.Key() + homeVal := homedir.Get() + + defer func() { os.Setenv(homeKey, homeVal) }() + os.Setenv(homeKey, tmpHome) + + fn := filepath.Join(tmpHome, OLD_CONFIGFILE) + js := `{"https://index.docker.io/v1/":{"auth":"am9lam9lOmhlbGxv","email":"user@example.com"}}` + ioutil.WriteFile(fn, []byte(js), 0600) + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on empty json file: %q", err) + } + + ac := config.AuthConfigs["https://index.docker.io/v1/"] + if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" { + t.Fatalf("Missing data from parsing:\n%q", config) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) || + !strings.Contains(string(buf), "user@example.com") { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +} + +func TestNewJson(t *testing.T) { + tmpHome, _ := ioutil.TempDir("", "config-test") + fn := filepath.Join(tmpHome, CONFIGFILE) + js := ` { "auths": { "https://index.docker.io/v1/": { "auth": "am9lam9lOmhlbGxv", "email": "user@example.com" } } }` + ioutil.WriteFile(fn, []byte(js), 0600) + + config, err := LoadConfig(tmpHome) + if err != nil { + t.Fatalf("Failed loading on empty json file: %q", err) + } + + ac := config.AuthConfigs["https://index.docker.io/v1/"] + if ac.Email != "user@example.com" || ac.Username != "joejoe" || ac.Password != "hello" { + t.Fatalf("Missing data from parsing:\n%q", config) + } + + // Now save it and make sure it shows up in new form + err = config.Save() + if err != nil { + t.Fatalf("Failed to save: %q", err) + } + + buf, err := ioutil.ReadFile(filepath.Join(tmpHome, CONFIGFILE)) + if !strings.Contains(string(buf), `"auths":`) || + !strings.Contains(string(buf), "user@example.com") { + t.Fatalf("Should have save in new form: %s", string(buf)) + } +}