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