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