github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/lang/functions.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package lang 5 6 import ( 7 "fmt" 8 9 "github.com/hashicorp/hcl/v2/ext/tryfunc" 10 ctyyaml "github.com/zclconf/go-cty-yaml" 11 "github.com/zclconf/go-cty/cty" 12 "github.com/zclconf/go-cty/cty/function" 13 "github.com/zclconf/go-cty/cty/function/stdlib" 14 15 "github.com/terramate-io/tf/experiments" 16 "github.com/terramate-io/tf/lang/funcs" 17 ) 18 19 var impureFunctions = []string{ 20 "bcrypt", 21 "timestamp", 22 "uuid", 23 } 24 25 // Functions returns the set of functions that should be used to when evaluating 26 // expressions in the receiving scope. 27 func (s *Scope) Functions() map[string]function.Function { 28 s.funcsLock.Lock() 29 if s.funcs == nil { 30 // Some of our functions are just directly the cty stdlib functions. 31 // Others are implemented in the subdirectory "funcs" here in this 32 // repository. New functions should generally start out their lives 33 // in the "funcs" directory and potentially graduate to cty stdlib 34 // later if the functionality seems to be something domain-agnostic 35 // that would be useful to all applications using cty functions. 36 37 s.funcs = map[string]function.Function{ 38 "abs": stdlib.AbsoluteFunc, 39 "abspath": funcs.AbsPathFunc, 40 "alltrue": funcs.AllTrueFunc, 41 "anytrue": funcs.AnyTrueFunc, 42 "basename": funcs.BasenameFunc, 43 "base64decode": funcs.Base64DecodeFunc, 44 "base64encode": funcs.Base64EncodeFunc, 45 "base64gzip": funcs.Base64GzipFunc, 46 "base64sha256": funcs.Base64Sha256Func, 47 "base64sha512": funcs.Base64Sha512Func, 48 "bcrypt": funcs.BcryptFunc, 49 "can": tryfunc.CanFunc, 50 "ceil": stdlib.CeilFunc, 51 "chomp": stdlib.ChompFunc, 52 "cidrhost": funcs.CidrHostFunc, 53 "cidrnetmask": funcs.CidrNetmaskFunc, 54 "cidrsubnet": funcs.CidrSubnetFunc, 55 "cidrsubnets": funcs.CidrSubnetsFunc, 56 "coalesce": funcs.CoalesceFunc, 57 "coalescelist": stdlib.CoalesceListFunc, 58 "compact": stdlib.CompactFunc, 59 "concat": stdlib.ConcatFunc, 60 "contains": stdlib.ContainsFunc, 61 "csvdecode": stdlib.CSVDecodeFunc, 62 "dirname": funcs.DirnameFunc, 63 "distinct": stdlib.DistinctFunc, 64 "element": stdlib.ElementFunc, 65 "endswith": funcs.EndsWithFunc, 66 "chunklist": stdlib.ChunklistFunc, 67 "file": funcs.MakeFileFunc(s.BaseDir, false), 68 "fileexists": funcs.MakeFileExistsFunc(s.BaseDir), 69 "fileset": funcs.MakeFileSetFunc(s.BaseDir), 70 "filebase64": funcs.MakeFileFunc(s.BaseDir, true), 71 "filebase64sha256": funcs.MakeFileBase64Sha256Func(s.BaseDir), 72 "filebase64sha512": funcs.MakeFileBase64Sha512Func(s.BaseDir), 73 "filemd5": funcs.MakeFileMd5Func(s.BaseDir), 74 "filesha1": funcs.MakeFileSha1Func(s.BaseDir), 75 "filesha256": funcs.MakeFileSha256Func(s.BaseDir), 76 "filesha512": funcs.MakeFileSha512Func(s.BaseDir), 77 "flatten": stdlib.FlattenFunc, 78 "floor": stdlib.FloorFunc, 79 "format": stdlib.FormatFunc, 80 "formatdate": stdlib.FormatDateFunc, 81 "formatlist": stdlib.FormatListFunc, 82 "indent": stdlib.IndentFunc, 83 "index": funcs.IndexFunc, // stdlib.IndexFunc is not compatible 84 "join": stdlib.JoinFunc, 85 "jsondecode": stdlib.JSONDecodeFunc, 86 "jsonencode": stdlib.JSONEncodeFunc, 87 "keys": stdlib.KeysFunc, 88 "length": funcs.LengthFunc, 89 "list": funcs.ListFunc, 90 "log": stdlib.LogFunc, 91 "lookup": funcs.LookupFunc, 92 "lower": stdlib.LowerFunc, 93 "map": funcs.MapFunc, 94 "matchkeys": funcs.MatchkeysFunc, 95 "max": stdlib.MaxFunc, 96 "md5": funcs.Md5Func, 97 "merge": stdlib.MergeFunc, 98 "min": stdlib.MinFunc, 99 "one": funcs.OneFunc, 100 "parseint": stdlib.ParseIntFunc, 101 "pathexpand": funcs.PathExpandFunc, 102 "pow": stdlib.PowFunc, 103 "range": stdlib.RangeFunc, 104 "regex": stdlib.RegexFunc, 105 "regexall": stdlib.RegexAllFunc, 106 "replace": funcs.ReplaceFunc, 107 "reverse": stdlib.ReverseListFunc, 108 "rsadecrypt": funcs.RsaDecryptFunc, 109 "sensitive": funcs.SensitiveFunc, 110 "nonsensitive": funcs.NonsensitiveFunc, 111 "setintersection": stdlib.SetIntersectionFunc, 112 "setproduct": stdlib.SetProductFunc, 113 "setsubtract": stdlib.SetSubtractFunc, 114 "setunion": stdlib.SetUnionFunc, 115 "sha1": funcs.Sha1Func, 116 "sha256": funcs.Sha256Func, 117 "sha512": funcs.Sha512Func, 118 "signum": stdlib.SignumFunc, 119 "slice": stdlib.SliceFunc, 120 "sort": stdlib.SortFunc, 121 "split": stdlib.SplitFunc, 122 "startswith": funcs.StartsWithFunc, 123 "strcontains": funcs.StrContainsFunc, 124 "strrev": stdlib.ReverseFunc, 125 "substr": stdlib.SubstrFunc, 126 "sum": funcs.SumFunc, 127 "textdecodebase64": funcs.TextDecodeBase64Func, 128 "textencodebase64": funcs.TextEncodeBase64Func, 129 "timestamp": funcs.TimestampFunc, 130 "timeadd": stdlib.TimeAddFunc, 131 "timecmp": funcs.TimeCmpFunc, 132 "title": stdlib.TitleFunc, 133 "tostring": funcs.MakeToFunc(cty.String), 134 "tonumber": funcs.MakeToFunc(cty.Number), 135 "tobool": funcs.MakeToFunc(cty.Bool), 136 "toset": funcs.MakeToFunc(cty.Set(cty.DynamicPseudoType)), 137 "tolist": funcs.MakeToFunc(cty.List(cty.DynamicPseudoType)), 138 "tomap": funcs.MakeToFunc(cty.Map(cty.DynamicPseudoType)), 139 "transpose": funcs.TransposeFunc, 140 "trim": stdlib.TrimFunc, 141 "trimprefix": stdlib.TrimPrefixFunc, 142 "trimspace": stdlib.TrimSpaceFunc, 143 "trimsuffix": stdlib.TrimSuffixFunc, 144 "try": tryfunc.TryFunc, 145 "upper": stdlib.UpperFunc, 146 "urlencode": funcs.URLEncodeFunc, 147 "uuid": funcs.UUIDFunc, 148 "uuidv5": funcs.UUIDV5Func, 149 "values": stdlib.ValuesFunc, 150 "yamldecode": ctyyaml.YAMLDecodeFunc, 151 "yamlencode": ctyyaml.YAMLEncodeFunc, 152 "zipmap": stdlib.ZipmapFunc, 153 } 154 155 s.funcs["templatefile"] = funcs.MakeTemplateFileFunc(s.BaseDir, func() map[string]function.Function { 156 // The templatefile function prevents recursive calls to itself 157 // by copying this map and overwriting the "templatefile" entry. 158 return s.funcs 159 }) 160 161 if s.ConsoleMode { 162 // The type function is only available in terraform console. 163 s.funcs["type"] = funcs.TypeFunc 164 } 165 166 if !s.ConsoleMode { 167 // The plantimestamp function doesn't make sense in the terraform 168 // console. 169 s.funcs["plantimestamp"] = funcs.MakeStaticTimestampFunc(s.PlanTimestamp) 170 } 171 172 if s.PureOnly { 173 // Force our few impure functions to return unknown so that we 174 // can defer evaluating them until a later pass. 175 for _, name := range impureFunctions { 176 s.funcs[name] = function.Unpredictable(s.funcs[name]) 177 } 178 } 179 180 // Add a description to each function and parameter based on the 181 // contents of descriptionList. 182 // One must create a matching description entry whenever a new 183 // function is introduced. 184 for name, f := range s.funcs { 185 s.funcs[name] = funcs.WithDescription(name, f) 186 } 187 } 188 s.funcsLock.Unlock() 189 190 return s.funcs 191 } 192 193 // experimentalFunction checks whether the given experiment is enabled for 194 // the recieving scope. If so, it will return the given function verbatim. 195 // If not, it will return a placeholder function that just returns an 196 // error explaining that the function requires the experiment to be enabled. 197 // 198 //lint:ignore U1000 Ignore unused function error for now 199 func (s *Scope) experimentalFunction(experiment experiments.Experiment, fn function.Function) function.Function { 200 if s.activeExperiments.Has(experiment) { 201 return fn 202 } 203 204 err := fmt.Errorf( 205 "this function is experimental and available only when the experiment keyword %s is enabled for the current module", 206 experiment.Keyword(), 207 ) 208 209 return function.New(&function.Spec{ 210 Params: fn.Params(), 211 VarParam: fn.VarParam(), 212 Type: func(args []cty.Value) (cty.Type, error) { 213 return cty.DynamicPseudoType, err 214 }, 215 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) { 216 // It would be weird to get here because the Type function always 217 // fails, but we'll return an error here too anyway just to be 218 // robust. 219 return cty.DynamicVal, err 220 }, 221 }) 222 }