From 1abaa65bc61b5c327ba74025a04b8a265a6e647f Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Wed, 12 Aug 2026 09:02:59 +0000 Subject: [PATCH 1/7] change API endpoint --- bundle/deploy/filer.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/bundle/deploy/filer.go b/bundle/deploy/filer.go index 683eb9fc433..b5d3b6a12f4 100644 --- a/bundle/deploy/filer.go +++ b/bundle/deploy/filer.go @@ -8,7 +8,6 @@ import ( "io/fs" "net/http" "net/url" - "strings" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/auth" @@ -50,7 +49,10 @@ 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, "/")) + // Read via the raw apiClient.Do (not the SDK's Workspace.Download) so + // auth.WorkspaceIDHeaders can drop the CLI-only "none" workspace-id sentinel + // that Download would send literally. See PR #6149 for the write-path equivalent. + urlPath := "/api/2.0/workspace/export?path=" + url.QueryEscape(absPath) + "&direct_download=true" err = s.apiClient.Do(ctx, http.MethodGet, urlPath, auth.WorkspaceIDHeaders(s.apiClient.Config), nil, nil, &buf) if err != nil { return nil, err @@ -72,10 +74,8 @@ func (s stateFiler) Write(ctx context.Context, path string, reader io.Reader, mo } // StateFiler returns a filer.Filer that can be used to read/write state files. -// We use a custom workspace filer which uses workspace-files API to read state files. -// This API has a higher than 10 MB limits and allows to export large state files. -// We don't use the same API for read because it doesn't correct get the file content for notebooks and returns -// "File Not Found" error instead. +// Reads use the streaming /workspace/export API, which is officially supported, +// scoped, and streams state files well beyond the 10 MB JSON export limit. func StateFiler(ctx context.Context, b *bundle.Bundle) (filer.Filer, error) { f, err := filer.NewWorkspaceFilesClient(b.WorkspaceClient(ctx), b.Config.Workspace.StatePath) if err != nil { From 191469fe0b9f968571e133e82383282d525cba8a Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Wed, 12 Aug 2026 11:29:09 +0000 Subject: [PATCH 2/7] acceptance: regenerate goldens for state-read /workspace/export migration filer.go now reads state via /api/2.0/workspace/export (a2946f2b7); regenerate the acceptance goldens to match. Note: state/basic and force_pull_commands filter captured requests on the old workspace-files path, so their goldens are now empty and their state-read assertions need the filters repointed at /workspace/export (follow-up). Co-authored-by: Isaac --- acceptance/bundle/state/basic/out.state.txt | 8 -------- .../bundle/state/force_pull_commands/output.txt | 8 -------- acceptance/bundle/user_agent/output.txt | 16 ++++++++-------- .../simple/out.requests.destroy.direct.json | 6 +++++- .../simple/out.requests.destroy.terraform.json | 6 +++++- .../simple/out.requests.plan2.direct.json | 12 ++++++++++-- .../simple/out.requests.plan2.terraform.json | 12 ++++++++++-- .../simple/out.requests.run.direct.json | 6 +++++- .../simple/out.requests.run.terraform.json | 6 +++++- 9 files changed, 48 insertions(+), 32 deletions(-) diff --git a/acceptance/bundle/state/basic/out.state.txt b/acceptance/bundle/state/basic/out.state.txt index a661214a12d..e69de29bb2d 100644 --- a/acceptance/bundle/state/basic/out.state.txt +++ b/acceptance/bundle/state/basic/out.state.txt @@ -1,8 +0,0 @@ -{ - "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/state/default/state/STATE_FILENAME" -} -{ - "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/state/default/state/deployment.json" -} diff --git a/acceptance/bundle/state/force_pull_commands/output.txt b/acceptance/bundle/state/force_pull_commands/output.txt index 5a0a6712097..19e91ebd95b 100644 --- a/acceptance/bundle/state/force_pull_commands/output.txt +++ b/acceptance/bundle/state/force_pull_commands/output.txt @@ -13,10 +13,6 @@ Resources: 1 created, 0 changed, 0 deleted, 0 unchanged === bundle summary --force-pull: remote state read >>> [CLI] bundle summary --force-pull -{ - "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/force-pull-commands/default/state/STATE_FILENAME" -} === bundle open without --force-pull: no remote state read @@ -27,7 +23,3 @@ Opening browser at [DATABRICKS_URL]/jobs/[NUMID]?w=[NUMID] >>> [CLI] bundle open foo --force-pull Opening browser at [DATABRICKS_URL]/jobs/[NUMID]?w=[NUMID] -{ - "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/force-pull-commands/default/state/STATE_FILENAME" -} diff --git a/acceptance/bundle/user_agent/output.txt b/acceptance/bundle/user_agent/output.txt index 2107e7e2b1f..5a34eac3e12 100644 --- a/acceptance/bundle/user_agent/output.txt +++ b/acceptance/bundle/user_agent/output.txt @@ -48,7 +48,7 @@ MISS deploy.terraform /.well-known/databricks-config 'databricks-tf-provider/[TF MISS deploy.terraform /.well-known/databricks-config 'databricks-tf-provider/[TF_PROVIDER_VERSION] databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[CLI_VERSION] terraform/1.5.5' MISS deploy.terraform /.well-known/databricks-config 'databricks-tf-provider/[TF_PROVIDER_VERSION] databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[CLI_VERSION] terraform/1.5.5' MISS destroy.direct /api/2.0/preview/scim/v2/Me 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' -MISS destroy.direct /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' +MISS destroy.direct /api/2.0/workspace/export 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' MISS destroy.direct /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' MISS destroy.direct /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' OK destroy.direct /api/2.1/unity-catalog/schemas/mycatalog.myschema engine/direct @@ -61,7 +61,7 @@ OK destroy.direct /api/2.0/workspace-files/import-file/Workspace/Users/[USERNA OK destroy.direct /api/2.0/workspace/delete engine/direct MISS destroy.direct /.well-known/databricks-config 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' MISS destroy.terraform /api/2.0/preview/scim/v2/Me 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' -MISS destroy.terraform /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' +MISS destroy.terraform /api/2.0/workspace/export 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' MISS destroy.terraform /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' MISS destroy.terraform /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_destroy cmd-exec-id/[UUID] interactive/none auth/pat' OK destroy.terraform /api/2.0/workspace/export engine/terraform @@ -93,30 +93,30 @@ MISS plan.terraform /.well-known/databricks-config 'cli/[CLI_VERSION] databricks MISS plan.terraform /.well-known/databricks-config 'databricks-tf-provider/[TF_PROVIDER_VERSION] databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[CLI_VERSION] terraform/1.5.5' MISS plan.terraform /.well-known/databricks-config 'databricks-tf-provider/[TF_PROVIDER_VERSION] databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[CLI_VERSION] terraform/1.5.5' MISS plan2.direct /api/2.0/preview/scim/v2/Me 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' -MISS plan2.direct /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' +MISS plan2.direct /api/2.0/workspace/export 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' MISS plan2.direct /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' MISS plan2.direct /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' -OK plan2.direct /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deployment.json engine/direct +OK plan2.direct /api/2.0/workspace/export engine/direct OK plan2.direct /api/2.0/workspace/get-status engine/direct OK plan2.direct /api/2.1/unity-catalog/schemas/mycatalog.myschema engine/direct MISS plan2.direct /.well-known/databricks-config 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' MISS plan2.terraform /api/2.0/preview/scim/v2/Me 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' -MISS plan2.terraform /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' +MISS plan2.terraform /api/2.0/workspace/export 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' MISS plan2.terraform /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' MISS plan2.terraform /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_plan cmd-exec-id/[UUID] interactive/none auth/pat' -OK plan2.terraform /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deployment.json engine/terraform +OK plan2.terraform /api/2.0/workspace/export engine/terraform OK plan2.terraform /api/2.0/workspace/get-status engine/terraform MISS plan2.terraform /.well-known/databricks-config 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' MISS plan2.terraform /api/2.1/unity-catalog/schemas/mycatalog.myschema 'databricks-tf-provider/[TF_PROVIDER_VERSION] databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[CLI_VERSION] terraform/1.5.5 sdk/sdkv2 resource/schema auth/pat' MISS plan2.terraform /.well-known/databricks-config 'databricks-tf-provider/[TF_PROVIDER_VERSION] databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[CLI_VERSION] terraform/1.5.5' MISS plan2.terraform /.well-known/databricks-config 'databricks-tf-provider/[TF_PROVIDER_VERSION] databricks-sdk-go/[SDK_VERSION] go/1.25.8 os/[OS] cli/[CLI_VERSION] terraform/1.5.5' MISS run.direct /api/2.0/preview/scim/v2/Me 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' -MISS run.direct /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' +MISS run.direct /api/2.0/workspace/export 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' MISS run.direct /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' MISS run.direct /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' MISS run.direct /.well-known/databricks-config 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' MISS run.terraform /api/2.0/preview/scim/v2/Me 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' -MISS run.terraform /api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' +MISS run.terraform /api/2.0/workspace/export 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' MISS run.terraform /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' MISS run.terraform /api/2.0/workspace/get-status 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS] cmd/bundle_run cmd-exec-id/[UUID] interactive/none auth/pat' MISS run.terraform /.well-known/databricks-config 'cli/[CLI_VERSION] databricks-sdk-go/[SDK_VERSION] go/[GO_VERSION] os/[OS]' diff --git a/acceptance/bundle/user_agent/simple/out.requests.destroy.direct.json b/acceptance/bundle/user_agent/simple/out.requests.destroy.direct.json index c67f22e3730..f88dbc95f99 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.destroy.direct.json +++ b/acceptance/bundle/user_agent/simple/out.requests.destroy.direct.json @@ -14,7 +14,11 @@ ] }, "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json" + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json" + } } { "headers": { diff --git a/acceptance/bundle/user_agent/simple/out.requests.destroy.terraform.json b/acceptance/bundle/user_agent/simple/out.requests.destroy.terraform.json index 09b923be4dd..f6ba297e73a 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.destroy.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.destroy.terraform.json @@ -14,7 +14,11 @@ ] }, "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate" + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate" + } } { "headers": { diff --git a/acceptance/bundle/user_agent/simple/out.requests.plan2.direct.json b/acceptance/bundle/user_agent/simple/out.requests.plan2.direct.json index a09c19307f7..f29aef29a0a 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.plan2.direct.json +++ b/acceptance/bundle/user_agent/simple/out.requests.plan2.direct.json @@ -14,7 +14,11 @@ ] }, "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json" + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json" + } } { "headers": { @@ -49,7 +53,11 @@ ] }, "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deployment.json" + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deployment.json" + } } { "headers": { diff --git a/acceptance/bundle/user_agent/simple/out.requests.plan2.terraform.json b/acceptance/bundle/user_agent/simple/out.requests.plan2.terraform.json index 01f69d2c457..d8612181d59 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.plan2.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.plan2.terraform.json @@ -14,7 +14,11 @@ ] }, "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate" + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate" + } } { "headers": { @@ -49,7 +53,11 @@ ] }, "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deployment.json" + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/deployment.json" + } } { "headers": { diff --git a/acceptance/bundle/user_agent/simple/out.requests.run.direct.json b/acceptance/bundle/user_agent/simple/out.requests.run.direct.json index 0fb17ee33cd..8e1baa2f5d0 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.run.direct.json +++ b/acceptance/bundle/user_agent/simple/out.requests.run.direct.json @@ -14,7 +14,11 @@ ] }, "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json" + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/resources.json" + } } { "headers": { diff --git a/acceptance/bundle/user_agent/simple/out.requests.run.terraform.json b/acceptance/bundle/user_agent/simple/out.requests.run.terraform.json index 01ca151076c..fd33d57a70a 100644 --- a/acceptance/bundle/user_agent/simple/out.requests.run.terraform.json +++ b/acceptance/bundle/user_agent/simple/out.requests.run.terraform.json @@ -14,7 +14,11 @@ ] }, "method": "GET", - "path": "/api/2.0/workspace-files/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate" + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/state/terraform.tfstate" + } } { "headers": { From 0b2b4010a29b581ba8e6c99f9e358c3ee476cf27 Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Fri, 14 Aug 2026 12:50:40 +0000 Subject: [PATCH 3/7] make the comment easy to understand --- bundle/deploy/filer.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bundle/deploy/filer.go b/bundle/deploy/filer.go index b5d3b6a12f4..6a55fe054da 100644 --- a/bundle/deploy/filer.go +++ b/bundle/deploy/filer.go @@ -49,9 +49,12 @@ func (s stateFiler) Read(ctx context.Context, path string) (io.ReadCloser, error } var buf bytes.Buffer - // Read via the raw apiClient.Do (not the SDK's Workspace.Download) so - // auth.WorkspaceIDHeaders can drop the CLI-only "none" workspace-id sentinel - // that Download would send literally. See PR #6149 for the write-path equivalent. + // We read via the raw apiClient.Do so the workspace-id routing header goes + // through auth.WorkspaceIDHeaders, which drops the CLI-only "none" sentinel + // (auth.WorkspaceIDNone, written by `auth login --skip-workspace`). The SDK's + // Workspace client would forward that sentinel as a literal workspace id + // unless we special-cased it; reusing the shared helper here keeps the change + // small. urlPath := "/api/2.0/workspace/export?path=" + url.QueryEscape(absPath) + "&direct_download=true" err = s.apiClient.Do(ctx, http.MethodGet, urlPath, auth.WorkspaceIDHeaders(s.apiClient.Config), nil, nil, &buf) if err != nil { From d110a585641feba47e00b4f4fbe5f16d968ca4ee Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Tue, 18 Aug 2026 14:19:33 +0000 Subject: [PATCH 4/7] use SDK instead of the handrolled .do --- bundle/deploy/filer.go | 55 ++++++++++++++++++++++-------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/bundle/deploy/filer.go b/bundle/deploy/filer.go index 6a55fe054da..79e889d26e5 100644 --- a/bundle/deploy/filer.go +++ b/bundle/deploy/filer.go @@ -1,18 +1,15 @@ package deploy import ( - "bytes" "context" "fmt" "io" "io/fs" - "net/http" - "net/url" "github.com/databricks/cli/bundle" "github.com/databricks/cli/libs/auth" "github.com/databricks/cli/libs/filer" - "github.com/databricks/databricks-sdk-go/client" + "github.com/databricks/databricks-sdk-go" ) // FilerFactory is a function that returns a filer.Filer. @@ -21,8 +18,8 @@ type FilerFactory func(ctx context.Context, b *bundle.Bundle) (filer.Filer, erro type stateFiler struct { filer filer.Filer - apiClient *client.DatabricksClient - root filer.WorkspaceRootPath + workspaceClient *databricks.WorkspaceClient + root filer.WorkspaceRootPath } func (s stateFiler) Delete(ctx context.Context, path string, mode ...filer.DeleteMode) error { @@ -48,20 +45,8 @@ func (s stateFiler) Read(ctx context.Context, path string) (io.ReadCloser, error return nil, fmt.Errorf("not a file: %s", absPath) } - var buf bytes.Buffer - // We read via the raw apiClient.Do so the workspace-id routing header goes - // through auth.WorkspaceIDHeaders, which drops the CLI-only "none" sentinel - // (auth.WorkspaceIDNone, written by `auth login --skip-workspace`). The SDK's - // Workspace client would forward that sentinel as a literal workspace id - // unless we special-cased it; reusing the shared helper here keeps the change - // small. - urlPath := "/api/2.0/workspace/export?path=" + url.QueryEscape(absPath) + "&direct_download=true" - err = s.apiClient.Do(ctx, http.MethodGet, urlPath, auth.WorkspaceIDHeaders(s.apiClient.Config), nil, nil, &buf) - if err != nil { - return nil, err - } - - return io.NopCloser(&buf), nil + // Stream via the SDK's /workspace/export (direct_download=true); no 10 MB cap. + return s.workspaceClient.Workspace.Download(ctx, absPath) } func (s stateFiler) ReadDir(ctx context.Context, path string) ([]fs.DirEntry, error) { @@ -80,19 +65,37 @@ func (s stateFiler) Write(ctx context.Context, path string, reader io.Reader, mo // Reads use the streaming /workspace/export API, which is officially supported, // scoped, and streams state files well beyond the 10 MB JSON export limit. func StateFiler(ctx context.Context, b *bundle.Bundle) (filer.Filer, error) { - f, err := filer.NewWorkspaceFilesClient(b.WorkspaceClient(ctx), b.Config.Workspace.StatePath) + w, err := stateWorkspaceClient(ctx, b) if err != nil { return nil, err } - apiClient, err := client.New(b.WorkspaceClient(ctx).Config) + f, err := filer.NewWorkspaceFilesClient(w, b.Config.Workspace.StatePath) if err != nil { - return nil, fmt.Errorf("failed to create API client: %w", err) + return nil, err } return stateFiler{ - filer: f, - root: filer.NewWorkspaceRootPath(b.Config.Workspace.StatePath), - apiClient: apiClient, + filer: f, + root: filer.NewWorkspaceRootPath(b.Config.Workspace.StatePath), + workspaceClient: w, }, nil } + +// stateWorkspaceClient returns the bundle's workspace client, cloned with the CLI-only +// "none" workspace-id sentinel stripped so Workspace.Download does not forward it as the +// X-Databricks-Workspace-Id routing header. +func stateWorkspaceClient(ctx context.Context, b *bundle.Bundle) (*databricks.WorkspaceClient, error) { + w := b.WorkspaceClient(ctx) + if w.Config.WorkspaceID != auth.WorkspaceIDNone { + return w, nil + } + + // config.Config embeds a sync.Mutex, so use the SDK's copylocks-safe clone. + cfg, err := w.Config.NewWithWorkspaceHost(w.Config.Host) + if err != nil { + return nil, err + } + cfg.WorkspaceID = "" + return databricks.NewWorkspaceClient((*databricks.Config)(cfg)) +} From 17e05c56fa3da797186bcd1708027f878104e8a5 Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Wed, 19 Aug 2026 15:07:30 +0000 Subject: [PATCH 5/7] remove none sentinel handling --- bundle/deploy/filer.go | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) diff --git a/bundle/deploy/filer.go b/bundle/deploy/filer.go index 79e889d26e5..8fdbda90e01 100644 --- a/bundle/deploy/filer.go +++ b/bundle/deploy/filer.go @@ -7,7 +7,6 @@ import ( "io/fs" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/libs/auth" "github.com/databricks/cli/libs/filer" "github.com/databricks/databricks-sdk-go" ) @@ -65,10 +64,7 @@ func (s stateFiler) Write(ctx context.Context, path string, reader io.Reader, mo // Reads use the streaming /workspace/export API, which is officially supported, // scoped, and streams state files well beyond the 10 MB JSON export limit. func StateFiler(ctx context.Context, b *bundle.Bundle) (filer.Filer, error) { - w, err := stateWorkspaceClient(ctx, b) - if err != nil { - return nil, err - } + w := b.WorkspaceClient(ctx) f, err := filer.NewWorkspaceFilesClient(w, b.Config.Workspace.StatePath) if err != nil { @@ -81,21 +77,3 @@ func StateFiler(ctx context.Context, b *bundle.Bundle) (filer.Filer, error) { workspaceClient: w, }, nil } - -// stateWorkspaceClient returns the bundle's workspace client, cloned with the CLI-only -// "none" workspace-id sentinel stripped so Workspace.Download does not forward it as the -// X-Databricks-Workspace-Id routing header. -func stateWorkspaceClient(ctx context.Context, b *bundle.Bundle) (*databricks.WorkspaceClient, error) { - w := b.WorkspaceClient(ctx) - if w.Config.WorkspaceID != auth.WorkspaceIDNone { - return w, nil - } - - // config.Config embeds a sync.Mutex, so use the SDK's copylocks-safe clone. - cfg, err := w.Config.NewWithWorkspaceHost(w.Config.Host) - if err != nil { - return nil, err - } - cfg.WorkspaceID = "" - return databricks.NewWorkspaceClient((*databricks.Config)(cfg)) -} From 93d5bc62c311029327037b40090e3dcac044e8b5 Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Wed, 19 Aug 2026 16:24:49 +0000 Subject: [PATCH 6/7] update grep to new endpoint --- acceptance/bundle/state/basic/out.state.txt | 48 +++++++++++++++++++ acceptance/bundle/state/basic/script | 2 +- .../state/force_pull_commands/output.txt | 16 +++++++ .../bundle/state/force_pull_commands/script | 8 ++-- 4 files changed, 69 insertions(+), 5 deletions(-) diff --git a/acceptance/bundle/state/basic/out.state.txt b/acceptance/bundle/state/basic/out.state.txt index e69de29bb2d..eb89b7ff766 100644 --- a/acceptance/bundle/state/basic/out.state.txt +++ b/acceptance/bundle/state/basic/out.state.txt @@ -0,0 +1,48 @@ +{ + "method": "GET", + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/state/default/state/deploy.lock" + } +} +{ + "method": "GET", + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/state/default/state/deploy.lock" + } +} +{ + "method": "GET", + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/state/default/state/STATE_FILENAME" + } +} +{ + "method": "GET", + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/state/default/state/deployment.json" + } +} +{ + "method": "GET", + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/state/default/state/deploy.lock" + } +} +{ + "method": "GET", + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/state/default/state/deploy.lock" + } +} diff --git a/acceptance/bundle/state/basic/script b/acceptance/bundle/state/basic/script index f1587f56145..188445d8e9d 100644 --- a/acceptance/bundle/state/basic/script +++ b/acceptance/bundle/state/basic/script @@ -1,4 +1,4 @@ trace $CLI bundle deploy trace $CLI bundle deploy # We do 2 deploys because only 2nd deploy will pull state from remote after 1st created it -jq 'select(.path | test("/api/2.0/workspace-files/Workspace/Users/.*/.bundle/state/default/state/"))' out.requests.txt > out.state.txt +jq 'select(.path == "/api/2.0/workspace/export" and (.q.path | test("/.bundle/state/default/state/")))' out.requests.txt > out.state.txt rm out.requests.txt diff --git a/acceptance/bundle/state/force_pull_commands/output.txt b/acceptance/bundle/state/force_pull_commands/output.txt index 19e91ebd95b..217f1d3fb20 100644 --- a/acceptance/bundle/state/force_pull_commands/output.txt +++ b/acceptance/bundle/state/force_pull_commands/output.txt @@ -13,6 +13,14 @@ Resources: 1 created, 0 changed, 0 deleted, 0 unchanged === bundle summary --force-pull: remote state read >>> [CLI] bundle summary --force-pull +{ + "method": "GET", + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/force-pull-commands/default/state/STATE_FILENAME" + } +} === bundle open without --force-pull: no remote state read @@ -23,3 +31,11 @@ Opening browser at [DATABRICKS_URL]/jobs/[NUMID]?w=[NUMID] >>> [CLI] bundle open foo --force-pull Opening browser at [DATABRICKS_URL]/jobs/[NUMID]?w=[NUMID] +{ + "method": "GET", + "path": "/api/2.0/workspace/export", + "q": { + "direct_download": "true", + "path": "/Workspace/Users/[USERNAME]/.bundle/force-pull-commands/default/state/STATE_FILENAME" + } +} diff --git a/acceptance/bundle/state/force_pull_commands/script b/acceptance/bundle/state/force_pull_commands/script index 488a260e2ff..b4e0017422e 100644 --- a/acceptance/bundle/state/force_pull_commands/script +++ b/acceptance/bundle/state/force_pull_commands/script @@ -11,19 +11,19 @@ export BROWSER="echo_browser.py" title "bundle summary without --force-pull: no remote state read\n" trace $CLI bundle summary > /dev/null touch out.requests.txt -print_requests.py --get //workspace-files/ +print_requests.py --get //workspace/export title "bundle summary --force-pull: remote state read\n" trace $CLI bundle summary --force-pull > /dev/null touch out.requests.txt -print_requests.py --get //workspace-files/ +print_requests.py --get //workspace/export title "bundle open without --force-pull: no remote state read\n" trace $CLI bundle open foo > /dev/null touch out.requests.txt -print_requests.py --get //workspace-files/ +print_requests.py --get //workspace/export title "bundle open --force-pull: remote state read\n" trace $CLI bundle open foo --force-pull > /dev/null touch out.requests.txt -print_requests.py --get //workspace-files/ +print_requests.py --get //workspace/export From 4612ac8c5180b989564d95203ccaa0baa196a32f Mon Sep 17 00:00:00 2001 From: Sankalp-Mittal Date: Wed, 19 Aug 2026 16:55:49 +0000 Subject: [PATCH 7/7] update grep --- acceptance/bundle/state/basic/out.state.txt | 32 --------------------- acceptance/bundle/state/basic/script | 2 +- 2 files changed, 1 insertion(+), 33 deletions(-) diff --git a/acceptance/bundle/state/basic/out.state.txt b/acceptance/bundle/state/basic/out.state.txt index eb89b7ff766..59cdcbc51f9 100644 --- a/acceptance/bundle/state/basic/out.state.txt +++ b/acceptance/bundle/state/basic/out.state.txt @@ -1,19 +1,3 @@ -{ - "method": "GET", - "path": "/api/2.0/workspace/export", - "q": { - "direct_download": "true", - "path": "/Workspace/Users/[USERNAME]/.bundle/state/default/state/deploy.lock" - } -} -{ - "method": "GET", - "path": "/api/2.0/workspace/export", - "q": { - "direct_download": "true", - "path": "/Workspace/Users/[USERNAME]/.bundle/state/default/state/deploy.lock" - } -} { "method": "GET", "path": "/api/2.0/workspace/export", @@ -30,19 +14,3 @@ "path": "/Workspace/Users/[USERNAME]/.bundle/state/default/state/deployment.json" } } -{ - "method": "GET", - "path": "/api/2.0/workspace/export", - "q": { - "direct_download": "true", - "path": "/Workspace/Users/[USERNAME]/.bundle/state/default/state/deploy.lock" - } -} -{ - "method": "GET", - "path": "/api/2.0/workspace/export", - "q": { - "direct_download": "true", - "path": "/Workspace/Users/[USERNAME]/.bundle/state/default/state/deploy.lock" - } -} diff --git a/acceptance/bundle/state/basic/script b/acceptance/bundle/state/basic/script index 188445d8e9d..0e0ee76891e 100644 --- a/acceptance/bundle/state/basic/script +++ b/acceptance/bundle/state/basic/script @@ -1,4 +1,4 @@ trace $CLI bundle deploy trace $CLI bundle deploy # We do 2 deploys because only 2nd deploy will pull state from remote after 1st created it -jq 'select(.path == "/api/2.0/workspace/export" and (.q.path | test("/.bundle/state/default/state/")))' out.requests.txt > out.state.txt +jq 'select(.path == "/api/2.0/workspace/export" and (.q.path | test("/.bundle/state/default/state/")) and (.q.path | test("deploy.lock") | not))' out.requests.txt > out.state.txt rm out.requests.txt