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

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package hcl2template
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/base64"
     9  
    10  	"github.com/hashicorp/go-cty-funcs/cidr"
    11  	"github.com/hashicorp/go-cty-funcs/collection"
    12  	"github.com/hashicorp/go-cty-funcs/crypto"
    13  	"github.com/hashicorp/go-cty-funcs/encoding"
    14  	"github.com/hashicorp/go-cty-funcs/filesystem"
    15  	"github.com/hashicorp/go-cty-funcs/uuid"
    16  	"github.com/hashicorp/hcl/v2/ext/tryfunc"
    17  	"github.com/hashicorp/hcl/v2/ext/typeexpr"
    18  	pkrfunction "github.com/hashicorp/packer/hcl2template/function"
    19  	ctyyaml "github.com/zclconf/go-cty-yaml"
    20  	"github.com/zclconf/go-cty/cty"
    21  	"github.com/zclconf/go-cty/cty/function"
    22  	"github.com/zclconf/go-cty/cty/function/stdlib"
    23  	"golang.org/x/text/encoding/ianaindex"
    24  )
    25  
    26  // Functions returns the set of functions that should be used to when
    27  // evaluating expressions in the receiving scope.
    28  //
    29  // basedir is used with file functions and allows a user to reference a file
    30  // using local path. Usually basedir is the directory in which the config file
    31  // is located
    32  func Functions(basedir string) map[string]function.Function {
    33  
    34  	funcs := map[string]function.Function{
    35  		"abs":                    stdlib.AbsoluteFunc,
    36  		"abspath":                filesystem.AbsPathFunc,
    37  		"alltrue":                pkrfunction.AllTrue,
    38  		"anytrue":                pkrfunction.AnyTrue,
    39  		"aws_secretsmanager":     pkrfunction.AWSSecret,
    40  		"aws_secretsmanager_raw": pkrfunction.AWSSecretRaw,
    41  		"basename":               filesystem.BasenameFunc,
    42  		"base64decode":           encoding.Base64DecodeFunc,
    43  		"base64encode":           encoding.Base64EncodeFunc,
    44  		"base64gzip":             pkrfunction.Base64GzipFunc,
    45  		"bcrypt":                 crypto.BcryptFunc,
    46  		"can":                    tryfunc.CanFunc,
    47  		"ceil":                   stdlib.CeilFunc,
    48  		"chomp":                  stdlib.ChompFunc,
    49  		"chunklist":              stdlib.ChunklistFunc,
    50  		"cidrhost":               cidr.HostFunc,
    51  		"cidrnetmask":            cidr.NetmaskFunc,
    52  		"cidrsubnet":             cidr.SubnetFunc,
    53  		"cidrsubnets":            cidr.SubnetsFunc,
    54  		"coalesce":               collection.CoalesceFunc,
    55  		"coalescelist":           stdlib.CoalesceListFunc,
    56  		"compact":                stdlib.CompactFunc,
    57  		"concat":                 stdlib.ConcatFunc,
    58  		"consul_key":             pkrfunction.ConsulFunc,
    59  		"contains":               stdlib.ContainsFunc,
    60  		"convert":                typeexpr.ConvertFunc,
    61  		"csvdecode":              stdlib.CSVDecodeFunc,
    62  		"dirname":                filesystem.DirnameFunc,
    63  		"distinct":               stdlib.DistinctFunc,
    64  		"element":                stdlib.ElementFunc,
    65  		"endswith":               pkrfunction.EndsWithFunc,
    66  		"file":                   filesystem.MakeFileFunc(basedir, false),
    67  		"filebase64":             pkrfunction.Filebase64,
    68  		"fileexists":             filesystem.MakeFileExistsFunc(basedir),
    69  		"fileset":                filesystem.MakeFileSetFunc(basedir),
    70  		"flatten":                stdlib.FlattenFunc,
    71  		"floor":                  stdlib.FloorFunc,
    72  		"format":                 stdlib.FormatFunc,
    73  		"formatdate":             stdlib.FormatDateFunc,
    74  		"formatlist":             stdlib.FormatListFunc,
    75  		"indent":                 stdlib.IndentFunc,
    76  		"index":                  pkrfunction.IndexFunc, // stdlib.IndexFunc is not compatible
    77  		"join":                   stdlib.JoinFunc,
    78  		"jsondecode":             stdlib.JSONDecodeFunc,
    79  		"jsonencode":             stdlib.JSONEncodeFunc,
    80  		"keys":                   stdlib.KeysFunc,
    81  		"legacy_isotime":         pkrfunction.LegacyIsotimeFunc,
    82  		"legacy_strftime":        pkrfunction.LegacyStrftimeFunc,
    83  		"length":                 pkrfunction.LengthFunc,
    84  		"log":                    stdlib.LogFunc,
    85  		"lookup":                 stdlib.LookupFunc,
    86  		"lower":                  stdlib.LowerFunc,
    87  		"max":                    stdlib.MaxFunc,
    88  		"md5":                    crypto.Md5Func,
    89  		"merge":                  stdlib.MergeFunc,
    90  		"min":                    stdlib.MinFunc,
    91  		"parseint":               stdlib.ParseIntFunc,
    92  		"pathexpand":             filesystem.PathExpandFunc,
    93  		"pow":                    stdlib.PowFunc,
    94  		"range":                  stdlib.RangeFunc,
    95  		"reverse":                stdlib.ReverseListFunc,
    96  		"replace":                stdlib.ReplaceFunc,
    97  		"regex":                  stdlib.RegexFunc,
    98  		"regexall":               stdlib.RegexAllFunc,
    99  		"regex_replace":          stdlib.RegexReplaceFunc,
   100  		"rsadecrypt":             crypto.RsaDecryptFunc,
   101  		"setintersection":        stdlib.SetIntersectionFunc,
   102  		"setproduct":             stdlib.SetProductFunc,
   103  		"setunion":               stdlib.SetUnionFunc,
   104  		"sha1":                   crypto.Sha1Func,
   105  		"sha256":                 crypto.Sha256Func,
   106  		"sha512":                 crypto.Sha512Func,
   107  		"signum":                 stdlib.SignumFunc,
   108  		"slice":                  stdlib.SliceFunc,
   109  		"sort":                   stdlib.SortFunc,
   110  		"split":                  stdlib.SplitFunc,
   111  		"startswith":             pkrfunction.StartsWithFunc,
   112  		"strcontains":            pkrfunction.StrContains,
   113  		"strrev":                 stdlib.ReverseFunc,
   114  		"substr":                 stdlib.SubstrFunc,
   115  		"sum":                    pkrfunction.SumFunc,
   116  		"textdecodebase64":       TextDecodeBase64Func,
   117  		"textencodebase64":       TextEncodeBase64Func,
   118  		"timestamp":              pkrfunction.TimestampFunc,
   119  		"timeadd":                stdlib.TimeAddFunc,
   120  		"title":                  stdlib.TitleFunc,
   121  		"trim":                   stdlib.TrimFunc,
   122  		"trimprefix":             stdlib.TrimPrefixFunc,
   123  		"trimspace":              stdlib.TrimSpaceFunc,
   124  		"trimsuffix":             stdlib.TrimSuffixFunc,
   125  		"try":                    tryfunc.TryFunc,
   126  		"upper":                  stdlib.UpperFunc,
   127  		"urlencode":              encoding.URLEncodeFunc,
   128  		"uuidv4":                 uuid.V4Func,
   129  		"uuidv5":                 uuid.V5Func,
   130  		"values":                 stdlib.ValuesFunc,
   131  		"vault":                  pkrfunction.VaultFunc,
   132  		"yamldecode":             ctyyaml.YAMLDecodeFunc,
   133  		"yamlencode":             ctyyaml.YAMLEncodeFunc,
   134  		"zipmap":                 stdlib.ZipmapFunc,
   135  	}
   136  
   137  	funcs["templatefile"] = pkrfunction.MakeTemplateFileFunc(basedir, func() map[string]function.Function {
   138  		// The templatefile function prevents recursive calls to itself
   139  		// by copying this map and overwriting the "templatefile" entry.
   140  		return funcs
   141  	})
   142  
   143  	return funcs
   144  }
   145  
   146  // TextEncodeBase64Func constructs a function that encodes a string to a target encoding and then to a base64 sequence.
   147  var TextEncodeBase64Func = function.New(&function.Spec{
   148  	Params: []function.Parameter{
   149  		{
   150  			Name: "string",
   151  			Type: cty.String,
   152  		},
   153  		{
   154  			Name: "encoding",
   155  			Type: cty.String,
   156  		},
   157  	},
   158  	Description:  "Encodes the input string (UTF-8) to the destination encoding. The output is base64 to account for cty limiting strings to NFC normalised UTF-8 strings.",
   159  	Type:         function.StaticReturnType(cty.String),
   160  	RefineResult: func(rb *cty.RefinementBuilder) *cty.RefinementBuilder { return rb.NotNull() },
   161  	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
   162  		encoding, err := ianaindex.IANA.Encoding(args[1].AsString())
   163  		if err != nil || encoding == nil {
   164  			return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "%q is not a supported IANA encoding name or alias", args[1].AsString())
   165  		}
   166  
   167  		encName, err := ianaindex.IANA.Name(encoding)
   168  		if err != nil { // would be weird, since we just read this encoding out
   169  			encName = args[1].AsString()
   170  		}
   171  
   172  		encoder := encoding.NewEncoder()
   173  		encodedInput, err := encoder.Bytes([]byte(args[0].AsString()))
   174  		if err != nil {
   175  			// The string representations of "err" disclose implementation
   176  			// details of the underlying library, and the main error we might
   177  			// like to return a special message for is unexported as
   178  			// golang.org/x/text/encoding/internal.RepertoireError, so this
   179  			// is just a generic error message for now.
   180  			//
   181  			// We also don't include the string itself in the message because
   182  			// it can typically be very large, contain newline characters,
   183  			// etc.
   184  			return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "the given string contains characters that cannot be represented in %s", encName)
   185  		}
   186  
   187  		return cty.StringVal(base64.StdEncoding.EncodeToString(encodedInput)), nil
   188  	},
   189  })
   190  
   191  // TextDecodeBase64Func constructs a function that decodes a base64 sequence from the source encoding to UTF-8.
   192  var TextDecodeBase64Func = function.New(&function.Spec{
   193  	Params: []function.Parameter{
   194  		{
   195  			Name: "source",
   196  			Type: cty.String,
   197  		},
   198  		{
   199  			Name: "encoding",
   200  			Type: cty.String,
   201  		},
   202  	},
   203  	Type:         function.StaticReturnType(cty.String),
   204  	Description:  "Encodes the input base64 blob from an encoding to utf-8. The input is base64 to account for cty limiting strings to NFC normalised UTF-8 strings.",
   205  	RefineResult: func(rb *cty.RefinementBuilder) *cty.RefinementBuilder { return rb.NotNull() },
   206  	Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
   207  		encoding, err := ianaindex.IANA.Encoding(args[1].AsString())
   208  		if err != nil || encoding == nil {
   209  			return cty.UnknownVal(cty.String), function.NewArgErrorf(1, "%q is not a supported IANA encoding name or alias", args[1].AsString())
   210  		}
   211  
   212  		encName, err := ianaindex.IANA.Name(encoding)
   213  		if err != nil { // would be weird, since we just read this encoding out
   214  			encName = args[1].AsString()
   215  		}
   216  
   217  		s := args[0].AsString()
   218  		sDec, err := base64.StdEncoding.DecodeString(s)
   219  		if err != nil {
   220  			switch err := err.(type) {
   221  			case base64.CorruptInputError:
   222  				return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "the given value is has an invalid base64 symbol at offset %d", int(err))
   223  			default:
   224  				return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "invalid source string: %w", err)
   225  			}
   226  		}
   227  
   228  		decoder := encoding.NewDecoder()
   229  		decoded, err := decoder.Bytes(sDec)
   230  		if err != nil || bytes.ContainsRune(decoded, '�') {
   231  			return cty.UnknownVal(cty.String), function.NewArgErrorf(0, "the given string contains symbols that are not defined for %s", encName)
   232  		}
   233  
   234  		return cty.StringVal(string(decoded)), nil
   235  	},
   236  })