-
Notifications
You must be signed in to change notification settings - Fork 1.8k
/
Copy pathcommon_diff_suppress.go
291 lines (250 loc) · 9.06 KB
/
common_diff_suppress.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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// Contains common diff suppress functions.
package tpgresource
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"log"
"net"
"reflect"
"regexp"
"strconv"
"strings"
"time"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
func OptionalPrefixSuppress(prefix string) schema.SchemaDiffSuppressFunc {
return func(k, old, new string, d *schema.ResourceData) bool {
return prefix+old == new || prefix+new == old
}
}
func IgnoreMissingKeyInMap(key string) schema.SchemaDiffSuppressFunc {
return func(k, old, new string, d *schema.ResourceData) bool {
log.Printf("[DEBUG] - suppressing diff %q with old %q, new %q", k, old, new)
if strings.HasSuffix(k, ".%") {
oldNum, err := strconv.Atoi(old)
if err != nil {
log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", old)
return false
}
newNum, err := strconv.Atoi(new)
if err != nil {
log.Printf("[ERROR] could not parse %q as number, no longer attempting diff suppress", new)
return false
}
return oldNum+1 == newNum
} else if strings.HasSuffix(k, "."+key) {
return old == ""
}
return false
}
}
func OptionalSurroundingSpacesSuppress(k, old, new string, d *schema.ResourceData) bool {
return strings.TrimSpace(old) == strings.TrimSpace(new)
}
func EmptyOrDefaultStringSuppress(defaultVal string) schema.SchemaDiffSuppressFunc {
return func(k, old, new string, d *schema.ResourceData) bool {
return (old == "" && new == defaultVal) || (new == "" && old == defaultVal)
}
}
func EmptyOrFalseSuppressBoolean(k, old, new string, d *schema.ResourceData) bool {
o, n := d.GetChange(k)
return (o == nil && !n.(bool))
}
func IpCidrRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
// The range may be a:
// A) single IP address (e.g. 10.2.3.4)
// B) CIDR format string (e.g. 10.1.2.0/24)
// C) netmask (e.g. /24)
//
// For A) and B), no diff to suppress, they have to match completely.
// For C), The API picks a network IP address and this creates a diff of the form:
// network_interface.0.alias_ip_range.0.ip_cidr_range: "10.128.1.0/24" => "/24"
// We should only compare the mask portion for this case.
if len(new) > 0 && new[0] == '/' {
oldNetmaskStartPos := strings.LastIndex(old, "/")
if oldNetmaskStartPos != -1 {
oldNetmask := old[strings.LastIndex(old, "/"):]
if oldNetmask == new {
return true
}
}
}
return false
}
// Sha256DiffSuppress
// if old is the hex-encoded sha256 sum of new, treat them as equal
func Sha256DiffSuppress(_, old, new string, _ *schema.ResourceData) bool {
return hex.EncodeToString(sha256.New().Sum([]byte(old))) == new
}
func CaseDiffSuppress(_, old, new string, _ *schema.ResourceData) bool {
return strings.ToUpper(old) == strings.ToUpper(new)
}
// Port range '80' and '80-80' is equivalent.
// `old` is read from the server and always has the full range format (e.g. '80-80', '1024-2048').
// `new` can be either a single port or a port range.
func PortRangeDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
return old == new+"-"+new
}
// Single-digit hour is equivalent to hour with leading zero e.g. suppress diff 1:00 => 01:00.
// Assume either value could be in either format.
func Rfc3339TimeDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
if (len(old) == 4 && "0"+old == new) || (len(new) == 4 && "0"+new == old) {
return true
}
return false
}
func EmptyOrUnsetBlockDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
o, n := d.GetChange(strings.TrimSuffix(k, ".#"))
return EmptyOrUnsetBlockDiffSuppressLogic(k, old, new, o, n)
}
// The core logic for EmptyOrUnsetBlockDiffSuppress, in a format that is more conducive
// to unit testing.
func EmptyOrUnsetBlockDiffSuppressLogic(k, old, new string, o, n interface{}) bool {
if !strings.HasSuffix(k, ".#") {
return false
}
var l []interface{}
if old == "0" && new == "1" {
l = n.([]interface{})
} else if new == "0" && old == "1" {
l = o.([]interface{})
} else {
// we don't have one set and one unset, so don't suppress the diff
return false
}
contents, ok := l[0].(map[string]interface{})
if !ok {
return false
}
for _, v := range contents {
if !IsEmptyValue(reflect.ValueOf(v)) {
return false
}
}
return true
}
// Suppress diffs for values that are equivalent except for their use of the words "location"
// compared to "region" or "zone"
func LocationDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
return LocationDiffSuppressHelper(old, new) || LocationDiffSuppressHelper(new, old)
}
func LocationDiffSuppressHelper(a, b string) bool {
return strings.Replace(a, "/locations/", "/regions/", 1) == b ||
strings.Replace(a, "/locations/", "/zones/", 1) == b
}
// For managed SSL certs, if new is an absolute FQDN (trailing '.') but old isn't, treat them as equals.
func AbsoluteDomainSuppress(k, old, new string, _ *schema.ResourceData) bool {
if strings.HasPrefix(k, "managed.0.domains.") {
return old == strings.TrimRight(new, ".") || new == strings.TrimRight(old, ".")
}
return false
}
func TimestampDiffSuppress(format string) schema.SchemaDiffSuppressFunc {
return func(_, old, new string, _ *schema.ResourceData) bool {
oldT, err := time.Parse(format, old)
if err != nil {
return false
}
newT, err := time.Parse(format, new)
if err != nil {
return false
}
return oldT == newT
}
}
// suppress diff when saved is Ipv4 and Ipv6 format while new is required a reference
// this happens for an internal ip for Private Services Connect
func InternalIpDiffSuppress(_, old, new string, _ *schema.ResourceData) bool {
olds := strings.Split(old, "/")
news := strings.Split(new, "/")
if len(olds) == 2 {
if len(news) == 2 {
return bytes.Equal(net.ParseIP(olds[0]), net.ParseIP(news[0])) && olds[1] == news[1]
} else {
return (net.ParseIP(olds[0]) != nil) && (net.ParseIP(new) == nil)
}
}
if (net.ParseIP(old) != nil) && (net.ParseIP(new) != nil) {
return bytes.Equal(net.ParseIP(old), net.ParseIP(new))
}
return (net.ParseIP(old) != nil) && (net.ParseIP(new) == nil)
}
// Suppress diffs for duration format. ex "60.0s" and "60s" same
// https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#duration
func DurationDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
oDuration, err := time.ParseDuration(old)
if err != nil {
return false
}
nDuration, err := time.ParseDuration(new)
if err != nil {
return false
}
return oDuration == nDuration
}
// Use this method when the field accepts either an IP address or a
// self_link referencing a resource (such as google_compute_route's
// next_hop_ilb)
func CompareIpAddressOrSelfLinkOrResourceName(_, old, new string, _ *schema.ResourceData) bool {
// if we can parse `new` as an IP address, then compare as strings
if net.ParseIP(new) != nil {
return new == old
}
// otherwise compare as self links
return CompareSelfLinkOrResourceName("", old, new, nil)
}
// Use this method when subnet is optioanl and auto_create_subnetworks = true
// API sometimes choose a subnet so the diff needs to be ignored
func CompareOptionalSubnet(_, old, new string, _ *schema.ResourceData) bool {
if IsEmptyValue(reflect.ValueOf(new)) {
return true
}
// otherwise compare as self links
return CompareSelfLinkOrResourceName("", old, new, nil)
}
// Suppress diffs in below cases
// "https://hello-rehvs75zla-uc.a.run.app/" -> "https://hello-rehvs75zla-uc.a.run.app"
// "https://hello-rehvs75zla-uc.a.run.app" -> "https://hello-rehvs75zla-uc.a.run.app/"
func LastSlashDiffSuppress(_, old, new string, _ *schema.ResourceData) bool {
if last := len(new) - 1; last >= 0 && new[last] == '/' {
new = new[:last]
}
if last := len(old) - 1; last >= 0 && old[last] == '/' {
old = old[:last]
}
return new == old
}
// Suppress diffs when the value read from api
// has the project number instead of the project name
func ProjectNumberDiffSuppress(_, old, new string, _ *schema.ResourceData) bool {
var a2, b2 string
reN := regexp.MustCompile("projects/\\d+")
re := regexp.MustCompile("projects/[^/]+")
replacement := []byte("projects/equal")
a2 = string(reN.ReplaceAll([]byte(old), replacement))
b2 = string(re.ReplaceAll([]byte(new), replacement))
return a2 == b2
}
func CompareCaseInsensitive(k, old, new string, d *schema.ResourceData) bool {
return strings.ToLower(old) == strings.ToLower(new)
}
func IsNewResource(diff TerraformResourceDiff) bool {
name := diff.Get("name")
return name.(string) == ""
}
func CompareCryptoKeyVersions(_, old, new string, _ *schema.ResourceData) bool {
// The API can return cryptoKeyVersions even though it wasn't specified.
// format: projects/<project>/locations/<region>/keyRings/<keyring>/cryptoKeys/<key>/cryptoKeyVersions/1
kmsKeyWithoutVersions := strings.Split(old, "/cryptoKeyVersions")[0]
if kmsKeyWithoutVersions == new {
return true
}
return false
}
func CidrOrSizeDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
// If the user specified a size and the API returned a full cidr block, suppress.
return strings.HasPrefix(new, "/") && strings.HasSuffix(old, new)
}