add SetUnknowns
SetUnknown walks through a resource and changes any unset (null) values that are going computed in the schema to Unknown.
This commit is contained in:
parent
be127725cc
commit
d17ba647a8
|
@ -0,0 +1,89 @@
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetUnknowns takes a cty.Value, and compares it to the schema setting any null
|
||||||
|
// leaf values which are computed as unknown.
|
||||||
|
func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
|
||||||
|
if val.IsNull() || !val.IsKnown() {
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
valMap := val.AsValueMap()
|
||||||
|
newVals := make(map[string]cty.Value)
|
||||||
|
|
||||||
|
for name, attr := range schema.Attributes {
|
||||||
|
v := valMap[name]
|
||||||
|
|
||||||
|
if attr.Computed && v.IsNull() {
|
||||||
|
newVals[name] = cty.UnknownVal(attr.Type)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
newVals[name] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, blockS := range schema.BlockTypes {
|
||||||
|
blockVal := valMap[name]
|
||||||
|
if blockVal.IsNull() || !blockVal.IsKnown() {
|
||||||
|
newVals[name] = blockVal
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
blockType := blockS.Block.ImpliedType()
|
||||||
|
|
||||||
|
switch blockS.Nesting {
|
||||||
|
case configschema.NestingSingle:
|
||||||
|
newVals[name] = SetUnknowns(blockVal, &blockS.Block)
|
||||||
|
case configschema.NestingSet, configschema.NestingList:
|
||||||
|
listVals := blockVal.AsValueSlice()
|
||||||
|
newListVals := make([]cty.Value, 0, len(listVals))
|
||||||
|
|
||||||
|
for _, v := range listVals {
|
||||||
|
newListVals = append(newListVals, SetUnknowns(v, &blockS.Block))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch blockS.Nesting {
|
||||||
|
case configschema.NestingSet:
|
||||||
|
switch len(newListVals) {
|
||||||
|
case 0:
|
||||||
|
newVals[name] = cty.SetValEmpty(blockType)
|
||||||
|
default:
|
||||||
|
newVals[name] = cty.SetVal(newListVals)
|
||||||
|
}
|
||||||
|
case configschema.NestingList:
|
||||||
|
switch len(newListVals) {
|
||||||
|
case 0:
|
||||||
|
newVals[name] = cty.ListValEmpty(blockType)
|
||||||
|
default:
|
||||||
|
newVals[name] = cty.ListVal(newListVals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case configschema.NestingMap:
|
||||||
|
mapVals := blockVal.AsValueMap()
|
||||||
|
newMapVals := make(map[string]cty.Value)
|
||||||
|
|
||||||
|
for k, v := range mapVals {
|
||||||
|
newMapVals[k] = SetUnknowns(v, &blockS.Block)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch len(newMapVals) {
|
||||||
|
case 0:
|
||||||
|
newVals[name] = cty.MapValEmpty(blockType)
|
||||||
|
default:
|
||||||
|
newVals[name] = cty.MapVal(newMapVals)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("failed to set unknown values for nested block %q", name))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cty.ObjectVal(newVals)
|
||||||
|
}
|
|
@ -0,0 +1,353 @@
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/configs/configschema"
|
||||||
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetUnknowns(t *testing.T) {
|
||||||
|
for n, tc := range map[string]struct {
|
||||||
|
Schema *configschema.Block
|
||||||
|
Val cty.Value
|
||||||
|
Expected cty.Value
|
||||||
|
}{
|
||||||
|
"empty": {
|
||||||
|
&configschema.Block{},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
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.ObjectVal(map[string]cty.Value{}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"no prior with set": {
|
||||||
|
// the set value should remain null
|
||||||
|
&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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{}),
|
||||||
|
},
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.StringVal("bonjour"),
|
||||||
|
"bar": cty.StringVal("petit dejeuner"),
|
||||||
|
"baz": cty.StringVal("grande dejeuner"),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.StringVal("bonjour"),
|
||||||
|
"bar": cty.StringVal("petit dejeuner"),
|
||||||
|
"baz": cty.StringVal("grande dejeuner"),
|
||||||
|
"boz": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("beep"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("beep"),
|
||||||
|
"baz": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("bap"),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("blep"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.ListVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("bap"),
|
||||||
|
"baz": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("blep"),
|
||||||
|
"baz": cty.UnknownVal(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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.MapVal(map[string]cty.Value{
|
||||||
|
"a": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.NullVal(cty.String),
|
||||||
|
"baz": cty.StringVal("boop"),
|
||||||
|
}),
|
||||||
|
"b": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("blep"),
|
||||||
|
"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.UnknownVal(cty.String),
|
||||||
|
"baz": cty.StringVal("boop"),
|
||||||
|
}),
|
||||||
|
"b": cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("blep"),
|
||||||
|
"baz": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"prior nested set": {
|
||||||
|
&configschema.Block{
|
||||||
|
BlockTypes: map[string]*configschema.NestedBlock{
|
||||||
|
"foo": {
|
||||||
|
Nesting: configschema.NestingSet,
|
||||||
|
Block: configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bar": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"baz": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("blep"),
|
||||||
|
"baz": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("boop"),
|
||||||
|
"baz": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("blep"),
|
||||||
|
"baz": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("boop"),
|
||||||
|
"baz": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
"sets differing only by unknown": {
|
||||||
|
&configschema.Block{
|
||||||
|
BlockTypes: map[string]*configschema.NestedBlock{
|
||||||
|
"foo": {
|
||||||
|
Nesting: configschema.NestingSet,
|
||||||
|
Block: configschema.Block{
|
||||||
|
Attributes: map[string]*configschema.Attribute{
|
||||||
|
"bar": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
},
|
||||||
|
"baz": {
|
||||||
|
Type: cty.String,
|
||||||
|
Optional: true,
|
||||||
|
Computed: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("boop"),
|
||||||
|
"baz": cty.NullVal(cty.String),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("boop"),
|
||||||
|
"baz": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"foo": cty.SetVal([]cty.Value{
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("boop"),
|
||||||
|
"baz": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
cty.ObjectVal(map[string]cty.Value{
|
||||||
|
"bar": cty.StringVal("boop"),
|
||||||
|
"baz": cty.UnknownVal(cty.String),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(n, func(t *testing.T) {
|
||||||
|
// coerce the values because SetUnknowns expects the values to be
|
||||||
|
// complete, and so we can take shortcuts writing them in the
|
||||||
|
// test.
|
||||||
|
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