Skip to content

Discovery-driven login with workspace selection for SPOG hosts#4809

Open
simonfaltum wants to merge 7 commits intomainfrom
simonfaltum/spog-discovery-login
Open

Discovery-driven login with workspace selection for SPOG hosts#4809
simonfaltum wants to merge 7 commits intomainfrom
simonfaltum/spog-discovery-login

Conversation

@simonfaltum
Copy link
Member

@simonfaltum simonfaltum commented Mar 22, 2026

Why

SPOG (Single Pane of Glass) hosts use one URL for both account-level and workspace-level APIs. The CLI's login flow doesn't call the .well-known/databricks-config discovery endpoint, so it can't detect SPOG hosts. This means account_id and workspace_id don't get populated for SPOG profiles, and users of multi-workspace SPOG accounts have no way to set workspace_id through the interactive login flow.

Changes

The CLI now calls discovery during login to detect SPOG hosts and auto-populate fields. Users can also paste browser URLs with query params directly into auth login.

Before: SPOG hosts classified as regular workspace hosts. No account_id/workspace_id discovery. Users must manually edit .databrickscfg.

Now: Discovery runs automatically during login. SPOG fields are populated from .well-known/databricks-config. Multi-workspace accounts get an interactive workspace picker.

Concrete changes:

  • URL query param parsing in setHostAndAccountId(): extracts ?o= (workspace_id) and ?a= (account_id) from host URLs, strips query params from host. Explicit flags take precedence over URL params.
  • Host metadata discovery: calls EnsureResolved() with a temporary config during login to fetch .well-known/databricks-config. Populates account_id/workspace_id if not already set. Best-effort, never blocks login on failure.
  • ToOAuthArgument() refactored: routes OAuth flow based on DiscoveryURL from EnsureResolved() instead of Experimental_IsUnifiedHost flag. Account-scoped OIDC endpoints (/oidc/accounts/) trigger unified OAuth. Classic workspace hosts are never misrouted.
  • Profile matching simplified: MatchWorkspaceProfiles() uses WorkspaceID != "" presence. MatchAccountProfiles() uses AccountID != "" && WorkspaceID == "". Both still handle legacy IsUnifiedHost profiles.
  • Post-auth workspace selection: for SPOG hosts with account_id but no workspace_id, prompts user to select from Workspaces.List(). Auto-selects single workspace. Capped at 50 entries. Skippable. Non-interactive mode proceeds without selection.
  • Token path updated: loadToken() uses workspace_id for disambiguation when matching multi-workspace SPOG profiles.

Note on discovery in ToOAuthArgument(): This function now calls EnsureResolved() on every invocation, which makes an unauthenticated HTTP request to {host}/.well-known/databricks-config. This is a deliberate tradeoff. The alternative was to persist additional state (like discovery_url or experimental_is_unified_host) to .databrickscfg so we could route the OAuth flow without a network call. We chose not to do that because we want to move away from persisting host-type markers to profiles and instead derive behavior from the host at runtime. The added latency (~100ms per call) will be addressed by a separate discovery caching PR that caches .well-known/databricks-config responses with a 1-hour TTL, making repeated calls essentially free.

Backward compatibility:

  • Existing profiles with experimental_is_unified_host = true still work (legacy fallback in ToOAuthArgument())
  • Classic accounts.* login flow unchanged
  • Regular workspace login unchanged
  • --experimental-is-unified-host flag preserved in re-auth error messages

Test plan

  • Table-driven tests for URL query param parsing (all param combinations, explicit flag precedence, invalid URLs)
  • Discovery tests with mock HTTP server (SPOG host, classic workspace host, discovery failure)
  • ToOAuthArgument() routing tests (SPOG -> unified, classic workspace not misrouted, env var contamination prevented, legacy IsUnifiedHost fallback)
  • Profile matching tests (all profile types: regular workspace, regular account, SPOG workspace, SPOG account, legacy unified)
  • Token path profile disambiguation tests (workspace_id matching for multi-workspace SPOG)
  • IsAccountsHost() helper tests
  • make checks passes
  • All existing auth tests pass

Call .well-known/databricks-config during login to detect SPOG hosts and
auto-populate account_id/workspace_id. Refactor ToOAuthArgument() to
route based on DiscoveryURL from EnsureResolved() instead of the
Experimental_IsUnifiedHost flag.

Key changes:
- Parse ?o= and ?a= query params from host URLs in auth login
- Run host metadata discovery via EnsureResolved() in setHostAndAccountId()
- Refactor ToOAuthArgument() to use DiscoveryURL for OAuth flow routing
- Simplify profile matching to use WorkspaceID/AccountID presence
- Add post-auth workspace selection for multi-workspace SPOG accounts
- Remove --experimental-is-unified-host from generated login commands
- Keep backward compat with existing profiles that have IsUnifiedHost
- URL query params (?o=) now override stale profile workspace_id values
  by deferring profile inheritance to after URL parsing
