From 6af22efda2b63877b8658116e040b391619aac55 Mon Sep 17 00:00:00 2001 From: Andrew Nester Date: Mon, 26 Oct 2026 14:12:38 +0100 Subject: [PATCH] Add a new workspace.sync_mode flag --- .nextchanges/bundles/workspace-sync-mode.md | 1 + .../immutable-dev-mode/databricks.yml.tmpl | 4 +- .../databricks.yml.tmpl | 4 +- .../databricks.yml.tmpl | 4 +- .../deploy/immutable/databricks.yml.tmpl | 4 +- .../dms/immutable-folder/databricks.yml.tmpl | 3 +- .../apps/immutable/databricks.yml.tmpl | 4 +- .../databricks.yml | 4 +- .../immutable_workspace_paths/databricks.yml | 4 +- .../immutable_workspace_paths/output.txt | 3 +- .../bundle/validate/sync_mode/databricks.yml | 50 +++ .../bundle/validate/sync_mode/out.test.toml | 2 + .../bundle/validate/sync_mode/output.txt | 122 +++++++ acceptance/bundle/validate/sync_mode/script | 9 + .../bundle/validate/sync_mode/src/main.py | 1 + .../bundle/validate/sync_mode/test.toml | 1 + .../sync_mode_immutable_folder/databricks.yml | 28 ++ .../sync_mode_immutable_folder/out.test.toml | 2 + .../sync_mode_immutable_folder/output.txt | 60 ++++ .../sync_mode_immutable_folder/script | 4 + .../sync_mode_immutable_folder/src/main.py | 1 + .../sync_mode_immutable_folder/test.toml | 1 + bundle/bundle.go | 29 +- bundle/bundle_test.go | 34 ++ bundle/config/experimental.go | 2 + .../apply_source_linked_deployment_preset.go | 86 ----- ...ly_source_linked_deployment_preset_test.go | 152 --------- bundle/config/mutator/apply_sync_mode.go | 151 +++++++++ bundle/config/mutator/apply_sync_mode_test.go | 299 ++++++++++++++++++ .../mutator/override_immutable_folder.go | 7 +- .../mutator/override_immutable_folder_test.go | 14 +- .../mutator/resolve_variable_references.go | 3 +- bundle/config/mutator/translate_paths.go | 5 +- bundle/config/presets.go | 2 + bundle/config/workspace.go | 30 ++ bundle/configsync/variables.go | 3 +- bundle/deploy/files/upload.go | 3 +- bundle/deploy/metadata/compute.go | 2 +- bundle/internal/schema/annotations.yml | 22 ++ .../validation/generated/enum_fields.go | 2 + bundle/phases/dms.go | 2 +- bundle/phases/initialize.go | 8 +- bundle/schema/jsonschema.json | 33 +- bundle/trampoline/python_dbr_warning.go | 5 +- 44 files changed, 922 insertions(+), 288 deletions(-) create mode 100644 .nextchanges/bundles/workspace-sync-mode.md create mode 100644 acceptance/bundle/validate/sync_mode/databricks.yml create mode 100644 acceptance/bundle/validate/sync_mode/out.test.toml create mode 100644 acceptance/bundle/validate/sync_mode/output.txt create mode 100644 acceptance/bundle/validate/sync_mode/script create mode 100644 acceptance/bundle/validate/sync_mode/src/main.py create mode 100644 acceptance/bundle/validate/sync_mode/test.toml create mode 100644 acceptance/bundle/validate/sync_mode_immutable_folder/databricks.yml create mode 100644 acceptance/bundle/validate/sync_mode_immutable_folder/out.test.toml create mode 100644 acceptance/bundle/validate/sync_mode_immutable_folder/output.txt create mode 100644 acceptance/bundle/validate/sync_mode_immutable_folder/script create mode 100644 acceptance/bundle/validate/sync_mode_immutable_folder/src/main.py create mode 100644 acceptance/bundle/validate/sync_mode_immutable_folder/test.toml delete mode 100644 bundle/config/mutator/apply_source_linked_deployment_preset.go delete mode 100644 bundle/config/mutator/apply_source_linked_deployment_preset_test.go create mode 100644 bundle/config/mutator/apply_sync_mode.go create mode 100644 bundle/config/mutator/apply_sync_mode_test.go diff --git a/.nextchanges/bundles/workspace-sync-mode.md b/.nextchanges/bundles/workspace-sync-mode.md new file mode 100644 index 00000000000..147b2aa5289 --- /dev/null +++ b/.nextchanges/bundles/workspace-sync-mode.md @@ -0,0 +1 @@ +* Add the `workspace.sync_mode` setting with the values `overwrite` (default), `in-place`, and `immutable`. It replaces `presets.source_linked_deployment` (use `in-place`) and `experimental.immutable_folder` (use `immutable`), which are now deprecated and emit a warning. diff --git a/acceptance/bundle/deploy/immutable-dev-mode/databricks.yml.tmpl b/acceptance/bundle/deploy/immutable-dev-mode/databricks.yml.tmpl index 601b9277507..5ac5c52647f 100644 --- a/acceptance/bundle/deploy/immutable-dev-mode/databricks.yml.tmpl +++ b/acceptance/bundle/deploy/immutable-dev-mode/databricks.yml.tmpl @@ -1,8 +1,8 @@ bundle: name: test-bundle-immutable-dev-mode-$UNIQUE_NAME -experimental: - immutable_folder: true +workspace: + sync_mode: immutable resources: jobs: diff --git a/acceptance/bundle/deploy/immutable-no-artifacts/databricks.yml.tmpl b/acceptance/bundle/deploy/immutable-no-artifacts/databricks.yml.tmpl index 6c41e997af6..0a4210f68ab 100644 --- a/acceptance/bundle/deploy/immutable-no-artifacts/databricks.yml.tmpl +++ b/acceptance/bundle/deploy/immutable-no-artifacts/databricks.yml.tmpl @@ -1,8 +1,8 @@ bundle: name: test-bundle-immutable-no-artifacts-$UNIQUE_NAME -experimental: - immutable_folder: true +workspace: + sync_mode: immutable resources: jobs: diff --git a/acceptance/bundle/deploy/immutable-permissions-change/databricks.yml.tmpl b/acceptance/bundle/deploy/immutable-permissions-change/databricks.yml.tmpl index e23e6f3af5c..a38dfbda648 100644 --- a/acceptance/bundle/deploy/immutable-permissions-change/databricks.yml.tmpl +++ b/acceptance/bundle/deploy/immutable-permissions-change/databricks.yml.tmpl @@ -1,8 +1,8 @@ bundle: name: test-bundle-immutable-perms-$UNIQUE_NAME -experimental: - immutable_folder: true +workspace: + sync_mode: immutable resources: jobs: diff --git a/acceptance/bundle/deploy/immutable/databricks.yml.tmpl b/acceptance/bundle/deploy/immutable/databricks.yml.tmpl index 407a1bbbe6f..8a7c3142916 100644 --- a/acceptance/bundle/deploy/immutable/databricks.yml.tmpl +++ b/acceptance/bundle/deploy/immutable/databricks.yml.tmpl @@ -1,8 +1,8 @@ bundle: name: test-bundle-immutable-$UNIQUE_NAME -experimental: - immutable_folder: true +workspace: + sync_mode: immutable artifacts: python_artifact: diff --git a/acceptance/bundle/dms/immutable-folder/databricks.yml.tmpl b/acceptance/bundle/dms/immutable-folder/databricks.yml.tmpl index dad517c9cfa..e2cd7a9360a 100644 --- a/acceptance/bundle/dms/immutable-folder/databricks.yml.tmpl +++ b/acceptance/bundle/dms/immutable-folder/databricks.yml.tmpl @@ -1,7 +1,8 @@ bundle: name: dms-immutable-folder-$UNIQUE_NAME +workspace: + sync_mode: immutable experimental: - immutable_folder: true deployment_history: true resources: diff --git a/acceptance/bundle/resources/apps/immutable/databricks.yml.tmpl b/acceptance/bundle/resources/apps/immutable/databricks.yml.tmpl index 6b9b696ef01..5db9cc10265 100644 --- a/acceptance/bundle/resources/apps/immutable/databricks.yml.tmpl +++ b/acceptance/bundle/resources/apps/immutable/databricks.yml.tmpl @@ -1,8 +1,8 @@ bundle: name: test-bundle-immutable-app-$UNIQUE_NAME -experimental: - immutable_folder: true +workspace: + sync_mode: immutable resources: apps: diff --git a/acceptance/bundle/validate/immutable_folder_permissions/databricks.yml b/acceptance/bundle/validate/immutable_folder_permissions/databricks.yml index 9d9cb5407eb..a263cdf0df9 100644 --- a/acceptance/bundle/validate/immutable_folder_permissions/databricks.yml +++ b/acceptance/bundle/validate/immutable_folder_permissions/databricks.yml @@ -1,8 +1,8 @@ bundle: name: my-bundle -experimental: - immutable_folder: true +workspace: + sync_mode: immutable permissions: - level: CAN_VIEW diff --git a/acceptance/bundle/validate/immutable_workspace_paths/databricks.yml b/acceptance/bundle/validate/immutable_workspace_paths/databricks.yml index 997dc455b8f..decae70a9f8 100644 --- a/acceptance/bundle/validate/immutable_workspace_paths/databricks.yml +++ b/acceptance/bundle/validate/immutable_workspace_paths/databricks.yml @@ -1,8 +1,8 @@ bundle: name: my-bundle -experimental: - immutable_folder: true +workspace: + sync_mode: immutable resources: jobs: diff --git a/acceptance/bundle/validate/immutable_workspace_paths/output.txt b/acceptance/bundle/validate/immutable_workspace_paths/output.txt index 2da05989620..e7ed88a8087 100644 --- a/acceptance/bundle/validate/immutable_workspace_paths/output.txt +++ b/acceptance/bundle/validate/immutable_workspace_paths/output.txt @@ -12,7 +12,8 @@ "file_path": "${resources.internal_immutable_snapshots.immutable.full_path}/files", "resource_path": "/Workspace/Users/[USERNAME]/.bundle/my-bundle/default/resources", "root_path": "/Workspace/Users/[USERNAME]/.bundle/my-bundle/default", - "state_path": "/Workspace/Users/[USERNAME]/.bundle/my-bundle/default/state" + "state_path": "/Workspace/Users/[USERNAME]/.bundle/my-bundle/default/state", + "sync_mode": "immutable" }, "tasks": [ { diff --git a/acceptance/bundle/validate/sync_mode/databricks.yml b/acceptance/bundle/validate/sync_mode/databricks.yml new file mode 100644 index 00000000000..a18d41239b8 --- /dev/null +++ b/acceptance/bundle/validate/sync_mode/databricks.yml @@ -0,0 +1,50 @@ +bundle: + name: test-bundle + +variables: + mode: + default: immutable + +resources: + jobs: + my_job: + name: my job + tasks: + - task_key: my_task + existing_cluster_id: "0101-120000-aaaaaaaa" + spark_python_task: + python_file: ./src/main.py + +targets: + default: + default: true + overwrite: + workspace: + sync_mode: overwrite + in-place: + workspace: + sync_mode: in-place + immutable: + workspace: + sync_mode: immutable + source_linked_enabled: + presets: + source_linked_deployment: true + source_linked_disabled: + presets: + source_linked_deployment: false + source_linked_with_sync_mode: + presets: + source_linked_deployment: true + workspace: + sync_mode: immutable + invalid: + workspace: + sync_mode: inplace + variable: + workspace: + sync_mode: ${var.mode} + immutable_file_path: + workspace: + sync_mode: immutable + file_path: /Workspace/Shared/files diff --git a/acceptance/bundle/validate/sync_mode/out.test.toml b/acceptance/bundle/validate/sync_mode/out.test.toml new file mode 100644 index 00000000000..a927a5fbc06 --- /dev/null +++ b/acceptance/bundle/validate/sync_mode/out.test.toml @@ -0,0 +1,2 @@ +Cloud = false +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/validate/sync_mode/output.txt b/acceptance/bundle/validate/sync_mode/output.txt new file mode 100644 index 00000000000..62860acdb96 --- /dev/null +++ b/acceptance/bundle/validate/sync_mode/output.txt @@ -0,0 +1,122 @@ + +=== target default +>>> [CLI] bundle validate -t default -o json +{ + "sync_mode": null, + "file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files", + "artifact_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/artifacts", + "python_file": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files/src/main.py" +} + +=== target overwrite +>>> [CLI] bundle validate -t overwrite -o json +{ + "sync_mode": "overwrite", + "file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/overwrite/files", + "artifact_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/overwrite/artifacts", + "python_file": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/overwrite/files/src/main.py" +} + +=== target in-place +>>> [CLI] bundle validate -t in-place -o json +Warning: source-linked deployment is available only in the Databricks Workspace + at workspace.sync_mode + in databricks.yml:26:18 + +{ + "sync_mode": "overwrite", + "file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/in-place/files", + "artifact_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/in-place/artifacts", + "python_file": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/in-place/files/src/main.py" +} + +=== target immutable +>>> [CLI] bundle validate -t immutable -o json +{ + "sync_mode": "immutable", + "file_path": "${resources.internal_immutable_snapshots.immutable.full_path}/files", + "artifact_path": "${resources.internal_immutable_snapshots.immutable.full_path}/artifacts", + "python_file": "${resources.internal_immutable_snapshots.immutable.full_path}/files/src/main.py" +} + +=== target source_linked_enabled +>>> [CLI] bundle validate -t source_linked_enabled -o json +Warning: presets.source_linked_deployment is deprecated, please set workspace.sync_mode to "in-place" instead + at presets.source_linked_deployment + in databricks.yml:32:33 + +Warning: source-linked deployment is available only in the Databricks Workspace + at presets.source_linked_deployment + in databricks.yml:32:33 + +{ + "sync_mode": "overwrite", + "file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/source_linked_enabled/files", + "artifact_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/source_linked_enabled/artifacts", + "python_file": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/source_linked_enabled/files/src/main.py" +} + +=== target source_linked_disabled +>>> [CLI] bundle validate -t source_linked_disabled -o json +Warning: presets.source_linked_deployment is deprecated, please set workspace.sync_mode to "overwrite" instead + at presets.source_linked_deployment + in databricks.yml:35:33 + +{ + "sync_mode": null, + "file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/source_linked_disabled/files", + "artifact_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/source_linked_disabled/artifacts", + "python_file": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/source_linked_disabled/files/src/main.py" +} + +=== target source_linked_with_sync_mode +>>> [CLI] bundle validate -t source_linked_with_sync_mode -o json +Warning: presets.source_linked_deployment is deprecated and ignored because workspace.sync_mode is set, please remove it + at presets.source_linked_deployment + in databricks.yml:38:33 + +{ + "sync_mode": "immutable", + "file_path": "${resources.internal_immutable_snapshots.immutable.full_path}/files", + "artifact_path": "${resources.internal_immutable_snapshots.immutable.full_path}/artifacts", + "python_file": "${resources.internal_immutable_snapshots.immutable.full_path}/files/src/main.py" +} + +=== target invalid +>>> [CLI] bundle validate -t invalid +Error: invalid value "inplace" for workspace.sync_mode (expected one of [overwrite in-place immutable]) + at workspace.sync_mode + in databricks.yml:43:18 + +Name: test-bundle +Target: invalid +Workspace: + User: [USERNAME] + +Found 1 error + +=== target variable +>>> [CLI] bundle validate -t variable +Error: invalid value "${var.mode}" for workspace.sync_mode (expected one of [overwrite in-place immutable]) + at workspace.sync_mode + in databricks.yml:46:18 + +Name: test-bundle +Target: variable +Workspace: + User: [USERNAME] + +Found 1 error + +=== target immutable_file_path +>>> [CLI] bundle validate -t immutable_file_path +Error: workspace.file_path cannot be configured when workspace.sync_mode is immutable + in databricks.yml:50:18 + +Name: test-bundle +Target: immutable_file_path +Workspace: + User: [USERNAME] + Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/immutable_file_path + +Found 1 error diff --git a/acceptance/bundle/validate/sync_mode/script b/acceptance/bundle/validate/sync_mode/script new file mode 100644 index 00000000000..2aabe0e564b --- /dev/null +++ b/acceptance/bundle/validate/sync_mode/script @@ -0,0 +1,9 @@ +for target in default overwrite in-place immutable source_linked_enabled source_linked_disabled source_linked_with_sync_mode; do + title "target $target" + trace $CLI bundle validate -t $target -o json | jq '{sync_mode: .workspace.sync_mode, file_path: .workspace.file_path, artifact_path: .workspace.artifact_path, python_file: .resources.jobs.my_job.tasks[0].spark_python_task.python_file}' +done + +for target in invalid variable immutable_file_path; do + title "target $target" + musterr trace $CLI bundle validate -t $target +done diff --git a/acceptance/bundle/validate/sync_mode/src/main.py b/acceptance/bundle/validate/sync_mode/src/main.py new file mode 100644 index 00000000000..11b15b1a458 --- /dev/null +++ b/acceptance/bundle/validate/sync_mode/src/main.py @@ -0,0 +1 @@ +print("hello") diff --git a/acceptance/bundle/validate/sync_mode/test.toml b/acceptance/bundle/validate/sync_mode/test.toml new file mode 100644 index 00000000000..601384fdf96 --- /dev/null +++ b/acceptance/bundle/validate/sync_mode/test.toml @@ -0,0 +1 @@ +Ignore = [".databricks"] diff --git a/acceptance/bundle/validate/sync_mode_immutable_folder/databricks.yml b/acceptance/bundle/validate/sync_mode_immutable_folder/databricks.yml new file mode 100644 index 00000000000..97ef0ed250d --- /dev/null +++ b/acceptance/bundle/validate/sync_mode_immutable_folder/databricks.yml @@ -0,0 +1,28 @@ +bundle: + name: test-bundle + +experimental: + immutable_folder: true + +resources: + jobs: + my_job: + name: my job + tasks: + - task_key: my_task + existing_cluster_id: "0101-120000-aaaaaaaa" + spark_python_task: + python_file: ./src/main.py + +targets: + default: + default: true + source_linked_disabled: + presets: + source_linked_deployment: false + source_linked_enabled: + presets: + source_linked_deployment: true + with_sync_mode: + workspace: + sync_mode: overwrite diff --git a/acceptance/bundle/validate/sync_mode_immutable_folder/out.test.toml b/acceptance/bundle/validate/sync_mode_immutable_folder/out.test.toml new file mode 100644 index 00000000000..a927a5fbc06 --- /dev/null +++ b/acceptance/bundle/validate/sync_mode_immutable_folder/out.test.toml @@ -0,0 +1,2 @@ +Cloud = false +EnvMatrix.DMS = ["", "true"] diff --git a/acceptance/bundle/validate/sync_mode_immutable_folder/output.txt b/acceptance/bundle/validate/sync_mode_immutable_folder/output.txt new file mode 100644 index 00000000000..87f7b2c1578 --- /dev/null +++ b/acceptance/bundle/validate/sync_mode_immutable_folder/output.txt @@ -0,0 +1,60 @@ + +=== target default +>>> [CLI] bundle validate -t default -o json +Warning: experimental.immutable_folder is deprecated, please set workspace.sync_mode to "immutable" instead + at experimental.immutable_folder + in databricks.yml:5:21 + +{ + "sync_mode": null, + "file_path": "${resources.internal_immutable_snapshots.immutable.full_path}/files", + "artifact_path": "${resources.internal_immutable_snapshots.immutable.full_path}/artifacts", + "python_file": "${resources.internal_immutable_snapshots.immutable.full_path}/files/src/main.py" +} + +=== target source_linked_disabled +>>> [CLI] bundle validate -t source_linked_disabled -o json +Warning: experimental.immutable_folder is deprecated, please set workspace.sync_mode to "immutable" instead + at experimental.immutable_folder + in databricks.yml:5:21 + +Warning: presets.source_linked_deployment is deprecated and ignored because experimental.immutable_folder is set, please remove it + at presets.source_linked_deployment + in databricks.yml:22:33 + +{ + "sync_mode": null, + "file_path": "${resources.internal_immutable_snapshots.immutable.full_path}/files", + "artifact_path": "${resources.internal_immutable_snapshots.immutable.full_path}/artifacts", + "python_file": "${resources.internal_immutable_snapshots.immutable.full_path}/files/src/main.py" +} + +=== target source_linked_enabled +>>> [CLI] bundle validate -t source_linked_enabled -o json +Warning: experimental.immutable_folder is deprecated, please set workspace.sync_mode to "immutable" instead + at experimental.immutable_folder + in databricks.yml:5:21 + +Warning: presets.source_linked_deployment is deprecated and ignored because experimental.immutable_folder is set, please remove it + at presets.source_linked_deployment + in databricks.yml:25:33 + +{ + "sync_mode": null, + "file_path": "${resources.internal_immutable_snapshots.immutable.full_path}/files", + "artifact_path": "${resources.internal_immutable_snapshots.immutable.full_path}/artifacts", + "python_file": "${resources.internal_immutable_snapshots.immutable.full_path}/files/src/main.py" +} + +=== target with_sync_mode +>>> [CLI] bundle validate -t with_sync_mode -o json +Warning: experimental.immutable_folder is deprecated and ignored because workspace.sync_mode is set, please remove it + at experimental.immutable_folder + in databricks.yml:5:21 + +{ + "sync_mode": "overwrite", + "file_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/with_sync_mode/files", + "artifact_path": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/with_sync_mode/artifacts", + "python_file": "/Workspace/Users/[USERNAME]/.bundle/test-bundle/with_sync_mode/files/src/main.py" +} diff --git a/acceptance/bundle/validate/sync_mode_immutable_folder/script b/acceptance/bundle/validate/sync_mode_immutable_folder/script new file mode 100644 index 00000000000..e6686590669 --- /dev/null +++ b/acceptance/bundle/validate/sync_mode_immutable_folder/script @@ -0,0 +1,4 @@ +for target in default source_linked_disabled source_linked_enabled with_sync_mode; do + title "target $target" + trace $CLI bundle validate -t $target -o json | jq '{sync_mode: .workspace.sync_mode, file_path: .workspace.file_path, artifact_path: .workspace.artifact_path, python_file: .resources.jobs.my_job.tasks[0].spark_python_task.python_file}' +done diff --git a/acceptance/bundle/validate/sync_mode_immutable_folder/src/main.py b/acceptance/bundle/validate/sync_mode_immutable_folder/src/main.py new file mode 100644 index 00000000000..11b15b1a458 --- /dev/null +++ b/acceptance/bundle/validate/sync_mode_immutable_folder/src/main.py @@ -0,0 +1 @@ +print("hello") diff --git a/acceptance/bundle/validate/sync_mode_immutable_folder/test.toml b/acceptance/bundle/validate/sync_mode_immutable_folder/test.toml new file mode 100644 index 00000000000..601384fdf96 --- /dev/null +++ b/acceptance/bundle/validate/sync_mode_immutable_folder/test.toml @@ -0,0 +1 @@ +Ignore = [".databricks"] diff --git a/bundle/bundle.go b/bundle/bundle.go index 6c80882134a..da7bcdc5e89 100644 --- a/bundle/bundle.go +++ b/bundle/bundle.go @@ -428,7 +428,32 @@ func (b *Bundle) StateFilenameTerraform(ctx context.Context) (string, string) { return terraformStateFilename, filepath.ToSlash(filepath.Join(b.GetLocalStateDir(ctx), "terraform", terraformStateFilename)) } -// IsImmutableFolder reports whether experimental.immutable_folder is enabled. +// SyncMode returns workspace.sync_mode or, if it is not set, the sync mode selected by the +// deprecated experimental.immutable_folder or presets.source_linked_deployment setting, in that +// order of precedence. It returns "" if none of them is set. +func (b *Bundle) SyncMode() config.SyncMode { + sourceLinked := b.Config.Presets.SourceLinkedDeployment + switch { + case b.Config.Workspace.SyncMode != "": + return b.Config.Workspace.SyncMode + case b.Config.Experimental != nil && b.Config.Experimental.ImmutableFolder: + return config.SyncModeImmutable + case config.IsExplicitlyEnabled(sourceLinked): + return config.SyncModeInPlace + case config.IsExplicitlyDisabled(sourceLinked): + return config.SyncModeOverwrite + default: + return "" + } +} + +// IsImmutableFolder reports whether bundle files and artifacts are deployed as an immutable snapshot. func (b *Bundle) IsImmutableFolder() bool { - return b.Config.Experimental != nil && b.Config.Experimental.ImmutableFolder + return b.SyncMode() == config.SyncModeImmutable +} + +// IsSourceLinkedDeployment reports whether deployed resources reference the source files in the +// workspace directly instead of uploaded copies. +func (b *Bundle) IsSourceLinkedDeployment() bool { + return b.SyncMode() == config.SyncModeInPlace } diff --git a/bundle/bundle_test.go b/bundle/bundle_test.go index 9bd667afe62..69c8fa5bdcb 100644 --- a/bundle/bundle_test.go +++ b/bundle/bundle_test.go @@ -203,3 +203,37 @@ func TestClearWorkspaceClient(t *testing.T) { assert.Contains(t, err2.Error(), "profile-B", "expected re-execution to pick up new profile") assert.NotContains(t, err2.Error(), "profile-A", "stale cached error should not appear") } + +func TestBundleSyncMode(t *testing.T) { + enabled := true + disabled := false + + tests := []struct { + name string + syncMode config.SyncMode + sourceLinked *bool + immutable bool + expected config.SyncMode + }{ + {name: "nothing set", expected: ""}, + {name: "sync_mode", syncMode: config.SyncModeInPlace, expected: config.SyncModeInPlace}, + {name: "deprecated source_linked_deployment enabled", sourceLinked: &enabled, expected: config.SyncModeInPlace}, + {name: "deprecated source_linked_deployment disabled", sourceLinked: &disabled, expected: config.SyncModeOverwrite}, + {name: "deprecated immutable_folder", immutable: true, expected: config.SyncModeImmutable}, + {name: "deprecated immutable_folder over source_linked_deployment", sourceLinked: &enabled, immutable: true, expected: config.SyncModeImmutable}, + {name: "sync_mode over deprecated settings", syncMode: config.SyncModeOverwrite, sourceLinked: &enabled, immutable: true, expected: config.SyncModeOverwrite}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &Bundle{Config: config.Root{ + Workspace: config.Workspace{SyncMode: tt.syncMode}, + Presets: config.Presets{SourceLinkedDeployment: tt.sourceLinked}, + Experimental: &config.Experimental{ImmutableFolder: tt.immutable}, + }} + assert.Equal(t, tt.expected, b.SyncMode()) + assert.Equal(t, tt.expected == config.SyncModeImmutable, b.IsImmutableFolder()) + assert.Equal(t, tt.expected == config.SyncModeInPlace, b.IsSourceLinkedDeployment()) + }) + } +} diff --git a/bundle/config/experimental.go b/bundle/config/experimental.go index 6fd5511b79b..58b7b5f125c 100644 --- a/bundle/config/experimental.go +++ b/bundle/config/experimental.go @@ -8,6 +8,8 @@ type Experimental struct { // the deployment calls /api/2.0/repos/snapshots with a zip of all files and sets // workspace.file_path and workspace.artifact_path to the returned content-addressed // path. + // + // ImmutableFolder is deprecated, use workspace.sync_mode: immutable instead. ImmutableFolder bool `json:"immutable_folder,omitempty"` // By default Python wheel tasks deployed as is to Databricks platform. diff --git a/bundle/config/mutator/apply_source_linked_deployment_preset.go b/bundle/config/mutator/apply_source_linked_deployment_preset.go deleted file mode 100644 index 695edabc68a..00000000000 --- a/bundle/config/mutator/apply_source_linked_deployment_preset.go +++ /dev/null @@ -1,86 +0,0 @@ -package mutator - -import ( - "context" - "strings" - - "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config" - "github.com/databricks/cli/libs/dbr" - "github.com/databricks/cli/libs/diag" - "github.com/databricks/cli/libs/structs/structpath" -) - -type applySourceLinkedDeploymentPreset struct{} - -// Apply source-linked deployment preset -func ApplySourceLinkedDeploymentPreset() *applySourceLinkedDeploymentPreset { - return &applySourceLinkedDeploymentPreset{} -} - -func (m *applySourceLinkedDeploymentPreset) Name() string { - return "ApplySourceLinkedDeploymentPreset" -} - -func (m *applySourceLinkedDeploymentPreset) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - if config.IsExplicitlyDisabled(b.Config.Presets.SourceLinkedDeployment) { - return nil - } - - var diags diag.Diagnostics - isDatabricksWorkspace := dbr.RunsOnRuntime(ctx) && strings.HasPrefix(b.SyncRootPath, "/Workspace/") - target := b.Config.Bundle.Target - - if config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) { - if !isDatabricksWorkspace { - path := structpath.NewPath(nil, "targets", target, "presets", "source_linked_deployment") - diags = diags.Append( - diag.Diagnostic{ - Severity: diag.Warning, - Summary: "source-linked deployment is available only in the Databricks Workspace", - Paths: []*structpath.PathNode{path}, - Locations: b.Config.GetLocationsOf(path.SkipPrefix(2)), - }, - ) - - disabled := false - b.Config.Presets.SourceLinkedDeployment = &disabled - return diags - } - - b.Metrics.AddBoolValue("source_linked_set_for_non_development", b.Config.Bundle.Mode != config.Development) - - if b.Config.Bundle.Mode != config.Development { - path := structpath.NewPath(nil, "targets", target, "presets", "source_linked_deployment") - diags = diags.Append( - diag.Diagnostic{ - Severity: diag.Warning, - Summary: "source-linked deployment in non-development mode is deprecated and will not be supported in a future release", - Paths: []*structpath.PathNode{path}, - Locations: b.Config.GetLocationsOf(path.SkipPrefix(2)), - }, - ) - } - } - - if isDatabricksWorkspace && b.Config.Bundle.Mode == config.Development { - enabled := true - b.Config.Presets.SourceLinkedDeployment = &enabled - } - - // This mutator runs before workspace paths are defaulted so it's safe to check for the user-defined value - if b.Config.Workspace.FilePath != "" && config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) { - path := structpath.NewPath(nil, "workspace", "file_path") - diags = diags.Append( - diag.Diagnostic{ - Severity: diag.Warning, - Summary: "workspace.file_path setting will be ignored in source-linked deployment mode", - Detail: "In source-linked deployment files are not copied to the destination and resources use source files instead", - Paths: []*structpath.PathNode{path}, - Locations: b.Config.GetLocationsOf(path), - }, - ) - } - - return diags -} diff --git a/bundle/config/mutator/apply_source_linked_deployment_preset_test.go b/bundle/config/mutator/apply_source_linked_deployment_preset_test.go deleted file mode 100644 index 8d50788a3b3..00000000000 --- a/bundle/config/mutator/apply_source_linked_deployment_preset_test.go +++ /dev/null @@ -1,152 +0,0 @@ -package mutator_test - -import ( - "context" - "runtime" - "testing" - - "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config" - "github.com/databricks/cli/bundle/config/mutator" - "github.com/databricks/cli/bundle/config/resources" - "github.com/databricks/cli/bundle/internal/bundletest" - "github.com/databricks/cli/libs/dbr" - "github.com/databricks/cli/libs/diag" - "github.com/stretchr/testify/require" -) - -func TestApplyPresetsSourceLinkedDeployment(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip("this test is not applicable on Windows because source-linked mode works only in the Databricks Workspace") - } - - testContext := t.Context() - enabled := true - disabled := false - workspacePath := "/Workspace/user.name@company.com" - - tests := []struct { - name string - ctx context.Context - mutateBundle func(b *bundle.Bundle) - initialValue *bool - expectedValue *bool - expectedWarning string - expectedError string - }{ - { - name: "preset enabled, bundle in Workspace, databricks runtime", - ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), - initialValue: &enabled, - expectedValue: &enabled, - }, - { - name: "preset enabled, bundle not in Workspace, databricks runtime", - ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), - mutateBundle: func(b *bundle.Bundle) { - b.SyncRootPath = "/Users/user.name@company.com" - }, - initialValue: &enabled, - expectedValue: &disabled, - expectedWarning: "source-linked deployment is available only in the Databricks Workspace", - }, - { - name: "preset enabled, bundle in Workspace, not databricks runtime", - ctx: dbr.MockRuntime(testContext, dbr.Environment{}), - initialValue: &enabled, - expectedValue: &disabled, - expectedWarning: "source-linked deployment is available only in the Databricks Workspace", - }, - { - name: "preset disabled, bundle in Workspace, databricks runtime", - ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), - initialValue: &disabled, - expectedValue: &disabled, - }, - { - name: "preset nil, bundle in Workspace, databricks runtime", - ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), - initialValue: nil, - expectedValue: nil, - }, - { - name: "preset nil, dev mode true, bundle in Workspace, databricks runtime", - ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), - mutateBundle: func(b *bundle.Bundle) { - b.Config.Bundle.Mode = config.Development - }, - initialValue: nil, - expectedValue: &enabled, - }, - { - name: "preset enabled, workspace.file_path is defined by user", - ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), - mutateBundle: func(b *bundle.Bundle) { - b.Config.Workspace.FilePath = "file_path" - b.Config.Bundle.Mode = config.Development - }, - initialValue: &enabled, - expectedValue: &enabled, - expectedWarning: "workspace.file_path setting will be ignored in source-linked deployment mode", - }, - { - name: "preset enabled, apps is defined by user", - ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), - mutateBundle: func(b *bundle.Bundle) { - b.Config.Resources.Apps = map[string]*resources.App{ - "app": {}, - } - b.Config.Bundle.Mode = config.Development - }, - initialValue: &enabled, - expectedValue: &enabled, - }, - { - name: "preset enabled, production mode, bundle in Workspace, databricks runtime", - ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), - mutateBundle: func(b *bundle.Bundle) { - b.Config.Bundle.Mode = config.Production - }, - initialValue: &enabled, - expectedValue: &enabled, - expectedWarning: "source-linked deployment in non-development mode is deprecated and will not be supported in a future release", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - b := &bundle.Bundle{ - SyncRootPath: workspacePath, - Config: config.Root{ - Presets: config.Presets{ - SourceLinkedDeployment: tt.initialValue, - }, - }, - } - - if tt.mutateBundle != nil { - tt.mutateBundle(b) - } - - bundletest.SetLocation(b, "presets.source_linked_deployment", []diag.Location{{File: "databricks.yml"}}) - bundletest.SetLocation(b, "workspace.file_path", []diag.Location{{File: "databricks.yml"}}) - - diags := bundle.Apply(tt.ctx, b, mutator.ApplySourceLinkedDeploymentPreset()) - if diags.HasError() && tt.expectedError == "" { - t.Fatalf("unexpected error: %v", diags) - } - - if tt.expectedWarning != "" { - require.Equal(t, tt.expectedWarning, diags[0].Summary) - require.NotEmpty(t, diags[0].Locations) - } - - if tt.expectedError != "" { - require.Equal(t, tt.expectedError, diags[0].Summary) - require.NotEmpty(t, diags[0].Locations) - } - - require.Equal(t, tt.expectedValue, b.Config.Presets.SourceLinkedDeployment) - }) - } -} diff --git a/bundle/config/mutator/apply_sync_mode.go b/bundle/config/mutator/apply_sync_mode.go new file mode 100644 index 00000000000..f362cce2905 --- /dev/null +++ b/bundle/config/mutator/apply_sync_mode.go @@ -0,0 +1,151 @@ +package mutator + +import ( + "context" + "fmt" + "slices" + "strings" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/libs/dbr" + "github.com/databricks/cli/libs/diag" + "github.com/databricks/cli/libs/structs/structpath" +) + +var ( + syncModePath = structpath.MustParsePath("workspace.sync_mode") + sourceLinkedDeploymentPath = structpath.MustParsePath("presets.source_linked_deployment") + immutableFolderPath = structpath.MustParsePath("experimental.immutable_folder") +) + +type applySyncMode struct{} + +// ApplySyncMode validates workspace.sync_mode and warns about the deprecated settings it replaces. +// It also checks that in-place is supported and makes it the default for development deployments +// from the Databricks Workspace. +func ApplySyncMode() *applySyncMode { + return &applySyncMode{} +} + +func (m *applySyncMode) Name() string { + return "ApplySyncMode" +} + +func (m *applySyncMode) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { + diags := warnDeprecatedSyncMode(b) + + // Variable references are rejected too: the sync mode is needed before variables are resolved. + if mode := b.Config.Workspace.SyncMode; mode != "" && !slices.Contains(mode.Values(), mode) { + return diags.Append(diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("invalid value %q for workspace.sync_mode (expected one of %v)", mode, mode.Values()), + Paths: []*structpath.PathNode{syncModePath}, + Locations: b.Config.GetLocationsOf(syncModePath), + }) + } + + // An explicit sync mode other than in-place opts out of the in-place default below. + mode := b.SyncMode() + if mode != "" && mode != config.SyncModeInPlace { + return diags + } + + isDatabricksWorkspace := dbr.RunsOnRuntime(ctx) && strings.HasPrefix(b.SyncRootPath, "/Workspace/") + + if mode == config.SyncModeInPlace { + // In-place is set by workspace.sync_mode or the deprecated presets.source_linked_deployment. + path := syncModePath + if b.Config.Workspace.SyncMode == "" { + path = sourceLinkedDeploymentPath + } + + if !isDatabricksWorkspace { + diags = diags.Append( + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "source-linked deployment is available only in the Databricks Workspace", + Paths: []*structpath.PathNode{path}, + Locations: b.Config.GetLocationsOf(path), + }, + ) + + // workspace.sync_mode takes precedence over the deprecated setting as well. + b.Config.Workspace.SyncMode = config.SyncModeOverwrite + return diags + } + + b.Metrics.AddBoolValue("source_linked_set_for_non_development", b.Config.Bundle.Mode != config.Development) + + if b.Config.Bundle.Mode != config.Development { + diags = diags.Append( + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "source-linked deployment in non-development mode is deprecated and will not be supported in a future release", + Paths: []*structpath.PathNode{path}, + Locations: b.Config.GetLocationsOf(path), + }, + ) + } + } + + if isDatabricksWorkspace && b.Config.Bundle.Mode == config.Development { + b.Config.Workspace.SyncMode = config.SyncModeInPlace + } + + // This mutator runs before workspace paths are defaulted so it's safe to check for the user-defined value + if b.Config.Workspace.FilePath != "" && b.IsSourceLinkedDeployment() { + path := structpath.NewPath(nil, "workspace", "file_path") + diags = diags.Append( + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "workspace.file_path setting will be ignored in source-linked deployment mode", + Detail: "In source-linked deployment files are not copied to the destination and resources use source files instead", + Paths: []*structpath.PathNode{path}, + Locations: b.Config.GetLocationsOf(path), + }, + ) + } + + return diags +} + +// warnDeprecatedSyncMode warns about the deprecated settings that workspace.sync_mode replaces. +// They keep working, with the precedence of [bundle.Bundle.SyncMode]. +func warnDeprecatedSyncMode(b *bundle.Bundle) diag.Diagnostics { + var diags diag.Diagnostics + + // override is the highest-precedence setting that is set so far; it overrides the ones below it. + var override *structpath.PathNode + if b.Config.Workspace.SyncMode != "" { + override = syncModePath + } + + if b.Config.Experimental != nil && b.Config.Experimental.ImmutableFolder { + diags = diags.Append(deprecatedSyncModeWarning(b, immutableFolderPath, override)) + if override == nil { + override = immutableFolderPath + } + } + + if b.Config.Presets.SourceLinkedDeployment != nil { + diags = diags.Append(deprecatedSyncModeWarning(b, sourceLinkedDeploymentPath, override)) + } + + return diags +} + +// deprecatedSyncModeWarning warns about the deprecated setting at path. A non-nil override is +// the setting that takes precedence over it. +func deprecatedSyncModeWarning(b *bundle.Bundle, path, override *structpath.PathNode) diag.Diagnostic { + summary := fmt.Sprintf("%s is deprecated, please set workspace.sync_mode to %q instead", path, b.SyncMode()) + if override != nil { + summary = fmt.Sprintf("%s is deprecated and ignored because %s is set, please remove it", path, override) + } + return diag.Diagnostic{ + Severity: diag.Warning, + Summary: summary, + Paths: []*structpath.PathNode{path}, + Locations: b.Config.GetLocationsOf(path), + } +} diff --git a/bundle/config/mutator/apply_sync_mode_test.go b/bundle/config/mutator/apply_sync_mode_test.go new file mode 100644 index 00000000000..8a2a2a98640 --- /dev/null +++ b/bundle/config/mutator/apply_sync_mode_test.go @@ -0,0 +1,299 @@ +package mutator_test + +import ( + "context" + "runtime" + "testing" + + "github.com/databricks/cli/bundle" + "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/mutator" + "github.com/databricks/cli/bundle/config/resources" + "github.com/databricks/cli/bundle/internal/bundletest" + "github.com/databricks/cli/libs/dbr" + "github.com/databricks/cli/libs/diag" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestApplySyncMode(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("this test is not applicable on Windows because source-linked mode works only in the Databricks Workspace") + } + + testContext := t.Context() + enabled := true + disabled := false + workspacePath := "/Workspace/user.name@company.com" + + tests := []struct { + name string + ctx context.Context + mutateBundle func(b *bundle.Bundle) + initialValue config.SyncMode + expectedValue config.SyncMode + expectedWarning string + expectedError string + }{ + { + name: "in-place, bundle in Workspace, databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + initialValue: config.SyncModeInPlace, + expectedValue: config.SyncModeInPlace, + }, + { + name: "in-place, bundle not in Workspace, databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.SyncRootPath = "/Users/user.name@company.com" + }, + initialValue: config.SyncModeInPlace, + expectedValue: config.SyncModeOverwrite, + expectedWarning: "source-linked deployment is available only in the Databricks Workspace", + }, + { + name: "in-place, bundle in Workspace, not databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{}), + initialValue: config.SyncModeInPlace, + expectedValue: config.SyncModeOverwrite, + expectedWarning: "source-linked deployment is available only in the Databricks Workspace", + }, + { + name: "overwrite, bundle in Workspace, databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + initialValue: config.SyncModeOverwrite, + expectedValue: config.SyncModeOverwrite, + }, + { + name: "unset, bundle in Workspace, databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + initialValue: "", + expectedValue: "", + }, + { + name: "unset, dev mode true, bundle in Workspace, databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Bundle.Mode = config.Development + }, + initialValue: "", + expectedValue: config.SyncModeInPlace, + }, + { + name: "overwrite, dev mode true, bundle in Workspace, databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Bundle.Mode = config.Development + }, + initialValue: config.SyncModeOverwrite, + expectedValue: config.SyncModeOverwrite, + }, + { + name: "immutable, dev mode true, bundle in Workspace, databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Bundle.Mode = config.Development + }, + initialValue: config.SyncModeImmutable, + expectedValue: config.SyncModeImmutable, + }, + { + name: "in-place, workspace.file_path is defined by user", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Workspace.FilePath = "file_path" + b.Config.Bundle.Mode = config.Development + }, + initialValue: config.SyncModeInPlace, + expectedValue: config.SyncModeInPlace, + expectedWarning: "workspace.file_path setting will be ignored in source-linked deployment mode", + }, + { + name: "in-place, apps is defined by user", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Resources.Apps = map[string]*resources.App{ + "app": {}, + } + b.Config.Bundle.Mode = config.Development + }, + initialValue: config.SyncModeInPlace, + expectedValue: config.SyncModeInPlace, + }, + { + name: "in-place, production mode, bundle in Workspace, databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Bundle.Mode = config.Production + }, + initialValue: config.SyncModeInPlace, + expectedValue: config.SyncModeInPlace, + expectedWarning: "source-linked deployment in non-development mode is deprecated and will not be supported in a future release", + }, + { + name: "invalid value", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + initialValue: "inplace", + expectedValue: "inplace", + expectedError: `invalid value "inplace" for workspace.sync_mode (expected one of [overwrite in-place immutable])`, + }, + { + name: "deprecated source_linked_deployment enabled, bundle in Workspace, databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Presets.SourceLinkedDeployment = &enabled + }, + expectedValue: config.SyncModeInPlace, + expectedWarning: `presets.source_linked_deployment is deprecated, please set workspace.sync_mode to "in-place" instead`, + }, + { + name: "deprecated source_linked_deployment enabled, not databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Presets.SourceLinkedDeployment = &enabled + }, + expectedValue: config.SyncModeOverwrite, + expectedWarning: `presets.source_linked_deployment is deprecated, please set workspace.sync_mode to "in-place" instead`, + }, + { + name: "deprecated source_linked_deployment disabled, dev mode true, bundle in Workspace, databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Presets.SourceLinkedDeployment = &disabled + b.Config.Bundle.Mode = config.Development + }, + expectedValue: config.SyncModeOverwrite, + expectedWarning: `presets.source_linked_deployment is deprecated, please set workspace.sync_mode to "overwrite" instead`, + }, + { + name: "deprecated immutable_folder", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Experimental = &config.Experimental{ImmutableFolder: true} + }, + expectedValue: config.SyncModeImmutable, + expectedWarning: `experimental.immutable_folder is deprecated, please set workspace.sync_mode to "immutable" instead`, + }, + { + name: "deprecated immutable_folder, dev mode true, bundle in Workspace, databricks runtime", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Experimental = &config.Experimental{ImmutableFolder: true} + b.Config.Bundle.Mode = config.Development + }, + expectedValue: config.SyncModeImmutable, + expectedWarning: `experimental.immutable_folder is deprecated, please set workspace.sync_mode to "immutable" instead`, + }, + { + name: "deprecated immutable_folder, deprecated source_linked_deployment disabled", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Experimental = &config.Experimental{ImmutableFolder: true} + b.Config.Presets.SourceLinkedDeployment = &disabled + }, + expectedValue: config.SyncModeImmutable, + expectedWarning: "presets.source_linked_deployment is deprecated and ignored because experimental.immutable_folder is set, please remove it", + }, + { + name: "deprecated immutable_folder, deprecated source_linked_deployment enabled", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Experimental = &config.Experimental{ImmutableFolder: true} + b.Config.Presets.SourceLinkedDeployment = &enabled + }, + expectedValue: config.SyncModeImmutable, + expectedWarning: "presets.source_linked_deployment is deprecated and ignored because experimental.immutable_folder is set, please remove it", + }, + { + name: "deprecated source_linked_deployment with sync_mode", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Presets.SourceLinkedDeployment = &enabled + }, + initialValue: config.SyncModeOverwrite, + expectedValue: config.SyncModeOverwrite, + expectedWarning: "presets.source_linked_deployment is deprecated and ignored because workspace.sync_mode is set, please remove it", + }, + { + name: "deprecated immutable_folder with sync_mode", + ctx: dbr.MockRuntime(testContext, dbr.Environment{IsDbr: true, Version: "15.4"}), + mutateBundle: func(b *bundle.Bundle) { + b.Config.Experimental = &config.Experimental{ImmutableFolder: true} + }, + initialValue: config.SyncModeOverwrite, + expectedValue: config.SyncModeOverwrite, + expectedWarning: "experimental.immutable_folder is deprecated and ignored because workspace.sync_mode is set, please remove it", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + b := &bundle.Bundle{ + SyncRootPath: workspacePath, + Config: config.Root{ + Workspace: config.Workspace{ + SyncMode: tt.initialValue, + }, + }, + } + + if tt.mutateBundle != nil { + tt.mutateBundle(b) + } + + bundletest.SetLocation(b, "workspace.sync_mode", []diag.Location{{File: "databricks.yml"}}) + bundletest.SetLocation(b, "presets.source_linked_deployment", []diag.Location{{File: "databricks.yml"}}) + bundletest.SetLocation(b, "experimental.immutable_folder", []diag.Location{{File: "databricks.yml"}}) + bundletest.SetLocation(b, "workspace.file_path", []diag.Location{{File: "databricks.yml"}}) + + diags := bundle.Apply(tt.ctx, b, mutator.ApplySyncMode()) + if diags.HasError() && tt.expectedError == "" { + t.Fatalf("unexpected error: %v", diags) + } + + if tt.expectedWarning != "" { + requireDiagnosticWithLocation(t, diags, diag.Warning, tt.expectedWarning) + } + + if tt.expectedError != "" { + requireDiagnosticWithLocation(t, diags, diag.Error, tt.expectedError) + } + + require.Equal(t, tt.expectedValue, b.SyncMode()) + }) + } +} + +// requireDiagnosticWithLocation requires diags to contain a diagnostic with the given severity +// and summary that points at a location. +func requireDiagnosticWithLocation(t *testing.T, diags diag.Diagnostics, severity diag.Severity, summary string) { + t.Helper() + for _, d := range diags { + if d.Severity == severity && d.Summary == summary { + require.NotEmpty(t, d.Locations) + return + } + } + t.Fatalf("expected diagnostic %q, got %v", summary, diags) +} + +func TestApplySyncModeDeprecatedSourceLinkedDeploymentLocations(t *testing.T) { + enabled := true + b := &bundle.Bundle{ + SyncRootPath: "/Workspace/user.name@company.com", + Config: config.Root{ + Presets: config.Presets{ + SourceLinkedDeployment: &enabled, + }, + }, + } + bundletest.SetLocation(b, "presets.source_linked_deployment", []diag.Location{{File: "databricks.yml", Line: 5}}) + + diags := bundle.Apply(dbr.MockRuntime(t.Context(), dbr.Environment{}), b, mutator.ApplySyncMode()) + require.Len(t, diags, 2) + + // The in-place warning points at the deprecated setting that enabled it. + assert.Equal(t, "source-linked deployment is available only in the Databricks Workspace", diags[1].Summary) + assert.Equal(t, "presets.source_linked_deployment", diags[1].Paths[0].String()) + assert.Equal(t, []diag.Location{{File: "databricks.yml", Line: 5}}, diags[1].Locations) +} diff --git a/bundle/config/mutator/override_immutable_folder.go b/bundle/config/mutator/override_immutable_folder.go index a033ca12e5f..7fad1ab2aa0 100644 --- a/bundle/config/mutator/override_immutable_folder.go +++ b/bundle/config/mutator/override_immutable_folder.go @@ -11,7 +11,7 @@ import ( type overrideImmutableFolder struct{} -// OverrideImmutableFolder sets bundle.deployment.immutable_folder to true +// OverrideImmutableFolder sets workspace.sync_mode to immutable // if the __TEST_DATABRICKS_IMMUTABLE_FOLDER environment variable is non-empty. // This allows running the acceptance test suite against the immutable folder // code path without modifying any databricks.yml files. @@ -27,9 +27,6 @@ func (m *overrideImmutableFolder) Apply(ctx context.Context, b *bundle.Bundle) d if env.Get(ctx, "__TEST_DATABRICKS_IMMUTABLE_FOLDER") == "" { return nil } - if b.Config.Experimental == nil { - b.Config.Experimental = &config.Experimental{} - } - b.Config.Experimental.ImmutableFolder = true + b.Config.Workspace.SyncMode = config.SyncModeImmutable return nil } diff --git a/bundle/config/mutator/override_immutable_folder_test.go b/bundle/config/mutator/override_immutable_folder_test.go index a675974e7ed..cfc7eb4b89f 100644 --- a/bundle/config/mutator/override_immutable_folder_test.go +++ b/bundle/config/mutator/override_immutable_folder_test.go @@ -17,7 +17,7 @@ func TestOverrideImmutableFolderNotSet(t *testing.T) { b := &bundle.Bundle{Config: config.Root{}} diags := bundle.Apply(ctx, b, mutator.OverrideImmutableFolder()) require.NoError(t, diags.Error()) - assert.True(t, b.Config.Experimental == nil || !b.Config.Experimental.ImmutableFolder) + assert.Empty(t, b.Config.Workspace.SyncMode) } func TestOverrideImmutableFolderSet(t *testing.T) { @@ -26,18 +26,16 @@ func TestOverrideImmutableFolderSet(t *testing.T) { b := &bundle.Bundle{Config: config.Root{}} diags := bundle.Apply(ctx, b, mutator.OverrideImmutableFolder()) require.NoError(t, diags.Error()) - require.NotNil(t, b.Config.Experimental) - assert.True(t, b.Config.Experimental.ImmutableFolder) + assert.Equal(t, config.SyncModeImmutable, b.Config.Workspace.SyncMode) } -func TestOverrideImmutableFolderAlreadyTrue(t *testing.T) { +func TestOverrideImmutableFolderAlreadyImmutable(t *testing.T) { t.Parallel() ctx := env.Set(t.Context(), "__TEST_DATABRICKS_IMMUTABLE_FOLDER", "") b := &bundle.Bundle{Config: config.Root{}} - b.Config.Experimental = &config.Experimental{ImmutableFolder: true} + b.Config.Workspace.SyncMode = config.SyncModeImmutable diags := bundle.Apply(ctx, b, mutator.OverrideImmutableFolder()) require.NoError(t, diags.Error()) - // Existing true value must not be cleared when the env var is absent. - require.NotNil(t, b.Config.Experimental) - assert.True(t, b.Config.Experimental.ImmutableFolder) + // Existing immutable value must not be cleared when the env var is absent. + assert.Equal(t, config.SyncModeImmutable, b.Config.Workspace.SyncMode) } diff --git a/bundle/config/mutator/resolve_variable_references.go b/bundle/config/mutator/resolve_variable_references.go index 257309c55f8..42c22b61533 100644 --- a/bundle/config/mutator/resolve_variable_references.go +++ b/bundle/config/mutator/resolve_variable_references.go @@ -10,7 +10,6 @@ import ( "github.com/databricks/cli/libs/dyn/merge" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/variable" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" @@ -113,7 +112,7 @@ func ResolveVolumePathReferencesOnlyResources() bundle.Mutator { } func lookup(v dyn.Value, path dyn.Path, b *bundle.Bundle) (dyn.Value, error) { - if config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) { + if b.IsSourceLinkedDeployment() { if path.String() == "workspace.file_path" { return dyn.V(b.SyncRootPath), nil } diff --git a/bundle/config/mutator/translate_paths.go b/bundle/config/mutator/translate_paths.go index f44c7235043..956d010ec95 100644 --- a/bundle/config/mutator/translate_paths.go +++ b/bundle/config/mutator/translate_paths.go @@ -15,7 +15,6 @@ import ( "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" @@ -329,12 +328,12 @@ func applyTranslations(ctx context.Context, b *bundle.Bundle, t *translateContex if loc := b.Config.GetLocation("workspace.file_path"); loc.File != "" { return diag.Diagnostics{{ Severity: diag.Error, - Summary: "workspace.file_path cannot be configured when experimental.immutable_folder is enabled", + Summary: "workspace.file_path cannot be configured when workspace.sync_mode is immutable", Locations: []diag.Location{loc}, }} } t.remoteRoot = resources.SnapshotFullPathRef + "/files" - case config.IsExplicitlyEnabled(t.b.Config.Presets.SourceLinkedDeployment): + case t.b.IsSourceLinkedDeployment(): t.remoteRoot = t.b.SyncRootPath default: t.remoteRoot = t.b.Config.Workspace.FilePath diff --git a/bundle/config/presets.go b/bundle/config/presets.go index a831d83c9c2..1e1f6ff9a00 100644 --- a/bundle/config/presets.go +++ b/bundle/config/presets.go @@ -22,6 +22,8 @@ type Presets struct { // SourceLinkedDeployment indicates whether source-linked deployment is enabled. Works only in Databricks Workspace // When set to true, resources created during deployment will point to source files in the workspace instead of their workspace copies. // File synchronization to ${workspace.file_path} is skipped. + // + // SourceLinkedDeployment is deprecated, use workspace.sync_mode: in-place instead. SourceLinkedDeployment *bool `json:"source_linked_deployment,omitempty"` // ArtifactsDynamicVersion enables dynamic_version on every artifact. diff --git a/bundle/config/workspace.go b/bundle/config/workspace.go index b3311bf07d2..e1448dc7212 100644 --- a/bundle/config/workspace.go +++ b/bundle/config/workspace.go @@ -79,6 +79,36 @@ type Workspace struct { // Remote workspace path for deployment state. // This defaults to "${workspace.root}/state". StatePath string `json:"state_path,omitempty"` + + // SyncMode determines how bundle files are made available to deployed resources. + // Unset means [SyncModeOverwrite], except for development deployments from the + // Databricks Workspace, which default to [SyncModeInPlace]. + SyncMode SyncMode `json:"sync_mode,omitempty"` +} + +type SyncMode string + +const ( + // SyncModeOverwrite uploads bundle files to [Workspace.FilePath], replacing the + // files of the previous deployment. + SyncModeOverwrite SyncMode = "overwrite" + + // SyncModeInPlace skips the upload: deployed resources reference the source files + // in the workspace directly (source-linked deployment). Works only in the Databricks Workspace. + SyncModeInPlace SyncMode = "in-place" + + // SyncModeImmutable uploads bundle files and artifacts as a single immutable snapshot + // and points [Workspace.FilePath] and [Workspace.ArtifactPath] at it. + SyncModeImmutable SyncMode = "immutable" +) + +// Values returns all valid SyncMode values. +func (SyncMode) Values() []SyncMode { + return []SyncMode{ + SyncModeOverwrite, + SyncModeInPlace, + SyncModeImmutable, + } } type User struct { diff --git a/bundle/configsync/variables.go b/bundle/configsync/variables.go index 442224158f5..2a0ac89cc92 100644 --- a/bundle/configsync/variables.go +++ b/bundle/configsync/variables.go @@ -6,7 +6,6 @@ import ( "strings" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/config/mutator" "github.com/databricks/cli/bundle/config/mutator/resourcemutator" "github.com/databricks/cli/libs/dyn" @@ -64,7 +63,7 @@ func RestoreVariableReferences(ctx context.Context, b *bundle.Bundle, fieldChang // Without this, substring matching against the typed value misses the // actual deployed path and variables are lost on Replace. Keep this in // sync with mutator.lookup if new overrides are added there. - if config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) { + if b.IsSourceLinkedDeployment() { fpPath := dyn.NewPath(dyn.Key("workspace"), dyn.Key("file_path")) if updated, err := dyn.SetByPath(resolved, fpPath, dyn.V(b.SyncRootPath)); err == nil { resolved = updated diff --git a/bundle/deploy/files/upload.go b/bundle/deploy/files/upload.go index a74876ba4d1..b0d320bc994 100644 --- a/bundle/deploy/files/upload.go +++ b/bundle/deploy/files/upload.go @@ -7,7 +7,6 @@ import ( "io/fs" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/permissions" "github.com/databricks/cli/libs/cmdio" "github.com/databricks/cli/libs/diag" @@ -24,7 +23,7 @@ func (m *upload) Name() string { } func (m *upload) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { - if config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) { + if b.IsSourceLinkedDeployment() { cmdio.LogProgress(ctx, "Source-linked deployment is enabled. Deployed resources reference the source files in your working tree instead of separate copies.") return nil } diff --git a/bundle/deploy/metadata/compute.go b/bundle/deploy/metadata/compute.go index 1fa4c3bf2d9..d845ec05bed 100644 --- a/bundle/deploy/metadata/compute.go +++ b/bundle/deploy/metadata/compute.go @@ -122,7 +122,7 @@ func (m *compute) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics // Set file upload destination of the bundle in metadata b.Metadata.Config.Workspace.FilePath = b.Config.Workspace.FilePath // In source-linked deployment files are not copied and resources use source files, therefore we use sync path as file path in metadata - if config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) { + if b.IsSourceLinkedDeployment() { b.Metadata.Config.Workspace.FilePath = b.SyncRootPath b.Metadata.Config.Presets.SourceLinkedDeployment = true } diff --git a/bundle/internal/schema/annotations.yml b/bundle/internal/schema/annotations.yml index 1a5c4ccbfdd..e57d8edaede 100644 --- a/bundle/internal/schema/annotations.yml +++ b/bundle/internal/schema/annotations.yml @@ -165,6 +165,8 @@ experimental: "immutable_folder": "description": |- Whether to deploy bundle files and artifacts as a single immutable snapshot. When true, all files are packaged into a content-addressed archive and workspace.file_path and workspace.artifact_path are set to the resulting location. + "deprecation_message": |- + Deprecated: please set workspace.sync_mode to immutable instead "pydabs": "description": |- The PyDABs configuration. @@ -2245,6 +2247,8 @@ targets: "source_linked_deployment": "description": |- Whether to link the deployment to the bundle source. + "deprecation_message": |- + Deprecated: please set workspace.sync_mode to in-place instead "tags": "description": |- The tags for the bundle deployment. @@ -2411,6 +2415,24 @@ workspace: "state_path": "description": |- The workspace state path + "sync_mode": + "description": |- + How bundle files are made available to deployed resources. Valid values are `overwrite`, `in-place`, and `immutable`. Defaults to `in-place` for development deployments from the Databricks Workspace and to `overwrite` otherwise. + "$type": + "enum": + - |- + overwrite + - |- + in-place + - |- + immutable + "x-databricks-enum-descriptions": + "immutable": |- + Upload bundle files and artifacts as a single immutable snapshot and set workspace.file_path and workspace.artifact_path to its location. + "in-place": |- + Do not upload bundle files. Deployed resources reference the source files in the workspace directly. Available only when deploying from the Databricks Workspace. + "overwrite": |- + Upload bundle files to workspace.file_path, replacing the files of the previous deployment. "workspace_id": "description": |- The Databricks workspace ID diff --git a/bundle/internal/validation/generated/enum_fields.go b/bundle/internal/validation/generated/enum_fields.go index e67e605689d..f991d2b20ec 100644 --- a/bundle/internal/validation/generated/enum_fields.go +++ b/bundle/internal/validation/generated/enum_fields.go @@ -319,4 +319,6 @@ var EnumFields = map[string][]string{ "resources.volumes.*.volume_type": {"EXTERNAL", "MANAGED"}, "variables.*.type": {"complex"}, + + "workspace.sync_mode": {"overwrite", "in-place", "immutable"}, } diff --git a/bundle/phases/dms.go b/bundle/phases/dms.go index a2399c9e3a1..28f4f001c7b 100644 --- a/bundle/phases/dms.go +++ b/bundle/phases/dms.go @@ -228,7 +228,7 @@ func deploymentMetadata(b *bundle.Bundle) (dms.Metadata, error) { } // In a source-linked deployment files are not copied, so resources read them // from the sync root instead of file_path (see bundle/deploy/metadata.Compute). - if config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) { + if b.IsSourceLinkedDeployment() { ws.FilePath = b.SyncRootPath ws.SourceLinked = true } diff --git a/bundle/phases/initialize.go b/bundle/phases/initialize.go index 902a277b018..bd9e3f3b314 100644 --- a/bundle/phases/initialize.go +++ b/bundle/phases/initialize.go @@ -71,12 +71,14 @@ func Initialize(ctx context.Context, b *bundle.Bundle) { // Loads git repository information and updates bundle configuration with git details mutator.LoadGitDetails(), + // Reads (typed): b.Config.Workspace.SyncMode, b.Config.Presets.SourceLinkedDeployment, b.Config.Experimental.ImmutableFolder (warns about the deprecated settings) + // Updates (typed): b.Config.Workspace.SyncMode (in-place for development deployments in the Databricks Workspace, overwrite where in-place is unavailable) // This mutator needs to be run before variable interpolation and defining default workspace paths // because it affects how workspace variables are resolved. - mutator.ApplySourceLinkedDeploymentPreset(), + mutator.ApplySyncMode(), // Reads (env): __TEST_DATABRICKS_IMMUTABLE_FOLDER (non-empty value enables immutable folder mode) - // Updates (typed): b.Config.Experimental.ImmutableFolder (forces to true when env var is set) + // Updates (typed): b.Config.Workspace.SyncMode (forces to immutable when env var is set) // Allows running the full test suite against the immutable folder code path without // modifying any databricks.yml files. mutator.OverrideImmutableFolder(), @@ -216,7 +218,7 @@ func Initialize(ctx context.Context, b *bundle.Bundle) { mutator.TranslatePaths(), - // Reads (typed): b.Config.Experimental.PythonWheelWrapper, b.Config.Presets.SourceLinkedDeployment (checks Python wheel wrapper and deployment mode settings) + // Reads (typed): b.Config.Experimental.PythonWheelWrapper, b.Config.Workspace.SyncMode (checks Python wheel wrapper and deployment mode settings) // Reads (dynamic): resources.jobs.*.tasks (checks for tasks with local libraries and incompatible DBR versions) // Provides warnings when Python wheel tasks are used with DBR < 13.3 or when wheel wrapper is incompatible with source-linked deployment trampoline.WrapperWarning(), diff --git a/bundle/schema/jsonschema.json b/bundle/schema/jsonschema.json index 76a608b5275..3760689f5bd 100644 --- a/bundle/schema/jsonschema.json +++ b/bundle/schema/jsonschema.json @@ -3927,7 +3927,9 @@ }, "immutable_folder": { "description": "Whether to deploy bundle files and artifacts as a single immutable snapshot. When true, all files are packaged into a content-addressed archive and workspace.file_path and workspace.artifact_path are set to the resulting location.", - "$ref": "#/$defs/bool" + "$ref": "#/$defs/bool", + "deprecationMessage": "Deprecated: please set workspace.sync_mode to immutable instead", + "deprecated": true }, "pydabs": { "description": "The PyDABs configuration.", @@ -4040,7 +4042,9 @@ }, "source_linked_deployment": { "description": "Whether to link the deployment to the bundle source.", - "$ref": "#/$defs/bool" + "$ref": "#/$defs/bool", + "deprecationMessage": "Deprecated: please set workspace.sync_mode to in-place instead", + "deprecated": true }, "tags": { "description": "The tags for the bundle deployment.", @@ -4327,6 +4331,27 @@ } ] }, + "config.SyncMode": { + "oneOf": [ + { + "type": "string", + "enum": [ + "overwrite", + "in-place", + "immutable" + ], + "enumDescriptions": [ + "Upload bundle files to workspace.file_path, replacing the files of the previous deployment.", + "Do not upload bundle files. Deployed resources reference the source files in the workspace directly. Available only when deploying from the Databricks Workspace.", + "Upload bundle files and artifacts as a single immutable snapshot and set workspace.file_path and workspace.artifact_path to its location." + ] + }, + { + "type": "string", + "pattern": "\\$\\{(var(\\._*\\p{L}+([-_]*[\\p{L}\\p{N}]+)*(\\[[0-9]+\\])*)+)\\}" + } + ] + }, "config.Target": { "oneOf": [ { @@ -4478,6 +4503,10 @@ "description": "The workspace state path", "$ref": "#/$defs/string" }, + "sync_mode": { + "description": "How bundle files are made available to deployed resources. Valid values are `overwrite`, `in-place`, and `immutable`. Defaults to `in-place` for development deployments from the Databricks Workspace and to `overwrite` otherwise.", + "$ref": "#/$defs/github.com/databricks/cli/bundle/config.SyncMode" + }, "workspace_id": { "description": "The Databricks workspace ID", "$ref": "#/$defs/string" diff --git a/bundle/trampoline/python_dbr_warning.go b/bundle/trampoline/python_dbr_warning.go index 4cc7a67dc8c..a6f45e12311 100644 --- a/bundle/trampoline/python_dbr_warning.go +++ b/bundle/trampoline/python_dbr_warning.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/databricks/cli/bundle" - "github.com/databricks/cli/bundle/config" "github.com/databricks/cli/bundle/libraries" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn/dynvar" @@ -24,8 +23,8 @@ func WrapperWarning() bundle.Mutator { func (m *wrapperWarning) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnostics { if isPythonWheelWrapperOn(b) { - if config.IsExplicitlyEnabled(b.Config.Presets.SourceLinkedDeployment) { - return diag.Warningf("Python wheel notebook wrapper is not available when using source-linked deployment mode. You can disable this mode by setting 'presets.source_linked_deployment: false'") + if b.IsSourceLinkedDeployment() { + return diag.Warningf("Python wheel notebook wrapper is not available when using source-linked deployment mode. You can disable this mode by setting 'workspace.sync_mode: overwrite'") } return nil }