github.com/arvindram03/terraform@v0.3.7-0.20150212015210-408f838db36d/helper/schema/schema_test.go (about) 1 package schema 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "reflect" 8 "testing" 9 10 "github.com/hashicorp/terraform/config" 11 "github.com/hashicorp/terraform/config/lang/ast" 12 "github.com/hashicorp/terraform/helper/hashcode" 13 "github.com/hashicorp/terraform/terraform" 14 ) 15 16 func TestEnvDefaultFunc(t *testing.T) { 17 key := "TF_TEST_ENV_DEFAULT_FUNC" 18 defer os.Unsetenv(key) 19 20 f := EnvDefaultFunc(key, "42") 21 if err := os.Setenv(key, "foo"); err != nil { 22 t.Fatalf("err: %s", err) 23 } 24 25 actual, err := f() 26 if err != nil { 27 t.Fatalf("err: %s", err) 28 } 29 if actual != "foo" { 30 t.Fatalf("bad: %#v", actual) 31 } 32 33 if err := os.Unsetenv(key); err != nil { 34 t.Fatalf("err: %s", err) 35 } 36 37 actual, err = f() 38 if err != nil { 39 t.Fatalf("err: %s", err) 40 } 41 if actual != "42" { 42 t.Fatalf("bad: %#v", actual) 43 } 44 } 45 46 func TestMultiEnvDefaultFunc(t *testing.T) { 47 keys := []string{ 48 "TF_TEST_MULTI_ENV_DEFAULT_FUNC1", 49 "TF_TEST_MULTI_ENV_DEFAULT_FUNC2", 50 } 51 defer func() { 52 for _, k := range keys { 53 os.Unsetenv(k) 54 } 55 }() 56 57 // Test that the first key is returned first 58 f := MultiEnvDefaultFunc(keys, "42") 59 if err := os.Setenv(keys[0], "foo"); err != nil { 60 t.Fatalf("err: %s", err) 61 } 62 63 actual, err := f() 64 if err != nil { 65 t.Fatalf("err: %s", err) 66 } 67 if actual != "foo" { 68 t.Fatalf("bad: %#v", actual) 69 } 70 71 if err := os.Unsetenv(keys[0]); err != nil { 72 t.Fatalf("err: %s", err) 73 } 74 75 // Test that the second key is returned if the first one is empty 76 f = MultiEnvDefaultFunc(keys, "42") 77 if err := os.Setenv(keys[1], "foo"); err != nil { 78 t.Fatalf("err: %s", err) 79 } 80 81 actual, err = f() 82 if err != nil { 83 t.Fatalf("err: %s", err) 84 } 85 if actual != "foo" { 86 t.Fatalf("bad: %#v", actual) 87 } 88 89 if err := os.Unsetenv(keys[1]); err != nil { 90 t.Fatalf("err: %s", err) 91 } 92 93 // Test that the default value is returned when no keys are set 94 actual, err = f() 95 if err != nil { 96 t.Fatalf("err: %s", err) 97 } 98 if actual != "42" { 99 t.Fatalf("bad: %#v", actual) 100 } 101 } 102 103 func TestValueType_Zero(t *testing.T) { 104 cases := []struct { 105 Type ValueType 106 Value interface{} 107 }{ 108 {TypeBool, false}, 109 {TypeInt, 0}, 110 {TypeFloat, 0.0}, 111 {TypeString, ""}, 112 {TypeList, []interface{}{}}, 113 {TypeMap, map[string]interface{}{}}, 114 {TypeSet, nil}, 115 } 116 117 for i, tc := range cases { 118 actual := tc.Type.Zero() 119 if !reflect.DeepEqual(actual, tc.Value) { 120 t.Fatalf("%d: %#v != %#v", i, actual, tc.Value) 121 } 122 } 123 } 124 125 func TestSchemaMap_Diff(t *testing.T) { 126 cases := []struct { 127 Schema map[string]*Schema 128 State *terraform.InstanceState 129 Config map[string]interface{} 130 ConfigVariables map[string]string 131 Diff *terraform.InstanceDiff 132 Err bool 133 }{ 134 /* 135 * String decode 136 */ 137 138 // #0 139 { 140 Schema: map[string]*Schema{ 141 "availability_zone": &Schema{ 142 Type: TypeString, 143 Optional: true, 144 Computed: true, 145 ForceNew: true, 146 }, 147 }, 148 149 State: nil, 150 151 Config: map[string]interface{}{ 152 "availability_zone": "foo", 153 }, 154 155 Diff: &terraform.InstanceDiff{ 156 Attributes: map[string]*terraform.ResourceAttrDiff{ 157 "availability_zone": &terraform.ResourceAttrDiff{ 158 Old: "", 159 New: "foo", 160 RequiresNew: true, 161 }, 162 }, 163 }, 164 165 Err: false, 166 }, 167 168 // #1 169 { 170 Schema: map[string]*Schema{ 171 "availability_zone": &Schema{ 172 Type: TypeString, 173 Optional: true, 174 Computed: true, 175 ForceNew: true, 176 }, 177 }, 178 179 State: nil, 180 181 Config: map[string]interface{}{}, 182 183 Diff: &terraform.InstanceDiff{ 184 Attributes: map[string]*terraform.ResourceAttrDiff{ 185 "availability_zone": &terraform.ResourceAttrDiff{ 186 Old: "", 187 NewComputed: true, 188 RequiresNew: true, 189 }, 190 }, 191 }, 192 193 Err: false, 194 }, 195 196 // #2 197 { 198 Schema: map[string]*Schema{ 199 "availability_zone": &Schema{ 200 Type: TypeString, 201 Optional: true, 202 Computed: true, 203 ForceNew: true, 204 }, 205 }, 206 207 State: &terraform.InstanceState{ 208 ID: "foo", 209 }, 210 211 Config: map[string]interface{}{}, 212 213 Diff: nil, 214 215 Err: false, 216 }, 217 218 // #3 Computed, but set in config 219 { 220 Schema: map[string]*Schema{ 221 "availability_zone": &Schema{ 222 Type: TypeString, 223 Optional: true, 224 Computed: true, 225 }, 226 }, 227 228 State: &terraform.InstanceState{ 229 Attributes: map[string]string{ 230 "availability_zone": "foo", 231 }, 232 }, 233 234 Config: map[string]interface{}{ 235 "availability_zone": "bar", 236 }, 237 238 Diff: &terraform.InstanceDiff{ 239 Attributes: map[string]*terraform.ResourceAttrDiff{ 240 "availability_zone": &terraform.ResourceAttrDiff{ 241 Old: "foo", 242 New: "bar", 243 }, 244 }, 245 }, 246 247 Err: false, 248 }, 249 250 // #4 Default 251 { 252 Schema: map[string]*Schema{ 253 "availability_zone": &Schema{ 254 Type: TypeString, 255 Optional: true, 256 Default: "foo", 257 }, 258 }, 259 260 State: nil, 261 262 Config: nil, 263 264 Diff: &terraform.InstanceDiff{ 265 Attributes: map[string]*terraform.ResourceAttrDiff{ 266 "availability_zone": &terraform.ResourceAttrDiff{ 267 Old: "", 268 New: "foo", 269 }, 270 }, 271 }, 272 273 Err: false, 274 }, 275 276 // #5 DefaultFunc, value 277 { 278 Schema: map[string]*Schema{ 279 "availability_zone": &Schema{ 280 Type: TypeString, 281 Optional: true, 282 DefaultFunc: func() (interface{}, error) { 283 return "foo", nil 284 }, 285 }, 286 }, 287 288 State: nil, 289 290 Config: nil, 291 292 Diff: &terraform.InstanceDiff{ 293 Attributes: map[string]*terraform.ResourceAttrDiff{ 294 "availability_zone": &terraform.ResourceAttrDiff{ 295 Old: "", 296 New: "foo", 297 }, 298 }, 299 }, 300 301 Err: false, 302 }, 303 304 // #6 DefaultFunc, configuration set 305 { 306 Schema: map[string]*Schema{ 307 "availability_zone": &Schema{ 308 Type: TypeString, 309 Optional: true, 310 DefaultFunc: func() (interface{}, error) { 311 return "foo", nil 312 }, 313 }, 314 }, 315 316 State: nil, 317 318 Config: map[string]interface{}{ 319 "availability_zone": "bar", 320 }, 321 322 Diff: &terraform.InstanceDiff{ 323 Attributes: map[string]*terraform.ResourceAttrDiff{ 324 "availability_zone": &terraform.ResourceAttrDiff{ 325 Old: "", 326 New: "bar", 327 }, 328 }, 329 }, 330 331 Err: false, 332 }, 333 334 // #7 String with StateFunc 335 { 336 Schema: map[string]*Schema{ 337 "availability_zone": &Schema{ 338 Type: TypeString, 339 Optional: true, 340 Computed: true, 341 StateFunc: func(a interface{}) string { 342 return a.(string) + "!" 343 }, 344 }, 345 }, 346 347 State: nil, 348 349 Config: map[string]interface{}{ 350 "availability_zone": "foo", 351 }, 352 353 Diff: &terraform.InstanceDiff{ 354 Attributes: map[string]*terraform.ResourceAttrDiff{ 355 "availability_zone": &terraform.ResourceAttrDiff{ 356 Old: "", 357 New: "foo!", 358 NewExtra: "foo", 359 }, 360 }, 361 }, 362 363 Err: false, 364 }, 365 366 // #8 Variable (just checking) 367 { 368 Schema: map[string]*Schema{ 369 "availability_zone": &Schema{ 370 Type: TypeString, 371 Optional: true, 372 }, 373 }, 374 375 State: nil, 376 377 Config: map[string]interface{}{ 378 "availability_zone": "${var.foo}", 379 }, 380 381 ConfigVariables: map[string]string{ 382 "var.foo": "bar", 383 }, 384 385 Diff: &terraform.InstanceDiff{ 386 Attributes: map[string]*terraform.ResourceAttrDiff{ 387 "availability_zone": &terraform.ResourceAttrDiff{ 388 Old: "", 389 New: "bar", 390 }, 391 }, 392 }, 393 394 Err: false, 395 }, 396 397 // #9 Variable computed 398 { 399 Schema: map[string]*Schema{ 400 "availability_zone": &Schema{ 401 Type: TypeString, 402 Optional: true, 403 }, 404 }, 405 406 State: nil, 407 408 Config: map[string]interface{}{ 409 "availability_zone": "${var.foo}", 410 }, 411 412 ConfigVariables: map[string]string{ 413 "var.foo": config.UnknownVariableValue, 414 }, 415 416 Diff: &terraform.InstanceDiff{ 417 Attributes: map[string]*terraform.ResourceAttrDiff{ 418 "availability_zone": &terraform.ResourceAttrDiff{ 419 Old: "", 420 New: "${var.foo}", 421 }, 422 }, 423 }, 424 425 Err: false, 426 }, 427 428 /* 429 * Int decode 430 */ 431 432 // #10 433 { 434 Schema: map[string]*Schema{ 435 "port": &Schema{ 436 Type: TypeInt, 437 Optional: true, 438 Computed: true, 439 ForceNew: true, 440 }, 441 }, 442 443 State: nil, 444 445 Config: map[string]interface{}{ 446 "port": 27, 447 }, 448 449 Diff: &terraform.InstanceDiff{ 450 Attributes: map[string]*terraform.ResourceAttrDiff{ 451 "port": &terraform.ResourceAttrDiff{ 452 Old: "", 453 New: "27", 454 RequiresNew: true, 455 }, 456 }, 457 }, 458 459 Err: false, 460 }, 461 462 /* 463 * Bool decode 464 */ 465 466 // #11 467 { 468 Schema: map[string]*Schema{ 469 "port": &Schema{ 470 Type: TypeBool, 471 Optional: true, 472 Computed: true, 473 ForceNew: true, 474 }, 475 }, 476 477 State: nil, 478 479 Config: map[string]interface{}{ 480 "port": false, 481 }, 482 483 Diff: &terraform.InstanceDiff{ 484 Attributes: map[string]*terraform.ResourceAttrDiff{ 485 "port": &terraform.ResourceAttrDiff{ 486 Old: "", 487 New: "0", 488 RequiresNew: true, 489 }, 490 }, 491 }, 492 493 Err: false, 494 }, 495 496 /* 497 * Bool 498 */ 499 500 // #12 501 { 502 Schema: map[string]*Schema{ 503 "delete": &Schema{ 504 Type: TypeBool, 505 Optional: true, 506 Default: false, 507 }, 508 }, 509 510 State: &terraform.InstanceState{ 511 Attributes: map[string]string{ 512 "delete": "false", 513 }, 514 }, 515 516 Config: nil, 517 518 Diff: nil, 519 520 Err: false, 521 }, 522 523 /* 524 * List decode 525 */ 526 527 // #13 528 { 529 Schema: map[string]*Schema{ 530 "ports": &Schema{ 531 Type: TypeList, 532 Required: true, 533 Elem: &Schema{Type: TypeInt}, 534 }, 535 }, 536 537 State: nil, 538 539 Config: map[string]interface{}{ 540 "ports": []interface{}{1, 2, 5}, 541 }, 542 543 Diff: &terraform.InstanceDiff{ 544 Attributes: map[string]*terraform.ResourceAttrDiff{ 545 "ports.#": &terraform.ResourceAttrDiff{ 546 Old: "0", 547 New: "3", 548 }, 549 "ports.0": &terraform.ResourceAttrDiff{ 550 Old: "", 551 New: "1", 552 }, 553 "ports.1": &terraform.ResourceAttrDiff{ 554 Old: "", 555 New: "2", 556 }, 557 "ports.2": &terraform.ResourceAttrDiff{ 558 Old: "", 559 New: "5", 560 }, 561 }, 562 }, 563 564 Err: false, 565 }, 566 567 // #14 568 { 569 Schema: map[string]*Schema{ 570 "ports": &Schema{ 571 Type: TypeList, 572 Required: true, 573 Elem: &Schema{Type: TypeInt}, 574 }, 575 }, 576 577 State: nil, 578 579 Config: map[string]interface{}{ 580 "ports": []interface{}{1, "${var.foo}"}, 581 }, 582 583 ConfigVariables: map[string]string{ 584 "var.foo": "2" + config.InterpSplitDelim + "5", 585 }, 586 587 Diff: &terraform.InstanceDiff{ 588 Attributes: map[string]*terraform.ResourceAttrDiff{ 589 "ports.#": &terraform.ResourceAttrDiff{ 590 Old: "0", 591 New: "3", 592 }, 593 "ports.0": &terraform.ResourceAttrDiff{ 594 Old: "", 595 New: "1", 596 }, 597 "ports.1": &terraform.ResourceAttrDiff{ 598 Old: "", 599 New: "2", 600 }, 601 "ports.2": &terraform.ResourceAttrDiff{ 602 Old: "", 603 New: "5", 604 }, 605 }, 606 }, 607 608 Err: false, 609 }, 610 611 // #15 612 { 613 Schema: map[string]*Schema{ 614 "ports": &Schema{ 615 Type: TypeList, 616 Required: true, 617 Elem: &Schema{Type: TypeInt}, 618 }, 619 }, 620 621 State: nil, 622 623 Config: map[string]interface{}{ 624 "ports": []interface{}{1, "${var.foo}"}, 625 }, 626 627 ConfigVariables: map[string]string{ 628 "var.foo": config.UnknownVariableValue + 629 config.InterpSplitDelim + "5", 630 }, 631 632 Diff: &terraform.InstanceDiff{ 633 Attributes: map[string]*terraform.ResourceAttrDiff{ 634 "ports.#": &terraform.ResourceAttrDiff{ 635 Old: "0", 636 New: "", 637 NewComputed: true, 638 }, 639 }, 640 }, 641 642 Err: false, 643 }, 644 645 // #16 646 { 647 Schema: map[string]*Schema{ 648 "ports": &Schema{ 649 Type: TypeList, 650 Required: true, 651 Elem: &Schema{Type: TypeInt}, 652 }, 653 }, 654 655 State: &terraform.InstanceState{ 656 Attributes: map[string]string{ 657 "ports.#": "3", 658 "ports.0": "1", 659 "ports.1": "2", 660 "ports.2": "5", 661 }, 662 }, 663 664 Config: map[string]interface{}{ 665 "ports": []interface{}{1, 2, 5}, 666 }, 667 668 Diff: nil, 669 670 Err: false, 671 }, 672 673 // #17 674 { 675 Schema: map[string]*Schema{ 676 "ports": &Schema{ 677 Type: TypeList, 678 Required: true, 679 Elem: &Schema{Type: TypeInt}, 680 }, 681 }, 682 683 State: &terraform.InstanceState{ 684 Attributes: map[string]string{ 685 "ports.#": "2", 686 "ports.0": "1", 687 "ports.1": "2", 688 }, 689 }, 690 691 Config: map[string]interface{}{ 692 "ports": []interface{}{1, 2, 5}, 693 }, 694 695 Diff: &terraform.InstanceDiff{ 696 Attributes: map[string]*terraform.ResourceAttrDiff{ 697 "ports.#": &terraform.ResourceAttrDiff{ 698 Old: "2", 699 New: "3", 700 }, 701 "ports.2": &terraform.ResourceAttrDiff{ 702 Old: "", 703 New: "5", 704 }, 705 }, 706 }, 707 708 Err: false, 709 }, 710 711 // #18 712 { 713 Schema: map[string]*Schema{ 714 "ports": &Schema{ 715 Type: TypeList, 716 Required: true, 717 Elem: &Schema{Type: TypeInt}, 718 ForceNew: true, 719 }, 720 }, 721 722 State: nil, 723 724 Config: map[string]interface{}{ 725 "ports": []interface{}{1, 2, 5}, 726 }, 727 728 Diff: &terraform.InstanceDiff{ 729 Attributes: map[string]*terraform.ResourceAttrDiff{ 730 "ports.#": &terraform.ResourceAttrDiff{ 731 Old: "0", 732 New: "3", 733 RequiresNew: true, 734 }, 735 "ports.0": &terraform.ResourceAttrDiff{ 736 Old: "", 737 New: "1", 738 RequiresNew: true, 739 }, 740 "ports.1": &terraform.ResourceAttrDiff{ 741 Old: "", 742 New: "2", 743 RequiresNew: true, 744 }, 745 "ports.2": &terraform.ResourceAttrDiff{ 746 Old: "", 747 New: "5", 748 RequiresNew: true, 749 }, 750 }, 751 }, 752 753 Err: false, 754 }, 755 756 // #19 757 { 758 Schema: map[string]*Schema{ 759 "ports": &Schema{ 760 Type: TypeList, 761 Optional: true, 762 Computed: true, 763 Elem: &Schema{Type: TypeInt}, 764 }, 765 }, 766 767 State: nil, 768 769 Config: map[string]interface{}{}, 770 771 Diff: &terraform.InstanceDiff{ 772 Attributes: map[string]*terraform.ResourceAttrDiff{ 773 "ports.#": &terraform.ResourceAttrDiff{ 774 Old: "", 775 NewComputed: true, 776 }, 777 }, 778 }, 779 780 Err: false, 781 }, 782 783 /* 784 * Set 785 */ 786 787 // #20 788 { 789 Schema: map[string]*Schema{ 790 "ports": &Schema{ 791 Type: TypeSet, 792 Required: true, 793 Elem: &Schema{Type: TypeInt}, 794 Set: func(a interface{}) int { 795 return a.(int) 796 }, 797 }, 798 }, 799 800 State: nil, 801 802 Config: map[string]interface{}{ 803 "ports": []interface{}{5, 2, 1}, 804 }, 805 806 Diff: &terraform.InstanceDiff{ 807 Attributes: map[string]*terraform.ResourceAttrDiff{ 808 "ports.#": &terraform.ResourceAttrDiff{ 809 Old: "0", 810 New: "3", 811 }, 812 "ports.1": &terraform.ResourceAttrDiff{ 813 Old: "", 814 New: "1", 815 }, 816 "ports.2": &terraform.ResourceAttrDiff{ 817 Old: "", 818 New: "2", 819 }, 820 "ports.5": &terraform.ResourceAttrDiff{ 821 Old: "", 822 New: "5", 823 }, 824 }, 825 }, 826 827 Err: false, 828 }, 829 830 // #21 831 { 832 Schema: map[string]*Schema{ 833 "ports": &Schema{ 834 Type: TypeSet, 835 Computed: true, 836 Required: true, 837 Elem: &Schema{Type: TypeInt}, 838 Set: func(a interface{}) int { 839 return a.(int) 840 }, 841 }, 842 }, 843 844 State: &terraform.InstanceState{ 845 Attributes: map[string]string{ 846 "ports.#": "0", 847 }, 848 }, 849 850 Config: nil, 851 852 Diff: nil, 853 854 Err: false, 855 }, 856 857 // #22 858 { 859 Schema: map[string]*Schema{ 860 "ports": &Schema{ 861 Type: TypeSet, 862 Optional: true, 863 Computed: true, 864 Elem: &Schema{Type: TypeInt}, 865 Set: func(a interface{}) int { 866 return a.(int) 867 }, 868 }, 869 }, 870 871 State: nil, 872 873 Config: nil, 874 875 Diff: &terraform.InstanceDiff{ 876 Attributes: map[string]*terraform.ResourceAttrDiff{ 877 "ports.#": &terraform.ResourceAttrDiff{ 878 Old: "", 879 NewComputed: true, 880 }, 881 }, 882 }, 883 884 Err: false, 885 }, 886 887 // #23 888 { 889 Schema: map[string]*Schema{ 890 "ports": &Schema{ 891 Type: TypeSet, 892 Required: true, 893 Elem: &Schema{Type: TypeInt}, 894 Set: func(a interface{}) int { 895 return a.(int) 896 }, 897 }, 898 }, 899 900 State: nil, 901 902 Config: map[string]interface{}{ 903 "ports": []interface{}{"${var.foo}", 1}, 904 }, 905 906 ConfigVariables: map[string]string{ 907 "var.foo": "2" + config.InterpSplitDelim + "5", 908 }, 909 910 Diff: &terraform.InstanceDiff{ 911 Attributes: map[string]*terraform.ResourceAttrDiff{ 912 "ports.#": &terraform.ResourceAttrDiff{ 913 Old: "0", 914 New: "3", 915 }, 916 "ports.1": &terraform.ResourceAttrDiff{ 917 Old: "", 918 New: "1", 919 }, 920 "ports.2": &terraform.ResourceAttrDiff{ 921 Old: "", 922 New: "2", 923 }, 924 "ports.5": &terraform.ResourceAttrDiff{ 925 Old: "", 926 New: "5", 927 }, 928 }, 929 }, 930 931 Err: false, 932 }, 933 934 // #24 935 { 936 Schema: map[string]*Schema{ 937 "ports": &Schema{ 938 Type: TypeSet, 939 Required: true, 940 Elem: &Schema{Type: TypeInt}, 941 Set: func(a interface{}) int { 942 return a.(int) 943 }, 944 }, 945 }, 946 947 State: nil, 948 949 Config: map[string]interface{}{ 950 "ports": []interface{}{1, "${var.foo}"}, 951 }, 952 953 ConfigVariables: map[string]string{ 954 "var.foo": config.UnknownVariableValue + 955 config.InterpSplitDelim + "5", 956 }, 957 958 Diff: &terraform.InstanceDiff{ 959 Attributes: map[string]*terraform.ResourceAttrDiff{ 960 "ports.#": &terraform.ResourceAttrDiff{ 961 Old: "", 962 New: "", 963 NewComputed: true, 964 }, 965 }, 966 }, 967 968 Err: false, 969 }, 970 971 // #25 972 { 973 Schema: map[string]*Schema{ 974 "ports": &Schema{ 975 Type: TypeSet, 976 Required: true, 977 Elem: &Schema{Type: TypeInt}, 978 Set: func(a interface{}) int { 979 return a.(int) 980 }, 981 }, 982 }, 983 984 State: &terraform.InstanceState{ 985 Attributes: map[string]string{ 986 "ports.#": "2", 987 "ports.1": "1", 988 "ports.2": "2", 989 }, 990 }, 991 992 Config: map[string]interface{}{ 993 "ports": []interface{}{5, 2, 1}, 994 }, 995 996 Diff: &terraform.InstanceDiff{ 997 Attributes: map[string]*terraform.ResourceAttrDiff{ 998 "ports.#": &terraform.ResourceAttrDiff{ 999 Old: "2", 1000 New: "3", 1001 }, 1002 "ports.1": &terraform.ResourceAttrDiff{ 1003 Old: "1", 1004 New: "1", 1005 }, 1006 "ports.2": &terraform.ResourceAttrDiff{ 1007 Old: "2", 1008 New: "2", 1009 }, 1010 "ports.5": &terraform.ResourceAttrDiff{ 1011 Old: "", 1012 New: "5", 1013 }, 1014 }, 1015 }, 1016 1017 Err: false, 1018 }, 1019 1020 // #26 1021 { 1022 Schema: map[string]*Schema{ 1023 "ports": &Schema{ 1024 Type: TypeSet, 1025 Required: true, 1026 Elem: &Schema{Type: TypeInt}, 1027 Set: func(a interface{}) int { 1028 return a.(int) 1029 }, 1030 }, 1031 }, 1032 1033 State: &terraform.InstanceState{ 1034 Attributes: map[string]string{ 1035 "ports.#": "2", 1036 "ports.1": "1", 1037 "ports.2": "2", 1038 }, 1039 }, 1040 1041 Config: map[string]interface{}{}, 1042 1043 Diff: &terraform.InstanceDiff{ 1044 Attributes: map[string]*terraform.ResourceAttrDiff{ 1045 "ports.#": &terraform.ResourceAttrDiff{ 1046 Old: "2", 1047 New: "0", 1048 }, 1049 }, 1050 }, 1051 1052 Err: false, 1053 }, 1054 1055 // #27 1056 { 1057 Schema: map[string]*Schema{ 1058 "ports": &Schema{ 1059 Type: TypeSet, 1060 Optional: true, 1061 Computed: true, 1062 Elem: &Schema{Type: TypeInt}, 1063 Set: func(a interface{}) int { 1064 return a.(int) 1065 }, 1066 }, 1067 }, 1068 1069 State: &terraform.InstanceState{ 1070 Attributes: map[string]string{ 1071 "availability_zone": "bar", 1072 "ports.#": "1", 1073 "ports.80": "80", 1074 }, 1075 }, 1076 1077 Config: map[string]interface{}{}, 1078 1079 Diff: nil, 1080 1081 Err: false, 1082 }, 1083 1084 // #28 1085 { 1086 Schema: map[string]*Schema{ 1087 "ingress": &Schema{ 1088 Type: TypeSet, 1089 Required: true, 1090 Elem: &Resource{ 1091 Schema: map[string]*Schema{ 1092 "ports": &Schema{ 1093 Type: TypeList, 1094 Optional: true, 1095 Elem: &Schema{Type: TypeInt}, 1096 }, 1097 }, 1098 }, 1099 Set: func(v interface{}) int { 1100 m := v.(map[string]interface{}) 1101 ps := m["ports"].([]interface{}) 1102 result := 0 1103 for _, p := range ps { 1104 result += p.(int) 1105 } 1106 return result 1107 }, 1108 }, 1109 }, 1110 1111 State: &terraform.InstanceState{ 1112 Attributes: map[string]string{ 1113 "ingress.#": "2", 1114 "ingress.80.ports.#": "1", 1115 "ingress.80.ports.0": "80", 1116 "ingress.443.ports.#": "1", 1117 "ingress.443.ports.0": "443", 1118 }, 1119 }, 1120 1121 Config: map[string]interface{}{ 1122 "ingress": []map[string]interface{}{ 1123 map[string]interface{}{ 1124 "ports": []interface{}{443}, 1125 }, 1126 map[string]interface{}{ 1127 "ports": []interface{}{80}, 1128 }, 1129 }, 1130 }, 1131 1132 Diff: nil, 1133 1134 Err: false, 1135 }, 1136 1137 /* 1138 * List of structure decode 1139 */ 1140 1141 // #29 1142 { 1143 Schema: map[string]*Schema{ 1144 "ingress": &Schema{ 1145 Type: TypeList, 1146 Required: true, 1147 Elem: &Resource{ 1148 Schema: map[string]*Schema{ 1149 "from": &Schema{ 1150 Type: TypeInt, 1151 Required: true, 1152 }, 1153 }, 1154 }, 1155 }, 1156 }, 1157 1158 State: nil, 1159 1160 Config: map[string]interface{}{ 1161 "ingress": []interface{}{ 1162 map[string]interface{}{ 1163 "from": 8080, 1164 }, 1165 }, 1166 }, 1167 1168 Diff: &terraform.InstanceDiff{ 1169 Attributes: map[string]*terraform.ResourceAttrDiff{ 1170 "ingress.#": &terraform.ResourceAttrDiff{ 1171 Old: "0", 1172 New: "1", 1173 }, 1174 "ingress.0.from": &terraform.ResourceAttrDiff{ 1175 Old: "", 1176 New: "8080", 1177 }, 1178 }, 1179 }, 1180 1181 Err: false, 1182 }, 1183 1184 /* 1185 * ComputedWhen 1186 */ 1187 1188 // #30 1189 { 1190 Schema: map[string]*Schema{ 1191 "availability_zone": &Schema{ 1192 Type: TypeString, 1193 Computed: true, 1194 ComputedWhen: []string{"port"}, 1195 }, 1196 1197 "port": &Schema{ 1198 Type: TypeInt, 1199 Optional: true, 1200 }, 1201 }, 1202 1203 State: &terraform.InstanceState{ 1204 Attributes: map[string]string{ 1205 "availability_zone": "foo", 1206 "port": "80", 1207 }, 1208 }, 1209 1210 Config: map[string]interface{}{ 1211 "port": 80, 1212 }, 1213 1214 Diff: nil, 1215 1216 Err: false, 1217 }, 1218 1219 // #31 1220 { 1221 Schema: map[string]*Schema{ 1222 "availability_zone": &Schema{ 1223 Type: TypeString, 1224 Computed: true, 1225 ComputedWhen: []string{"port"}, 1226 }, 1227 1228 "port": &Schema{ 1229 Type: TypeInt, 1230 Optional: true, 1231 }, 1232 }, 1233 1234 State: &terraform.InstanceState{ 1235 Attributes: map[string]string{ 1236 "port": "80", 1237 }, 1238 }, 1239 1240 Config: map[string]interface{}{ 1241 "port": 80, 1242 }, 1243 1244 Diff: &terraform.InstanceDiff{ 1245 Attributes: map[string]*terraform.ResourceAttrDiff{ 1246 "availability_zone": &terraform.ResourceAttrDiff{ 1247 NewComputed: true, 1248 }, 1249 }, 1250 }, 1251 1252 Err: false, 1253 }, 1254 1255 /* TODO 1256 { 1257 Schema: map[string]*Schema{ 1258 "availability_zone": &Schema{ 1259 Type: TypeString, 1260 Computed: true, 1261 ComputedWhen: []string{"port"}, 1262 }, 1263 1264 "port": &Schema{ 1265 Type: TypeInt, 1266 Optional: true, 1267 }, 1268 }, 1269 1270 State: &terraform.InstanceState{ 1271 Attributes: map[string]string{ 1272 "availability_zone": "foo", 1273 "port": "80", 1274 }, 1275 }, 1276 1277 Config: map[string]interface{}{ 1278 "port": 8080, 1279 }, 1280 1281 Diff: &terraform.ResourceDiff{ 1282 Attributes: map[string]*terraform.ResourceAttrDiff{ 1283 "availability_zone": &terraform.ResourceAttrDiff{ 1284 Old: "foo", 1285 NewComputed: true, 1286 }, 1287 "port": &terraform.ResourceAttrDiff{ 1288 Old: "80", 1289 New: "8080", 1290 }, 1291 }, 1292 }, 1293 1294 Err: false, 1295 }, 1296 */ 1297 1298 /* 1299 * Maps 1300 */ 1301 1302 // #32 1303 { 1304 Schema: map[string]*Schema{ 1305 "config_vars": &Schema{ 1306 Type: TypeMap, 1307 }, 1308 }, 1309 1310 State: nil, 1311 1312 Config: map[string]interface{}{ 1313 "config_vars": []interface{}{ 1314 map[string]interface{}{ 1315 "bar": "baz", 1316 }, 1317 }, 1318 }, 1319 1320 Diff: &terraform.InstanceDiff{ 1321 Attributes: map[string]*terraform.ResourceAttrDiff{ 1322 "config_vars.#": &terraform.ResourceAttrDiff{ 1323 Old: "0", 1324 New: "1", 1325 }, 1326 1327 "config_vars.bar": &terraform.ResourceAttrDiff{ 1328 Old: "", 1329 New: "baz", 1330 }, 1331 }, 1332 }, 1333 1334 Err: false, 1335 }, 1336 1337 // #33 1338 { 1339 Schema: map[string]*Schema{ 1340 "config_vars": &Schema{ 1341 Type: TypeMap, 1342 }, 1343 }, 1344 1345 State: &terraform.InstanceState{ 1346 Attributes: map[string]string{ 1347 "config_vars.foo": "bar", 1348 }, 1349 }, 1350 1351 Config: map[string]interface{}{ 1352 "config_vars": []interface{}{ 1353 map[string]interface{}{ 1354 "bar": "baz", 1355 }, 1356 }, 1357 }, 1358 1359 Diff: &terraform.InstanceDiff{ 1360 Attributes: map[string]*terraform.ResourceAttrDiff{ 1361 "config_vars.foo": &terraform.ResourceAttrDiff{ 1362 Old: "bar", 1363 NewRemoved: true, 1364 }, 1365 "config_vars.bar": &terraform.ResourceAttrDiff{ 1366 Old: "", 1367 New: "baz", 1368 }, 1369 }, 1370 }, 1371 1372 Err: false, 1373 }, 1374 1375 // #34 1376 { 1377 Schema: map[string]*Schema{ 1378 "vars": &Schema{ 1379 Type: TypeMap, 1380 Optional: true, 1381 Computed: true, 1382 }, 1383 }, 1384 1385 State: &terraform.InstanceState{ 1386 Attributes: map[string]string{ 1387 "vars.foo": "bar", 1388 }, 1389 }, 1390 1391 Config: map[string]interface{}{ 1392 "vars": []interface{}{ 1393 map[string]interface{}{ 1394 "bar": "baz", 1395 }, 1396 }, 1397 }, 1398 1399 Diff: &terraform.InstanceDiff{ 1400 Attributes: map[string]*terraform.ResourceAttrDiff{ 1401 "vars.foo": &terraform.ResourceAttrDiff{ 1402 Old: "bar", 1403 New: "", 1404 NewRemoved: true, 1405 }, 1406 "vars.bar": &terraform.ResourceAttrDiff{ 1407 Old: "", 1408 New: "baz", 1409 }, 1410 }, 1411 }, 1412 1413 Err: false, 1414 }, 1415 1416 // #35 1417 { 1418 Schema: map[string]*Schema{ 1419 "vars": &Schema{ 1420 Type: TypeMap, 1421 Computed: true, 1422 }, 1423 }, 1424 1425 State: &terraform.InstanceState{ 1426 Attributes: map[string]string{ 1427 "vars.foo": "bar", 1428 }, 1429 }, 1430 1431 Config: nil, 1432 1433 Diff: nil, 1434 1435 Err: false, 1436 }, 1437 1438 // #36 1439 { 1440 Schema: map[string]*Schema{ 1441 "config_vars": &Schema{ 1442 Type: TypeList, 1443 Elem: &Schema{Type: TypeMap}, 1444 }, 1445 }, 1446 1447 State: &terraform.InstanceState{ 1448 Attributes: map[string]string{ 1449 "config_vars.#": "1", 1450 "config_vars.0.foo": "bar", 1451 }, 1452 }, 1453 1454 Config: map[string]interface{}{ 1455 "config_vars": []interface{}{ 1456 map[string]interface{}{ 1457 "bar": "baz", 1458 }, 1459 }, 1460 }, 1461 1462 Diff: &terraform.InstanceDiff{ 1463 Attributes: map[string]*terraform.ResourceAttrDiff{ 1464 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1465 Old: "bar", 1466 NewRemoved: true, 1467 }, 1468 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1469 Old: "", 1470 New: "baz", 1471 }, 1472 }, 1473 }, 1474 1475 Err: false, 1476 }, 1477 1478 // #37 1479 { 1480 Schema: map[string]*Schema{ 1481 "config_vars": &Schema{ 1482 Type: TypeList, 1483 Elem: &Schema{Type: TypeMap}, 1484 }, 1485 }, 1486 1487 State: &terraform.InstanceState{ 1488 Attributes: map[string]string{ 1489 "config_vars.#": "1", 1490 "config_vars.0.foo": "bar", 1491 "config_vars.0.bar": "baz", 1492 }, 1493 }, 1494 1495 Config: map[string]interface{}{}, 1496 1497 Diff: &terraform.InstanceDiff{ 1498 Attributes: map[string]*terraform.ResourceAttrDiff{ 1499 "config_vars.#": &terraform.ResourceAttrDiff{ 1500 Old: "1", 1501 New: "0", 1502 }, 1503 "config_vars.0.#": &terraform.ResourceAttrDiff{ 1504 Old: "2", 1505 New: "0", 1506 }, 1507 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1508 Old: "bar", 1509 NewRemoved: true, 1510 }, 1511 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1512 Old: "baz", 1513 NewRemoved: true, 1514 }, 1515 }, 1516 }, 1517 1518 Err: false, 1519 }, 1520 1521 /* 1522 * ForceNews 1523 */ 1524 1525 // #38 1526 { 1527 Schema: map[string]*Schema{ 1528 "availability_zone": &Schema{ 1529 Type: TypeString, 1530 Optional: true, 1531 ForceNew: true, 1532 }, 1533 1534 "address": &Schema{ 1535 Type: TypeString, 1536 Optional: true, 1537 Computed: true, 1538 }, 1539 }, 1540 1541 State: &terraform.InstanceState{ 1542 Attributes: map[string]string{ 1543 "availability_zone": "bar", 1544 "address": "foo", 1545 }, 1546 }, 1547 1548 Config: map[string]interface{}{ 1549 "availability_zone": "foo", 1550 }, 1551 1552 Diff: &terraform.InstanceDiff{ 1553 Attributes: map[string]*terraform.ResourceAttrDiff{ 1554 "availability_zone": &terraform.ResourceAttrDiff{ 1555 Old: "bar", 1556 New: "foo", 1557 RequiresNew: true, 1558 }, 1559 1560 "address": &terraform.ResourceAttrDiff{ 1561 Old: "foo", 1562 New: "", 1563 NewComputed: true, 1564 }, 1565 }, 1566 }, 1567 1568 Err: false, 1569 }, 1570 1571 // #39 Set 1572 { 1573 Schema: map[string]*Schema{ 1574 "availability_zone": &Schema{ 1575 Type: TypeString, 1576 Optional: true, 1577 ForceNew: true, 1578 }, 1579 1580 "ports": &Schema{ 1581 Type: TypeSet, 1582 Optional: true, 1583 Computed: true, 1584 Elem: &Schema{Type: TypeInt}, 1585 Set: func(a interface{}) int { 1586 return a.(int) 1587 }, 1588 }, 1589 }, 1590 1591 State: &terraform.InstanceState{ 1592 Attributes: map[string]string{ 1593 "availability_zone": "bar", 1594 "ports.#": "1", 1595 "ports.80": "80", 1596 }, 1597 }, 1598 1599 Config: map[string]interface{}{ 1600 "availability_zone": "foo", 1601 }, 1602 1603 Diff: &terraform.InstanceDiff{ 1604 Attributes: map[string]*terraform.ResourceAttrDiff{ 1605 "availability_zone": &terraform.ResourceAttrDiff{ 1606 Old: "bar", 1607 New: "foo", 1608 RequiresNew: true, 1609 }, 1610 1611 "ports.#": &terraform.ResourceAttrDiff{ 1612 Old: "1", 1613 New: "", 1614 NewComputed: true, 1615 }, 1616 }, 1617 }, 1618 1619 Err: false, 1620 }, 1621 1622 // #40 Set 1623 { 1624 Schema: map[string]*Schema{ 1625 "instances": &Schema{ 1626 Type: TypeSet, 1627 Elem: &Schema{Type: TypeString}, 1628 Optional: true, 1629 Computed: true, 1630 Set: func(v interface{}) int { 1631 return len(v.(string)) 1632 }, 1633 }, 1634 }, 1635 1636 State: &terraform.InstanceState{ 1637 Attributes: map[string]string{ 1638 "instances.#": "0", 1639 }, 1640 }, 1641 1642 Config: map[string]interface{}{ 1643 "instances": []interface{}{"${var.foo}"}, 1644 }, 1645 1646 ConfigVariables: map[string]string{ 1647 "var.foo": config.UnknownVariableValue, 1648 }, 1649 1650 Diff: &terraform.InstanceDiff{ 1651 Attributes: map[string]*terraform.ResourceAttrDiff{ 1652 "instances.#": &terraform.ResourceAttrDiff{ 1653 NewComputed: true, 1654 }, 1655 }, 1656 }, 1657 1658 Err: false, 1659 }, 1660 1661 // #41 Set 1662 { 1663 Schema: map[string]*Schema{ 1664 "route": &Schema{ 1665 Type: TypeSet, 1666 Optional: true, 1667 Elem: &Resource{ 1668 Schema: map[string]*Schema{ 1669 "index": &Schema{ 1670 Type: TypeInt, 1671 Required: true, 1672 }, 1673 1674 "gateway": &Schema{ 1675 Type: TypeString, 1676 Optional: true, 1677 }, 1678 }, 1679 }, 1680 Set: func(v interface{}) int { 1681 m := v.(map[string]interface{}) 1682 return m["index"].(int) 1683 }, 1684 }, 1685 }, 1686 1687 State: nil, 1688 1689 Config: map[string]interface{}{ 1690 "route": []map[string]interface{}{ 1691 map[string]interface{}{ 1692 "index": "1", 1693 "gateway": "${var.foo}", 1694 }, 1695 }, 1696 }, 1697 1698 ConfigVariables: map[string]string{ 1699 "var.foo": config.UnknownVariableValue, 1700 }, 1701 1702 Diff: &terraform.InstanceDiff{ 1703 Attributes: map[string]*terraform.ResourceAttrDiff{ 1704 "route.#": &terraform.ResourceAttrDiff{ 1705 Old: "0", 1706 New: "1", 1707 }, 1708 "route.~1.index": &terraform.ResourceAttrDiff{ 1709 Old: "", 1710 New: "1", 1711 }, 1712 "route.~1.gateway": &terraform.ResourceAttrDiff{ 1713 Old: "", 1714 New: "${var.foo}", 1715 }, 1716 }, 1717 }, 1718 1719 Err: false, 1720 }, 1721 1722 // #42 Set 1723 { 1724 Schema: map[string]*Schema{ 1725 "route": &Schema{ 1726 Type: TypeSet, 1727 Optional: true, 1728 Elem: &Resource{ 1729 Schema: map[string]*Schema{ 1730 "index": &Schema{ 1731 Type: TypeInt, 1732 Required: true, 1733 }, 1734 1735 "gateway": &Schema{ 1736 Type: TypeSet, 1737 Optional: true, 1738 Elem: &Schema{Type: TypeInt}, 1739 Set: func(a interface{}) int { 1740 return a.(int) 1741 }, 1742 }, 1743 }, 1744 }, 1745 Set: func(v interface{}) int { 1746 m := v.(map[string]interface{}) 1747 return m["index"].(int) 1748 }, 1749 }, 1750 }, 1751 1752 State: nil, 1753 1754 Config: map[string]interface{}{ 1755 "route": []map[string]interface{}{ 1756 map[string]interface{}{ 1757 "index": "1", 1758 "gateway": []interface{}{ 1759 "${var.foo}", 1760 }, 1761 }, 1762 }, 1763 }, 1764 1765 ConfigVariables: map[string]string{ 1766 "var.foo": config.UnknownVariableValue, 1767 }, 1768 1769 Diff: &terraform.InstanceDiff{ 1770 Attributes: map[string]*terraform.ResourceAttrDiff{ 1771 "route.#": &terraform.ResourceAttrDiff{ 1772 Old: "0", 1773 New: "1", 1774 }, 1775 "route.~1.index": &terraform.ResourceAttrDiff{ 1776 Old: "", 1777 New: "1", 1778 }, 1779 "route.~1.gateway.#": &terraform.ResourceAttrDiff{ 1780 NewComputed: true, 1781 }, 1782 }, 1783 }, 1784 1785 Err: false, 1786 }, 1787 1788 // #43 - Computed maps 1789 { 1790 Schema: map[string]*Schema{ 1791 "vars": &Schema{ 1792 Type: TypeMap, 1793 Computed: true, 1794 }, 1795 }, 1796 1797 State: nil, 1798 1799 Config: nil, 1800 1801 Diff: &terraform.InstanceDiff{ 1802 Attributes: map[string]*terraform.ResourceAttrDiff{ 1803 "vars.#": &terraform.ResourceAttrDiff{ 1804 Old: "", 1805 NewComputed: true, 1806 }, 1807 }, 1808 }, 1809 1810 Err: false, 1811 }, 1812 1813 // #44 - Computed maps 1814 { 1815 Schema: map[string]*Schema{ 1816 "vars": &Schema{ 1817 Type: TypeMap, 1818 Computed: true, 1819 }, 1820 }, 1821 1822 State: &terraform.InstanceState{ 1823 Attributes: map[string]string{ 1824 "vars.#": "0", 1825 }, 1826 }, 1827 1828 Config: map[string]interface{}{ 1829 "vars": map[string]interface{}{ 1830 "bar": "${var.foo}", 1831 }, 1832 }, 1833 1834 ConfigVariables: map[string]string{ 1835 "var.foo": config.UnknownVariableValue, 1836 }, 1837 1838 Diff: &terraform.InstanceDiff{ 1839 Attributes: map[string]*terraform.ResourceAttrDiff{ 1840 "vars.#": &terraform.ResourceAttrDiff{ 1841 Old: "", 1842 NewComputed: true, 1843 }, 1844 }, 1845 }, 1846 1847 Err: false, 1848 }, 1849 1850 // #45 - Empty 1851 { 1852 Schema: map[string]*Schema{}, 1853 1854 State: &terraform.InstanceState{}, 1855 1856 Config: map[string]interface{}{}, 1857 1858 Diff: nil, 1859 1860 Err: false, 1861 }, 1862 1863 // #46 - Float 1864 { 1865 Schema: map[string]*Schema{ 1866 "some_threshold": &Schema{ 1867 Type: TypeFloat, 1868 }, 1869 }, 1870 1871 State: &terraform.InstanceState{ 1872 Attributes: map[string]string{ 1873 "some_threshold": "567.8", 1874 }, 1875 }, 1876 1877 Config: map[string]interface{}{ 1878 "some_threshold": 12.34, 1879 }, 1880 1881 Diff: &terraform.InstanceDiff{ 1882 Attributes: map[string]*terraform.ResourceAttrDiff{ 1883 "some_threshold": &terraform.ResourceAttrDiff{ 1884 Old: "567.8", 1885 New: "12.34", 1886 }, 1887 }, 1888 }, 1889 1890 Err: false, 1891 }, 1892 1893 // #47 - https://github.com/hashicorp/terraform/issues/824 1894 { 1895 Schema: map[string]*Schema{ 1896 "block_device": &Schema{ 1897 Type: TypeSet, 1898 Optional: true, 1899 Computed: true, 1900 Elem: &Resource{ 1901 Schema: map[string]*Schema{ 1902 "device_name": &Schema{ 1903 Type: TypeString, 1904 Required: true, 1905 }, 1906 "delete_on_termination": &Schema{ 1907 Type: TypeBool, 1908 Optional: true, 1909 Default: true, 1910 }, 1911 }, 1912 }, 1913 Set: func(v interface{}) int { 1914 var buf bytes.Buffer 1915 m := v.(map[string]interface{}) 1916 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 1917 buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) 1918 return hashcode.String(buf.String()) 1919 }, 1920 }, 1921 }, 1922 1923 State: &terraform.InstanceState{ 1924 Attributes: map[string]string{ 1925 "block_device.#": "2", 1926 "block_device.616397234.delete_on_termination": "true", 1927 "block_device.616397234.device_name": "/dev/sda1", 1928 "block_device.2801811477.delete_on_termination": "true", 1929 "block_device.2801811477.device_name": "/dev/sdx", 1930 }, 1931 }, 1932 1933 Config: map[string]interface{}{ 1934 "block_device": []map[string]interface{}{ 1935 map[string]interface{}{ 1936 "device_name": "/dev/sda1", 1937 }, 1938 map[string]interface{}{ 1939 "device_name": "/dev/sdx", 1940 }, 1941 }, 1942 }, 1943 Diff: nil, 1944 Err: false, 1945 }, 1946 } 1947 1948 for i, tc := range cases { 1949 c, err := config.NewRawConfig(tc.Config) 1950 if err != nil { 1951 t.Fatalf("#%d err: %s", i, err) 1952 } 1953 1954 if len(tc.ConfigVariables) > 0 { 1955 vars := make(map[string]ast.Variable) 1956 for k, v := range tc.ConfigVariables { 1957 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 1958 } 1959 1960 if err := c.Interpolate(vars); err != nil { 1961 t.Fatalf("#%d err: %s", i, err) 1962 } 1963 } 1964 1965 d, err := schemaMap(tc.Schema).Diff( 1966 tc.State, terraform.NewResourceConfig(c)) 1967 if (err != nil) != tc.Err { 1968 t.Fatalf("#%d err: %s", i, err) 1969 } 1970 1971 if !reflect.DeepEqual(tc.Diff, d) { 1972 t.Fatalf("#%d: bad:\n\n%#v", i, d) 1973 } 1974 } 1975 } 1976 1977 func TestSchemaMap_Input(t *testing.T) { 1978 cases := map[string]struct { 1979 Schema map[string]*Schema 1980 Config map[string]interface{} 1981 Input map[string]string 1982 Result map[string]interface{} 1983 Err bool 1984 }{ 1985 /* 1986 * String decode 1987 */ 1988 1989 "uses input on optional field with no config": { 1990 Schema: map[string]*Schema{ 1991 "availability_zone": &Schema{ 1992 Type: TypeString, 1993 Optional: true, 1994 }, 1995 }, 1996 1997 Input: map[string]string{ 1998 "availability_zone": "foo", 1999 }, 2000 2001 Result: map[string]interface{}{ 2002 "availability_zone": "foo", 2003 }, 2004 2005 Err: false, 2006 }, 2007 2008 "input ignored when config has a value": { 2009 Schema: map[string]*Schema{ 2010 "availability_zone": &Schema{ 2011 Type: TypeString, 2012 Optional: true, 2013 }, 2014 }, 2015 2016 Config: map[string]interface{}{ 2017 "availability_zone": "bar", 2018 }, 2019 2020 Input: map[string]string{ 2021 "availability_zone": "foo", 2022 }, 2023 2024 Result: map[string]interface{}{}, 2025 2026 Err: false, 2027 }, 2028 2029 "input ignored when schema has a default": { 2030 Schema: map[string]*Schema{ 2031 "availability_zone": &Schema{ 2032 Type: TypeString, 2033 Default: "foo", 2034 Optional: true, 2035 }, 2036 }, 2037 2038 Input: map[string]string{ 2039 "availability_zone": "bar", 2040 }, 2041 2042 Result: map[string]interface{}{}, 2043 2044 Err: false, 2045 }, 2046 2047 "input ignored when default function returns a value": { 2048 Schema: map[string]*Schema{ 2049 "availability_zone": &Schema{ 2050 Type: TypeString, 2051 DefaultFunc: func() (interface{}, error) { 2052 return "foo", nil 2053 }, 2054 Optional: true, 2055 }, 2056 }, 2057 2058 Input: map[string]string{ 2059 "availability_zone": "bar", 2060 }, 2061 2062 Result: map[string]interface{}{}, 2063 2064 Err: false, 2065 }, 2066 2067 "input used when default function returns nil": { 2068 Schema: map[string]*Schema{ 2069 "availability_zone": &Schema{ 2070 Type: TypeString, 2071 DefaultFunc: func() (interface{}, error) { 2072 return nil, nil 2073 }, 2074 Optional: true, 2075 }, 2076 }, 2077 2078 Input: map[string]string{ 2079 "availability_zone": "bar", 2080 }, 2081 2082 Result: map[string]interface{}{ 2083 "availability_zone": "bar", 2084 }, 2085 2086 Err: false, 2087 }, 2088 } 2089 2090 for i, tc := range cases { 2091 if tc.Config == nil { 2092 tc.Config = make(map[string]interface{}) 2093 } 2094 2095 c, err := config.NewRawConfig(tc.Config) 2096 if err != nil { 2097 t.Fatalf("err: %s", err) 2098 } 2099 2100 input := new(terraform.MockUIInput) 2101 input.InputReturnMap = tc.Input 2102 2103 rc := terraform.NewResourceConfig(c) 2104 rc.Config = make(map[string]interface{}) 2105 2106 actual, err := schemaMap(tc.Schema).Input(input, rc) 2107 if (err != nil) != tc.Err { 2108 t.Fatalf("#%v err: %s", i, err) 2109 } 2110 2111 if !reflect.DeepEqual(tc.Result, actual.Config) { 2112 t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result) 2113 } 2114 } 2115 } 2116 2117 func TestSchemaMap_InternalValidate(t *testing.T) { 2118 cases := []struct { 2119 In map[string]*Schema 2120 Err bool 2121 }{ 2122 { 2123 nil, 2124 false, 2125 }, 2126 2127 // No optional and no required 2128 { 2129 map[string]*Schema{ 2130 "foo": &Schema{ 2131 Type: TypeInt, 2132 Optional: true, 2133 Required: true, 2134 }, 2135 }, 2136 true, 2137 }, 2138 2139 // No optional and no required 2140 { 2141 map[string]*Schema{ 2142 "foo": &Schema{ 2143 Type: TypeInt, 2144 }, 2145 }, 2146 true, 2147 }, 2148 2149 // Missing Type 2150 { 2151 map[string]*Schema{ 2152 "foo": &Schema{ 2153 Required: true, 2154 }, 2155 }, 2156 true, 2157 }, 2158 2159 // Required but computed 2160 { 2161 map[string]*Schema{ 2162 "foo": &Schema{ 2163 Type: TypeInt, 2164 Required: true, 2165 Computed: true, 2166 }, 2167 }, 2168 true, 2169 }, 2170 2171 // Looks good 2172 { 2173 map[string]*Schema{ 2174 "foo": &Schema{ 2175 Type: TypeString, 2176 Required: true, 2177 }, 2178 }, 2179 false, 2180 }, 2181 2182 // Computed but has default 2183 { 2184 map[string]*Schema{ 2185 "foo": &Schema{ 2186 Type: TypeInt, 2187 Optional: true, 2188 Computed: true, 2189 Default: "foo", 2190 }, 2191 }, 2192 true, 2193 }, 2194 2195 // Required but has default 2196 { 2197 map[string]*Schema{ 2198 "foo": &Schema{ 2199 Type: TypeInt, 2200 Optional: true, 2201 Required: true, 2202 Default: "foo", 2203 }, 2204 }, 2205 true, 2206 }, 2207 2208 // List element not set 2209 { 2210 map[string]*Schema{ 2211 "foo": &Schema{ 2212 Type: TypeList, 2213 }, 2214 }, 2215 true, 2216 }, 2217 2218 // List default 2219 { 2220 map[string]*Schema{ 2221 "foo": &Schema{ 2222 Type: TypeList, 2223 Elem: &Schema{Type: TypeInt}, 2224 Default: "foo", 2225 }, 2226 }, 2227 true, 2228 }, 2229 2230 // List element computed 2231 { 2232 map[string]*Schema{ 2233 "foo": &Schema{ 2234 Type: TypeList, 2235 Optional: true, 2236 Elem: &Schema{ 2237 Type: TypeInt, 2238 Computed: true, 2239 }, 2240 }, 2241 }, 2242 true, 2243 }, 2244 2245 // List element with Set set 2246 { 2247 map[string]*Schema{ 2248 "foo": &Schema{ 2249 Type: TypeList, 2250 Elem: &Schema{Type: TypeInt}, 2251 Set: func(interface{}) int { return 0 }, 2252 Optional: true, 2253 }, 2254 }, 2255 true, 2256 }, 2257 2258 // Set element with no Set set 2259 { 2260 map[string]*Schema{ 2261 "foo": &Schema{ 2262 Type: TypeSet, 2263 Elem: &Schema{Type: TypeInt}, 2264 Optional: true, 2265 }, 2266 }, 2267 true, 2268 }, 2269 2270 // Required but computed 2271 { 2272 map[string]*Schema{ 2273 "foo": &Schema{ 2274 Type: TypeInt, 2275 Required: true, 2276 ComputedWhen: []string{"foo"}, 2277 }, 2278 }, 2279 true, 2280 }, 2281 2282 // Sub-resource invalid 2283 { 2284 map[string]*Schema{ 2285 "foo": &Schema{ 2286 Type: TypeList, 2287 Optional: true, 2288 Elem: &Resource{ 2289 Schema: map[string]*Schema{ 2290 "foo": new(Schema), 2291 }, 2292 }, 2293 }, 2294 }, 2295 true, 2296 }, 2297 2298 // Sub-resource valid 2299 { 2300 map[string]*Schema{ 2301 "foo": &Schema{ 2302 Type: TypeList, 2303 Optional: true, 2304 Elem: &Resource{ 2305 Schema: map[string]*Schema{ 2306 "foo": &Schema{ 2307 Type: TypeInt, 2308 Optional: true, 2309 }, 2310 }, 2311 }, 2312 }, 2313 }, 2314 false, 2315 }, 2316 } 2317 2318 for i, tc := range cases { 2319 err := schemaMap(tc.In).InternalValidate() 2320 if (err != nil) != tc.Err { 2321 t.Fatalf("%d: bad: %s\n\n%#v", i, err, tc.In) 2322 } 2323 } 2324 2325 } 2326 2327 func TestSchemaMap_Validate(t *testing.T) { 2328 cases := []struct { 2329 Schema map[string]*Schema 2330 Config map[string]interface{} 2331 Vars map[string]string 2332 Warn bool 2333 Err bool 2334 }{ 2335 // Good 2336 { 2337 Schema: map[string]*Schema{ 2338 "availability_zone": &Schema{ 2339 Type: TypeString, 2340 Optional: true, 2341 Computed: true, 2342 ForceNew: true, 2343 }, 2344 }, 2345 2346 Config: map[string]interface{}{ 2347 "availability_zone": "foo", 2348 }, 2349 }, 2350 2351 // Good, because the var is not set and that error will come elsewhere 2352 { 2353 Schema: map[string]*Schema{ 2354 "size": &Schema{ 2355 Type: TypeInt, 2356 Required: true, 2357 }, 2358 }, 2359 2360 Config: map[string]interface{}{ 2361 "size": "${var.foo}", 2362 }, 2363 2364 Vars: map[string]string{ 2365 "var.foo": config.UnknownVariableValue, 2366 }, 2367 }, 2368 2369 // Required field not set 2370 { 2371 Schema: map[string]*Schema{ 2372 "availability_zone": &Schema{ 2373 Type: TypeString, 2374 Required: true, 2375 }, 2376 }, 2377 2378 Config: map[string]interface{}{}, 2379 2380 Err: true, 2381 }, 2382 2383 // Invalid type 2384 { 2385 Schema: map[string]*Schema{ 2386 "port": &Schema{ 2387 Type: TypeInt, 2388 Required: true, 2389 }, 2390 }, 2391 2392 Config: map[string]interface{}{ 2393 "port": "I am invalid", 2394 }, 2395 2396 Err: true, 2397 }, 2398 2399 { 2400 Schema: map[string]*Schema{ 2401 "user_data": &Schema{ 2402 Type: TypeString, 2403 Optional: true, 2404 }, 2405 }, 2406 2407 Config: map[string]interface{}{ 2408 "user_data": []interface{}{ 2409 map[string]interface{}{ 2410 "foo": "bar", 2411 }, 2412 }, 2413 }, 2414 2415 Err: true, 2416 }, 2417 2418 // Bad type, interpolated 2419 { 2420 Schema: map[string]*Schema{ 2421 "size": &Schema{ 2422 Type: TypeInt, 2423 Required: true, 2424 }, 2425 }, 2426 2427 Config: map[string]interface{}{ 2428 "size": "${var.foo}", 2429 }, 2430 2431 Vars: map[string]string{ 2432 "var.foo": "nope", 2433 }, 2434 2435 Err: true, 2436 }, 2437 2438 // Required but has DefaultFunc 2439 { 2440 Schema: map[string]*Schema{ 2441 "availability_zone": &Schema{ 2442 Type: TypeString, 2443 Required: true, 2444 DefaultFunc: func() (interface{}, error) { 2445 return "foo", nil 2446 }, 2447 }, 2448 }, 2449 2450 Config: nil, 2451 }, 2452 2453 // Required but has DefaultFunc return nil 2454 { 2455 Schema: map[string]*Schema{ 2456 "availability_zone": &Schema{ 2457 Type: TypeString, 2458 Required: true, 2459 DefaultFunc: func() (interface{}, error) { 2460 return nil, nil 2461 }, 2462 }, 2463 }, 2464 2465 Config: nil, 2466 2467 Err: true, 2468 }, 2469 2470 // Optional sub-resource 2471 { 2472 Schema: map[string]*Schema{ 2473 "ingress": &Schema{ 2474 Type: TypeList, 2475 Elem: &Resource{ 2476 Schema: map[string]*Schema{ 2477 "from": &Schema{ 2478 Type: TypeInt, 2479 Required: true, 2480 }, 2481 }, 2482 }, 2483 }, 2484 }, 2485 2486 Config: map[string]interface{}{}, 2487 2488 Err: false, 2489 }, 2490 2491 // Not a list 2492 { 2493 Schema: map[string]*Schema{ 2494 "ingress": &Schema{ 2495 Type: TypeList, 2496 Elem: &Resource{ 2497 Schema: map[string]*Schema{ 2498 "from": &Schema{ 2499 Type: TypeInt, 2500 Required: true, 2501 }, 2502 }, 2503 }, 2504 }, 2505 }, 2506 2507 Config: map[string]interface{}{ 2508 "ingress": "foo", 2509 }, 2510 2511 Err: true, 2512 }, 2513 2514 // Required sub-resource field 2515 { 2516 Schema: map[string]*Schema{ 2517 "ingress": &Schema{ 2518 Type: TypeList, 2519 Elem: &Resource{ 2520 Schema: map[string]*Schema{ 2521 "from": &Schema{ 2522 Type: TypeInt, 2523 Required: true, 2524 }, 2525 }, 2526 }, 2527 }, 2528 }, 2529 2530 Config: map[string]interface{}{ 2531 "ingress": []interface{}{ 2532 map[string]interface{}{}, 2533 }, 2534 }, 2535 2536 Err: true, 2537 }, 2538 2539 // Good sub-resource 2540 { 2541 Schema: map[string]*Schema{ 2542 "ingress": &Schema{ 2543 Type: TypeList, 2544 Optional: true, 2545 Elem: &Resource{ 2546 Schema: map[string]*Schema{ 2547 "from": &Schema{ 2548 Type: TypeInt, 2549 Required: true, 2550 }, 2551 }, 2552 }, 2553 }, 2554 }, 2555 2556 Config: map[string]interface{}{ 2557 "ingress": []interface{}{ 2558 map[string]interface{}{ 2559 "from": 80, 2560 }, 2561 }, 2562 }, 2563 2564 Err: false, 2565 }, 2566 2567 // Invalid/unknown field 2568 { 2569 Schema: map[string]*Schema{ 2570 "availability_zone": &Schema{ 2571 Type: TypeString, 2572 Optional: true, 2573 Computed: true, 2574 ForceNew: true, 2575 }, 2576 }, 2577 2578 Config: map[string]interface{}{ 2579 "foo": "bar", 2580 }, 2581 2582 Err: true, 2583 }, 2584 2585 // Computed field set 2586 { 2587 Schema: map[string]*Schema{ 2588 "availability_zone": &Schema{ 2589 Type: TypeString, 2590 Computed: true, 2591 }, 2592 }, 2593 2594 Config: map[string]interface{}{ 2595 "availability_zone": "bar", 2596 }, 2597 2598 Err: true, 2599 }, 2600 2601 // Not a set 2602 { 2603 Schema: map[string]*Schema{ 2604 "ports": &Schema{ 2605 Type: TypeSet, 2606 Required: true, 2607 Elem: &Schema{Type: TypeInt}, 2608 Set: func(a interface{}) int { 2609 return a.(int) 2610 }, 2611 }, 2612 }, 2613 2614 Config: map[string]interface{}{ 2615 "ports": "foo", 2616 }, 2617 2618 Err: true, 2619 }, 2620 2621 // Maps 2622 { 2623 Schema: map[string]*Schema{ 2624 "user_data": &Schema{ 2625 Type: TypeMap, 2626 Optional: true, 2627 }, 2628 }, 2629 2630 Config: map[string]interface{}{ 2631 "user_data": "foo", 2632 }, 2633 2634 Err: true, 2635 }, 2636 2637 { 2638 Schema: map[string]*Schema{ 2639 "user_data": &Schema{ 2640 Type: TypeMap, 2641 Optional: true, 2642 }, 2643 }, 2644 2645 Config: map[string]interface{}{ 2646 "user_data": []interface{}{ 2647 map[string]interface{}{ 2648 "foo": "bar", 2649 }, 2650 }, 2651 }, 2652 }, 2653 2654 { 2655 Schema: map[string]*Schema{ 2656 "user_data": &Schema{ 2657 Type: TypeMap, 2658 Optional: true, 2659 }, 2660 }, 2661 2662 Config: map[string]interface{}{ 2663 "user_data": map[string]interface{}{ 2664 "foo": "bar", 2665 }, 2666 }, 2667 }, 2668 2669 { 2670 Schema: map[string]*Schema{ 2671 "user_data": &Schema{ 2672 Type: TypeMap, 2673 Optional: true, 2674 }, 2675 }, 2676 2677 Config: map[string]interface{}{ 2678 "user_data": []interface{}{ 2679 "foo", 2680 }, 2681 }, 2682 2683 Err: true, 2684 }, 2685 2686 { 2687 Schema: map[string]*Schema{ 2688 "security_groups": &Schema{ 2689 Type: TypeSet, 2690 Optional: true, 2691 Computed: true, 2692 ForceNew: true, 2693 Elem: &Schema{Type: TypeString}, 2694 Set: func(v interface{}) int { 2695 return len(v.(string)) 2696 }, 2697 }, 2698 }, 2699 2700 Config: map[string]interface{}{ 2701 "security_groups": []interface{}{"${var.foo}"}, 2702 }, 2703 2704 Err: false, 2705 }, 2706 2707 { 2708 Schema: map[string]*Schema{ 2709 "security_groups": &Schema{ 2710 Type: TypeSet, 2711 Optional: true, 2712 Computed: true, 2713 ForceNew: true, 2714 Elem: &Schema{Type: TypeString}, 2715 }, 2716 }, 2717 2718 Config: map[string]interface{}{ 2719 "security_groups": "${var.foo}", 2720 }, 2721 2722 Err: true, 2723 }, 2724 } 2725 2726 for i, tc := range cases { 2727 c, err := config.NewRawConfig(tc.Config) 2728 if err != nil { 2729 t.Fatalf("err: %s", err) 2730 } 2731 if tc.Vars != nil { 2732 vars := make(map[string]ast.Variable) 2733 for k, v := range tc.Vars { 2734 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 2735 } 2736 2737 if err := c.Interpolate(vars); err != nil { 2738 t.Fatalf("err: %s", err) 2739 } 2740 } 2741 2742 ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 2743 if (len(es) > 0) != tc.Err { 2744 if len(es) == 0 { 2745 t.Errorf("%d: no errors", i) 2746 } 2747 2748 for _, e := range es { 2749 t.Errorf("%d: err: %s", i, e) 2750 } 2751 2752 t.FailNow() 2753 } 2754 2755 if (len(ws) > 0) != tc.Warn { 2756 t.Fatalf("%d: ws: %#v", i, ws) 2757 } 2758 } 2759 }