Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion acceptance/cmd/api/account-flag/script
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/clusters/list --account
trace print_requests.py --get //api/2.0/clusters/list | contains.py "!X-Databricks-Org-Id"
trace print_requests.py --get //api/2.0/clusters/list | contains.py "!X-Databricks-Workspace-Id"
2 changes: 1 addition & 1 deletion acceptance/cmd/api/account-path/script
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/accounts/abc-123/network-policies
trace print_requests.py --get //api/2.0/accounts/abc-123/network-policies | contains.py "!X-Databricks-Org-Id"
trace print_requests.py --get //api/2.0/accounts/abc-123/network-policies | contains.py "!X-Databricks-Workspace-Id"
2 changes: 1 addition & 1 deletion acceptance/cmd/api/default-profile/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"User-Agent": [
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"[NUMID]"
]
},
Expand Down
2 changes: 1 addition & 1 deletion acceptance/cmd/api/default-profile/script
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ unset DATABRICKS_CONFIG_PROFILE

title "default_profile is used when no --profile flag and no DATABRICKS_CONFIG_PROFILE\n"
MSYS_NO_PATHCONV=1 trace $CLI api get /api/2.0/clusters/list
trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Org-Id"
trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Workspace-Id"

title "--profile overrides default_profile (negative case)\n"
MSYS_NO_PATHCONV=1 errcode $CLI api get /api/2.0/clusters/list -p other 2>&1 | contains.py "other.test"
2 changes: 1 addition & 1 deletion acceptance/cmd/api/test.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
RecordRequests = true
IncludeRequestHeaders = ["Authorization", "User-Agent", "X-Databricks-Org-Id"]
IncludeRequestHeaders = ["Authorization", "User-Agent", "X-Databricks-Workspace-Id"]

# Normalize OS-dependent and CI-only User-Agent segments so the recorded
# requests are stable across local macOS/Linux runs and GitHub Actions.
Expand Down
2 changes: 1 addition & 1 deletion acceptance/cmd/api/workspace-id-flag/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"User-Agent": [
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"999"
]
},
Expand Down
2 changes: 1 addition & 1 deletion acceptance/cmd/api/workspace-id-flag/script
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/clusters/list --workspace-id 999
trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Org-Id" "999"
trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Workspace-Id" "999"
2 changes: 1 addition & 1 deletion acceptance/cmd/api/workspace-id-from-query/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"User-Agent": [
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"999"
]
},
Expand Down
2 changes: 1 addition & 1 deletion acceptance/cmd/api/workspace-id-from-query/script
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
MSYS_NO_PATHCONV=1 $CLI api get "/api/2.0/clusters/list?o=999"
trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Org-Id" "999"
trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Workspace-Id" "999"
3 changes: 3 additions & 0 deletions acceptance/cmd/api/workspace-id-from-w-query/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions acceptance/cmd/api/workspace-id-from-w-query/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{}

>>> print_requests.py --get //api/2.0/clusters/list
{
"headers": {
"Authorization": [
"Bearer [DATABRICKS_TOKEN]"
],
"User-Agent": [
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat"
],
"X-Databricks-Workspace-Id": [
"999"
]
},
"method": "GET",
"path": "/api/2.0/clusters/list",
"q": {
"w": "999"
}
}
2 changes: 2 additions & 0 deletions acceptance/cmd/api/workspace-id-from-w-query/script
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
MSYS_NO_PATHCONV=1 $CLI api get "/api/2.0/clusters/list?w=999"
trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Workspace-Id" "999"
2 changes: 1 addition & 1 deletion acceptance/cmd/api/workspace-id-none/script
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ workspace_id = none
EOF

MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/clusters/list --profile spog-account
trace print_requests.py --get //api/2.0/clusters/list | contains.py "!X-Databricks-Org-Id"
trace print_requests.py --get //api/2.0/clusters/list | contains.py "!X-Databricks-Workspace-Id"
2 changes: 1 addition & 1 deletion acceptance/cmd/api/workspace-path/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"User-Agent": [
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"[NUMID]"
]
},
Expand Down
2 changes: 1 addition & 1 deletion acceptance/cmd/api/workspace-path/script
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/clusters/list
trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Org-Id"
trace print_requests.py --get //api/2.0/clusters/list | contains.py "X-Databricks-Workspace-Id"
2 changes: 1 addition & 1 deletion acceptance/cmd/api/workspace-proxy-regression/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"User-Agent": [
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/api_get cmd-exec-id/[UUID] interactive/none auth/pat"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"[NUMID]"
]
},
Expand Down
2 changes: 1 addition & 1 deletion acceptance/cmd/api/workspace-proxy-regression/script
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# being misclassified as account-scope, so the routing identifier should be
# present on the recorded request.
MSYS_NO_PATHCONV=1 $CLI api get /api/2.0/preview/accounts/access-control/rule-sets
trace print_requests.py --get //api/2.0/preview/accounts/access-control/rule-sets | contains.py "X-Databricks-Org-Id"
trace print_requests.py --get //api/2.0/preview/accounts/access-control/rule-sets | contains.py "X-Databricks-Workspace-Id"
6 changes: 3 additions & 3 deletions acceptance/telemetry/failure/out.requests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"Authorization": [
"Bearer [DATABRICKS_TOKEN]"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"[NUMID]"
]
},
Expand All @@ -27,7 +27,7 @@
"Authorization": [
"Bearer [DATABRICKS_TOKEN]"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"[NUMID]"
]
},
Expand All @@ -47,7 +47,7 @@
"Authorization": [
"Bearer [DATABRICKS_TOKEN]"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"[NUMID]"
]
},
Expand Down
6 changes: 3 additions & 3 deletions acceptance/telemetry/partial-success/out.requests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"Authorization": [
"Bearer [DATABRICKS_TOKEN]"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"[NUMID]"
]
},
Expand All @@ -27,7 +27,7 @@
"Authorization": [
"Bearer [DATABRICKS_TOKEN]"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"[NUMID]"
]
},
Expand All @@ -47,7 +47,7 @@
"Authorization": [
"Bearer [DATABRICKS_TOKEN]"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"[NUMID]"
]
},
Expand Down
2 changes: 1 addition & 1 deletion acceptance/telemetry/success/out.requests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"User-Agent": [
"cli/[DEV_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/selftest_send-telemetry cmd-exec-id/[CMD-EXEC-ID] interactive/none auth/pat"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"[NUMID]"
]
},
Expand Down
2 changes: 1 addition & 1 deletion acceptance/telemetry/test.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
IncludeRequestHeaders = ["Authorization", "X-Databricks-Org-Id"]
IncludeRequestHeaders = ["Authorization", "X-Databricks-Workspace-Id"]
RecordRequests = true

Local = true
Expand Down
2 changes: 1 addition & 1 deletion acceptance/telemetry/timeout/out.requests.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"Authorization": [
"Bearer [DATABRICKS_TOKEN]"
],
"X-Databricks-Org-Id": [
"X-Databricks-Workspace-Id": [
"[NUMID]"
]
},
Expand Down
12 changes: 6 additions & 6 deletions bundle/deploy/filer.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ type stateFiler struct {
root filer.WorkspaceRootPath
}

