diff --git a/helper/schema/field_reader_config.go b/helper/schema/field_reader_config.go index 0d4c2a97c9bb..3e62a70a5a9b 100644 --- a/helper/schema/field_reader_config.go +++ b/helper/schema/field_reader_config.go @@ -265,12 +265,30 @@ func (r *ConfigFieldReader) hasComputedSubKeys(key string, schema *Schema) bool switch t := schema.Elem.(type) { case *Resource: for k, schema := range t.Schema { - if r.Config.IsComputed(prefix + k) { + addr := prefix + k + if r.Config.IsComputed(addr) { return true } - if r.hasComputedSubKeys(prefix+k, schema) { - return true + // We need to loop into sets and lists to ensure we pass the correct + // address to the raw config - otherwise for sets we get something like + // set.0.set.item instead of set.0.set.0.item, which renders an + // inaccurate result. + if schema.Type == TypeSet || schema.Type == TypeList { + raw, err := readListField(&nestedConfigFieldReader{r}, strings.Split(addr, "."), schema) + if err != nil { + panic(fmt.Errorf("readListField failed when field was supposed to be list-like: %v", err)) + } + // Just range into the address space here, we don't need the value. + for i := range raw.Value.([]interface{}) { + if r.hasComputedSubKeys(addr+"."+strconv.Itoa(i), schema) { + return true + } + } + } else { + if r.hasComputedSubKeys(addr, schema) { + return true + } } } } diff --git a/helper/schema/schema_test.go b/helper/schema/schema_test.go index 94b6b4dabb90..33ac26f76322 100644 --- a/helper/schema/schema_test.go +++ b/helper/schema/schema_test.go @@ -3267,6 +3267,159 @@ func TestSchemaMap_DiffSuppress(t *testing.T) { Err: false, }, + + "Complex structure with set of computed string should mark root set as computed": { + Schema: map[string]*Schema{ + "outer": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "outer_str": &Schema{ + Type: TypeString, + Optional: true, + }, + "inner": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_str": &Schema{ + Type: TypeString, + Optional: true, + }, + }, + }, + Set: func(v interface{}) int { + return 2 + }, + }, + }, + }, + Set: func(v interface{}) int { + return 1 + }, + }, + }, + + State: nil, + + Config: map[string]interface{}{ + "outer": []map[string]interface{}{ + map[string]interface{}{ + "outer_str": "foo", + "inner": []map[string]interface{}{ + map[string]interface{}{ + "inner_str": "${var.bar}", + }, + }, + }, + }, + }, + + ConfigVariables: map[string]ast.Variable{ + "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), + }, + + ExpectedDiff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "outer.#": &terraform.ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "outer.~1.outer_str": &terraform.ResourceAttrDiff{ + Old: "", + New: "foo", + }, + "outer.~1.inner.#": &terraform.ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "outer.~1.inner.~2.inner_str": &terraform.ResourceAttrDiff{ + Old: "", + New: "${var.bar}", + NewComputed: true, + }, + }, + }, + + Err: false, + }, + + "Complex structure with complex list of computed string should mark root set as computed": { + Schema: map[string]*Schema{ + "outer": &Schema{ + Type: TypeSet, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "outer_str": &Schema{ + Type: TypeString, + Optional: true, + }, + "inner": &Schema{ + Type: TypeList, + Optional: true, + Elem: &Resource{ + Schema: map[string]*Schema{ + "inner_str": &Schema{ + Type: TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + Set: func(v interface{}) int { + return 1 + }, + }, + }, + + State: nil, + + Config: map[string]interface{}{ + "outer": []map[string]interface{}{ + map[string]interface{}{ + "outer_str": "foo", + "inner": []map[string]interface{}{ + map[string]interface{}{ + "inner_str": "${var.bar}", + }, + }, + }, + }, + }, + + ConfigVariables: map[string]ast.Variable{ + "var.bar": interfaceToVariableSwallowError(config.UnknownVariableValue), + }, + + ExpectedDiff: &terraform.InstanceDiff{ + Attributes: map[string]*terraform.ResourceAttrDiff{ + "outer.#": &terraform.ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "outer.~1.outer_str": &terraform.ResourceAttrDiff{ + Old: "", + New: "foo", + }, + "outer.~1.inner.#": &terraform.ResourceAttrDiff{ + Old: "0", + New: "1", + }, + "outer.~1.inner.0.inner_str": &terraform.ResourceAttrDiff{ + Old: "", + New: "${var.bar}", + NewComputed: true, + }, + }, + }, + + Err: false, + }, } for tn, tc := range cases {