diff --git a/terraform/diff.go b/terraform/diff.go index 5a99e0fac..d5008c6c8 100644 --- a/terraform/diff.go +++ b/terraform/diff.go @@ -471,12 +471,12 @@ func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) { } } - // This is a little tricky, but when a diff contains a computed list - // or set that can only be interpolated after the apply command has - // created the dependent resources, it could turn out that the result - // is actually the same as the existing state which would remove the - // key from the diff. - if diffOld.NewComputed && strings.HasSuffix(k, ".#") { + // This is a little tricky, but when a diff contains a computed + // list, set, or map that can only be interpolated after the apply + // command has created the dependent resources, it could turn out + // that the result is actually the same as the existing state which + // would remove the key from the diff. + if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { ok = true } @@ -492,10 +492,16 @@ func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) { } } - if diffOld.NewComputed && strings.HasSuffix(k, ".#") { - // This is a computed list or set, so remove any keys with this - // prefix from the check list. - kprefix := k[:len(k)-1] + // search for the suffix of the base of a [computed] map, list or set. + multiVal := regexp.MustCompile(`\.(#|~#|%)$`) + match := multiVal.FindStringSubmatch(k) + + if diffOld.NewComputed && len(match) == 2 { + matchLen := len(match[1]) + + // This is a computed list, set, or map, so remove any keys with + // this prefix from the check list. + kprefix := k[:len(k)-matchLen] for k2, _ := range checkOld { if strings.HasPrefix(k2, kprefix) { delete(checkOld, k2) diff --git a/terraform/diff_test.go b/terraform/diff_test.go index f769737c5..bf1c1c9f2 100644 --- a/terraform/diff_test.go +++ b/terraform/diff_test.go @@ -566,6 +566,34 @@ func TestInstanceDiffSame(t *testing.T) { "", }, + // Computed values in maps will fail the "Same" check as well + { + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.%": &ResourceAttrDiff{ + Old: "", + New: "", + NewComputed: true, + }, + }, + }, + &InstanceDiff{ + Attributes: map[string]*ResourceAttrDiff{ + "foo.%": &ResourceAttrDiff{ + Old: "0", + New: "1", + NewComputed: false, + }, + "foo.val": &ResourceAttrDiff{ + Old: "", + New: "something", + }, + }, + }, + true, + "", + }, + // In a DESTROY/CREATE scenario, the plan diff will be run against the // state of the old instance, while the apply diff will be run against an // empty state (because the state is cleared when the destroy runs.)