github.com/jpreese/tflint@v0.19.2-0.20200908152133-b01686250fb6/tflint/runner_eval_test.go (about) 1 package tflint 2 3 import ( 4 "errors" 5 "os" 6 "path/filepath" 7 "testing" 8 9 "github.com/google/go-cmp/cmp" 10 hcl "github.com/hashicorp/hcl/v2" 11 "github.com/hashicorp/terraform/configs/configschema" 12 "github.com/hashicorp/terraform/terraform" 13 "github.com/zclconf/go-cty/cty" 14 ) 15 16 func Test_EvaluateExpr_string(t *testing.T) { 17 cases := []struct { 18 Name string 19 Content string 20 Expected string 21 }{ 22 { 23 Name: "literal", 24 Content: ` 25 resource "null_resource" "test" { 26 key = "literal_val" 27 }`, 28 Expected: "literal_val", 29 }, 30 { 31 Name: "string interpolation", 32 Content: ` 33 variable "string_var" { 34 default = "string_val" 35 } 36 37 resource "null_resource" "test" { 38 key = "${var.string_var}" 39 }`, 40 Expected: "string_val", 41 }, 42 { 43 Name: "new style interpolation", 44 Content: ` 45 variable "string_var" { 46 default = "string_val" 47 } 48 49 resource "null_resource" "test" { 50 key = var.string_var 51 }`, 52 Expected: "string_val", 53 }, 54 { 55 Name: "list element", 56 Content: ` 57 variable "list_var" { 58 default = ["one", "two"] 59 } 60 61 resource "null_resource" "test" { 62 key = "${var.list_var[0]}" 63 }`, 64 Expected: "one", 65 }, 66 { 67 Name: "map element", 68 Content: ` 69 variable "map_var" { 70 default = { 71 one = "one" 72 two = "two" 73 } 74 } 75 76 resource "null_resource" "test" { 77 key = "${var.map_var["one"]}" 78 }`, 79 Expected: "one", 80 }, 81 { 82 Name: "object item", 83 Content: ` 84 variable "object" { 85 type = object({ foo = string }) 86 default = { foo = "bar" } 87 } 88 89 resource "null_resource" "test" { 90 key = var.object.foo 91 }`, 92 Expected: "bar", 93 }, 94 { 95 Name: "convert from integer", 96 Content: ` 97 variable "string_var" { 98 default = 10 99 } 100 101 resource "null_resource" "test" { 102 key = "${var.string_var}" 103 }`, 104 Expected: "10", 105 }, 106 { 107 Name: "conditional", 108 Content: ` 109 resource "null_resource" "test" { 110 key = "${true ? "production" : "development"}" 111 }`, 112 Expected: "production", 113 }, 114 { 115 Name: "bulit-in function", 116 Content: ` 117 resource "null_resource" "test" { 118 key = "${md5("foo")}" 119 }`, 120 Expected: "acbd18db4cc2f85cedef654fccc4a4d8", 121 }, 122 { 123 Name: "terraform workspace", 124 Content: ` 125 resource "null_resource" "test" { 126 key = "${terraform.workspace}" 127 }`, 128 Expected: "default", 129 }, 130 { 131 Name: "inside interpolation", 132 Content: ` 133 variable "string_var" { 134 default = "World" 135 } 136 137 resource "null_resource" "test" { 138 key = "Hello ${var.string_var}" 139 }`, 140 Expected: "Hello World", 141 }, 142 { 143 Name: "path.root", 144 Content: ` 145 resource "null_resource" "test" { 146 key = path.root 147 }`, 148 Expected: ".", 149 }, 150 { 151 Name: "path.module", 152 Content: ` 153 resource "null_resource" "test" { 154 key = path.module 155 }`, 156 Expected: ".", 157 }, 158 } 159 160 for _, tc := range cases { 161 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 162 163 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 164 var ret string 165 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 166 return err 167 } 168 169 if tc.Expected != ret { 170 t.Fatalf("Failed `%s` test: expected value is `%s`, but get `%s`", tc.Name, tc.Expected, ret) 171 } 172 return nil 173 }) 174 175 if err != nil { 176 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 177 } 178 } 179 } 180 181 func Test_EvaluateExpr_pathCwd(t *testing.T) { 182 cwd, err := os.Getwd() 183 if err != nil { 184 t.Fatal(err) 185 } 186 expected := filepath.ToSlash(cwd) 187 188 content := ` 189 resource "null_resource" "test" { 190 key = path.cwd 191 }` 192 runner := TestRunner(t, map[string]string{"main.tf": content}) 193 194 err = runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 195 var ret string 196 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 197 return err 198 } 199 200 if expected != ret { 201 t.Fatalf("expected value is `%s`, but get `%s`", expected, ret) 202 } 203 return nil 204 }) 205 206 if err != nil { 207 t.Fatalf("Failed: `%s` occurred", err) 208 } 209 } 210 211 func Test_EvaluateExpr_integer(t *testing.T) { 212 cases := []struct { 213 Name string 214 Content string 215 Expected int 216 }{ 217 { 218 Name: "integer interpolation", 219 Content: ` 220 variable "integer_var" { 221 default = 3 222 } 223 224 resource "null_resource" "test" { 225 key = "${var.integer_var}" 226 }`, 227 Expected: 3, 228 }, 229 { 230 Name: "convert from string", 231 Content: ` 232 variable "integer_var" { 233 default = "3" 234 } 235 236 resource "null_resource" "test" { 237 key = "${var.integer_var}" 238 }`, 239 Expected: 3, 240 }, 241 } 242 243 for _, tc := range cases { 244 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 245 246 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 247 var ret int 248 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 249 return err 250 } 251 252 if tc.Expected != ret { 253 t.Fatalf("Failed `%s` test: expected value is `%d`, but get `%d`", tc.Name, tc.Expected, ret) 254 } 255 return nil 256 }) 257 258 if err != nil { 259 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 260 } 261 } 262 } 263 264 func Test_EvaluateExpr_stringList(t *testing.T) { 265 cases := []struct { 266 Name string 267 Content string 268 Expected []string 269 }{ 270 { 271 Name: "list literal", 272 Content: ` 273 resource "null_resource" "test" { 274 key = ["one", "two", "three"] 275 }`, 276 Expected: []string{"one", "two", "three"}, 277 }, 278 } 279 280 for _, tc := range cases { 281 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 282 283 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 284 var ret []string 285 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 286 return err 287 } 288 289 if !cmp.Equal(tc.Expected, ret) { 290 t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret)) 291 } 292 return nil 293 }) 294 295 if err != nil { 296 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 297 } 298 } 299 } 300 301 func Test_EvaluateExpr_numberList(t *testing.T) { 302 cases := []struct { 303 Name string 304 Content string 305 Expected []int 306 }{ 307 { 308 Name: "list literal", 309 Content: ` 310 resource "null_resource" "test" { 311 key = [1, 2, 3] 312 }`, 313 Expected: []int{1, 2, 3}, 314 }, 315 } 316 317 for _, tc := range cases { 318 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 319 320 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 321 var ret []int 322 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 323 return err 324 } 325 326 if !cmp.Equal(tc.Expected, ret) { 327 t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret)) 328 } 329 return nil 330 }) 331 332 if err != nil { 333 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 334 } 335 } 336 } 337 338 func Test_EvaluateExpr_stringMap(t *testing.T) { 339 cases := []struct { 340 Name string 341 Content string 342 Expected map[string]string 343 }{ 344 { 345 Name: "map literal", 346 Content: ` 347 resource "null_resource" "test" { 348 key = { 349 one = 1 350 two = "2" 351 } 352 }`, 353 Expected: map[string]string{"one": "1", "two": "2"}, 354 }, 355 } 356 357 for _, tc := range cases { 358 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 359 360 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 361 var ret map[string]string 362 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 363 return err 364 } 365 366 if !cmp.Equal(tc.Expected, ret) { 367 t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret)) 368 } 369 return nil 370 }) 371 372 if err != nil { 373 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 374 } 375 } 376 } 377 378 func Test_EvaluateExpr_numberMap(t *testing.T) { 379 cases := []struct { 380 Name string 381 Content string 382 Expected map[string]int 383 }{ 384 { 385 Name: "map literal", 386 Content: ` 387 resource "null_resource" "test" { 388 key = { 389 one = 1 390 two = "2" 391 } 392 }`, 393 Expected: map[string]int{"one": 1, "two": 2}, 394 }, 395 } 396 397 for _, tc := range cases { 398 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 399 400 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 401 var ret map[string]int 402 if err := runner.EvaluateExpr(attribute.Expr, &ret); err != nil { 403 return err 404 } 405 406 if !cmp.Equal(tc.Expected, ret) { 407 t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret)) 408 } 409 return nil 410 }) 411 412 if err != nil { 413 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 414 } 415 } 416 } 417 418 func Test_EvaluateExpr_interpolationError(t *testing.T) { 419 cases := []struct { 420 Name string 421 Content string 422 Error Error 423 }{ 424 { 425 Name: "undefined variable", 426 Content: ` 427 resource "null_resource" "test" { 428 key = "${var.undefined_var}" 429 }`, 430 Error: Error{ 431 Code: EvaluationError, 432 Level: ErrorLevel, 433 Message: "Failed to eval an expression in main.tf:3; Reference to undeclared input variable: An input variable with the name \"undefined_var\" has not been declared. This variable can be declared with a variable \"undefined_var\" {} block.", 434 }, 435 }, 436 { 437 Name: "no default value", 438 Content: ` 439 variable "no_value_var" {} 440 441 resource "null_resource" "test" { 442 key = "${var.no_value_var}" 443 }`, 444 Error: Error{ 445 Code: UnknownValueError, 446 Level: WarningLevel, 447 Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value", 448 }, 449 }, 450 { 451 Name: "null value", 452 Content: ` 453 variable "null_var" { 454 type = string 455 default = null 456 } 457 458 resource "null_resource" "test" { 459 key = var.null_var 460 }`, 461 Error: Error{ 462 Code: NullValueError, 463 Level: WarningLevel, 464 Message: "Null value found in main.tf:8", 465 }, 466 }, 467 { 468 Name: "terraform env", 469 Content: ` 470 resource "null_resource" "test" { 471 key = "${terraform.env}" 472 }`, 473 Error: Error{ 474 Code: EvaluationError, 475 Level: ErrorLevel, 476 Message: "Failed to eval an expression in main.tf:3; Invalid \"terraform\" attribute: The terraform.env attribute was deprecated in v0.10 and removed in v0.12. The \"state environment\" concept was rename to \"workspace\" in v0.12, and so the workspace name can now be accessed using the terraform.workspace attribute.", 477 }, 478 }, 479 { 480 Name: "type mismatch", 481 Content: ` 482 resource "null_resource" "test" { 483 key = ["one", "two", "three"] 484 }`, 485 Error: Error{ 486 Code: EvaluationError, 487 Level: ErrorLevel, 488 Message: "Failed to eval an expression in main.tf:3; Incorrect value type: Invalid expression value: string required.", 489 }, 490 }, 491 { 492 Name: "unevalauble", 493 Content: ` 494 resource "null_resource" "test" { 495 key = "${module.text}" 496 }`, 497 Error: Error{ 498 Code: UnevaluableError, 499 Level: WarningLevel, 500 Message: "Unevaluable expression found in main.tf:3", 501 }, 502 }, 503 } 504 505 for _, tc := range cases { 506 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 507 508 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 509 var ret string 510 err := runner.EvaluateExpr(attribute.Expr, &ret) 511 512 AssertAppError(t, tc.Error, err) 513 return nil 514 }) 515 516 if err != nil { 517 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 518 } 519 } 520 } 521 522 func Test_EvaluateExpr_mapWithInterpolationError(t *testing.T) { 523 cases := []struct { 524 Name string 525 Content string 526 Error Error 527 }{ 528 { 529 Name: "undefined variable", 530 Content: ` 531 resource "null_resource" "test" { 532 key = { 533 value = var.undefined_var 534 } 535 }`, 536 Error: Error{ 537 Code: EvaluationError, 538 Level: ErrorLevel, 539 Message: "Failed to eval an expression in main.tf:3; Reference to undeclared input variable: An input variable with the name \"undefined_var\" has not been declared. This variable can be declared with a variable \"undefined_var\" {} block.", 540 }, 541 }, 542 { 543 Name: "no default value", 544 Content: ` 545 variable "no_value_var" {} 546 547 resource "null_resource" "test" { 548 key = { 549 value = var.no_value_var 550 } 551 }`, 552 Error: Error{ 553 Code: UnknownValueError, 554 Level: WarningLevel, 555 Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value", 556 }, 557 }, 558 { 559 Name: "null value", 560 Content: ` 561 variable "null_var" { 562 type = string 563 default = null 564 } 565 566 resource "null_resource" "test" { 567 key = { 568 value = var.null_var 569 } 570 }`, 571 Error: Error{ 572 Code: NullValueError, 573 Level: WarningLevel, 574 Message: "Null value found in main.tf:8", 575 }, 576 }, 577 { 578 Name: "unevalauble", 579 Content: ` 580 resource "null_resource" "test" { 581 key = { 582 value = module.text 583 } 584 }`, 585 Error: Error{ 586 Code: UnevaluableError, 587 Level: WarningLevel, 588 Message: "Unevaluable expression found in main.tf:3", 589 }, 590 }, 591 } 592 593 for _, tc := range cases { 594 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 595 596 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 597 var ret map[string]string 598 err := runner.EvaluateExpr(attribute.Expr, &ret) 599 600 AssertAppError(t, tc.Error, err) 601 return nil 602 }) 603 604 if err != nil { 605 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 606 } 607 } 608 } 609 610 func Test_EvaluateBlock(t *testing.T) { 611 cases := []struct { 612 Name string 613 Content string 614 Expected map[string]string 615 }{ 616 { 617 Name: "map literal", 618 Content: ` 619 resource "null_resource" "test" { 620 key { 621 one = 1 622 two = "2" 623 } 624 }`, 625 Expected: map[string]string{"one": "1", "two": "2"}, 626 }, 627 { 628 Name: "variable", 629 Content: ` 630 variable "one" { 631 default = 1 632 } 633 634 resource "null_resource" "test" { 635 key { 636 one = var.one 637 two = "2" 638 } 639 }`, 640 Expected: map[string]string{"one": "1", "two": "2"}, 641 }, 642 { 643 Name: "null value", 644 Content: ` 645 variable "null_var" { 646 type = string 647 default = null 648 } 649 650 resource "null_resource" "test" { 651 key { 652 one = "1" 653 two = var.null_var 654 } 655 }`, 656 Expected: map[string]string{"one": "1", "two": ""}, 657 }, 658 } 659 660 for _, tc := range cases { 661 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 662 663 err := runner.WalkResourceBlocks("null_resource", "key", func(block *hcl.Block) error { 664 var ret map[string]string 665 schema := &configschema.Block{ 666 Attributes: map[string]*configschema.Attribute{ 667 "one": {Type: cty.String}, 668 "two": {Type: cty.String}, 669 }, 670 } 671 if err := runner.EvaluateBlock(block, schema, &ret); err != nil { 672 return err 673 } 674 675 if !cmp.Equal(tc.Expected, ret) { 676 t.Fatalf("Failed `%s` test: diff: %s", tc.Name, cmp.Diff(tc.Expected, ret)) 677 } 678 return nil 679 }) 680 681 if err != nil { 682 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 683 } 684 } 685 } 686 687 func Test_EvaluateBlock_error(t *testing.T) { 688 cases := []struct { 689 Name string 690 Content string 691 Error Error 692 }{ 693 { 694 Name: "undefined variable", 695 Content: ` 696 resource "null_resource" "test" { 697 key { 698 one = "1" 699 two = var.undefined_var 700 } 701 }`, 702 Error: Error{ 703 Code: EvaluationError, 704 Level: ErrorLevel, 705 Message: "Failed to eval a block in main.tf:3; Reference to undeclared input variable: An input variable with the name \"undefined_var\" has not been declared. This variable can be declared with a variable \"undefined_var\" {} block.", 706 }, 707 }, 708 { 709 Name: "no default value", 710 Content: ` 711 variable "no_value_var" {} 712 713 resource "null_resource" "test" { 714 key { 715 one = "1" 716 two = var.no_value_var 717 } 718 }`, 719 Error: Error{ 720 Code: UnknownValueError, 721 Level: WarningLevel, 722 Message: "Unknown value found in main.tf:5; Please use environment variables or tfvars to set the value", 723 }, 724 }, 725 { 726 Name: "type mismatch", 727 Content: ` 728 resource "null_resource" "test" { 729 key { 730 one = "1" 731 two = { 732 three = 3 733 } 734 } 735 }`, 736 Error: Error{ 737 Code: EvaluationError, 738 Level: ErrorLevel, 739 Message: "Failed to eval a block in main.tf:3; Incorrect attribute value type: Inappropriate value for attribute \"two\": string required.", 740 }, 741 }, 742 { 743 Name: "unevalauble", 744 Content: ` 745 resource "null_resource" "test" { 746 key { 747 one = "1" 748 two = module.text 749 } 750 }`, 751 Error: Error{ 752 Code: UnevaluableError, 753 Level: WarningLevel, 754 Message: "Unevaluable block found in main.tf:3", 755 }, 756 }, 757 } 758 759 for _, tc := range cases { 760 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 761 762 err := runner.WalkResourceBlocks("null_resource", "key", func(block *hcl.Block) error { 763 var ret map[string]string 764 schema := &configschema.Block{ 765 Attributes: map[string]*configschema.Attribute{ 766 "one": {Type: cty.String}, 767 "two": {Type: cty.String}, 768 }, 769 } 770 err := runner.EvaluateBlock(block, schema, &ret) 771 772 AssertAppError(t, tc.Error, err) 773 return nil 774 }) 775 776 if err != nil { 777 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 778 } 779 } 780 } 781 782 func Test_isEvaluableExpr(t *testing.T) { 783 cases := []struct { 784 Name string 785 Content string 786 Expected bool 787 Error string 788 }{ 789 { 790 Name: "literal", 791 Content: ` 792 resource "null_resource" "test" { 793 key = "literal_val" 794 }`, 795 Expected: true, 796 }, 797 { 798 Name: "var syntax", 799 Content: ` 800 resource "null_resource" "test" { 801 key = "${var.string_var}" 802 }`, 803 Expected: true, 804 }, 805 { 806 Name: "new var syntax", 807 Content: ` 808 resource "null_resource" "test" { 809 key = var.string_var 810 }`, 811 Expected: true, 812 }, 813 { 814 Name: "conditional", 815 Content: ` 816 resource "null_resource" "test" { 817 key = "${true ? "production" : "development"}" 818 }`, 819 Expected: true, 820 }, 821 { 822 Name: "function", 823 Content: ` 824 resource "null_resource" "test" { 825 key = "${md5("foo")}" 826 }`, 827 Expected: true, 828 }, 829 { 830 Name: "terraform attributes", 831 Content: ` 832 resource "null_resource" "test" { 833 key = "${terraform.workspace}" 834 }`, 835 Expected: true, 836 }, 837 { 838 Name: "include supported syntax", 839 Content: ` 840 resource "null_resource" "test" { 841 key = "Hello ${var.string_var}" 842 }`, 843 Expected: true, 844 }, 845 { 846 Name: "list", 847 Content: ` 848 resource "null_resource" "test" { 849 key = ["one", "two", "three"] 850 }`, 851 Expected: true, 852 }, 853 { 854 Name: "map", 855 Content: ` 856 resource "null_resource" "test" { 857 key = { 858 one = 1 859 two = 2 860 } 861 }`, 862 Expected: true, 863 }, 864 { 865 Name: "module", 866 Content: ` 867 resource "null_resource" "test" { 868 key = "${module.text}" 869 }`, 870 Expected: false, 871 }, 872 { 873 Name: "resource", 874 Content: ` 875 resource "null_resource" "test" { 876 key = "${aws_subnet.app.id}" 877 }`, 878 Expected: false, 879 }, 880 { 881 Name: "include unsupported syntax", 882 Content: ` 883 resource "null_resource" "test" { 884 key = "${var.text} ${lookup(var.roles, count.index)}" 885 }`, 886 Expected: false, 887 }, 888 { 889 Name: "include unsupported syntax map", 890 Content: ` 891 resource "null_resource" "test" { 892 key = { 893 var = var.text 894 unsupported = aws_subnet.app.id 895 } 896 }`, 897 Expected: false, 898 }, 899 { 900 Name: "path attributes", 901 Content: ` 902 resource "null_resource" "test" { 903 key = path.cwd 904 }`, 905 Expected: true, 906 }, 907 { 908 Name: "invalid reference", 909 Content: ` 910 resource "null_resource" "test" { 911 key = invalid 912 }`, 913 Expected: false, 914 Error: "Invalid reference: A reference to a resource type must be followed by at least one attribute access, specifying the resource name.", 915 }, 916 } 917 918 for _, tc := range cases { 919 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 920 921 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 922 ret, err := isEvaluableExpr(attribute.Expr) 923 if err != nil && tc.Error == "" { 924 t.Fatalf("Failed `%s` test: unexpected error occurred: %s", tc.Name, err) 925 } 926 if err == nil && tc.Error != "" { 927 t.Fatalf("Failed `%s` test: expected error is %s, but no errors", tc.Name, tc.Error) 928 } 929 if err != nil && tc.Error != "" && err.Error() != tc.Error { 930 t.Fatalf("Failed `%s` test: expected error is %s, but got %s", tc.Name, tc.Error, err) 931 } 932 if ret != tc.Expected { 933 t.Fatalf("Failed `%s` test: expected value is %t, but get %t", tc.Name, tc.Expected, ret) 934 } 935 return nil 936 }) 937 938 if err != nil { 939 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 940 } 941 } 942 } 943 944 func Test_overrideVariables(t *testing.T) { 945 cases := []struct { 946 Name string 947 Content string 948 EnvVar map[string]string 949 InputValues []terraform.InputValues 950 Expected string 951 }{ 952 { 953 Name: "override default value by environment variables", 954 Content: ` 955 variable "instance_type" { 956 default = "t2.micro" 957 } 958 959 resource "null_resource" "test" { 960 key = "${var.instance_type}" 961 }`, 962 EnvVar: map[string]string{"TF_VAR_instance_type": "m4.large"}, 963 Expected: "m4.large", 964 }, 965 { 966 Name: "override environment variables by passed variables", 967 Content: ` 968 variable "instance_type" {} 969 970 resource "null_resource" "test" { 971 key = "${var.instance_type}" 972 }`, 973 EnvVar: map[string]string{"TF_VAR_instance_type": "m4.large"}, 974 InputValues: []terraform.InputValues{ 975 terraform.InputValues{ 976 "instance_type": &terraform.InputValue{ 977 Value: cty.StringVal("c5.2xlarge"), 978 SourceType: terraform.ValueFromNamedFile, 979 }, 980 }, 981 }, 982 Expected: "c5.2xlarge", 983 }, 984 { 985 Name: "override variables by variables passed later", 986 Content: ` 987 variable "instance_type" {} 988 989 resource "null_resource" "test" { 990 key = "${var.instance_type}" 991 }`, 992 InputValues: []terraform.InputValues{ 993 terraform.InputValues{ 994 "instance_type": &terraform.InputValue{ 995 Value: cty.StringVal("c5.2xlarge"), 996 SourceType: terraform.ValueFromNamedFile, 997 }, 998 }, 999 terraform.InputValues{ 1000 "instance_type": &terraform.InputValue{ 1001 Value: cty.StringVal("p3.8xlarge"), 1002 SourceType: terraform.ValueFromNamedFile, 1003 }, 1004 }, 1005 }, 1006 Expected: "p3.8xlarge", 1007 }, 1008 } 1009 1010 for _, tc := range cases { 1011 withEnvVars(t, tc.EnvVar, func() { 1012 runner := testRunnerWithInputVariables(t, map[string]string{"main.tf": tc.Content}, tc.InputValues...) 1013 1014 err := runner.WalkResourceAttributes("null_resource", "key", func(attribute *hcl.Attribute) error { 1015 var ret string 1016 err := runner.EvaluateExpr(attribute.Expr, &ret) 1017 if err != nil { 1018 return err 1019 } 1020 1021 if tc.Expected != ret { 1022 t.Fatalf("Failed `%s` test: expected value is %s, but get %s", tc.Name, tc.Expected, ret) 1023 } 1024 return nil 1025 }) 1026 1027 if err != nil { 1028 t.Fatalf("Failed `%s` test: `%s` occurred", tc.Name, err) 1029 } 1030 }) 1031 } 1032 } 1033 1034 func Test_willEvaluateResource(t *testing.T) { 1035 cases := []struct { 1036 Name string 1037 Content string 1038 Expected bool 1039 }{ 1040 { 1041 Name: "no meta-arguments", 1042 Content: ` 1043 resource "null_resource" "test" { 1044 }`, 1045 Expected: true, 1046 }, 1047 { 1048 Name: "count is not zero (literal)", 1049 Content: ` 1050 resource "null_resource" "test" { 1051 count = 1 1052 }`, 1053 Expected: true, 1054 }, 1055 { 1056 Name: "count is not zero (variable)", 1057 Content: ` 1058 variable "foo" { 1059 default = 1 1060 } 1061 1062 resource "null_resource" "test" { 1063 count = var.foo 1064 }`, 1065 Expected: true, 1066 }, 1067 { 1068 Name: "count is unevaluable", 1069 Content: ` 1070 variable "foo" {} 1071 1072 resource "null_resource" "test" { 1073 count = var.foo 1074 }`, 1075 Expected: false, 1076 }, 1077 { 1078 Name: "count is zero", 1079 Content: ` 1080 resource "null_resource" "test" { 1081 count = 0 1082 }`, 1083 Expected: false, 1084 }, 1085 { 1086 Name: "for_each is not empty (literal)", 1087 Content: ` 1088 resource "null_resource" "test" { 1089 for_each = { 1090 foo = "bar" 1091 } 1092 }`, 1093 Expected: true, 1094 }, 1095 { 1096 Name: "for_each is not empty (variable)", 1097 Content: ` 1098 variable "object" { 1099 default = { 1100 foo = "bar" 1101 } 1102 } 1103 1104 resource "null_resource" "test" { 1105 for_each = var.object 1106 }`, 1107 Expected: true, 1108 }, 1109 { 1110 Name: "for_each is unevaluable", 1111 Content: ` 1112 variable "foo" {} 1113 1114 resource "null_resource" "test" { 1115 for_each = var.foo 1116 }`, 1117 Expected: false, 1118 }, 1119 { 1120 Name: "for_each is empty", 1121 Content: ` 1122 resource "null_resource" "test" { 1123 for_each = {} 1124 }`, 1125 Expected: false, 1126 }, 1127 { 1128 Name: "for_each is not empty set", 1129 Content: ` 1130 resource "null_resource" "test" { 1131 for_each = toset(["foo", "bar"]) 1132 }`, 1133 Expected: true, 1134 }, 1135 { 1136 Name: "for_each is empty set", 1137 Content: ` 1138 resource "null_resource" "test" { 1139 for_each = toset([]) 1140 }`, 1141 Expected: false, 1142 }, 1143 } 1144 1145 for _, tc := range cases { 1146 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 1147 1148 got, err := runner.willEvaluateResource(runner.LookupResourcesByType("null_resource")[0]) 1149 if err != nil { 1150 t.Fatalf("Failed `%s`: %s", tc.Name, err) 1151 } 1152 1153 if got != tc.Expected { 1154 t.Fatalf("Failed `%s`: expect to get %t, but got %t", tc.Name, tc.Expected, got) 1155 } 1156 } 1157 } 1158 1159 func Test_willEvaluateResource_Error(t *testing.T) { 1160 cases := []struct { 1161 Name string 1162 Content string 1163 Error error 1164 }{ 1165 { 1166 Name: "not iterable", 1167 Content: ` 1168 resource "null_resource" "test" { 1169 for_each = "foo" 1170 }`, 1171 Error: errors.New("The `for_each` value is not iterable in main.tf:3"), 1172 }, 1173 { 1174 Name: "eval error", 1175 Content: ` 1176 resource "null_resource" "test" { 1177 for_each = var.undefined 1178 }`, 1179 Error: &Error{ 1180 Code: EvaluationError, 1181 Level: ErrorLevel, 1182 Message: "Failed to eval an expression in main.tf:3; Reference to undeclared input variable: An input variable with the name \"undefined\" has not been declared. This variable can be declared with a variable \"undefined\" {} block.", 1183 }, 1184 }, 1185 } 1186 1187 for _, tc := range cases { 1188 runner := TestRunner(t, map[string]string{"main.tf": tc.Content}) 1189 1190 _, err := runner.willEvaluateResource(runner.LookupResourcesByType("null_resource")[0]) 1191 if err == nil { 1192 t.Fatalf("Failed `%s`: expected to get an error, but not", tc.Name) 1193 } 1194 if err.Error() != tc.Error.Error() { 1195 t.Fatalf("Failed `%s`: expected to get '%s', but got '%s'", tc.Name, tc.Error.Error(), err.Error()) 1196 } 1197 } 1198 }