Fix CLI token source --profile fallback with version detection#1377
Conversation
1d03f6d to
9e82d18
Compare
Range-diff: main (1d03f6d -> 9e82d18)
Reproduce locally: |
9e82d18 to
3195f5d
Compare
Range-diff: main (9e82d18 -> 3195f5d)
Reproduce locally: |
3195f5d to
4115f37
Compare
Range-diff: main (3195f5d -> 4115f37)
Reproduce locally: |
4115f37 to
469cb44
Compare
Range-diff: main (4115f37 -> 469cb44)
Reproduce locally: |
469cb44 to
32c2cbd
Compare
Range-diff: main (469cb44 -> 32c2cbd)
Reproduce locally: |
32c2cbd to
194256c
Compare
Range-diff: main (32c2cbd -> 194256c)
Reproduce locally: |
194256c to
cd6c876
Compare
Range-diff: main (194256c -> cd6c876)
Reproduce locally: |
13e5ee8 to
343baae
Compare
simonfaltum
left a comment
There was a problem hiding this comment.
Review Swarm: Isaac + Cursor (2-round deep review)
Verdict: Not ready yet. 0 Critical | 3 Major | 2 Gap | 5 Nit | 3 Suggestion
Two independent reviewers (Isaac, Cursor) reviewed this PR, cross-checked each other's findings, and a synthesis follows. Inline comments are attached to specific lines below; this top-level body highlights the priorities and disputes.
Top priorities (block merge)
- Unnecessary subprocess on host-only path (Major, both reviewers) —
_resolve_cli_commandunconditionally calls_get_cli_version, but the version is ignored whencfg.profileis unset. The common host-only config pays a fork/exec + possible WARNING for a value it never uses. Short-circuit the host-only case before version detection. - No subprocess timeout on
databricks version(Major, Isaac + confirmed by Cursor) —_get_cli_versioncalls_run_subprocess(..., check=True)with the defaulttimeout=None. If the CLI hangs (slow disk, AV scan, stdin wait, network in a subcommand), SDK init blocks forever. Passtimeout=5. - Happy-path test missing (Gap/Major, Isaac + confirmed by Cursor) — tests exist for dev-build, detection failure, old-CLI fallback, and error cases, but none stubs a successful post-v0.207.1 version probe and asserts the resulting command includes
--profile. Easy to regress silently.
Other Majors
- Per-instance subprocess; no caching —
_get_cli_versionre-spawns on everyDatabricksCliTokenSourceconstruction. Immutable for a givencli_path; wrap withfunctools.lru_cache. (Severity disagreement: Isaac Major, Cursor Suggestion; taking higher.) - Disputed: init-time exception type change —
__init__now raisesValueErrorwhere it previously surfaced asIOErroratrefresh(). Thedatabricks_cli()credential chain catchesIOErrorand a narrowValueError; the newValueErrorfrom_resolve_cli_commandcould skip graceful-skip. Cursor noted@oauth_credentials_strategy("databricks-cli", ["host"])limits reachability. At minimum, add aNEXT_CHANGELOG.mdnote.
Nits
- Fallback WARNING overstates certainty for
unknown/v0.0.0states — both reviewers. CliVersion.__str__renders the dev-build sentinel asv0.0.0, ambiguous in logs.- Adjacent string-literal concatenation in the version-detect WARNING reads like a typo (disputed).
- Forward reference to module-level
_DEFAULT_DEV_BUILDfrom insideCliVersion(disputed, stylistic). - No
caplogassertion on the old-CLI fallback WARNING, though the parallel dev-build/detection-failure tests have one.
Suggestions
- Extensibility: declare a list of
(min_version, args)tuples instead of nested-iffor capability gates. Pairs naturally with stacked PR #1378. - Broaden
test_cli_version_geinto a total-order test covering<,<=,==to lock in thedataclass(order=True)contract. - Consider lazy-at-refresh detection with caching (disputed; current init-time approach mirrors the Go SDK and is defensible).
Overall
The design is sound and mirrors databricks/databricks-sdk-go#1605. The test restructuring from class-based to parametrized is a clear improvement. No correctness bugs. The Majors are hot-path regressions and init-time robustness; fixing those (plus the happy-path test) makes this ready to merge.
ba0f51c to
c435a7e
Compare
c435a7e to
e62bfbb
Compare
e62bfbb to
1dc0960
Compare
1dc0960 to
d8cf968
Compare
simonfaltum
left a comment
There was a problem hiding this comment.
Take a look at my comment, otherwise looks good to me
| @functools.lru_cache(maxsize=8) | ||
| def _get_cli_version(cli_path: str) -> "CliVersion": | ||
| """Run `databricks version --output json` and return the parsed CliVersion. | ||
|
|
||
| Cached by `cli_path` for the Python process lifetime: the installed binary's | ||
| version is immutable during the process, and the credential chain may |
There was a problem hiding this comment.
_get_cli_version caches transient failures for the full process lifetime. If databricks version times out or hits a temporary OSError once, the unknown CliVersion() sentinel is cached for that CLI path. Later DatabricksCliTokenSource instances never retry, so a supported CLI can keep falling back to --host, or fail with “no host fallback is configured,” until process restart.
Suggested fix: cache only successful detections, or keep the failure handling outside the cached helper. Add a regression test where the first probe fails and the second probe returns v0.207.1+, then assert the second source uses --profile.
Replace the broken error-based --profile detection with version-based CLI detection at init time. The --profile flag is a global Cobra flag that old CLIs (< v0.207.1) silently accept instead of reporting "unknown flag: --profile", making the previous --host fallback dead code. The SDK now runs `databricks version` at init time, parses the semver, and uses it to decide between --profile and --host. If version detection fails, the SDK falls back to --host (most conservative command). This also simplifies CliTokenSource to hold a single cmd with no runtime fallback logic, and enables future version-gated flags (e.g. --force-refresh) without additional subprocess calls. See: databricks/cli#855
d8cf968 to
c714a13
Compare
|
If integration tests don't run automatically, an authorized user can run them manually by following the instructions below: Trigger: Inputs:
Checks will be approved automatically on success. |
🥞 Stacked PR
Use this link to review incremental changes.
Summary
Replace the broken error-based
--profilefallback inCliTokenSourcewith version-based CLI detection at init time. Mirrors databricks/databricks-sdk-go#1605.Why
--profileondatabricks auth tokenis a global Cobra flag, so old CLIs (< v0.207.1) silently accept it and then fail with"cannot fetch credentials"instead of"unknown flag: --profile". The existing retry check was matching on the latter and never fired — the--hostfallback it gated was effectively dead code. Switching todatabricks version+ a minimum-version constant makes the fallback reliable and sets up future capability-gated flags (e.g.--force-refreshin #1378) without additional subprocess calls.What changed
Interface changes
None.
CliTokenSourceandDatabricksCliTokenSourceare not part of the public API surface.Behavioral changes
cfg.profile+ CLI < v0.207.1 now correctly falls back to--host(previously broken).databricks versionfailures log aWARNINGand fall back to the most conservative command.v0.0.0-dev[+commit]) logs anINFOexplaining why feature gates are conservative."neither profile nor host is configured"/"…does not support --profile … and no host fallback is configured") instead of a single generic message.AzureCliTokenSourceis untouched.Internal changes
CliVersiondataclass with semver-aware comparison operators and anis_default_dev_buildproperty.CliTokenSourcesimplified to a singlecmd; thefallback_cmdparameter and its retry logic are removed.DatabricksCliTokenSourcegains_get_cli_version,_resolve_cli_command,_build_cli_command, and_build_host_commandhelpers.How is this tested?
Unit tests in
tests/test_credentials_provider.pycover version parsing, comparison operators, dev-build detection, command assembly for every profile/host/version combination, precise error messages, and logging behavior on detection failure and dev builds. Existingtests/test_core.py::test_databricks_cli_token_source_*tests pass unchanged.