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