github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/configs/hcl2shim/values.go (about) 1 package hcl2shim 2 3 import ( 4 "fmt" 5 "math/big" 6 7 "github.com/hashicorp/hil/ast" 8 "github.com/zclconf/go-cty/cty" 9 10 "github.com/hashicorp/terraform/configs/configschema" 11 ) 12 13 // UnknownVariableValue is a sentinel value that can be used 14 // to denote that the value of a variable is unknown at this time. 15 // RawConfig uses this information to build up data about 16 // unknown keys. 17 const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66" 18 19 // ConfigValueFromHCL2Block is like ConfigValueFromHCL2 but it works only for 20 // known object values and uses the provided block schema to perform some 21 // additional normalization to better mimic the shape of value that the old 22 // HCL1/HIL-based codepaths would've produced. 23 // 24 // In particular, it discards the collections that we use to represent nested 25 // blocks (other than NestingSingle) if they are empty, which better mimics 26 // the HCL1 behavior because HCL1 had no knowledge of the schema and so didn't 27 // know that an unspecified block _could_ exist. 28 // 29 // The given object value must conform to the schema's implied type or this 30 // function will panic or produce incorrect results. 31 // 32 // This is primarily useful for the final transition from new-style values to 33 // terraform.ResourceConfig before calling to a legacy provider, since 34 // helper/schema (the old provider SDK) is particularly sensitive to these 35 // subtle differences within its validation code. 36 func ConfigValueFromHCL2Block(v cty.Value, schema *configschema.Block) map[string]interface{} { 37 if v.IsNull() { 38 return nil 39 } 40 if !v.IsKnown() { 41 panic("ConfigValueFromHCL2Block used with unknown value") 42 } 43 if !v.Type().IsObjectType() { 44 panic(fmt.Sprintf("ConfigValueFromHCL2Block used with non-object value %#v", v)) 45 } 46 47 atys := v.Type().AttributeTypes() 48 ret := make(map[string]interface{}) 49 50 for name := range schema.Attributes { 51 if _, exists := atys[name]; !exists { 52 continue 53 } 54 55 av := v.GetAttr(name) 56 if av.IsNull() { 57 // Skip nulls altogether, to better mimic how HCL1 would behave 58 continue 59 } 60 ret[name] = ConfigValueFromHCL2(av) 61 } 62 63 for name, blockS := range schema.BlockTypes { 64 if _, exists := atys[name]; !exists { 65 continue 66 } 67 bv := v.GetAttr(name) 68 if !bv.IsKnown() { 69 ret[name] = UnknownVariableValue 70 continue 71 } 72 if bv.IsNull() { 73 continue 74 } 75 76 switch blockS.Nesting { 77 78 case configschema.NestingSingle, configschema.NestingGroup: 79 ret[name] = ConfigValueFromHCL2Block(bv, &blockS.Block) 80 81 case configschema.NestingList, configschema.NestingSet: 82 l := bv.LengthInt() 83 if l == 0 { 84 // skip empty collections to better mimic how HCL1 would behave 85 continue 86 } 87 88 elems := make([]interface{}, 0, l) 89 for it := bv.ElementIterator(); it.Next(); { 90 _, ev := it.Element() 91 if !ev.IsKnown() { 92 elems = append(elems, UnknownVariableValue) 93 continue 94 } 95 elems = append(elems, ConfigValueFromHCL2Block(ev, &blockS.Block)) 96 } 97 ret[name] = elems 98 99 case configschema.NestingMap: 100 if bv.LengthInt() == 0 { 101 // skip empty collections to better mimic how HCL1 would behave 102 continue 103 } 104 105 elems := make(map[string]interface{}) 106 for it := bv.ElementIterator(); it.Next(); { 107 ek, ev := it.Element() 108 if !ev.IsKnown() { 109 elems[ek.AsString()] = UnknownVariableValue 110 continue 111 } 112 elems[ek.AsString()] = ConfigValueFromHCL2Block(ev, &blockS.Block) 113 } 114 ret[name] = elems 115 } 116 } 117 118 return ret 119 } 120 121 // ConfigValueFromHCL2 converts a value from HCL2 (really, from the cty dynamic 122 // types library that HCL2 uses) to a value type that matches what would've 123 // been produced from the HCL-based interpolator for an equivalent structure. 124 // 125 // This function will transform a cty null value into a Go nil value, which 126 // isn't a possible outcome of the HCL/HIL-based decoder and so callers may 127 // need to detect and reject any null values. 128 func ConfigValueFromHCL2(v cty.Value) interface{} { 129 if !v.IsKnown() { 130 return UnknownVariableValue 131 } 132 if v.IsNull() { 133 return nil 134 } 135 136 switch v.Type() { 137 case cty.Bool: 138 return v.True() // like HCL.BOOL 139 case cty.String: 140 return v.AsString() // like HCL token.STRING or token.HEREDOC 141 case cty.Number: 142 // We can't match HCL _exactly_ here because it distinguishes between 143 // int and float values, but we'll get as close as we can by using 144 // an int if the number is exactly representable, and a float if not. 145 // The conversion to float will force precision to that of a float64, 146 // which is potentially losing information from the specific number 147 // given, but no worse than what HCL would've done in its own conversion 148 // to float. 149 150 f := v.AsBigFloat() 151 if i, acc := f.Int64(); acc == big.Exact { 152 // if we're on a 32-bit system and the number is too big for 32-bit 153 // int then we'll fall through here and use a float64. 154 const MaxInt = int(^uint(0) >> 1) 155 const MinInt = -MaxInt - 1 156 if i <= int64(MaxInt) && i >= int64(MinInt) { 157 return int(i) // Like HCL token.NUMBER 158 } 159 } 160 161 f64, _ := f.Float64() 162 return f64 // like HCL token.FLOAT 163 } 164 165 if v.Type().IsListType() || v.Type().IsSetType() || v.Type().IsTupleType() { 166 l := make([]interface{}, 0, v.LengthInt()) 167 it := v.ElementIterator() 168 for it.Next() { 169 _, ev := it.Element() 170 l = append(l, ConfigValueFromHCL2(ev)) 171 } 172 return l 173 } 174 175 if v.Type().IsMapType() || v.Type().IsObjectType() { 176 l := make(map[string]interface{}) 177 it := v.ElementIterator() 178 for it.Next() { 179 ek, ev := it.Element() 180 cv := ConfigValueFromHCL2(ev) 181 if cv != nil { 182 l[ek.AsString()] = cv 183 } 184 } 185 return l 186 } 187 188 // If we fall out here then we have some weird type that we haven't 189 // accounted for. This should never happen unless the caller is using 190 // capsule types, and we don't currently have any such types defined. 191 panic(fmt.Errorf("can't convert %#v to config value", v)) 192 } 193 194 // HCL2ValueFromConfigValue is the opposite of configValueFromHCL2: it takes 195 // a value as would be returned from the old interpolator and turns it into 196 // a cty.Value so it can be used within, for example, an HCL2 EvalContext. 197 func HCL2ValueFromConfigValue(v interface{}) cty.Value { 198 if v == nil { 199 return cty.NullVal(cty.DynamicPseudoType) 200 } 201 if v == UnknownVariableValue { 202 return cty.DynamicVal 203 } 204 205 switch tv := v.(type) { 206 case bool: 207 return cty.BoolVal(tv) 208 case string: 209 return cty.StringVal(tv) 210 case int: 211 return cty.NumberIntVal(int64(tv)) 212 case float64: 213 return cty.NumberFloatVal(tv) 214 case []interface{}: 215 vals := make([]cty.Value, len(tv)) 216 for i, ev := range tv { 217 vals[i] = HCL2ValueFromConfigValue(ev) 218 } 219 return cty.TupleVal(vals) 220 case map[string]interface{}: 221 vals := map[string]cty.Value{} 222 for k, ev := range tv { 223 vals[k] = HCL2ValueFromConfigValue(ev) 224 } 225 return cty.ObjectVal(vals) 226 default: 227 // HCL/HIL should never generate anything that isn't caught by 228 // the above, so if we get here something has gone very wrong. 229 panic(fmt.Errorf("can't convert %#v to cty.Value", v)) 230 } 231 } 232 233 func HILVariableFromHCL2Value(v cty.Value) ast.Variable { 234 if v.IsNull() { 235 // Caller should guarantee/check this before calling 236 panic("Null values cannot be represented in HIL") 237 } 238 if !v.IsKnown() { 239 return ast.Variable{ 240 Type: ast.TypeUnknown, 241 Value: UnknownVariableValue, 242 } 243 } 244 245 switch v.Type() { 246 case cty.Bool: 247 return ast.Variable{ 248 Type: ast.TypeBool, 249 Value: v.True(), 250 } 251 case cty.Number: 252 v := ConfigValueFromHCL2(v) 253 switch tv := v.(type) { 254 case int: 255 return ast.Variable{ 256 Type: ast.TypeInt, 257 Value: tv, 258 } 259 case float64: 260 return ast.Variable{ 261 Type: ast.TypeFloat, 262 Value: tv, 263 } 264 default: 265 // should never happen 266 panic("invalid return value for configValueFromHCL2") 267 } 268 case cty.String: 269 return ast.Variable{ 270 Type: ast.TypeString, 271 Value: v.AsString(), 272 } 273 } 274 275 if v.Type().IsListType() || v.Type().IsSetType() || v.Type().IsTupleType() { 276 l := make([]ast.Variable, 0, v.LengthInt()) 277 it := v.ElementIterator() 278 for it.Next() { 279 _, ev := it.Element() 280 l = append(l, HILVariableFromHCL2Value(ev)) 281 } 282 // If we were given a tuple then this could actually produce an invalid 283 // list with non-homogenous types, which we expect to be caught inside 284 // HIL just like a user-supplied non-homogenous list would be. 285 return ast.Variable{ 286 Type: ast.TypeList, 287 Value: l, 288 } 289 } 290 291 if v.Type().IsMapType() || v.Type().IsObjectType() { 292 l := make(map[string]ast.Variable) 293 it := v.ElementIterator() 294 for it.Next() { 295 ek, ev := it.Element() 296 l[ek.AsString()] = HILVariableFromHCL2Value(ev) 297 } 298 // If we were given an object then this could actually produce an invalid 299 // map with non-homogenous types, which we expect to be caught inside 300 // HIL just like a user-supplied non-homogenous map would be. 301 return ast.Variable{ 302 Type: ast.TypeMap, 303 Value: l, 304 } 305 } 306 307 // If we fall out here then we have some weird type that we haven't 308 // accounted for. This should never happen unless the caller is using 309 // capsule types, and we don't currently have any such types defined. 310 panic(fmt.Errorf("can't convert %#v to HIL variable", v)) 311 } 312 313 func HCL2ValueFromHILVariable(v ast.Variable) cty.Value { 314 switch v.Type { 315 case ast.TypeList: 316 vals := make([]cty.Value, len(v.Value.([]ast.Variable))) 317 for i, ev := range v.Value.([]ast.Variable) { 318 vals[i] = HCL2ValueFromHILVariable(ev) 319 } 320 return cty.TupleVal(vals) 321 case ast.TypeMap: 322 vals := make(map[string]cty.Value, len(v.Value.(map[string]ast.Variable))) 323 for k, ev := range v.Value.(map[string]ast.Variable) { 324 vals[k] = HCL2ValueFromHILVariable(ev) 325 } 326 return cty.ObjectVal(vals) 327 default: 328 return HCL2ValueFromConfigValue(v.Value) 329 } 330 } 331 332 func HCL2TypeForHILType(hilType ast.Type) cty.Type { 333 switch hilType { 334 case ast.TypeAny: 335 return cty.DynamicPseudoType 336 case ast.TypeUnknown: 337 return cty.DynamicPseudoType 338 case ast.TypeBool: 339 return cty.Bool 340 case ast.TypeInt: 341 return cty.Number 342 case ast.TypeFloat: 343 return cty.Number 344 case ast.TypeString: 345 return cty.String 346 case ast.TypeList: 347 return cty.List(cty.DynamicPseudoType) 348 case ast.TypeMap: 349 return cty.Map(cty.DynamicPseudoType) 350 default: 351 return cty.NilType // equilvalent to ast.TypeInvalid 352 } 353 }