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