github.com/chanzuckerberg/terraform@v0.11.12-beta1/config/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 11 // UnknownVariableValue is a sentinel value that can be used 12 // to denote that the value of a variable is unknown at this time. 13 // RawConfig uses this information to build up data about 14 // unknown keys. 15 const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66" 16 17 // ConfigValueFromHCL2 converts a value from HCL2 (really, from the cty dynamic 18 // types library that HCL2 uses) to a value type that matches what would've 19 // been produced from the HCL-based interpolator for an equivalent structure. 20 // 21 // This function will transform a cty null value into a Go nil value, which 22 // isn't a possible outcome of the HCL/HIL-based decoder and so callers may 23 // need to detect and reject any null values. 24 func ConfigValueFromHCL2(v cty.Value) interface{} { 25 if !v.IsKnown() { 26 return UnknownVariableValue 27 } 28 if v.IsNull() { 29 return nil 30 } 31 32 switch v.Type() { 33 case cty.Bool: 34 return v.True() // like HCL.BOOL 35 case cty.String: 36 return v.AsString() // like HCL token.STRING or token.HEREDOC 37 case cty.Number: 38 // We can't match HCL _exactly_ here because it distinguishes between 39 // int and float values, but we'll get as close as we can by using 40 // an int if the number is exactly representable, and a float if not. 41 // The conversion to float will force precision to that of a float64, 42 // which is potentially losing information from the specific number 43 // given, but no worse than what HCL would've done in its own conversion 44 // to float. 45 46 f := v.AsBigFloat() 47 if i, acc := f.Int64(); acc == big.Exact { 48 // if we're on a 32-bit system and the number is too big for 32-bit 49 // int then we'll fall through here and use a float64. 50 const MaxInt = int(^uint(0) >> 1) 51 const MinInt = -MaxInt - 1 52 if i <= int64(MaxInt) && i >= int64(MinInt) { 53 return int(i) // Like HCL token.NUMBER 54 } 55 } 56 57 f64, _ := f.Float64() 58 return f64 // like HCL token.FLOAT 59 } 60 61 if v.Type().IsListType() || v.Type().IsSetType() || v.Type().IsTupleType() { 62 l := make([]interface{}, 0, v.LengthInt()) 63 it := v.ElementIterator() 64 for it.Next() { 65 _, ev := it.Element() 66 l = append(l, ConfigValueFromHCL2(ev)) 67 } 68 return l 69 } 70 71 if v.Type().IsMapType() || v.Type().IsObjectType() { 72 l := make(map[string]interface{}) 73 it := v.ElementIterator() 74 for it.Next() { 75 ek, ev := it.Element() 76 l[ek.AsString()] = ConfigValueFromHCL2(ev) 77 } 78 return l 79 } 80 81 // If we fall out here then we have some weird type that we haven't 82 // accounted for. This should never happen unless the caller is using 83 // capsule types, and we don't currently have any such types defined. 84 panic(fmt.Errorf("can't convert %#v to config value", v)) 85 } 86 87 // HCL2ValueFromConfigValue is the opposite of configValueFromHCL2: it takes 88 // a value as would be returned from the old interpolator and turns it into 89 // a cty.Value so it can be used within, for example, an HCL2 EvalContext. 90 func HCL2ValueFromConfigValue(v interface{}) cty.Value { 91 if v == nil { 92 return cty.NullVal(cty.DynamicPseudoType) 93 } 94 if v == UnknownVariableValue { 95 return cty.DynamicVal 96 } 97 98 switch tv := v.(type) { 99 case bool: 100 return cty.BoolVal(tv) 101 case string: 102 return cty.StringVal(tv) 103 case int: 104 return cty.NumberIntVal(int64(tv)) 105 case float64: 106 return cty.NumberFloatVal(tv) 107 case []interface{}: 108 vals := make([]cty.Value, len(tv)) 109 for i, ev := range tv { 110 vals[i] = HCL2ValueFromConfigValue(ev) 111 } 112 return cty.TupleVal(vals) 113 case map[string]interface{}: 114 vals := map[string]cty.Value{} 115 for k, ev := range tv { 116 vals[k] = HCL2ValueFromConfigValue(ev) 117 } 118 return cty.ObjectVal(vals) 119 default: 120 // HCL/HIL should never generate anything that isn't caught by 121 // the above, so if we get here something has gone very wrong. 122 panic(fmt.Errorf("can't convert %#v to cty.Value", v)) 123 } 124 } 125 126 func HILVariableFromHCL2Value(v cty.Value) ast.Variable { 127 if v.IsNull() { 128 // Caller should guarantee/check this before calling 129 panic("Null values cannot be represented in HIL") 130 } 131 if !v.IsKnown() { 132 return ast.Variable{ 133 Type: ast.TypeUnknown, 134 Value: UnknownVariableValue, 135 } 136 } 137 138 switch v.Type() { 139 case cty.Bool: 140 return ast.Variable{ 141 Type: ast.TypeBool, 142 Value: v.True(), 143 } 144 case cty.Number: 145 v := ConfigValueFromHCL2(v) 146 switch tv := v.(type) { 147 case int: 148 return ast.Variable{ 149 Type: ast.TypeInt, 150 Value: tv, 151 } 152 case float64: 153 return ast.Variable{ 154 Type: ast.TypeFloat, 155 Value: tv, 156 } 157 default: 158 // should never happen 159 panic("invalid return value for configValueFromHCL2") 160 } 161 case cty.String: 162 return ast.Variable{ 163 Type: ast.TypeString, 164 Value: v.AsString(), 165 } 166 } 167 168 if v.Type().IsListType() || v.Type().IsSetType() || v.Type().IsTupleType() { 169 l := make([]ast.Variable, 0, v.LengthInt()) 170 it := v.ElementIterator() 171 for it.Next() { 172 _, ev := it.Element() 173 l = append(l, HILVariableFromHCL2Value(ev)) 174 } 175 // If we were given a tuple then this could actually produce an invalid 176 // list with non-homogenous types, which we expect to be caught inside 177 // HIL just like a user-supplied non-homogenous list would be. 178 return ast.Variable{ 179 Type: ast.TypeList, 180 Value: l, 181 } 182 } 183 184 if v.Type().IsMapType() || v.Type().IsObjectType() { 185 l := make(map[string]ast.Variable) 186 it := v.ElementIterator() 187 for it.Next() { 188 ek, ev := it.Element() 189 l[ek.AsString()] = HILVariableFromHCL2Value(ev) 190 } 191 // If we were given an object then this could actually produce an invalid 192 // map with non-homogenous types, which we expect to be caught inside 193 // HIL just like a user-supplied non-homogenous map would be. 194 return ast.Variable{ 195 Type: ast.TypeMap, 196 Value: l, 197 } 198 } 199 200 // If we fall out here then we have some weird type that we haven't 201 // accounted for. This should never happen unless the caller is using 202 // capsule types, and we don't currently have any such types defined. 203 panic(fmt.Errorf("can't convert %#v to HIL variable", v)) 204 } 205 206 func HCL2ValueFromHILVariable(v ast.Variable) cty.Value { 207 switch v.Type { 208 case ast.TypeList: 209 vals := make([]cty.Value, len(v.Value.([]ast.Variable))) 210 for i, ev := range v.Value.([]ast.Variable) { 211 vals[i] = HCL2ValueFromHILVariable(ev) 212 } 213 return cty.TupleVal(vals) 214 case ast.TypeMap: 215 vals := make(map[string]cty.Value, len(v.Value.(map[string]ast.Variable))) 216 for k, ev := range v.Value.(map[string]ast.Variable) { 217 vals[k] = HCL2ValueFromHILVariable(ev) 218 } 219 return cty.ObjectVal(vals) 220 default: 221 return HCL2ValueFromConfigValue(v.Value) 222 } 223 } 224 225 func HCL2TypeForHILType(hilType ast.Type) cty.Type { 226 switch hilType { 227 case ast.TypeAny: 228 return cty.DynamicPseudoType 229 case ast.TypeUnknown: 230 return cty.DynamicPseudoType 231 case ast.TypeBool: 232 return cty.Bool 233 case ast.TypeInt: 234 return cty.Number 235 case ast.TypeFloat: 236 return cty.Number 237 case ast.TypeString: 238 return cty.String 239 case ast.TypeList: 240 return cty.List(cty.DynamicPseudoType) 241 case ast.TypeMap: 242 return cty.Map(cty.DynamicPseudoType) 243 default: 244 return cty.NilType // equilvalent to ast.TypeInvalid 245 } 246 }