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