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