From 4f97ef67eefa48feb105474c9f0d1e22a9d8a726 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 12 Feb 2026 10:39:13 -0500 Subject: [PATCH] enable use of the convert function We add `convert` to our set of functions, and we clean up the duplicate function assignments in this package. The multiple assignments have meant that any function changes are inevitably forgotten from the testing framework, and we shouldn't need to declare the mapping multiple times. --- internal/lang/functions.go | 159 ++++++-------------------------- internal/lang/functions_test.go | 13 +++ 2 files changed, 43 insertions(+), 129 deletions(-) diff --git a/internal/lang/functions.go b/internal/lang/functions.go index fba6bef2a0..206110f2a0 100644 --- a/internal/lang/functions.go +++ b/internal/lang/functions.go @@ -65,131 +65,22 @@ func (s *Scope) Functions() map[string]function.Function { s.funcsLock.Lock() if s.funcs == nil { - s.funcs = baseFunctions(s.BaseDir) + coreFuncs := baseFunctions(s.BaseDir) - // If you're adding something here, please consider whether it meets - // the criteria for either or both of the sets [filesystemFunctions] - // and [templateFunctions] and add it there if so, to ensure that - // functions relying on those classifications will behave correctly. - coreFuncs := map[string]function.Function{ - "abs": stdlib.AbsoluteFunc, - "abspath": funcs.AbsPathFunc, - "alltrue": funcs.AllTrueFunc, - "anytrue": funcs.AnyTrueFunc, - "basename": funcs.BasenameFunc, - "base64decode": funcs.Base64DecodeFunc, - "base64encode": funcs.Base64EncodeFunc, - "base64gzip": funcs.Base64GzipFunc, - "base64sha256": funcs.Base64Sha256Func, - "base64sha512": funcs.Base64Sha512Func, - "bcrypt": funcs.BcryptFunc, - "can": tryfunc.CanFunc, - "ceil": stdlib.CeilFunc, - "chomp": stdlib.ChompFunc, - "cidrhost": funcs.CidrHostFunc, - "cidrnetmask": funcs.CidrNetmaskFunc, - "cidrsubnet": funcs.CidrSubnetFunc, - "cidrsubnets": funcs.CidrSubnetsFunc, - "coalesce": funcs.CoalesceFunc, - "coalescelist": stdlib.CoalesceListFunc, - "compact": stdlib.CompactFunc, - "concat": stdlib.ConcatFunc, - "contains": stdlib.ContainsFunc, - "csvdecode": stdlib.CSVDecodeFunc, - "dirname": funcs.DirnameFunc, - "distinct": stdlib.DistinctFunc, - "element": stdlib.ElementFunc, - "endswith": funcs.EndsWithFunc, - "ephemeralasnull": funcs.EphemeralAsNullFunc, - "chunklist": stdlib.ChunklistFunc, - "file": funcs.MakeFileFunc(s.BaseDir, false, immutableResults("file", s.FunctionResults)), - "fileexists": funcs.MakeFileExistsFunc(s.BaseDir, immutableResults("fileexists", s.FunctionResults)), - "fileset": funcs.MakeFileSetFunc(s.BaseDir, immutableResults("fileset", s.FunctionResults)), - "filebase64": funcs.MakeFileFunc(s.BaseDir, true, immutableResults("filebase64", s.FunctionResults)), - "filebase64sha256": funcs.MakeFileBase64Sha256Func(s.BaseDir, immutableResults("filebase64sha256", s.FunctionResults)), - "filebase64sha512": funcs.MakeFileBase64Sha512Func(s.BaseDir, immutableResults("filebase64sha512", s.FunctionResults)), - "filemd5": funcs.MakeFileMd5Func(s.BaseDir, immutableResults("filemd5", s.FunctionResults)), - "filesha1": funcs.MakeFileSha1Func(s.BaseDir, immutableResults("filesha1", s.FunctionResults)), - "filesha256": funcs.MakeFileSha256Func(s.BaseDir, immutableResults("filesha256", s.FunctionResults)), - "filesha512": funcs.MakeFileSha512Func(s.BaseDir, immutableResults("filesha512", s.FunctionResults)), - "flatten": stdlib.FlattenFunc, - "floor": stdlib.FloorFunc, - "format": stdlib.FormatFunc, - "formatdate": stdlib.FormatDateFunc, - "formatlist": stdlib.FormatListFunc, - "indent": stdlib.IndentFunc, - "index": funcs.IndexFunc, // stdlib.IndexFunc is not compatible - "join": stdlib.JoinFunc, - "jsondecode": stdlib.JSONDecodeFunc, - "jsonencode": stdlib.JSONEncodeFunc, - "keys": stdlib.KeysFunc, - "length": funcs.LengthFunc, - "list": funcs.ListFunc, - "log": stdlib.LogFunc, - "lookup": funcs.LookupFunc, - "lower": stdlib.LowerFunc, - "map": funcs.MapFunc, - "matchkeys": funcs.MatchkeysFunc, - "max": stdlib.MaxFunc, - "md5": funcs.Md5Func, - "merge": stdlib.MergeFunc, - "min": stdlib.MinFunc, - "one": funcs.OneFunc, - "parseint": stdlib.ParseIntFunc, - "pathexpand": funcs.PathExpandFunc, - "pow": stdlib.PowFunc, - "range": stdlib.RangeFunc, - "regex": stdlib.RegexFunc, - "regexall": stdlib.RegexAllFunc, - "replace": funcs.ReplaceFunc, - "reverse": stdlib.ReverseListFunc, - "rsadecrypt": funcs.RsaDecryptFunc, - "sensitive": funcs.SensitiveFunc, - "nonsensitive": funcs.NonsensitiveFunc, - "issensitive": funcs.IssensitiveFunc, - "setintersection": stdlib.SetIntersectionFunc, - "setproduct": stdlib.SetProductFunc, - "setsubtract": stdlib.SetSubtractFunc, - "setunion": stdlib.SetUnionFunc, - "sha1": funcs.Sha1Func, - "sha256": funcs.Sha256Func, - "sha512": funcs.Sha512Func, - "signum": stdlib.SignumFunc, - "slice": stdlib.SliceFunc, - "sort": stdlib.SortFunc, - "split": stdlib.SplitFunc, - "startswith": funcs.StartsWithFunc, - "strcontains": funcs.StrContainsFunc, - "strrev": stdlib.ReverseFunc, - "substr": stdlib.SubstrFunc, - "sum": funcs.SumFunc, - "textdecodebase64": funcs.TextDecodeBase64Func, - "textencodebase64": funcs.TextEncodeBase64Func, - "timestamp": funcs.TimestampFunc, - "timeadd": stdlib.TimeAddFunc, - "timecmp": funcs.TimeCmpFunc, - "title": stdlib.TitleFunc, - "tostring": funcs.MakeToFunc(cty.String), - "tonumber": funcs.MakeToFunc(cty.Number), - "tobool": funcs.MakeToFunc(cty.Bool), - "toset": funcs.MakeToFunc(cty.Set(cty.DynamicPseudoType)), - "tolist": funcs.MakeToFunc(cty.List(cty.DynamicPseudoType)), - "tomap": funcs.MakeToFunc(cty.Map(cty.DynamicPseudoType)), - "transpose": funcs.TransposeFunc, - "trim": stdlib.TrimFunc, - "trimprefix": stdlib.TrimPrefixFunc, - "trimspace": stdlib.TrimSpaceFunc, - "trimsuffix": stdlib.TrimSuffixFunc, - "try": tryfunc.TryFunc, - "upper": stdlib.UpperFunc, - "urlencode": funcs.URLEncodeFunc, - "uuid": funcs.UUIDFunc, - "uuidv5": funcs.UUIDV5Func, - "values": stdlib.ValuesFunc, - "yamldecode": ctyyaml.YAMLDecodeFunc, - "yamlencode": ctyyaml.YAMLEncodeFunc, - "zipmap": stdlib.ZipmapFunc, - } + // Modify the functions which behave slightly differently in core. + // Filesystem functions will check for consistent results, and the + // template functions need to be updated to close over the correct + // versions of the filesystem functions. + coreFuncs["file"] = funcs.MakeFileFunc(s.BaseDir, false, immutableResults("file", s.FunctionResults)) + coreFuncs["fileexists"] = funcs.MakeFileExistsFunc(s.BaseDir, immutableResults("fileexists", s.FunctionResults)) + coreFuncs["fileset"] = funcs.MakeFileSetFunc(s.BaseDir, immutableResults("fileset", s.FunctionResults)) + coreFuncs["filebase64"] = funcs.MakeFileFunc(s.BaseDir, true, immutableResults("filebase64", s.FunctionResults)) + coreFuncs["filebase64sha256"] = funcs.MakeFileBase64Sha256Func(s.BaseDir, immutableResults("filebase64sha256", s.FunctionResults)) + coreFuncs["filebase64sha512"] = funcs.MakeFileBase64Sha512Func(s.BaseDir, immutableResults("filebase64sha512", s.FunctionResults)) + coreFuncs["filemd5"] = funcs.MakeFileMd5Func(s.BaseDir, immutableResults("filemd5", s.FunctionResults)) + coreFuncs["filesha1"] = funcs.MakeFileSha1Func(s.BaseDir, immutableResults("filesha1", s.FunctionResults)) + coreFuncs["filesha256"] = funcs.MakeFileSha256Func(s.BaseDir, immutableResults("filesha256", s.FunctionResults)) + coreFuncs["filesha512"] = funcs.MakeFileSha512Func(s.BaseDir, immutableResults("filesha512", s.FunctionResults)) // Our two template-rendering functions want to be able to call // all of the other functions themselves, but we pass them indirectly @@ -226,7 +117,7 @@ func (s *Scope) Functions() map[string]function.Function { // All of the built-in functions are also available under the "core::" // namespace, to distinguish from the "provider::" and "module::" // namespaces that can serve as external extension points. - s.funcs = make(map[string]function.Function, len(coreFuncs)*2) + s.funcs = make(map[string]function.Function) for name, fn := range coreFuncs { fn = funcs.WithDescription(name, fn) s.funcs[name] = fn @@ -306,11 +197,13 @@ func baseFunctions(baseDir string) map[string]function.Function { "compact": stdlib.CompactFunc, "concat": stdlib.ConcatFunc, "contains": stdlib.ContainsFunc, + "convert": funcs.ConvertFunc, "csvdecode": stdlib.CSVDecodeFunc, "dirname": funcs.DirnameFunc, "distinct": stdlib.DistinctFunc, "element": stdlib.ElementFunc, "endswith": funcs.EndsWithFunc, + "ephemeralasnull": funcs.EphemeralAsNullFunc, "chunklist": stdlib.ChunklistFunc, "file": funcs.MakeFileFunc(baseDir, false, noopWrapper), "fileexists": funcs.MakeFileExistsFunc(baseDir, noopWrapper), @@ -401,11 +294,19 @@ func baseFunctions(baseDir string) map[string]function.Function { "zipmap": stdlib.ZipmapFunc, } - fs["templatefile"] = funcs.MakeTemplateFileFunc(baseDir, func() (map[string]function.Function, collections.Set[string], collections.Set[string]) { - // The templatefile function prevents recursive calls to itself - // by copying this map and overwriting the "templatefile" entry. + // Our two template-rendering functions want to be able to call + // all of the other functions themselves, but we pass them indirectly + // via a callback to avoid chicken/egg problems while initializing + // the functions table. + funcsFunc := func() (funcs map[string]function.Function, fsFuncs collections.Set[string], templateFuncs collections.Set[string]) { + // The templatefile and templatestring functions prevent recursive + // calls to themselves and each other by copying this map and + // overwriting the relevant entries. return fs, filesystemFunctions, templateFunctions - }, noopWrapper) + } + + fs["templatefile"] = funcs.MakeTemplateFileFunc(baseDir, funcsFunc, noopWrapper) + fs["templatestring"] = funcs.MakeTemplateStringFunc(funcsFunc) return fs } diff --git a/internal/lang/functions_test.go b/internal/lang/functions_test.go index 7ccbd1afb5..0e96df36d4 100644 --- a/internal/lang/functions_test.go +++ b/internal/lang/functions_test.go @@ -281,6 +281,19 @@ func TestFunctions(t *testing.T) { }, }, + "convert": { + { + `convert({}, object({attr=optional(string, "default")}))`, + cty.ObjectVal(map[string]cty.Value{ + "attr": cty.StringVal("default"), + }), + }, + { + `convert({}, map(list(map(set(string)))))`, + cty.MapValEmpty(cty.List(cty.Map(cty.Set(cty.String)))), + }, + }, + "csvdecode": { { `csvdecode("a,b,c\n1,2,3\n4,5,6")`,