aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/config/hcl2shim/values_equiv.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/config/hcl2shim/values_equiv.go')
-rw-r--r--vendor/github.com/hashicorp/terraform/config/hcl2shim/values_equiv.go214
1 files changed, 214 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/config/hcl2shim/values_equiv.go b/vendor/github.com/hashicorp/terraform/config/hcl2shim/values_equiv.go
new file mode 100644
index 0000000..92f0213
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/config/hcl2shim/values_equiv.go
@@ -0,0 +1,214 @@
1package hcl2shim
2
3import (
4 "github.com/zclconf/go-cty/cty"
5)
6
7// ValuesSDKEquivalent returns true if both of the given values seem equivalent
8// as far as the legacy SDK diffing code would be concerned.
9//
10// Since SDK diffing is a fuzzy, inexact operation, this function is also
11// fuzzy and inexact. It will err on the side of returning false if it
12// encounters an ambiguous situation. Ambiguity is most common in the presence
13// of sets because in practice it is impossible to exactly correlate
14// nonequal-but-equivalent set elements because they have no identity separate
15// from their value.
16//
17// This must be used _only_ for comparing values for equivalence within the
18// SDK planning code. It is only meaningful to compare the "prior state"
19// provided by Terraform Core with the "planned new state" produced by the
20// legacy SDK code via shims. In particular it is not valid to use this
21// function with their the config value or the "proposed new state" value
22// because they contain only the subset of data that Terraform Core itself is
23// able to determine.
24func ValuesSDKEquivalent(a, b cty.Value) bool {
25 if a == cty.NilVal || b == cty.NilVal {
26 // We don't generally expect nils to appear, but we'll allow them
27 // for robustness since the data structures produced by legacy SDK code
28 // can sometimes be non-ideal.
29 return a == b // equivalent if they are _both_ nil
30 }
31 if a.RawEquals(b) {
32 // Easy case. We use RawEquals because we want two unknowns to be
33 // considered equal here, whereas "Equals" would return unknown.
34 return true
35 }
36 if !a.IsKnown() || !b.IsKnown() {
37 // Two unknown values are equivalent regardless of type. A known is
38 // never equivalent to an unknown.
39 return a.IsKnown() == b.IsKnown()
40 }
41 if aZero, bZero := valuesSDKEquivalentIsNullOrZero(a), valuesSDKEquivalentIsNullOrZero(b); aZero || bZero {
42 // Two null/zero values are equivalent regardless of type. A non-zero is
43 // never equivalent to a zero.
44 return aZero == bZero
45 }
46
47 // If we get down here then we are guaranteed that both a and b are known,
48 // non-null values.
49
50 aTy := a.Type()
51 bTy := b.Type()
52 switch {
53 case aTy.IsSetType() && bTy.IsSetType():
54 return valuesSDKEquivalentSets(a, b)
55 case aTy.IsListType() && bTy.IsListType():
56 return valuesSDKEquivalentSequences(a, b)
57 case aTy.IsTupleType() && bTy.IsTupleType():
58 return valuesSDKEquivalentSequences(a, b)
59 case aTy.IsMapType() && bTy.IsMapType():
60 return valuesSDKEquivalentMappings(a, b)
61 case aTy.IsObjectType() && bTy.IsObjectType():
62 return valuesSDKEquivalentMappings(a, b)
63 case aTy == cty.Number && bTy == cty.Number:
64 return valuesSDKEquivalentNumbers(a, b)
65 default:
66 // We've now covered all the interesting cases, so anything that falls
67 // down here cannot be equivalent.
68 return false
69 }
70}
71
72// valuesSDKEquivalentIsNullOrZero returns true if the given value is either
73// null or is the "zero value" (in the SDK/Go sense) for its type.
74func valuesSDKEquivalentIsNullOrZero(v cty.Value) bool {
75 if v == cty.NilVal {
76 return true
77 }
78
79 ty := v.Type()
80 switch {
81 case !v.IsKnown():
82 return false
83 case v.IsNull():
84 return true
85
86 // After this point, v is always known and non-null
87 case ty.IsListType() || ty.IsSetType() || ty.IsMapType() || ty.IsObjectType() || ty.IsTupleType():
88 return v.LengthInt() == 0
89 case ty == cty.String:
90 return v.RawEquals(cty.StringVal(""))
91 case ty == cty.Number:
92 return v.RawEquals(cty.Zero)
93 case ty == cty.Bool:
94 return v.RawEquals(cty.False)
95 default:
96 // The above is exhaustive, but for robustness we'll consider anything
97 // else to _not_ be zero unless it is null.
98 return false
99 }
100}
101
102// valuesSDKEquivalentSets returns true only if each of the elements in a can
103// be correlated with at least one equivalent element in b and vice-versa.
104// This is a fuzzy operation that prefers to signal non-equivalence if it cannot
105// be certain that all elements are accounted for.
106func valuesSDKEquivalentSets(a, b cty.Value) bool {
107 if aLen, bLen := a.LengthInt(), b.LengthInt(); aLen != bLen {
108 return false
109 }
110
111 // Our methodology here is a little tricky, to deal with the fact that
112 // it's impossible to directly correlate two non-equal set elements because
113 // they don't have identities separate from their values.
114 // The approach is to count the number of equivalent elements each element
115 // of a has in b and vice-versa, and then return true only if each element
116 // in both sets has at least one equivalent.
117 as := a.AsValueSlice()
118 bs := b.AsValueSlice()
119 aeqs := make([]bool, len(as))
120 beqs := make([]bool, len(bs))
121 for ai, av := range as {
122 for bi, bv := range bs {
123 if ValuesSDKEquivalent(av, bv) {
124 aeqs[ai] = true
125 beqs[bi] = true
126 }
127 }
128 }
129
130 for _, eq := range aeqs {
131 if !eq {
132 return false
133 }
134 }
135 for _, eq := range beqs {
136 if !eq {
137 return false
138 }
139 }
140 return true
141}
142
143// valuesSDKEquivalentSequences decides equivalence for two sequence values
144// (lists or tuples).
145func valuesSDKEquivalentSequences(a, b cty.Value) bool {
146 as := a.AsValueSlice()
147 bs := b.AsValueSlice()
148 if len(as) != len(bs) {
149 return false
150 }
151
152 for i := range as {
153 if !ValuesSDKEquivalent(as[i], bs[i]) {
154 return false
155 }
156 }
157 return true
158}
159
160// valuesSDKEquivalentMappings decides equivalence for two mapping values
161// (maps or objects).
162func valuesSDKEquivalentMappings(a, b cty.Value) bool {
163 as := a.AsValueMap()
164 bs := b.AsValueMap()
165 if len(as) != len(bs) {
166 return false
167 }
168
169 for k, av := range as {
170 bv, ok := bs[k]
171 if !ok {
172 return false
173 }
174 if !ValuesSDKEquivalent(av, bv) {
175 return false
176 }
177 }
178 return true
179}
180
181// valuesSDKEquivalentNumbers decides equivalence for two number values based
182// on the fact that the SDK uses int and float64 representations while
183// cty (and thus Terraform Core) uses big.Float, and so we expect to lose
184// precision in the round-trip.
185//
186// This does _not_ attempt to allow for an epsilon difference that may be
187// caused by accumulated innacuracy in a float calculation, under the
188// expectation that providers generally do not actually do compuations on
189// floats and instead just pass string representations of them on verbatim
190// to remote APIs. A remote API _itself_ may introduce inaccuracy, but that's
191// a problem for the provider itself to deal with, based on its knowledge of
192// the remote system, e.g. using DiffSuppressFunc.
193func valuesSDKEquivalentNumbers(a, b cty.Value) bool {
194 if a.RawEquals(b) {
195 return true // easy
196 }
197
198 af := a.AsBigFloat()
199 bf := b.AsBigFloat()
200
201 if af.IsInt() != bf.IsInt() {
202 return false
203 }
204 if af.IsInt() && bf.IsInt() {
205 return false // a.RawEquals(b) test above is good enough for integers
206 }
207
208 // The SDK supports only int and float64, so if it's not an integer
209 // we know that only a float64-level of precision can possibly be
210 // significant.
211 af64, _ := af.Float64()
212 bf64, _ := bf.Float64()
213 return af64 == bf64
214}