github.com/opentofu/opentofu@v1.7.1/internal/lang/funcs/render_template.go (about) 1 // Copyright (c) The OpenTofu Authors 2 // SPDX-License-Identifier: MPL-2.0 3 // Copyright (c) 2023 HashiCorp, Inc. 4 // SPDX-License-Identifier: MPL-2.0 5 6 package funcs 7 8 import ( 9 "fmt" 10 11 "github.com/hashicorp/hcl/v2" 12 "github.com/hashicorp/hcl/v2/hclsyntax" 13 "github.com/zclconf/go-cty/cty" 14 "github.com/zclconf/go-cty/cty/function" 15 ) 16 17 func renderTemplate(expr hcl.Expression, varsVal cty.Value, funcs map[string]function.Function) (cty.Value, error) { 18 if varsTy := varsVal.Type(); !(varsTy.IsMapType() || varsTy.IsObjectType()) { 19 return cty.DynamicVal, function.NewArgErrorf(1, "invalid vars value: must be a map") // or an object, but we don't strongly distinguish these most of the time 20 } 21 22 ctx := &hcl.EvalContext{ 23 Variables: varsVal.AsValueMap(), 24 Functions: funcs, 25 } 26 27 // We require all of the variables to be valid HCL identifiers, because 28 // otherwise there would be no way to refer to them in the template 29 // anyway. Rejecting this here gives better feedback to the user 30 // than a syntax error somewhere in the template itself. 31 for n := range ctx.Variables { 32 if !hclsyntax.ValidIdentifier(n) { 33 // This error message intentionally doesn't describe _all_ of 34 // the different permutations that are technically valid as an 35 // HCL identifier, but rather focuses on what we might 36 // consider to be an "idiomatic" variable name. 37 return cty.DynamicVal, function.NewArgErrorf(1, "invalid template variable name %q: must start with a letter, followed by zero or more letters, digits, and underscores", n) 38 } 39 } 40 41 // currFilename stores the filename of the template file, if any. 42 currFilename := expr.Range().Filename 43 44 // We'll pre-check references in the template here so we can give a 45 // more specialized error message than HCL would by default, so it's 46 // clearer that this problem is coming from a templatefile/templatestring call. 47 for _, traversal := range expr.Variables() { 48 root := traversal.RootName() 49 referencedPos := fmt.Sprintf("%q", root) 50 if currFilename != templateStringFilename { 51 referencedPos = fmt.Sprintf("%q, referenced at %s", root, traversal[0].SourceRange()) 52 } 53 if _, ok := ctx.Variables[root]; !ok { 54 return cty.DynamicVal, function.NewArgErrorf(1, "vars map does not contain key %s", referencedPos) 55 } 56 } 57 58 val, diags := expr.Value(ctx) 59 if diags.HasErrors() { 60 for _, diag := range diags { 61 // Roll up recursive errors 62 if extra, ok := diag.Extra.(hclsyntax.FunctionCallDiagExtra); ok { 63 if extra.CalledFunctionName() == "templatefile" { 64 err := extra.FunctionCallError() 65 if err, ok := err.(ErrorTemplateRecursionLimit); ok { 66 return cty.DynamicVal, ErrorTemplateRecursionLimit{sources: append(err.sources, diag.Subject.String())} 67 } 68 } 69 } 70 } 71 return cty.DynamicVal, diags 72 } 73 74 return val, nil 75 }