Skip to content

Add discovery login via login.databricks.com#4702

Open
simonfaltum wants to merge 21 commits intomainfrom
simonfaltum/login-discovery
Open

Add discovery login via login.databricks.com#4702
simonfaltum wants to merge 21 commits intomainfrom
simonfaltum/login-discovery

Conversation

@simonfaltum
Copy link
Member

@simonfaltum simonfaltum commented Mar 11, 2026

Why

When users run databricks auth login without specifying a host, the CLI had no fallback. Users had to know their workspace URL upfront. This adds a discovery flow via login.databricks.com where users authenticate in the browser, select a workspace, and the CLI automatically discovers the workspace URL.

This PR adds discovery login as the default option. A follow-up PR once this is merged, will merge the profile / new profile selector from auth token with auth login to ensure a uniform experience, and ensuring that users who already have profiles setup will be given the option to re-login as the primary option.

Changes

Before: databricks auth login without --host prompted for a host URL or failed.
Now: databricks auth login without --host opens login.databricks.com in the browser. After the user authenticates and selects a workspace, the CLI saves the profile with the discovered host, account ID, and workspace ID.

Implementation details:

  • shouldUseDiscovery() detects when no host is available from flags, args, or existing profile
  • discoveryLogin() orchestrates the browser-based flow using the SDK's discovery OAuth support
  • IntrospectToken() (in libs/auth) calls the workspace introspection endpoint to extract account/workspace IDs (best-effort, non-fatal on failure)
  • splitScopes() deduplicates scope parsing across login paths
  • Error messages include actionable tips (e.g., "you can specify a workspace directly with: databricks auth login --host ")
  • Discovery dependencies (PersistentAuth, OAuthArgument, IntrospectToken) are injectable via package-level vars for testability

Post-review fixes

  • IntrospectToken accepts an *http.Client parameter instead of using http.DefaultClient (enterprise proxy/TLS compatibility)
  • Introspection HTTP client clones http.DefaultTransport to inherit system CA certs, proxy settings, and timeouts
  • Discovery relogin now reuses existing profile scopes when --scopes is not explicitly set (consistent with normal login path)
  • Explicitly clear stale routing fields (account_id, workspace_id, experimental_is_unified_host, cluster_id, serverless_compute_id) before saving discovery profile
  • workspace_id only set when introspection returns a fresh value
  • Consolidate error message construction with discoveryErr() helper

Test plan

  • Unit tests for shouldUseDiscovery (table-driven, 6 cases)
  • Unit tests for splitScopes (empty, single, whitespace, empty entries)
  • Unit tests for IntrospectToken (success, zero workspace ID, HTTP errors, malformed JSON, auth header, endpoint path, custom HTTP client)
  • Integration test for discoveryLogin with introspection failure (verifies profile is still saved)
  • Integration test for discoveryLogin with introspection success (verifies metadata extraction)
  • Regression test for relogin preserving existing profile scopes
  • Regression test for clearing stale routing fields from unified profile
  • go test ./cmd/auth/... ./libs/auth/... passes

simonfaltum and others added 3 commits March 11, 2026 09:43
Add IntrospectToken() which calls /api/2.0/tokens/introspect to extract
account_id and workspace_id from workspace tokens. This will be used by
the discovery login flow to enrich profiles with metadata.

Note: go.mod contains a temporary replace directive pointing to the local
SDK worktree with discovery changes. This will be reverted before the PR.
Extract shouldUseDiscovery() routing function and test it directly with
table-driven tests instead of executing the full cobra command, which
hangs waiting for an OAuth browser callback that never arrives in tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…essages

Extract splitScopes helper to deduplicate scope parsing. Save scopes to
profile in discoveryLogin (was previously dropped). Add actionable error
messages with --host tip for setup failures and config file path for save
failures. Make discoveryLogin testable by introducing package-level vars
for PersistentAuth, OAuthArgument, and IntrospectToken. Fix typo in
introspect test data. Use http.MethodGet constant and drain response body
on error for TCP connection reuse.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@eng-dev-ecosystem-bot
Copy link
Collaborator

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

Commit: 5e91ce3

Run: 23333556736

