github.com/hashicorp/packer@v1.14.3/hcl2template/shim/values.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: BUSL-1.1 3 4 package hcl2shim 5 6 import ( 7 "fmt" 8 "math/big" 9 10 "github.com/hashicorp/packer-plugin-sdk/hcl2helper" 11 "github.com/zclconf/go-cty/cty" 12 ) 13 14 // ConfigValueFromHCL2 converts a value from HCL2 (really, from the cty dynamic 15 // types library that HCL2 uses) to a value type that matches what would've 16 // been produced from the HCL-based interpolator for an equivalent structure. 17 // 18 // This function will transform a cty null value into a Go nil value, which 19 // isn't a possible outcome of the HCL/HIL-based decoder and so callers may 20 // need to detect and reject any null values. 21 func ConfigValueFromHCL2(v cty.Value) interface{} { 22 if !v.IsKnown() { 23 return hcl2helper.UnknownVariableValue 24 } 25 if v.IsNull() { 26 return nil 27 } 28 29 switch v.Type() { 30 case cty.Bool: 31 return v.True() // like HCL.BOOL 32 case cty.String: 33 return v.AsString() // like HCL token.STRING or token.HEREDOC 34 case cty.Number: 35 // We can't match HCL _exactly_ here because it distinguishes between 36 // int and float values, but we'll get as close as we can by using 37 // an int if the number is exactly representable, and a float if not. 38 // The conversion to float will force precision to that of a float64, 39 // which is potentially losing information from the specific number 40 // given, but no worse than what HCL would've done in its own conversion 41 // to float. 42 43 f := v.AsBigFloat() 44 if i, acc := f.Int64(); acc == big.Exact { 45 // if we're on a 32-bit system and the number is too big for 32-bit 46 // int then we'll fall through here and use a float64. 47 const MaxInt = int(^uint(0) >> 1) 48 const MinInt = -MaxInt - 1 49 if i <= int64(MaxInt) && i >= int64(MinInt) { 50 return int(i) // Like HCL token.NUMBER 51 } 52 } 53 54 f64, _ := f.Float64() 55 return f64 // like HCL token.FLOAT 56 } 57 58 if v.Type().IsListType() || v.Type().IsSetType() || v.Type().IsTupleType() { 59 l := make([]interface{}, 0, v.LengthInt()) 60 it := v.ElementIterator() 61 for it.Next() { 62 _, ev := it.Element() 63 l = append(l, ConfigValueFromHCL2(ev)) 64 } 65 return l 66 } 67 68 if v.Type().IsMapType() || v.Type().IsObjectType() { 69 l := make(map[string]interface{}) 70 it := v.ElementIterator() 71 for it.Next() { 72 ek, ev := it.Element() 73 cv := ConfigValueFromHCL2(ev) 74 if cv != nil { 75 l[ek.AsString()] = cv 76 } 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 // WriteUnknownPlaceholderValues will replace every Unknown value with a equivalent placeholder. 88 // This is useful to use before marshaling the value to JSON. The default values are: 89 // - string: "<unknown>" 90 // - number: 0 91 // - bool: false 92 // - objects/lists/tuples/sets/maps: empty 93 func WriteUnknownPlaceholderValues(v cty.Value) cty.Value { 94 if v.IsNull() { 95 return v 96 } 97 t := v.Type() 98 switch { 99 case t.IsPrimitiveType(): 100 if v.IsKnown() { 101 return v 102 } 103 switch t { 104 case cty.String: 105 return cty.StringVal("<unknown>") 106 case cty.Number: 107 return cty.MustParseNumberVal("0") 108 case cty.Bool: 109 return cty.BoolVal(false) 110 default: 111 panic("unsupported primitive type") 112 } 113 case t.IsListType(): 114 if !v.IsKnown() { 115 return cty.ListValEmpty(t.ElementType()) 116 } 117 arr := []cty.Value{} 118 it := v.ElementIterator() 119 for it.Next() { 120 _, ev := it.Element() 121 arr = append(arr, WriteUnknownPlaceholderValues(ev)) 122 } 123 if len(arr) == 0 { 124 return cty.ListValEmpty(t.ElementType()) 125 } 126 return cty.ListVal(arr) 127 case t.IsSetType(): 128 if !v.IsKnown() { 129 return cty.SetValEmpty(t.ElementType()) 130 } 131 arr := []cty.Value{} 132 it := v.ElementIterator() 133 for it.Next() { 134 _, ev := it.Element() 135 arr = append(arr, WriteUnknownPlaceholderValues(ev)) 136 } 137 if len(arr) == 0 { 138 return cty.SetValEmpty(t.ElementType()) 139 } 140 return cty.SetVal(arr) 141 case t.IsMapType(): 142 if !v.IsKnown() { 143 return cty.MapValEmpty(t.ElementType()) 144 } 145 obj := map[string]cty.Value{} 146 it := v.ElementIterator() 147 for it.Next() { 148 ek, ev := it.Element() 149 obj[ek.AsString()] = WriteUnknownPlaceholderValues(ev) 150 } 151 if len(obj) == 0 { 152 return cty.MapValEmpty(t.ElementType()) 153 } 154 return cty.MapVal(obj) 155 case t.IsTupleType(): 156 if !v.IsKnown() { 157 return cty.EmptyTupleVal 158 } 159 arr := []cty.Value{} 160 it := v.ElementIterator() 161 for it.Next() { 162 _, ev := it.Element() 163 arr = append(arr, WriteUnknownPlaceholderValues(ev)) 164 } 165 if len(arr) == 0 { 166 return cty.EmptyTupleVal 167 } 168 return cty.TupleVal(arr) 169 case t.IsObjectType(): 170 if !v.IsKnown() { 171 return cty.EmptyObjectVal 172 } 173 obj := map[string]cty.Value{} 174 it := v.ElementIterator() 175 for it.Next() { 176 ek, ev := it.Element() 177 obj[ek.AsString()] = WriteUnknownPlaceholderValues(ev) 178 } 179 if len(obj) == 0 { 180 return cty.EmptyObjectVal 181 } 182 return cty.ObjectVal(obj) 183 default: 184 // should never happen 185 panic("unknown type") 186 } 187 }