]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
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 | } |