Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .nextchanges/bundles/suppress-anchor-container-warnings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Do not emit "unknown field" warnings for YAML anchors grouped in a list or map, matching the existing suppression for standalone anchors ([#5975](https://github.com/databricks/cli/pull/5975)).
48 changes: 48 additions & 0 deletions acceptance/bundle/validate/anchor_containers/databricks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
bundle:
name: test-bundle

# This test verifies that anchors grouped in a list or a map do not trigger
# "unknown field" warnings. The suppression recurses into containers whose
# elements are all anchors. A container that also holds a non-anchor element is
# not suppressed: it is reported as an unknown field, since it is more likely a
# misplaced configuration value than a block of reusable anchors.

task_defaults: &task_defaults
max_retries: 3

# List of anchors.
parameter_items:
- &p_first
name: first
default: one
- &p_second
name: second
default: two

# Map of anchors.
parameter_map:
entry: &p_map_entry
name: first

# Container nested inside a container: a map whose value is a list of anchors.
nested_parameters:
group:
- &p_nested
name: nested

# Mixed container: one anchor and one non-anchor element. This is reported as an
# unknown field because not all elements are anchor containers.
mixed_parameters:
- &p_mixed
name: mixed
- name: plain

resources:
jobs:
my_job:
name: "test job with anchors"
tasks:
- task_key: "main"
<<: *task_defaults
notebook_task:
notebook_path: "/some/notebook"
5 changes: 5 additions & 0 deletions acceptance/bundle/validate/anchor_containers/out.test.toml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 12 additions & 0 deletions acceptance/bundle/validate/anchor_containers/output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@

>>> [CLI] bundle validate
Warning: unknown field: mixed_parameters
in databricks.yml:35:1

Name: test-bundle
Target: default
Workspace:
User: [USERNAME]
Path: /Workspace/Users/[USERNAME]/.bundle/test-bundle/default

Found 1 warning
1 change: 1 addition & 0 deletions acceptance/bundle/validate/anchor_containers/script
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
trace $CLI bundle validate
32 changes: 31 additions & 1 deletion libs/dyn/convert/normalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,36 @@ func typeMismatch(expected dyn.Kind, src dyn.Value, path dyn.Path) diag.Diagnost
}
}

// isAnchorContainer reports whether v is a YAML anchor or a non-empty
// sequence/map composed entirely of anchor containers. Anchors define reusable
// blocks and must not trigger "unknown field" warnings, including when nested
// inside a container.
func isAnchorContainer(v dyn.Value) bool {
if v.IsAnchor() {
return true
}

var elements []dyn.Value
switch v.Kind() {
case dyn.KindSequence:
elements = v.MustSequence()
case dyn.KindMap:
elements = v.MustMap().Values()
default:
return false
}

if len(elements) == 0 {
return false
}
for _, e := range elements {
if !isAnchorContainer(e) {
return false
}
Comment thread
janniklasrose marked this conversation as resolved.
}
return true
}

func (n normalizeOptions) normalizeStruct(typ reflect.Type, src dyn.Value, seen []reflect.Type, path dyn.Path) (dyn.Value, diag.Diagnostics) {
var diags diag.Diagnostics

Expand All @@ -101,7 +131,7 @@ func (n normalizeOptions) normalizeStruct(typ reflect.Type, src dyn.Value, seen
fieldName := pk.MustString()
index, ok := info.Fields[fieldName]
if !ok {
if !pv.IsAnchor() {
if !isAnchorContainer(pv) {
// Special case: provide a more helpful message for "valueFrom" vs "value_from"
if fieldName == "valueFrom" {
if _, hasValueFrom := info.Fields["value_from"]; hasValueFrom {
Expand Down
73 changes: 73 additions & 0 deletions libs/dyn/convert/normalize_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,79 @@ func TestNormalizeAnchors(t *testing.T) {
}, vout.AsAny())
}

func TestNormalizeAnchorContainers(t *testing.T) {
type Tmp struct {
Foo string `json:"foo"`
}

anchor := func() dyn.Value {
return dyn.V(map[string]dyn.Value{"name": dyn.V("x")}).MarkAnchor()
}

tcases := []struct {
name string
value dyn.Value
wantWarn bool
}{
{
name: "list of anchors",
value: dyn.V([]dyn.Value{anchor(), anchor()}),
wantWarn: false,
},
{
name: "map of anchors",
value: dyn.V(map[string]dyn.Value{
"a": anchor(),
"b": anchor(),
}),
wantWarn: false,
},
{
name: "nested list of anchors",
value: dyn.V([]dyn.Value{dyn.V([]dyn.Value{anchor()})}),
wantWarn: false,
},
{
name: "list with a non-anchor element",
value: dyn.V([]dyn.Value{anchor(), dyn.V(map[string]dyn.Value{"name": dyn.V("x")})}),
wantWarn: true,
},
{
name: "empty list",
value: dyn.V([]dyn.Value{}),
wantWarn: true,
},
{
name: "empty map",
value: dyn.V(map[string]dyn.Value{}),
wantWarn: true,
},
}

for _, tc := range tcases {
t.Run(tc.name, func(t *testing.T) {
var typ Tmp
vin := dyn.V(map[string]dyn.Value{
"foo": dyn.V("bar"),
"thing": tc.value,
})

vout, diags := Normalize(typ, vin)
if tc.wantWarn {
assert.Len(t, diags, 1)
assert.Equal(t, "unknown field: thing", diags[0].Summary)
} else {
assert.Empty(t, diags)
}

// The unknown field is never retained regardless of the warning.
assert.Equal(t, map[string]any{
"foo": "bar",
}, vout.AsAny())
})
}
}

func TestNormalizeAnyFromSlice(t *testing.T) {
var typ any
v1 := dyn.NewValue(1, []dyn.Location{{File: "file", Line: 1, Column: 1}})
Expand Down
Loading