databricks-cli/libs/databrickscfg/loader.go

127 lines
3.3 KiB
Go
Raw Normal View History

package databrickscfg
import (
"context"
"errors"
"fmt"
"os"
"strings"
"github.com/databricks/cli/libs/log"
"github.com/databricks/databricks-sdk-go/config"
"gopkg.in/ini.v1"
)
var ResolveProfileFromHost = profileFromHostLoader{}
var errNoMatchingProfiles = errors.New("no matching config profiles found")
type errMultipleProfiles []string
func (e errMultipleProfiles) Error() string {
return fmt.Sprintf("multiple profiles matched: %s", strings.Join(e, ", "))
}
func findMatchingProfile(configFile *config.File, matcher func(*ini.Section) bool) (*ini.Section, error) {
// Look for sections in the configuration file that match the configured host.
var matching []*ini.Section
for _, section := range configFile.Sections() {
if !matcher(section) {
continue
}
matching = append(matching, section)
}
// If there are no matching sections, we don't do anything.
if len(matching) == 0 {
return nil, errNoMatchingProfiles
}
// If there are multiple matching sections, let the user know it is impossible
// to unambiguously select a profile to use.
if len(matching) > 1 {
var names errMultipleProfiles
for _, section := range matching {
names = append(names, section.Name())
}
return nil, names
}
return matching[0], nil
}
type profileFromHostLoader struct{}
func (l profileFromHostLoader) Name() string {
return "resolve-profile-from-host"
}
func (l profileFromHostLoader) Configure(cfg *config.Config) error {
// Skip an attempt to resolve a profile from the host if any authentication
// is already configured (either directly, through environment variables, or
// if a profile was specified).
if cfg.Host == "" || l.isAnyAuthConfigured(cfg) {
return nil
}
ctx := context.Background()
configFile, err := config.LoadFile(cfg.ConfigFile)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("cannot parse config file: %w", err)
}
// Normalized version of the configured host.
host := normalizeHost(cfg.Host)
match, err := findMatchingProfile(configFile, func(s *ini.Section) bool {
key, err := s.GetKey("host")
if err != nil {
log.Tracef(ctx, "section %s: %s", s.Name(), err)
return false
}
// Check if this section matches the normalized host
return normalizeHost(key.Value()) == host
})
if err == errNoMatchingProfiles {
return nil
}
if err, ok := err.(errMultipleProfiles); ok {
return fmt.Errorf(
"%s: %w: please set DATABRICKS_CONFIG_PROFILE or provide --profile flag to specify one",
host, err)
}
if err != nil {
return err
}
log.Debugf(ctx, "Loading profile %s because of host match", match.Name())
Added `auth describe` command (#1244) ## Changes This command provide details on auth configuration user is using as well as authenticated user and auth mechanism used. Relies on https://github.com/databricks/databricks-sdk-go/pull/838 (tests will fail until merged) Examples of output ``` Workspace: https://test.com User: andrew.nester@databricks.com Authenticated with: pat ----- Configuration: ✓ auth_type: pat ✓ host: https://test.com (from bundle) ✓ profile: DEFAULT (from --profile flag) ✓ token: ******** (from /Users/andrew.nester/.databrickscfg config file) ``` ``` DATABRICKS_AUTH_TYPE=azure-msi databricks auth describe -p "Azure 2" Unable to authenticate: inner token: Post "https://foobar.com/oauth2/token": AADSTS900023: Specified tenant identifier foobar_aaaaaaa' is neither a valid DNS name, nor a valid external domain. See https://login.microsoftonline.com/error?code=900023 ----- Configuration: ✓ auth_type: azure-msi (from DATABRICKS_AUTH_TYPE environment variable) ✓ azure_client_id: 8470f3ba-aaaa-bbbb-cccc-xxxxyyyyzzzz (from /Users/andrew.nester/.databrickscfg config file) ~ azure_client_secret: ******** (from /Users/andrew.nester/.databrickscfg config file, not used for auth type azure-msi) ~ azure_tenant_id: foobar_aaaaaaa (from /Users/andrew.nester/.databrickscfg config file, not used for auth type azure-msi) ✓ azure_use_msi: true (from /Users/andrew.nester/.databrickscfg config file) ✓ host: https://foobar.com (from /Users/andrew.nester/.databrickscfg config file) ✓ profile: Azure 2 (from --profile flag) ``` For account ``` Unable to authenticate: default auth: databricks-cli: cannot get access token: Error: token refresh: Post "https://xxxxxxx.com/v1/token": http 400: {"error":"invalid_request","error_description":"Refresh token is invalid"} . Config: host=https://xxxxxxx.com, account_id=ed0ca3c5-fae5-4619-bb38-eebe04a4af4b, profile=ACCOUNT-ed0ca3c5-fae5-4619-bb38-eebe04a4af4b ----- Configuration: ✓ account_id: ed0ca3c5-fae5-4619-bb38-eebe04a4af4b (from /Users/andrew.nester/.databrickscfg config file) ✓ auth_type: databricks-cli (from /Users/andrew.nester/.databrickscfg config file) ✓ host: https://xxxxxxxxx.com (from /Users/andrew.nester/.databrickscfg config file) ✓ profile: ACCOUNT-ed0ca3c5-fae5-4619-bb38-eebe04a4af4b ``` ## Tests Added unit tests --------- Co-authored-by: Julia Crawford (Databricks) <julia.crawford@databricks.com>
2024-04-03 08:14:04 +00:00
err = config.ConfigAttributes.ResolveFromStringMapWithSource(cfg, match.KeysHash(), config.Source{
Type: config.SourceFile,
Name: configFile.Path(),
})
if err != nil {
return fmt.Errorf("%s %s profile: %w", configFile.Path(), match.Name(), err)
}
cfg.Profile = match.Name()
return nil
}
func (l profileFromHostLoader) isAnyAuthConfigured(cfg *config.Config) bool {
// If any of the auth-specific attributes are set, we can skip profile resolution.
for _, a := range config.ConfigAttributes {
Added `auth describe` command (#1244) ## Changes This command provide details on auth configuration user is using as well as authenticated user and auth mechanism used. Relies on https://github.com/databricks/databricks-sdk-go/pull/838 (tests will fail until merged) Examples of output ``` Workspace: https://test.com User: andrew.nester@databricks.com Authenticated with: pat ----- Configuration: ✓ auth_type: pat ✓ host: https://test.com (from bundle) ✓ profile: DEFAULT (from --profile flag) ✓ token: ******** (from /Users/andrew.nester/.databrickscfg config file) ``` ``` DATABRICKS_AUTH_TYPE=azure-msi databricks auth describe -p "Azure 2" Unable to authenticate: inner token: Post "https://foobar.com/oauth2/token": AADSTS900023: Specified tenant identifier foobar_aaaaaaa' is neither a valid DNS name, nor a valid external domain. See https://login.microsoftonline.com/error?code=900023 ----- Configuration: ✓ auth_type: azure-msi (from DATABRICKS_AUTH_TYPE environment variable) ✓ azure_client_id: 8470f3ba-aaaa-bbbb-cccc-xxxxyyyyzzzz (from /Users/andrew.nester/.databrickscfg config file) ~ azure_client_secret: ******** (from /Users/andrew.nester/.databrickscfg config file, not used for auth type azure-msi) ~ azure_tenant_id: foobar_aaaaaaa (from /Users/andrew.nester/.databrickscfg config file, not used for auth type azure-msi) ✓ azure_use_msi: true (from /Users/andrew.nester/.databrickscfg config file) ✓ host: https://foobar.com (from /Users/andrew.nester/.databrickscfg config file) ✓ profile: Azure 2 (from --profile flag) ``` For account ``` Unable to authenticate: default auth: databricks-cli: cannot get access token: Error: token refresh: Post "https://xxxxxxx.com/v1/token": http 400: {"error":"invalid_request","error_description":"Refresh token is invalid"} . Config: host=https://xxxxxxx.com, account_id=ed0ca3c5-fae5-4619-bb38-eebe04a4af4b, profile=ACCOUNT-ed0ca3c5-fae5-4619-bb38-eebe04a4af4b ----- Configuration: ✓ account_id: ed0ca3c5-fae5-4619-bb38-eebe04a4af4b (from /Users/andrew.nester/.databrickscfg config file) ✓ auth_type: databricks-cli (from /Users/andrew.nester/.databrickscfg config file) ✓ host: https://xxxxxxxxx.com (from /Users/andrew.nester/.databrickscfg config file) ✓ profile: ACCOUNT-ed0ca3c5-fae5-4619-bb38-eebe04a4af4b ``` ## Tests Added unit tests --------- Co-authored-by: Julia Crawford (Databricks) <julia.crawford@databricks.com>
2024-04-03 08:14:04 +00:00
if !a.HasAuthAttribute() {
continue
}
if !a.IsZero(cfg) {
return true
}
}
// If the auth type is set, we can skip profile resolution.
// For example, to force "azure-cli", only the host and the auth type will be set.
return cfg.AuthType != ""
}