aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/config/hcl2_shim_util.go
blob: 207d105986b94f6d7295457be9673871961706ca (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package config

import (
	"fmt"

	"github.com/zclconf/go-cty/cty/function/stdlib"

	"github.com/hashicorp/hil/ast"
	"github.com/hashicorp/terraform/config/hcl2shim"

	hcl2 "github.com/hashicorp/hcl2/hcl"
	"github.com/zclconf/go-cty/cty"
	"github.com/zclconf/go-cty/cty/convert"
	"github.com/zclconf/go-cty/cty/function"
)

// ---------------------------------------------------------------------------
// This file contains some helper functions that are used to shim between
// HCL2 concepts and HCL/HIL concepts, to help us mostly preserve the existing
// public API that was built around HCL/HIL-oriented approaches.
// ---------------------------------------------------------------------------

func hcl2InterpolationFuncs() map[string]function.Function {
	hcl2Funcs := map[string]function.Function{}

	for name, hilFunc := range Funcs() {
		hcl2Funcs[name] = hcl2InterpolationFuncShim(hilFunc)
	}

	// Some functions in the old world are dealt with inside langEvalConfig
	// due to their legacy reliance on direct access to the symbol table.
	// Since 0.7 they don't actually need it anymore and just ignore it,
	// so we're cheating a bit here and exploiting that detail by passing nil.
	hcl2Funcs["lookup"] = hcl2InterpolationFuncShim(interpolationFuncLookup(nil))
	hcl2Funcs["keys"] = hcl2InterpolationFuncShim(interpolationFuncKeys(nil))
	hcl2Funcs["values"] = hcl2InterpolationFuncShim(interpolationFuncValues(nil))

	// As a bonus, we'll provide the JSON-handling functions from the cty
	// function library since its "jsonencode" is more complete (doesn't force
	// weird type conversions) and HIL's type system can't represent
	// "jsondecode" at all. The result of jsondecode will eventually be forced
	// to conform to the HIL type system on exit into the rest of Terraform due
	// to our shimming right now, but it should be usable for decoding _within_
	// an expression.
	hcl2Funcs["jsonencode"] = stdlib.JSONEncodeFunc
	hcl2Funcs["jsondecode"] = stdlib.JSONDecodeFunc

	return hcl2Funcs
}

func hcl2InterpolationFuncShim(hilFunc ast.Function) function.Function {
	spec := &function.Spec{}

	for i, hilArgType := range hilFunc.ArgTypes {
		spec.Params = append(spec.Params, function.Parameter{
			Type: hcl2shim.HCL2TypeForHILType(hilArgType),
			Name: fmt.Sprintf("arg%d", i+1), // HIL args don't have names, so we'll fudge it
		})
	}

	if hilFunc.Variadic {
		spec.VarParam = &function.Parameter{
			Type: hcl2shim.HCL2TypeForHILType(hilFunc.VariadicType),
			Name: "varargs", // HIL args don't have names, so we'll fudge it
		}
	}

	spec.Type = func(args []cty.Value) (cty.Type, error) {
		return hcl2shim.HCL2TypeForHILType(hilFunc.ReturnType), nil
	}
	spec.Impl = func(args []cty.Value, retType cty.Type) (cty.Value, error) {
		hilArgs := make([]interface{}, len(args))
		for i, arg := range args {
			hilV := hcl2shim.HILVariableFromHCL2Value(arg)

			// Although the cty function system does automatic type conversions
			// to match the argument types, cty doesn't distinguish int and
			// float and so we may need to adjust here to ensure that the
			// wrapped function gets exactly the Go type it was expecting.
			var wantType ast.Type
			if i < len(hilFunc.ArgTypes) {
				wantType = hilFunc.ArgTypes[i]
			} else {
				wantType = hilFunc.VariadicType
			}
			switch {
			case hilV.Type == ast.TypeInt && wantType == ast.TypeFloat:
				hilV.Type = wantType
				hilV.Value = float64(hilV.Value.(int))
			case hilV.Type == ast.TypeFloat && wantType == ast.TypeInt:
				hilV.Type = wantType
				hilV.Value = int(hilV.Value.(float64))
			}

			// HIL functions actually expect to have the outermost variable
			// "peeled" but any nested values (in lists or maps) will
			// still have their ast.Variable wrapping.
			hilArgs[i] = hilV.Value
		}

		hilResult, err := hilFunc.Callback(hilArgs)
		if err != nil {
			return cty.DynamicVal, err
		}

		// Just as on the way in, we get back a partially-peeled ast.Variable
		// which we need to re-wrap in order to convert it back into what
		// we're calling a "config value".
		rv := hcl2shim.HCL2ValueFromHILVariable(ast.Variable{
			Type:  hilFunc.ReturnType,
			Value: hilResult,
		})

		return convert.Convert(rv, retType) // if result is unknown we'll force the correct type here
	}
	return function.New(spec)
}

func hcl2EvalWithUnknownVars(expr hcl2.Expression) (cty.Value, hcl2.Diagnostics) {
	trs := expr.Variables()
	vars := map[string]cty.Value{}
	val := cty.DynamicVal

	for _, tr := range trs {
		name := tr.RootName()
		vars[name] = val
	}

	ctx := &hcl2.EvalContext{
		Variables: vars,
		Functions: hcl2InterpolationFuncs(),
	}
	return expr.Value(ctx)
}