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