Include [DEFAULT] section header when writing ~/.databrickscfg (#464)

## Changes

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.

## Tests

New tests pass. Manually confirmed the DEFAULT section header is
included.

---------

Co-authored-by: PaulCornellDB <paul.cornell@databricks.com>
This commit is contained in:
Pieter Noordhuis 2023-06-13 18:41:56 +02:00 committed by GitHub
parent f219a0da5a
commit a17876480a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 50 additions and 1 deletions

View File

@ -13,6 +13,8 @@ import (
const fileMode = 0o600
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 == "" {
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
}

View File

@ -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 to be used as a 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) {