package objchange import ( "testing" "github.com/apparentlymart/go-dump/dump" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/configs/configschema" ) func TestProposedNew(t *testing.T) { tests := map[string]struct { Schema *configschema.Block Prior cty.Value Config cty.Value Want cty.Value }{ "empty": { &configschema.Block{}, cty.EmptyObjectVal, cty.EmptyObjectVal, cty.EmptyObjectVal, }, "no prior": { &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "foo": { Type: cty.String, Optional: true, }, "bar": { Type: cty.String, Computed: true, }, "bloop": { NestedType: &configschema.Object{ Nesting: configschema.NestingSingle, Attributes: map[string]*configschema.Attribute{ "blop": { Type: cty.String, Required: true, }, }, }, Computed: true, }, }, BlockTypes: map[string]*configschema.NestedBlock{ "baz": { Nesting: configschema.NestingSingle, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "boz": { Type: cty.String, Optional: true, Computed: true, }, "biz": { Type: cty.String, Optional: true, Computed: true, }, }, }, }, }, }, cty.NullVal(cty.DynamicPseudoType), cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("hello"), "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ "blop": cty.String, })), "bar": cty.NullVal(cty.String), "baz": cty.ObjectVal(map[string]cty.Value{ "boz": cty.StringVal("world"), // An unknown in the config represents a situation where // an argument is explicitly set to an expression result // that is derived from an unknown value. This is distinct // from leaving it null, which allows the provider itself // to decide the value during PlanResourceChange. "biz": cty.UnknownVal(cty.String), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("hello"), // unset computed attributes are null in the proposal; provider // usually changes them to "unknown" during PlanResourceChange, // to indicate that the value will be decided during apply. "bar": cty.NullVal(cty.String), "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ "blop": cty.String, })), "baz": cty.ObjectVal(map[string]cty.Value{ "boz": cty.StringVal("world"), "biz": cty.UnknownVal(cty.String), // explicit unknown preserved from config }), }), }, "null block remains null": { &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "foo": { Type: cty.String, Optional: true, }, "bloop": { NestedType: &configschema.Object{ Nesting: configschema.NestingSingle, Attributes: map[string]*configschema.Attribute{ "blop": { Type: cty.String, Required: true, }, }, }, Computed: true, }, }, BlockTypes: map[string]*configschema.NestedBlock{ "baz": { Nesting: configschema.NestingSingle, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "boz": { Type: cty.String, Optional: true, Computed: true, }, }, }, }, }, }, cty.NullVal(cty.DynamicPseudoType), cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("bar"), "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ "blop": cty.String, })), "baz": cty.NullVal(cty.Object(map[string]cty.Type{ "boz": cty.String, })), }), // The bloop attribue and baz block does not exist in the config, // and therefore shouldn't be planned. cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("bar"), "bloop": cty.NullVal(cty.Object(map[string]cty.Type{ "blop": cty.String, })), "baz": cty.NullVal(cty.Object(map[string]cty.Type{ "boz": cty.String, })), }), }, "no prior with set": { // This one is here because our handling of sets is more complex // than others (due to the fuzzy correlation heuristic) and // historically that caused us some panic-related grief. &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "baz": { Nesting: configschema.NestingSet, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "boz": { Type: cty.String, Optional: true, Computed: true, }, }, }, }, }, Attributes: map[string]*configschema.Attribute{ "bloop": { NestedType: &configschema.Object{ Nesting: configschema.NestingSet, Attributes: map[string]*configschema.Attribute{ "blop": { Type: cty.String, Required: true, }, }, }, Computed: true, Optional: true, }, }, }, cty.NullVal(cty.DynamicPseudoType), cty.ObjectVal(map[string]cty.Value{ "baz": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "boz": cty.StringVal("world"), }), }), "bloop": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("blub"), }), }), }), cty.ObjectVal(map[string]cty.Value{ "baz": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "boz": cty.StringVal("world"), }), }), "bloop": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("blub"), }), }), }), }, "prior attributes": { &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "foo": { Type: cty.String, Optional: true, }, "bar": { Type: cty.String, Computed: true, }, "baz": { Type: cty.String, Optional: true, Computed: true, }, "boz": { Type: cty.String, Optional: true, Computed: true, }, "bloop": { NestedType: &configschema.Object{ Nesting: configschema.NestingSingle, Attributes: map[string]*configschema.Attribute{ "blop": { Type: cty.String, Required: true, }, }, }, Optional: true, }, }, }, cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("bonjour"), "bar": cty.StringVal("petit dejeuner"), "baz": cty.StringVal("grande dejeuner"), "boz": cty.StringVal("a la monde"), "bloop": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glub"), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("hello"), "bar": cty.NullVal(cty.String), "baz": cty.NullVal(cty.String), "boz": cty.StringVal("world"), "bloop": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("bleep"), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.StringVal("hello"), "bar": cty.StringVal("petit dejeuner"), "baz": cty.StringVal("grande dejeuner"), "boz": cty.StringVal("world"), "bloop": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("bleep"), }), }), }, "prior nested single": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "foo": { Nesting: configschema.NestingSingle, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "bar": { Type: cty.String, Optional: true, Computed: true, }, "baz": { Type: cty.String, Optional: true, Computed: true, }, }, }, }, }, Attributes: map[string]*configschema.Attribute{ "bloop": { NestedType: &configschema.Object{ Nesting: configschema.NestingSingle, Attributes: map[string]*configschema.Attribute{ "blop": { Type: cty.String, Required: true, }, "bleep": { Type: cty.String, Optional: true, }, }, }, Optional: true, }, }, }, cty.ObjectVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("beep"), "baz": cty.StringVal("boop"), }), "bloop": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glub"), "bleep": cty.NullVal(cty.String), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bap"), "baz": cty.NullVal(cty.String), }), "bloop": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glub"), "bleep": cty.StringVal("beep"), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bap"), "baz": cty.StringVal("boop"), }), "bloop": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glub"), "bleep": cty.StringVal("beep"), }), }), }, "prior nested list": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "foo": { Nesting: configschema.NestingList, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "bar": { Type: cty.String, Optional: true, Computed: true, }, "baz": { Type: cty.String, Optional: true, Computed: true, }, }, }, }, }, Attributes: map[string]*configschema.Attribute{ "bloop": { NestedType: &configschema.Object{ Nesting: configschema.NestingList, Attributes: map[string]*configschema.Attribute{ "blop": { Type: cty.String, Required: true, }, }, }, Optional: true, }, }, }, cty.ObjectVal(map[string]cty.Value{ "foo": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("beep"), "baz": cty.StringVal("boop"), }), }), "bloop": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("bar"), }), cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("baz"), }), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bap"), "baz": cty.NullVal(cty.String), }), cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("blep"), "baz": cty.NullVal(cty.String), }), }), "bloop": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("bar"), }), cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("baz"), }), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bap"), "baz": cty.StringVal("boop"), }), cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("blep"), "baz": cty.NullVal(cty.String), }), }), "bloop": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("bar"), }), cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("baz"), }), }), }), }, "prior nested list with dynamic": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "foo": { Nesting: configschema.NestingList, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "bar": { Type: cty.String, Optional: true, Computed: true, }, "baz": { Type: cty.DynamicPseudoType, Optional: true, Computed: true, }, }, }, }, }, Attributes: map[string]*configschema.Attribute{ "bloop": { NestedType: &configschema.Object{ Nesting: configschema.NestingList, Attributes: map[string]*configschema.Attribute{ "blop": { Type: cty.DynamicPseudoType, Required: true, }, "blub": { Type: cty.DynamicPseudoType, Optional: true, }, }, }, Optional: true, }, }, }, cty.ObjectVal(map[string]cty.Value{ "foo": cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("beep"), "baz": cty.StringVal("boop"), }), }), "bloop": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("bar"), "blub": cty.StringVal("glub"), }), cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("baz"), "blub": cty.NullVal(cty.String), }), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bap"), "baz": cty.NullVal(cty.String), }), cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("blep"), "baz": cty.NullVal(cty.String), }), }), "bloop": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("bar"), "blub": cty.NullVal(cty.String), }), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.TupleVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bap"), "baz": cty.StringVal("boop"), }), cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("blep"), "baz": cty.NullVal(cty.String), }), }), "bloop": cty.ListVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("bar"), "blub": cty.NullVal(cty.String), }), }), }), }, "prior nested map": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "foo": { Nesting: configschema.NestingMap, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "bar": { Type: cty.String, Optional: true, Computed: true, }, "baz": { Type: cty.String, Optional: true, Computed: true, }, }, }, }, }, Attributes: map[string]*configschema.Attribute{ "bloop": { NestedType: &configschema.Object{ Nesting: configschema.NestingMap, Attributes: map[string]*configschema.Attribute{ "blop": { Type: cty.String, Required: true, }, }, }, Optional: 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"), "baz": cty.StringVal("boop"), }), "b": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("blep"), "baz": cty.StringVal("boot"), }), }), "bloop": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glub"), }), "b": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("blub"), }), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bap"), "baz": cty.NullVal(cty.String), }), "c": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bosh"), "baz": cty.NullVal(cty.String), }), }), "bloop": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glub"), }), "c": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("blub"), }), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bap"), "baz": cty.StringVal("boop"), }), "c": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bosh"), "baz": cty.NullVal(cty.String), }), }), "bloop": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glub"), }), "c": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("blub"), }), }), }), }, "prior nested map with dynamic": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "foo": { Nesting: configschema.NestingMap, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "bar": { Type: cty.String, Optional: true, Computed: true, }, "baz": { Type: cty.DynamicPseudoType, Optional: true, Computed: true, }, }, }, }, }, Attributes: map[string]*configschema.Attribute{ "bloop": { NestedType: &configschema.Object{ Nesting: configschema.NestingMap, Attributes: map[string]*configschema.Attribute{ "blop": { Type: cty.DynamicPseudoType, Required: true, }, }, }, Optional: 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"), "baz": cty.StringVal("boop"), }), "b": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("blep"), "baz": cty.ListVal([]cty.Value{cty.StringVal("boot")}), }), }), "bloop": cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glub"), }), "b": cty.ObjectVal(map[string]cty.Value{ "blop": cty.NumberIntVal(13), }), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bap"), "baz": cty.NullVal(cty.String), }), "c": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bosh"), "baz": cty.NullVal(cty.List(cty.String)), }), }), "bloop": cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("blep"), }), "c": cty.ObjectVal(map[string]cty.Value{ "blop": cty.NumberIntVal(13), }), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bap"), "baz": cty.StringVal("boop"), }), "c": cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bosh"), "baz": cty.NullVal(cty.List(cty.String)), }), }), "bloop": cty.ObjectVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("blep"), }), "c": cty.ObjectVal(map[string]cty.Value{ "blop": cty.NumberIntVal(13), }), }), }), }, "prior nested set": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "foo": { Nesting: configschema.NestingSet, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "bar": { // This non-computed attribute will serve // as our matching key for propagating // "baz" from elements in the prior value. Type: cty.String, Optional: true, }, "baz": { Type: cty.String, Optional: true, Computed: true, }, }, }, }, }, Attributes: map[string]*configschema.Attribute{ "bloop": { NestedType: &configschema.Object{ Nesting: configschema.NestingSet, Attributes: map[string]*configschema.Attribute{ "blop": { Type: cty.String, Required: true, }, "bleep": { Type: cty.String, Optional: true, }, }, }, Optional: true, }, }, }, cty.ObjectVal(map[string]cty.Value{ "foo": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("beep"), "baz": cty.StringVal("boop"), }), cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("blep"), "baz": cty.StringVal("boot"), }), }), "bloop": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glubglub"), "bleep": cty.NullVal(cty.String), }), cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glubglub"), "bleep": cty.StringVal("beep"), }), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("beep"), "baz": cty.NullVal(cty.String), }), cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bosh"), "baz": cty.NullVal(cty.String), }), }), "bloop": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glubglub"), "bleep": cty.NullVal(cty.String), }), cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glub"), "bleep": cty.NullVal(cty.String), }), }), }), cty.ObjectVal(map[string]cty.Value{ "foo": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("beep"), "baz": cty.StringVal("boop"), }), cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("bosh"), "baz": cty.NullVal(cty.String), }), }), "bloop": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glubglub"), "bleep": cty.NullVal(cty.String), }), cty.ObjectVal(map[string]cty.Value{ "blop": cty.StringVal("glub"), "bleep": cty.NullVal(cty.String), }), }), }), }, "sets differing only by unknown": { &configschema.Block{ BlockTypes: map[string]*configschema.NestedBlock{ "multi": { Nesting: configschema.NestingSet, Block: configschema.Block{ Attributes: map[string]*configschema.Attribute{ "optional": { Type: cty.String, Optional: true, Computed: true, }, }, }, }, }, Attributes: map[string]*configschema.Attribute{ "bloop": { NestedType: &configschema.Object{ Nesting: configschema.NestingSet, Attributes: map[string]*configschema.Attribute{ "blop": { Type: cty.String, Required: true, }, }, }, Optional: true, }, }, }, cty.NullVal(cty.DynamicPseudoType), cty.ObjectVal(map[string]cty.Value{ "multi": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "optional": cty.UnknownVal(cty.String), }), cty.ObjectVal(map[string]cty.Value{ "optional": cty.UnknownVal(cty.String), }), }), "bloop": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.UnknownVal(cty.String), }), cty.ObjectVal(map[string]cty.Value{ "blop": cty.UnknownVal(cty.String), }), }), }), cty.ObjectVal(map[string]cty.Value{ "multi": cty.SetVal([]cty.Value{ // These remain distinct because unknown values never // compare equal. They may be consolidated together once // the values become known, though. cty.ObjectVal(map[string]cty.Value{ "optional": cty.UnknownVal(cty.String), }), cty.ObjectVal(map[string]cty.Value{ "optional": cty.UnknownVal(cty.String), }), }), "bloop": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "blop": cty.UnknownVal(cty.String), }), cty.ObjectVal(map[string]cty.Value{ "blop": cty.UnknownVal(cty.String), }), }), }), }, "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"), }), }), }), }), }), }, // This example has a mixture of optional, computed and required in a deeply-nested NestedType attribute "deeply NestedType": { &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "foo": { NestedType: &configschema.Object{ Nesting: configschema.NestingSingle, Attributes: map[string]*configschema.Attribute{ "bar": { NestedType: &configschema.Object{ Nesting: configschema.NestingSingle, Attributes: testAttributes, }, Required: true, }, "baz": { NestedType: &configschema.Object{ Nesting: configschema.NestingSingle, Attributes: testAttributes, }, Optional: true, }, }, }, Optional: true, }, }, }, // prior cty.ObjectVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "bar": cty.NullVal(cty.DynamicPseudoType), "baz": cty.ObjectVal(map[string]cty.Value{ "optional": cty.NullVal(cty.String), "computed": cty.StringVal("hello"), "optional_computed": cty.StringVal("prior"), "required": cty.StringVal("present"), }), }), }), // config cty.ObjectVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "bar": cty.UnknownVal(cty.Object(map[string]cty.Type{ // explicit unknown from the config "optional": cty.String, "computed": cty.String, "optional_computed": cty.String, "required": cty.String, })), "baz": cty.ObjectVal(map[string]cty.Value{ "optional": cty.NullVal(cty.String), "computed": cty.NullVal(cty.String), "optional_computed": cty.StringVal("hello"), "required": cty.StringVal("present"), }), }), }), // want cty.ObjectVal(map[string]cty.Value{ "foo": cty.ObjectVal(map[string]cty.Value{ "bar": cty.UnknownVal(cty.Object(map[string]cty.Type{ // explicit unknown preserved from the config "optional": cty.String, "computed": cty.String, "optional_computed": cty.String, "required": cty.String, })), "baz": cty.ObjectVal(map[string]cty.Value{ "optional": cty.NullVal(cty.String), // config is null "computed": cty.StringVal("hello"), // computed values come from prior "optional_computed": cty.StringVal("hello"), // config takes precedent over prior in opt+computed "required": cty.StringVal("present"), // value from config }), }), }), }, "deeply nested set": { &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "foo": { NestedType: &configschema.Object{ Nesting: configschema.NestingSet, Attributes: map[string]*configschema.Attribute{ "bar": { NestedType: &configschema.Object{ Nesting: configschema.NestingSet, Attributes: testAttributes, }, Required: true, }, }, }, Optional: true, }, }, }, // prior values cty.ObjectVal(map[string]cty.Value{ "foo": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "optional": cty.StringVal("prior"), "computed": cty.StringVal("prior"), "optional_computed": cty.StringVal("prior"), "required": cty.StringVal("prior"), }), }), }), cty.ObjectVal(map[string]cty.Value{ "bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ "optional": cty.StringVal("other_prior"), "computed": cty.StringVal("other_prior"), "optional_computed": cty.StringVal("other_prior"), "required": cty.StringVal("other_prior"), })}), }), }), }), // config differs from prior cty.ObjectVal(map[string]cty.Value{ "foo": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ "optional": cty.StringVal("configured"), "computed": cty.NullVal(cty.String), // computed attrs are null in config "optional_computed": cty.StringVal("configured"), "required": cty.StringVal("configured"), })}), }), cty.ObjectVal(map[string]cty.Value{ "bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ "optional": cty.NullVal(cty.String), // explicit null in config "computed": cty.NullVal(cty.String), // computed attrs are null in config "optional_computed": cty.StringVal("other_configured"), "required": cty.StringVal("other_configured"), })}), }), }), }), // want: cty.ObjectVal(map[string]cty.Value{ "foo": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ "optional": cty.StringVal("configured"), "computed": cty.NullVal(cty.String), "optional_computed": cty.StringVal("configured"), "required": cty.StringVal("configured"), })}), }), cty.ObjectVal(map[string]cty.Value{ "bar": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{ "optional": cty.NullVal(cty.String), // explicit null in config is preserved "computed": cty.NullVal(cty.String), "optional_computed": cty.StringVal("other_configured"), "required": cty.StringVal("other_configured"), })}), }), }), }), }, "expected null NestedTypes": { &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "single": { NestedType: &configschema.Object{ Nesting: configschema.NestingSingle, Attributes: map[string]*configschema.Attribute{ "bar": {Type: cty.String}, }, }, Optional: true, }, "list": { NestedType: &configschema.Object{ Nesting: configschema.NestingList, Attributes: map[string]*configschema.Attribute{ "bar": {Type: cty.String}, }, }, Optional: true, }, "set": { NestedType: &configschema.Object{ Nesting: configschema.NestingSet, Attributes: map[string]*configschema.Attribute{ "bar": {Type: cty.String}, }, }, Optional: true, }, "map": { NestedType: &configschema.Object{ Nesting: configschema.NestingMap, Attributes: map[string]*configschema.Attribute{ "bar": {Type: cty.String}, }, }, Optional: true, }, "nested_map": { NestedType: &configschema.Object{ Nesting: configschema.NestingMap, Attributes: map[string]*configschema.Attribute{ "inner": { NestedType: &configschema.Object{ Nesting: configschema.NestingSingle, Attributes: testAttributes, }, }, }, }, Optional: true, }, }, }, cty.ObjectVal(map[string]cty.Value{ "single": cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")}), "list": cty.ListVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")})}), "map": cty.MapVal(map[string]cty.Value{ "map_entry": cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")}), }), "set": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")})}), "nested_map": cty.MapVal(map[string]cty.Value{ "a": cty.ObjectVal(map[string]cty.Value{ "inner": cty.ObjectVal(map[string]cty.Value{ "optional": cty.StringVal("foo"), "computed": cty.StringVal("foo"), "optional_computed": cty.StringVal("foo"), "required": cty.StringVal("foo"), }), }), }), }), cty.ObjectVal(map[string]cty.Value{ "single": cty.NullVal(cty.Object(map[string]cty.Type{"bar": cty.String})), "list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"bar": cty.String}))), "map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{"bar": cty.String}))), "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{"bar": cty.String}))), "nested_map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ "inner": cty.Object(map[string]cty.Type{ "optional": cty.String, "computed": cty.String, "optional_computed": cty.String, "required": cty.String, }), }))), }), cty.ObjectVal(map[string]cty.Value{ "single": cty.NullVal(cty.Object(map[string]cty.Type{"bar": cty.String})), "list": cty.NullVal(cty.List(cty.Object(map[string]cty.Type{"bar": cty.String}))), "map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{"bar": cty.String}))), "set": cty.NullVal(cty.Set(cty.Object(map[string]cty.Type{"bar": cty.String}))), "nested_map": cty.NullVal(cty.Map(cty.Object(map[string]cty.Type{ "inner": cty.Object(map[string]cty.Type{ "optional": cty.String, "computed": cty.String, "optional_computed": cty.String, "required": cty.String, }), }))), }), }, "expected empty NestedTypes": { &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "set": { NestedType: &configschema.Object{ Nesting: configschema.NestingSet, Attributes: map[string]*configschema.Attribute{ "bar": {Type: cty.String}, }, }, Optional: true, }, "map": { NestedType: &configschema.Object{ Nesting: configschema.NestingMap, Attributes: map[string]*configschema.Attribute{ "bar": {Type: cty.String}, }, }, Optional: true, }, }, }, cty.ObjectVal(map[string]cty.Value{ "map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), }), cty.ObjectVal(map[string]cty.Value{ "map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), }), cty.ObjectVal(map[string]cty.Value{ "map": cty.MapValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), "set": cty.SetValEmpty(cty.Object(map[string]cty.Type{"bar": cty.String})), }), }, "optional types set replacement": { &configschema.Block{ Attributes: map[string]*configschema.Attribute{ "set": { NestedType: &configschema.Object{ Nesting: configschema.NestingSet, Attributes: map[string]*configschema.Attribute{ "bar": { Type: cty.String, Required: true, }, }, }, Optional: true, }, }, }, cty.ObjectVal(map[string]cty.Value{ "set": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("old"), }), }), }), cty.ObjectVal(map[string]cty.Value{ "set": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("new"), }), }), }), cty.ObjectVal(map[string]cty.Value{ "set": cty.SetVal([]cty.Value{ cty.ObjectVal(map[string]cty.Value{ "bar": cty.StringVal("new"), }), }), }), }, } for name, test := range tests { t.Run(name, func(t *testing.T) { got := ProposedNew(test.Schema, test.Prior, test.Config) if !got.RawEquals(test.Want) { t.Errorf("wrong result\ngot: %swant: %s", dump.Value(got), dump.Value(test.Want)) } }) } } var testAttributes = map[string]*configschema.Attribute{ "optional": { Type: cty.String, Optional: true, }, "computed": { Type: cty.String, Computed: true, }, "optional_computed": { Type: cty.String, Computed: true, Optional: true, }, "required": { Type: cty.String, Required: true, }, }