From 6482a8c46f5f628087dec03451f0f10c00fbac9c Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Mon, 12 Jun 2023 23:08:13 +0200 Subject: [PATCH 1/4] Include [DEFAULT] section header when writing ~/.databrickscfg The ini library omits the default section header and in doing so breaks compatibility with Python's config parser. It raises: ``` Error: MissingSectionHeaderError: File contains no section headers. ``` This commit makes sure the DEFAULT section header is included. If the config file doesn't include a DEFAULT section itself, we include a comment describing its purpose. --- libs/databrickscfg/ops.go | 14 +++++++++++++ libs/databrickscfg/ops_test.go | 37 +++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/libs/databrickscfg/ops.go b/libs/databrickscfg/ops.go index 8b4e1a5f30..13bc80a849 100644 --- a/libs/databrickscfg/ops.go +++ b/libs/databrickscfg/ops.go @@ -13,6 +13,8 @@ import ( const fileMode = 0o600 +const defaultComment = "The profile defined in the DEFAULT section is be used as fallback when no profile is explicitly specified." + func loadOrCreateConfigFile(filename string) (*config.File, error) { if filename == "" { filename = "~/.databrickscfg" @@ -104,6 +106,12 @@ func SaveToProfile(ctx context.Context, cfg *config.Config) error { key.SetValue(attr.GetString(cfg)) } + // Add a comment to the default section if it's empty. + section = configFile.Section(ini.DefaultSection) + if len(section.Keys()) == 0 && section.Comment == "" { + section.Comment = defaultComment + } + orig, backupErr := os.ReadFile(configFile.Path()) if len(orig) > 0 && backupErr == nil { log.Infof(ctx, "Backing up in %s.bak", configFile.Path()) @@ -120,3 +128,9 @@ func SaveToProfile(ctx context.Context, cfg *config.Config) error { } return configFile.SaveTo(configFile.Path()) } + +func init() { + // We document databrickscfg files with a [DEFAULT] header and wish to keep it that way. + // This, however, does mean we emit a [DEFAULT] section even if it's empty. + ini.DefaultHeader = true +} diff --git a/libs/databrickscfg/ops_test.go b/libs/databrickscfg/ops_test.go index 64b4fbadfd..42c8402c03 100644 --- a/libs/databrickscfg/ops_test.go +++ b/libs/databrickscfg/ops_test.go @@ -2,11 +2,13 @@ package databrickscfg import ( "context" + "os" "path/filepath" "testing" "github.com/databricks/databricks-sdk-go/config" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestLoadOrCreate(t *testing.T) { @@ -129,7 +131,7 @@ func TestSaveToProfile_ErrorOnMatch(t *testing.T) { assert.Error(t, err) } -func TestSaveToProfile_NewFile(t *testing.T) { +func TestSaveToProfile_NewFileWithoutDefault(t *testing.T) { ctx := context.Background() path := filepath.Join(t.TempDir(), "databrickscfg") @@ -141,6 +143,39 @@ func TestSaveToProfile_NewFile(t *testing.T) { }) assert.NoError(t, err) assert.NoFileExists(t, path+".bak") + + contents, err := os.ReadFile(path) + require.NoError(t, err) + assert.Equal(t, + `; The profile defined in the DEFAULT section is be used as fallback when no profile is explicitly specified. +[DEFAULT] + +[abc] +host = https://foo +token = xyz +`, string(contents)) +} + +func TestSaveToProfile_NewFileWithDefault(t *testing.T) { + ctx := context.Background() + path := filepath.Join(t.TempDir(), "databrickscfg") + + err := SaveToProfile(ctx, &config.Config{ + ConfigFile: path, + Profile: "DEFAULT", + Host: "https://foo", + Token: "xyz", + }) + assert.NoError(t, err) + assert.NoFileExists(t, path+".bak") + + contents, err := os.ReadFile(path) + require.NoError(t, err) + assert.Equal(t, + `[DEFAULT] +host = https://foo +token = xyz +`, string(contents)) } func TestSaveToProfile_ClearingPreviousProfile(t *testing.T) { From c3a03149afcd630bd79b37fe2645f37dd76b4a53 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Mon, 12 Jun 2023 23:16:36 +0200 Subject: [PATCH 2/4] . From 1a7bd4689048ce4c052956b706171270f518e0d9 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Mon, 12 Jun 2023 23:57:12 +0200 Subject: [PATCH 3/4] Update libs/databrickscfg/ops.go Co-authored-by: PaulCornellDB --- libs/databrickscfg/ops.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/databrickscfg/ops.go b/libs/databrickscfg/ops.go index 13bc80a849..52a966ef96 100644 --- a/libs/databrickscfg/ops.go +++ b/libs/databrickscfg/ops.go @@ -13,7 +13,7 @@ import ( const fileMode = 0o600 -const defaultComment = "The profile defined in the DEFAULT section is be used as fallback when no profile is explicitly specified." +const defaultComment = "The profile defined in the DEFAULT section is to be used as a fallback when no profile is explicitly specified." func loadOrCreateConfigFile(filename string) (*config.File, error) { if filename == "" { From df414cf1645d3a265f5c642874596c2d7301dc51 Mon Sep 17 00:00:00 2001 From: Pieter Noordhuis Date: Mon, 12 Jun 2023 23:57:17 +0200 Subject: [PATCH 4/4] Update libs/databrickscfg/ops_test.go Co-authored-by: PaulCornellDB --- libs/databrickscfg/ops_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/databrickscfg/ops_test.go b/libs/databrickscfg/ops_test.go index 42c8402c03..233555fe2c 100644 --- a/libs/databrickscfg/ops_test.go +++ b/libs/databrickscfg/ops_test.go @@ -147,7 +147,7 @@ func TestSaveToProfile_NewFileWithoutDefault(t *testing.T) { contents, err := os.ReadFile(path) require.NoError(t, err) assert.Equal(t, - `; The profile defined in the DEFAULT section is be used as fallback when no profile is explicitly specified. + `; The profile defined in the DEFAULT section is to be used as a fallback when no profile is explicitly specified. [DEFAULT] [abc]