github.com/miquella/terraform@v0.6.17-0.20160517195040-40db82f25ec0/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 "errors" 11 "fmt" 12 "io/ioutil" 13 "net" 14 "regexp" 15 "sort" 16 "strconv" 17 "strings" 18 19 "github.com/apparentlymart/go-cidr/cidr" 20 "github.com/hashicorp/go-uuid" 21 "github.com/hashicorp/hil" 22 "github.com/hashicorp/hil/ast" 23 "github.com/mitchellh/go-homedir" 24 ) 25 26 // stringSliceToVariableValue converts a string slice into the value 27 // required to be returned from interpolation functions which return 28 // TypeList. 29 func stringSliceToVariableValue(values []string) []ast.Variable { 30 output := make([]ast.Variable, len(values)) 31 for index, value := range values { 32 output[index] = ast.Variable{ 33 Type: ast.TypeString, 34 Value: value, 35 } 36 } 37 return output 38 } 39 40 func listVariableValueToStringSlice(values []ast.Variable) ([]string, error) { 41 output := make([]string, len(values)) 42 for index, value := range values { 43 if value.Type != ast.TypeString { 44 return []string{}, fmt.Errorf("list has non-string element (%T)", value.Type.String()) 45 } 46 output[index] = value.Value.(string) 47 } 48 return output, nil 49 } 50 51 // Funcs is the mapping of built-in functions for configuration. 52 func Funcs() map[string]ast.Function { 53 return map[string]ast.Function{ 54 "base64decode": interpolationFuncBase64Decode(), 55 "base64encode": interpolationFuncBase64Encode(), 56 "base64sha256": interpolationFuncBase64Sha256(), 57 "cidrhost": interpolationFuncCidrHost(), 58 "cidrnetmask": interpolationFuncCidrNetmask(), 59 "cidrsubnet": interpolationFuncCidrSubnet(), 60 "coalesce": interpolationFuncCoalesce(), 61 "compact": interpolationFuncCompact(), 62 "concat": interpolationFuncConcat(), 63 "element": interpolationFuncElement(), 64 "file": interpolationFuncFile(), 65 "format": interpolationFuncFormat(), 66 "formatlist": interpolationFuncFormatList(), 67 "index": interpolationFuncIndex(), 68 "join": interpolationFuncJoin(), 69 "jsonencode": interpolationFuncJSONEncode(), 70 "length": interpolationFuncLength(), 71 "lower": interpolationFuncLower(), 72 "md5": interpolationFuncMd5(), 73 "uuid": interpolationFuncUUID(), 74 "replace": interpolationFuncReplace(), 75 "sha1": interpolationFuncSha1(), 76 "sha256": interpolationFuncSha256(), 77 "signum": interpolationFuncSignum(), 78 "split": interpolationFuncSplit(), 79 "trimspace": interpolationFuncTrimSpace(), 80 "upper": interpolationFuncUpper(), 81 } 82 } 83 84 // interpolationFuncCompact strips a list of multi-variable values 85 // (e.g. as returned by "split") of any empty strings. 86 func interpolationFuncCompact() ast.Function { 87 return ast.Function{ 88 ArgTypes: []ast.Type{ast.TypeList}, 89 ReturnType: ast.TypeList, 90 Variadic: false, 91 Callback: func(args []interface{}) (interface{}, error) { 92 inputList := args[0].([]ast.Variable) 93 94 var outputList []string 95 for _, val := range inputList { 96 if strVal, ok := val.Value.(string); ok { 97 if strVal == "" { 98 continue 99 } 100 101 outputList = append(outputList, strVal) 102 } 103 } 104 return stringSliceToVariableValue(outputList), nil 105 }, 106 } 107 } 108 109 // interpolationFuncCidrHost implements the "cidrhost" function that 110 // fills in the host part of a CIDR range address to create a single 111 // host address 112 func interpolationFuncCidrHost() ast.Function { 113 return ast.Function{ 114 ArgTypes: []ast.Type{ 115 ast.TypeString, // starting CIDR mask 116 ast.TypeInt, // host number to insert 117 }, 118 ReturnType: ast.TypeString, 119 Variadic: false, 120 Callback: func(args []interface{}) (interface{}, error) { 121 hostNum := args[1].(int) 122 _, network, err := net.ParseCIDR(args[0].(string)) 123 if err != nil { 124 return nil, fmt.Errorf("invalid CIDR expression: %s", err) 125 } 126 127 ip, err := cidr.Host(network, hostNum) 128 if err != nil { 129 return nil, err 130 } 131 132 return ip.String(), nil 133 }, 134 } 135 } 136 137 // interpolationFuncCidrNetmask implements the "cidrnetmask" function 138 // that returns the subnet mask in IP address notation. 139 func interpolationFuncCidrNetmask() ast.Function { 140 return ast.Function{ 141 ArgTypes: []ast.Type{ 142 ast.TypeString, // CIDR mask 143 }, 144 ReturnType: ast.TypeString, 145 Variadic: false, 146 Callback: func(args []interface{}) (interface{}, error) { 147 _, network, err := net.ParseCIDR(args[0].(string)) 148 if err != nil { 149 return nil, fmt.Errorf("invalid CIDR expression: %s", err) 150 } 151 152 return net.IP(network.Mask).String(), nil 153 }, 154 } 155 } 156 157 // interpolationFuncCidrSubnet implements the "cidrsubnet" function that 158 // adds an additional subnet of the given length onto an existing 159 // IP block expressed in CIDR notation. 160 func interpolationFuncCidrSubnet() ast.Function { 161 return ast.Function{ 162 ArgTypes: []ast.Type{ 163 ast.TypeString, // starting CIDR mask 164 ast.TypeInt, // number of bits to extend the prefix 165 ast.TypeInt, // network number to append to the prefix 166 }, 167 ReturnType: ast.TypeString, 168 Variadic: false, 169 Callback: func(args []interface{}) (interface{}, error) { 170 extraBits := args[1].(int) 171 subnetNum := args[2].(int) 172 _, network, err := net.ParseCIDR(args[0].(string)) 173 if err != nil { 174 return nil, fmt.Errorf("invalid CIDR expression: %s", err) 175 } 176 177 // For portability with 32-bit systems where the subnet number 178 // will be a 32-bit int, we only allow extension of 32 bits in 179 // one call even if we're running on a 64-bit machine. 180 // (Of course, this is significant only for IPv6.) 181 if extraBits > 32 { 182 return nil, fmt.Errorf("may not extend prefix by more than 32 bits") 183 } 184 185 newNetwork, err := cidr.Subnet(network, extraBits, subnetNum) 186 if err != nil { 187 return nil, err 188 } 189 190 return newNetwork.String(), nil 191 }, 192 } 193 } 194 195 // interpolationFuncCoalesce implements the "coalesce" function that 196 // returns the first non null / empty string from the provided input 197 func interpolationFuncCoalesce() ast.Function { 198 return ast.Function{ 199 ArgTypes: []ast.Type{ast.TypeString}, 200 ReturnType: ast.TypeString, 201 Variadic: true, 202 VariadicType: ast.TypeString, 203 Callback: func(args []interface{}) (interface{}, error) { 204 if len(args) < 2 { 205 return nil, fmt.Errorf("must provide at least two arguments") 206 } 207 for _, arg := range args { 208 argument := arg.(string) 209 210 if argument != "" { 211 return argument, nil 212 } 213 } 214 return "", nil 215 }, 216 } 217 } 218 219 // interpolationFuncConcat implements the "concat" function that 220 // concatenates multiple strings. This isn't actually necessary anymore 221 // since our language supports string concat natively, but for backwards 222 // compat we do this. 223 func interpolationFuncConcat() ast.Function { 224 return ast.Function{ 225 ArgTypes: []ast.Type{ast.TypeAny}, 226 ReturnType: ast.TypeList, 227 Variadic: true, 228 VariadicType: ast.TypeAny, 229 Callback: func(args []interface{}) (interface{}, error) { 230 var finalListElements []string 231 232 for _, arg := range args { 233 // Append strings for backward compatibility 234 if argument, ok := arg.(string); ok { 235 finalListElements = append(finalListElements, argument) 236 continue 237 } 238 239 // Otherwise variables 240 if argument, ok := arg.([]ast.Variable); ok { 241 for _, element := range argument { 242 finalListElements = append(finalListElements, element.Value.(string)) 243 } 244 continue 245 } 246 247 return nil, fmt.Errorf("arguments to concat() must be a string or list") 248 } 249 250 return stringSliceToVariableValue(finalListElements), nil 251 }, 252 } 253 } 254 255 // interpolationFuncFile implements the "file" function that allows 256 // loading contents from a file. 257 func interpolationFuncFile() ast.Function { 258 return ast.Function{ 259 ArgTypes: []ast.Type{ast.TypeString}, 260 ReturnType: ast.TypeString, 261 Callback: func(args []interface{}) (interface{}, error) { 262 path, err := homedir.Expand(args[0].(string)) 263 if err != nil { 264 return "", err 265 } 266 data, err := ioutil.ReadFile(path) 267 if err != nil { 268 return "", err 269 } 270 271 return string(data), nil 272 }, 273 } 274 } 275 276 // interpolationFuncFormat implements the "format" function that does 277 // string formatting. 278 func interpolationFuncFormat() ast.Function { 279 return ast.Function{ 280 ArgTypes: []ast.Type{ast.TypeString}, 281 Variadic: true, 282 VariadicType: ast.TypeAny, 283 ReturnType: ast.TypeString, 284 Callback: func(args []interface{}) (interface{}, error) { 285 format := args[0].(string) 286 return fmt.Sprintf(format, args[1:]...), nil 287 }, 288 } 289 } 290 291 // interpolationFuncFormatList implements the "formatlist" function that does 292 // string formatting on lists. 293 func interpolationFuncFormatList() ast.Function { 294 return ast.Function{ 295 ArgTypes: []ast.Type{ast.TypeAny}, 296 Variadic: true, 297 VariadicType: ast.TypeAny, 298 ReturnType: ast.TypeList, 299 Callback: func(args []interface{}) (interface{}, error) { 300 // Make a copy of the variadic part of args 301 // to avoid modifying the original. 302 varargs := make([]interface{}, len(args)-1) 303 copy(varargs, args[1:]) 304 305 // Convert arguments that are lists into slices. 306 // Confirm along the way that all lists have the same length (n). 307 var n int 308 for i := 1; i < len(args); i++ { 309 s, ok := args[i].([]ast.Variable) 310 if !ok { 311 continue 312 } 313 314 parts, err := listVariableValueToStringSlice(s) 315 if err != nil { 316 return nil, err 317 } 318 319 // otherwise the list is sent down to be indexed 320 varargs[i-1] = parts 321 322 // Check length 323 if n == 0 { 324 // first list we've seen 325 n = len(parts) 326 continue 327 } 328 if n != len(parts) { 329 return nil, fmt.Errorf("format: mismatched list lengths: %d != %d", n, len(parts)) 330 } 331 } 332 333 if n == 0 { 334 return nil, errors.New("no lists in arguments to formatlist") 335 } 336 337 // Do the formatting. 338 format := args[0].(string) 339 340 // Generate a list of formatted strings. 341 list := make([]string, n) 342 fmtargs := make([]interface{}, len(varargs)) 343 for i := 0; i < n; i++ { 344 for j, arg := range varargs { 345 switch arg := arg.(type) { 346 default: 347 fmtargs[j] = arg 348 case []string: 349 fmtargs[j] = arg[i] 350 } 351 } 352 list[i] = fmt.Sprintf(format, fmtargs...) 353 } 354 return stringSliceToVariableValue(list), nil 355 }, 356 } 357 } 358 359 // interpolationFuncIndex implements the "index" function that allows one to 360 // find the index of a specific element in a list 361 func interpolationFuncIndex() ast.Function { 362 return ast.Function{ 363 ArgTypes: []ast.Type{ast.TypeList, ast.TypeString}, 364 ReturnType: ast.TypeInt, 365 Callback: func(args []interface{}) (interface{}, error) { 366 haystack := args[0].([]ast.Variable) 367 needle := args[1].(string) 368 for index, element := range haystack { 369 if needle == element.Value { 370 return index, nil 371 } 372 } 373 return nil, fmt.Errorf("Could not find '%s' in '%s'", needle, haystack) 374 }, 375 } 376 } 377 378 // interpolationFuncJoin implements the "join" function that allows 379 // multi-variable values to be joined by some character. 380 func interpolationFuncJoin() ast.Function { 381 return ast.Function{ 382 ArgTypes: []ast.Type{ast.TypeString}, 383 Variadic: true, 384 VariadicType: ast.TypeList, 385 ReturnType: ast.TypeString, 386 Callback: func(args []interface{}) (interface{}, error) { 387 var list []string 388 389 if len(args) < 2 { 390 return nil, fmt.Errorf("not enough arguments to join()") 391 } 392 393 for _, arg := range args[1:] { 394 if parts, ok := arg.(ast.Variable); ok { 395 for _, part := range parts.Value.([]ast.Variable) { 396 list = append(list, part.Value.(string)) 397 } 398 } 399 if parts, ok := arg.([]ast.Variable); ok { 400 for _, part := range parts { 401 list = append(list, part.Value.(string)) 402 } 403 } 404 } 405 406 return strings.Join(list, args[0].(string)), nil 407 }, 408 } 409 } 410 411 // interpolationFuncJSONEncode implements the "jsonencode" function that encodes 412 // a string as its JSON representation. 413 func interpolationFuncJSONEncode() ast.Function { 414 return ast.Function{ 415 ArgTypes: []ast.Type{ast.TypeString}, 416 ReturnType: ast.TypeString, 417 Callback: func(args []interface{}) (interface{}, error) { 418 s := args[0].(string) 419 jEnc, err := json.Marshal(s) 420 if err != nil { 421 return "", fmt.Errorf("failed to encode JSON data '%s'", s) 422 } 423 return string(jEnc), nil 424 }, 425 } 426 } 427 428 // interpolationFuncReplace implements the "replace" function that does 429 // string replacement. 430 func interpolationFuncReplace() ast.Function { 431 return ast.Function{ 432 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString, ast.TypeString}, 433 ReturnType: ast.TypeString, 434 Callback: func(args []interface{}) (interface{}, error) { 435 s := args[0].(string) 436 search := args[1].(string) 437 replace := args[2].(string) 438 439 // We search/replace using a regexp if the string is surrounded 440 // in forward slashes. 441 if len(search) > 1 && search[0] == '/' && search[len(search)-1] == '/' { 442 re, err := regexp.Compile(search[1 : len(search)-1]) 443 if err != nil { 444 return nil, err 445 } 446 447 return re.ReplaceAllString(s, replace), nil 448 } 449 450 return strings.Replace(s, search, replace, -1), nil 451 }, 452 } 453 } 454 455 func interpolationFuncLength() ast.Function { 456 return ast.Function{ 457 ArgTypes: []ast.Type{ast.TypeAny}, 458 ReturnType: ast.TypeInt, 459 Variadic: false, 460 Callback: func(args []interface{}) (interface{}, error) { 461 subject := args[0] 462 463 switch typedSubject := subject.(type) { 464 case string: 465 return len(typedSubject), nil 466 case []ast.Variable: 467 return len(typedSubject), nil 468 } 469 470 return 0, fmt.Errorf("arguments to length() must be a string or list") 471 }, 472 } 473 } 474 475 func interpolationFuncSignum() ast.Function { 476 return ast.Function{ 477 ArgTypes: []ast.Type{ast.TypeInt}, 478 ReturnType: ast.TypeInt, 479 Variadic: false, 480 Callback: func(args []interface{}) (interface{}, error) { 481 num := args[0].(int) 482 switch { 483 case num < 0: 484 return -1, nil 485 case num > 0: 486 return +1, nil 487 default: 488 return 0, nil 489 } 490 }, 491 } 492 } 493 494 // interpolationFuncSplit implements the "split" function that allows 495 // strings to split into multi-variable values 496 func interpolationFuncSplit() ast.Function { 497 return ast.Function{ 498 ArgTypes: []ast.Type{ast.TypeString, ast.TypeString}, 499 ReturnType: ast.TypeList, 500 Callback: func(args []interface{}) (interface{}, error) { 501 sep := args[0].(string) 502 s := args[1].(string) 503 elements := strings.Split(s, sep) 504 return stringSliceToVariableValue(elements), nil 505 }, 506 } 507 } 508 509 // interpolationFuncLookup implements the "lookup" function that allows 510 // dynamic lookups of map types within a Terraform configuration. 511 func interpolationFuncLookup(vs map[string]ast.Variable) ast.Function { 512 return ast.Function{ 513 ArgTypes: []ast.Type{ast.TypeMap, ast.TypeString}, 514 ReturnType: ast.TypeString, 515 Callback: func(args []interface{}) (interface{}, error) { 516 index := args[1].(string) 517 mapVar := args[0].(map[string]ast.Variable) 518 519 v, ok := mapVar[index] 520 if !ok { 521 return "", fmt.Errorf( 522 "lookup failed to find '%s'", 523 args[1].(string)) 524 } 525 if v.Type != ast.TypeString { 526 return "", fmt.Errorf( 527 "lookup for '%s' has bad type %s", 528 args[1].(string), v.Type) 529 } 530 531 return v.Value.(string), nil 532 }, 533 } 534 } 535 536 // interpolationFuncElement implements the "element" function that allows 537 // a specific index to be looked up in a multi-variable value. Note that this will 538 // wrap if the index is larger than the number of elements in the multi-variable value. 539 func interpolationFuncElement() ast.Function { 540 return ast.Function{ 541 ArgTypes: []ast.Type{ast.TypeList, ast.TypeString}, 542 ReturnType: ast.TypeString, 543 Callback: func(args []interface{}) (interface{}, error) { 544 list := args[0].([]ast.Variable) 545 546 index, err := strconv.Atoi(args[1].(string)) 547 if err != nil || index < 0 { 548 return "", fmt.Errorf( 549 "invalid number for index, got %s", args[1]) 550 } 551 552 resolvedIndex := index % len(list) 553 554 v := list[resolvedIndex].Value 555 return v, nil 556 }, 557 } 558 } 559 560 // interpolationFuncKeys implements the "keys" function that yields a list of 561 // keys of map types within a Terraform configuration. 562 func interpolationFuncKeys(vs map[string]ast.Variable) ast.Function { 563 return ast.Function{ 564 ArgTypes: []ast.Type{ast.TypeMap}, 565 ReturnType: ast.TypeList, 566 Callback: func(args []interface{}) (interface{}, error) { 567 mapVar := args[0].(map[string]ast.Variable) 568 keys := make([]string, 0) 569 570 for k, _ := range mapVar { 571 keys = append(keys, k) 572 } 573 574 sort.Strings(keys) 575 576 //Keys are guaranteed to be strings 577 return stringSliceToVariableValue(keys), nil 578 }, 579 } 580 } 581 582 // interpolationFuncValues implements the "values" function that yields a list of 583 // keys of map types within a Terraform configuration. 584 func interpolationFuncValues(vs map[string]ast.Variable) ast.Function { 585 return ast.Function{ 586 ArgTypes: []ast.Type{ast.TypeMap}, 587 ReturnType: ast.TypeList, 588 Callback: func(args []interface{}) (interface{}, error) { 589 mapVar := args[0].(map[string]ast.Variable) 590 keys := make([]string, 0) 591 592 for k, _ := range mapVar { 593 keys = append(keys, k) 594 } 595 596 sort.Strings(keys) 597 598 values := make([]string, len(keys)) 599 for index, key := range keys { 600 if value, ok := mapVar[key].Value.(string); ok { 601 values[index] = value 602 } else { 603 return "", fmt.Errorf("values(): %q has element with bad type %s", 604 key, mapVar[key].Type) 605 } 606 } 607 608 variable, err := hil.InterfaceToVariable(values) 609 if err != nil { 610 return nil, err 611 } 612 613 return variable.Value, nil 614 }, 615 } 616 } 617 618 // interpolationFuncBase64Encode implements the "base64encode" function that 619 // allows Base64 encoding. 620 func interpolationFuncBase64Encode() ast.Function { 621 return ast.Function{ 622 ArgTypes: []ast.Type{ast.TypeString}, 623 ReturnType: ast.TypeString, 624 Callback: func(args []interface{}) (interface{}, error) { 625 s := args[0].(string) 626 return base64.StdEncoding.EncodeToString([]byte(s)), nil 627 }, 628 } 629 } 630 631 // interpolationFuncBase64Decode implements the "base64decode" function that 632 // allows Base64 decoding. 633 func interpolationFuncBase64Decode() ast.Function { 634 return ast.Function{ 635 ArgTypes: []ast.Type{ast.TypeString}, 636 ReturnType: ast.TypeString, 637 Callback: func(args []interface{}) (interface{}, error) { 638 s := args[0].(string) 639 sDec, err := base64.StdEncoding.DecodeString(s) 640 if err != nil { 641 return "", fmt.Errorf("failed to decode base64 data '%s'", s) 642 } 643 return string(sDec), nil 644 }, 645 } 646 } 647 648 // interpolationFuncLower implements the "lower" function that does 649 // string lower casing. 650 func interpolationFuncLower() ast.Function { 651 return ast.Function{ 652 ArgTypes: []ast.Type{ast.TypeString}, 653 ReturnType: ast.TypeString, 654 Callback: func(args []interface{}) (interface{}, error) { 655 toLower := args[0].(string) 656 return strings.ToLower(toLower), nil 657 }, 658 } 659 } 660 661 func interpolationFuncMd5() ast.Function { 662 return ast.Function{ 663 ArgTypes: []ast.Type{ast.TypeString}, 664 ReturnType: ast.TypeString, 665 Callback: func(args []interface{}) (interface{}, error) { 666 s := args[0].(string) 667 h := md5.New() 668 h.Write([]byte(s)) 669 hash := hex.EncodeToString(h.Sum(nil)) 670 return hash, nil 671 }, 672 } 673 } 674 675 // interpolationFuncUpper implements the "upper" function that does 676 // string upper casing. 677 func interpolationFuncUpper() ast.Function { 678 return ast.Function{ 679 ArgTypes: []ast.Type{ast.TypeString}, 680 ReturnType: ast.TypeString, 681 Callback: func(args []interface{}) (interface{}, error) { 682 toUpper := args[0].(string) 683 return strings.ToUpper(toUpper), nil 684 }, 685 } 686 } 687 688 func interpolationFuncSha1() ast.Function { 689 return ast.Function{ 690 ArgTypes: []ast.Type{ast.TypeString}, 691 ReturnType: ast.TypeString, 692 Callback: func(args []interface{}) (interface{}, error) { 693 s := args[0].(string) 694 h := sha1.New() 695 h.Write([]byte(s)) 696 hash := hex.EncodeToString(h.Sum(nil)) 697 return hash, nil 698 }, 699 } 700 } 701 702 // hexadecimal representation of sha256 sum 703 func interpolationFuncSha256() ast.Function { 704 return ast.Function{ 705 ArgTypes: []ast.Type{ast.TypeString}, 706 ReturnType: ast.TypeString, 707 Callback: func(args []interface{}) (interface{}, error) { 708 s := args[0].(string) 709 h := sha256.New() 710 h.Write([]byte(s)) 711 hash := hex.EncodeToString(h.Sum(nil)) 712 return hash, nil 713 }, 714 } 715 } 716 717 func interpolationFuncTrimSpace() ast.Function { 718 return ast.Function{ 719 ArgTypes: []ast.Type{ast.TypeString}, 720 ReturnType: ast.TypeString, 721 Callback: func(args []interface{}) (interface{}, error) { 722 trimSpace := args[0].(string) 723 return strings.TrimSpace(trimSpace), nil 724 }, 725 } 726 } 727 728 func interpolationFuncBase64Sha256() ast.Function { 729 return ast.Function{ 730 ArgTypes: []ast.Type{ast.TypeString}, 731 ReturnType: ast.TypeString, 732 Callback: func(args []interface{}) (interface{}, error) { 733 s := args[0].(string) 734 h := sha256.New() 735 h.Write([]byte(s)) 736 shaSum := h.Sum(nil) 737 encoded := base64.StdEncoding.EncodeToString(shaSum[:]) 738 return encoded, nil 739 }, 740 } 741 } 742 743 func interpolationFuncUUID() ast.Function { 744 return ast.Function{ 745 ArgTypes: []ast.Type{}, 746 ReturnType: ast.TypeString, 747 Callback: func(args []interface{}) (interface{}, error) { 748 return uuid.GenerateUUID() 749 }, 750 } 751 }