mirror of
https://github.com/opentffoundation/opentf.git
synced 2025-12-25 01:00:16 -05:00
Smarter approach to .Equals on state objects for unordered lists (#3024)
Signed-off-by: Christian Mesh <christianmesh1@gmail.com>
This commit is contained in:
@@ -6,6 +6,9 @@
|
||||
package states
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
ctyjson "github.com/zclconf/go-cty/cty/json"
|
||||
|
||||
@@ -66,6 +69,96 @@ type ResourceInstanceObjectSrc struct {
|
||||
CreateBeforeDestroy bool
|
||||
}
|
||||
|
||||
// Compare two lists using an given element equal function, ignoring order and duplicates
|
||||
func equalSlicesIgnoreOrder[S ~[]E, E any](a, b S, fn func(E, E) bool) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Not sure if this is the most efficient approach, but it works
|
||||
// First check if all elements in a existing in b
|
||||
for _, v := range a {
|
||||
found := false
|
||||
for _, o := range b {
|
||||
if fn(v, o) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Now check if all elements in b exist in a
|
||||
// This is necessary just in case there are duplicate entries (there should not be).
|
||||
for _, v := range b {
|
||||
found := false
|
||||
for _, o := range a {
|
||||
if fn(v, o) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (os *ResourceInstanceObjectSrc) Equal(other *ResourceInstanceObjectSrc) bool {
|
||||
if os == other {
|
||||
return true
|
||||
}
|
||||
if os == nil || other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if os.SchemaVersion != other.SchemaVersion {
|
||||
return false
|
||||
}
|
||||
|
||||
if !bytes.Equal(os.AttrsJSON, other.AttrsJSON) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(os.AttrsFlat, other.AttrsFlat) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Ignore order/duplicates as that is the assumption in the rest of the codebase.
|
||||
// Given that these are generated from maps, it is known that the order is not consistent.
|
||||
if !equalSlicesIgnoreOrder(os.AttrSensitivePaths, other.AttrSensitivePaths, cty.PathValueMarks.Equal) {
|
||||
return false
|
||||
}
|
||||
// Ignore order/duplicates as that is the assumption in the rest of the codebase.
|
||||
// Given that these are generated from maps, it is known that the order is not consistent.
|
||||
if !equalSlicesIgnoreOrder(os.TransientPathValueMarks, other.TransientPathValueMarks, cty.PathValueMarks.Equal) {
|
||||
return false
|
||||
}
|
||||
|
||||
if !bytes.Equal(os.Private, other.Private) {
|
||||
return false
|
||||
}
|
||||
|
||||
if os.Status != other.Status {
|
||||
return false
|
||||
}
|
||||
|
||||
// This represents a set of dependencies. They must all be resolved before executing and therefore the order does not matter.
|
||||
if !equalSlicesIgnoreOrder(os.Dependencies, other.Dependencies, addrs.ConfigResource.Equal) {
|
||||
return false
|
||||
}
|
||||
|
||||
if os.CreateBeforeDestroy != other.CreateBeforeDestroy {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Decode unmarshals the raw representation of the object attributes. Pass the
|
||||
// implied type of the corresponding resource type schema for correct operation.
|
||||
//
|
||||
|
||||
@@ -59,6 +59,33 @@ func (rs *Resource) EnsureInstance(key addrs.InstanceKey) *ResourceInstance {
|
||||
return ret
|
||||
}
|
||||
|
||||
func (rs *Resource) Equal(other *Resource) bool {
|
||||
if rs == other {
|
||||
// Handles both pointers being nil
|
||||
return true
|
||||
}
|
||||
if rs == nil || other == nil {
|
||||
// Handles one pointer being nil
|
||||
return false
|
||||
}
|
||||
|
||||
if !rs.Addr.Equal(other.Addr) || rs.ProviderConfig.String() != other.ProviderConfig.String() {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(rs.Instances) != len(other.Instances) {
|
||||
return false
|
||||
}
|
||||
|
||||
for key, inst := range rs.Instances {
|
||||
if !inst.Equal(other.Instances[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ResourceInstance represents the state of a particular instance of a resource.
|
||||
type ResourceInstance struct {
|
||||
// Current, if non-nil, is the remote object that is currently represented
|
||||
@@ -100,6 +127,40 @@ func (i *ResourceInstance) HasAnyDeposed() bool {
|
||||
return i != nil && len(i.Deposed) > 0
|
||||
}
|
||||
|
||||
func (i *ResourceInstance) Equal(other *ResourceInstance) bool {
|
||||
if i == other {
|
||||
// Handles both pointers being nil
|
||||
return true
|
||||
}
|
||||
if i == nil || other == nil {
|
||||
// Handles one pointer being nil
|
||||
return false
|
||||
}
|
||||
|
||||
if !i.Current.Equal(other.Current) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (i.ProviderKey == nil) != (other.ProviderKey == nil) {
|
||||
return false
|
||||
}
|
||||
if i.ProviderKey != nil && i.ProviderKey.Value().Equals(other.ProviderKey.Value()).False() {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(i.Deposed) != len(other.Deposed) {
|
||||
return false
|
||||
}
|
||||
|
||||
for key, dep := range i.Deposed {
|
||||
if !dep.Equal(other.Deposed[key]) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// HasObjects returns true if this resource has any objects at all, whether
|
||||
// current or deposed.
|
||||
func (i *ResourceInstance) HasObjects() bool {
|
||||
|
||||
@@ -62,7 +62,7 @@ func sameManagedResources(s1, s2 *State) bool {
|
||||
continue
|
||||
}
|
||||
otherRS := s2.Resource(addr)
|
||||
if !reflect.DeepEqual(rs, otherRS) {
|
||||
if !rs.Equal(otherRS) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user