Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.databricks.sdk.core.http.Request;
import com.databricks.sdk.core.http.Response;
import com.databricks.sdk.core.oauth.ErrorTokenSource;
import com.databricks.sdk.core.oauth.HostMetadata;
import com.databricks.sdk.core.oauth.OAuthHeaderFactory;
import com.databricks.sdk.core.oauth.OpenIDConnectEndpoints;
import com.databricks.sdk.core.oauth.TokenSource;
Expand Down Expand Up @@ -800,6 +801,32 @@ public OpenIDConnectEndpoints getDatabricksOidcEndpoints() throws IOException {
return fetchOidcEndpointsFromDiscovery();
}

/**
* [Experimental] Fetch the raw Databricks well-known configuration from
* {host}/.well-known/databricks-config.
*
* <p><b>Note:</b> This API is experimental and may change or be removed in future releases
* without notice.
*
* @return Parsed {@link HostMetadata} as returned by the server.
* @throws DatabricksException if the request fails or the server returns a non-200 status.
*/
HostMetadata getHostMetadata() throws IOException {
String url = host + "/.well-known/databricks-config";
try {
Request request = new Request("GET", url);
Response resp = getHttpClient().execute(request);
if (resp.getStatusCode() != 200) {
Comment on lines +817 to +819
Copy link
Contributor

Choose a reason for hiding this comment

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

Thoughts on checking only for 200 vs making it flexible with other status codes 2xx?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If the server is returning a valid response, 200 is enough here:

────────────────────────────────────────
Code: 200
Name: OK
Meaning: Request succeeded, body contains the resource
Why it doesn't apply: This is the only valid response — a GET
for a config document should return the document.
────────────────────────────────────────
Code: 201
Name: Created
Meaning: A new resource was created
Why it doesn't apply: Only for POST/PUT that creates a
resource. A read-only GET cannot create anything.
────────────────────────────────────────
Code: 202
Name: Accepted
Meaning: Request accepted for async processing
Why it doesn't apply: Implies the response body is not yet
available — we'd have nothing to parse.
────────────────────────────────────────
Code: 203
Name: Non-Authoritative Information
Meaning: Response from a proxy/cache, not the origin server
Why it doesn't apply: The body might be valid, but this
signals
the data may be stale or transformed. Not something we'd
expect from a well-known endpoint.
────────────────────────────────────────
Code: 204
Name: No Content
Meaning: Success but no body
Why it doesn't apply: The body would be empty —
ObjectMapper.readValue would throw, and a config endpoint
with no config is meaningless.
────────────────────────────────────────
Code: 205
Name: Reset Content
Meaning: Success, client should reset its view
Why it doesn't apply: Intended for form resets in browsers;
has
no semantic meaning for an API client.
────────────────────────────────────────
Code: 206
Name: Partial Content
Meaning: Response is a byte-range chunk
Why it doesn't apply: Only for range requests (Range: header).

We send no Range header, so the server should never return
this.

────────────────────────────────────────
Code: 207
Name: Multi-Status
Meaning: Multiple status codes in a single body (WebDAV)
Why it doesn't apply: WebDAV-specific. Not part of HTTP for a
REST endpoint.
────────────────────────────────────────
Code: 208
Name: Already Reported
Meaning: Resource already listed earlier (WebDAV)
Why it doesn't apply: WebDAV-specific, same as above.
────────────────────────────────────────
Code: 226
Name: IM Used
Meaning: Response is a delta encoding
Why it doesn't apply: Requires the client to send A-IM: header

with a delta-encoding request. We don't

throw new DatabricksException(
"Failed to fetch host metadata from " + url + ": HTTP " + resp.getStatusCode());
}
return new ObjectMapper().readValue(resp.getBody(), HostMetadata.class);
} catch (IOException e) {
throw new DatabricksException(
"Failed to fetch host metadata from " + url + ": " + e.getMessage(), e);
}
}