Env 🟨​KNOWN 🔄​flaky 💚​RECOVERED 🙈​SKIP ✅​pass 🙈​skip Time
🟨​ aws linux 7 1 9 268 797 7:05
🟨​ aws windows 7 1 9 270 795 6:05
🔄​ aws-ucws linux 4 7 9 362 712 8:15
🔄​ aws-ucws windows 2 7 9 366 710 6:08
💚​ azure windows 2 11 273 793 5:27
🔄​ azure-ucws linux 2 11 370 708 8:22
🔄​ azure-ucws windows 2 1 11 371 706 6:42
💚​ gcp linux 2 11 267 798 6:14
💚​ gcp windows 2 11 269 796 5:39
20 interesting tests: 9 SKIP, 7 KNOWN, 4 flaky
Test Name aws linux aws windows aws-ucws linux aws-ucws windows azure windows azure-ucws linux azure-ucws windows gcp linux gcp windows
🟨​ TestAccept 🟨​K 🟨​K 🔄​f 🔄​f 💚​R 🔄​f 🔄​f 💚​R 💚​R
🙈​ TestAccept/bundle/resources/permissions 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions 🟨​K 🟨​K 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/with_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions 🟨​K 🟨​K 💚​R 💚​R 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=direct 🟨​K 🟨​K 💚​R 💚​R
🟨​ TestAccept/bundle/resources/permissions/jobs/destroy_without_mgmtperms/without_permissions/DATABRICKS_BUNDLE_ENGINE=terraform 🟨​K 🟨​K 💚​R 💚​R
🙈​ TestAccept/bundle/resources/postgres_branches/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_branches/recreate 🙈​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
🙈​ TestAccept/bundle/resources/postgres_branches/without_branch_id 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🙈​ TestAccept/bundle/resources/postgres_endpoints/recreate 🙈​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
🙈​ TestAccept/bundle/resources/synced_database_tables/basic 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S 🙈​S
🔄​ TestAccept/ssh/connect-serverless-gpu 🙈​s 🙈​s 🔄​f 🔄​f 🙈​s ✅​p 🔄​f 🙈​s 🙈​s
🔄​ TestAccept/ssh/connection 💚​R 💚​R 💚​R 💚​R 💚​R 🔄​f 💚​R 💚​R 💚​R
🔄​ TestFsCpDirToDirWithOverwriteFlag ✅​p ✅​p 🔄​f ✅​p ✅​p ✅​p ✅​p ✅​p ✅​p
🔄​ TestFsCpDirToDirWithOverwriteFlag/dbfs_to_uc-volumes 🙈​s 🙈​s 🔄​f ✅​p 🙈​s ✅​p ✅​p 🙈​s 🙈​s
Top 18 slowest tests (at least 2 minutes):
duration env testname
3:44 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:42 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:28 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:17 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:16 azure-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
3:15 gcp linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:13 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:13 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
3:08 gcp windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:46 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:45 aws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:45 aws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:41 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:41 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:38 aws-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct
2:12 azure-ucws linux TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:09 azure windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=terraform
2:05 aws-ucws windows TestAccept/bundle/resources/apps/inline_config/DATABRICKS_BUNDLE_ENGINE=direct

Group same-type function parameters and use strconv.FormatInt instead of
fmt.Sprintf for integer conversion.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract repeated discovery fallback tip to discoveryFallbackTip const
- Reuse splitScopes in token.go instead of inline split+trim (also fixes
  missing empty-entry filtering)
- Rename TestDiscoveryLogin_IntrospectionSuccessExtractsMetadata to
  TestIntrospectToken_SuccessExtractsMetadata (it tests IntrospectToken
  directly, not discoveryLogin)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SDKs are not yet ready to handle account_id on workspace profiles
as part of the cache key. Saving it now would break existing auth flows.
Keeping the introspection call so workspace_id is still extracted.

Co-authored-by: Isaac
…mismatch

Reject --configure-cluster and --configure-serverless when discovery login
is used (no host available), since these flags require a known workspace.

Warn when the introspected account_id differs from the existing profile's
account_id to surface potential misconfigurations early.

Also use the discoveryFallbackTip constant instead of hardcoded strings.

Co-authored-by: Isaac
Test that a warning is logged when the introspected account_id differs
from the existing profile's account_id, and that no warning is emitted
when they match.

Co-authored-by: Isaac
- Remove TestIntrospectToken_ServerError (same code path as _HTTPError)
- Merge TestIntrospectToken_VerifyAuthHeader and _VerifyEndpoint into one
- Remove TestIntrospectToken_SuccessExtractsMetadata from login_test.go
  (duplicate of introspect_test.go:TestIntrospectToken_Success)

Co-authored-by: Isaac
Follow the codebase convention of using the context-aware env package
for environment variable access.

Co-authored-by: Isaac
The existing non-discovery login path also used os.Getenv for
DATABRICKS_CONFIG_FILE. Replaced with the context-aware env package
to follow codebase conventions and enable test overrides.

Co-authored-by: Isaac
Copy link
Contributor

