diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/config/interpolate_funcs.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/config/interpolate_funcs.go | 1390 |
1 files changed, 1390 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/config/interpolate_funcs.go b/vendor/github.com/hashicorp/terraform/config/interpolate_funcs.go new file mode 100644 index 0000000..f1f97b0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/config/interpolate_funcs.go | |||
@@ -0,0 +1,1390 @@ | |||
1 | package config | ||
2 | |||
3 | import ( | ||
4 | "crypto/md5" | ||
5 | "crypto/sha1" | ||
6 | "crypto/sha256" | ||
7 | "crypto/sha512" | ||
8 | "encoding/base64" | ||
9 | "encoding/hex" | ||
10 | "encoding/json" | ||
11 | "fmt" | ||
12 | "io/ioutil" | ||
13 | "math" | ||
14 | "net" | ||
15 | "path/filepath" | ||
16 | "regexp" | ||
17 | "sort" | ||
18 | "strconv" | ||
19 | "strings" | ||
20 | "time" | ||
21 | |||
22 | "github.com/apparentlymart/go-cidr/cidr" | ||
23 | "github.com/hashicorp/go-uuid" | ||
24 | "github.com/hashicorp/hil" | ||
25 | "github.com/hashicorp/hil/ast" | ||
26 | "github.com/mitchellh/go-homedir" | ||
27 | ) | ||
28 | |||
29 | // stringSliceToVariableValue converts a string slice into the value | ||
30 | // required to be returned from interpolation functions which return | ||
31 | // TypeList. | ||
32 | func stringSliceToVariableValue(values []string) []ast.Variable { | ||
33 | output := make([]ast.Variable, len(values)) | ||
34 | for index, value := range values { | ||
35 | output[index] = ast.Variable{ | ||
36 | Type: ast.TypeString, | ||
37 | Value: value, | ||
38 | } | ||
39 | } | ||
40 | return output | ||
41 | } | ||
42 | |||
43 | func listVariableValueToStringSlice(values []ast.Variable) ([]string, error) { | ||
44 | output := make([]string, len(values)) | ||
45 | for index, value := range values { | ||
46 | if value.Type != ast.TypeString { | ||
47 | return []string{}, fmt.Errorf("list has non-string element (%T)", value.Type.String()) | ||
48 | } | ||
49 | output[index] = value.Value.(string) | ||
50 | } | ||
51 | return output, nil | ||
52 | } | ||
53 | |||
54 | // Funcs is the mapping of built-in functions for configuration. | ||
55 | func Funcs() map[string]ast.Function { | ||
56 | return map[string]ast.Function{ | ||
57 | "basename": interpolationFuncBasename(), | ||
58 | "base64decode": interpolationFuncBase64Decode(), | ||
59 | "base64encode": interpolationFuncBase64Encode(), | ||
60 | "base64sha256": interpolationFuncBase64Sha256(), | ||
61 | "base64sha512": interpolationFuncBase64Sha512(), | ||
62 | "ceil": interpolationFuncCeil(), | ||
63 | "chomp": interpolationFuncChomp(), | ||
64 | "cidrhost": interpolationFuncCidrHost(), | ||
65 | "cidrnetmask": interpolationFuncCidrNetmask(), | ||
66 | "cidrsubnet": interpolationFuncCidrSubnet(), | ||
67 | "coalesce": interpolationFuncCoalesce(), | ||
68 | "coalescelist": interpolationFuncCoalesceList(), | ||
69 | "compact": interpolationFuncCompact(), | ||
70 | "concat": interpolationFuncConcat(), | ||
71 | "dirname": interpolationFuncDirname(), | ||
72 | "distinct": interpolationFuncDistinct(), | ||
73 | "element": interpolationFuncElement(), | ||
74 | "file": interpolationFuncFile(), | ||
75 | "matchkeys": interpolationFuncMatchKeys(), | ||
76 | "floor": interpolationFuncFloor(), | ||
77 | "format": interpolationFuncFormat(), | ||
78 | "formatlist": interpolationFuncFormatList(), | ||
79 | "index": interpolationFuncIndex(), | ||
80 | "join": interpolationFuncJoin(), | ||
81 | "jsonencode": interpolationFuncJSONEncode(), | ||
82 | "length": interpolationFuncLength(), | ||
83 | "list": interpolationFuncList(), | ||
84 | "log": interpolationFuncLog(), | ||
85 | "lower": interpolationFuncLower(), | ||
86 | "map": interpolationFuncMap(), | ||
87 | "max": interpolationFuncMax(), | ||
88 | "md5": interpolationFuncMd5(), | ||
89 | "merge": interpolationFuncMerge(), | ||
90 | "min": interpolationFuncMin(), | ||
91 | "pathexpand": interpolationFuncPathExpand(), | ||
92 | "uuid": interpolationFuncUUID(), | ||
93 | "replace": interpolationFuncReplace(), | ||
94 | "sha1": interpolationFuncSha1(), | ||
95 | "sha256": interpolationFuncSha256(), | ||
96 | "sha512": interpolationFuncSha512(), | ||
97 | "signum": interpolationFuncSignum(), | ||
98 | "slice": interpolationFuncSlice(), | ||
99 | "sort": interpolationFuncSort(), | ||
100 | "split": interpolationFuncSplit(), | ||
101 | "substr": interpolationFuncSubstr(), | ||
102 | "timestamp": interpolationFuncTimestamp(), | ||
103 | "title": interpolationFuncTitle(), | ||
104 | "trimspace": interpolationFuncTrimSpace(), | ||
105 | "upper": interpolationFuncUpper(), | ||
106 | "zipmap": interpolationFuncZipMap(), | ||
107 | } | ||
108 | } | ||
109 | |||
110 | // interpolationFuncList creates a list from the parameters passed | ||
111 | // to it. | ||
112 | func interpolationFuncList() ast.Function { | ||
113 | return ast.Function{ | ||
114 | ArgTypes: []ast.Type{}, | ||
115 | ReturnType: ast.TypeList, | ||
116 | Variadic: true, | ||
117 | VariadicType: ast.TypeAny, | ||
118 | Callback: func(args []interface{}) (interface{}, error) { | ||
119 | var outputList []ast.Variable | ||
120 | |||
121 | for i, val := range args { | ||
122 | switch v := val.(type) { | ||
123 | case string: | ||
124 | outputList = append(outputList, ast.Variable{Type: ast.TypeString, Value: v}) | ||
125 | case []ast.Variable: | ||
126 | outputList = append(outputList, ast.Variable{Type: ast.TypeList, Value: v}) | ||
127 | case map[string]ast.Variable: | ||
128 | outputList = append(outputList, ast.Variable{Type: ast.TypeMap, Value: v}) | ||
129 | default: | ||
130 | return nil, fmt.Errorf("unexpected type %T for argument %d in list", v, i) | ||
131 | } | ||
132 | } | ||
133 | |||
134 | // we don't support heterogeneous types, so make sure all types match the first | ||
135 | if len(outputList) > 0 { | ||
136 | firstType := outputList[0].Type | ||
137 | for i, v := range outputList[1:] { | ||
138 | if v.Type != firstType { | ||
139 | return nil, fmt.Errorf("unexpected type %s for argument %d in list", v.Type, i+1) | ||
140 | } | ||
141 | } | ||
142 | } | ||
143 | |||
144 | return outputList, nil | ||
145 | }, | ||
146 | } | ||
147 | } | ||
148 | |||
149 | // interpolationFuncMap creates a map from the parameters passed | ||
150 | // to it. | ||
151 | func interpolationFuncMap() ast.Function { | ||
152 | return ast.Function{ | ||
153 | ArgTypes: []ast.Type{}, | ||
154 | ReturnType: ast.TypeMap, | ||
155 | Variadic: true, | ||
156 | VariadicType: ast.TypeAny, | ||
157 | Callback: func(args []interface{}) (interface{}, error) { | ||
158 | outputMap := make(map[string]ast.Variable) | ||
159 | |||
160 | if len(args)%2 != 0 { | ||
161 | return nil, fmt.Errorf("requires an even number of arguments, got %d", len(args)) | ||
162 | } | ||
163 | |||
164 | var firstType *ast.Type | ||
165 | for i := 0; i < len(args); i += 2 { | ||
166 | key, ok := args[i].(string) | ||
167 | if !ok { | ||
168 | return nil, fmt.Errorf("argument %d represents a key, so it must be a string", i+1) | ||
169 | } | ||
170 | val := args[i+1] | ||
171 | variable, err := hil.InterfaceToVariable(val) | ||
172 | if err != nil { | ||
173 | return nil, err | ||
174 | } | ||
175 | // Enforce map type homogeneity | ||
176 | if firstType == nil { | ||
177 | firstType = &variable.Type | ||
178 | } else if variable.Type != *firstType { | ||
179 | return nil, fmt.Errorf("all map values must have the same type, got %s then %s", firstType.Printable(), variable.Type.Printable()) | ||
180 | } | ||
181 | // Check for duplicate keys | ||
182 | if _, ok := outputMap[key]; ok { | ||
183 | return nil, fmt.Errorf("argument %d is a duplicate key: %q", i+1, key) | ||
184 | } | ||
185 | outputMap[key] = variable | ||
186 | } | ||
187 | |||
188 | return outputMap, nil | ||
189 | }, | ||
190 | } | ||
191 | } | ||
192 | |||
193 | // interpolationFuncCompact strips a list of multi-variable values | ||
194 | // (e.g. as returned by "split") of any empty strings. | ||
195 | func interpolationFuncCompact() ast.Function { | ||
196 | return ast.Function{ | ||
197 | ArgTypes: []ast.Type{ast.TypeList}, | ||
198 | ReturnType: ast.TypeList, | ||
199 | Variadic: false, | ||
200 | Callback: func(args []interface{}) (interface{}, error) { | ||
201 | inputList := args[0].([]ast.Variable) | ||
202 | |||
203 | var outputList []string | ||
204 | for _, val := range inputList { | ||
205 | strVal, ok := val.Value.(string) | ||
206 | if !ok { | ||
207 | return nil, fmt.Errorf( | ||
208 | "compact() may only be used with flat lists, this list contains elements of %s", | ||
209 | val.Type.Printable()) | ||
210 | } | ||
211 | if strVal == "" { | ||
212 | continue | ||
213 | } | ||
214 | |||
215 | outputList = append(outputList, strVal) | ||
216 | } | ||
217 | return stringSliceToVariableValue(outputList), nil | ||
218 | }, | ||
219 | } | ||
220 | } | ||
221 | |||
222 | // interpolationFuncCidrHost implements the "cidrhost" function that | ||
223 | // fills in the host part of a CIDR range address to create a single | ||
224 | // host address | ||
225 | func interpolationFuncCidrHost() ast.Function { | ||
226 | return ast.Function{ | ||
227 | ArgTypes: []ast.Type{ | ||
228 | ast.TypeString, // starting CIDR mask | ||
229 | ast.TypeInt, // host number to insert | ||
230 | }, | ||
231 | ReturnType: ast.TypeString, | ||
232 | Variadic: false, | ||
233 | Callback: func(args []interface{}) (interface{}, error) { | ||
234 | hostNum := args[1].(int) | ||
235 | _, network, err := net.ParseCIDR(args[0].(string)) | ||
236 | if err != nil { | ||
237 | return nil, fmt.Errorf("invalid CIDR expression: %s", err) | ||
238 | } | ||
239 | |||
240 | ip, err := cidr.Host(network, hostNum) | ||
241 | if err != nil { | ||
242 | return nil, err | ||
243 | } | ||
244 | |||
245 | return ip.String(), nil | ||
246 | }, | ||
247 | } | ||
248 | } | ||
249 | |||
250 | // interpolationFuncCidrNetmask implements the "cidrnetmask" function | ||
251 | // that returns the subnet mask in IP address notation. | ||
252 | func interpolationFuncCidrNetmask() ast.Function { | ||
253 | return ast.Function{ | ||
254 | ArgTypes: []ast.Type{ | ||
255 | ast.TypeString, // CIDR mask | ||
256 | }, | ||
257 | ReturnType: ast.TypeString, | ||
258 | Variadic: false, | ||
259 | Callback: func(args []interface{}) (interface{}, error) { | ||
260 | _, network, err := net.ParseCIDR(args[0].(string)) | ||
261 | if err != nil { | ||
262 | return nil, fmt.Errorf("invalid CIDR expression: %s", err) | ||
263 | } | ||
264 | |||
265 | return net.IP(network.Mask).String(), nil | ||
266 | }, | ||
267 | } | ||
268 | } | ||
269 | |||
270 | // interpolationFuncCidrSubnet implements the "cidrsubnet" function that | ||
271 | // adds an additional subnet of the given length onto an existing | ||
272 | // IP block expressed in CIDR notation. | ||
273 | func interpolationFuncCidrSubnet() ast.Function { | ||
274 | return ast.Function{ | ||
275 | ArgTypes: []ast.Type{ | ||
276 | ast.TypeString, // starting CIDR mask | ||
277 | ast.TypeInt, // number of bits to extend the prefix | ||
278 | ast.TypeInt, // network number to append to the prefix | ||
279 | }, | ||
280 | ReturnType: ast.TypeString, | ||
281 | Variadic: false, | ||
282 | Callback: func(args []interface{}) (interface{}, error) { | ||
283 | extraBits := args[1].(int) | ||
284 | subnetNum := args[2].(int) | ||
285 | _, network, err := net.ParseCIDR(args[0].(string)) | ||
286 | if err != nil { | ||
287 | return nil, fmt.Errorf("invalid CIDR expression: %s", err) | ||
288 | } | ||
289 | |||
290 | // For portability with 32-bit systems where the subnet number | ||
291 | // will be a 32-bit int, we only allow extension of 32 bits in | ||
292 | // one call even if we're running on a 64-bit machine. | ||
293 | // (Of course, this is significant only for IPv6.) | ||
294 | if extraBits > 32 { | ||
295 | return nil, fmt.Errorf("may not extend prefix by more than 32 bits") | ||
296 | } | ||
297 | |||
298 | newNetwork, err := cidr.Subnet(network, extraBits, subnetNum) | ||
299 | if err != nil { | ||
300 | return nil, err | ||
301 | } | ||
302 | |||
303 | return newNetwork.String(), nil | ||
304 | }, | ||
305 | } | ||
306 | } | ||
307 | |||
308 | // interpolationFuncCoalesce implements the "coalesce" function that | ||
309 | // returns the first non null / empty string from the provided input | ||
310 | func interpolationFuncCoalesce() ast.Function { | ||
311 | return ast.Function{ | ||
312 | ArgTypes: []ast.Type{ast.TypeString}, | ||
313 | ReturnType: ast.TypeString, | ||
314 | Variadic: true, | ||
315 | VariadicType: ast.TypeString, | ||
316 | Callback: func(args []interface{}) (interface{}, error) { | ||
317 | if len(args) < 2 { | ||
318 | return nil, fmt.Errorf("must provide at least two arguments") | ||
319 | } | ||
320 | for _, arg := range args { | ||
321 | argument := arg.(string) | ||
322 | |||
323 | if argument != "" { | ||
324 | return argument, nil | ||
325 | } | ||
326 | } | ||
327 | return "", nil | ||
328 | }, | ||
329 | } | ||
330 | } | ||
331 | |||
332 | // interpolationFuncCoalesceList implements the "coalescelist" function that | ||
333 | // returns the first non empty list from the provided input | ||
334 | func interpolationFuncCoalesceList() ast.Function { | ||
335 | return ast.Function{ | ||
336 | ArgTypes: []ast.Type{ast.TypeList}, | ||
337 | ReturnType: ast.TypeList, | ||
338 | Variadic: true, | ||
339 | VariadicType: ast.TypeList, | ||
340 | Callback: func(args []interface{}) (interface{}, error) { | ||
341 | if len(args) < 2 { | ||
342 | return nil, fmt.Errorf("must provide at least two arguments") | ||
343 | } | ||
344 | for _, arg := range args { | ||
345 | argument := arg.([]ast.Variable) | ||
346 | |||
347 | if len(argument) > 0 { | ||
348 | return argument, nil | ||
349 | } | ||
350 | } | ||
351 | return make([]ast.Variable, 0), nil | ||
352 | }, | ||
353 | } | ||
354 | } | ||
355 | |||
356 | // interpolationFuncConcat implements the "concat" function that concatenates | ||
357 | // multiple lists. | ||
358 | func interpolationFuncConcat() ast.Function { | ||
359 | return ast.Function{ | ||
360 | ArgTypes: []ast.Type{ast.TypeList}, | ||
361 | ReturnType: ast.TypeList, | ||
362 | Variadic: true, | ||
363 | VariadicType: ast.TypeList, | ||
364 | Callback: func(args []interface{}) (interface{}, error) { | ||
365 | var outputList []ast.Variable | ||
366 | |||
367 | for _, arg := range args { | ||
368 | for _, v := range arg.([]ast.Variable) { | ||
369 | switch v.Type { | ||
370 | case ast.TypeString: | ||
371 | outputList = append(outputList, v) | ||
372 | case ast.TypeList: | ||
373 | outputList = append(outputList, v) | ||
374 | case ast.TypeMap: | ||
375 | outputList = append(outputList, v) | ||
376 | default: | ||
377 | return nil, fmt.Errorf("concat() does not support lists of %s", v.Type.Printable()) | ||
378 | } | ||
379 | } | ||
380 | } | ||
381 | |||
382 | // we don't support heterogeneous types, so make sure all types match the first | ||
383 | if len(outputList) > 0 { | ||
384 | firstType := outputList[0].Type | ||
385 | for _, v := range outputList[1:] { | ||
386 | if v.Type != firstType { | ||
387 | return nil, fmt.Errorf("unexpected %s in list of %s", v.Type.Printable(), firstType.Printable()) | ||
388 | } | ||
389 | } | ||
390 | } | ||
391 | |||
392 | return outputList, nil | ||
393 | }, | ||
394 | } | ||
395 | } | ||
396 | |||
397 | // interpolationFuncFile implements the "file" function that allows | ||
398 | // loading contents from a file. | ||
399 | func interpolationFuncFile() ast.Function { | ||
400 | return ast.Function{ | ||
401 | ArgTypes: []ast.Type{ast.TypeString}, | ||
402 | ReturnType: ast.TypeString, | ||
403 | Callback: func(args []interface{}) (interface{}, error) { | ||
404 | path, err := homedir.Expand(args[0].(string)) | ||
405 | if err != nil { | ||
406 | return "", err | ||
407 | } | ||
408 | data, err := ioutil.ReadFile(path) | ||
409 | if err != nil { | ||
410 | return "", err | ||
411 | } | ||
412 | |||
413 | return string(data), nil | ||
414 | }, | ||
415 | } | ||
416 | } | ||
417 | |||
418 | // interpolationFuncFormat implements the "format" function that does | ||
419 | // string formatting. | ||
420 | func interpolationFuncFormat() ast.Function { | ||
421 | return ast.Function{ | ||
422 | ArgTypes: []ast.Type{ast.TypeString}, | ||
423 | Variadic: true, | ||
424 | VariadicType: ast.TypeAny, | ||
425 | ReturnType: ast.TypeString, | ||
426 | Callback: func(args []interface{}) (interface{}, error) { | ||
427 | format := args[0].(string) | ||
428 | return fmt.Sprintf(format, args[1:]...), nil | ||
429 | }, | ||
430 | } | ||
431 | } | ||
432 | |||
433 | // interpolationFuncMax returns the maximum of the numeric arguments | ||
434 | func interpolationFuncMax() ast.Function { | ||
435 | return ast.Function{ | ||
436 | ArgTypes: []ast.Type{ast.TypeFloat}, | ||
437 | ReturnType: ast.TypeFloat, | ||
438 | Variadic: true, | ||
439 | VariadicType: ast.TypeFloat, | ||
440 | Callback: func(args []interface{}) (interface{}, error) { | ||
441 | max := args[0].(float64) | ||
442 | |||
443 | for i := 1; i < len(args); i++ { | ||
444 | max = math.Max(max, args[i].(float64)) | ||
445 | } | ||
446 | |||
447 | return max, nil | ||
448 | }, | ||
449 | } | ||
450 | } | ||
451 | |||
452 | // interpolationFuncMin returns the minimum of the numeric arguments | ||
453 | func interpolationFuncMin() ast.Function { | ||
454 | return ast.Function{ | ||
455 | ArgTypes: []ast.Type{ast.TypeFloat}, | ||
456 | ReturnType: ast.TypeFloat, | ||
457 | Variadic: true, | ||
458 | VariadicType: ast.TypeFloat, | ||
459 | Callback: func(args []interface{}) (interface{}, error) { | ||
460 | min := args[0].(float64) | ||
461 | |||
462 | for i := 1; i < len(args); i++ { | ||
463 | min = math.Min(min, args[i].(float64)) | ||
464 | } | ||
465 | |||
466 | return min, nil | ||
467 | }, | ||
468 | } | ||
469 | } | ||
470 | |||
471 | // interpolationFuncPathExpand will expand any `~`'s found with the full file path | ||
472 | func interpolationFuncPathExpand() ast.Function { | ||
473 | return ast.Function{ | ||
474 | ArgTypes: []ast.Type{ast.TypeString}, | ||
475 | ReturnType: ast.TypeString, | ||
476 | Callback: func(args []interface{}) (interface{}, error) { | ||
477 | return homedir.Expand(args[0].(string)) | ||
478 | }, | ||
479 | } | ||
480 | } | ||
481 | |||
482 | // interpolationFuncCeil returns the the least integer value greater than or equal to the argument | ||
483 | func interpolationFuncCeil() ast.Function { | ||
484 | return ast.Function{ | ||
485 | ArgTypes: []ast.Type{ast.TypeFloat}, | ||
486 | ReturnType: ast.TypeInt, | ||
487 | Callback: func(args []interface{}) (interface{}, error) { | ||
488 | return int(math.Ceil(args[0].(float64))), nil | ||
489 | }, | ||
490 | } | ||
491 | } | ||
492 | |||
493 | // interpolationFuncLog returns the logarithnm. | ||
494 | func interpolationFuncLog() ast.Function { | ||
495 | return ast.Function{ | ||
496 | ArgTypes: []ast.Type{ast.TypeFloat, ast.TypeFloat}, | ||
497 | ReturnType: ast.TypeFloat, | ||
498 | Callback: func(args []interface{}) (interface{}, error) { | ||
499 | return math.Log(args[0].(float64)) / math.Log(args[1].(float64)), nil | ||
500 | }, | ||
501 | } | ||
502 | } | ||
503 | |||
504 | // interpolationFuncChomp removes trailing newlines from the given string | ||
505 | func interpolationFuncChomp() ast.Function { | ||
506 | newlines := regexp.MustCompile(`(?:\r\n?|\n)*\z`) | ||
507 | return ast.Function{ | ||
508 | ArgTypes: []ast.Type{ast.TypeString}, | ||
509 | ReturnType: ast.TypeString, | ||
510 | Callback: func(args []interface{}) (interface{}, error) { | ||
511 | return newlines.ReplaceAllString(args[0].(string), ""), nil | ||
512 | }, | ||
513 | } | ||
514 | } | ||
515 | |||
516 | // interpolationFuncFloorreturns returns the greatest integer value less than or equal to the argument | ||
517 | func interpolationFuncFloor() ast.Function { | ||
518 | return ast.Function{ | ||
519 | ArgTypes: []ast.Type{ast.TypeFloat}, | ||
520 | ReturnType: ast.TypeInt, | ||
521 | Callback: func(args []interface{}) (interface{}, error) { | ||
522 | return int(math.Floor(args[0].(float64))), nil | ||
523 | }, | ||
524 | } | ||
525 | } | ||
526 | |||
527 | func interpolationFuncZipMap() ast.Function { | ||
528 | return ast.Function{ | ||
529 | ArgTypes: []ast.Type{ | ||
530 | ast.TypeList, // Keys | ||
531 | ast.TypeList, // Values | ||
532 | }, | ||
533 | ReturnType: ast.TypeMap, | ||
534 | Callback: func(args []interface{}) (interface{}, error) { | ||
535 | keys := args[0].([]ast.Variable) | ||
536 | values := args[1].([]ast.Variable) | ||
537 | |||
538 | if len(keys) != len(values) { | ||
539 | return nil, fmt.Errorf("count of keys (%d) does not match count of values (%d)", | ||
540 | len(keys), len(values)) | ||
541 | } | ||
542 | |||
543 | for i, val := range keys { | ||
544 | if val.Type != ast.TypeString { | ||
545 | return nil, fmt.Errorf("keys must be strings. value at position %d is %s", | ||
546 | i, val.Type.Printable()) | ||
547 | } | ||
548 | } | ||
549 | |||
550 | result := map[string]ast.Variable{} | ||
551 | for i := 0; i < len(keys); i++ { | ||
552 | result[keys[i].Value.(string)] = values[i] | ||
553 | } | ||
554 | |||
555 | return result, nil | ||
556 | }, | ||
557 | } | ||
558 | } | ||
559 | |||
560 | // interpolationFuncFormatList implements the "formatlist" function that does | ||
561 | // string formatting on lists. | ||
562 | func interpolationFuncFormatList() ast.Function { | ||
563 | return ast.Function{ | ||
564 | ArgTypes: []ast.Type{ast.TypeAny}, | ||
565 | Variadic: true, | ||
566 | VariadicType: ast.TypeAny, | ||
567 | ReturnType: ast.TypeList, | ||
568 | Callback: func(args []interface{}) (interface{}, error) { | ||
569 | // Make a copy of the variadic part of args | ||
570 | // to avoid modifying the original. | ||
571 | varargs := make([]interface{}, len(args)-1) | ||
572 | copy(varargs, args[1:]) | ||
573 | |||
574 | // Verify we have some arguments | ||
575 | if len(varargs) == 0 { | ||
576 | return nil, fmt.Errorf("no arguments to formatlist") | ||
577 | } | ||
578 | |||
579 | // Convert arguments that are lists into slices. | ||
580 | // Confirm along the way that all lists have the same length (n). | ||
581 | var n int | ||
582 | listSeen := false | ||
583 | for i := 1; i < len(args); i++ { | ||
584 | s, ok := args[i].([]ast.Variable) | ||
585 | if !ok { | ||
586 | continue | ||
587 | } | ||
588 | |||
589 | // Mark that we've seen at least one list | ||
590 | listSeen = true | ||
591 | |||
592 | // Convert the ast.Variable to a slice of strings | ||
593 | parts, err := listVariableValueToStringSlice(s) | ||
594 | if err != nil { | ||
595 | return nil, err | ||
596 | } | ||
597 | |||
598 | // otherwise the list is sent down to be indexed | ||
599 | varargs[i-1] = parts | ||
600 | |||
601 | // Check length | ||
602 | if n == 0 { | ||
603 | // first list we've seen | ||
604 | n = len(parts) | ||
605 | continue | ||
606 | } | ||
607 | if n != len(parts) { | ||
608 | return nil, fmt.Errorf("format: mismatched list lengths: %d != %d", n, len(parts)) | ||
609 | } | ||
610 | } | ||
611 | |||
612 | // If we didn't see a list this is an error because we | ||
613 | // can't determine the return value length. | ||
614 | if !listSeen { | ||
615 | return nil, fmt.Errorf( | ||
616 | "formatlist requires at least one list argument") | ||
617 | } | ||
618 | |||
619 | // Do the formatting. | ||
620 | format := args[0].(string) | ||
621 | |||
622 | // Generate a list of formatted strings. | ||
623 | list := make([]string, n) | ||
624 | fmtargs := make([]interface{}, len(varargs)) | ||
625 | for i := 0; i < n; i++ { | ||
626 | for j, arg := range varargs { | ||
627 | switch arg := arg.(type) { | ||
628 | default: | ||
629 | fmtargs[j] = arg | ||
630 | case []string: | ||
631 | fmtargs[j] = arg[i] | ||
632 | } | ||
633 | } | ||
634 | list[i] = fmt.Sprintf(format, fmtargs...) | ||
635 | } | ||
636 | return stringSliceToVariableValue(list), nil | ||
637 | }, | ||
638 | } | ||
639 | } | ||
640 | |||
641 | // interpolationFuncIndex implements the "index" function that allows one to | ||
642 | // find the index of a specific element in a list | ||
643 | func interpolationFuncIndex() ast.Function { | ||
644 | return ast.Function{ | ||
645 | ArgTypes: []ast.Type{ast.TypeList, ast.TypeString}, | ||
646 | ReturnType: ast.TypeInt, | ||
647 | Callback: func(args []interface{}) (interface{}, error) { | ||
648 | haystack := args[0].([]ast.Variable) | ||
649 | needle := args[1].(string) | ||
650 | for index, element := range haystack { | ||
651 | if needle == element.Value { | ||
652 | return index, nil | ||
653 | } | ||
654 | } | ||
655 | return nil, fmt.Errorf("Could not find '%s' in '%s'", needle, haystack) | ||
656 | }, | ||
657 | } | ||
658 | } | ||
659 | |||
660 | // interpolationFuncBasename implements the "dirname" function. | ||
661 | func interpolationFuncDirname() ast.Function { | ||
662 | return ast.Function{ | ||
663 | ArgTypes: []ast.Type{ast.TypeString}, | ||
664 | ReturnType: ast.TypeString, | ||
665 | Callback: func(args []interface{}) (interface{}, error) { | ||
666 | return filepath.Dir(args[0].(string)), nil | ||
667 | }, | ||
668 | } | ||
669 | } | ||
670 | |||
671 | // interpolationFuncDistinct implements the "distinct" function that | ||
672 | // removes duplicate elements from a list. | ||
673 | func interpolationFuncDistinct() ast.Function { | ||
674 | return ast.Function{ | ||
675 | ArgTypes: []ast.Type{ast.TypeList}, | ||
676 | ReturnType: ast.TypeList, | ||
677 | Variadic: true, | ||
678 | VariadicType: ast.TypeList, | ||
679 | Callback: func(args []interface{}) (interface{}, error) { | ||
680 | var list []string | ||
681 | |||
682 | if len(args) != 1 { | ||
683 | return nil, fmt.Errorf("accepts only one argument.") | ||
684 | } | ||
685 | |||
686 | if argument, ok := args[0].([]ast.Variable); ok { | ||
687 | for _, element := range argument { | ||
688 | if element.Type != ast.TypeString { | ||
689 | return nil, fmt.Errorf( | ||
690 | "only works for flat lists, this list contains elements of %s", | ||
691 | element.Type.Printable()) | ||
692 | } | ||
693 | list = appendIfMissing(list, element.Value.(string)) | ||
694 | } | ||
695 | } | ||
696 | |||
697 | return stringSliceToVariableValue(list), nil | ||
698 | }, | ||
699 | } | ||
700 | } | ||
701 | |||
702 | // helper function to add an element to a list, if it does not already exsit | ||
703 | func appendIfMissing(slice []string, element string) []string { | ||
704 | for _, ele := range slice { | ||
705 | if ele == element { | ||
706 | return slice | ||
707 | } | ||
708 | } | ||
709 | return append(slice, element) | ||
710 | } | ||
711 | |||
712 | // for two lists `keys` and `values` of equal length, returns all elements | ||
713 | // from `values` where the corresponding element from `keys` is in `searchset`. | ||
714 | func interpolationFuncMatchKeys() ast.Function { | ||
715 | return ast.Function{ | ||
716 | ArgTypes: []ast.Type{ast.TypeList, ast.TypeList, ast.TypeList}, | ||
717 | ReturnType: ast.TypeList, | ||
718 | Callback: func(args []interface{}) (interface{}, error) { | ||
719 | output := make([]ast.Variable, 0) | ||
720 | |||
721 | values, _ := args[0].([]ast.Variable) | ||
722 | keys, _ := args[1].([]ast.Variable) | ||
723 | searchset, _ := args[2].([]ast.Variable) | ||
724 | |||
725 | if len(keys) != len(values) { | ||
726 | return nil, fmt.Errorf("length of keys and values should be equal") | ||
727 | } | ||
728 | |||
729 | for i, key := range keys { | ||
730 | for _, search := range searchset { | ||
731 | if res, err := compareSimpleVariables(key, search); err != nil { | ||
732 | return nil, err | ||
733 | } else if res == true { | ||
734 | output = append(output, values[i]) | ||
735 | break | ||
736 | } | ||
737 | } | ||
738 | } | ||
739 | // if searchset is empty, then output is an empty list as well. | ||
740 | // if we haven't matched any key, then output is an empty list. | ||
741 | return output, nil | ||
742 | }, | ||
743 | } | ||
744 | } | ||
745 | |||
746 | // compare two variables of the same type, i.e. non complex one, such as TypeList or TypeMap | ||
747 | func compareSimpleVariables(a, b ast.Variable) (bool, error) { | ||
748 | if a.Type != b.Type { | ||
749 | return false, fmt.Errorf( | ||
750 | "won't compare items of different types %s and %s", | ||
751 | a.Type.Printable(), b.Type.Printable()) | ||
752 | } | ||
753 | switch a.Type { | ||
754 | case ast.TypeString: | ||
755 | return a.Value.(string) == b.Value.(string), nil | ||
756 | default: | ||
757 | return false, fmt.Errorf( | ||
758 | "can't compare items of type %s", | ||
759 | a.Type.Printable()) | ||
760 | } | ||
761 | } | ||
762 | |||
763 | // interpolationFuncJoin implements the "join" function that allows | ||
764 | // multi-variable values to be joined by some character. | ||
765 | func interpolationFuncJoin() ast.Function { | ||
766 | return ast.Function{ | ||
767 | ArgTypes: []ast.Type{ast.TypeString}, | ||
768 | Variadic: true, | ||
769 | VariadicType: ast.TypeList, | ||
770 | ReturnType: ast.TypeString, | ||
771 | Callback: func(args []interface{}) (interface{}, error) { | ||
772 | var list []string | ||
773 | |||
774 | if len(args) < 2 { | ||
775 | return nil, fmt.Errorf("not enough arguments to join()") | ||
776 | } | ||
777 | |||
778 | for _, arg := range args[1:] { | ||
779 | for _, part := range arg.([]ast.Variable) { | ||
780 | if part.Type != ast.TypeString { | ||
781 | return nil, fmt.Errorf( | ||
782 | "only works on flat lists, this list contains elements of %s", | ||
783 | part.Type.Printable()) | ||
784 | } | ||
785 | list = append(list, part.Value.(string)) | ||
786 | } | ||
787 | } | ||
788 | |||
789 | return strings.Join(list, args[0].(string)), nil | ||
790 | }, | ||
791 | } | ||
792 | } | ||
793 | |||
794 | // interpolationFuncJSONEncode implements the "jsonencode" function that encodes | ||
795 | // a string, list, or map as its JSON representation. For now, values in the | ||
796 | // list or map may only be strings. | ||
797 | func interpolationFuncJSONEncode() ast.Function { | ||
798 | return ast.Function{ | ||
799 | ArgTypes: []ast.Type{ast.TypeAny}, | ||
800 | ReturnType: ast.TypeString, | ||
801 | Callback: func(args []interface{}) (interface{}, error) { | ||
802 | var toEncode interface{} | ||
803 | |||
804 | switch typedArg := args[0].(type) { | ||
805 | case string: | ||
806 | toEncode = typedArg | ||
807 | |||
808 | case []ast.Variable: | ||
809 | // We preallocate the list here. Note that it's important that in | ||
810 | // the length 0 case, we have an empty list rather than nil, as | ||
811 | // they encode differently. | ||
812 | // XXX It would be nice to support arbitrarily nested data here. Is | ||
813 | // there an inverse of hil.InterfaceToVariable? | ||
814 | strings := make([]string, len(typedArg)) | ||
815 | |||
816 | for i, v := range typedArg { | ||
817 | if v.Type != ast.TypeString { | ||
818 | return "", fmt.Errorf("list elements must be strings") | ||
819 | } | ||
820 | strings[i] = v.Value.(string) | ||
821 | } | ||
822 | toEncode = strings | ||
823 | |||
824 | case map[string]ast.Variable: | ||
825 | // XXX It would be nice to support arbitrarily nested data here. Is | ||
826 | // there an inverse of hil.InterfaceToVariable? | ||
827 | stringMap := make(map[string]string) | ||
828 | for k, v := range typedArg { | ||
829 | if v.Type != ast.TypeString { | ||
830 | return "", fmt.Errorf("map values must be strings") | ||
831 | } | ||
832 | stringMap[k] = v.Value.(string) | ||
833 | } | ||
834 | toEncode = stringMap | ||
835 | |||
836 | default: | ||
837 | return "", fmt.Errorf("unknown type for JSON encoding: %T", args[0]) | ||
838 | } | ||
839 | |||
840 | jEnc, err := json.Marshal(toEncode) | ||
841 | if err != nil { | ||
842 | return "", fmt.Errorf("failed to encode JSON data '%s'", toEncode) | ||
843 | } | ||
844 | return string(jEnc), nil | ||
845 | }, | ||
846 | } | ||
847 | } | ||
848 | |||
849 | // interpolationFuncReplace implements the "replace" function that does | ||
850 | // string replacement. | ||
851 | func interpolationFuncReplace() ast.Function { | ||
852 | return ast.Function{ | ||
853 | ArgTypes: []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString}, | ||
854 | ReturnType: ast.TypeString, | ||
855 | Callback: func(args []interface{}) (interface{}, error) { | ||
856 | s := args[0].(string) | ||
857 | search := args[1].(string) | ||
858 | replace := args[2].(string) | ||
859 | |||
860 | // We search/replace using a regexp if the string is surrounded | ||
861 | // in forward slashes. | ||
862 | if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' { | ||
863 | re, err := regexp.Compile(search[1 : len(search)-1]) | ||
864 | if err != nil { | ||
865 | return nil, err | ||
866 | } | ||
867 | |||
868 | return re.ReplaceAllString(s, replace), nil | ||
869 | } | ||
870 | |||
871 | return strings.Replace(s, search, replace, -1), nil | ||
872 | }, | ||
873 | } | ||
874 | } | ||
875 | |||
876 | func interpolationFuncLength() ast.Function { | ||
877 | return ast.Function{ | ||
878 | ArgTypes: []ast.Type{ast.TypeAny}, | ||
879 | ReturnType: ast.TypeInt, | ||
880 | Variadic: false, | ||
881 | Callback: func(args []interface{}) (interface{}, error) { | ||
882 | subject := args[0] | ||
883 | |||
884 | switch typedSubject := subject.(type) { | ||
885 | case string: | ||
886 | return len(typedSubject), nil | ||
887 | case []ast.Variable: | ||
888 | return len(typedSubject), nil | ||
889 | case map[string]ast.Variable: | ||
890 | return len(typedSubject), nil | ||
891 | } | ||
892 | |||
893 | return 0, fmt.Errorf("arguments to length() must be a string, list, or map") | ||
894 | }, | ||
895 | } | ||
896 | } | ||
897 | |||
898 | func interpolationFuncSignum() ast.Function { | ||
899 | return ast.Function{ | ||
900 | ArgTypes: []ast.Type{ast.TypeInt}, | ||
901 | ReturnType: ast.TypeInt, | ||
902 | Variadic: false, | ||
903 | Callback: func(args []interface{}) (interface{}, error) { | ||
904 | num := args[0].(int) | ||
905 | switch { | ||
906 | case num < 0: | ||
907 | return -1, nil | ||
908 | case num > 0: | ||
909 | return +1, nil | ||
910 | default: | ||
911 | return 0, nil | ||
912 | } | ||
913 | }, | ||
914 | } | ||
915 | } | ||
916 | |||
917 | // interpolationFuncSlice returns a portion of the input list between from, inclusive and to, exclusive. | ||
918 | func interpolationFuncSlice() ast.Function { | ||
919 | return ast.Function{ | ||
920 | ArgTypes: []ast.Type{ | ||
921 | ast.TypeList, // inputList | ||
922 | ast.TypeInt, // from | ||
923 | ast.TypeInt, // to | ||
924 | }, | ||
925 | ReturnType: ast.TypeList, | ||
926 | Variadic: false, | ||
927 | Callback: func(args []interface{}) (interface{}, error) { | ||
928 | inputList := args[0].([]ast.Variable) | ||
929 | from := args[1].(int) | ||
930 | to := args[2].(int) | ||
931 | |||
932 | if from < 0 { | ||
933 | return nil, fmt.Errorf("from index must be >= 0") | ||
934 | } | ||
935 | if to > len(inputList) { | ||
936 | return nil, fmt.Errorf("to index must be <= length of the input list") | ||
937 | } | ||
938 | if from > to { | ||
939 | return nil, fmt.Errorf("from index must be <= to index") | ||
940 | } | ||
941 | |||
942 | var outputList []ast.Variable | ||
943 | for i, val := range inputList { | ||
944 | if i >= from && i < to { | ||
945 | outputList = append(outputList, val) | ||
946 | } | ||
947 | } | ||
948 | return outputList, nil | ||
949 | }, | ||
950 | } | ||
951 | } | ||
952 | |||
953 | // interpolationFuncSort sorts a list of a strings lexographically | ||
954 | func interpolationFuncSort() ast.Function { | ||
955 | return ast.Function{ | ||
956 | ArgTypes: []ast.Type{ast.TypeList}, | ||
957 | ReturnType: ast.TypeList, | ||
958 | Variadic: false, | ||
959 | Callback: func(args []interface{}) (interface{}, error) { | ||
960 | inputList := args[0].([]ast.Variable) | ||
961 | |||
962 | // Ensure that all the list members are strings and | ||
963 | // create a string slice from them | ||
964 | members := make([]string, len(inputList)) | ||
965 | for i, val := range inputList { | ||
966 | if val.Type != ast.TypeString { | ||
967 | return nil, fmt.Errorf( | ||
968 | "sort() may only be used with lists of strings - %s at index %d", | ||
969 | val.Type.String(), i) | ||
970 | } | ||
971 | |||
972 | members[i] = val.Value.(string) | ||
973 | } | ||
974 | |||
975 | sort.Strings(members) | ||
976 | return stringSliceToVariableValue(members), nil | ||
977 | }, | ||
978 | } | ||
979 | } | ||
980 | |||
981 | // interpolationFuncSplit implements the "split" function that allows | ||
982 | // strings to split into multi-variable values | ||
983 | func interpolationFuncSplit() ast.Function { | ||
984 | return ast.Function{ | ||
985 | ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, | ||
986 | ReturnType: ast.TypeList, | ||
987 | Callback: func(args []interface{}) (interface{}, error) { | ||
988 | sep := args[0].(string) | ||
989 | s := args[1].(string) | ||
990 | elements := strings.Split(s, sep) | ||
991 | return stringSliceToVariableValue(elements), nil | ||
992 | }, | ||
993 | } | ||
994 | } | ||
995 | |||
996 | // interpolationFuncLookup implements the "lookup" function that allows | ||
997 | // dynamic lookups of map types within a Terraform configuration. | ||
998 | func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function { | ||
999 | return ast.Function{ | ||
1000 | ArgTypes: []ast.Type{ast.TypeMap, ast.TypeString}, | ||
1001 | ReturnType: ast.TypeString, | ||
1002 | Variadic: true, | ||
1003 | VariadicType: ast.TypeString, | ||
1004 | Callback: func(args []interface{}) (interface{}, error) { | ||
1005 | defaultValue := "" | ||
1006 | defaultValueSet := false | ||
1007 | if len(args) > 2 { | ||
1008 | defaultValue = args[2].(string) | ||
1009 | defaultValueSet = true | ||
1010 | } | ||
1011 | if len(args) > 3 { | ||
1012 | return "", fmt.Errorf("lookup() takes no more than three arguments") | ||
1013 | } | ||
1014 | index := args[1].(string) | ||
1015 | mapVar := args[0].(map[string]ast.Variable) | ||
1016 | |||
1017 | v, ok := mapVar[index] | ||
1018 | if !ok { | ||
1019 | if defaultValueSet { | ||
1020 | return defaultValue, nil | ||
1021 | } else { | ||
1022 | return "", fmt.Errorf( | ||
1023 | "lookup failed to find '%s'", | ||
1024 | args[1].(string)) | ||
1025 | } | ||
1026 | } | ||
1027 | if v.Type != ast.TypeString { | ||
1028 | return nil, fmt.Errorf( | ||
1029 | "lookup() may only be used with flat maps, this map contains elements of %s", | ||
1030 | v.Type.Printable()) | ||
1031 | } | ||
1032 | |||
1033 | return v.Value.(string), nil | ||
1034 | }, | ||
1035 | } | ||
1036 | } | ||
1037 | |||
1038 | // interpolationFuncElement implements the "element" function that allows | ||
1039 | // a specific index to be looked up in a multi-variable value. Note that this will | ||
1040 | // wrap if the index is larger than the number of elements in the multi-variable value. | ||
1041 | func interpolationFuncElement() ast.Function { | ||
1042 | return ast.Function{ | ||
1043 | ArgTypes: []ast.Type{ast.TypeList, ast.TypeString}, | ||
1044 | ReturnType: ast.TypeString, | ||
1045 | Callback: func(args []interface{}) (interface{}, error) { | ||
1046 | list := args[0].([]ast.Variable) | ||
1047 | if len(list) == 0 { | ||
1048 | return nil, fmt.Errorf("element() may not be used with an empty list") | ||
1049 | } | ||
1050 | |||
1051 | index, err := strconv.Atoi(args[1].(string)) | ||
1052 | if err != nil || index < 0 { | ||
1053 | return "", fmt.Errorf( | ||
1054 | "invalid number for index, got %s", args[1]) | ||
1055 | } | ||
1056 | |||
1057 | resolvedIndex := index % len(list) | ||
1058 | |||
1059 | v := list[resolvedIndex] | ||
1060 | if v.Type != ast.TypeString { | ||
1061 | return nil, fmt.Errorf( | ||
1062 | "element() may only be used with flat lists, this list contains elements of %s", | ||
1063 | v.Type.Printable()) | ||
1064 | } | ||
1065 | return v.Value, nil | ||
1066 | }, | ||
1067 | } | ||
1068 | } | ||
1069 | |||
1070 | // interpolationFuncKeys implements the "keys" function that yields a list of | ||
1071 | // keys of map types within a Terraform configuration. | ||
1072 | func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function { | ||
1073 | return ast.Function{ | ||
1074 | ArgTypes: []ast.Type{ast.TypeMap}, | ||
1075 | ReturnType: ast.TypeList, | ||
1076 | Callback: func(args []interface{}) (interface{}, error) { | ||
1077 | mapVar := args[0].(map[string]ast.Variable) | ||
1078 | keys := make([]string, 0) | ||
1079 | |||
1080 | for k, _ := range mapVar { | ||
1081 | keys = append(keys, k) | ||
1082 | } | ||
1083 | |||
1084 | sort.Strings(keys) | ||
1085 | |||
1086 | // Keys are guaranteed to be strings | ||
1087 | return stringSliceToVariableValue(keys), nil | ||
1088 | }, | ||
1089 | } | ||
1090 | } | ||
1091 | |||
1092 | // interpolationFuncValues implements the "values" function that yields a list of | ||
1093 | // keys of map types within a Terraform configuration. | ||
1094 | func interpolationFuncValues(vs map[string]ast.Variable) ast.Function { | ||
1095 | return ast.Function{ | ||
1096 | ArgTypes: []ast.Type{ast.TypeMap}, | ||
1097 | ReturnType: ast.TypeList, | ||
1098 | Callback: func(args []interface{}) (interface{}, error) { | ||
1099 | mapVar := args[0].(map[string]ast.Variable) | ||
1100 | keys := make([]string, 0) | ||
1101 | |||
1102 | for k, _ := range mapVar { | ||
1103 | keys = append(keys, k) | ||
1104 | } | ||
1105 | |||
1106 | sort.Strings(keys) | ||
1107 | |||
1108 | values := make([]string, len(keys)) | ||
1109 | for index, key := range keys { | ||
1110 | if value, ok := mapVar[key].Value.(string); ok { | ||
1111 | values[index] = value | ||
1112 | } else { | ||
1113 | return "", fmt.Errorf("values(): %q has element with bad type %s", | ||
1114 | key, mapVar[key].Type) | ||
1115 | } | ||
1116 | } | ||
1117 | |||
1118 | variable, err := hil.InterfaceToVariable(values) | ||
1119 | if err != nil { | ||
1120 | return nil, err | ||
1121 | } | ||
1122 | |||
1123 | return variable.Value, nil | ||
1124 | }, | ||
1125 | } | ||
1126 | } | ||
1127 | |||
1128 | // interpolationFuncBasename implements the "basename" function. | ||
1129 | func interpolationFuncBasename() ast.Function { | ||
1130 | return ast.Function{ | ||
1131 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1132 | ReturnType: ast.TypeString, | ||
1133 | Callback: func(args []interface{}) (interface{}, error) { | ||
1134 | return filepath.Base(args[0].(string)), nil | ||
1135 | }, | ||
1136 | } | ||
1137 | } | ||
1138 | |||
1139 | // interpolationFuncBase64Encode implements the "base64encode" function that | ||
1140 | // allows Base64 encoding. | ||
1141 | func interpolationFuncBase64Encode() ast.Function { | ||
1142 | return ast.Function{ | ||
1143 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1144 | ReturnType: ast.TypeString, | ||
1145 | Callback: func(args []interface{}) (interface{}, error) { | ||
1146 | s := args[0].(string) | ||
1147 | return base64.StdEncoding.EncodeToString([]byte(s)), nil | ||
1148 | }, | ||
1149 | } | ||
1150 | } | ||
1151 | |||
1152 | // interpolationFuncBase64Decode implements the "base64decode" function that | ||
1153 | // allows Base64 decoding. | ||
1154 | func interpolationFuncBase64Decode() ast.Function { | ||
1155 | return ast.Function{ | ||
1156 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1157 | ReturnType: ast.TypeString, | ||
1158 | Callback: func(args []interface{}) (interface{}, error) { | ||
1159 | s := args[0].(string) | ||
1160 | sDec, err := base64.StdEncoding.DecodeString(s) | ||
1161 | if err != nil { | ||
1162 | return "", fmt.Errorf("failed to decode base64 data '%s'", s) | ||
1163 | } | ||
1164 | return string(sDec), nil | ||
1165 | }, | ||
1166 | } | ||
1167 | } | ||
1168 | |||
1169 | // interpolationFuncLower implements the "lower" function that does | ||
1170 | // string lower casing. | ||
1171 | func interpolationFuncLower() ast.Function { | ||
1172 | return ast.Function{ | ||
1173 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1174 | ReturnType: ast.TypeString, | ||
1175 | Callback: func(args []interface{}) (interface{}, error) { | ||
1176 | toLower := args[0].(string) | ||
1177 | return strings.ToLower(toLower), nil | ||
1178 | }, | ||
1179 | } | ||
1180 | } | ||
1181 | |||
1182 | func interpolationFuncMd5() ast.Function { | ||
1183 | return ast.Function{ | ||
1184 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1185 | ReturnType: ast.TypeString, | ||
1186 | Callback: func(args []interface{}) (interface{}, error) { | ||
1187 | s := args[0].(string) | ||
1188 | h := md5.New() | ||
1189 | h.Write([]byte(s)) | ||
1190 | hash := hex.EncodeToString(h.Sum(nil)) | ||
1191 | return hash, nil | ||
1192 | }, | ||
1193 | } | ||
1194 | } | ||
1195 | |||
1196 | func interpolationFuncMerge() ast.Function { | ||
1197 | return ast.Function{ | ||
1198 | ArgTypes: []ast.Type{ast.TypeMap}, | ||
1199 | ReturnType: ast.TypeMap, | ||
1200 | Variadic: true, | ||
1201 | VariadicType: ast.TypeMap, | ||
1202 | Callback: func(args []interface{}) (interface{}, error) { | ||
1203 | outputMap := make(map[string]ast.Variable) | ||
1204 | |||
1205 | for _, arg := range args { | ||
1206 | for k, v := range arg.(map[string]ast.Variable) { | ||
1207 | outputMap[k] = v | ||
1208 | } | ||
1209 | } | ||
1210 | |||
1211 | return outputMap, nil | ||
1212 | }, | ||
1213 | } | ||
1214 | } | ||
1215 | |||
1216 | // interpolationFuncUpper implements the "upper" function that does | ||
1217 | // string upper casing. | ||
1218 | func interpolationFuncUpper() ast.Function { | ||
1219 | return ast.Function{ | ||
1220 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1221 | ReturnType: ast.TypeString, | ||
1222 | Callback: func(args []interface{}) (interface{}, error) { | ||
1223 | toUpper := args[0].(string) | ||
1224 | return strings.ToUpper(toUpper), nil | ||
1225 | }, | ||
1226 | } | ||
1227 | } | ||
1228 | |||
1229 | func interpolationFuncSha1() ast.Function { | ||
1230 | return ast.Function{ | ||
1231 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1232 | ReturnType: ast.TypeString, | ||
1233 | Callback: func(args []interface{}) (interface{}, error) { | ||
1234 | s := args[0].(string) | ||
1235 | h := sha1.New() | ||
1236 | h.Write([]byte(s)) | ||
1237 | hash := hex.EncodeToString(h.Sum(nil)) | ||
1238 | return hash, nil | ||
1239 | }, | ||
1240 | } | ||
1241 | } | ||
1242 | |||
1243 | // hexadecimal representation of sha256 sum | ||
1244 | func interpolationFuncSha256() ast.Function { | ||
1245 | return ast.Function{ | ||
1246 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1247 | ReturnType: ast.TypeString, | ||
1248 | Callback: func(args []interface{}) (interface{}, error) { | ||
1249 | s := args[0].(string) | ||
1250 | h := sha256.New() | ||
1251 | h.Write([]byte(s)) | ||
1252 | hash := hex.EncodeToString(h.Sum(nil)) | ||
1253 | return hash, nil | ||
1254 | }, | ||
1255 | } | ||
1256 | } | ||
1257 | |||
1258 | func interpolationFuncSha512() ast.Function { | ||
1259 | return ast.Function{ | ||
1260 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1261 | ReturnType: ast.TypeString, | ||
1262 | Callback: func(args []interface{}) (interface{}, error) { | ||
1263 | s := args[0].(string) | ||
1264 | h := sha512.New() | ||
1265 | h.Write([]byte(s)) | ||
1266 | hash := hex.EncodeToString(h.Sum(nil)) | ||
1267 | return hash, nil | ||
1268 | }, | ||
1269 | } | ||
1270 | } | ||
1271 | |||
1272 | func interpolationFuncTrimSpace() ast.Function { | ||
1273 | return ast.Function{ | ||
1274 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1275 | ReturnType: ast.TypeString, | ||
1276 | Callback: func(args []interface{}) (interface{}, error) { | ||
1277 | trimSpace := args[0].(string) | ||
1278 | return strings.TrimSpace(trimSpace), nil | ||
1279 | }, | ||
1280 | } | ||
1281 | } | ||
1282 | |||
1283 | func interpolationFuncBase64Sha256() ast.Function { | ||
1284 | return ast.Function{ | ||
1285 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1286 | ReturnType: ast.TypeString, | ||
1287 | Callback: func(args []interface{}) (interface{}, error) { | ||
1288 | s := args[0].(string) | ||
1289 | h := sha256.New() | ||
1290 | h.Write([]byte(s)) | ||
1291 | shaSum := h.Sum(nil) | ||
1292 | encoded := base64.StdEncoding.EncodeToString(shaSum[:]) | ||
1293 | return encoded, nil | ||
1294 | }, | ||
1295 | } | ||
1296 | } | ||
1297 | |||
1298 | func interpolationFuncBase64Sha512() ast.Function { | ||
1299 | return ast.Function{ | ||
1300 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1301 | ReturnType: ast.TypeString, | ||
1302 | Callback: func(args []interface{}) (interface{}, error) { | ||
1303 | s := args[0].(string) | ||
1304 | h := sha512.New() | ||
1305 | h.Write([]byte(s)) | ||
1306 | shaSum := h.Sum(nil) | ||
1307 | encoded := base64.StdEncoding.EncodeToString(shaSum[:]) | ||
1308 | return encoded, nil | ||
1309 | }, | ||
1310 | } | ||
1311 | } | ||
1312 | |||
1313 | func interpolationFuncUUID() ast.Function { | ||
1314 | return ast.Function{ | ||
1315 | ArgTypes: []ast.Type{}, | ||
1316 | ReturnType: ast.TypeString, | ||
1317 | Callback: func(args []interface{}) (interface{}, error) { | ||
1318 | return uuid.GenerateUUID() | ||
1319 | }, | ||
1320 | } | ||
1321 | } | ||
1322 | |||
1323 | // interpolationFuncTimestamp | ||
1324 | func interpolationFuncTimestamp() ast.Function { | ||
1325 | return ast.Function{ | ||
1326 | ArgTypes: []ast.Type{}, | ||
1327 | ReturnType: ast.TypeString, | ||
1328 | Callback: func(args []interface{}) (interface{}, error) { | ||
1329 | return time.Now().UTC().Format(time.RFC3339), nil | ||
1330 | }, | ||
1331 | } | ||
1332 | } | ||
1333 | |||
1334 | // interpolationFuncTitle implements the "title" function that returns a copy of the | ||
1335 | // string in which first characters of all the words are capitalized. | ||
1336 | func interpolationFuncTitle() ast.Function { | ||
1337 | return ast.Function{ | ||
1338 | ArgTypes: []ast.Type{ast.TypeString}, | ||
1339 | ReturnType: ast.TypeString, | ||
1340 | Callback: func(args []interface{}) (interface{}, error) { | ||
1341 | toTitle := args[0].(string) | ||
1342 | return strings.Title(toTitle), nil | ||
1343 | }, | ||
1344 | } | ||
1345 | } | ||
1346 | |||
1347 | // interpolationFuncSubstr implements the "substr" function that allows strings | ||
1348 | // to be truncated. | ||
1349 | func interpolationFuncSubstr() ast.Function { | ||
1350 | return ast.Function{ | ||
1351 | ArgTypes: []ast.Type{ | ||
1352 | ast.TypeString, // input string | ||
1353 | ast.TypeInt, // offset | ||
1354 | ast.TypeInt, // length | ||
1355 | }, | ||
1356 | ReturnType: ast.TypeString, | ||
1357 | Callback: func(args []interface{}) (interface{}, error) { | ||
1358 | str := args[0].(string) | ||
1359 | offset := args[1].(int) | ||
1360 | length := args[2].(int) | ||
1361 | |||
1362 | // Interpret a negative offset as being equivalent to a positive | ||
1363 | // offset taken from the end of the string. | ||
1364 | if offset < 0 { | ||
1365 | offset += len(str) | ||
1366 | } | ||
1367 | |||
1368 | // Interpret a length of `-1` as indicating that the substring | ||
1369 | // should start at `offset` and continue until the end of the | ||
1370 | // string. Any other negative length (other than `-1`) is invalid. | ||
1371 | if length == -1 { | ||
1372 | length = len(str) | ||
1373 | } else if length >= 0 { | ||
1374 | length += offset | ||
1375 | } else { | ||
1376 | return nil, fmt.Errorf("length should be a non-negative integer") | ||
1377 | } | ||
1378 | |||
1379 | if offset > len(str) { | ||
1380 | return nil, fmt.Errorf("offset cannot be larger than the length of the string") | ||
1381 | } | ||
1382 | |||
1383 | if length > len(str) { | ||
1384 | return nil, fmt.Errorf("'offset + length' cannot be larger than the length of the string") | ||
1385 | } | ||
1386 | |||
1387 | return str[offset:length], nil | ||
1388 | }, | ||
1389 | } | ||
1390 | } | ||