Skip to content

Commit 3c867c9

Browse files
authored
api: route per-call against unified hosts (#5137)
## Why `databricks api` always sent the workspace routing identifier (`X-Databricks-Org-Id`) when the profile had one, even when the path was an account API. On unified hosts (one host serving both workspace and account APIs) this misrouted account calls. There was also no way to explicitly route a call to the account API or override the identifier per call. ## Changes Before: routing was decided once from the profile and applied to every call. Now: routing is decided per call from the path being requested. - Paths under `/accounts/{id}/` are auto-detected as account-scope; the routing identifier is dropped. - A small hand-written list in `cmd/api/paths.go` carves out workspace-routed proxy APIs that happen to live under `/accounts/`, so they keep the identifier. - `--account` forces account-scope on a non-`/accounts/` path. - `--workspace-id <id>` overrides the identifier per call. Mutually exclusive with `--account`. - `?o=<id>` on the path (the SPOG URL convention used by the Databricks UI) is recognized as a per-call workspace override, so URLs pasted from the browser route correctly. - The CLI-only `workspace_id = none` sentinel is stripped before the routing decision so the literal "none" never goes on the wire. Routing logic lives in pure functions (`hasAccountSegment`, `extractOrgIDFromQuery`, `resolveOrgID`, `normalizeWorkspaceID`, `isWorkspaceProxyPath`) that take primitives. The cobra `RunE` is a thin adapter that resolves config and calls them. ## Test plan - [x] `go test ./cmd/api` covers the helpers with table-driven cases: deny-list hits and misses, query/fragment edge cases, mutual-exclusion errors, sentinel stripping, `?o=` extraction. - [x] `go test ./acceptance -run TestAccept/cmd/api` exercises seven variants end to end against terraform and direct engines: workspace path, account path, deny-listed proxy under `/accounts/`, `--account`, `--workspace-id`, `?o=` query, `workspace_id = none`. Each test asserts header presence/absence explicitly via `print_requests.py | contains.py`. - [x] `make checks`
1 parent 521074c commit 3c867c9

28 files changed

Lines changed: 648 additions & 2 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
### CLI
66

7+
* `databricks api` now works against unified hosts. Adds `--account` to scope a call to the account API and `--workspace-id` to override the workspace routing identifier per call. A `?o=<workspace-id>` query parameter on the path (the SPOG URL convention used by the Databricks UI) is also recognized as a per-call workspace override, so URLs pasted from the browser route correctly.
8+
79
### Bundles
810

911
### Dependency updates

acceptance/cmd/api/account-flag/out.test.toml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{}
2+
3+
>>> print_requests.py --get //api/2.0/clusters/list
4+
{
5+
"headers": {
6+
"Authorization": [
7+
"Bearer [DATABRICKS_TOKEN]"
8+
],
9+
"User-Agent": [
10+
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat"
11+
]
12+
},
13+
"method": "GET",
14+
"path": "/api/2.0/clusters/list"
15+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/clusters/list --account
2+
trace print_requests.py --get //api/2.0/clusters/list | contains.py "!X-Databricks-Org-Id"

acceptance/cmd/api/account-path/out.test.toml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{}
2+
3+
>>> print_requests.py --get //api/2.0/accounts/abc-123/network-policies
4+
{
5+
"headers": {
6+
"Authorization": [
7+
"Bearer [DATABRICKS_TOKEN]"
8+
],
9+
"User-Agent": [
10+
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat"
11+
]
12+
},
13+
"method": "GET",
14+
"path": "/api/2.0/accounts/abc-123/network-policies"
15+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/accounts/abc-123/network-policies
2+
trace print_requests.py --get //api/2.0/accounts/abc-123/network-policies | contains.py "!X-Databricks-Org-Id"

acceptance/cmd/api/test.toml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
RecordRequests = true
2+
IncludeRequestHeaders = ["Authorization", "User-Agent", "X-Databricks-Org-Id"]
3+
4+
# Normalize OS-dependent and CI-only User-Agent segments so the recorded
5+
# requests are stable across local macOS/Linux runs and GitHub Actions.
6+
[[Repls]]
7+
Old = '(linux|darwin|windows)'
8+
New = '[OS]'
9+
10+
[[Repls]]
11+
Old = " cicd/[A-Za-z0-9.-]+"
12+
New = ""
13+
14+
[[Repls]]
15+
Old = " upstream/[A-Za-z0-9.-]+"
16+
New = ""
17+
18+
[[Repls]]
19+
Old = " upstream-version/[A-Za-z0-9.-]+"
20+
New = ""
21+
22+
# Common stubs for paths used across variants. Each returns an empty JSON
23+
# object; the variants assert on the *recorded request* (out.requests.txt),
24+
# not on the response body, so any well-formed JSON is fine.
25+
26+
[[Server]]
27+
Pattern = "GET /api/2.0/clusters/list"
28+
Response.Body = '{}'
29+
30+
[[Server]]
31+
Pattern = "GET /api/2.0/accounts/abc-123/network-policies"
32+
Response.Body = '{}'
33+
34+
[[Server]]
35+
Pattern = "GET /api/2.0/accounts/abc-123/oauth2/published-app-integrations"
36+
Response.Body = '{}'
37+
38+
[[Server]]
39+
Pattern = "GET /api/2.0/preview/accounts/access-control/rule-sets"
40+
Response.Body = '{}'

acceptance/cmd/api/workspace-id-flag/out.test.toml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{}
2+
3+
>>> print_requests.py --get //api/2.0/clusters/list
4+
{
5+
"headers": {
6+
"Authorization": [
7+
"Bearer [DATABRICKS_TOKEN]"
8+
],
9+
"User-Agent": [
10+
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat"
11+
],
12+
"X-Databricks-Org-Id": [
13+
"999"
14+
]
15+
},
16+
"method": "GET",
17+
"path": "/api/2.0/clusters/list"
18+
}

0 commit comments

Comments
 (0)