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