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