github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/configs/hcl2shim/values.go (about) 1 package hcl2shim 2 3 import ( 4 "fmt" 5 "math/big" 6 7 "github.com/zclconf/go-cty/cty" 8 9 "github.com/muratcelep/terraform/not-internal/configs/configschema" 10 ) 11 12 // UnknownVariableValue is a sentinel value that can be used 13 // to denote that the value of a variable is unknown at this time. 14 // RawConfig uses this information to build up data about 15 // unknown keys. 16 const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66" 17 18 // ConfigValueFromHCL2Block is like ConfigValueFromHCL2 but it works only for 19 // known object values and uses the provided block schema to perform some 20 // additional normalization to better mimic the shape of value that the old 21 // HCL1/HIL-based codepaths would've produced. 22 // 23 // In particular, it discards the collections that we use to represent nested 24 // blocks (other than NestingSingle) if they are empty, which better mimics 25 // the HCL1 behavior because HCL1 had no knowledge of the schema and so didn't 26 // know that an unspecified block _could_ exist. 27 // 28 // The given object value must conform to the schema's implied type or this 29 // function will panic or produce incorrect results. 30 // 31 // This is primarily useful for the final transition from new-style values to 32 // terraform.ResourceConfig before calling to a legacy provider, since 33 // helper/schema (the old provider SDK) is particularly sensitive to these 34 // subtle differences within its validation code. 35 func ConfigValueFromHCL2Block(v cty.Value, schema *configschema.Block) map[string]interface{} { 36 if v.IsNull() { 37 return nil 38 } 39 if !v.IsKnown() { 40 panic("ConfigValueFromHCL2Block used with unknown value") 41 } 42 if !v.Type().IsObjectType() { 43 panic(fmt.Sprintf("ConfigValueFromHCL2Block used with non-object value %#v", v)) 44 } 45 46 atys := v.Type().AttributeTypes() 47 ret := make(map[string]interface{}) 48 49 for name := range schema.Attributes { 50 if _, exists := atys[name]; !exists { 51 continue 52 } 53 54 av := v.GetAttr(name) 55 if av.IsNull() { 56 // Skip nulls altogether, to better mimic how HCL1 would behave 57 continue 58 } 59 ret[name] = ConfigValueFromHCL2(av) 60 } 61 62 for name, blockS := range schema.BlockTypes { 63 if _, exists := atys[name]; !exists { 64 continue 65 } 66 bv := v.GetAttr(name) 67 if !bv.IsKnown() { 68 ret[name] = UnknownVariableValue 69 continue 70 } 71 if bv.IsNull() { 72 continue 73 } 74 75 switch blockS.Nesting { 76 77 case configschema.NestingSingle, configschema.NestingGroup: 78 ret[name] = ConfigValueFromHCL2Block(bv, &blockS.Block) 79 80 case configschema.NestingList, configschema.NestingSet: 81 l := bv.LengthInt() 82 if l == 0 { 83 // skip empty collections to better mimic how HCL1 would behave 84 continue 85 } 86 87 elems := make([]interface{}, 0, l) 88 for it := bv.ElementIterator(); it.Next(); { 89 _, ev := it.Element() 90 if !ev.IsKnown() { 91 elems = append(elems, UnknownVariableValue) 92 continue 93 } 94 elems = append(elems, ConfigValueFromHCL2Block(ev, &blockS.Block)) 95 } 96 ret[name] = elems 97 98 case configschema.NestingMap: 99 if bv.LengthInt() == 0 { 100 // skip empty collections to better mimic how HCL1 would behave 101 continue 102 } 103 104 elems := make(map[string]interface{}) 105 for it := bv.ElementIterator(); it.Next(); { 106 ek, ev := it.Element() 107 if !ev.IsKnown() { 108 elems[ek.AsString()] = UnknownVariableValue 109 continue 110 } 111 elems[ek.AsString()] = ConfigValueFromHCL2Block(ev, &blockS.Block) 112 } 113 ret[name] = elems 114 } 115 } 116 117 return ret 118 } 119 120 // ConfigValueFromHCL2 converts a value from HCL2 (really, from the cty dynamic 121 // types library that HCL2 uses) to a value type that matches what would've 122 // been produced from the HCL-based interpolator for an equivalent structure. 123 // 124 // This function will transform a cty null value into a Go nil value, which 125 // isn't a possible outcome of the HCL/HIL-based decoder and so callers may 126 // need to detect and reject any null values. 127 func ConfigValueFromHCL2(v cty.Value) interface{} { 128 if !v.IsKnown() { 129 return UnknownVariableValue 130 } 131 if v.IsNull() { 132 return nil 133 } 134 135 switch v.Type() { 136 case cty.Bool: 137 return v.True() // like HCL.BOOL 138 case cty.String: 139 return v.AsString() // like HCL token.STRING or token.HEREDOC 140 case cty.Number: 141 // We can't match HCL _exactly_ here because it distinguishes between 142 // int and float values, but we'll get as close as we can by using 143 // an int if the number is exactly representable, and a float if not. 144 // The conversion to float will force precision to that of a float64, 145 // which is potentially losing information from the specific number 146 // given, but no worse than what HCL would've done in its own conversion 147 // to float. 148 149 f := v.AsBigFloat() 150 if i, acc := f.Int64(); acc == big.Exact { 151 // if we're on a 32-bit system and the number is too big for 32-bit 152 // int then we'll fall through here and use a float64. 153 const MaxInt = int(^uint(0) >> 1) 154 const MinInt = -MaxInt - 1 155 if i <= int64(MaxInt) && i >= int64(MinInt) { 156 return int(i) // Like HCL token.NUMBER 157 } 158 } 159 160 f64, _ := f.Float64() 161 return f64 // like HCL token.FLOAT 162 } 163 164 if v.Type().IsListType() || v.Type().IsSetType() || v.Type().IsTupleType() { 165 l := make([]interface{}, 0, v.LengthInt()) 166 it := v.ElementIterator() 167 for it.Next() { 168 _, ev := it.Element() 169 l = append(l, ConfigValueFromHCL2(ev)) 170 } 171 return l 172 } 173 174 if v.Type().IsMapType() || v.Type().IsObjectType() { 175 l := make(map[string]interface{}) 176 it := v.ElementIterator() 177 for it.Next() { 178 ek, ev := it.Element() 179 cv := ConfigValueFromHCL2(ev) 180 if cv != nil { 181 l[ek.AsString()] = cv 182 } 183 } 184 return l 185 } 186 187 // If we fall out here then we have some weird type that we haven't 188 // accounted for. This should never happen unless the caller is using 189 // capsule types, and we don't currently have any such types defined. 190 panic(fmt.Errorf("can't convert %#v to config value", v)) 191 } 192 193 // HCL2ValueFromConfigValue is the opposite of configValueFromHCL2: it takes 194 // a value as would be returned from the old interpolator and turns it into 195 // a cty.Value so it can be used within, for example, an HCL2 EvalContext. 196 func HCL2ValueFromConfigValue(v interface{}) cty.Value { 197 if v == nil { 198 return cty.NullVal(cty.DynamicPseudoType) 199 } 200 if v == UnknownVariableValue { 201 return cty.DynamicVal 202 } 203 204 switch tv := v.(type) { 205 case bool: 206 return cty.BoolVal(tv) 207 case string: 208 return cty.StringVal(tv) 209 case int: 210 return cty.NumberIntVal(int64(tv)) 211 case float64: 212 return cty.NumberFloatVal(tv) 213 case []interface{}: 214 vals := make([]cty.Value, len(tv)) 215 for i, ev := range tv { 216 vals[i] = HCL2ValueFromConfigValue(ev) 217 } 218 return cty.TupleVal(vals) 219 case map[string]interface{}: 220 vals := map[string]cty.Value{} 221 for k, ev := range tv { 222 vals[k] = HCL2ValueFromConfigValue(ev) 223 } 224 return cty.ObjectVal(vals) 225 default: 226 // HCL/HIL should never generate anything that isn't caught by 227 // the above, so if we get here something has gone very wrong. 228 panic(fmt.Errorf("can't convert %#v to cty.Value", v)) 229 } 230 }