// orgIDHeaders returns headers with X-Databricks-Org-Id set if a workspace ID
// is configured. SPOG hosts require this header to route requests to the
// correct workspace.
func (s stateFiler) orgIDHeaders() map[string]string {
// workspaceIDHeaders returns headers with X-Databricks-Workspace-Id set if a
// workspace ID is configured. SPOG hosts require this header to route requests
// to the correct workspace.
func (s stateFiler) workspaceIDHeaders() map[string]string {
wsID := s.apiClient.Config.WorkspaceID
if wsID == "" {
return nil
}
return map[string]string{
"X-Databricks-Org-Id": wsID,
"X-Databricks-Workspace-Id": wsID,
}
}

Expand Down Expand Up @@ -63,7 +63,7 @@ func (s stateFiler) Read(ctx context.Context, path string) (io.ReadCloser, error

var buf bytes.Buffer
urlPath := "/api/2.0/workspace-files/" + url.PathEscape(strings.TrimLeft(absPath, "/"))
err = s.apiClient.Do(ctx, http.MethodGet, urlPath, s.orgIDHeaders(), nil, nil, &buf)
err = s.apiClient.Do(ctx, http.MethodGet, urlPath, s.workspaceIDHeaders(), nil, nil, &buf)
if err != nil {
return nil, err
}
Expand Down
51 changes: 32 additions & 19 deletions cmd/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,25 @@ import (
)

const (
// orgIDHeader is the workspace routing identifier sent on workspace-scope
// requests against unified hosts. Generated SDK service methods set this
// per-call when cfg.WorkspaceID is populated; we mirror the same idiom.
orgIDHeader = "X-Databricks-Org-Id"

// orgIDQueryParam is the SPOG (single-page-of-glass) URL convention used
// by the Databricks UI: "?o=<workspace-id>" identifies the workspace a URL
// targets. When present on the path, we treat it as a per-call override
// for the workspace routing identifier so that pasted SPOG URLs route
// correctly without requiring --workspace-id.
orgIDQueryParam = "o"
// workspaceIDHeader is the workspace routing identifier sent on
// workspace-scope requests against unified hosts. Generated SDK service
// methods set this per-call when cfg.WorkspaceID is populated; we mirror
// the same idiom. The gateway also accepts the legacy X-Databricks-Org-Id
// header for rollback safety.
workspaceIDHeader = "X-Databricks-Workspace-Id"

// orgIDQueryParam and workspaceIDQueryParam are the SPOG
// (single-page-of-glass) URL convention used by the Databricks UI:
// "?o=<workspace-id>" or "?w=<workspace-id>" identifies the workspace a
// URL targets. When present on the path, we treat it as a per-call
// override for the workspace routing identifier so that pasted SPOG URLs
// route correctly without requiring --workspace-id. "w" is the new
// spelling that matches the X-Databricks-Workspace-Id header; "o" stays
// accepted for URLs already pasted from older UI builds, shell history,
// or committed databricks.yml files. "o" takes precedence when both are
// present to preserve the meaning of existing URLs.
orgIDQueryParam = "o"
workspaceIDQueryParam = "w"
)

// accountSegmentRe matches a non-empty segment immediately after "accounts/",
Expand Down Expand Up @@ -114,7 +122,7 @@ func makeCommand(method string) *cobra.Command {

headers := map[string]string{"Content-Type": "application/json"}
if orgID != "" {
headers[orgIDHeader] = orgID
headers[workspaceIDHeader] = orgID
}

var response any
Expand Down Expand Up @@ -163,14 +171,19 @@ func hasAccountSegment(rawPath string) (bool, error) {
return accountSegmentRe.MatchString(p), nil
}

// extractOrgIDFromQuery returns the value of the "o" query parameter on path
// (the SPOG URL convention), or "" if absent or empty.
func extractOrgIDFromQuery(rawPath string) (string, error) {
// extractWorkspaceIDFromQuery returns the workspace ID encoded in the path's
// query string (the SPOG URL convention). It checks "o" first, then "w";
// returns "" if neither is present or non-empty.
func extractWorkspaceIDFromQuery(rawPath string) (string, error) {
u, err := url.Parse(rawPath)
if err != nil {
return "", fmt.Errorf("parse path: %w", err)
}
return u.Query().Get(orgIDQueryParam), nil
q := u.Query()
if v := q.Get(orgIDQueryParam); v != "" {
return v, nil
}
return q.Get(workspaceIDQueryParam), nil
}

// resolveOrgID picks the value (if any) for the workspace routing identifier
Expand All @@ -195,12 +208,12 @@ func resolveOrgID(
}
return workspaceIDFlag, nil
}
orgIDFromQuery, err := extractOrgIDFromQuery(path)
workspaceIDFromQuery, err := extractWorkspaceIDFromQuery(path)
if err != nil {
return "", err
}
if orgIDFromQuery != "" {
return orgIDFromQuery, nil
if workspaceIDFromQuery != "" {
return workspaceIDFromQuery, nil
}
isAccount, err := hasAccountSegment(path)
if err != nil {
Expand Down
30 changes: 28 additions & 2 deletions cmd/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func TestHasAccountSegment(t *testing.T) {
}
}

func TestExtractOrgIDFromQuery(t *testing.T) {
func TestExtractWorkspaceIDFromQuery(t *testing.T) {
cases := []struct {
name string
path string
Expand All @@ -60,10 +60,15 @@ func TestExtractOrgIDFromQuery(t *testing.T) {
{"unrelated o-prefixed param ignored", "/api/2.0/clusters/list?other=1", ""},
{"absolute URL", "https://example.com/api/2.0/clusters/list?o=42", "42"},
{"first value wins on duplicate", "/api/2.0/clusters/list?o=1&o=2", "1"},
{"w param present", "/api/2.2/jobs/list?w=7474644166319138", "7474644166319138"},
{"w param empty", "/api/2.0/clusters/list?w=", ""},
{"w among other params", "/api/2.0/clusters/list?foo=bar&w=123", "123"},
{"o wins over w when both present", "/api/2.0/clusters/list?o=111&w=222", "111"},
{"w used when o is empty", "/api/2.0/clusters/list?o=&w=222", "222"},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got, err := extractOrgIDFromQuery(c.path)
got, err := extractWorkspaceIDFromQuery(c.path)
require.NoError(t, err)
assert.Equal(t, c.want, got)
})
Expand All @@ -76,6 +81,7 @@ func TestResolveOrgID(t *testing.T) {
accountPath = "/api/2.0/accounts/abc-123/network-policies"
proxyPath = "/api/2.0/preview/accounts/access-control/rule-sets"
spogPath = "/api/2.2/jobs/list?o=7474644166319138"
spogPathW = "/api/2.2/jobs/list?w=7474644166319138"
spogAccountPath = "/api/2.0/accounts/abc-123/network-policies?o=7474644166319138"
spogWorkspaceID = "7474644166319138"
resolvedWSID = "900800700600"
Expand Down Expand Up @@ -189,6 +195,26 @@ func TestResolveOrgID(t *testing.T) {
path: spogAccountPath,
want: spogWorkspaceID,
},
{
name: "?w=<id> sets identifier when no flag and no profile WorkspaceID",
cfgWorkspaceID: "",
path: spogPathW,
want: spogWorkspaceID,
},
{
name: "?w=<id> overrides profile WorkspaceID",
cfgWorkspaceID: resolvedWSID,
path: spogPathW,
want: spogWorkspaceID,
},
{
name: "--workspace-id wins over ?w=",
workspaceIDFlag: flagWSID,
flagSet: true,
cfgWorkspaceID: resolvedWSID,
path: spogPathW,
want: flagWSID,
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
Expand Down
6 changes: 4 additions & 2 deletions cmd/auth/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,9 +118,11 @@ use the flags directly to specify both.

The host URL may include query parameters to set the workspace and account ID:

databricks auth login --host "https://<host>?o=<workspace_id>&account_id=<id>"
databricks auth login --host "https://<host>?w=<workspace_id>&account_id=<id>"

Note: URLs containing "?" must be quoted to prevent shell interpretation.
The workspace ID may be passed as ?w= (preferred), ?o= (legacy), or
?workspace_id=. Note: URLs containing "?" must be quoted to prevent shell
interpretation.

If a profile with the given name already exists, it is updated. Otherwise
a new profile is created.
Expand Down
Loading
Loading