helper/schema: only mark "ForceNew" on resources that cause the ForceNew

Fixes #2748

This changes the diff to only mark "forces new resource" on the fields
that actually caused the new resource, not every field that changed.
This makes diffs much more accurate.

I'd like to request a review but I'm going to defer merging until
Terraform 0.8. Changes like this are very possible to cause "diffs
didn't match" errors and I want some real world testing in a beta before
we hit prod with this.
This commit is contained in:
Mitchell Hashimoto 2016-10-25 20:56:45 -04:00
parent 645d3271f6
commit e45debe0e5
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
2 changed files with 89 additions and 21 deletions

View File

@ -288,9 +288,21 @@ func (s *Schema) finalizeDiff(
d.New = normalizeBoolString(d.New)
}
if s.Computed && !d.NewRemoved && d.New == "" {
// Computed attribute without a new value set
d.NewComputed = true
}
if s.ForceNew {
// Force new, set it to true in the diff
d.RequiresNew = true
// ForceNew, mark that this field is requiring new under the
// following conditions, explained below:
//
// * Old != New - There is a change in value. This field
// is therefore causing a new resource.
//
// * NewComputed - This field is being computed, hence a
// potential change in value, mark as causing a new resource.
d.RequiresNew = d.Old != d.New || d.NewComputed
}
if d.NewRemoved {

View File

@ -2341,9 +2341,8 @@ func TestSchemaMap_Diff(t *testing.T) {
RequiresNew: true,
},
"instances.3": &terraform.ResourceAttrDiff{
Old: "333",
New: "333",
RequiresNew: true,
Old: "333",
New: "333",
},
"instances.4": &terraform.ResourceAttrDiff{
Old: "",
@ -2426,6 +2425,61 @@ func TestSchemaMap_Diff(t *testing.T) {
Err: false,
},
"Set ForceNew only marks the changing element as ForceNew": {
Schema: map[string]*Schema{
"ports": &Schema{
Type: TypeSet,
Required: true,
ForceNew: true,
Elem: &Schema{Type: TypeInt},
Set: func(a interface{}) int {
return a.(int)
},
},
},
State: &terraform.InstanceState{
Attributes: map[string]string{
"ports.#": "3",
"ports.1": "1",
"ports.2": "2",
"ports.4": "4",
},
},
Config: map[string]interface{}{
"ports": []interface{}{5, 2, 1},
},
Diff: &terraform.InstanceDiff{
Attributes: map[string]*terraform.ResourceAttrDiff{
"ports.#": &terraform.ResourceAttrDiff{
Old: "3",
New: "3",
},
"ports.1": &terraform.ResourceAttrDiff{
Old: "1",
New: "1",
},
"ports.2": &terraform.ResourceAttrDiff{
Old: "2",
New: "2",
},
"ports.5": &terraform.ResourceAttrDiff{
Old: "",
New: "5",
RequiresNew: true,
},
"ports.4": &terraform.ResourceAttrDiff{
Old: "4",
New: "0",
NewRemoved: true,
RequiresNew: true,
},
},
},
},
"removed optional items should trigger ForceNew": {
Schema: map[string]*Schema{
"description": &Schema{
@ -2495,26 +2549,28 @@ func TestSchemaMap_Diff(t *testing.T) {
}
for tn, tc := range cases {
c, err := config.NewRawConfig(tc.Config)
if err != nil {
t.Fatalf("#%q err: %s", tn, err)
}
if len(tc.ConfigVariables) > 0 {
if err := c.Interpolate(tc.ConfigVariables); err != nil {
t.Run(tn, func(t *testing.T) {
c, err := config.NewRawConfig(tc.Config)
if err != nil {
t.Fatalf("#%q err: %s", tn, err)
}
}
d, err := schemaMap(tc.Schema).Diff(
tc.State, terraform.NewResourceConfig(c))
if err != nil != tc.Err {
t.Fatalf("#%q err: %s", tn, err)
}
if len(tc.ConfigVariables) > 0 {
if err := c.Interpolate(tc.ConfigVariables); err != nil {
t.Fatalf("#%q err: %s", tn, err)
}
}
if !reflect.DeepEqual(tc.Diff, d) {
t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Diff, d)
}
d, err := schemaMap(tc.Schema).Diff(
tc.State, terraform.NewResourceConfig(c))
if err != nil != tc.Err {
t.Fatalf("#%q err: %s", tn, err)
}
if !reflect.DeepEqual(tc.Diff, d) {
t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.Diff, d)
}
})
}
}