opentofu/config/hcl2_shim_util.go
Martin Atkins 71e989ba3e config/hcl2shim: make some of the HCL2 shim functions public
The value-conversion machinery is also needed in the main "terraform"
package to help us populate our HCL2 evaluation scope, so a subset of the
shim functions move here into a new package where they can be public.

Some of them remain private within the config package since they depend
on some other symbols in the config package, and they are not needed
by outside callers anyway.
2017-10-16 17:54:02 -07:00

135 lines
4.5 KiB
Go

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)
}