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()) 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 { 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 != "" }