package config import ( "fmt" "github.com/zclconf/go-cty/cty/function/stdlib" "github.com/hashicorp/hil/ast" "github.com/hashicorp/terraform/configs/hcl2shim" hcl2 "github.com/hashicorp/hcl/v2" "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) }