From c20164ab311f6b4e0f700dbb70b1f867d5c9b70b Mon Sep 17 00:00:00 2001 From: James Bardin Date: Fri, 8 Feb 2019 16:33:05 -0500 Subject: [PATCH] fix CoerceValue to handle changing dynamic types Objects with DynamicPseudoType attributes can't be coerced within a map if a concrete type is set. Change the Value type used to an Object when there is a type mismatch. --- configs/configschema/coerce_value.go | 24 +++++- configs/configschema/coerce_value_test.go | 95 +++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/configs/configschema/coerce_value.go b/configs/configschema/coerce_value.go index bae5733df7..877e860f3f 100644 --- a/configs/configschema/coerce_value.go +++ b/configs/configschema/coerce_value.go @@ -225,7 +225,29 @@ func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) { elems[key.AsString()] = val } } - attrs[typeName] = cty.MapVal(elems) + + // If the attribute values here contain any DynamicPseudoTypes, + // the concrete type must be an object. + useObject := false + switch { + case coll.Type().IsObjectType(): + useObject = true + default: + // It's possible that we were given a map, and need to coerce it to an object + ety := coll.Type().ElementType() + for _, v := range elems { + if !v.Type().Equals(ety) { + useObject = true + break + } + } + } + + if useObject { + attrs[typeName] = cty.ObjectVal(elems) + } else { + attrs[typeName] = cty.MapVal(elems) + } default: attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType()) } diff --git a/configs/configschema/coerce_value_test.go b/configs/configschema/coerce_value_test.go index b7cc5f632d..3286751a35 100644 --- a/configs/configschema/coerce_value_test.go +++ b/configs/configschema/coerce_value_test.go @@ -436,6 +436,101 @@ func TestCoerceValue(t *testing.T) { }), ``, }, + "dynamic value attributes": { + &Block{ + BlockTypes: map[string]*NestedBlock{ + "foo": { + Nesting: NestingMap, + Block: Block{ + Attributes: map[string]*Attribute{ + "bar": { + Type: cty.String, + Optional: true, + Computed: true, + }, + "baz": { + Type: cty.DynamicPseudoType, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.ObjectVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "bar": cty.StringVal("beep"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "bar": cty.StringVal("boop"), + "baz": cty.NumberIntVal(8), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.ObjectVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "bar": cty.StringVal("beep"), + "baz": cty.NullVal(cty.DynamicPseudoType), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "bar": cty.StringVal("boop"), + "baz": cty.NumberIntVal(8), + }), + }), + }), + ``, + }, + "dynamic attributes in map": { + // Convert a block represented as a map to an object if a + // DynamicPseudoType causes the element types to mismatch. + &Block{ + BlockTypes: map[string]*NestedBlock{ + "foo": { + Nesting: NestingMap, + Block: Block{ + Attributes: map[string]*Attribute{ + "bar": { + Type: cty.String, + Optional: true, + Computed: true, + }, + "baz": { + Type: cty.DynamicPseudoType, + Optional: true, + Computed: true, + }, + }, + }, + }, + }, + }, + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.MapVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "bar": cty.StringVal("beep"), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "bar": cty.StringVal("boop"), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.ObjectVal(map[string]cty.Value{ + "a": cty.ObjectVal(map[string]cty.Value{ + "bar": cty.StringVal("beep"), + "baz": cty.NullVal(cty.DynamicPseudoType), + }), + "b": cty.ObjectVal(map[string]cty.Value{ + "bar": cty.StringVal("boop"), + "baz": cty.NullVal(cty.DynamicPseudoType), + }), + }), + }), + ``, + }, } for name, test := range tests {