diff --git a/internal/lang/marks/paths.go b/internal/lang/marks/paths.go index 461ec1bb42..b1833823ab 100644 --- a/internal/lang/marks/paths.go +++ b/internal/lang/marks/paths.go @@ -4,6 +4,9 @@ package marks import ( + "fmt" + "sort" + "github.com/zclconf/go-cty/cty" ) @@ -60,3 +63,34 @@ func MarkPaths(val cty.Value, mark any, paths []cty.Path) cty.Value { } return val.MarkWithPaths(markses) } + +// 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 +} diff --git a/internal/stacks/stackruntime/internal/stackeval/input_variable.go b/internal/stacks/stackruntime/internal/stackeval/input_variable.go index 1eae32f44d..6ca699e994 100644 --- a/internal/stacks/stackruntime/internal/stackeval/input_variable.go +++ b/internal/stacks/stackruntime/internal/stackeval/input_variable.go @@ -11,6 +11,7 @@ import ( "github.com/zclconf/go-cty/cty" "github.com/zclconf/go-cty/cty/convert" + "github.com/hashicorp/terraform/internal/lang/marks" "github.com/hashicorp/terraform/internal/plans" "github.com/hashicorp/terraform/internal/plans/objchange" "github.com/hashicorp/terraform/internal/promising" @@ -268,13 +269,17 @@ func (v *InputVariable) PlanChanges(ctx context.Context) ([]stackplan.PlannedCha // we'll set the operation to an update even if the actual hasn't // changed action = plans.Update - } else if result := before.Equals(after); result.IsKnown() && result.True() { - // The values are definitely equal, so NoOp change. - action = plans.NoOp } else { - // If we don't know for sure that the values are equal, then we'll - // call this an update. - action = plans.Update + unmarkedBefore, beforePaths := before.UnmarkDeepWithPaths() + unmarkedAfter, afterPaths := after.UnmarkDeepWithPaths() + result := unmarkedBefore.Equals(unmarkedAfter) + if result.IsKnown() && result.True() && marks.MarksEqual(beforePaths, afterPaths) { + action = plans.NoOp + } else { + // If we don't know for sure that the values are equal, then we'll + // call this an update. + action = plans.Update + } } } else { action = plans.Create diff --git a/internal/stacks/stackruntime/internal/stackeval/stack.go b/internal/stacks/stackruntime/internal/stackeval/stack.go index 7363099ef7..f03f39d2d1 100644 --- a/internal/stacks/stackruntime/internal/stackeval/stack.go +++ b/internal/stacks/stackruntime/internal/stackeval/stack.go @@ -800,7 +800,10 @@ func (s *Stack) PlanChanges(ctx context.Context) ([]stackplan.PlannedChange, tfd if actualBefore, exists := beforeVal[addr]; exists { before = actualBefore - if result := before.Equals(after); result.IsKnown() && result.True() { + unmarkedBefore, beforePaths := before.UnmarkDeepWithPaths() + unmarkedAfter, afterPaths := after.UnmarkDeepWithPaths() + result := unmarkedBefore.Equals(unmarkedAfter) + if result.IsKnown() && result.True() && marks.MarksEqual(beforePaths, afterPaths) { action = plans.NoOp } else { action = plans.Update diff --git a/internal/terraform/marks.go b/internal/terraform/marks.go index 840c49e778..65c32b98a1 100644 --- a/internal/terraform/marks.go +++ b/internal/terraform/marks.go @@ -4,9 +4,7 @@ package terraform import ( - "fmt" - "sort" - + "github.com/hashicorp/terraform/internal/lang/marks" "github.com/zclconf/go-cty/cty" ) @@ -14,36 +12,5 @@ import ( func valueMarksEqual(a, b cty.Value) bool { _, aMarks := a.UnmarkDeepWithPaths() _, bMarks := b.UnmarkDeepWithPaths() - return marksEqual(aMarks, bMarks) -} - -// 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 + return marks.MarksEqual(aMarks, bMarks) } diff --git a/internal/terraform/node_resource_abstract_instance.go b/internal/terraform/node_resource_abstract_instance.go index a529c42f73..5c00799931 100644 --- a/internal/terraform/node_resource_abstract_instance.go +++ b/internal/terraform/node_resource_abstract_instance.go @@ -2462,7 +2462,7 @@ func (n *NodeAbstractResourceInstance) apply( // persisted. eqV := unmarkedBefore.Equals(unmarkedAfter) eq := eqV.IsKnown() && eqV.True() - if change.Action == plans.Update && eq && !marksEqual(beforePaths, afterPaths) { + if change.Action == plans.Update && eq && !marks.MarksEqual(beforePaths, afterPaths) { // Copy the previous state, changing only the value newState := &states.ResourceInstanceObject{ CreateBeforeDestroy: state.CreateBeforeDestroy,