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  }