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