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