From dd609e4a89d115c57dde69774eea939680b069a6 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Sun, 8 Feb 2026 13:59:05 +0100 Subject: [PATCH 1/5] Capture weird json output with test --- .../config-remote-sync/job_fields/output.txt | 19 +++++++++++-------- .../config-remote-sync/job_fields/script | 3 ++- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/acceptance/bundle/config-remote-sync/job_fields/output.txt b/acceptance/bundle/config-remote-sync/job_fields/output.txt index a434e8c53c..9c2f026447 100644 --- a/acceptance/bundle/config-remote-sync/job_fields/output.txt +++ b/acceptance/bundle/config-remote-sync/job_fields/output.txt @@ -14,7 +14,9 @@ Resource: resources.jobs.my_job email_notifications.on_failure: add parameters: replace tags['team']: add - trigger.periodic.interval: replace + trigger.pause_status: add + trigger.periodic: remove + trigger.table_update: add @@ -29,7 +31,7 @@ Resource: resources.jobs.my_job - resources: jobs: -@@ -8,12 +7,17 @@ +@@ -8,13 +7,15 @@ on_success: - success@example.com + no_alert_for_skipped_runs: true @@ -40,19 +42,20 @@ Resource: resources.jobs.my_job - default: main - - name: env - default: dev +- trigger: +- periodic: +- interval: 1 +- unit: DAYS + - default: main + name: catalog + - default: dev + name: env + - default: us-east-1 + name: region - trigger: - periodic: -- interval: 1 -+ interval: 2 - unit: DAYS ++ trigger: {pause_status: UNPAUSED, table_update: {table_names: [samples.nyctaxi.trips]}} environments: -@@ -31,5 +35,6 @@ + - environment_key: default +@@ -31,5 +32,6 @@ node_type_id: [NODE_TYPE_ID] num_workers: 1 - diff --git a/acceptance/bundle/config-remote-sync/job_fields/script b/acceptance/bundle/config-remote-sync/job_fields/script index 29dd2d3104..d09c724e3c 100755 --- a/acceptance/bundle/config-remote-sync/job_fields/script +++ b/acceptance/bundle/config-remote-sync/job_fields/script @@ -13,7 +13,8 @@ edit_resource.py jobs $job_id < Date: Mon, 9 Feb 2026 12:05:12 +0100 Subject: [PATCH 2/5] Fix yaml flow formatting in added nodes --- .../formatting_preserved/databricks.yml.tmpl | 5 ++ .../formatting_preserved/output.txt | 7 +- .../config-remote-sync/job_fields/output.txt | 17 ++-- bundle/configsync/patch.go | 88 ++++++++++++++++++- go.mod | 2 +- 5 files changed, 109 insertions(+), 10 deletions(-) diff --git a/acceptance/bundle/config-remote-sync/formatting_preserved/databricks.yml.tmpl b/acceptance/bundle/config-remote-sync/formatting_preserved/databricks.yml.tmpl index 22fa31dd68..f35086833f 100644 --- a/acceptance/bundle/config-remote-sync/formatting_preserved/databricks.yml.tmpl +++ b/acceptance/bundle/config-remote-sync/formatting_preserved/databricks.yml.tmpl @@ -23,3 +23,8 @@ resources: tags: env: dev # environment tag team: data-eng + + # Flow-style formatting (should be preserved) + parameters: + - {name: catalog, default: main} + - {name: schema, default: dev} diff --git a/acceptance/bundle/config-remote-sync/formatting_preserved/output.txt b/acceptance/bundle/config-remote-sync/formatting_preserved/output.txt index 524b261d24..7cb9de25a6 100644 --- a/acceptance/bundle/config-remote-sync/formatting_preserved/output.txt +++ b/acceptance/bundle/config-remote-sync/formatting_preserved/output.txt @@ -31,9 +31,14 @@ Resource: resources.jobs.my_job + max_concurrent_runs: 5 # Task configuration tasks: -@@ -19,5 +17,4 @@ +@@ -19,10 +17,8 @@ node_type_id: [NODE_TYPE_ID] num_workers: 1 # inline comment about workers - # Tags for categorization tags: + env: dev # environment tag + team: data-eng +- + # Flow-style formatting (should be preserved) + parameters: diff --git a/acceptance/bundle/config-remote-sync/job_fields/output.txt b/acceptance/bundle/config-remote-sync/job_fields/output.txt index 9c2f026447..e117ad9d1c 100644 --- a/acceptance/bundle/config-remote-sync/job_fields/output.txt +++ b/acceptance/bundle/config-remote-sync/job_fields/output.txt @@ -31,7 +31,7 @@ Resource: resources.jobs.my_job - resources: jobs: -@@ -8,13 +7,15 @@ +@@ -8,13 +7,19 @@ on_success: - success@example.com + no_alert_for_skipped_runs: true @@ -42,20 +42,23 @@ Resource: resources.jobs.my_job - default: main - - name: env - default: dev -- trigger: -- periodic: -- interval: 1 -- unit: DAYS + - default: main + name: catalog + - default: dev + name: env + - default: us-east-1 + name: region -+ trigger: {pause_status: UNPAUSED, table_update: {table_names: [samples.nyctaxi.trips]}} + trigger: +- periodic: +- interval: 1 +- unit: DAYS ++ pause_status: UNPAUSED ++ table_update: ++ table_names: ++ - samples.nyctaxi.trips environments: - environment_key: default -@@ -31,5 +32,6 @@ +@@ -31,5 +36,6 @@ node_type_id: [NODE_TYPE_ID] num_workers: 1 - diff --git a/bundle/configsync/patch.go b/bundle/configsync/patch.go index 701894b451..e275da548a 100644 --- a/bundle/configsync/patch.go +++ b/bundle/configsync/patch.go @@ -1,6 +1,7 @@ package configsync import ( + "bytes" "context" "errors" "fmt" @@ -14,12 +15,14 @@ import ( "github.com/databricks/cli/libs/structs/structpath" "github.com/palantir/pkg/yamlpatch/gopkgv3yamlpatcher" "github.com/palantir/pkg/yamlpatch/yamlpatch" + "gopkg.in/yaml.v3" ) // ApplyChangesToYAML generates YAML files for the given field changes. func ApplyChangesToYAML(ctx context.Context, b *bundle.Bundle, fieldChanges []FieldChange) ([]FileChange, error) { originalFiles := make(map[string][]byte) modifiedFiles := make(map[string][]byte) + fileFieldChanges := make(map[string][]FieldChange) for _, fieldChange := range fieldChanges { filePath := fieldChange.FilePath @@ -39,14 +42,19 @@ func ApplyChangesToYAML(ctx context.Context, b *bundle.Bundle, fieldChanges []Fi } modifiedFiles[filePath] = modifiedContent + fileFieldChanges[filePath] = append(fileFieldChanges[filePath], fieldChange) } var result []FileChange for filePath := range modifiedFiles { + normalized, err := clearAddedFlowStyle(modifiedFiles[filePath], fileFieldChanges[filePath]) + if err != nil { + return nil, fmt.Errorf("failed to normalize YAML style in %s: %w", filePath, err) + } result = append(result, FileChange{ Path: filePath, OriginalContent: string(originalFiles[filePath]), - ModifiedContent: string(modifiedFiles[filePath]), + ModifiedContent: string(normalized), }) } @@ -261,3 +269,81 @@ func strPathToJSONPointer(pathStr string) (string, error) { } return "/" + strings.Join(parts, "/"), nil } + +// clearAddedFlowStyle clears FlowStyle on YAML nodes along the changed field paths. +// This prevents flow-style formatting (e.g. {key: value}) that yaml.v3 introduces +// when empty mappings are serialized as "{}" during patch operations +func clearAddedFlowStyle(content []byte, fieldChanges []FieldChange) ([]byte, error) { + var doc yaml.Node + if err := yaml.Unmarshal(content, &doc); err != nil { + return content, nil + } + for _, fc := range fieldChanges { + for _, candidate := range fc.FieldCandidates { + clearFlowStyleAlongPath(&doc, candidate) + } + } + var buf bytes.Buffer + enc := yaml.NewEncoder(&buf) + enc.SetIndent(2) + if err := enc.Encode(&doc); err != nil { + return nil, err + } + return buf.Bytes(), enc.Close() +} + +// clearFlowStyleAlongPath navigates the YAML tree along the given structpath, +// clearing FlowStyle on every node from root to leaf (inclusive). +func clearFlowStyleAlongPath(doc *yaml.Node, pathStr string) { + node, err := structpath.Parse(pathStr) + if err != nil { + return + } + + current := doc + if current.Kind == yaml.DocumentNode && len(current.Content) > 0 { + current = current.Content[0] + } + + for _, n := range node.AsSlice() { + current.Style &^= yaml.FlowStyle + + if key, ok := n.StringKey(); ok { + if current.Kind != yaml.MappingNode { + return + } + found := false + // current.Content: [key1, val1, key2, val2, ...] + for i := 0; i+1 < len(current.Content); i += 2 { + if current.Content[i].Value == key { + current = current.Content[i+1] + found = true + break + } + } + if !found { + return + } + continue + } + + if idx, ok := n.Index(); ok { + if current.Kind != yaml.SequenceNode || idx < 0 || idx >= len(current.Content) { + return + } + current = current.Content[idx] + continue + } + + return + } + + clearFlowStyleNodes(current) +} + +func clearFlowStyleNodes(node *yaml.Node) { + node.Style &^= yaml.FlowStyle + for _, child := range node.Content { + clearFlowStyleNodes(child) + } +} diff --git a/go.mod b/go.mod index 27f170cc04..759b9b4895 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( // Dependencies for experimental MCP commands require github.com/google/jsonschema-go v0.4.2 // MIT -require gopkg.in/yaml.v3 v3.0.1 // indirect +require gopkg.in/yaml.v3 v3.0.1 require ( cloud.google.com/go/auth v0.16.5 // indirect From e0bcfc66f65555bf1249cdcbe51bb49d6fc5502b Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Mon, 9 Feb 2026 14:13:04 +0100 Subject: [PATCH 3/5] Require UC to be enabled for job fields test --- acceptance/bundle/config-remote-sync/job_fields/out.test.toml | 1 + acceptance/bundle/config-remote-sync/job_fields/test.toml | 1 + 2 files changed, 2 insertions(+) diff --git a/acceptance/bundle/config-remote-sync/job_fields/out.test.toml b/acceptance/bundle/config-remote-sync/job_fields/out.test.toml index 382d99ed10..152a1f10a9 100644 --- a/acceptance/bundle/config-remote-sync/job_fields/out.test.toml +++ b/acceptance/bundle/config-remote-sync/job_fields/out.test.toml @@ -1,5 +1,6 @@ Local = true Cloud = true +RequiresUnityCatalog = true [GOOS] windows = false diff --git a/acceptance/bundle/config-remote-sync/job_fields/test.toml b/acceptance/bundle/config-remote-sync/job_fields/test.toml index 3894d66826..625c660c61 100644 --- a/acceptance/bundle/config-remote-sync/job_fields/test.toml +++ b/acceptance/bundle/config-remote-sync/job_fields/test.toml @@ -1,4 +1,5 @@ Cloud = true +RequiresUnityCatalog = true RecordRequests = false Ignore = [".databricks", "dummy.whl", "databricks.yml", "databricks.yml.backup"] From d05c9b8cc5f5d5be7a671c6e1d25e26c502a6855 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Mon, 9 Feb 2026 16:07:47 +0100 Subject: [PATCH 4/5] Updated to use go.yaml.in/yaml/v3 --- bundle/configsync/patch.go | 2 +- go.mod | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bundle/configsync/patch.go b/bundle/configsync/patch.go index e275da548a..284d00f99a 100644 --- a/bundle/configsync/patch.go +++ b/bundle/configsync/patch.go @@ -15,7 +15,7 @@ import ( "github.com/databricks/cli/libs/structs/structpath" "github.com/palantir/pkg/yamlpatch/gopkgv3yamlpatcher" "github.com/palantir/pkg/yamlpatch/yamlpatch" - "gopkg.in/yaml.v3" + "go.yaml.in/yaml/v3" ) // ApplyChangesToYAML generates YAML files for the given field changes. diff --git a/go.mod b/go.mod index 759b9b4895..27f170cc04 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,7 @@ require ( // Dependencies for experimental MCP commands require github.com/google/jsonschema-go v0.4.2 // MIT -require gopkg.in/yaml.v3 v3.0.1 +require gopkg.in/yaml.v3 v3.0.1 // indirect require ( cloud.google.com/go/auth v0.16.5 // indirect From 3e07780e501e1a8d0f5413203242014734ffbb65 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Tue, 10 Feb 2026 23:36:58 +0100 Subject: [PATCH 5/5] Fix lint --- bundle/configsync/patch.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundle/configsync/patch.go b/bundle/configsync/patch.go index 98a1e74421..e7a321816a 100644 --- a/bundle/configsync/patch.go +++ b/bundle/configsync/patch.go @@ -296,7 +296,7 @@ func clearAddedFlowStyle(content []byte, fieldChanges []FieldChange) ([]byte, er // clearFlowStyleAlongPath navigates the YAML tree along the given structpath, // clearing FlowStyle on every node from root to leaf (inclusive). func clearFlowStyleAlongPath(doc *yaml.Node, pathStr string) { - node, err := structpath.Parse(pathStr) + node, err := structpath.ParsePath(pathStr) if err != nil { return }