@shreyas-goenka shreyas-goenka left a comment

Choose a reason for hiding this comment

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

Note: This review was posted by Claude (AI assistant). Shreyas will do a separate, more thorough review pass.

Priority: MEDIUM — Well-structured with a few concerns

MEDIUM: http.DefaultClient in IntrospectToken bypasses SDK HTTP configuration

libs/auth/introspect.go uses http.DefaultClient, bypassing proxy, custom TLS, retry, and timeout settings configured in the SDK. In enterprise environments, this call will fail. Since introspection is best-effort, this is acceptable for v1 but should be tracked for improvement.

LOW: Missing discoveredHost validation

After arg.GetDiscoveredHost(), there's no check that the returned host is non-empty. This could save an empty host string to the profile.

LOW: Missing IsUnifiedHost in discovery profile save

The discovery path doesn't set Experimental_IsUnifiedHost nor clear experimental_is_unified_host, unlike the non-discovery path.

DESIGN: Verify introspection endpoint supports GET

The code uses http.MethodGet for /api/2.0/tokens/introspect. If this follows RFC 7662 (which expects POST), the call will silently fail with 405.

What looks good

  • Clean discoveryPersistentAuth narrow interface
  • shouldUseDiscovery is pure and testable (6 table-driven test cases)
  • splitScopes extraction reduces duplication
  • Best-effort introspection doesn't block login on failure
  • env.Get(ctx, ...) replaces os.Getenv correctly

After GetDiscoveredHost(), check that the returned host is not empty
before saving it to the profile. An empty host would produce a broken
profile entry. Return a clear error with the fallback tip so the user
knows they can retry with --host.
…ithout --host in discovery login

These flags are meaningless in discovery mode (login.databricks.com) and
silently ignored before this change. For example, 'databricks auth login
--account-id <id>' would go through workspace discovery but getProfileName()
would generate an ACCOUNT-<id> profile name for a workspace token.

Now the CLI returns a clear error telling the user to provide --host when
any of these flags are explicitly set.
Allow callers to pass a configured *http.Client so corporate proxy and
custom CA certificate settings are respected. Falls back to
http.DefaultClient when nil for backward compatibility.

Co-authored-by: Isaac
Addresses four issues found in code review:

1. Pass a dedicated HTTP client (cloned transport) to IntrospectToken
   instead of nil, so proxy/TLS settings are inherited without sharing
   global state.

2. Reuse existing profile scopes during discovery relogin when --scopes
   is not explicitly set. Add regression tests for both fallback and
   override cases.

3. Add workspace_id to the clearKeys list so stale values from older
   hostless/unified profiles are removed. Fresh introspection values are
   re-written after clearing. Add regression tests for stale field
   cleanup and fresh workspace_id writes.

4. Replace string concatenation with discoveryFallbackTip with a
   discoveryErr helper for consistent, readable error construction.

Co-authored-by: Isaac
Build the introspection HTTP client from a resolved SDK config.Config
instead of cloning http.DefaultTransport. This picks up TLS settings
(InsecureSkipVerify, custom transport) from environment variables,
matching how the rest of the SDK handles HTTP connections.

Add httpClientFromConfig helper with tests, and add a test verifying
IntrospectToken uses the supplied HTTP client.

Co-authored-by: Isaac
Replace httpClientFromConfig with httpClientForIntrospection that clones
http.DefaultTransport. The previous approach tried to load TLS settings
via EnsureResolved on a config with Host+Token already set, which
short-circuits the config-file loader and never picks up profile TLS
settings. The InsecureSkipVerify branch also created a zero-value
transport missing proxy, dialer, and HTTP/2 settings.

Since discovery login may not have a profile yet, the correct approach is
to clone DefaultTransport which inherits system CA certs, proxy settings,
timeouts, and connection pooling.

Co-authored-by: Isaac
Copy link
Contributor

@pietern pietern left a comment

Choose a reason for hiding this comment

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

A follow-up PR once this is merged, will merge the profile / new profile selector from auth token with auth login to ensure a uniform experience, and ensuring that users who already have profiles setup will be given the option to re-login as the primary option.

What do you mean by this? I suspect auth token already prompts with a profile list.

@simonfaltum
Copy link
Member Author

What do you mean by this? I suspect auth token already prompts with a profile list.

I mean, login will get the profile selector and "new profile" will get the discovery flow

@simonfaltum simonfaltum requested a review from pietern March 20, 2026 07:39
@simonfaltum simonfaltum deployed to test-trigger-is March 20, 2026 07:40 — with GitHub Actions Active
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.

4 participants