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 <tsunanet@gmail.com>
This commit is contained in:
Benoit Sigoure 2018-09-10 13:39:30 -07:00
parent 8ec21567a7
commit 7e9e2c10bc
2 changed files with 22 additions and 4 deletions

View File

@ -175,15 +175,21 @@ func (configFile *ConfigFile) Save() error {
return errors.Errorf("Can't save config with empty filename") 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 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 { if err != nil {
return err return err
} }
defer f.Close() err = configFile.SaveToWriter(temp)
return configFile.SaveToWriter(f) 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 // ParseProxyConfig computes proxy configuration by retrieving the config for the provided host and

View File

@ -2,6 +2,8 @@ package configfile
import ( import (
"fmt" "fmt"
"io/ioutil"
"os"
"testing" "testing"
"github.com/docker/cli/cli/config/credentials" "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}"))
}