Skip to content

Add support for default_profile in [__settings__] section#1534

Merged
simonfaltum merged 10 commits intomainfrom
default-profile
Mar 17, 2026
Merged

Add support for default_profile in [__settings__] section#1534
simonfaltum merged 10 commits intomainfrom
default-profile

Conversation

@simonfaltum
Copy link
Copy Markdown
Member

Changes

When no profile is explicitly configured, the SDK now checks for a
[__settings__].default_profile key in .databrickscfg before falling
back to the DEFAULT section. This aligns the SDK with the CLI's
databricks auth switch command, which writes the user's chosen
default profile to this section.

Profile resolution order:

  1. Explicit profile (--profile flag, env var, or programmatic setting)
  2. [__settings__].default_profile (new)
  3. [DEFAULT] section (legacy fallback)

The [__settings__] section is never treated as a profile.

Backwards compatible: existing configs without [__settings__] behave identically.

Tests

Seven test scenarios covering:

  • default_profile resolves correctly
  • default_profile takes precedence over [DEFAULT]
  • Legacy fallback when no [__settings__]
  • Legacy fallback when default_profile is empty
  • [__settings__] is not a profile
  • Explicit --profile overrides default_profile
  • default_profile pointing to nonexistent section returns error

When no profile is explicitly configured, the SDK now checks for a
[__settings__].default_profile key in .databrickscfg before falling
back to the DEFAULT section. This aligns the SDK with the CLI's
'databricks auth switch' command, which writes the user's chosen
default profile to this section.

Profile resolution order:
1. Explicit profile (--profile flag, env var, or programmatic)
2. [__settings__].default_profile (new)
3. [DEFAULT] section (legacy fallback)

The [__settings__] section is never treated as a profile.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Signed-off-by: simon <[email protected]>
- Reject default_profile = __settings__ (self-reference falls through
  to DEFAULT instead of trying to load __settings__ as a profile)
- Add test for the self-reference guard with dedicated fixture
- Differentiate TestConfigFile_DefaultProfileResolves from the
  precedence test by using a fixture without a [DEFAULT] section
- Strengthen TestConfigFile_SettingsSectionNotAProfile to verify
  __settings__ is present in SectionStrings (documenting that callers
  must filter it) and that resolveDefaultProfile still rejects it
Persist the resolved profile name and reject the reserved settings section as a profile target so OAuth cache keys and CLI auth keep using the selected profile.
@simonfaltum simonfaltum marked this pull request as ready for review March 11, 2026 16:23
@renaudhartert-db
Copy link
Copy Markdown
Contributor

renaudhartert-db commented Mar 16, 2026

I think the logic in Configure is getting a bit hard to follow with the two booleans (hasExplicitProfile, hasDefaultProfileSetting). The condition !hasExplicitProfile && !hasDefaultProfileSetting is really encoding a three-way state: explicit, from settings, or default fallback.

Also, unless I'm missing something, the branch where profile == settingsSection and both booleans are false is unreachable -- resolveDefaultProfile only returns "__settings__" with hasDefaultProfileSetting = true, and the fallback is always "DEFAULT". So the profile == settingsSection guard and the !hasExplicitProfile && !hasDefaultProfileSetting check can't interact in practice, but it takes a moment to convince yourself of that.

What would you think of moving all the resolution logic (including the __settings__ handling) into a single function that returns the resolved profile and whether it's a silent fallback?

// resolveProfile returns the profile name to use and whether it is a
// default fallback:
//   - If cfg.Profile is set explicitly, returns it with false.
//   - If [__settings__].default_profile is set, returns it with false.
//   - Otherwise, returns "DEFAULT" with true.
//
// Returns an error if cfg.Profile is the reserved __settings__ section name.
func resolveProfile(cfg *Config, f *File) (string, bool, error) {
    if cfg.Profile != "" {
        if cfg.Profile == settingsSection {
            return "", false, fmt.Errorf(
                "%s is a reserved section name and cannot be used as a profile", settingsSection)
        }
        return cfg.Profile, false, nil
    }
    section, err := f.GetSection(settingsSection)
    if err == nil {
        key, err := section.GetKey("default_profile")
        if err == nil {
            v := strings.TrimSpace(key.String())
            if v != "" && v != settingsSection {
                return v, false, nil
            }
        }
    }
    return "DEFAULT", true, nil
}

Then Configure doesn't need to know about __settings__ at all:

profile, isDefault, err := resolveProfile(cfg, configFile)
if err != nil {
    return err
}

profileValues := configFile.Section(profile)
if len(profileValues.Keys()) == 0 {
    if isDefault {
        logger.Debugf(context.Background(), "%s has no %s profile configured", configFile.Path(), profile)
        return nil
    }
    return fmt.Errorf("%s has no %s profile configured", configFile.Path(), profile)
}

err = cfg.loadProfileFromSection(profileValues)
if err != nil {
    return fmt.Errorf("%s %s profile: %w", configFile.Path(), profile, err)
}
if !isDefault {
    cfg.Profile = profile
}
return nil

The wins:

  • isDefault reads more naturally than !hasExplicitProfile && !hasDefaultProfileSetting.
  • All __settings__ knowledge is encapsulated in resolveProfile -- it doesn't leak into Configure.

Replaces resolveDefaultProfile and the two-boolean logic in Configure
with a single resolveProfile(requestedProfile, file) function that
returns (profile, isFallback, error). This encapsulates all __settings__
knowledge in one place and makes Configure read cleanly with a single
isFallback boolean instead of !hasExplicitProfile && !hasDefaultProfileSetting.

The __settings__ self-reference case now returns a clear "reserved
section name" error instead of a generic "no profile configured" message.

Co-authored-by: Isaac
Signed-off-by: simon <[email protected]>
Simplify TestConfigFile_SettingsSectionNotAProfile: replace the loop
with a direct assert.Contains since resolveProfile now validates
against __settings__ via error return.

Add TestConfigFile_ExplicitDefaultProfileViaSetting to document that
default_profile = DEFAULT is treated as an explicit selection (sets
cfg.Profile, errors on missing section) rather than as the silent
legacy fallback.

Co-authored-by: Isaac
Signed-off-by: simon <[email protected]>
@github-actions
Copy link
Copy Markdown

If integration tests don't run automatically, an authorized user can run them manually by following the instructions below:

Trigger:
go/deco-tests-run/sdk-go

Inputs:

  • PR number: 1534
  • Commit SHA: eb28f0e00c8d92db45104c5c3f2eda82392f22cc

Checks will be approved automatically on success.

Copy link
Copy Markdown
Contributor

@renaudhartert-db renaudhartert-db left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks great!

@simonfaltum simonfaltum added this pull request to the merge queue Mar 17, 2026
Merged via the queue into main with commit 6e27bb2 Mar 17, 2026
15 checks passed
@simonfaltum simonfaltum deleted the default-profile branch March 17, 2026 07:58
deco-sdk-tagging bot added a commit that referenced this pull request Mar 17, 2026
## Release v0.123.0

### New Features and Improvements

* Support `default_profile` in `[__settings__]` section of `.databrickscfg` ([#1534](#1534)).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants