switch blocks based on value type, and check attrs
Check attributes on null objects, and fill in unknowns. If we're evaluating the object, it either means we are at the top level, or a NestingSingle block was present, and in either case we need to treat the attributes as null rather than the entire object. Switch on the block types rather than Nesting, so we don't need add any logic to change between List/Tuple or Map/Object when DynamicPseudoType is involved.
This commit is contained in:
parent
312d798a89
commit
82588af892
|
@ -8,12 +8,35 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SetUnknowns takes a cty.Value, and compares it to the schema setting any null
|
// SetUnknowns takes a cty.Value, and compares it to the schema setting any null
|
||||||
// leaf values which are computed as unknown.
|
// values which are computed to unknown.
|
||||||
func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
|
func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
|
||||||
if val.IsNull() || !val.IsKnown() {
|
if !val.IsKnown() {
|
||||||
return val
|
return val
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the object was null, we still need to handle the top level attributes
|
||||||
|
// which might be computed, but we don't need to expand the blocks.
|
||||||
|
if val.IsNull() {
|
||||||
|
objMap := map[string]cty.Value{}
|
||||||
|
allNull := true
|
||||||
|
for name, attr := range schema.Attributes {
|
||||||
|
switch {
|
||||||
|
case attr.Computed:
|
||||||
|
objMap[name] = cty.UnknownVal(attr.Type)
|
||||||
|
allNull = false
|
||||||
|
default:
|
||||||
|
objMap[name] = cty.NullVal(attr.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this object has no unknown attributes, then we can leave it null.
|
||||||
|
if allNull {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.ObjectVal(objMap)
|
||||||
|
}
|
||||||
|
|
||||||
valMap := val.AsValueMap()
|
valMap := val.AsValueMap()
|
||||||
newVals := make(map[string]cty.Value)
|
newVals := make(map[string]cty.Value)
|
||||||
|
|
||||||
|
@ -35,12 +58,18 @@ func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
blockType := blockS.Block.ImpliedType()
|
blockValType := blockVal.Type()
|
||||||
|
blockElementType := blockS.Block.ImpliedType()
|
||||||
|
|
||||||
switch blockS.Nesting {
|
// This switches on the value type here, so we can correctly switch
|
||||||
case configschema.NestingSingle:
|
// between Tuples/Lists and Maps/Objects.
|
||||||
|
switch {
|
||||||
|
case blockS.Nesting == configschema.NestingSingle:
|
||||||
|
// NestingSingle is the only exception here, where we treat the
|
||||||
|
// block directly as an object
|
||||||
newVals[name] = SetUnknowns(blockVal, &blockS.Block)
|
newVals[name] = SetUnknowns(blockVal, &blockS.Block)
|
||||||
case configschema.NestingSet, configschema.NestingList:
|
|
||||||
|
case blockValType.IsSetType(), blockValType.IsListType(), blockValType.IsTupleType():
|
||||||
listVals := blockVal.AsValueSlice()
|
listVals := blockVal.AsValueSlice()
|
||||||
newListVals := make([]cty.Value, 0, len(listVals))
|
newListVals := make([]cty.Value, 0, len(listVals))
|
||||||
|
|
||||||
|
@ -48,24 +77,26 @@ func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
|
||||||
newListVals = append(newListVals, SetUnknowns(v, &blockS.Block))
|
newListVals = append(newListVals, SetUnknowns(v, &blockS.Block))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch blockS.Nesting {
|
switch {
|
||||||
case configschema.NestingSet:
|
case blockValType.IsSetType():
|
||||||
switch len(newListVals) {
|
switch len(newListVals) {
|
||||||
case 0:
|
case 0:
|
||||||
newVals[name] = cty.SetValEmpty(blockType)
|
newVals[name] = cty.SetValEmpty(blockElementType)
|
||||||
default:
|
default:
|
||||||
newVals[name] = cty.SetVal(newListVals)
|
newVals[name] = cty.SetVal(newListVals)
|
||||||
}
|
}
|
||||||
case configschema.NestingList:
|
case blockValType.IsListType():
|
||||||
switch len(newListVals) {
|
switch len(newListVals) {
|
||||||
case 0:
|
case 0:
|
||||||
newVals[name] = cty.ListValEmpty(blockType)
|
newVals[name] = cty.ListValEmpty(blockElementType)
|
||||||
default:
|
default:
|
||||||
newVals[name] = cty.ListVal(newListVals)
|
newVals[name] = cty.ListVal(newListVals)
|
||||||
}
|
}
|
||||||
|
case blockValType.IsTupleType():
|
||||||
|
newVals[name] = cty.TupleVal(newListVals)
|
||||||
}
|
}
|
||||||
|
|
||||||
case configschema.NestingMap:
|
case blockValType.IsMapType(), blockValType.IsObjectType():
|
||||||
mapVals := blockVal.AsValueMap()
|
mapVals := blockVal.AsValueMap()
|
||||||
newMapVals := make(map[string]cty.Value)
|
newMapVals := make(map[string]cty.Value)
|
||||||
|
|
||||||
|
@ -73,15 +104,26 @@ func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
|
||||||
newMapVals[k] = SetUnknowns(v, &blockS.Block)
|
newMapVals[k] = SetUnknowns(v, &blockS.Block)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case blockValType.IsMapType():
|
||||||
switch len(newMapVals) {
|
switch len(newMapVals) {
|
||||||
case 0:
|
case 0:
|
||||||
newVals[name] = cty.MapValEmpty(blockType)
|
newVals[name] = cty.MapValEmpty(blockElementType)
|
||||||
default:
|
default:
|
||||||
newVals[name] = cty.MapVal(newMapVals)
|
newVals[name] = cty.MapVal(newMapVals)
|
||||||
}
|
}
|
||||||
|
case blockValType.IsObjectType():
|
||||||
|
if len(newMapVals) == 0 {
|
||||||
|
// We need to populate empty values to make a valid object.
|
||||||
|
for attr, ty := range blockElementType.AttributeTypes() {
|
||||||
|
newMapVals[attr] = cty.NullVal(ty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newVals[name] = cty.ObjectVal(newMapVals)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("failed to set unknown values for nested block %q", name))
|
panic(fmt.Sprintf("failed to set unknown values for nested block %q:%#v", name, blockValType))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,14 +50,27 @@ func TestSetUnknowns(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cty.ObjectVal(map[string]cty.Value{}),
|
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
|
"foo": cty.String,
|
||||||
|
"bar": cty.String,
|
||||||
|
"baz": cty.Object(map[string]cty.Type{
|
||||||
|
"boz": cty.String,
|
||||||
|
"biz": cty.String,
|
||||||
|
}),
|
||||||
|
})),
|
||||||
cty.ObjectVal(map[string]cty.Value{
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.NullVal(cty.String),
|
||||||
"bar": cty.UnknownVal(cty.String),
|
"bar": cty.UnknownVal(cty.String),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
"no prior with set": {
|
"null stays null": {
|
||||||
// the set value should remain null
|
// if the object has no computed attributes, it should stay null
|
||||||
&configschema.Block{
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"foo": &configschema.Attribute{
|
||||||
|
Type: cty.String,
|
||||||
|
},
|
||||||
|
},
|
||||||
BlockTypes: map[string]*configschema.NestedBlock{
|
BlockTypes: map[string]*configschema.NestedBlock{
|
||||||
"baz": {
|
"baz": {
|
||||||
Nesting: configschema.NestingSet,
|
Nesting: configschema.NestingSet,
|
||||||
|
@ -73,8 +86,52 @@ func TestSetUnknowns(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
cty.ObjectVal(map[string]cty.Value{}),
|
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
cty.ObjectVal(map[string]cty.Value{}),
|
"foo": cty.String,
|
||||||
|
"baz": cty.Set(cty.Object(map[string]cty.Type{
|
||||||
|
"boz": cty.String,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
|
"foo": cty.String,
|
||||||
|
"baz": cty.Set(cty.Object(map[string]cty.Type{
|
||||||
|
"boz": cty.String,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
"no prior with set": {
|
||||||
|
// the set value should remain null
|
||||||
|
&configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"foo": &configschema.Attribute{
|
||||||
|
Type: cty.String,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.NullVal(cty.Object(map[string]cty.Type{
|
||||||
|
"foo": cty.String,
|
||||||
|
"baz": cty.Set(cty.Object(map[string]cty.Type{
|
||||||
|
"boz": cty.String,
|
||||||
|
})),
|
||||||
|
})),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
"prior attributes": {
|
"prior attributes": {
|
||||||
&configschema.Block{
|
&configschema.Block{
|
||||||
|
@ -329,24 +386,97 @@ func TestSetUnknowns(t *testing.T) {
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
"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{
|
||||||
|
"foo": cty.TupleVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.NullVal(cty.String),
|
||||||
|
"baz": cty.NumberIntVal(8),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.TupleVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.UnknownVal(cty.String),
|
||||||
|
"baz": cty.NumberIntVal(8),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"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{
|
||||||
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"a": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("beep"),
|
||||||
|
"baz": cty.NullVal(cty.DynamicPseudoType),
|
||||||
|
}),
|
||||||
|
"b": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("boop"),
|
||||||
|
"baz": cty.NumberIntVal(8),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
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.UnknownVal(cty.DynamicPseudoType),
|
||||||
|
}),
|
||||||
|
"b": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("boop"),
|
||||||
|
"baz": cty.NumberIntVal(8),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(n, func(t *testing.T) {
|
t.Run(n, func(t *testing.T) {
|
||||||
// coerce the values because SetUnknowns expects the values to be
|
got := SetUnknowns(tc.Val, tc.Schema)
|
||||||
// complete, and so we can take shortcuts writing them in the
|
if !got.RawEquals(tc.Expected) {
|
||||||
// test.
|
t.Fatalf("\nexpected: %#v\ngot: %#v\n", tc.Expected, got)
|
||||||
v, err := tc.Schema.CoerceValue(tc.Val)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
expected, err := tc.Schema.CoerceValue(tc.Expected)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got := SetUnknowns(v, tc.Schema)
|
|
||||||
if !got.RawEquals(expected) {
|
|
||||||
t.Fatalf("\nexpected: %#v\ngot: %#v\n", expected, got)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue