Always resolve .databrickscfg file (#659)

## Changes
#629 introduced a change to autopopulate the host from .databrickscfg if
the user is logging back into a host they were previously using. This
did not respect the DATABRICKS_CONFIG_FILE env variable, causing the
flow to stop working for users with no .databrickscfg file in their home
directory.

This PR refactors all config file loading to go through one interface,
`databrickscfg.GetDatabricksCfg()`, and an auxiliary
`databrickscfg.GetDatabricksCfgPath()` to get the configured file path.

Closes #655.

## Tests
```
$ databricks auth login --profile abc
Error: open /Users/miles/.databrickscfg: no such file or directory

$ ./cli auth login --profile abc
Error: cannot load Databricks config file: open /Users/miles/.databrickscfg: no such file or directory


$ DATABRICKS_CONFIG_FILE=~/.databrickscfg.bak ./cli auth login --profile abc
Databricks Host: https://asdf
```
This commit is contained in:
Miles Yucht 2023-08-14 14:45:08 +02:00 committed by GitHub
parent 97699b849f
commit 5b819cd982
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 55 additions and 38 deletions

View File

@ -9,6 +9,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"github.com/databricks/cli/libs/databrickscfg"
"github.com/databricks/databricks-sdk-go/config" "github.com/databricks/databricks-sdk-go/config"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
@ -28,7 +29,7 @@ func canonicalHost(host string) (string, error) {
var ErrNoMatchingProfiles = errors.New("no matching profiles found") var ErrNoMatchingProfiles = errors.New("no matching profiles found")
func resolveSection(cfg *config.Config, iniFile *ini.File) (*ini.Section, error) { func resolveSection(cfg *config.Config, iniFile *config.File) (*ini.Section, error) {
var candidates []*ini.Section var candidates []*ini.Section
configuredHost, err := canonicalHost(cfg.Host) configuredHost, err := canonicalHost(cfg.Host)
if err != nil { if err != nil {
@ -68,7 +69,7 @@ func resolveSection(cfg *config.Config, iniFile *ini.File) (*ini.Section, error)
} }
func loadFromDatabricksCfg(cfg *config.Config) error { func loadFromDatabricksCfg(cfg *config.Config) error {
iniFile, err := getDatabricksCfg() iniFile, err := databrickscfg.Get()
if errors.Is(err, fs.ErrNotExist) { if errors.Is(err, fs.ErrNotExist) {
// it's fine not to have ~/.databrickscfg // it's fine not to have ~/.databrickscfg
return nil return nil

View File

@ -61,7 +61,7 @@ func newLoginCommand(persistentAuth *auth.PersistentAuth) *cobra.Command {
} }
// If the chosen profile has a hostname and the user hasn't specified a host, infer the host from the profile. // If the chosen profile has a hostname and the user hasn't specified a host, infer the host from the profile.
_, profiles, err := databrickscfg.LoadProfiles(databrickscfg.DefaultPath, func(p databrickscfg.Profile) bool { _, profiles, err := databrickscfg.LoadProfiles(func(p databrickscfg.Profile) bool {
return p.Name == profileName return p.Name == profileName
}) })
if err != nil { if err != nil {

View File

@ -5,32 +5,16 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"os" "os"
"path/filepath"
"strings"
"sync" "sync"
"github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/cmdio"
"github.com/databricks/cli/libs/databrickscfg"
"github.com/databricks/databricks-sdk-go" "github.com/databricks/databricks-sdk-go"
"github.com/databricks/databricks-sdk-go/config" "github.com/databricks/databricks-sdk-go/config"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"gopkg.in/ini.v1" "gopkg.in/ini.v1"
) )
func getDatabricksCfg() (*ini.File, error) {
configFile := os.Getenv("DATABRICKS_CONFIG_FILE")
if configFile == "" {
configFile = "~/.databrickscfg"
}
if strings.HasPrefix(configFile, "~") {
homedir, err := os.UserHomeDir()
if err != nil {
return nil, fmt.Errorf("cannot find homedir: %w", err)
}
configFile = filepath.Join(homedir, configFile[1:])
}
return ini.Load(configFile)
}
type profileMetadata struct { type profileMetadata struct {
Name string `json:"name"` Name string `json:"name"`
Host string `json:"host,omitempty"` Host string `json:"host,omitempty"`
@ -111,10 +95,12 @@ func newProfilesCommand() *cobra.Command {
cmd.RunE = func(cmd *cobra.Command, args []string) error { cmd.RunE = func(cmd *cobra.Command, args []string) error {
var profiles []*profileMetadata var profiles []*profileMetadata
iniFile, err := getDatabricksCfg() iniFile, err := databrickscfg.Get()
if os.IsNotExist(err) { if os.IsNotExist(err) {
// return empty list for non-configured machines // return empty list for non-configured machines
iniFile = ini.Empty() iniFile = &config.File{
File: &ini.File{},
}
} else if err != nil { } else if err != nil {
return fmt.Errorf("cannot parse config file: %w", err) return fmt.Errorf("cannot parse config file: %w", err)
} }

View File

@ -40,10 +40,7 @@ func MustAccountClient(cmd *cobra.Command, args []string) error {
// 1. only admins will have account configured // 1. only admins will have account configured
// 2. 99% of admins will have access to just one account // 2. 99% of admins will have access to just one account
// hence, we don't need to create a special "DEFAULT_ACCOUNT" profile yet // hence, we don't need to create a special "DEFAULT_ACCOUNT" profile yet
_, profiles, err := databrickscfg.LoadProfiles( _, profiles, err := databrickscfg.LoadProfiles(databrickscfg.MatchAccountProfiles)
databrickscfg.DefaultPath,
databrickscfg.MatchAccountProfiles,
)
if err != nil { if err != nil {
return err return err
} }
@ -124,8 +121,11 @@ func transformLoadError(path string, err error) error {
} }
func askForWorkspaceProfile() (string, error) { func askForWorkspaceProfile() (string, error) {
path := databrickscfg.DefaultPath path, err := databrickscfg.GetPath()
file, profiles, err := databrickscfg.LoadProfiles(path, databrickscfg.MatchWorkspaceProfiles) if err != nil {
return "", fmt.Errorf("cannot determine Databricks config file path: %w", err)
}
file, profiles, err := databrickscfg.LoadProfiles(databrickscfg.MatchWorkspaceProfiles)
if err != nil { if err != nil {
return "", transformLoadError(path, err) return "", transformLoadError(path, err)
} }
@ -156,8 +156,11 @@ func askForWorkspaceProfile() (string, error) {
} }
func askForAccountProfile() (string, error) { func askForAccountProfile() (string, error) {
path := databrickscfg.DefaultPath path, err := databrickscfg.GetPath()
file, profiles, err := databrickscfg.LoadProfiles(path, databrickscfg.MatchAccountProfiles) if err != nil {
return "", fmt.Errorf("cannot determine Databricks config file path: %w", err)
}
file, profiles, err := databrickscfg.LoadProfiles(databrickscfg.MatchAccountProfiles)
if err != nil { if err != nil {
return "", transformLoadError(path, err) return "", transformLoadError(path, err)
} }

View File

@ -1,7 +1,9 @@
package databrickscfg package databrickscfg
import ( import (
"fmt"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/databricks/databricks-sdk-go/config" "github.com/databricks/databricks-sdk-go/config"
@ -64,12 +66,34 @@ func MatchAllProfiles(p Profile) bool {
return true return true
} }
const DefaultPath = "~/.databrickscfg" // Get the path to the .databrickscfg file, falling back to the default in the current user's home directory.
func GetPath() (string, error) {
configFile := os.Getenv("DATABRICKS_CONFIG_FILE")
if configFile == "" {
configFile = "~/.databrickscfg"
}
if strings.HasPrefix(configFile, "~") {
homedir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("cannot find homedir: %w", err)
}
configFile = filepath.Join(homedir, configFile[1:])
}
return configFile, nil
}
func LoadProfiles(path string, fn ProfileMatchFunction) (file string, profiles Profiles, err error) { func Get() (*config.File, error) {
f, err := config.LoadFile(path) configFile, err := GetPath()
if err != nil { if err != nil {
return return nil, fmt.Errorf("cannot determine Databricks config file path: %w", err)
}
return config.LoadFile(configFile)
}
func LoadProfiles(fn ProfileMatchFunction) (file string, profiles Profiles, err error) {
f, err := Get()
if err != nil {
return "", nil, fmt.Errorf("cannot load Databricks config file: %w", err)
} }
homedir, err := os.UserHomeDir() homedir, err := os.UserHomeDir()
@ -106,7 +130,7 @@ func LoadProfiles(path string, fn ProfileMatchFunction) (file string, profiles P
} }
func ProfileCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { func ProfileCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
_, profiles, err := LoadProfiles(DefaultPath, MatchAllProfiles) _, profiles, err := LoadProfiles(MatchAllProfiles)
if err != nil { if err != nil {
return nil, cobra.ShellCompDirectiveError return nil, cobra.ShellCompDirectiveError
} }

View File

@ -32,19 +32,22 @@ func TestLoadProfilesReturnsHomedirAsTilde(t *testing.T) {
} else { } else {
t.Setenv("HOME", "./testdata") t.Setenv("HOME", "./testdata")
} }
file, _, err := LoadProfiles("./testdata/databrickscfg", func(p Profile) bool { return true }) t.Setenv("DATABRICKS_CONFIG_FILE", "./testdata/databrickscfg")
file, _, err := LoadProfiles(func(p Profile) bool { return true })
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, "~/databrickscfg", file) assert.Equal(t, "~/databrickscfg", file)
} }
func TestLoadProfilesMatchWorkspace(t *testing.T) { func TestLoadProfilesMatchWorkspace(t *testing.T) {
_, profiles, err := LoadProfiles("./testdata/databrickscfg", MatchWorkspaceProfiles) t.Setenv("DATABRICKS_CONFIG_FILE", "./testdata/databrickscfg")
_, profiles, err := LoadProfiles(MatchWorkspaceProfiles)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []string{"DEFAULT", "query", "foo1", "foo2"}, profiles.Names()) assert.Equal(t, []string{"DEFAULT", "query", "foo1", "foo2"}, profiles.Names())
} }
func TestLoadProfilesMatchAccount(t *testing.T) { func TestLoadProfilesMatchAccount(t *testing.T) {
_, profiles, err := LoadProfiles("./testdata/databrickscfg", MatchAccountProfiles) t.Setenv("DATABRICKS_CONFIG_FILE", "./testdata/databrickscfg")
_, profiles, err := LoadProfiles(MatchAccountProfiles)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, []string{"acc"}, profiles.Names()) assert.Equal(t, []string{"acc"}, profiles.Names())
} }