configschema: Handle nested blocks containing dynamic-typed attributes
We need to make the collection itself be a tuple or object rather than list or map in this case, since otherwise all of the elements of the collection are constrained to be of the same type and that isn't the intent of a provider indicating that it accepts any type.
This commit is contained in:
parent
4c78539c2b
commit
549544f201
|
@ -45,13 +45,31 @@ func (b *Block) DecoderSpec() hcldec.Spec {
|
||||||
Required: blockS.MinItems == 1 && blockS.MaxItems >= 1,
|
Required: blockS.MinItems == 1 && blockS.MaxItems >= 1,
|
||||||
}
|
}
|
||||||
case NestingList:
|
case NestingList:
|
||||||
ret[name] = &hcldec.BlockListSpec{
|
// We prefer to use a list where possible, since it makes our
|
||||||
TypeName: name,
|
// implied type more complete, but if there are any
|
||||||
Nested: childSpec,
|
// dynamically-typed attributes inside we must use a tuple
|
||||||
MinItems: blockS.MinItems,
|
// instead, at the expense of our type then not being predictable.
|
||||||
MaxItems: blockS.MaxItems,
|
if blockS.Block.ImpliedType().HasDynamicTypes() {
|
||||||
|
ret[name] = &hcldec.BlockTupleSpec{
|
||||||
|
TypeName: name,
|
||||||
|
Nested: childSpec,
|
||||||
|
MinItems: blockS.MinItems,
|
||||||
|
MaxItems: blockS.MaxItems,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret[name] = &hcldec.BlockListSpec{
|
||||||
|
TypeName: name,
|
||||||
|
Nested: childSpec,
|
||||||
|
MinItems: blockS.MinItems,
|
||||||
|
MaxItems: blockS.MaxItems,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case NestingSet:
|
case NestingSet:
|
||||||
|
// We forbid dynamically-typed attributes inside NestingSet in
|
||||||
|
// InternalValidate, so we don't do anything special to handle
|
||||||
|
// that here. (There is no set analog to tuple and object types,
|
||||||
|
// because cty's set implementation depends on knowing the static
|
||||||
|
// type in order to properly compute its internal hashes.)
|
||||||
ret[name] = &hcldec.BlockSetSpec{
|
ret[name] = &hcldec.BlockSetSpec{
|
||||||
TypeName: name,
|
TypeName: name,
|
||||||
Nested: childSpec,
|
Nested: childSpec,
|
||||||
|
@ -59,10 +77,22 @@ func (b *Block) DecoderSpec() hcldec.Spec {
|
||||||
MaxItems: blockS.MaxItems,
|
MaxItems: blockS.MaxItems,
|
||||||
}
|
}
|
||||||
case NestingMap:
|
case NestingMap:
|
||||||
ret[name] = &hcldec.BlockMapSpec{
|
// We prefer to use a list where possible, since it makes our
|
||||||
TypeName: name,
|
// implied type more complete, but if there are any
|
||||||
Nested: childSpec,
|
// dynamically-typed attributes inside we must use a tuple
|
||||||
LabelNames: mapLabelNames,
|
// instead, at the expense of our type then not being predictable.
|
||||||
|
if blockS.Block.ImpliedType().HasDynamicTypes() {
|
||||||
|
ret[name] = &hcldec.BlockObjectSpec{
|
||||||
|
TypeName: name,
|
||||||
|
Nested: childSpec,
|
||||||
|
LabelNames: mapLabelNames,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ret[name] = &hcldec.BlockMapSpec{
|
||||||
|
TypeName: name,
|
||||||
|
Nested: childSpec,
|
||||||
|
LabelNames: mapLabelNames,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
// Invalid nesting type is just ignored. It's checked by
|
// Invalid nesting type is just ignored. It's checked by
|
||||||
|
|
|
@ -3,6 +3,7 @@ package configschema
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/apparentlymart/go-dump/dump"
|
||||||
"github.com/davecgh/go-spew/spew"
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl2/hcl"
|
"github.com/hashicorp/hcl2/hcl"
|
||||||
|
@ -236,6 +237,95 @@ func TestBlockDecoderSpec(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
0,
|
0,
|
||||||
},
|
},
|
||||||
|
"blocks with dynamically-typed attributes": {
|
||||||
|
&Block{
|
||||||
|
BlockTypes: map[string]*NestedBlock{
|
||||||
|
"single": {
|
||||||
|
Nesting: NestingSingle,
|
||||||
|
Block: Block{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"a": {
|
||||||
|
Type: cty.DynamicPseudoType,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"list": {
|
||||||
|
Nesting: NestingList,
|
||||||
|
Block: Block{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"a": {
|
||||||
|
Type: cty.DynamicPseudoType,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
Nesting: NestingMap,
|
||||||
|
Block: Block{
|
||||||
|
Attributes: map[string]*Attribute{
|
||||||
|
"a": {
|
||||||
|
Type: cty.DynamicPseudoType,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hcltest.MockBody(&hcl.BodyContent{
|
||||||
|
Blocks: hcl.Blocks{
|
||||||
|
&hcl.Block{
|
||||||
|
Type: "list",
|
||||||
|
Body: hcl.EmptyBody(),
|
||||||
|
},
|
||||||
|
&hcl.Block{
|
||||||
|
Type: "single",
|
||||||
|
Body: hcl.EmptyBody(),
|
||||||
|
},
|
||||||
|
&hcl.Block{
|
||||||
|
Type: "list",
|
||||||
|
Body: hcl.EmptyBody(),
|
||||||
|
},
|
||||||
|
&hcl.Block{
|
||||||
|
Type: "map",
|
||||||
|
Labels: []string{"foo"},
|
||||||
|
LabelRanges: []hcl.Range{hcl.Range{}},
|
||||||
|
Body: hcl.EmptyBody(),
|
||||||
|
},
|
||||||
|
&hcl.Block{
|
||||||
|
Type: "map",
|
||||||
|
Labels: []string{"bar"},
|
||||||
|
LabelRanges: []hcl.Range{hcl.Range{}},
|
||||||
|
Body: hcl.EmptyBody(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"single": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.NullVal(cty.DynamicPseudoType),
|
||||||
|
}),
|
||||||
|
"list": cty.TupleVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.NullVal(cty.DynamicPseudoType),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.NullVal(cty.DynamicPseudoType),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
"map": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.NullVal(cty.DynamicPseudoType),
|
||||||
|
}),
|
||||||
|
"bar": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.NullVal(cty.DynamicPseudoType),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
0,
|
||||||
|
},
|
||||||
"too many list items": {
|
"too many list items": {
|
||||||
&Block{
|
&Block{
|
||||||
BlockTypes: map[string]*NestedBlock{
|
BlockTypes: map[string]*NestedBlock{
|
||||||
|
@ -294,7 +384,7 @@ func TestBlockDecoderSpec(t *testing.T) {
|
||||||
|
|
||||||
if !got.RawEquals(test.Want) {
|
if !got.RawEquals(test.Want) {
|
||||||
t.Logf("[INFO] implied schema is %s", spew.Sdump(hcldec.ImpliedSchema(spec)))
|
t.Logf("[INFO] implied schema is %s", spew.Sdump(hcldec.ImpliedSchema(spec)))
|
||||||
t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want)
|
t.Errorf("wrong result\ngot: %s\nwant: %s", dump.Value(got), dump.Value(test.Want))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Double-check that we're producing consistent results for DecoderSpec
|
// Double-check that we're producing consistent results for DecoderSpec
|
||||||
|
|
|
@ -80,31 +80,66 @@ func assertObjectCompatible(schema *configschema.Block, planned, actual cty.Valu
|
||||||
moreErrs := assertObjectCompatible(&blockS.Block, plannedV, actualV, path)
|
moreErrs := assertObjectCompatible(&blockS.Block, plannedV, actualV, path)
|
||||||
errs = append(errs, moreErrs...)
|
errs = append(errs, moreErrs...)
|
||||||
case configschema.NestingList:
|
case configschema.NestingList:
|
||||||
|
// A NestingList might either be a list or a tuple, depending on
|
||||||
|
// whether there are dynamically-typed attributes inside. However,
|
||||||
|
// both support a similar-enough API that we can treat them the
|
||||||
|
// same for our purposes here.
|
||||||
plannedL := plannedV.LengthInt()
|
plannedL := plannedV.LengthInt()
|
||||||
actualL := actualV.LengthInt()
|
actualL := actualV.LengthInt()
|
||||||
if plannedL != actualL {
|
if plannedL != actualL {
|
||||||
errs = append(errs, path.NewErrorf("block count changed from %d to %d", plannedL, actualL))
|
errs = append(errs, path.NewErrorf("block count changed from %d to %d", plannedL, actualL))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
case configschema.NestingMap:
|
for it := plannedV.ElementIterator(); it.Next(); {
|
||||||
plannedAtys := plannedV.Type().AttributeTypes()
|
idx, plannedEV := it.Element()
|
||||||
actualAtys := actualV.Type().AttributeTypes()
|
if !actualV.HasIndex(idx).True() {
|
||||||
for k := range plannedAtys {
|
|
||||||
if _, ok := actualAtys[k]; !ok {
|
|
||||||
errs = append(errs, path.NewErrorf("block key %q has vanished", k))
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
actualEV := actualV.Index(idx)
|
||||||
plannedEV := plannedV.GetAttr(k)
|
moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.IndexStep{Key: idx}))
|
||||||
actualEV := actualV.GetAttr(k)
|
|
||||||
moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.GetAttrStep{Name: k}))
|
|
||||||
errs = append(errs, moreErrs...)
|
errs = append(errs, moreErrs...)
|
||||||
}
|
}
|
||||||
for k := range actualAtys {
|
case configschema.NestingMap:
|
||||||
if _, ok := plannedAtys[k]; !ok {
|
// A NestingMap might either be a map or an object, depending on
|
||||||
errs = append(errs, path.NewErrorf("new block key %q has appeared", k))
|
// whether there are dynamically-typed attributes inside, but
|
||||||
|
// that's decided statically and so both values will have the same
|
||||||
|
// kind.
|
||||||
|
if plannedV.Type().IsObjectType() {
|
||||||
|
plannedAtys := plannedV.Type().AttributeTypes()
|
||||||
|
actualAtys := actualV.Type().AttributeTypes()
|
||||||
|
for k := range plannedAtys {
|
||||||
|
if _, ok := actualAtys[k]; !ok {
|
||||||
|
errs = append(errs, path.NewErrorf("block key %q has vanished", k))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
plannedEV := plannedV.GetAttr(k)
|
||||||
|
actualEV := actualV.GetAttr(k)
|
||||||
|
moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.GetAttrStep{Name: k}))
|
||||||
|
errs = append(errs, moreErrs...)
|
||||||
|
}
|
||||||
|
for k := range actualAtys {
|
||||||
|
if _, ok := plannedAtys[k]; !ok {
|
||||||
|
errs = append(errs, path.NewErrorf("new block key %q has appeared", k))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
plannedL := plannedV.LengthInt()
|
||||||
|
actualL := actualV.LengthInt()
|
||||||
|
if plannedL != actualL {
|
||||||
|
errs = append(errs, path.NewErrorf("block count changed from %d to %d", plannedL, actualL))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
for it := plannedV.ElementIterator(); it.Next(); {
|
||||||
|
idx, plannedEV := it.Element()
|
||||||
|
if !actualV.HasIndex(idx).True() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
actualEV := actualV.Index(idx)
|
||||||
|
moreErrs := assertObjectCompatible(&blockS.Block, plannedEV, actualEV, append(path, cty.IndexStep{Key: idx}))
|
||||||
|
errs = append(errs, moreErrs...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case configschema.NestingSet:
|
case configschema.NestingSet:
|
||||||
// We can't do any reasonable matching of set elements since their
|
// We can't do any reasonable matching of set elements since their
|
||||||
|
|
|
@ -90,13 +90,6 @@ func ProposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.
|
||||||
newV = ProposedNewObject(&blockType.Block, priorV, configV)
|
newV = ProposedNewObject(&blockType.Block, priorV, configV)
|
||||||
|
|
||||||
case configschema.NestingList:
|
case configschema.NestingList:
|
||||||
if !configV.Type().IsTupleType() {
|
|
||||||
// Despite the name, we expect NestingList to produce a tuple
|
|
||||||
// type so that different elements may have dynamically-typed
|
|
||||||
// attributes that have a different actual type.
|
|
||||||
panic("configschema.NestingList value is not a tuple as expected")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Nested blocks are correlated by index.
|
// Nested blocks are correlated by index.
|
||||||
if l := configV.LengthInt(); l > 0 {
|
if l := configV.LengthInt(); l > 0 {
|
||||||
newVals := make([]cty.Value, 0, l)
|
newVals := make([]cty.Value, 0, l)
|
||||||
|
@ -113,45 +106,73 @@ func ProposedNewObject(schema *configschema.Block, prior, config cty.Value) cty.
|
||||||
newEV := ProposedNewObject(&blockType.Block, priorEV, configEV)
|
newEV := ProposedNewObject(&blockType.Block, priorEV, configEV)
|
||||||
newVals = append(newVals, newEV)
|
newVals = append(newVals, newEV)
|
||||||
}
|
}
|
||||||
// Although we call the nesting mode "list", we actually use
|
// Despite the name, a NestingList might also be a tuple, if
|
||||||
// tuple values so that elements might have different types
|
// its nested schema contains dynamically-typed attributes.
|
||||||
// in case of dynamically-typed attributes.
|
if configV.Type().IsTupleType() {
|
||||||
newV = cty.TupleVal(newVals)
|
newV = cty.TupleVal(newVals)
|
||||||
|
} else {
|
||||||
|
newV = cty.ListVal(newVals)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
newV = cty.EmptyTupleVal
|
// Despite the name, a NestingList might also be a tuple, if
|
||||||
|
// its nested schema contains dynamically-typed attributes.
|
||||||
|
if configV.Type().IsTupleType() {
|
||||||
|
newV = cty.EmptyTupleVal
|
||||||
|
} else {
|
||||||
|
newV = cty.ListValEmpty(blockType.ImpliedType())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case configschema.NestingMap:
|
case configschema.NestingMap:
|
||||||
if !configV.Type().IsObjectType() {
|
// Despite the name, a NestingMap may produce either a map or
|
||||||
// Despite the name, we expect NestingMap to produce an object
|
// object value, depending on whether the nested schema contains
|
||||||
// type so that different elements may have dynamically-typed
|
// dynamically-typed attributes.
|
||||||
// attributes that have a different actual type.
|
if configV.Type().IsObjectType() {
|
||||||
panic("configschema.NestingMap value is not an object as expected")
|
// Nested blocks are correlated by key.
|
||||||
}
|
if l := configV.LengthInt(); l > 0 {
|
||||||
|
newVals := make(map[string]cty.Value, l)
|
||||||
|
atys := configV.Type().AttributeTypes()
|
||||||
|
for name := range atys {
|
||||||
|
configEV := configV.GetAttr(name)
|
||||||
|
if !priorV.Type().HasAttribute(name) {
|
||||||
|
// If there is no corresponding prior element then
|
||||||
|
// we just take the config value as-is.
|
||||||
|
newVals[name] = configEV
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
priorEV := priorV.GetAttr(name)
|
||||||
|
|
||||||
// Nested blocks are correlated by key.
|
newEV := ProposedNewObject(&blockType.Block, priorEV, configEV)
|
||||||
if l := configV.LengthInt(); l > 0 {
|
newVals[name] = newEV
|
||||||
newVals := make(map[string]cty.Value, l)
|
|
||||||
atys := configV.Type().AttributeTypes()
|
|
||||||
for name := range atys {
|
|
||||||
configEV := configV.GetAttr(name)
|
|
||||||
if !priorV.Type().HasAttribute(name) {
|
|
||||||
// If there is no corresponding prior element then
|
|
||||||
// we just take the config value as-is.
|
|
||||||
newVals[name] = configEV
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
priorEV := priorV.GetAttr(name)
|
// Although we call the nesting mode "map", we actually use
|
||||||
|
// object values so that elements might have different types
|
||||||
newEV := ProposedNewObject(&blockType.Block, priorEV, configEV)
|
// in case of dynamically-typed attributes.
|
||||||
newVals[name] = newEV
|
newV = cty.ObjectVal(newVals)
|
||||||
|
} else {
|
||||||
|
newV = cty.EmptyObjectVal
|
||||||
}
|
}
|
||||||
// Although we call the nesting mode "map", we actually use
|
|
||||||
// object values so that elements might have different types
|
|
||||||
// in case of dynamically-typed attributes.
|
|
||||||
newV = cty.ObjectVal(newVals)
|
|
||||||
} else {
|
} else {
|
||||||
newV = cty.EmptyObjectVal
|
if l := configV.LengthInt(); l > 0 {
|
||||||
|
newVals := make(map[string]cty.Value, l)
|
||||||
|
for it := configV.ElementIterator(); it.Next(); {
|
||||||
|
idx, configEV := it.Element()
|
||||||
|
k := idx.AsString()
|
||||||
|
if !priorV.HasIndex(idx).True() {
|
||||||
|
// If there is no corresponding prior element then
|
||||||
|
// we just take the config value as-is.
|
||||||
|
newVals[k] = configEV
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
priorEV := priorV.Index(idx)
|
||||||
|
|
||||||
|
newEV := ProposedNewObject(&blockType.Block, priorEV, configEV)
|
||||||
|
newVals[k] = newEV
|
||||||
|
}
|
||||||
|
newV = cty.MapVal(newVals)
|
||||||
|
} else {
|
||||||
|
newV = cty.MapValEmpty(blockType.ImpliedType())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
case configschema.NestingSet:
|
case configschema.NestingSet:
|
||||||
|
|
|
@ -170,6 +170,61 @@ func TestProposedNewObject(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
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"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
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),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
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),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.TupleVal([]cty.Value{
|
"foo": cty.TupleVal([]cty.Value{
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
@ -225,6 +280,65 @@ func TestProposedNewObject(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
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"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
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),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
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),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
"foo": cty.ObjectVal(map[string]cty.Value{
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
"a": cty.ObjectVal(map[string]cty.Value{
|
"a": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
|
Loading…
Reference in New Issue