private OpenIDConnectEndpoints fetchOidcEndpointsFromDiscovery() {
try {
Request request = new Request("GET", discoveryUrl);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.databricks.sdk.core.oauth;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;

/**
* [Experimental] Parsed response from the /.well-known/databricks-config discovery endpoint.
*
* <p><b>Note:</b> This API is experimental and may change or be removed in future releases without
* notice.
*/
@JsonIgnoreProperties(ignoreUnknown = true)
public class HostMetadata {
@JsonProperty("oidc_endpoint")
private String oidcEndpoint;

@JsonProperty("account_id")
private String accountId;

@JsonProperty("workspace_id")
private String workspaceId;

public HostMetadata() {}

public HostMetadata(String oidcEndpoint, String accountId, String workspaceId) {
this.oidcEndpoint = oidcEndpoint;
this.accountId = accountId;
this.workspaceId = workspaceId;
}

public String getOidcEndpoint() {
return oidcEndpoint;
}

public String getAccountId() {
return accountId;
}

public String getWorkspaceId() {
return workspaceId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.databricks.sdk.core.commons.CommonsHttpClient;
import com.databricks.sdk.core.http.HttpClient;
import com.databricks.sdk.core.oauth.ErrorTokenSource;
import com.databricks.sdk.core.oauth.HostMetadata;
import com.databricks.sdk.core.oauth.OAuthHeaderFactory;
import com.databricks.sdk.core.oauth.OpenIDConnectEndpoints;
import com.databricks.sdk.core.oauth.Token;
Expand Down Expand Up @@ -421,4 +422,61 @@ public void testGetClientTypeAccountOnUnified() {
.setExperimentalIsUnifiedHost(true)
.getClientType());
}

// --- HostMetadata tests ---

private static final String DUMMY_ACCOUNT_ID = "00000000-0000-0000-0000-000000000001";
private static final String DUMMY_WORKSPACE_ID = "111111111111111";

private static Environment emptyEnv() {
return new Environment(new HashMap<>(), new ArrayList<>(), System.getProperty("os.name"));
}

@Test
public void testGetHostMetadataWorkspaceStaticOidcEndpoint() throws IOException {
String response =
"{\"oidc_endpoint\":\"https://ws.databricks.com/oidc\","
+ "\"account_id\":\""
+ DUMMY_ACCOUNT_ID
+ "\","
+ "\"workspace_id\":\""
+ DUMMY_WORKSPACE_ID
+ "\"}";
try (FixtureServer server =
new FixtureServer().with("GET", "/.well-known/databricks-config", response, 200)) {
DatabricksConfig config = new DatabricksConfig().setHost(server.getUrl());
config.resolve(emptyEnv());
HostMetadata meta = config.getHostMetadata();
assertEquals("https://ws.databricks.com/oidc", meta.getOidcEndpoint());
assertEquals(DUMMY_ACCOUNT_ID, meta.getAccountId());
assertEquals(DUMMY_WORKSPACE_ID, meta.getWorkspaceId());
}
}

@Test
public void testGetHostMetadataAccountRawOidcTemplate() throws IOException {
String response =
"{\"oidc_endpoint\":\"https://acc.databricks.com/oidc/accounts/{account_id}\"}";
try (FixtureServer server =
new FixtureServer().with("GET", "/.well-known/databricks-config", response, 200)) {
DatabricksConfig config = new DatabricksConfig().setHost(server.getUrl());
config.resolve(emptyEnv());
HostMetadata meta = config.getHostMetadata();
assertEquals("https://acc.databricks.com/oidc/accounts/{account_id}", meta.getOidcEndpoint());
assertNull(meta.getAccountId());
assertNull(meta.getWorkspaceId());
}
}

@Test
public void testGetHostMetadataRaisesOnHttpError() throws IOException {
try (FixtureServer server =
new FixtureServer().with("GET", "/.well-known/databricks-config", "{}", 404)) {
DatabricksConfig config = new DatabricksConfig().setHost(server.getUrl());
config.resolve(emptyEnv());
DatabricksException ex =
assertThrows(DatabricksException.class, () -> config.getHostMetadata());
assertTrue(ex.getMessage().contains("Failed to fetch host metadata"));
}
}
}
Loading