Fix ConfigFile.Save() replacing symlink with file

In situations where `~/.docker/config.json` was a symlink, saving
the file would replace the symlink with a file, instead of updating
the target file location;

    mkdir -p ~/.docker
    touch ~/real-config.json
    ln -s ~/real-config.json ~/.docker/config.json

    ls -la ~/.docker/config.json
    # lrwxrwxrwx 1 root root 22 Jun 23 12:34 /root/.docker/config.json -> /root/real-config.json

    docker login
    # Username: thajeztah
    # Password:

    # Login Succeeded

    ls -la ~/.docker/config.json
    -rw-r--r-- 1 root root 229 Jun 23 12:36 /root/.docker/config.json

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2020-07-15 17:59:47 +02:00
parent 03716c0a07
commit 75ab44af6f
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
2 changed files with 36 additions and 2 deletions

View File

@ -209,9 +209,15 @@ func (configFile *ConfigFile) Save() (retErr error) {
return errors.Wrap(err, "error closing temp file") 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 // Try copying the current config file (if any) ownership and permissions
copyFilePermissions(configFile.Filename, temp.Name()) copyFilePermissions(cfgFile, temp.Name())
return os.Rename(temp.Name(), configFile.Filename) return os.Rename(temp.Name(), cfgFile)
} }
// 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

@ -11,6 +11,7 @@ import (
"github.com/docker/cli/cli/config/types" "github.com/docker/cli/cli/config/types"
"gotest.tools/v3/assert" "gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp" is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/fs"
"gotest.tools/v3/golden" "gotest.tools/v3/golden"
) )
@ -468,6 +469,33 @@ func TestSave(t *testing.T) {
assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}")) 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) { func TestPluginConfig(t *testing.T) {
configFile := New("test-plugin") configFile := New("test-plugin")
defer os.Remove("test-plugin") defer os.Remove("test-plugin")