github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/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/hashicorp/terraform/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  }