diff --git a/cli/config/configfile/file.go b/cli/config/configfile/file.go index 66ba413649..951fd973eb 100644 --- a/cli/config/configfile/file.go +++ b/cli/config/configfile/file.go @@ -209,9 +209,15 @@ func (configFile *ConfigFile) Save() (retErr error) { return errors.Wrap(err, "error closing temp file") } + // Handle situation where the configfile is a symlink + cfgFile := configFile.Filename + if f, err := os.Readlink(cfgFile); err == nil { + cfgFile = f + } + // Try copying the current config file (if any) ownership and permissions - copyFilePermissions(configFile.Filename, temp.Name()) - return os.Rename(temp.Name(), configFile.Filename) + copyFilePermissions(cfgFile, temp.Name()) + return os.Rename(temp.Name(), cfgFile) } // 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 ab20602f0d..d6b6228bc3 100644 --- a/cli/config/configfile/file_test.go +++ b/cli/config/configfile/file_test.go @@ -11,6 +11,7 @@ import ( "github.com/docker/cli/cli/config/types" "gotest.tools/v3/assert" is "gotest.tools/v3/assert/cmp" + "gotest.tools/v3/fs" "gotest.tools/v3/golden" ) @@ -468,6 +469,33 @@ func TestSave(t *testing.T) { assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}")) } +func TestSaveWithSymlink(t *testing.T) { + dir := fs.NewDir(t, t.Name(), fs.WithFile("real-config.json", `{}`)) + defer dir.Remove() + + symLink := dir.Join("config.json") + realFile := dir.Join("real-config.json") + err := os.Symlink(realFile, symLink) + assert.NilError(t, err) + + configFile := New(symLink) + + err = configFile.Save() + assert.NilError(t, err) + + fi, err := os.Lstat(symLink) + assert.NilError(t, err) + assert.Assert(t, fi.Mode()&os.ModeSymlink != 0, "expected %s to be a symlink", symLink) + + cfg, err := ioutil.ReadFile(symLink) + assert.NilError(t, err) + assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}")) + + cfg, err = ioutil.ReadFile(realFile) + assert.NilError(t, err) + assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}")) +} + func TestPluginConfig(t *testing.T) { configFile := New("test-plugin") defer os.Remove("test-plugin")