github.com/jsoriano/terraform@v0.6.7-0.20151026070445-8b70867fdd95/config/interpolate_funcs_test.go (about) 1 package config 2 3 import ( 4 "fmt" 5 "io/ioutil" 6 "os" 7 "reflect" 8 "testing" 9 10 "github.com/hashicorp/terraform/config/lang" 11 "github.com/hashicorp/terraform/config/lang/ast" 12 ) 13 14 func TestInterpolateFuncCompact(t *testing.T) { 15 testFunction(t, testFunctionConfig{ 16 Cases: []testFunctionCase{ 17 // empty string within array 18 { 19 `${compact(split(",", "a,,b"))}`, 20 NewStringList([]string{"a", "b"}).String(), 21 false, 22 }, 23 24 // empty string at the end of array 25 { 26 `${compact(split(",", "a,b,"))}`, 27 NewStringList([]string{"a", "b"}).String(), 28 false, 29 }, 30 31 // single empty string 32 { 33 `${compact(split(",", ""))}`, 34 NewStringList([]string{}).String(), 35 false, 36 }, 37 }, 38 }) 39 } 40 41 func TestInterpolateFuncCidrHost(t *testing.T) { 42 testFunction(t, testFunctionConfig{ 43 Cases: []testFunctionCase{ 44 { 45 `${cidrhost("192.168.1.0/24", 5)}`, 46 "192.168.1.5", 47 false, 48 }, 49 { 50 `${cidrhost("192.168.1.0/30", 255)}`, 51 nil, 52 true, // 255 doesn't fit in two bits 53 }, 54 { 55 `${cidrhost("not-a-cidr", 6)}`, 56 nil, 57 true, // not a valid CIDR mask 58 }, 59 { 60 `${cidrhost("10.256.0.0/8", 6)}`, 61 nil, 62 true, // can't have an octet >255 63 }, 64 }, 65 }) 66 } 67 68 func TestInterpolateFuncCidrNetmask(t *testing.T) { 69 testFunction(t, testFunctionConfig{ 70 Cases: []testFunctionCase{ 71 { 72 `${cidrnetmask("192.168.1.0/24")}`, 73 "255.255.255.0", 74 false, 75 }, 76 { 77 `${cidrnetmask("192.168.1.0/32")}`, 78 "255.255.255.255", 79 false, 80 }, 81 { 82 `${cidrnetmask("0.0.0.0/0")}`, 83 "0.0.0.0", 84 false, 85 }, 86 { 87 // This doesn't really make sense for IPv6 networks 88 // but it ought to do something sensible anyway. 89 `${cidrnetmask("1::/64")}`, 90 "ffff:ffff:ffff:ffff::", 91 false, 92 }, 93 { 94 `${cidrnetmask("not-a-cidr")}`, 95 nil, 96 true, // not a valid CIDR mask 97 }, 98 { 99 `${cidrnetmask("10.256.0.0/8")}`, 100 nil, 101 true, // can't have an octet >255 102 }, 103 }, 104 }) 105 } 106 107 func TestInterpolateFuncCidrSubnet(t *testing.T) { 108 testFunction(t, testFunctionConfig{ 109 Cases: []testFunctionCase{ 110 { 111 `${cidrsubnet("192.168.2.0/20", 4, 6)}`, 112 "192.168.6.0/24", 113 false, 114 }, 115 { 116 `${cidrsubnet("fe80::/48", 16, 6)}`, 117 "fe80:0:0:6::/64", 118 false, 119 }, 120 { 121 // IPv4 address encoded in IPv6 syntax gets normalized 122 `${cidrsubnet("::ffff:192.168.0.0/112", 8, 6)}`, 123 "192.168.6.0/24", 124 false, 125 }, 126 { 127 `${cidrsubnet("192.168.0.0/30", 4, 6)}`, 128 nil, 129 true, // not enough bits left 130 }, 131 { 132 `${cidrsubnet("192.168.0.0/16", 2, 16)}`, 133 nil, 134 true, // can't encode 16 in 2 bits 135 }, 136 { 137 `${cidrsubnet("not-a-cidr", 4, 6)}`, 138 nil, 139 true, // not a valid CIDR mask 140 }, 141 { 142 `${cidrsubnet("10.256.0.0/8", 4, 6)}`, 143 nil, 144 true, // can't have an octet >255 145 }, 146 }, 147 }) 148 } 149 150 func TestInterpolateFuncDeprecatedConcat(t *testing.T) { 151 testFunction(t, testFunctionConfig{ 152 Cases: []testFunctionCase{ 153 { 154 `${concat("foo", "bar")}`, 155 "foobar", 156 false, 157 }, 158 159 { 160 `${concat("foo")}`, 161 "foo", 162 false, 163 }, 164 165 { 166 `${concat()}`, 167 nil, 168 true, 169 }, 170 }, 171 }) 172 } 173 174 func TestInterpolateFuncConcat(t *testing.T) { 175 testFunction(t, testFunctionConfig{ 176 Cases: []testFunctionCase{ 177 // String + list 178 { 179 `${concat("a", split(",", "b,c"))}`, 180 NewStringList([]string{"a", "b", "c"}).String(), 181 false, 182 }, 183 184 // List + string 185 { 186 `${concat(split(",", "a,b"), "c")}`, 187 NewStringList([]string{"a", "b", "c"}).String(), 188 false, 189 }, 190 191 // Single list 192 { 193 `${concat(split(",", ",foo,"))}`, 194 NewStringList([]string{"", "foo", ""}).String(), 195 false, 196 }, 197 { 198 `${concat(split(",", "a,b,c"))}`, 199 NewStringList([]string{"a", "b", "c"}).String(), 200 false, 201 }, 202 203 // Two lists 204 { 205 `${concat(split(",", "a,b,c"), split(",", "d,e"))}`, 206 NewStringList([]string{"a", "b", "c", "d", "e"}).String(), 207 false, 208 }, 209 // Two lists with different separators 210 { 211 `${concat(split(",", "a,b,c"), split(" ", "d e"))}`, 212 NewStringList([]string{"a", "b", "c", "d", "e"}).String(), 213 false, 214 }, 215 216 // More lists 217 { 218 `${concat(split(",", "a,b"), split(",", "c,d"), split(",", "e,f"), split(",", "0,1"))}`, 219 NewStringList([]string{"a", "b", "c", "d", "e", "f", "0", "1"}).String(), 220 false, 221 }, 222 }, 223 }) 224 } 225 226 func TestInterpolateFuncFile(t *testing.T) { 227 tf, err := ioutil.TempFile("", "tf") 228 if err != nil { 229 t.Fatalf("err: %s", err) 230 } 231 path := tf.Name() 232 tf.Write([]byte("foo")) 233 tf.Close() 234 defer os.Remove(path) 235 236 testFunction(t, testFunctionConfig{ 237 Cases: []testFunctionCase{ 238 { 239 fmt.Sprintf(`${file("%s")}`, path), 240 "foo", 241 false, 242 }, 243 244 // Invalid path 245 { 246 `${file("/i/dont/exist")}`, 247 nil, 248 true, 249 }, 250 251 // Too many args 252 { 253 `${file("foo", "bar")}`, 254 nil, 255 true, 256 }, 257 }, 258 }) 259 } 260 261 func TestInterpolateFuncFormat(t *testing.T) { 262 testFunction(t, testFunctionConfig{ 263 Cases: []testFunctionCase{ 264 { 265 `${format("hello")}`, 266 "hello", 267 false, 268 }, 269 270 { 271 `${format("hello %s", "world")}`, 272 "hello world", 273 false, 274 }, 275 276 { 277 `${format("hello %d", 42)}`, 278 "hello 42", 279 false, 280 }, 281 282 { 283 `${format("hello %05d", 42)}`, 284 "hello 00042", 285 false, 286 }, 287 288 { 289 `${format("hello %05d", 12345)}`, 290 "hello 12345", 291 false, 292 }, 293 }, 294 }) 295 } 296 297 func TestInterpolateFuncFormatList(t *testing.T) { 298 testFunction(t, testFunctionConfig{ 299 Cases: []testFunctionCase{ 300 // formatlist requires at least one list 301 { 302 `${formatlist("hello")}`, 303 nil, 304 true, 305 }, 306 { 307 `${formatlist("hello %s", "world")}`, 308 nil, 309 true, 310 }, 311 // formatlist applies to each list element in turn 312 { 313 `${formatlist("<%s>", split(",", "A,B"))}`, 314 NewStringList([]string{"<A>", "<B>"}).String(), 315 false, 316 }, 317 // formatlist repeats scalar elements 318 { 319 `${join(", ", formatlist("%s=%s", "x", split(",", "A,B,C")))}`, 320 "x=A, x=B, x=C", 321 false, 322 }, 323 // Multiple lists are walked in parallel 324 { 325 `${join(", ", formatlist("%s=%s", split(",", "A,B,C"), split(",", "1,2,3")))}`, 326 "A=1, B=2, C=3", 327 false, 328 }, 329 // Mismatched list lengths generate an error 330 { 331 `${formatlist("%s=%2s", split(",", "A,B,C,D"), split(",", "1,2,3"))}`, 332 nil, 333 true, 334 }, 335 // Works with lists of length 1 [GH-2240] 336 { 337 `${formatlist("%s.id", split(",", "demo-rest-elb"))}`, 338 NewStringList([]string{"demo-rest-elb.id"}).String(), 339 false, 340 }, 341 }, 342 }) 343 } 344 345 func TestInterpolateFuncIndex(t *testing.T) { 346 testFunction(t, testFunctionConfig{ 347 Cases: []testFunctionCase{ 348 { 349 `${index("test", "")}`, 350 nil, 351 true, 352 }, 353 354 { 355 fmt.Sprintf(`${index("%s", "foo")}`, 356 NewStringList([]string{"notfoo", "stillnotfoo", "bar"}).String()), 357 nil, 358 true, 359 }, 360 361 { 362 fmt.Sprintf(`${index("%s", "foo")}`, 363 NewStringList([]string{"foo"}).String()), 364 "0", 365 false, 366 }, 367 368 { 369 fmt.Sprintf(`${index("%s", "bar")}`, 370 NewStringList([]string{"foo", "spam", "bar", "eggs"}).String()), 371 "2", 372 false, 373 }, 374 }, 375 }) 376 } 377 378 func TestInterpolateFuncJoin(t *testing.T) { 379 testFunction(t, testFunctionConfig{ 380 Cases: []testFunctionCase{ 381 { 382 `${join(",")}`, 383 nil, 384 true, 385 }, 386 387 { 388 fmt.Sprintf(`${join(",", "%s")}`, 389 NewStringList([]string{"foo"}).String()), 390 "foo", 391 false, 392 }, 393 394 /* 395 TODO 396 { 397 `${join(",", "foo", "bar")}`, 398 "foo,bar", 399 false, 400 }, 401 */ 402 403 { 404 fmt.Sprintf(`${join(".", "%s")}`, 405 NewStringList([]string{"foo", "bar", "baz"}).String()), 406 "foo.bar.baz", 407 false, 408 }, 409 }, 410 }) 411 } 412 413 func TestInterpolateFuncReplace(t *testing.T) { 414 testFunction(t, testFunctionConfig{ 415 Cases: []testFunctionCase{ 416 // Regular search and replace 417 { 418 `${replace("hello", "hel", "bel")}`, 419 "bello", 420 false, 421 }, 422 423 // Search string doesn't match 424 { 425 `${replace("hello", "nope", "bel")}`, 426 "hello", 427 false, 428 }, 429 430 // Regular expression 431 { 432 `${replace("hello", "/l/", "L")}`, 433 "heLLo", 434 false, 435 }, 436 437 { 438 `${replace("helo", "/(l)/", "$1$1")}`, 439 "hello", 440 false, 441 }, 442 443 // Bad regexp 444 { 445 `${replace("helo", "/(l/", "$1$1")}`, 446 nil, 447 true, 448 }, 449 }, 450 }) 451 } 452 453 func TestInterpolateFuncLength(t *testing.T) { 454 testFunction(t, testFunctionConfig{ 455 Cases: []testFunctionCase{ 456 // Raw strings 457 { 458 `${length("")}`, 459 "0", 460 false, 461 }, 462 { 463 `${length("a")}`, 464 "1", 465 false, 466 }, 467 { 468 `${length(" ")}`, 469 "1", 470 false, 471 }, 472 { 473 `${length(" a ,")}`, 474 "4", 475 false, 476 }, 477 { 478 `${length("aaa")}`, 479 "3", 480 false, 481 }, 482 483 // Lists 484 { 485 `${length(split(",", "a"))}`, 486 "1", 487 false, 488 }, 489 { 490 `${length(split(",", "foo,"))}`, 491 "2", 492 false, 493 }, 494 { 495 `${length(split(",", ",foo,"))}`, 496 "3", 497 false, 498 }, 499 { 500 `${length(split(",", "foo,bar"))}`, 501 "2", 502 false, 503 }, 504 { 505 `${length(split(".", "one.two.three.four.five"))}`, 506 "5", 507 false, 508 }, 509 }, 510 }) 511 } 512 513 func TestInterpolateFuncSplit(t *testing.T) { 514 testFunction(t, testFunctionConfig{ 515 Cases: []testFunctionCase{ 516 { 517 `${split(",")}`, 518 nil, 519 true, 520 }, 521 522 { 523 `${split(",", "")}`, 524 NewStringList([]string{""}).String(), 525 false, 526 }, 527 528 { 529 `${split(",", "foo")}`, 530 NewStringList([]string{"foo"}).String(), 531 false, 532 }, 533 534 { 535 `${split(",", ",,,")}`, 536 NewStringList([]string{"", "", "", ""}).String(), 537 false, 538 }, 539 540 { 541 `${split(",", "foo,")}`, 542 NewStringList([]string{"foo", ""}).String(), 543 false, 544 }, 545 546 { 547 `${split(",", ",foo,")}`, 548 NewStringList([]string{"", "foo", ""}).String(), 549 false, 550 }, 551 552 { 553 `${split(".", "foo.bar.baz")}`, 554 NewStringList([]string{"foo", "bar", "baz"}).String(), 555 false, 556 }, 557 }, 558 }) 559 } 560 561 func TestInterpolateFuncLookup(t *testing.T) { 562 testFunction(t, testFunctionConfig{ 563 Vars: map[string]ast.Variable{ 564 "var.foo.bar": ast.Variable{ 565 Value: "baz", 566 Type: ast.TypeString, 567 }, 568 }, 569 Cases: []testFunctionCase{ 570 { 571 `${lookup("foo", "bar")}`, 572 "baz", 573 false, 574 }, 575 576 // Invalid key 577 { 578 `${lookup("foo", "baz")}`, 579 nil, 580 true, 581 }, 582 583 // Too many args 584 { 585 `${lookup("foo", "bar", "baz")}`, 586 nil, 587 true, 588 }, 589 }, 590 }) 591 } 592 593 func TestInterpolateFuncKeys(t *testing.T) { 594 testFunction(t, testFunctionConfig{ 595 Vars: map[string]ast.Variable{ 596 "var.foo.bar": ast.Variable{ 597 Value: "baz", 598 Type: ast.TypeString, 599 }, 600 "var.foo.qux": ast.Variable{ 601 Value: "quack", 602 Type: ast.TypeString, 603 }, 604 "var.str": ast.Variable{ 605 Value: "astring", 606 Type: ast.TypeString, 607 }, 608 }, 609 Cases: []testFunctionCase{ 610 { 611 `${keys("foo")}`, 612 NewStringList([]string{"bar", "qux"}).String(), 613 false, 614 }, 615 616 // Invalid key 617 { 618 `${keys("not")}`, 619 nil, 620 true, 621 }, 622 623 // Too many args 624 { 625 `${keys("foo", "bar")}`, 626 nil, 627 true, 628 }, 629 630 // Not a map 631 { 632 `${keys("str")}`, 633 nil, 634 true, 635 }, 636 }, 637 }) 638 } 639 640 func TestInterpolateFuncValues(t *testing.T) { 641 testFunction(t, testFunctionConfig{ 642 Vars: map[string]ast.Variable{ 643 "var.foo.bar": ast.Variable{ 644 Value: "quack", 645 Type: ast.TypeString, 646 }, 647 "var.foo.qux": ast.Variable{ 648 Value: "baz", 649 Type: ast.TypeString, 650 }, 651 "var.str": ast.Variable{ 652 Value: "astring", 653 Type: ast.TypeString, 654 }, 655 }, 656 Cases: []testFunctionCase{ 657 { 658 `${values("foo")}`, 659 NewStringList([]string{"quack", "baz"}).String(), 660 false, 661 }, 662 663 // Invalid key 664 { 665 `${values("not")}`, 666 nil, 667 true, 668 }, 669 670 // Too many args 671 { 672 `${values("foo", "bar")}`, 673 nil, 674 true, 675 }, 676 677 // Not a map 678 { 679 `${values("str")}`, 680 nil, 681 true, 682 }, 683 }, 684 }) 685 } 686 687 func TestInterpolateFuncElement(t *testing.T) { 688 testFunction(t, testFunctionConfig{ 689 Cases: []testFunctionCase{ 690 { 691 fmt.Sprintf(`${element("%s", "1")}`, 692 NewStringList([]string{"foo", "baz"}).String()), 693 "baz", 694 false, 695 }, 696 697 { 698 fmt.Sprintf(`${element("%s", "0")}`, 699 NewStringList([]string{"foo"}).String()), 700 "foo", 701 false, 702 }, 703 704 // Invalid index should wrap vs. out-of-bounds 705 { 706 fmt.Sprintf(`${element("%s", "2")}`, 707 NewStringList([]string{"foo", "baz"}).String()), 708 "foo", 709 false, 710 }, 711 712 // Too many args 713 { 714 fmt.Sprintf(`${element("%s", "0", "2")}`, 715 NewStringList([]string{"foo", "baz"}).String()), 716 nil, 717 true, 718 }, 719 }, 720 }) 721 } 722 723 func TestInterpolateFuncBase64Encode(t *testing.T) { 724 testFunction(t, testFunctionConfig{ 725 Cases: []testFunctionCase{ 726 // Regular base64 encoding 727 { 728 `${base64encode("abc123!?$*&()'-=@~")}`, 729 "YWJjMTIzIT8kKiYoKSctPUB+", 730 false, 731 }, 732 }, 733 }) 734 } 735 736 func TestInterpolateFuncBase64Decode(t *testing.T) { 737 testFunction(t, testFunctionConfig{ 738 Cases: []testFunctionCase{ 739 // Regular base64 decoding 740 { 741 `${base64decode("YWJjMTIzIT8kKiYoKSctPUB+")}`, 742 "abc123!?$*&()'-=@~", 743 false, 744 }, 745 746 // Invalid base64 data decoding 747 { 748 `${base64decode("this-is-an-invalid-base64-data")}`, 749 nil, 750 true, 751 }, 752 }, 753 }) 754 } 755 756 func TestInterpolateFuncLower(t *testing.T) { 757 testFunction(t, testFunctionConfig{ 758 Cases: []testFunctionCase{ 759 { 760 `${lower("HELLO")}`, 761 "hello", 762 false, 763 }, 764 765 { 766 `${lower("")}`, 767 "", 768 false, 769 }, 770 771 { 772 `${lower()}`, 773 nil, 774 true, 775 }, 776 }, 777 }) 778 } 779 780 func TestInterpolateFuncUpper(t *testing.T) { 781 testFunction(t, testFunctionConfig{ 782 Cases: []testFunctionCase{ 783 { 784 `${upper("hello")}`, 785 "HELLO", 786 false, 787 }, 788 789 { 790 `${upper("")}`, 791 "", 792 false, 793 }, 794 795 { 796 `${upper()}`, 797 nil, 798 true, 799 }, 800 }, 801 }) 802 } 803 804 type testFunctionConfig struct { 805 Cases []testFunctionCase 806 Vars map[string]ast.Variable 807 } 808 809 type testFunctionCase struct { 810 Input string 811 Result interface{} 812 Error bool 813 } 814 815 func testFunction(t *testing.T, config testFunctionConfig) { 816 for i, tc := range config.Cases { 817 ast, err := lang.Parse(tc.Input) 818 if err != nil { 819 t.Fatalf("Case #%d: input: %#v\nerr: %s", i, tc.Input, err) 820 } 821 822 out, _, err := lang.Eval(ast, langEvalConfig(config.Vars)) 823 if err != nil != tc.Error { 824 t.Fatalf("Case #%d:\ninput: %#v\nerr: %s", i, tc.Input, err) 825 } 826 827 if !reflect.DeepEqual(out, tc.Result) { 828 t.Fatalf( 829 "%d: bad output for input: %s\n\nOutput: %#v\nExpected: %#v", 830 i, tc.Input, out, tc.Result) 831 } 832 } 833 }