From 97b83b48d0c4e9746195003098c16351777b713b Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 12:46:07 +0000 Subject: [PATCH 01/14] direct: key job task depends_on slices by task_key Register tasks[*].depends_on and tasks[*].for_each_task.task.depends_on as keyed slices so task dependencies are diffed by task_key rather than by position. Also fix pathToPattern in structdiff to treat key-value path nodes as [*] wildcards, enabling nested keyed-slice patterns to match correctly. Co-authored-by: Isaac --- bundle/direct/dresources/job.go | 14 ++++++++++---- libs/structs/structdiff/diff.go | 15 ++++----------- libs/structs/structdiff/diff_test.go | 10 ++++++++++ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/bundle/direct/dresources/job.go b/bundle/direct/dresources/job.go index f10813b2fc..9477bf5251 100644 --- a/bundle/direct/dresources/job.go +++ b/bundle/direct/dresources/job.go @@ -71,12 +71,18 @@ func getEnvironmentKey(x jobs.JobEnvironment) (string, string) { return "environment_key", x.EnvironmentKey } +func getDependsOnTaskKey(x jobs.TaskDependency) (string, string) { + return "task_key", x.TaskKey +} + func (*ResourceJob) KeyedSlices() map[string]any { return map[string]any{ - "tasks": getTaskKey, - "parameters": getParameterName, - "job_clusters": getJobClusterKey, - "environments": getEnvironmentKey, + "tasks": getTaskKey, + "parameters": getParameterName, + "job_clusters": getJobClusterKey, + "environments": getEnvironmentKey, + "tasks[*].depends_on": getDependsOnTaskKey, + "tasks[*].for_each_task.task.depends_on": getDependsOnTaskKey, } } diff --git a/libs/structs/structdiff/diff.go b/libs/structs/structdiff/diff.go index 61c909dfd1..a852d347e4 100644 --- a/libs/structs/structdiff/diff.go +++ b/libs/structs/structdiff/diff.go @@ -308,7 +308,7 @@ func (ctx *diffContext) findKeyFunc(path *structpath.PathNode) KeyFunc { } // pathToPattern converts a PathNode to a pattern string for matching. -// Slice indices are converted to [*] wildcard. +// Slice indices and key-value pairs are converted to [*] wildcard. func pathToPattern(path *structpath.PathNode) string { if path == nil { return "" @@ -318,17 +318,10 @@ func pathToPattern(path *structpath.PathNode) string { var result strings.Builder for i, node := range components { - if idx, ok := node.Index(); ok { - // Convert numeric index to wildcard - _ = idx + if _, ok := node.Index(); ok { + result.WriteString("[*]") + } else if _, _, ok := node.KeyValue(); ok { result.WriteString("[*]") - } else if key, value, ok := node.KeyValue(); ok { - // Key-value syntax - result.WriteString("[") - result.WriteString(key) - result.WriteString("=") - result.WriteString(structpath.EncodeMapKey(value)) - result.WriteString("]") } else if key, ok := node.StringKey(); ok { if i != 0 { result.WriteString(".") diff --git a/libs/structs/structdiff/diff_test.go b/libs/structs/structdiff/diff_test.go index 4b57f87c88..72012b11a6 100644 --- a/libs/structs/structdiff/diff_test.go +++ b/libs/structs/structdiff/diff_test.go @@ -558,10 +558,16 @@ func TestGetStructDiffEmbedTagWithKeyFunc(t *testing.T) { } } +type Dep struct { + TaskKey string `json:"task_key,omitempty"` + Outcome string `json:"outcome,omitempty"` +} + type Task struct { TaskKey string `json:"task_key,omitempty"` Description string `json:"description,omitempty"` Timeout int `json:"timeout,omitempty"` + DependsOn []Dep `json:"depends_on,omitempty"` } type Job struct { @@ -573,6 +579,10 @@ func taskKeyFunc(task Task) (string, string) { return "task_key", task.TaskKey } +func depKeyFunc(dep Dep) (string, string) { + return "task_key", dep.TaskKey +} + func TestGetStructDiffSliceKeys(t *testing.T) { sliceKeys := map[string]KeyFunc{ "tasks": taskKeyFunc, From ff15e472da05883f20aa340025b5cf836012c481 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 12:50:04 +0000 Subject: [PATCH 02/14] direct: add unit tests for nested depends_on keyed slices Test that reordered depends_on arrays within job tasks produce no phantom diffs when using the tasks[*].depends_on key function. Task: 001.md Co-authored-by: Isaac --- libs/structs/structdiff/diff_test.go | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/libs/structs/structdiff/diff_test.go b/libs/structs/structdiff/diff_test.go index 72012b11a6..567287fd8c 100644 --- a/libs/structs/structdiff/diff_test.go +++ b/libs/structs/structdiff/diff_test.go @@ -652,6 +652,64 @@ func TestGetStructDiffSliceKeys(t *testing.T) { } } +func TestGetStructDiffNestedDependsOn(t *testing.T) { + sliceKeys := map[string]KeyFunc{ + "tasks": taskKeyFunc, + "tasks[*].depends_on": depKeyFunc, + } + + tests := []struct { + name string + a, b Job + want []ResolvedChange + }{ + { + name: "depends_on reordered no diff", + a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}, {TaskKey: "b"}}}}}, + b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "b"}, {TaskKey: "a"}}}}}, + want: nil, + }, + { + name: "depends_on field change", + a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a", Outcome: "success"}}}}}, + b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a", Outcome: "failed"}}}}}, + want: []ResolvedChange{{Field: "tasks[task_key='c'].depends_on[task_key='a'].outcome", Old: "success", New: "failed"}}, + }, + { + name: "depends_on element added", + a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}}}}}, + b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}, {TaskKey: "b"}}}}}, + want: []ResolvedChange{{Field: "tasks[task_key='c'].depends_on[task_key='b']", Old: nil, New: Dep{TaskKey: "b"}}}, + }, + { + name: "depends_on element removed", + a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}, {TaskKey: "b"}}}}}, + b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}}}}}, + want: []ResolvedChange{{Field: "tasks[task_key='c'].depends_on[task_key='b']", Old: Dep{TaskKey: "b"}, New: nil}}, + }, + { + name: "tasks and depends_on both reordered no diff", + a: Job{Tasks: []Task{ + {TaskKey: "x", DependsOn: []Dep{{TaskKey: "a"}, {TaskKey: "b"}}}, + {TaskKey: "y", DependsOn: []Dep{{TaskKey: "c"}}}, + }}, + b: Job{Tasks: []Task{ + {TaskKey: "y", DependsOn: []Dep{{TaskKey: "c"}}}, + {TaskKey: "x", DependsOn: []Dep{{TaskKey: "b"}, {TaskKey: "a"}}}, + }}, + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := GetStructDiff(tt.a, tt.b, sliceKeys) + assert.NoError(t, err) + assert.Equal(t, tt.want, resolveChanges(got)) + }) + } +} + type Nested struct { Items []Item `json:"items,omitempty"` } From d06fa1e184e3918217135a5710ec6b0d8f69aba7 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 13:00:59 +0000 Subject: [PATCH 03/14] testserver: sort depends_on by task_key; add reorder acceptance test Simulates real API behavior where depends_on entries are returned in a different order than submitted. Adds an acceptance test that verifies bundle plan shows 0 changes after deploy despite the reordering. --- .../jobs/depends-on-reorder/databricks.yml | 21 +++++++++++++++++++ .../resources/jobs/depends-on-reorder/main.py | 1 + .../jobs/depends-on-reorder/out.test.toml | 5 +++++ .../resources/jobs/depends-on-reorder/script | 2 ++ .../jobs/depends-on-reorder/test.toml | 2 ++ libs/testserver/jobs.go | 6 ++++++ 6 files changed, 37 insertions(+) create mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/databricks.yml create mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/main.py create mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml create mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/script create mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/test.toml diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/databricks.yml b/acceptance/bundle/resources/jobs/depends-on-reorder/databricks.yml new file mode 100644 index 0000000000..3fdfae66b9 --- /dev/null +++ b/acceptance/bundle/resources/jobs/depends-on-reorder/databricks.yml @@ -0,0 +1,21 @@ +bundle: + name: test-bundle + +resources: + jobs: + foo: + tasks: + - task_key: main + notebook_task: + notebook_path: main.py + - task_key: process + depends_on: + - task_key: main + notebook_task: + notebook_path: main.py + - task_key: finalize + depends_on: + - task_key: process + - task_key: main + notebook_task: + notebook_path: main.py diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/main.py b/acceptance/bundle/resources/jobs/depends-on-reorder/main.py new file mode 100644 index 0000000000..1645e04b1d --- /dev/null +++ b/acceptance/bundle/resources/jobs/depends-on-reorder/main.py @@ -0,0 +1 @@ +# Databricks notebook source diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml b/acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml new file mode 100644 index 0000000000..d560f1de04 --- /dev/null +++ b/acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml @@ -0,0 +1,5 @@ +Local = true +Cloud = false + +[EnvMatrix] + DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/script b/acceptance/bundle/resources/jobs/depends-on-reorder/script new file mode 100644 index 0000000000..3e0c67c396 --- /dev/null +++ b/acceptance/bundle/resources/jobs/depends-on-reorder/script @@ -0,0 +1,2 @@ +trace $CLI bundle deploy +trace $CLI bundle plan | contains.py "0 to add, 0 to change, 0 to delete" diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/test.toml b/acceptance/bundle/resources/jobs/depends-on-reorder/test.toml new file mode 100644 index 0000000000..e1290bad9f --- /dev/null +++ b/acceptance/bundle/resources/jobs/depends-on-reorder/test.toml @@ -0,0 +1,2 @@ +RecordRequests = false +Ignore = [".databricks"] diff --git a/libs/testserver/jobs.go b/libs/testserver/jobs.go index 15800341de..cd9432fac1 100644 --- a/libs/testserver/jobs.go +++ b/libs/testserver/jobs.go @@ -106,6 +106,12 @@ func jobFixUps(jobSettings *jobs.JobSettings) { for i := range jobSettings.Tasks { task := &jobSettings.Tasks[i] + // Sort depends_on by task_key to simulate the real API which returns + // dependencies in a different order than submitted. + slices.SortFunc(task.DependsOn, func(a, b jobs.TaskDependency) int { + return cmp.Compare(a.TaskKey, b.TaskKey) + }) + // Set task email notifications to empty struct if not set if task.EmailNotifications == nil { task.EmailNotifications = &jobs.TaskEmailNotifications{} From 1e0aa1acc60c7a2d60829bdb679e2c337e9d89c6 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 13:03:48 +0000 Subject: [PATCH 04/14] acceptance: add output.txt and make depends-on-reorder test direct-only The depends_on reordering fix is in the direct engine. The terraform engine handles ordering through its provider, so the acceptance test should only run with the direct engine. Task: 001.md Co-authored-by: Isaac --- .../resources/jobs/depends-on-reorder/out.test.toml | 2 +- .../bundle/resources/jobs/depends-on-reorder/output.txt | 9 +++++++++ .../bundle/resources/jobs/depends-on-reorder/test.toml | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/output.txt diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml b/acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml index d560f1de04..54146af564 100644 --- a/acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml +++ b/acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml @@ -2,4 +2,4 @@ Local = true Cloud = false [EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["terraform", "direct"] + DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/output.txt b/acceptance/bundle/resources/jobs/depends-on-reorder/output.txt new file mode 100644 index 0000000000..6d239f5055 --- /dev/null +++ b/acceptance/bundle/resources/jobs/depends-on-reorder/output.txt @@ -0,0 +1,9 @@ + +>>> [CLI] bundle deploy +Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... +Deploying resources... +Updating deployment state... +Deployment complete! + +>>> [CLI] bundle plan +Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/test.toml b/acceptance/bundle/resources/jobs/depends-on-reorder/test.toml index e1290bad9f..09aa42374f 100644 --- a/acceptance/bundle/resources/jobs/depends-on-reorder/test.toml +++ b/acceptance/bundle/resources/jobs/depends-on-reorder/test.toml @@ -1,2 +1,5 @@ RecordRequests = false Ignore = [".databricks"] + +[EnvMatrix] +DATABRICKS_BUNDLE_ENGINE = ["direct"] From fc5dd710a84c7c498b99609ed77ebeab7cad045e Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 13:18:01 +0000 Subject: [PATCH 05/14] Add changelog entry for depends_on reordering fix Co-authored-by: Isaac --- NEXT_CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 370e2f8bd8..bf1f0a63c1 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -11,6 +11,8 @@ ### Bundles * Remove `experimental-jobs-as-code` template, superseded by `pydabs` ([#4999](https://github.com/databricks/cli/pull/4999)). +* engine/direct: Fix phantom diffs from `depends_on` reordering in job tasks (denik/jobs-depends-on) + * engine/direct: Added support for Vector Search Endpoints ([#4887](https://github.com/databricks/cli/pull/4887)) ### Dependency updates From d8ea34659fce03fb716c9755c77ccd813733ed39 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 13:24:58 +0000 Subject: [PATCH 06/14] Update NEXT_CHANGELOG.md with PR #4990 --- NEXT_CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index bf1f0a63c1..9dd5e9a629 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -12,6 +12,7 @@ ### Bundles * Remove `experimental-jobs-as-code` template, superseded by `pydabs` ([#4999](https://github.com/databricks/cli/pull/4999)). * engine/direct: Fix phantom diffs from `depends_on` reordering in job tasks (denik/jobs-depends-on) +* engine/direct: Fix phantom diffs from `depends_on` reordering in job tasks ([#4990](https://github.com/databricks/cli/pull/4990)) * engine/direct: Added support for Vector Search Endpoints ([#4887](https://github.com/databricks/cli/pull/4887)) From 007cf634351462cb9332f42f3a669af2c5e8f9d3 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 13:29:05 +0000 Subject: [PATCH 07/14] acceptance: add invariant test config for job with depends_on Adds a template config with tasks that use depends_on to verify depends_on handling is stable across deploy/redeploy cycles. --- .../configs/job_with_depends_on.yml.tmpl | 22 +++++++++++++++++++ acceptance/bundle/invariant/test.toml | 1 + 2 files changed, 23 insertions(+) create mode 100644 acceptance/bundle/invariant/configs/job_with_depends_on.yml.tmpl diff --git a/acceptance/bundle/invariant/configs/job_with_depends_on.yml.tmpl b/acceptance/bundle/invariant/configs/job_with_depends_on.yml.tmpl new file mode 100644 index 0000000000..8a460d1d90 --- /dev/null +++ b/acceptance/bundle/invariant/configs/job_with_depends_on.yml.tmpl @@ -0,0 +1,22 @@ +bundle: + name: test-bundle-$UNIQUE_NAME + +resources: + jobs: + foo: + name: test-job-$UNIQUE_NAME + tasks: + - task_key: main + notebook_task: + notebook_path: /Shared/notebook + - task_key: process + depends_on: + - task_key: main + notebook_task: + notebook_path: /Shared/notebook + - task_key: finalize + depends_on: + - task_key: process + - task_key: main + notebook_task: + notebook_path: /Shared/notebook diff --git a/acceptance/bundle/invariant/test.toml b/acceptance/bundle/invariant/test.toml index 02f355168f..bb66a393be 100644 --- a/acceptance/bundle/invariant/test.toml +++ b/acceptance/bundle/invariant/test.toml @@ -35,6 +35,7 @@ EnvMatrix.INPUT_CONFIG = [ "job_pydabs_1000_tasks.yml.tmpl", "job_cross_resource_ref.yml.tmpl", "job_permission_ref.yml.tmpl", + "job_with_depends_on.yml.tmpl", "job_with_permissions.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", From 6e1531480a25a28c10f8d75135236d1077687d5c Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 13:36:43 +0000 Subject: [PATCH 08/14] Replace custom depends-on-reorder test with invariant test config Remove the dedicated acceptance/bundle/resources/jobs/depends-on-reorder/ test since the invariant test config job_with_depends_on.yml.tmpl covers the same scenario through the no_drift and continue_293 variants. Exclude job_with_depends_on from the migrate variant because terraform does not apply keyed-slice ordering for depends_on. Task: 002.md Co-authored-by: Isaac --- .../invariant/continue_293/out.test.toml | 2 +- .../bundle/invariant/migrate/out.test.toml | 2 +- acceptance/bundle/invariant/migrate/test.toml | 4 ++++ .../bundle/invariant/no_drift/out.test.toml | 2 +- .../jobs/depends-on-reorder/databricks.yml | 21 ------------------- .../resources/jobs/depends-on-reorder/main.py | 1 - .../jobs/depends-on-reorder/out.test.toml | 5 ----- .../jobs/depends-on-reorder/output.txt | 9 -------- .../resources/jobs/depends-on-reorder/script | 2 -- .../jobs/depends-on-reorder/test.toml | 5 ----- 10 files changed, 7 insertions(+), 46 deletions(-) delete mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/databricks.yml delete mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/main.py delete mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml delete mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/output.txt delete mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/script delete mode 100644 acceptance/bundle/resources/jobs/depends-on-reorder/test.toml diff --git a/acceptance/bundle/invariant/continue_293/out.test.toml b/acceptance/bundle/invariant/continue_293/out.test.toml index 33ad99cacb..0622360897 100644 --- a/acceptance/bundle/invariant/continue_293/out.test.toml +++ b/acceptance/bundle/invariant/continue_293/out.test.toml @@ -4,4 +4,4 @@ RequiresUnityCatalog = true [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["direct"] - INPUT_CONFIG = ["alert.yml.tmpl", "app.yml.tmpl", "catalog.yml.tmpl", "cluster.yml.tmpl", "dashboard.yml.tmpl", "database_catalog.yml.tmpl", "database_instance.yml.tmpl", "experiment.yml.tmpl", "external_location.yml.tmpl", "job.yml.tmpl", "job_pydabs_10_tasks.yml.tmpl", "job_pydabs_1000_tasks.yml.tmpl", "job_cross_resource_ref.yml.tmpl", "job_permission_ref.yml.tmpl", "job_with_permissions.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", "pipeline_config_dots.yml.tmpl", "postgres_branch.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", "schema_with_grants.yml.tmpl", "secret_scope.yml.tmpl", "secret_scope_default_backend_type.yml.tmpl", "secret_scope_with_permissions.yml.tmpl", "sql_warehouse.yml.tmpl", "synced_database_table.yml.tmpl", "vector_search_endpoint.yml.tmpl", "volume.yml.tmpl"] + INPUT_CONFIG = ["alert.yml.tmpl", "app.yml.tmpl", "catalog.yml.tmpl", "cluster.yml.tmpl", "dashboard.yml.tmpl", "database_catalog.yml.tmpl", "database_instance.yml.tmpl", "experiment.yml.tmpl", "external_location.yml.tmpl", "job.yml.tmpl", "job_pydabs_10_tasks.yml.tmpl", "job_pydabs_1000_tasks.yml.tmpl", "job_cross_resource_ref.yml.tmpl", "job_permission_ref.yml.tmpl", "job_with_depends_on.yml.tmpl", "job_with_permissions.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", "pipeline_config_dots.yml.tmpl", "postgres_branch.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", "schema_with_grants.yml.tmpl", "secret_scope.yml.tmpl", "secret_scope_default_backend_type.yml.tmpl", "secret_scope_with_permissions.yml.tmpl", "sql_warehouse.yml.tmpl", "synced_database_table.yml.tmpl", "vector_search_endpoint.yml.tmpl", "volume.yml.tmpl"] diff --git a/acceptance/bundle/invariant/migrate/out.test.toml b/acceptance/bundle/invariant/migrate/out.test.toml index 33ad99cacb..0622360897 100644 --- a/acceptance/bundle/invariant/migrate/out.test.toml +++ b/acceptance/bundle/invariant/migrate/out.test.toml @@ -4,4 +4,4 @@ RequiresUnityCatalog = true [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["direct"] - INPUT_CONFIG = ["alert.yml.tmpl", "app.yml.tmpl", "catalog.yml.tmpl", "cluster.yml.tmpl", "dashboard.yml.tmpl", "database_catalog.yml.tmpl", "database_instance.yml.tmpl", "experiment.yml.tmpl", "external_location.yml.tmpl", "job.yml.tmpl", "job_pydabs_10_tasks.yml.tmpl", "job_pydabs_1000_tasks.yml.tmpl", "job_cross_resource_ref.yml.tmpl", "job_permission_ref.yml.tmpl", "job_with_permissions.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", "pipeline_config_dots.yml.tmpl", "postgres_branch.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", "schema_with_grants.yml.tmpl", "secret_scope.yml.tmpl", "secret_scope_default_backend_type.yml.tmpl", "secret_scope_with_permissions.yml.tmpl", "sql_warehouse.yml.tmpl", "synced_database_table.yml.tmpl", "vector_search_endpoint.yml.tmpl", "volume.yml.tmpl"] + INPUT_CONFIG = ["alert.yml.tmpl", "app.yml.tmpl", "catalog.yml.tmpl", "cluster.yml.tmpl", "dashboard.yml.tmpl", "database_catalog.yml.tmpl", "database_instance.yml.tmpl", "experiment.yml.tmpl", "external_location.yml.tmpl", "job.yml.tmpl", "job_pydabs_10_tasks.yml.tmpl", "job_pydabs_1000_tasks.yml.tmpl", "job_cross_resource_ref.yml.tmpl", "job_permission_ref.yml.tmpl", "job_with_depends_on.yml.tmpl", "job_with_permissions.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", "pipeline_config_dots.yml.tmpl", "postgres_branch.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", "schema_with_grants.yml.tmpl", "secret_scope.yml.tmpl", "secret_scope_default_backend_type.yml.tmpl", "secret_scope_with_permissions.yml.tmpl", "sql_warehouse.yml.tmpl", "synced_database_table.yml.tmpl", "vector_search_endpoint.yml.tmpl", "volume.yml.tmpl"] diff --git a/acceptance/bundle/invariant/migrate/test.toml b/acceptance/bundle/invariant/migrate/test.toml index adc49c2992..d6ba555ed7 100644 --- a/acceptance/bundle/invariant/migrate/test.toml +++ b/acceptance/bundle/invariant/migrate/test.toml @@ -19,3 +19,7 @@ EnvMatrixExclude.no_grant_ref = ["INPUT_CONFIG=schema_grant_ref.yml.tmpl"] # SQL warehouses currently failing with migration with permanent drift. TODO: fix this. EnvMatrixExclude.no_sql_warehouse = ["INPUT_CONFIG=sql_warehouse.yml.tmpl"] + +# Terraform does not apply keyed-slice ordering for depends_on, so migration +# detects drift when depends_on order in config differs from server-side order. +EnvMatrixExclude.no_depends_on = ["INPUT_CONFIG=job_with_depends_on.yml.tmpl"] diff --git a/acceptance/bundle/invariant/no_drift/out.test.toml b/acceptance/bundle/invariant/no_drift/out.test.toml index 33ad99cacb..0622360897 100644 --- a/acceptance/bundle/invariant/no_drift/out.test.toml +++ b/acceptance/bundle/invariant/no_drift/out.test.toml @@ -4,4 +4,4 @@ RequiresUnityCatalog = true [EnvMatrix] DATABRICKS_BUNDLE_ENGINE = ["direct"] - INPUT_CONFIG = ["alert.yml.tmpl", "app.yml.tmpl", "catalog.yml.tmpl", "cluster.yml.tmpl", "dashboard.yml.tmpl", "database_catalog.yml.tmpl", "database_instance.yml.tmpl", "experiment.yml.tmpl", "external_location.yml.tmpl", "job.yml.tmpl", "job_pydabs_10_tasks.yml.tmpl", "job_pydabs_1000_tasks.yml.tmpl", "job_cross_resource_ref.yml.tmpl", "job_permission_ref.yml.tmpl", "job_with_permissions.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", "pipeline_config_dots.yml.tmpl", "postgres_branch.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", "schema_with_grants.yml.tmpl", "secret_scope.yml.tmpl", "secret_scope_default_backend_type.yml.tmpl", "secret_scope_with_permissions.yml.tmpl", "sql_warehouse.yml.tmpl", "synced_database_table.yml.tmpl", "vector_search_endpoint.yml.tmpl", "volume.yml.tmpl"] + INPUT_CONFIG = ["alert.yml.tmpl", "app.yml.tmpl", "catalog.yml.tmpl", "cluster.yml.tmpl", "dashboard.yml.tmpl", "database_catalog.yml.tmpl", "database_instance.yml.tmpl", "experiment.yml.tmpl", "external_location.yml.tmpl", "job.yml.tmpl", "job_pydabs_10_tasks.yml.tmpl", "job_pydabs_1000_tasks.yml.tmpl", "job_cross_resource_ref.yml.tmpl", "job_permission_ref.yml.tmpl", "job_with_depends_on.yml.tmpl", "job_with_permissions.yml.tmpl", "job_with_task.yml.tmpl", "model.yml.tmpl", "model_with_permissions.yml.tmpl", "model_serving_endpoint.yml.tmpl", "pipeline.yml.tmpl", "pipeline_config_dots.yml.tmpl", "postgres_branch.yml.tmpl", "postgres_endpoint.yml.tmpl", "postgres_project.yml.tmpl", "registered_model.yml.tmpl", "schema.yml.tmpl", "schema_grant_ref.yml.tmpl", "schema_with_grants.yml.tmpl", "secret_scope.yml.tmpl", "secret_scope_default_backend_type.yml.tmpl", "secret_scope_with_permissions.yml.tmpl", "sql_warehouse.yml.tmpl", "synced_database_table.yml.tmpl", "vector_search_endpoint.yml.tmpl", "volume.yml.tmpl"] diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/databricks.yml b/acceptance/bundle/resources/jobs/depends-on-reorder/databricks.yml deleted file mode 100644 index 3fdfae66b9..0000000000 --- a/acceptance/bundle/resources/jobs/depends-on-reorder/databricks.yml +++ /dev/null @@ -1,21 +0,0 @@ -bundle: - name: test-bundle - -resources: - jobs: - foo: - tasks: - - task_key: main - notebook_task: - notebook_path: main.py - - task_key: process - depends_on: - - task_key: main - notebook_task: - notebook_path: main.py - - task_key: finalize - depends_on: - - task_key: process - - task_key: main - notebook_task: - notebook_path: main.py diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/main.py b/acceptance/bundle/resources/jobs/depends-on-reorder/main.py deleted file mode 100644 index 1645e04b1d..0000000000 --- a/acceptance/bundle/resources/jobs/depends-on-reorder/main.py +++ /dev/null @@ -1 +0,0 @@ -# Databricks notebook source diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml b/acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml deleted file mode 100644 index 54146af564..0000000000 --- a/acceptance/bundle/resources/jobs/depends-on-reorder/out.test.toml +++ /dev/null @@ -1,5 +0,0 @@ -Local = true -Cloud = false - -[EnvMatrix] - DATABRICKS_BUNDLE_ENGINE = ["direct"] diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/output.txt b/acceptance/bundle/resources/jobs/depends-on-reorder/output.txt deleted file mode 100644 index 6d239f5055..0000000000 --- a/acceptance/bundle/resources/jobs/depends-on-reorder/output.txt +++ /dev/null @@ -1,9 +0,0 @@ - ->>> [CLI] bundle deploy -Uploading bundle files to /Workspace/Users/[USERNAME]/.bundle/test-bundle/default/files... -Deploying resources... -Updating deployment state... -Deployment complete! - ->>> [CLI] bundle plan -Plan: 0 to add, 0 to change, 0 to delete, 1 unchanged diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/script b/acceptance/bundle/resources/jobs/depends-on-reorder/script deleted file mode 100644 index 3e0c67c396..0000000000 --- a/acceptance/bundle/resources/jobs/depends-on-reorder/script +++ /dev/null @@ -1,2 +0,0 @@ -trace $CLI bundle deploy -trace $CLI bundle plan | contains.py "0 to add, 0 to change, 0 to delete" diff --git a/acceptance/bundle/resources/jobs/depends-on-reorder/test.toml b/acceptance/bundle/resources/jobs/depends-on-reorder/test.toml deleted file mode 100644 index 09aa42374f..0000000000 --- a/acceptance/bundle/resources/jobs/depends-on-reorder/test.toml +++ /dev/null @@ -1,5 +0,0 @@ -RecordRequests = false -Ignore = [".databricks"] - -[EnvMatrix] -DATABRICKS_BUNDLE_ENGINE = ["direct"] From 4d1bbb61c0e91869cbab227c2410277151614ab1 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 14:00:21 +0000 Subject: [PATCH 09/14] Enable migration test for job_with_depends_on config Remove the EnvMatrixExclude for job_with_depends_on.yml.tmpl from the migration invariant test. Instead, pass --noplancheck to the migrate command for this config, since Terraform's plan detects false drift due to depends_on ordering differences. The post-migration plan check (using direct engine) validates no drift correctly because the direct engine handles keyed-slice ordering. Task: 003.md Co-authored-by: Isaac --- acceptance/bundle/invariant/migrate/script | 9 ++++++++- acceptance/bundle/invariant/migrate/test.toml | 4 ---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/acceptance/bundle/invariant/migrate/script b/acceptance/bundle/invariant/migrate/script index d02200cb53..cd4a077ee8 100644 --- a/acceptance/bundle/invariant/migrate/script +++ b/acceptance/bundle/invariant/migrate/script @@ -34,7 +34,14 @@ cat LOG.deploy | contains.py '!panic:' '!internal error' > /dev/null echo INPUT_CONFIG_OK -trace $CLI bundle deployment migrate &> LOG.migrate +MIGRATE_ARGS="" +# Terraform does not apply keyed-slice ordering for depends_on, so its plan +# detects drift when depends_on order in config differs from server-side order. +if [[ "$INPUT_CONFIG" == "job_with_depends_on.yml.tmpl" ]]; then + MIGRATE_ARGS="--noplancheck" +fi + +trace $CLI bundle deployment migrate $MIGRATE_ARGS &> LOG.migrate cat LOG.migrate | contains.py '!panic:' '!internal error' > /dev/null diff --git a/acceptance/bundle/invariant/migrate/test.toml b/acceptance/bundle/invariant/migrate/test.toml index d6ba555ed7..adc49c2992 100644 --- a/acceptance/bundle/invariant/migrate/test.toml +++ b/acceptance/bundle/invariant/migrate/test.toml @@ -19,7 +19,3 @@ EnvMatrixExclude.no_grant_ref = ["INPUT_CONFIG=schema_grant_ref.yml.tmpl"] # SQL warehouses currently failing with migration with permanent drift. TODO: fix this. EnvMatrixExclude.no_sql_warehouse = ["INPUT_CONFIG=sql_warehouse.yml.tmpl"] - -# Terraform does not apply keyed-slice ordering for depends_on, so migration -# detects drift when depends_on order in config differs from server-side order. -EnvMatrixExclude.no_depends_on = ["INPUT_CONFIG=job_with_depends_on.yml.tmpl"] From c2dd78acc0ec74f02c065a809b20e02af433f659 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 14:18:59 +0000 Subject: [PATCH 10/14] Sort depends_on by task_key in terraform job conversion The terraform provider sorts depends_on entries by task_key on Read (see terraform-provider-databricks PR #3000). Since depends_on uses TypeList (order-sensitive), terraform plan detects positional differences when the config order doesn't match the sorted state order. Fix by sorting depends_on in the CLI's terraform conversion, matching the provider's behavior. This removes the --noplancheck workaround from the migration invariant test. Task: 004.md --- acceptance/bundle/invariant/migrate/script | 9 +----- bundle/deploy/terraform/tfdyn/convert_job.go | 29 +++++++++++++++++++ .../terraform/tfdyn/convert_job_test.go | 8 +++++ 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/acceptance/bundle/invariant/migrate/script b/acceptance/bundle/invariant/migrate/script index cd4a077ee8..d02200cb53 100644 --- a/acceptance/bundle/invariant/migrate/script +++ b/acceptance/bundle/invariant/migrate/script @@ -34,14 +34,7 @@ cat LOG.deploy | contains.py '!panic:' '!internal error' > /dev/null echo INPUT_CONFIG_OK -MIGRATE_ARGS="" -# Terraform does not apply keyed-slice ordering for depends_on, so its plan -# detects drift when depends_on order in config differs from server-side order. -if [[ "$INPUT_CONFIG" == "job_with_depends_on.yml.tmpl" ]]; then - MIGRATE_ARGS="--noplancheck" -fi - -trace $CLI bundle deployment migrate $MIGRATE_ARGS &> LOG.migrate +trace $CLI bundle deployment migrate &> LOG.migrate cat LOG.migrate | contains.py '!panic:' '!internal error' > /dev/null diff --git a/bundle/deploy/terraform/tfdyn/convert_job.go b/bundle/deploy/terraform/tfdyn/convert_job.go index c9f7e8219a..3efc49de98 100644 --- a/bundle/deploy/terraform/tfdyn/convert_job.go +++ b/bundle/deploy/terraform/tfdyn/convert_job.go @@ -114,6 +114,20 @@ func patchApplyPolicyDefaultValues(_ dyn.Path, v dyn.Value) (dyn.Value, error) { return v, nil } +func sortDependsOn(v dyn.Value, key string) dyn.Value { + deps, ok := v.Get(key).AsSequence() + if !ok || len(deps) < 2 { + return v + } + slices.SortFunc(deps, func(a, b dyn.Value) int { + ak, _ := a.Get("task_key").AsString() + bk, _ := b.Get("task_key").AsString() + return cmp.Compare(ak, bk) + }) + v, _ = dyn.Set(v, key, dyn.V(deps)) + return v +} + func convertJobResource(ctx context.Context, vin dyn.Value) (dyn.Value, error) { // Normalize the input value to the underlying job schema. // This removes superfluous keys and adapts the input to the expected schema. @@ -155,6 +169,21 @@ func convertJobResource(ctx context.Context, vin dyn.Value) (dyn.Value, error) { } } + // Sort depends_on entries within each task by task_key. The terraform provider + // sorts depends_on on Read (see https://github.com/databricks/terraform-provider-databricks/pull/3000). + // Since depends_on uses TypeList (order-sensitive), the config order must match the + // state order to avoid perpetual drift in terraform plan. + vout, err = dyn.Map(vout, "tasks", dyn.Foreach(func(_ dyn.Path, task dyn.Value) (dyn.Value, error) { + task = sortDependsOn(task, "depends_on") + task, _ = dyn.Map(task, "for_each_task.task", func(_ dyn.Path, inner dyn.Value) (dyn.Value, error) { + return sortDependsOn(inner, "depends_on"), nil + }) + return task, nil + })) + if err != nil { + return dyn.InvalidValue, err + } + // Apply default task source logic vout, err = applyDefaultTaskSource(vout) if err != nil { diff --git a/bundle/deploy/terraform/tfdyn/convert_job_test.go b/bundle/deploy/terraform/tfdyn/convert_job_test.go index 782075fc7f..fb0f14979f 100644 --- a/bundle/deploy/terraform/tfdyn/convert_job_test.go +++ b/bundle/deploy/terraform/tfdyn/convert_job_test.go @@ -65,6 +65,10 @@ func TestConvertJob(t *testing.T) { { TaskKey: "task_key_c", JobClusterKey: "job_cluster_key_c", + DependsOn: []jobs.TaskDependency{ + {TaskKey: "task_key_b"}, + {TaskKey: "task_key_a"}, + }, }, { Description: "missing task key 😱", @@ -137,6 +141,10 @@ func TestConvertJob(t *testing.T) { map[string]any{ "task_key": "task_key_c", "job_cluster_key": "job_cluster_key_c", + "depends_on": []any{ + map[string]any{"task_key": "task_key_a"}, + map[string]any{"task_key": "task_key_b"}, + }, }, }, }, out.Job["my_job"]) From 33de3f0bfe8fb62bd4012f1bd1aa78f31f0a96de Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 14:42:36 +0000 Subject: [PATCH 11/14] Revert depends_on sorting in terraform job conversion Remove the sortDependsOn function and its usage in convertJobResource. The terraform provider already sorts depends_on by task_key on Read, so fixing the ordering in the CLI is unnecessary complexity. Instead, restore --noplancheck for job_with_depends_on in the migrate invariant test with a detailed comment explaining the false drift: the provider sorts depends_on by task_key using TypeList (order-sensitive), so terraform plan reports positional differences when the config order doesn't match the sorted state. Task: 005.md Co-authored-by: Isaac --- acceptance/bundle/invariant/migrate/script | 12 +++++++- bundle/deploy/terraform/tfdyn/convert_job.go | 29 ------------------- .../terraform/tfdyn/convert_job_test.go | 8 ----- 3 files changed, 11 insertions(+), 38 deletions(-) diff --git a/acceptance/bundle/invariant/migrate/script b/acceptance/bundle/invariant/migrate/script index d02200cb53..78f45faa7d 100644 --- a/acceptance/bundle/invariant/migrate/script +++ b/acceptance/bundle/invariant/migrate/script @@ -34,7 +34,17 @@ cat LOG.deploy | contains.py '!panic:' '!internal error' > /dev/null echo INPUT_CONFIG_OK -trace $CLI bundle deployment migrate &> LOG.migrate +MIGRATE_ARGS="" +# The terraform provider sorts depends_on entries alphabetically by task_key on Read +# (see terraform-provider-databricks PR #3000). Since depends_on uses TypeList +# (order-sensitive), terraform plan reports positional drift when the bundle config +# specifies depends_on in a different order than the provider's sorted state. +# This is a false positive -- the logical dependencies are identical. +if [[ "$INPUT_CONFIG" == "job_with_depends_on.yml.tmpl" ]]; then + MIGRATE_ARGS="--noplancheck" +fi + +trace $CLI bundle deployment migrate $MIGRATE_ARGS &> LOG.migrate cat LOG.migrate | contains.py '!panic:' '!internal error' > /dev/null diff --git a/bundle/deploy/terraform/tfdyn/convert_job.go b/bundle/deploy/terraform/tfdyn/convert_job.go index 3efc49de98..c9f7e8219a 100644 --- a/bundle/deploy/terraform/tfdyn/convert_job.go +++ b/bundle/deploy/terraform/tfdyn/convert_job.go @@ -114,20 +114,6 @@ func patchApplyPolicyDefaultValues(_ dyn.Path, v dyn.Value) (dyn.Value, error) { return v, nil } -func sortDependsOn(v dyn.Value, key string) dyn.Value { - deps, ok := v.Get(key).AsSequence() - if !ok || len(deps) < 2 { - return v - } - slices.SortFunc(deps, func(a, b dyn.Value) int { - ak, _ := a.Get("task_key").AsString() - bk, _ := b.Get("task_key").AsString() - return cmp.Compare(ak, bk) - }) - v, _ = dyn.Set(v, key, dyn.V(deps)) - return v -} - func convertJobResource(ctx context.Context, vin dyn.Value) (dyn.Value, error) { // Normalize the input value to the underlying job schema. // This removes superfluous keys and adapts the input to the expected schema. @@ -169,21 +155,6 @@ func convertJobResource(ctx context.Context, vin dyn.Value) (dyn.Value, error) { } } - // Sort depends_on entries within each task by task_key. The terraform provider - // sorts depends_on on Read (see https://github.com/databricks/terraform-provider-databricks/pull/3000). - // Since depends_on uses TypeList (order-sensitive), the config order must match the - // state order to avoid perpetual drift in terraform plan. - vout, err = dyn.Map(vout, "tasks", dyn.Foreach(func(_ dyn.Path, task dyn.Value) (dyn.Value, error) { - task = sortDependsOn(task, "depends_on") - task, _ = dyn.Map(task, "for_each_task.task", func(_ dyn.Path, inner dyn.Value) (dyn.Value, error) { - return sortDependsOn(inner, "depends_on"), nil - }) - return task, nil - })) - if err != nil { - return dyn.InvalidValue, err - } - // Apply default task source logic vout, err = applyDefaultTaskSource(vout) if err != nil { diff --git a/bundle/deploy/terraform/tfdyn/convert_job_test.go b/bundle/deploy/terraform/tfdyn/convert_job_test.go index fb0f14979f..782075fc7f 100644 --- a/bundle/deploy/terraform/tfdyn/convert_job_test.go +++ b/bundle/deploy/terraform/tfdyn/convert_job_test.go @@ -65,10 +65,6 @@ func TestConvertJob(t *testing.T) { { TaskKey: "task_key_c", JobClusterKey: "job_cluster_key_c", - DependsOn: []jobs.TaskDependency{ - {TaskKey: "task_key_b"}, - {TaskKey: "task_key_a"}, - }, }, { Description: "missing task key 😱", @@ -141,10 +137,6 @@ func TestConvertJob(t *testing.T) { map[string]any{ "task_key": "task_key_c", "job_cluster_key": "job_cluster_key_c", - "depends_on": []any{ - map[string]any{"task_key": "task_key_a"}, - map[string]any{"task_key": "task_key_b"}, - }, }, }, }, out.Job["my_job"]) From 04de73118ad428a463fa5f2638c26b40ff8d5bc1 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Thu, 16 Apr 2026 15:41:57 +0000 Subject: [PATCH 12/14] lint fix --- libs/structs/structdiff/diff_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/libs/structs/structdiff/diff_test.go b/libs/structs/structdiff/diff_test.go index 567287fd8c..474694d5e6 100644 --- a/libs/structs/structdiff/diff_test.go +++ b/libs/structs/structdiff/diff_test.go @@ -654,7 +654,7 @@ func TestGetStructDiffSliceKeys(t *testing.T) { func TestGetStructDiffNestedDependsOn(t *testing.T) { sliceKeys := map[string]KeyFunc{ - "tasks": taskKeyFunc, + "tasks": taskKeyFunc, "tasks[*].depends_on": depKeyFunc, } @@ -665,26 +665,26 @@ func TestGetStructDiffNestedDependsOn(t *testing.T) { }{ { name: "depends_on reordered no diff", - a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}, {TaskKey: "b"}}}}}, - b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "b"}, {TaskKey: "a"}}}}}, + a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}, {TaskKey: "b"}}}}}, + b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "b"}, {TaskKey: "a"}}}}}, want: nil, }, { name: "depends_on field change", - a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a", Outcome: "success"}}}}}, - b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a", Outcome: "failed"}}}}}, + a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a", Outcome: "success"}}}}}, + b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a", Outcome: "failed"}}}}}, want: []ResolvedChange{{Field: "tasks[task_key='c'].depends_on[task_key='a'].outcome", Old: "success", New: "failed"}}, }, { name: "depends_on element added", - a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}}}}}, - b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}, {TaskKey: "b"}}}}}, + a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}}}}}, + b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}, {TaskKey: "b"}}}}}, want: []ResolvedChange{{Field: "tasks[task_key='c'].depends_on[task_key='b']", Old: nil, New: Dep{TaskKey: "b"}}}, }, { name: "depends_on element removed", - a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}, {TaskKey: "b"}}}}}, - b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}}}}}, + a: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}, {TaskKey: "b"}}}}}, + b: Job{Tasks: []Task{{TaskKey: "c", DependsOn: []Dep{{TaskKey: "a"}}}}}, want: []ResolvedChange{{Field: "tasks[task_key='c'].depends_on[task_key='b']", Old: Dep{TaskKey: "b"}, New: nil}}, }, { From 0ab3565512b8b9438f5169aea508628c4ac05a35 Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Mon, 20 Apr 2026 11:43:22 +0000 Subject: [PATCH 13/14] acceptance: update output.txt for keyed depends_on diffs Diff output now identifies task dependency changes by task_key rather than by position, following the depends_on keying introduced in 296468039. Regenerated via make test-update. Task: 006.md Co-authored-by: Isaac --- .../config-remote-sync/job_multiple_tasks/output.txt | 9 ++++++--- .../bundle/config-remote-sync/multiple_files/output.txt | 3 ++- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/acceptance/bundle/config-remote-sync/job_multiple_tasks/output.txt b/acceptance/bundle/config-remote-sync/job_multiple_tasks/output.txt index 0d8b9275a9..24c69a9675 100644 --- a/acceptance/bundle/config-remote-sync/job_multiple_tasks/output.txt +++ b/acceptance/bundle/config-remote-sync/job_multiple_tasks/output.txt @@ -8,7 +8,8 @@ Deployment complete! Detected changes in 2 resource(s): Resource: resources.jobs.my_job - tasks[task_key='c_task'].depends_on[0].task_key: replace + tasks[task_key='c_task'].depends_on[task_key='b_task']: add + tasks[task_key='c_task'].depends_on[task_key='d_task']: remove tasks[task_key='c_task'].new_cluster.num_workers: replace tasks[task_key='c_task'].timeout_seconds: add tasks[task_key='d_task']: remove @@ -83,8 +84,10 @@ Resource: resources.jobs.rename_task_job tasks[task_key='a_task'].notebook_task.notebook_path: replace tasks[task_key='b_task']: remove tasks[task_key='b_task_renamed']: add - tasks[task_key='c_task'].depends_on[0].task_key: replace - tasks[task_key='d_task'].depends_on[0].task_key: replace + tasks[task_key='c_task'].depends_on[task_key='b_task']: remove + tasks[task_key='c_task'].depends_on[task_key='b_task_renamed']: add + tasks[task_key='d_task'].depends_on[task_key='b_task']: remove + tasks[task_key='d_task'].depends_on[task_key='b_task_renamed']: add tasks[task_key='synced_task']: add diff --git a/acceptance/bundle/config-remote-sync/multiple_files/output.txt b/acceptance/bundle/config-remote-sync/multiple_files/output.txt index e616ca008c..aa943ca185 100644 --- a/acceptance/bundle/config-remote-sync/multiple_files/output.txt +++ b/acceptance/bundle/config-remote-sync/multiple_files/output.txt @@ -12,7 +12,8 @@ Detected changes in 2 resource(s): Resource: resources.jobs.job_one max_concurrent_runs: replace - tasks[task_key='a_task'].depends_on[0].task_key: replace + tasks[task_key='a_task'].depends_on[task_key='c_task']: remove + tasks[task_key='a_task'].depends_on[task_key='c_task_renamed']: add tasks[task_key='c_task']: remove tasks[task_key='c_task_renamed']: add tasks[task_key='synced_task']: add From 81f8c1fff5551ce45ec8cd7b27b104b62efbb28d Mon Sep 17 00:00:00 2001 From: Denis Bilenko Date: Tue, 21 Apr 2026 16:54:21 +0200 Subject: [PATCH 14/14] Update changelog --- NEXT_CHANGELOG.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 9dd5e9a629..18e827e4d3 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -11,10 +11,8 @@ ### Bundles * Remove `experimental-jobs-as-code` template, superseded by `pydabs` ([#4999](https://github.com/databricks/cli/pull/4999)). -* engine/direct: Fix phantom diffs from `depends_on` reordering in job tasks (denik/jobs-depends-on) -* engine/direct: Fix phantom diffs from `depends_on` reordering in job tasks ([#4990](https://github.com/databricks/cli/pull/4990)) - * engine/direct: Added support for Vector Search Endpoints ([#4887](https://github.com/databricks/cli/pull/4887)) +* engine/direct: Fix phantom diffs from `depends_on` reordering in job tasks ([#4990](https://github.com/databricks/cli/pull/4990)) ### Dependency updates