From 7f97bd44897e62e2d9d2d6449d81727aec9680c2 Mon Sep 17 00:00:00 2001 From: Alisdair McDiarmid Date: Fri, 12 Mar 2021 18:14:14 -0500 Subject: [PATCH] functions: Fix missing defaults for objects/tuples If no default is specified for a nested optional structural typed attribute, the defaults function should just pass through its input. Before this commit the function assumed that the fallback value was always of the correct type, which would panic. --- lang/funcs/defaults.go | 12 ++++++++-- lang/funcs/defaults_test.go | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/lang/funcs/defaults.go b/lang/funcs/defaults.go index c5acf26e89..0e4fd41ffa 100644 --- a/lang/funcs/defaults.go +++ b/lang/funcs/defaults.go @@ -97,7 +97,11 @@ func defaultsApply(input, fallback cty.Value) cty.Value { // For structural types, a null input value must be passed through. We // do not apply default values for missing optional structural values, // only their contents. - if input.IsNull() { + // + // We also pass through the input if the fallback value is null. This + // can happen if the given defaults do not include a value for this + // attribute. + if input.IsNull() || fallback.IsNull() { return input } atys := wantTy.AttributeTypes() @@ -116,7 +120,11 @@ func defaultsApply(input, fallback cty.Value) cty.Value { // For structural types, a null input value must be passed through. We // do not apply default values for missing optional structural values, // only their contents. - if input.IsNull() { + // + // We also pass through the input if the fallback value is null. This + // can happen if the given defaults do not include a value for this + // attribute. + if input.IsNull() || fallback.IsNull() { return input } diff --git a/lang/funcs/defaults_test.go b/lang/funcs/defaults_test.go index a77ff316a6..3d36d75cc8 100644 --- a/lang/funcs/defaults_test.go +++ b/lang/funcs/defaults_test.go @@ -398,6 +398,51 @@ func TestDefaults(t *testing.T) { "b": cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})), }), }, + // When applying default values to structural types, we permit null + // values in the defaults, and just pass through the input value. + { + Input: cty.ObjectVal(map[string]cty.Value{ + "a": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "p": cty.StringVal("xyz"), + "q": cty.StringVal("xyz"), + }), + }), + "b": cty.SetVal([]cty.Value{ + cty.TupleVal([]cty.Value{ + cty.NumberIntVal(0), + cty.NumberIntVal(2), + }), + cty.TupleVal([]cty.Value{ + cty.NumberIntVal(1), + cty.NumberIntVal(3), + }), + }), + "c": cty.NullVal(cty.String), + }), + Defaults: cty.ObjectVal(map[string]cty.Value{ + "c": cty.StringVal("tada"), + }), + Want: cty.ObjectVal(map[string]cty.Value{ + "a": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "p": cty.StringVal("xyz"), + "q": cty.StringVal("xyz"), + }), + }), + "b": cty.SetVal([]cty.Value{ + cty.TupleVal([]cty.Value{ + cty.NumberIntVal(0), + cty.NumberIntVal(2), + }), + cty.TupleVal([]cty.Value{ + cty.NumberIntVal(1), + cty.NumberIntVal(3), + }), + }), + "c": cty.StringVal("tada"), + }), + }, // When applying default values to collection types, null collections in the // input should result in empty collections in the output. {