- ToOAuthArgument() checks /oidc/accounts/ in DiscoveryURL and uses
  caller-provided AccountID to prevent env var contamination and
  classic workspace misrouting
- Token profile matching uses host+account_id+workspace_id for SPOG
  workspace disambiguation
- Re-add --experimental-is-unified-host to BuildLoginCommand for legacy
  profile compatibility
- Cap workspace selection prompt at 50 entries
- Strip trailing slash in extractHostQueryParams after removing query params
- Add tests for discovery routing, URL param precedence, and profile matching

Co-authored-by: Isaac
@eng-dev-ecosystem-bot
Copy link
Collaborator

eng-dev-ecosystem-bot commented Mar 22, 2026

Commit: 3563183

Run: 23400355862

Env 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
💚​ aws linux 8 9 271 797 6:21
💚​ aws windows 8 9 273 795 4:58
🔄​ aws-ucws linux 4 7 9 366 712 6:33
🔄​ aws-ucws windows 3 7 9 369 710 5:56
💚​ azure linux 2 11 274 795 5:13
💚​ azure windows 2 11 276 793 5:09
🔄​ azure-ucws linux 4 1 11 371 708 6:55
💚​ azure-ucws windows 2 11 376 706 6:05
💚​ gcp linux 2 11 270 798 5:15
💚​ gcp windows 2 11 272 796 4:37
20 interesting tests: 9 SKIP, 6 RECOVERED, 5 flaky
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure linux azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
🔄​ TestAccept 💚​R 💚​R 💚​R 🔄​f 💚​R 💚​R 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/resources/permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions 💚​R 💚​R 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct 💚​R 💚​R 💚​R 💚​R
💚​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 💚​R 💚​R 💚​R 💚​R
🙈​ TestAccept/bundle/resources/postgres_branches/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/update_protected 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/without_branch_id 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/recreate 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_projects/update_display_name 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/synced_database_tables/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🔄​ TestAccept/ssh/connect-serverless-gpu 🙈​s 🙈​s 🔄​f 🔄​f 🙈​s 🙈​s 🔄​f ✅​p 🙈​s 🙈​s
🔄​ TestAccept/ssh/connect-serverless-gpu/DATABRICKS_BUNDLE_ENGINE=direct 🔄​f 🔄​f 🔄​f ✅​p
🔄​ TestAccept/ssh/connection 💚​R 💚​R 🔄​f 💚​R 💚​R 💚​R 🔄​f 💚​R 💚​R 💚​R
🔄​ TestAccept/ssh/connection/DATABRICKS_BUNDLE_ENGINE=direct ✅​p ✅​p 🔄​f ✅​p ✅​p ✅​p 🔄​f ✅​p ✅​p ✅​p
Top 20 slowest tests (at least 2 minutes):
duration env testname
5:13 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:44 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:15 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:14 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:13 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:10 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:07 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:07 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:48 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:44 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:41 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:41 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:37 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:37 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:34 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:31 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:18 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:08 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:06 azure linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:05 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct

Remove context.Background() usage in ToOAuthArgument by silently
discarding resolution errors (discovery failure is expected for
non-SPOG hosts). Replace require.NoError in HTTP handlers with
http.Error to avoid cross-goroutine FailNow calls. Fix gofmt
alignment in login_test.go.

Co-authored-by: Isaac
1. Replace IsAccountsHost with SDK's HostType() to avoid divergence risk
   for new sovereign clouds. Remove IsAccountsHost and normalizeHost helpers.

2. Validate ?o= and ?workspace_id= query params are numeric, skipping
   non-numeric values to avoid confusing downstream errors.

3. Tighten writeReauthSteps condition to only append --workspace-id for
   SPOG/unified hosts (both account_id and workspace_id present).

4. Cache discovery results via DiscoveryURL field on AuthArguments to
   avoid duplicate .well-known/databricks-config HTTP requests during login.

Co-authored-by: Isaac
…ount admins

SPOG account admins who don't need a workspace were prompted on every login.
This adds two ways to persist that choice: a --skip-workspace flag on
`auth login`, and persisting workspace_id=none when the user selects "Skip"
from the interactive prompt. On subsequent logins the sentinel is recognized
and the prompt is suppressed.

Co-authored-by: Isaac
@simonfaltum simonfaltum marked this pull request as ready for review March 22, 2026 09:38
@github-actions
Copy link

Suggested reviewers

Based on git history of the changed files, these people are best suited to review:

  • @tanmay-db -- recent work in cmd/auth/, libs/auth/, libs/databrickscfg/profile/

Confidence: high

Eligible reviewers

Based on CODEOWNERS, these people or teams could also review:

@andrewnester, @anton-107, @denik, @pietern, @shreyas-goenka

Suggestions based on git history of 21 changed files (9 scored). See CODEOWNERS for path-specific ownership rules.

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