diff --git a/.nextchanges/cli/dotted-profile-parent-section.md b/.nextchanges/cli/dotted-profile-parent-section.md new file mode 100644 index 00000000000..bc0b308217f --- /dev/null +++ b/.nextchanges/cli/dotted-profile-parent-section.md @@ -0,0 +1 @@ +* Fix `databricks auth login` and `databricks configure` overwriting the parent profile's host and token when saving a profile with a dotted name such as `team.prod` next to an existing `[team]`. ([#7266](https://github.com/databricks/cli/pull/7266)) diff --git a/libs/databrickscfg/ops.go b/libs/databrickscfg/ops.go index 6358c5ba49d..c017dd06a67 100644 --- a/libs/databrickscfg/ops.go +++ b/libs/databrickscfg/ops.go @@ -278,8 +278,9 @@ func SaveResourcesToProfile(ctx context.Context, profileName, configFilePath str } if len(resources) == 0 { section.DeleteKey("resources") - } else { - section.Key("resources").SetValue(strings.Join(resources, ",")) + } else if _, err := section.NewKey("resources", strings.Join(resources, ",")); err != nil { + // See SaveToProfile for why this is not Section.Key. + return err } return writeConfigFile(ctx, configFile) } @@ -461,8 +462,12 @@ func SaveToProfile(ctx context.Context, cfg *config.Config, clearKeys ...string) if attr.IsZero(cfg) { continue } - key := section.Key(attr.Name) - key.SetValue(attr.GetString(cfg)) + // Section.Key falls back to the parent section for dotted names + // ([team] for [team.prod]), so it would overwrite the parent's value. + // NewKey only looks at the section's own keys. + if _, err := section.NewKey(attr.Name, attr.GetString(cfg)); err != nil { + return err + } } // Auto-set default profile when saving the first profile to the config file. diff --git a/libs/databrickscfg/ops_test.go b/libs/databrickscfg/ops_test.go index 8fe1956a81f..49fa1bd1458 100644 --- a/libs/databrickscfg/ops_test.go +++ b/libs/databrickscfg/ops_test.go @@ -212,6 +212,38 @@ func TestSaveResourcesToProfile(t *testing.T) { assert.NotContains(t, string(contents), "resources") } +func TestSaveToProfile_DottedNameDoesNotWriteParentSection(t *testing.T) { + ctx := t.Context() + path := filepath.Join(t.TempDir(), "databrickscfg") + err := os.WriteFile(path, []byte("[team]\nhost = https://team.test\ntoken = xyz\nresources = https://team.test/a\n"), 0o600) + require.NoError(t, err) + + require.NoError(t, SaveToProfile(ctx, &config.Config{ + ConfigFile: path, + Profile: "team.prod", + Host: "https://prod.test", + AuthType: "databricks-cli", + })) + require.NoError(t, SaveResourcesToProfile(ctx, "team.prod", path, []string{"https://prod.test/b"})) + + file, err := loadConfigFile(ctx, path) + require.NoError(t, err) + parent, err := file.GetSection("team") + require.NoError(t, err) + assert.Equal(t, map[string]string{ + "host": "https://team.test", + "token": "xyz", + "resources": "https://team.test/a", + }, parent.KeysHash()) + child, err := file.GetSection("team.prod") + require.NoError(t, err) + assert.Equal(t, map[string]string{ + "host": "https://prod.test", + "auth_type": "databricks-cli", + "resources": "https://prod.test/b", + }, child.KeysHash()) +} + func TestSaveResourcesToProfile_MissingProfile(t *testing.T) { ctx := t.Context() path := filepath.Join(t.TempDir(), "databrickscfg")