terraform/internal/plans/objchange/objchange_test.go

1495 lines
41 KiB
Go

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.ObjectVal(map[string]cty.Value{"bar": cty.NullVal(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.ObjectVal(map[string]cty.Value{"bar": cty.NullVal(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.ObjectWithOptionalAttrs(map[string]cty.Type{
"optional": cty.String,
"computed": cty.String,
"optional_computed": cty.String,
"required": cty.String,
}, []string{"optional", "optional_computed"}),
}))),
}),
},
}
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,
},
}