diff options
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.go | 214 |
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 @@ | |||
1 | package hcl2shim | ||
2 | |||
3 | import ( | ||
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. | ||
24 | func 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. | ||
74 | func 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. | ||
106 | func 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). | ||
145 | func 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). | ||
162 | func 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. | ||
193 | func 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 | } | ||