Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Release v0.100.0

### New Features and Improvements
* Support `default_profile` in `[__settings__]` section of `.databrickscfg` for consistent default profile resolution across CLI and SDKs.

### Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
@InternalApi
public class ConfigLoader {
private static final Logger LOG = LoggerFactory.getLogger(ConfigLoader.class);
private static final String SETTINGS_SECTION = "__settings__";

private static final List<ConfigAttributeAccessor> accessors = attributeAccessors();

Expand Down Expand Up @@ -94,19 +95,37 @@ static void loadFromConfig(DatabricksConfig cfg) throws IllegalAccessException {

String profile = cfg.getProfile();
boolean hasExplicitProfile = !isNullOrEmpty(profile);
boolean hasDefaultProfileSetting = false;
if (!hasExplicitProfile) {
SubnodeConfiguration settings = ini.getSection(SETTINGS_SECTION);
if (settings != null && !settings.isEmpty()) {
String defaultProfile = settings.getString("default_profile");
if (defaultProfile != null) {
defaultProfile = defaultProfile.trim();
}
if (!isNullOrEmpty(defaultProfile)) {
profile = defaultProfile;
hasDefaultProfileSetting = true;
}
}
}
if (!hasExplicitProfile && !hasDefaultProfileSetting) {
profile = "DEFAULT";
}
SubnodeConfiguration section = ini.getSection(profile);
SubnodeConfiguration section =
SETTINGS_SECTION.equals(profile) ? null : ini.getSection(profile);
boolean sectionNotPresent = section == null || section.isEmpty();
if (sectionNotPresent && !hasExplicitProfile) {
if (sectionNotPresent && !hasExplicitProfile && !hasDefaultProfileSetting) {
LOG.info("{} has no {} profile configured", configFile, profile);
return;
}
if (sectionNotPresent) {
String msg = String.format("resolve: %s has no %s profile configured", configFile, profile);
throw new DatabricksException(msg);
}
if (hasDefaultProfileSetting) {
cfg.setProfile(profile);
}

for (ConfigAttributeAccessor accessor : accessors) {
String value = section.getString(accessor.getName());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package com.databricks.sdk;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;

import com.databricks.sdk.core.ConfigResolving;
import com.databricks.sdk.core.DatabricksConfig;
import com.databricks.sdk.core.DatabricksException;
import com.databricks.sdk.core.http.HttpClient;
import com.databricks.sdk.core.utils.TestOSUtils;
import org.junit.jupiter.api.Test;

public class DefaultProfileTest implements ConfigResolving {

private DatabricksConfig createConfigWithMockClient() {
HttpClient mockClient = mock(HttpClient.class);
return new DatabricksConfig().setHttpClient(mockClient);
}

/** Test 1: default_profile resolves correctly and is written back to config */
@Test
public void testDefaultProfileResolvesCorrectly() {
StaticEnv env = new StaticEnv().with("HOME", TestOSUtils.resource("/testdata/default_profile"));
DatabricksConfig config = createConfigWithMockClient();
resolveConfig(config, env);
config.authenticate();

assertEquals("pat", config.getAuthType());
assertEquals("https://my-workspace.cloud.databricks.com", config.getHost());
assertEquals("my-workspace", config.getProfile());
}

/** Test 2: default_profile takes precedence over [DEFAULT] */
@Test
public void testDefaultProfileTakesPrecedenceOverDefault() {
StaticEnv env =
new StaticEnv().with("HOME", TestOSUtils.resource("/testdata/default_profile_precedence"));
DatabricksConfig config = createConfigWithMockClient();
resolveConfig(config, env);
config.authenticate();

assertEquals("pat", config.getAuthType());
assertEquals("https://my-workspace.cloud.databricks.com", config.getHost());
}

/** Test 3: Legacy fallback when no [__settings__] */
@Test
public void testLegacyFallbackWhenNoSettings() {
StaticEnv env = new StaticEnv().with("HOME", TestOSUtils.resource("/testdata"));
DatabricksConfig config = createConfigWithMockClient();
resolveConfig(config, env);
config.authenticate();

assertEquals("pat", config.getAuthType());
assertEquals("https://dbc-XXXXXXXX-YYYY.cloud.databricks.com", config.getHost());
}

/** Test 4: Legacy fallback when default_profile is empty */
@Test
public void testLegacyFallbackWhenDefaultProfileEmpty() {
StaticEnv env =
new StaticEnv()
.with("HOME", TestOSUtils.resource("/testdata/default_profile_empty_settings"));
DatabricksConfig config = createConfigWithMockClient();
resolveConfig(config, env);
config.authenticate();

assertEquals("pat", config.getAuthType());
assertEquals("https://default.cloud.databricks.com", config.getHost());
}

/** Test 5: default_profile = __settings__ is rejected */
@Test
public void testSettingsSelfReferenceIsRejected() {
StaticEnv env =
new StaticEnv()
.with("HOME", TestOSUtils.resource("/testdata/default_profile_settings_self_ref"));
DatabricksConfig config = createConfigWithMockClient();

DatabricksException ex =
assertThrows(
DatabricksException.class,
() -> {
resolveConfig(config, env);
config.authenticate();
});
assertTrue(
ex.getMessage().contains("has no __settings__ profile configured"),
"Error should reject __settings__ as a profile target: " + ex.getMessage());
}

/** Test 6: Explicit --profile overrides default_profile */
@Test
public void testExplicitProfileOverridesDefaultProfile() {
StaticEnv env =
new StaticEnv()
.with("DATABRICKS_CONFIG_PROFILE", "other")
.with("HOME", TestOSUtils.resource("/testdata/default_profile_explicit_override"));
DatabricksConfig config = createConfigWithMockClient();
resolveConfig(config, env);
config.authenticate();

assertEquals("pat", config.getAuthType());
assertEquals("https://other.cloud.databricks.com", config.getHost());
}

@Test
public void testExplicitSettingsSectionProfileIsRejected() {
StaticEnv env =
new StaticEnv()
.with("DATABRICKS_CONFIG_PROFILE", "__settings__")
.with("HOME", TestOSUtils.resource("/testdata/default_profile"));
DatabricksConfig config = createConfigWithMockClient();

DatabricksException ex =
assertThrows(
DatabricksException.class,
() -> {
resolveConfig(config, env);
config.authenticate();
});
assertTrue(
ex.getMessage().contains("has no __settings__ profile configured"),
"Error should reject __settings__ as a profile target: " + ex.getMessage());
}

/** Test 7: default_profile pointing to nonexistent section */
@Test
public void testDefaultProfileNonexistentSection() {
StaticEnv env =
new StaticEnv().with("HOME", TestOSUtils.resource("/testdata/default_profile_nonexistent"));
DatabricksConfig config = createConfigWithMockClient();

DatabricksException ex =
assertThrows(
DatabricksException.class,
() -> {
resolveConfig(config, env);
config.authenticate();
});
assertTrue(
ex.getMessage().contains("deleted-profile"),
"Error should mention the missing profile name: " + ex.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[__settings__]
default_profile = my-workspace

[my-workspace]
host = https://my-workspace.cloud.databricks.com
token = dapiXYZ
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[__settings__]

[DEFAULT]
host = https://default.cloud.databricks.com
token = dapiXYZ
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[__settings__]
default_profile = my-workspace

[my-workspace]
host = https://my-workspace.cloud.databricks.com
token = dapiXYZ

[other]
host = https://other.cloud.databricks.com
token = dapiOTHER
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[__settings__]
default_profile = deleted-profile

[my-workspace]
host = https://my-workspace.cloud.databricks.com
token = dapiXYZ
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[__settings__]
default_profile = my-workspace

[DEFAULT]
host = https://default.cloud.databricks.com
token = dapiOLD

[my-workspace]
host = https://my-workspace.cloud.databricks.com
token = dapiXYZ
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[__settings__]
default_profile = __settings__

[DEFAULT]
host = https://default.cloud.databricks.com
token = dapiXYZ
Loading