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  }