diff --git a/cli/config/configfile/file.go b/cli/config/configfile/file.go index ae9dcb3370..2fa34f9094 100644 --- a/cli/config/configfile/file.go +++ b/cli/config/configfile/file.go @@ -171,6 +171,10 @@ func (configFile *ConfigFile) Save() (retErr error) { cfgFile := configFile.Filename if f, err := os.Readlink(cfgFile); err == nil { cfgFile = f + // The target of a symlink can be a relative path, ensure the final path is absolute + if !filepath.IsAbs(f) { + cfgFile = filepath.Join(dir, f) + } } // 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")