Merge pull request #21209 from hashicorp/jbardin/computeds-in-config-shim

more precise handling of ComputedKeys in config
This commit is contained in:
James Bardin 2019-05-05 09:25:47 -04:00 committed by GitHub
commit 094c06df1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 142 additions and 60 deletions

View File

@ -2,7 +2,6 @@ package terraform
import ( import (
"fmt" "fmt"
"log"
"reflect" "reflect"
"sort" "sort"
"strconv" "strconv"
@ -236,7 +235,7 @@ func NewResourceConfigShimmed(val cty.Value, schema *configschema.Block) *Resour
// schema here so that we can preserve the expected invariant // schema here so that we can preserve the expected invariant
// that an attribute is always either wholly known or wholly unknown, while // that an attribute is always either wholly known or wholly unknown, while
// a child block can be partially unknown. // a child block can be partially unknown.
ret.ComputedKeys = newResourceConfigShimmedComputedKeys(val, schema, "") ret.ComputedKeys = newResourceConfigShimmedComputedKeys(val, "")
} else { } else {
ret.Config = make(map[string]interface{}) ret.Config = make(map[string]interface{})
} }
@ -245,72 +244,45 @@ func NewResourceConfigShimmed(val cty.Value, schema *configschema.Block) *Resour
return ret return ret
} }
// newResourceConfigShimmedComputedKeys finds all of the unknown values in the // Record the any config values in ComputedKeys. This field had been unused in
// given object, which must conform to the given schema, returning them in // helper/schema, but in the new protocol we're using this so that the SDK can
// the format that's expected for ResourceConfig.ComputedKeys. // now handle having an unknown collection. The legacy diff code doesn't
func newResourceConfigShimmedComputedKeys(obj cty.Value, schema *configschema.Block, prefix string) []string { // properly handle the unknown, because it can't be expressed in the same way
// between the config and diff.
func newResourceConfigShimmedComputedKeys(val cty.Value, path string) []string {
var ret []string var ret []string
ty := obj.Type() ty := val.Type()
if schema == nil { if val.IsNull() {
log.Printf("[WARN] NewResourceConfigShimmed: can't identify computed keys because no schema is available") return ret
return nil
} }
for attrName := range schema.Attributes { if !val.IsKnown() {
if !ty.HasAttribute(attrName) { // we shouldn't have an entirely unknown resource, but prevent empty
// Should never happen, but we'll tolerate it anyway // strings just in case
continue if len(path) > 0 {
} ret = append(ret, path)
attrVal := obj.GetAttr(attrName)
if !attrVal.IsWhollyKnown() {
ret = append(ret, prefix+attrName)
} }
return ret
} }
for typeName, blockS := range schema.BlockTypes { if path != "" {
if !ty.HasAttribute(typeName) { path += "."
// Should never happen, but we'll tolerate it anyway }
continue switch {
} case ty.IsListType(), ty.IsTupleType(), ty.IsSetType():
i := 0
blockVal := obj.GetAttr(typeName) for it := val.ElementIterator(); it.Next(); i++ {
if blockVal.IsNull() || !blockVal.IsKnown() { _, subVal := it.Element()
continue keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%d", path, i))
} ret = append(ret, keys...)
}
switch blockS.Nesting {
case configschema.NestingSingle, configschema.NestingGroup: case ty.IsMapType(), ty.IsObjectType():
keys := newResourceConfigShimmedComputedKeys(blockVal, &blockS.Block, fmt.Sprintf("%s%s.", prefix, typeName)) for it := val.ElementIterator(); it.Next(); {
subK, subVal := it.Element()
keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%s", path, subK.AsString()))
ret = append(ret, keys...) ret = append(ret, keys...)
case configschema.NestingList, configschema.NestingSet:
// Producing computed keys items for sets is not really useful
// since they are not usefully addressable anyway, but we'll treat
// them like lists just so that ret.ComputedKeys accounts for them
// all. Our legacy system didn't support sets here anyway, so
// treating them as lists is the most accurate translation. Although
// set traversal isn't in any particular order, it is _stable_ as
// long as the list isn't mutated, and so we know we'll see the
// same order here as hcl2shim.ConfigValueFromHCL2 would've seen
// inside NewResourceConfigShimmed above.
i := 0
for it := blockVal.ElementIterator(); it.Next(); i++ {
_, subVal := it.Element()
subPrefix := fmt.Sprintf("%s%s.%d.", prefix, typeName, i)
keys := newResourceConfigShimmedComputedKeys(subVal, &blockS.Block, subPrefix)
ret = append(ret, keys...)
}
case configschema.NestingMap:
for it := blockVal.ElementIterator(); it.Next(); {
subK, subVal := it.Element()
subPrefix := fmt.Sprintf("%s%s.%s.", prefix, typeName, subK.AsString())
keys := newResourceConfigShimmedComputedKeys(subVal, &blockS.Block, subPrefix)
ret = append(ret, keys...)
}
default:
// Should never happen, since the above is exhaustive.
panic(fmt.Errorf("unsupported block nesting type %s", blockS.Nesting))
} }
} }

View File

@ -919,6 +919,7 @@ func TestNewResourceConfigShimmed(t *testing.T) {
}, },
}, },
Expected: &ResourceConfig{ Expected: &ResourceConfig{
ComputedKeys: []string{"bar", "baz"},
Raw: map[string]interface{}{ Raw: map[string]interface{}{
"bar": config.UnknownVariableValue, "bar": config.UnknownVariableValue,
"baz": config.UnknownVariableValue, "baz": config.UnknownVariableValue,
@ -981,6 +982,115 @@ func TestNewResourceConfigShimmed(t *testing.T) {
}, },
}, },
}, },
{
Name: "unknown in set",
Val: cty.ObjectVal(map[string]cty.Value{
"bar": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"val": cty.UnknownVal(cty.String),
}),
}),
}),
Schema: &configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"bar": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"val": {
Type: cty.String,
Optional: true,
},
},
},
Nesting: configschema.NestingSet,
},
},
},
Expected: &ResourceConfig{
ComputedKeys: []string{"bar.0.val"},
Raw: map[string]interface{}{
"bar": []interface{}{map[string]interface{}{
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
}},
},
Config: map[string]interface{}{
"bar": []interface{}{map[string]interface{}{
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
}},
},
},
},
{
Name: "unknown in attribute sets",
Val: cty.ObjectVal(map[string]cty.Value{
"bar": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"val": cty.UnknownVal(cty.String),
}),
}),
"baz": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"obj": cty.UnknownVal(cty.Object(map[string]cty.Type{
"attr": cty.List(cty.String),
})),
}),
cty.ObjectVal(map[string]cty.Value{
"obj": cty.ObjectVal(map[string]cty.Value{
"attr": cty.UnknownVal(cty.List(cty.String)),
}),
}),
}),
}),
Schema: &configschema.Block{
Attributes: map[string]*configschema.Attribute{
"bar": &configschema.Attribute{
Type: cty.Set(cty.Object(map[string]cty.Type{
"val": cty.String,
})),
},
"baz": &configschema.Attribute{
Type: cty.Set(cty.Object(map[string]cty.Type{
"obj": cty.Object(map[string]cty.Type{
"attr": cty.List(cty.String),
}),
})),
},
},
},
Expected: &ResourceConfig{
ComputedKeys: []string{"bar.0.val", "baz.0.obj.attr", "baz.1.obj"},
Raw: map[string]interface{}{
"bar": []interface{}{map[string]interface{}{
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
}},
"baz": []interface{}{
map[string]interface{}{
"obj": map[string]interface{}{
"attr": "74D93920-ED26-11E3-AC10-0800200C9A66",
},
},
map[string]interface{}{
"obj": "74D93920-ED26-11E3-AC10-0800200C9A66",
},
},
},
Config: map[string]interface{}{
"bar": []interface{}{map[string]interface{}{
"val": "74D93920-ED26-11E3-AC10-0800200C9A66",
}},
"baz": []interface{}{
map[string]interface{}{
"obj": map[string]interface{}{
"attr": "74D93920-ED26-11E3-AC10-0800200C9A66",
},
},
map[string]interface{}{
"obj": "74D93920-ED26-11E3-AC10-0800200C9A66",
},
},
},
},
},
{ {
Name: "null blocks", Name: "null blocks",
Val: cty.ObjectVal(map[string]cty.Value{ Val: cty.ObjectVal(map[string]cty.Value{