From 7e9e2c10bccd6f01af6e1272b4f962ae3dea5a2d Mon Sep 17 00:00:00 2001 From: Benoit Sigoure Date: Mon, 10 Sep 2018 13:39:30 -0700 Subject: [PATCH] cli/config/configfile: Atomically rewrite the config file when saving. The config file was being truncated first, which created a window during which it was empty, causing concurrent uses of the `docker` command to potentially fail with: WARNING: Error loading config file: /var/lib/jenkins/.docker/config.json: EOF Error response from daemon: Get https://registry/v2/foo/manifests/latest: no basic auth credentials Signed-off-by: Benoit Sigoure --- cli/config/configfile/file.go | 14 ++++++++++---- cli/config/configfile/file_test.go | 12 ++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/cli/config/configfile/file.go b/cli/config/configfile/file.go index 37e1533f42..7fa9b734b9 100644 --- a/cli/config/configfile/file.go +++ b/cli/config/configfile/file.go @@ -175,15 +175,21 @@ func (configFile *ConfigFile) Save() error { return errors.Errorf("Can't save config with empty filename") } - if err := os.MkdirAll(filepath.Dir(configFile.Filename), 0700); err != nil { + dir := filepath.Dir(configFile.Filename) + if err := os.MkdirAll(dir, 0700); err != nil { return err } - f, err := os.OpenFile(configFile.Filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) + temp, err := ioutil.TempFile(dir, filepath.Base(configFile.Filename)) if err != nil { return err } - defer f.Close() - return configFile.SaveToWriter(f) + err = configFile.SaveToWriter(temp) + temp.Close() + if err != nil { + os.Remove(temp.Name()) + return err + } + return os.Rename(temp.Name(), configFile.Filename) } // ParseProxyConfig computes proxy configuration by retrieving the config for the provided host and diff --git a/cli/config/configfile/file_test.go b/cli/config/configfile/file_test.go index f0704cef3f..765a9f9cd9 100644 --- a/cli/config/configfile/file_test.go +++ b/cli/config/configfile/file_test.go @@ -2,6 +2,8 @@ package configfile import ( "fmt" + "io/ioutil" + "os" "testing" "github.com/docker/cli/cli/config/credentials" @@ -413,3 +415,13 @@ func TestCheckKubernetesConfigurationRaiseAnErrorOnInvalidValue(t *testing.T) { } } } + +func TestSave(t *testing.T) { + configFile := New("test-save") + defer os.Remove("test-save") + err := configFile.Save() + assert.NilError(t, err) + cfg, err := ioutil.ReadFile("test-save") + assert.NilError(t, err) + assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}")) +}