helper/schema: track map element counts

This adds "field.#" values to the state/diff with the element count of a
map. This fixes a major issue around not knowing when child elements are
computed when doing variable access of a computed map.

Example, if you have a schema like this:

    "foo": &Schema{
        Type:     TypeMap,
        Computed: true,
    }

And you access it like this in a resource:

    ${type.name.foo.computed-field}

Then Terraform will error that "field foo could not be found on resource
type.name". By adding that "foo.#" is computed, Terraform core will pick
up that it WILL exist, so its okay.
This commit is contained in:
Mitchell Hashimoto 2014-12-15 17:35:16 -08:00
parent 41dabd7abc
commit e5877543b2
2 changed files with 77 additions and 1 deletions

View File

@ -575,7 +575,7 @@ func (m schemaMap) diffMap(
// First get all the values from the state
var stateMap, configMap map[string]string
o, n, _, _ := d.diffChange(k)
o, n, _, computedMap := d.diffChange(k)
if err := mapstructure.WeakDecode(o, &stateMap); err != nil {
return fmt.Errorf("%s: %s", k, err)
}
@ -583,6 +583,48 @@ func (m schemaMap) diffMap(
return fmt.Errorf("%s: %s", k, err)
}
// Get the counts
oldLen, newLen := len(stateMap), len(configMap)
oldStr := strconv.FormatInt(int64(oldLen), 10)
// If the whole map is computed, then just say the # is computed and exit.
if computedMap {
diff.Attributes[k+".#"] = &terraform.ResourceAttrDiff{
Old: oldStr,
NewComputed: true,
}
return nil
}
// Check if the number of elements has changed. If we're computing
// a list and there isn't a config, then it hasn't changed.
changed := oldLen != newLen
if oldLen != 0 && newLen == 0 && schema.Computed {
changed = false
}
computed := oldLen == 0 && newLen == 0 && schema.Computed
if changed || computed {
countSchema := &Schema{
Type: TypeInt,
Computed: schema.Computed,
ForceNew: schema.ForceNew,
}
newStr := ""
if !computed {
newStr = strconv.FormatInt(int64(newLen), 10)
} else {
oldStr = ""
}
diff.Attributes[k+".#"] = countSchema.finalizeDiff(
&terraform.ResourceAttrDiff{
Old: oldStr,
New: newStr,
},
)
}
// If the new map is nil and we're computed, then ignore it.
if n == nil && schema.Computed {
return nil

View File

@ -1205,6 +1205,11 @@ func TestSchemaMap_Diff(t *testing.T) {
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"config_vars.#": &terraform.ResourceAttrDiff{
Old: "0",
New: "1",
},
"config_vars.bar": &terraform.ResourceAttrDiff{
Old: "",
New: "baz",
@ -1381,6 +1386,10 @@ func TestSchemaMap_Diff(t *testing.T) {
Old: "1",
New: "0",
},
"config_vars.0.#": &terraform.ResourceAttrDiff{
Old: "2",
New: "0",
},
"config_vars.0.foo": &terraform.ResourceAttrDiff{
Old: "bar",
NewRemoved: true,
@ -1663,6 +1672,31 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
// #43 - Computed maps
{
Schema: map[string]*Schema{
"vars": &Schema{
Type: TypeMap,
Computed: true,
},
},
State: nil,
Config: nil,
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"vars.#": &terraform.ResourceAttrDiff{
Old: "",
NewComputed: true,
},
},
},
Err: false,
},
}
for i, tc := range cases {