diff --git a/plans/objchange/objchange.go b/plans/objchange/objchange.go index 5a8af1481f..ab16f68a4b 100644 --- a/plans/objchange/objchange.go +++ b/plans/objchange/objchange.go @@ -333,8 +333,9 @@ func setElementCompareValue(schema *configschema.Block, v cty.Value, isConfig bo } for name, blockType := range schema.BlockTypes { - switch blockType.Nesting { + elementType := blockType.Block.ImpliedType() + switch blockType.Nesting { case configschema.NestingSingle, configschema.NestingGroup: attrs[name] = setElementCompareValue(&blockType.Block, v.GetAttr(name), isConfig) @@ -344,32 +345,44 @@ func setElementCompareValue(schema *configschema.Block, v cty.Value, isConfig bo attrs[name] = cv continue } + if l := cv.LengthInt(); l > 0 { elems := make([]cty.Value, 0, l) for it := cv.ElementIterator(); it.Next(); { _, ev := it.Element() elems = append(elems, setElementCompareValue(&blockType.Block, ev, isConfig)) } - if blockType.Nesting == configschema.NestingSet { + + switch { + case blockType.Nesting == configschema.NestingSet: // SetValEmpty would panic if given elements that are not // all of the same type, but that's guaranteed not to // happen here because our input value was _already_ a // set and we've not changed the types of any elements here. attrs[name] = cty.SetVal(elems) - } else { + + // NestingList cases + case elementType.HasDynamicTypes(): attrs[name] = cty.TupleVal(elems) + default: + attrs[name] = cty.ListVal(elems) } } else { - if blockType.Nesting == configschema.NestingSet { - attrs[name] = cty.SetValEmpty(blockType.Block.ImpliedType()) - } else { + switch { + case blockType.Nesting == configschema.NestingSet: + attrs[name] = cty.SetValEmpty(elementType) + + // NestingList cases + case elementType.HasDynamicTypes(): attrs[name] = cty.EmptyTupleVal + default: + attrs[name] = cty.ListValEmpty(elementType) } } case configschema.NestingMap: cv := v.GetAttr(name) - if cv.IsNull() || !cv.IsKnown() { + if cv.IsNull() || !cv.IsKnown() || cv.LengthInt() == 0 { attrs[name] = cv continue } @@ -378,7 +391,13 @@ func setElementCompareValue(schema *configschema.Block, v cty.Value, isConfig bo kv, ev := it.Element() elems[kv.AsString()] = setElementCompareValue(&blockType.Block, ev, isConfig) } - attrs[name] = cty.ObjectVal(elems) + + switch { + case elementType.HasDynamicTypes(): + attrs[name] = cty.ObjectVal(elems) + default: + attrs[name] = cty.MapVal(elems) + } default: // Should never happen, since the above cases are comprehensive. diff --git a/plans/objchange/objchange_test.go b/plans/objchange/objchange_test.go index 97f749acae..065e4add88 100644 --- a/plans/objchange/objchange_test.go +++ b/plans/objchange/objchange_test.go @@ -440,7 +440,7 @@ func TestProposedNewObject(t *testing.T) { }), "b": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("blep"), - "baz": cty.StringVal("boot"), + "baz": cty.ListVal([]cty.Value{cty.StringVal("boot")}), }), }), }), @@ -452,7 +452,7 @@ func TestProposedNewObject(t *testing.T) { }), "c": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bosh"), - "baz": cty.NullVal(cty.String), + "baz": cty.NullVal(cty.List(cty.String)), }), }), }), @@ -464,7 +464,7 @@ func TestProposedNewObject(t *testing.T) { }), "c": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bosh"), - "baz": cty.NullVal(cty.String), + "baz": cty.NullVal(cty.List(cty.String)), }), }), }), @@ -572,6 +572,288 @@ func TestProposedNewObject(t *testing.T) { }), }), }, + "nested list in set": { + &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "foo": { + Nesting: configschema.NestingSet, + Block: configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "bar": { + Nesting: configschema.NestingList, + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "baz": { + Type: cty.String, + }, + "qux": { + Type: cty.String, + Computed: true, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "baz": cty.StringVal("beep"), + "qux": cty.StringVal("boop"), + }), + }), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "baz": cty.StringVal("beep"), + "qux": cty.NullVal(cty.String), + }), + }), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.ListVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "baz": cty.StringVal("beep"), + "qux": cty.StringVal("boop"), + }), + }), + }), + }), + }), + }, + "empty nested list in set": { + &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "foo": { + Nesting: configschema.NestingSet, + Block: configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "bar": { + Nesting: configschema.NestingList, + Block: configschema.Block{}, + }, + }, + }, + }, + }, + }, + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.ListValEmpty((&configschema.Block{}).ImpliedType()), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.ListValEmpty((&configschema.Block{}).ImpliedType()), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.ListValEmpty((&configschema.Block{}).ImpliedType()), + }), + }), + }), + }, + "nested list with dynamic in set": { + &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "foo": { + Nesting: configschema.NestingSet, + Block: configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "bar": { + Nesting: configschema.NestingList, + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "baz": { + Type: cty.DynamicPseudoType, + }, + }, + }, + }, + }, + }, + }, + }, + }, + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.TupleVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "baz": cty.StringVal("true"), + }), + cty.ObjectVal(map[string]cty.Value{ + "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), + }), + }), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.TupleVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "baz": cty.StringVal("true"), + }), + cty.ObjectVal(map[string]cty.Value{ + "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), + }), + }), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.TupleVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "baz": cty.StringVal("true"), + }), + cty.ObjectVal(map[string]cty.Value{ + "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), + }), + }), + }), + }), + }), + }, + "nested map with dynamic in set": { + &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "foo": { + Nesting: configschema.NestingSet, + Block: configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "bar": { + Nesting: configschema.NestingMap, + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "baz": { + Type: cty.DynamicPseudoType, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.ObjectVal(map[string]cty.Value{ + "bing": cty.ObjectVal(map[string]cty.Value{ + "baz": cty.StringVal("true"), + }), + "bang": cty.ObjectVal(map[string]cty.Value{ + "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), + }), + }), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.ObjectVal(map[string]cty.Value{ + "bing": cty.ObjectVal(map[string]cty.Value{ + "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), + }), + }), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.ObjectVal(map[string]cty.Value{ + "bing": cty.ObjectVal(map[string]cty.Value{ + "baz": cty.ListVal([]cty.Value{cty.StringVal("true")}), + }), + }), + }), + }), + }), + }, + "empty nested map in set": { + &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "foo": { + Nesting: configschema.NestingSet, + Block: configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + "bar": { + Nesting: configschema.NestingMap, + Block: configschema.Block{ + Attributes: map[string]*configschema.Attribute{ + "baz": { + Type: cty.String, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.MapValEmpty(cty.Object(map[string]cty.Type{ + "baz": cty.String, + })), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.MapVal(map[string]cty.Value{ + "bing": cty.ObjectVal(map[string]cty.Value{ + "baz": cty.StringVal("true"), + }), + }), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "foo": cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "bar": cty.MapVal(map[string]cty.Value{ + "bing": cty.ObjectVal(map[string]cty.Value{ + "baz": cty.StringVal("true"), + }), + }), + }), + }), + }), + }, } for name, test := range tests {