Commit 1572b1a
Add discovery login via login.databricks.com (#4702)
## 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 <url>")
- 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
- [x] Unit tests for `shouldUseDiscovery` (table-driven, 6 cases)
- [x] Unit tests for `splitScopes` (empty, single, whitespace, empty
entries)
- [x] Unit tests for `IntrospectToken` (success, zero workspace ID, HTTP
errors, malformed JSON, auth header, endpoint path, custom HTTP client)
- [x] Integration test for `discoveryLogin` with introspection failure
(verifies profile is still saved)
- [x] Integration test for `discoveryLogin` with introspection success
(verifies metadata extraction)
- [x] Regression test for relogin preserving existing profile scopes
- [x] Regression test for clearing stale routing fields from unified
profile
- [x] `go test ./cmd/auth/... ./libs/auth/...` passes
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>1 parent 9aeed55 commit 1572b1a
File tree
11 files changed
+1067
-12
lines changed- acceptance
- bin
- cmd/auth/login/discovery
- cmd/auth
- libs/auth
11 files changed
+1067
-12
lines changed| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
0 commit comments