github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/configs/hcl2shim/values_equiv.go (about) 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 }