compare unordered sets of PathMarkValues

When comparing marks for values during plan and apply, we need to ensure
the order of the marked paths is consistent.
This commit is contained in:
James Bardin 2020-12-17 12:03:28 -05:00
parent 428d404d92
commit 79a31f627b
3 changed files with 145 additions and 3 deletions

39
terraform/marks.go Normal file
View File

@ -0,0 +1,39 @@
package terraform
import (
"fmt"
"sort"
"github.com/zclconf/go-cty/cty"
)
// marksEqual compares 2 unordered sets of PathValue marks for equality, with
// the comparison using the cty.PathValueMarks.Equal method.
func marksEqual(a, b []cty.PathValueMarks) bool {
if len(a) == 0 && len(b) == 0 {
return true
}
if len(a) != len(b) {
return false
}
less := func(s []cty.PathValueMarks) func(i, j int) bool {
return func(i, j int) bool {
// the sort only needs to be consistent, so use the GoString format
// to get a comparable value
return fmt.Sprintf("%#v", s[i]) < fmt.Sprintf("%#v", s[j])
}
}
sort.Slice(a, less(a))
sort.Slice(b, less(b))
for i := 0; i < len(a); i++ {
if !a[i].Equal(b[i]) {
return false
}
}
return true
}

104
terraform/marks_test.go Normal file
View File

@ -0,0 +1,104 @@
package terraform
import (
"fmt"
"testing"
"github.com/zclconf/go-cty/cty"
)
func TestMarksEqual(t *testing.T) {
for i, tc := range []struct {
a, b []cty.PathValueMarks
equal bool
}{
{
[]cty.PathValueMarks{
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks("sensitive")},
},
[]cty.PathValueMarks{
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks("sensitive")},
},
true,
},
{
[]cty.PathValueMarks{
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks("sensitive")},
},
[]cty.PathValueMarks{
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "A"}}, Marks: cty.NewValueMarks("sensitive")},
},
false,
},
{
[]cty.PathValueMarks{
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks("sensitive")},
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "b"}}, Marks: cty.NewValueMarks("sensitive")},
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "c"}}, Marks: cty.NewValueMarks("sensitive")},
},
[]cty.PathValueMarks{
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "b"}}, Marks: cty.NewValueMarks("sensitive")},
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "c"}}, Marks: cty.NewValueMarks("sensitive")},
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks("sensitive")},
},
true,
},
{
[]cty.PathValueMarks{
cty.PathValueMarks{
Path: cty.Path{cty.GetAttrStep{Name: "a"}, cty.GetAttrStep{Name: "b"}},
Marks: cty.NewValueMarks("sensitive"),
},
cty.PathValueMarks{
Path: cty.Path{cty.GetAttrStep{Name: "a"}, cty.GetAttrStep{Name: "c"}},
Marks: cty.NewValueMarks("sensitive"),
},
},
[]cty.PathValueMarks{
cty.PathValueMarks{
Path: cty.Path{cty.GetAttrStep{Name: "a"}, cty.GetAttrStep{Name: "c"}},
Marks: cty.NewValueMarks("sensitive"),
},
cty.PathValueMarks{
Path: cty.Path{cty.GetAttrStep{Name: "a"}, cty.GetAttrStep{Name: "b"}},
Marks: cty.NewValueMarks("sensitive"),
},
},
true,
},
{
[]cty.PathValueMarks{
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks("sensitive")},
},
[]cty.PathValueMarks{
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "b"}}, Marks: cty.NewValueMarks("sensitive")},
},
false,
},
{
nil,
nil,
true,
},
{
[]cty.PathValueMarks{
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks("sensitive")},
},
nil,
false,
},
{
nil,
[]cty.PathValueMarks{
cty.PathValueMarks{Path: cty.Path{cty.GetAttrStep{Name: "a"}}, Marks: cty.NewValueMarks("sensitive")},
},
false,
},
} {
t.Run(fmt.Sprint(i), func(t *testing.T) {
if marksEqual(tc.a, tc.b) != tc.equal {
t.Fatalf("marksEqual(\n%#v,\n%#v,\n) != %t\n", tc.a, tc.b, tc.equal)
}
})
}
}

View File

@ -3,7 +3,6 @@ package terraform
import (
"fmt"
"log"
"reflect"
"strings"
multierror "github.com/hashicorp/go-multierror"
@ -920,7 +919,7 @@ func (n *NodeAbstractResourceInstance) plan(
// If we plan to write or delete sensitive paths from state,
// this is an Update action
if action == plans.NoOp && !reflect.DeepEqual(priorPaths, unmarkedPaths) {
if action == plans.NoOp && !marksEqual(unmarkedPaths, priorPaths) {
action = plans.Update
}
@ -1863,7 +1862,7 @@ func (n *NodeAbstractResourceInstance) apply(
// persisted.
eqV := unmarkedBefore.Equals(unmarkedAfter)
eq := eqV.IsKnown() && eqV.True()
if change.Action == plans.Update && eq && !reflect.DeepEqual(beforePaths, afterPaths) {
if change.Action == plans.Update && eq && !marksEqual(beforePaths, afterPaths) {
// Copy the previous state, changing only the value
newState = &states.ResourceInstanceObject{
CreateBeforeDestroy: state.CreateBeforeDestroy,