github.com/hashicorp/packer@v1.14.3/hcl2template/function/templatefile.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package function
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/hashicorp/go-cty-funcs/filesystem"
    10  	"github.com/hashicorp/hcl/v2"
    11  	"github.com/hashicorp/hcl/v2/hclsyntax"
    12  	"github.com/zclconf/go-cty/cty"
    13  	"github.com/zclconf/go-cty/cty/function"
    14  )
    15  
    16  // MakeTemplateFileFunc constructs a function that takes a file path and
    17  // an arbitrary object of named values and attempts to render the referenced
    18  // file as a template using HCL template syntax.
    19  //
    20  // The template itself may recursively call other functions so a callback
    21  // must be provided to get access to those functions. The template cannot,
    22  // however, access any variables defined in the scope: it is restricted only to
    23  // those variables provided in the second function argument.
    24  //
    25  // As a special exception, a referenced template file may not recursively call
    26  // the templatefile function, since that would risk the same file being
    27  // included into itself indefinitely.
    28  func MakeTemplateFileFunc(baseDir string, funcsCb func() map[string]function.Function) function.Function {
    29  
    30  	params := []function.Parameter{
    31  		{
    32  			Name: "path",
    33  			Type: cty.String,
    34  		},
    35  		{
    36  			Name: "vars",
    37  			Type: cty.DynamicPseudoType,
    38  		},
    39  	}
    40  
    41  	loadTmpl := func(fn string) (hcl.Expression, error) {
    42  		// We re-use File here to ensure the same filename interpretation
    43  		// as it does, along with its other safety checks.
    44  		tmplVal, err := filesystem.File(baseDir, cty.StringVal(fn))
    45  		if err != nil {
    46  			return nil, err
    47  		}
    48  
    49  		expr, diags := hclsyntax.ParseTemplate([]byte(tmplVal.AsString()), fn, hcl.Pos{Line: 1, Column: 1})
    50  		if diags.HasErrors() {
    51  			return nil, diags
    52  		}
    53  
    54  		return expr, nil
    55  	}
    56  
    57  	renderTmpl := func(expr hcl.Expression, varsVal cty.Value) (cty.Value, error) {
    58  		if varsTy := varsVal.Type(); !(varsTy.IsMapType() || varsTy.IsObjectType()) {
    59  			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
    60  		}
    61  
    62  		ctx := &hcl.EvalContext{
    63  			Variables: varsVal.AsValueMap(),
    64  		}
    65  
    66  		// We require all of the variables to be valid HCL identifiers, because
    67  		// otherwise there would be no way to refer to them in the template
    68  		// anyway. Rejecting this here gives better feedback to the user
    69  		// than a syntax error somewhere in the template itself.
    70  		for n := range ctx.Variables {
    71  			if !hclsyntax.ValidIdentifier(n) {
    72  				// This error message intentionally doesn't describe _all_ of
    73  				// the different permutations that are technically valid as an
    74  				// HCL identifier, but rather focuses on what we might
    75  				// consider to be an "idiomatic" variable name.
    76  				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)
    77  			}
    78  		}
    79  
    80  		// We'll pre-check references in the template here so we can give a
    81  		// more specialized error message than HCL would by default, so it's
    82  		// clearer that this problem is coming from a templatefile call.
    83  		for _, traversal := range expr.Variables() {
    84  			root := traversal.RootName()
    85  			if _, ok := ctx.Variables[root]; !ok {
    86  				return cty.DynamicVal, function.NewArgErrorf(1, "vars map does not contain key %q, referenced at %s", root, traversal[0].SourceRange())
    87  			}
    88  		}
    89  
    90  		givenFuncs := funcsCb() // this callback indirection is to avoid chicken/egg problems
    91  		funcs := make(map[string]function.Function, len(givenFuncs))
    92  		for name, fn := range givenFuncs {
    93  			if name == "templatefile" {
    94  				// We stub this one out to prevent recursive calls.
    95  				funcs[name] = function.New(&function.Spec{
    96  					Params: params,
    97  					Type: func(args []cty.Value) (cty.Type, error) {
    98  						return cty.NilType, fmt.Errorf("cannot recursively call templatefile from inside templatefile call")
    99  					},
   100  				})
   101  				continue
   102  			}
   103  			funcs[name] = fn
   104  		}
   105  		ctx.Functions = funcs
   106  
   107  		val, diags := expr.Value(ctx)
   108  		if diags.HasErrors() {
   109  			return cty.DynamicVal, diags
   110  		}
   111  		return val, nil
   112  	}
   113  
   114  	return function.New(&function.Spec{
   115  		Params: params,
   116  		Type: func(args []cty.Value) (cty.Type, error) {
   117  			if !(args[0].IsKnown() && args[1].IsKnown()) {
   118  				return cty.DynamicPseudoType, nil
   119  			}
   120  
   121  			// We'll render our template now to see what result type it
   122  			// produces. A template consisting only of a single interpolation
   123  			// can potentially return any type.
   124  			expr, err := loadTmpl(args[0].AsString())
   125  			if err != nil {
   126  				return cty.DynamicPseudoType, err
   127  			}
   128  
   129  			// This is safe even if args[1] contains unknowns because the HCL
   130  			// template renderer itself knows how to short-circuit those.
   131  			val, err := renderTmpl(expr, args[1])
   132  			return val.Type(), err
   133  		},
   134  		Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
   135  			expr, err := loadTmpl(args[0].AsString())
   136  			if err != nil {
   137  				return cty.DynamicVal, err
   138  			}
   139  			return renderTmpl(expr, args[1])
   140  		},
   141  	})
   142  
   143  }