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