Skip to content

Commit 0a030bd

Browse files
authored
Use X-Databricks-Workspace-Id for workspace routing (#1688)
## Summary Workspace-scoped API calls now identify the target workspace via the `X-Databricks-Workspace-Id` request header instead of `X-Databricks-Org-Id`. The value source is unchanged: it still comes from `Config.WorkspaceID` (or the `DATABRICKS_WORKSPACE_ID` environment variable). The response-side header read in `CurrentWorkspaceID` is left on the legacy name for now. A new `CurrentWorkspaceIdentifier` helper is added to support workspace identifier formats other than classic numeric IDs. ## Why On unified Databricks hosts that serve multiple workspaces, the SDK sends a routing header on every workspace-scoped HTTP request so the gateway can dispatch it to the correct workspace. Until now that header was `X-Databricks-Org-Id`. The Databricks platform is consolidating workspace addressing onto a single header, `X-Databricks-Workspace-Id`, which accepts a broader range of workspace identifier formats and replaces the older single-purpose channels going forward. The previous header continues to be accepted by the platform, so older SDK versions keep working, but new SDK versions should use the new name. This PR is the Go SDK's part of that migration on the **request** side. The platform's response-side echo header has not yet been renamed, so the SDK continues to read the workspace ID back from the legacy `X-Databricks-Org-Id` response header. ## What changed ### Interface changes - **New**: `WorkspaceClient.CurrentWorkspaceIdentifier(ctx context.Context) (string, error)` — returns the workspace identifier exactly as the server echoes it back, without attempting to parse it as a number. Use this in preference to `CurrentWorkspaceID` if your code may run against workspaces whose identifier is not a classic numeric ID. - `Config.WorkspaceID` keeps the same field name, type (`string`), and environment variable (`DATABRICKS_WORKSPACE_ID`). The doc comment is widened to note that the value may now be either a classic numeric workspace ID or another workspace identifier format that the server understands. - `CurrentWorkspaceID(ctx) (int64, error)` is unchanged signature-wise but is now a thin `ParseInt` adapter over `CurrentWorkspaceIdentifier`. Existing callers keep working byte-for-byte against classic numeric workspaces; for non-numeric identifiers it returns the parse error from `strconv.ParseInt`. ### Behavioral changes - Workspace-scoped requests no longer send `X-Databricks-Org-Id`. They now send `X-Databricks-Workspace-Id` (with the same value, gated on `Config.WorkspaceID` being non-empty). Account-scoped requests are unaffected. - `WorkspaceClient.CurrentWorkspaceID` and the new `CurrentWorkspaceIdentifier` send `X-Databricks-Workspace-Id` as the routing header and read the workspace ID back from the legacy `X-Databricks-Org-Id` response header on `/api/2.0/preview/scim/v2/Me` — the server still emits the legacy name on responses, and the response-side migration is on a separate, later schedule. ### Internal changes - Regenerated `service/*/impl.go` and `internal/testspecs/service/*/impl.go` from the corresponding generator template change: 34 files, 944 line swaps, no logic change. - Updated the hand-written sites that emit the same header on workspace-scoped calls but are not covered by the generator: `service/iam/ext_impl.go`, `service/sharing/ext_api.go`, `service/workspace/ext_utilities.go`, and `workspace_functions.go`. - Manually patched 16 occurrences in newly-merged generated code (`service/bundle/impl.go`, `service/dashboards/impl.go:733`, `service/postgres/impl.go:625`) that came in via an auto-codegen update made before the generator template change was available. This will be a no-op against the next regen after the generator change lands upstream. - Renamed the two `TestCurrentWorkspaceID*` test functions so the names match the new header on the request side. ## How is this tested? - `make fmt`, `make lint`, and `make test` all pass locally. Full unit suite is green. - The four `workspace_functions_test.go` tests cover both helpers: they assert the new `X-Databricks-Workspace-Id` request header is sent, that the SDK reads the workspace ID back from the legacy `X-Databricks-Org-Id` response header, and that `CurrentWorkspaceIdentifier` returns non-numeric identifiers verbatim while `CurrentWorkspaceID` returns a parse error for those. - Spot-checked the regenerated diff: every changed line in `service/*/impl.go` is exclusively the request-header literal swap; no collateral changes. - End-to-end verified against a staging workspace: the SDK sends `X-Databricks-Workspace-Id` correctly and the gateway routes the request to the expected workspace. The response on `/api/2.0/preview/scim/v2/Me` echoes the workspace ID on `x-databricks-org-id`, which is why the response read site stays on the legacy name. - Added a new acceptance test `TestAccUnifiedHostSendsWorkspaceIdHeader` in `internal/unified_host_test.go`. It hooks a capturing `http.RoundTripper` into a workspace client built against a unified host and verifies on the wire that (1) the SDK sends `X-Databricks-Workspace-Id` and not `X-Databricks-Org-Id` on the request, (2) the server still echoes `X-Databricks-Org-Id` on the response, and (3) the SCIM Me call succeeds end-to-end. Skips when the workspace-test environment variables (`UNIFIED_HOST`, `THIS_WORKSPACE_ID`, `TEST_ACCOUNT_ID`, `DATABRICKS_CLIENT_ID`, `DATABRICKS_CLIENT_SECRET`) are missing. --------- Signed-off-by: Divyansh Vijayvergia <divyansh.vijayvergia@databricks.com>
1 parent 5b3c359 commit 0a030bd

43 files changed

Lines changed: 1108 additions & 1001 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

NEXT_CHANGELOG.md

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

1313
### Internal Changes
1414

15+
* Switch workspace addressing header on workspace-scoped API calls from `X-Databricks-Org-Id` to `X-Databricks-Workspace-Id`. The value continues to come from `Config.WorkspaceID` (`DATABRICKS_WORKSPACE_ID`), and now accepts either a classic numeric workspace ID or a CPDR connection ID (server disambiguates). Part of the DECO + Unified Workspace Addressing initiative.
16+
1517
### API Changes

config/config.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,9 @@ type Config struct {
123123
// Databricks Account ID for Accounts API. This field is used in dependencies.
124124
AccountID string `name:"account_id" env:"DATABRICKS_ACCOUNT_ID"`
125125

126-
// Databricks Workspace ID for Workspace clients when working with unified hosts
126+
// Databricks Workspace ID for Workspace clients when working with unified hosts.
127+
// Accepts either a classic numeric workspace ID or a CPDR connection ID; the
128+
// server disambiguates.
127129
WorkspaceID string `name:"workspace_id" env:"DATABRICKS_WORKSPACE_ID"`
128130

129131
Token string `name:"token" env:"DATABRICKS_TOKEN" auth:"pat,sensitive"`

internal/testspecs/service/httpcallv2/impl.go

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/testspecs/service/idempotencytesting/impl.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/testspecs/service/jsonmarshallv2/impl.go

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/testspecs/service/lrotesting/impl.go

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/unified_host_test.go

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
"net/http"
6+
"testing"
7+
8+
"github.com/databricks/databricks-sdk-go"
9+
"github.com/databricks/databricks-sdk-go/config"
10+
"github.com/databricks/databricks-sdk-go/service/iam"
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
// captureHeadersTransport snapshots the request and response headers for a
16+
// single matching URL path and lets every other request through unchanged.
17+
type captureHeadersTransport struct {
18+
inner http.RoundTripper
19+
path string
20+
21+
reqHdr http.Header
22+
respHdr http.Header
23+
}
24+
25+
func (t *captureHeadersTransport) RoundTrip(req *http.Request) (*http.Response, error) {
26+
matched := req.URL.Path == t.path
27+
if matched {
28+
t.reqHdr = req.Header.Clone()
29+
}
30+
resp, err := t.inner.RoundTrip(req)
31+
if matched && resp != nil {
32+
t.respHdr = resp.Header.Clone()
33+
}
34+
return resp, err
35+
}
36+
37+
// TestAccUnifiedHostSendsWorkspaceIdHeader verifies, against a real unified
38+
// (SPOG) host, that:
39+
// 1. the SDK sends the new X-Databricks-Workspace-Id routing header on
40+
// workspace-scoped requests, and no longer sends the legacy
41+
// X-Databricks-Org-Id, and
42+
// 2. the server still echoes the workspace ID on the legacy
43+
// X-Databricks-Org-Id response header — the response-side migration has
44+
// not yet happened, which is why CurrentWorkspaceID still reads the
45+
// legacy name in workspace_functions.go.
46+
//
47+
// Uses OAuth M2M auth (ClientID/ClientSecret) and probes the SCIM Me endpoint
48+
// as the workspace-scoped call. A transport-level header capture is hooked in
49+
// to verify which routing header actually crosses the wire.
50+
//
51+
// Runs against the workspace test environment (TEST_ENVIRONMENT_TYPE in
52+
// {WORKSPACE, UC_WORKSPACE}). Requires UNIFIED_HOST, THIS_WORKSPACE_ID,
53+
// TEST_ACCOUNT_ID, DATABRICKS_CLIENT_ID, and DATABRICKS_CLIENT_SECRET in the
54+
// environment.
55+
func TestAccUnifiedHostSendsWorkspaceIdHeader(t *testing.T) {
56+
loadDebugEnvIfRunsFromIDE(t, "workspace")
57+
if envType := testEnvironmentType(); envType != "" && envType != "WORKSPACE" && envType != "UC_WORKSPACE" {
58+
skipf(t)("Skipping workspace test: TEST_ENVIRONMENT_TYPE=%s", envType)
59+
}
60+
61+
const apiPath = "/api/2.0/preview/scim/v2/Me"
62+
transport := &captureHeadersTransport{
63+
inner: http.DefaultTransport,
64+
path: apiPath,
65+
}
66+
workspaceID := GetEnvOrSkipTest(t, "THIS_WORKSPACE_ID")
67+
cfg := &config.Config{
68+
Host: GetEnvOrSkipTest(t, "UNIFIED_HOST"),
69+
AccountID: GetEnvOrSkipTest(t, "TEST_ACCOUNT_ID"),
70+
ClientID: GetEnvOrSkipTest(t, "DATABRICKS_CLIENT_ID"),
71+
ClientSecret: GetEnvOrSkipTest(t, "DATABRICKS_CLIENT_SECRET"),
72+
WorkspaceID: workspaceID,
73+
HTTPTransport: transport,
74+
HTTPTimeoutSeconds: 60,
75+
}
76+
if err := cfg.EnsureResolved(); err != nil {
77+
skipf(t)("config resolve: %s", err)
78+
}
79+
t.Parallel()
80+
81+
ctx := context.Background()
82+
w := databricks.Must(databricks.NewWorkspaceClient((*databricks.Config)(cfg)))
83+
84+
// (1) workspace-scoped probe against the unified host: must succeed.
85+
me, err := w.CurrentUser.Me(ctx, iam.MeRequest{})
86+
require.NoError(t, err, "workspace-scoped SCIM Me should pass through the unified host gateway with the new routing header")
87+
assert.NotEmpty(t, me.UserName, "Me should return a non-empty UserName")
88+
89+
// (2) On the request side, the SDK must send the new header and not the legacy one.
90+
require.NotNil(t, transport.reqHdr, "transport did not observe a request to %s", apiPath)
91+
assert.Equal(t, workspaceID, transport.reqHdr.Get("X-Databricks-Workspace-Id"),
92+
"SDK must send X-Databricks-Workspace-Id with Config.WorkspaceID")
93+
assert.Empty(t, transport.reqHdr.Get("X-Databricks-Org-Id"),
94+
"SDK must no longer send the legacy X-Databricks-Org-Id request header")
95+
96+
// (3) On the response side, the server still echoes the legacy header name during the migration.
97+
require.NotNil(t, transport.respHdr, "transport did not observe a response from %s", apiPath)
98+
assert.NotEmpty(t, transport.respHdr.Get("X-Databricks-Org-Id"),
99+
"server is expected to still echo the legacy X-Databricks-Org-Id response header during the migration")
100+
}

service/agentbricks/impl.go

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)