diff --git a/cli/config/configfile/file.go b/cli/config/configfile/file.go index ae9dcb3370..c9652118f4 100644 --- a/cli/config/configfile/file.go +++ b/cli/config/configfile/file.go @@ -167,10 +167,13 @@ func (configFile *ConfigFile) Save() (retErr error) { return errors.Wrap(err, "error closing temp file") } - // Handle situation where the configfile is a symlink + // Handle situation where the configfile is a symlink, and allow for dangling symlinks cfgFile := configFile.Filename - if f, err := os.Readlink(cfgFile); err == nil { + if f, err := filepath.EvalSymlinks(cfgFile); err == nil { cfgFile = f + } else if os.IsNotExist(err) { + // extract the path from the error if the configfile does not exist or is a dangling symlink + cfgFile = err.(*os.PathError).Path } // Try copying the current config file (if any) ownership and permissions diff --git a/cli/config/configfile/file_test.go b/cli/config/configfile/file_test.go index 51aac67964..68a10e0f7b 100644 --- a/cli/config/configfile/file_test.go +++ b/cli/config/configfile/file_test.go @@ -538,6 +538,34 @@ func TestSaveWithSymlink(t *testing.T) { assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}")) } +func TestSaveWithRelativeSymlink(t *testing.T) { + dir := fs.NewDir(t, t.Name(), fs.WithFile("real-config.json", `{}`)) + defer dir.Remove() + + symLink := dir.Join("config.json") + relativeRealFile := "real-config.json" + realFile := dir.Join(relativeRealFile) + err := os.Symlink(relativeRealFile, 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 := os.ReadFile(symLink) + assert.NilError(t, err) + assert.Check(t, is.Equal(string(cfg), "{\n \"auths\": {}\n}")) + + cfg, err = os.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")