forked from fastly/terraform-provider-fastly
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdiff.go
147 lines (128 loc) · 4.3 KB
/
diff.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package fastly
import (
"fmt"
"reflect"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
// KeyFunc calculates a key from an element
type KeyFunc func(any) (any, error)
// SetDiff diffs two sets using a key to identify which elements have been added, changed, removed or not modified.
//
// This object compares sets using Terraform's schema.Set methods (e.g. Difference() and Intersection())
// so that the same differences displayed to the user are honoured here.
//
// SetDiff however is able to tell if two elements from two distinct sets have the same key. This is useful to detect
// that an element should be updated instead of recreated on the remote server.
type SetDiff struct {
keyFunc KeyFunc
}
// DiffResult contains the differences between two sets
type DiffResult struct {
Added []any
Modified []any
Deleted []any
Unmodified []any
}
// NewSetDiff creates a new SetDiff with a provided KeyFunc.
func NewSetDiff(keyFunc KeyFunc) *SetDiff {
return &SetDiff{
keyFunc: keyFunc,
}
}
// Diff diffs two Set objects and returns a DiffResult object containing the diffs.
//
// The DiffResult object will contain the elements from newSet on the Modified field.
//
// NOTE: there is a caveat with the current implementation which is related to
// the lookup 'key' you specify. If the key you use (to lookup a resource
// within the comparable set) is also updatable via the fastly API, then that
// means you'll end up deleting and recreating the resource rather than simply
// updating it (which is less efficient, as it's two separate operations).
//
// For example, a 'domain' can be updated by changing either its 'name' or its
// 'comment' attribute, but in order to compare changes using SetDiff we only
// really have the option to use 'name' as the lookup key.
func (h *SetDiff) Diff(oldSet, newSet *schema.Set) (*DiffResult, error) {
// Convert the set into a map to facilitate lookup
oldSetMap := map[any]any{}
newSetMap := map[any]any{}
for _, elem := range oldSet.List() {
key, err := h.computeKey(elem)
if err != nil {
return nil, newElementKeyError(elem, err)
}
oldSetMap[key] = elem
}
for _, elem := range newSet.List() {
key, err := h.computeKey(elem)
if err != nil {
return nil, newElementKeyError(elem, err)
}
newSetMap[key] = elem
}
// Compute all added and modified elements using Terraform set comparison method.
var added, modified []any
for _, newElem := range newSet.Difference(oldSet).List() {
key, err := h.computeKey(newElem)
if err != nil {
return nil, newElementKeyError(newElem, err)
}
if oldSetMap[key] != nil {
modified = append(modified, newElem)
} else {
added = append(added, newElem)
}
}
var deleted []any
for _, oldElem := range oldSet.Difference(newSet).List() {
key, err := h.computeKey(oldElem)
if err != nil {
return nil, newElementKeyError(oldElem, err)
}
if newSetMap[key] == nil {
deleted = append(deleted, oldElem)
}
}
unmodified := oldSet.Intersection(newSet).List()
return &DiffResult{
Added: added,
Modified: modified,
Deleted: deleted,
Unmodified: unmodified,
}, nil
}
func (h *SetDiff) computeKey(elem any) (any, error) {
key, err := h.keyFunc(elem)
if err != nil {
return nil, err
}
if key == nil {
return nil, fmt.Errorf("invalid key for element %v, %v", elem, err)
}
return key, nil
}
// Filter filters out unmodified fields of a Set elements map data structure by ranging over
// the original data and comparing each field against the new data.
//
// The motivation for this function is to avoid resetting an attribute on a
// resource to a value that hasn't actually changed because (depending on the
// attribute) it might have unexpected consequences (e.g. a nested resource
// gets replaced/recreated). Safer to only update attributes that need to be.
func (h *SetDiff) Filter(modified map[string]any, oldSet *schema.Set) map[string]any {
elements := oldSet.List()
filtered := make(map[string]any)
for _, e := range elements {
m := e.(map[string]any)
if m["name"].(string) == modified["name"].(string) {
for k, v := range m {
if !reflect.DeepEqual(v, modified[k]) {
filtered[k] = modified[k]
}
}
}
}
return filtered
}
func newElementKeyError(elem any, err error) error {
return fmt.Errorf("error computing the key for element %v, %v", elem, err)
}