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