1919import org .apache .http .HttpMessage ;
2020
2121public class DatabricksConfig {
22+ /** HostType represents the type of API the configured host supports. */
23+ public enum HostType {
24+ /** WorkspaceHost supports only workspace-level APIs. */
25+ WORKSPACE_HOST ,
26+ /** AccountHost supports only account-level APIs. */
27+ ACCOUNT_HOST ,
28+ /** UnifiedHost supports both workspace-level and account-level APIs. */
29+ UNIFIED_HOST
30+ }
31+
32+ /** ConfigType represents the type of API this config is valid for. */
33+ public enum ConfigType {
34+ /** WorkspaceConfig is valid for workspace-level API requests. */
35+ WORKSPACE_CONFIG ,
36+ /** AccountConfig is valid for account-level API requests. */
37+ ACCOUNT_CONFIG ,
38+ /**
39+ * InvalidConfig is returned when the config is not valid for either workspace-level or
40+ * account-level APIs.
41+ */
42+ INVALID_CONFIG
43+ }
2244 private CredentialsProvider credentialsProvider = new DefaultCredentialsProvider ();
2345
2446 @ ConfigAttribute (env = "DATABRICKS_HOST" )
@@ -27,6 +49,14 @@ public class DatabricksConfig {
2749 @ ConfigAttribute (env = "DATABRICKS_ACCOUNT_ID" )
2850 private String accountId ;
2951
52+ /** Databricks Workspace ID for Workspace clients when working with unified hosts. */
53+ @ ConfigAttribute (env = "DATABRICKS_WORKSPACE_ID" )
54+ private String workspaceId ;
55+
56+ /** Marker for unified hosts. Will be redundant once we can recognize unified hosts by their hostname. */
57+ @ ConfigAttribute (env = "DATABRICKS_EXPERIMENTAL_IS_UNIFIED_HOST" )
58+ private Boolean experimentalIsUnifiedHost ;
59+
3060 @ ConfigAttribute (env = "DATABRICKS_TOKEN" , auth = "pat" , sensitive = true )
3161 private String token ;
3262
@@ -290,6 +320,24 @@ public DatabricksConfig setAccountId(String accountId) {
290320 return this ;
291321 }
292322
323+ public String getWorkspaceId () {
324+ return workspaceId ;
325+ }
326+
327+ public DatabricksConfig setWorkspaceId (String workspaceId ) {
328+ this .workspaceId = workspaceId ;
329+ return this ;
330+ }
331+
332+ public boolean getExperimentalIsUnifiedHost () {
333+ return experimentalIsUnifiedHost != null && experimentalIsUnifiedHost ;
334+ }
335+
336+ public DatabricksConfig setExperimentalIsUnifiedHost (boolean experimentalIsUnifiedHost ) {
337+ this .experimentalIsUnifiedHost = experimentalIsUnifiedHost ;
338+ return this ;
339+ }
340+
293341 public String getDatabricksCliPath () {
294342 return this .databricksCliPath ;
295343 }
@@ -670,13 +718,67 @@ public boolean isAws() {
670718 return this .getDatabricksEnvironment ().getCloud () == Cloud .AWS ;
671719 }
672720
721+ /**
722+ * Returns true if client is configured for Accounts API. Panics if the config has the unified
723+ * host flag set.
724+ *
725+ * @deprecated Use {@link #getHostType()} if possible, or {@link #getConfigType()} if necessary.
726+ */
727+ @ Deprecated
673728 public boolean isAccountClient () {
729+ if (getExperimentalIsUnifiedHost ()) {
730+ throw new IllegalStateException (
731+ "isAccountClient cannot be used with unified hosts; use getHostType() instead" );
732+ }
674733 if (host == null ) {
675734 return false ;
676735 }
677736 return host .startsWith ("https://accounts." ) || host .startsWith ("https://accounts-dod." );
678737 }
679738
739+ /** Returns the type of host that the client is configured for. */
740+ public HostType getHostType () {
741+ if (getExperimentalIsUnifiedHost ()) {
742+ return HostType .UNIFIED_HOST ;
743+ }
744+
745+ if (host == null ) {
746+ return HostType .WORKSPACE_HOST ;
747+ }
748+
749+ if (host .startsWith ("https://accounts." ) || host .startsWith ("https://accounts-dod." )) {
750+ return HostType .ACCOUNT_HOST ;
751+ }
752+
753+ return HostType .WORKSPACE_HOST ;
754+ }
755+
756+ /**
757+ * Returns the type of config that the client is configured for. Returns InvalidConfig if the
758+ * config is invalid. Use of this method should be avoided where possible, because we plan to
759+ * remove WorkspaceClient and AccountClient in favor of a single unified client in the future.
760+ */
761+ public ConfigType getConfigType () {
762+ HostType hostType = getHostType ();
763+ switch (hostType ) {
764+ case ACCOUNT_HOST :
765+ return ConfigType .ACCOUNT_CONFIG ;
766+ case WORKSPACE_HOST :
767+ return ConfigType .WORKSPACE_CONFIG ;
768+ case UNIFIED_HOST :
769+ if (accountId == null || accountId .isEmpty ()) {
770+ // All unified host configs must have an account ID
771+ return ConfigType .INVALID_CONFIG ;
772+ }
773+ if (workspaceId != null && !workspaceId .isEmpty ()) {
774+ return ConfigType .WORKSPACE_CONFIG ;
775+ }
776+ return ConfigType .ACCOUNT_CONFIG ;
777+ default :
778+ return ConfigType .INVALID_CONFIG ;
779+ }
780+ }
781+
680782 public OpenIDConnectEndpoints getOidcEndpoints () throws IOException {
681783 if (discoveryUrl == null ) {
682784 return fetchDefaultOidcEndpoints ();
@@ -712,23 +814,49 @@ private OpenIDConnectEndpoints fetchDefaultOidcEndpoints() throws IOException {
712814 return new OpenIDConnectEndpoints (
713815 realAuthUrl .replaceAll ("/authorize" , "/token" ), realAuthUrl );
714816 }
715- if (isAccountClient () && getAccountId () != null ) {
716- String prefix = getHost () + "/oidc/accounts/" + getAccountId ();
717- return new OpenIDConnectEndpoints (prefix + "/v1/token" , prefix + "/v1/authorize" );
718- }
719817
720- ApiClient apiClient =
721- new ApiClient .Builder ()
722- .withHttpClient (getHttpClient ())
723- .withGetHostFunc (v -> getHost ())
724- .build ();
725- try {
726- return apiClient .execute (
727- new Request ("GET" , "/oidc/.well-known/oauth-authorization-server" ),
728- OpenIDConnectEndpoints .class );
729- } catch (IOException e ) {
730- throw new DatabricksException ("IO error: " + e .getMessage (), e );
818+ HostType hostType = getHostType ();
819+ switch (hostType ) {
820+ case ACCOUNT_HOST :
821+ if (getAccountId () != null ) {
822+ String prefix = getHost () + "/oidc/accounts/" + getAccountId ();
823+ return new OpenIDConnectEndpoints (prefix + "/v1/token" , prefix + "/v1/authorize" );
824+ }
825+ break ;
826+ case UNIFIED_HOST :
827+ if (getAccountId () != null ) {
828+ ApiClient apiClient =
829+ new ApiClient .Builder ()
830+ .withHttpClient (getHttpClient ())
831+ .withGetHostFunc (v -> getHost ())
832+ .build ();
833+ try {
834+ String discoveryPath =
835+ "/oidc/accounts/" + getAccountId () + "/.well-known/oauth-authorization-server" ;
836+ return apiClient .execute (
837+ new Request ("GET" , discoveryPath ), OpenIDConnectEndpoints .class );
838+ } catch (IOException e ) {
839+ throw new DatabricksException ("IO error: " + e .getMessage (), e );
840+ }
841+ }
842+ break ;
843+ case WORKSPACE_HOST :
844+ ApiClient apiClient =
845+ new ApiClient .Builder ()
846+ .withHttpClient (getHttpClient ())
847+ .withGetHostFunc (v -> getHost ())
848+ .build ();
849+ try {
850+ return apiClient .execute (
851+ new Request ("GET" , "/oidc/.well-known/oauth-authorization-server" ),
852+ OpenIDConnectEndpoints .class );
853+ } catch (IOException e ) {
854+ throw new DatabricksException ("IO error: " + e .getMessage (), e );
855+ }
856+ default :
857+ break ;
731858 }
859+ return null ;
732860 }
733861
734862 @ Override
@@ -795,9 +923,10 @@ public DatabricksConfig newWithWorkspaceHost(String host) {
795923 Arrays .asList (
796924 // The config for WorkspaceClient has a different host and Azure Workspace resource
797925 // ID, and also omits
798- // the account ID.
926+ // the account ID and workspace ID .
799927 "host" ,
800928 "accountId" ,
929+ "workspaceId" ,
801930 "azureWorkspaceResourceId" ,
802931 // For cloud-native OAuth, we need to reauthenticate as the audience has changed, so
803932 // don't cache the
0 commit comments