github.com/alexissmirnov/terraform@v0.4.3-0.20150423153700-1ef9731a2f14/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, new(Set)}, 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 // #48 - Zero value in state shouldn't result in diff 1948 { 1949 Schema: map[string]*Schema{ 1950 "port": &Schema{ 1951 Type: TypeBool, 1952 Optional: true, 1953 ForceNew: true, 1954 }, 1955 }, 1956 1957 State: &terraform.InstanceState{ 1958 Attributes: map[string]string{ 1959 "port": "false", 1960 }, 1961 }, 1962 1963 Config: map[string]interface{}{}, 1964 1965 Diff: nil, 1966 1967 Err: false, 1968 }, 1969 1970 // #49 Set - Same as #48 but for sets 1971 { 1972 Schema: map[string]*Schema{ 1973 "route": &Schema{ 1974 Type: TypeSet, 1975 Optional: true, 1976 Elem: &Resource{ 1977 Schema: map[string]*Schema{ 1978 "index": &Schema{ 1979 Type: TypeInt, 1980 Required: true, 1981 }, 1982 1983 "gateway": &Schema{ 1984 Type: TypeSet, 1985 Optional: true, 1986 Elem: &Schema{Type: TypeInt}, 1987 Set: func(a interface{}) int { 1988 return a.(int) 1989 }, 1990 }, 1991 }, 1992 }, 1993 Set: func(v interface{}) int { 1994 m := v.(map[string]interface{}) 1995 return m["index"].(int) 1996 }, 1997 }, 1998 }, 1999 2000 State: &terraform.InstanceState{ 2001 Attributes: map[string]string{ 2002 "route.#": "0", 2003 }, 2004 }, 2005 2006 Config: map[string]interface{}{}, 2007 2008 Diff: nil, 2009 2010 Err: false, 2011 }, 2012 2013 // #50 - A set computed element shouldn't cause a diff 2014 { 2015 Schema: map[string]*Schema{ 2016 "active": &Schema{ 2017 Type: TypeBool, 2018 Computed: true, 2019 ForceNew: true, 2020 }, 2021 }, 2022 2023 State: &terraform.InstanceState{ 2024 Attributes: map[string]string{ 2025 "active": "true", 2026 }, 2027 }, 2028 2029 Config: map[string]interface{}{}, 2030 2031 Diff: nil, 2032 2033 Err: false, 2034 }, 2035 2036 // #51 - An empty set should show up in the diff 2037 { 2038 Schema: map[string]*Schema{ 2039 "instances": &Schema{ 2040 Type: TypeSet, 2041 Elem: &Schema{Type: TypeString}, 2042 Optional: true, 2043 ForceNew: true, 2044 Set: func(v interface{}) int { 2045 return len(v.(string)) 2046 }, 2047 }, 2048 }, 2049 2050 State: &terraform.InstanceState{ 2051 Attributes: map[string]string{ 2052 "instances.#": "1", 2053 "instances.3": "foo", 2054 }, 2055 }, 2056 2057 Config: map[string]interface{}{}, 2058 2059 Diff: &terraform.InstanceDiff{ 2060 Attributes: map[string]*terraform.ResourceAttrDiff{ 2061 "instances.#": &terraform.ResourceAttrDiff{ 2062 Old: "1", 2063 New: "0", 2064 RequiresNew: true, 2065 }, 2066 }, 2067 }, 2068 2069 Err: false, 2070 }, 2071 2072 // #52 - Map with empty value 2073 { 2074 Schema: map[string]*Schema{ 2075 "vars": &Schema{ 2076 Type: TypeMap, 2077 }, 2078 }, 2079 2080 State: nil, 2081 2082 Config: map[string]interface{}{ 2083 "vars": map[string]interface{}{ 2084 "foo": "", 2085 }, 2086 }, 2087 2088 Diff: &terraform.InstanceDiff{ 2089 Attributes: map[string]*terraform.ResourceAttrDiff{ 2090 "vars.#": &terraform.ResourceAttrDiff{ 2091 Old: "0", 2092 New: "1", 2093 }, 2094 "vars.foo": &terraform.ResourceAttrDiff{ 2095 Old: "", 2096 New: "", 2097 }, 2098 }, 2099 }, 2100 2101 Err: false, 2102 }, 2103 2104 // #53 - Unset bool, not in state 2105 { 2106 Schema: map[string]*Schema{ 2107 "force": &Schema{ 2108 Type: TypeBool, 2109 Optional: true, 2110 ForceNew: true, 2111 }, 2112 }, 2113 2114 State: nil, 2115 2116 Config: map[string]interface{}{}, 2117 2118 Diff: nil, 2119 2120 Err: false, 2121 }, 2122 2123 // #54 - Unset set, not in state 2124 { 2125 Schema: map[string]*Schema{ 2126 "metadata_keys": &Schema{ 2127 Type: TypeSet, 2128 Optional: true, 2129 ForceNew: true, 2130 Elem: &Schema{Type: TypeInt}, 2131 Set: func(interface{}) int { return 0 }, 2132 }, 2133 }, 2134 2135 State: nil, 2136 2137 Config: map[string]interface{}{}, 2138 2139 Diff: nil, 2140 2141 Err: false, 2142 }, 2143 2144 // #55 - Unset list in state, should not show up computed 2145 { 2146 Schema: map[string]*Schema{ 2147 "metadata_keys": &Schema{ 2148 Type: TypeList, 2149 Optional: true, 2150 Computed: true, 2151 ForceNew: true, 2152 Elem: &Schema{Type: TypeInt}, 2153 }, 2154 }, 2155 2156 State: &terraform.InstanceState{ 2157 Attributes: map[string]string{ 2158 "metadata_keys.#": "0", 2159 }, 2160 }, 2161 2162 Config: map[string]interface{}{}, 2163 2164 Diff: nil, 2165 2166 Err: false, 2167 }, 2168 2169 // #56 - Set element computed substring 2170 { 2171 Schema: map[string]*Schema{ 2172 "ports": &Schema{ 2173 Type: TypeSet, 2174 Required: true, 2175 Elem: &Schema{Type: TypeInt}, 2176 Set: func(a interface{}) int { 2177 return a.(int) 2178 }, 2179 }, 2180 }, 2181 2182 State: nil, 2183 2184 Config: map[string]interface{}{ 2185 "ports": []interface{}{1, "${var.foo}32"}, 2186 }, 2187 2188 ConfigVariables: map[string]string{ 2189 "var.foo": config.UnknownVariableValue, 2190 }, 2191 2192 Diff: &terraform.InstanceDiff{ 2193 Attributes: map[string]*terraform.ResourceAttrDiff{ 2194 "ports.#": &terraform.ResourceAttrDiff{ 2195 Old: "", 2196 New: "", 2197 NewComputed: true, 2198 }, 2199 }, 2200 }, 2201 2202 Err: false, 2203 }, 2204 2205 // #57 - Computed map without config that's known to be empty does not 2206 // generate diff 2207 { 2208 Schema: map[string]*Schema{ 2209 "tags": &Schema{ 2210 Type: TypeMap, 2211 Computed: true, 2212 }, 2213 }, 2214 2215 Config: nil, 2216 2217 State: &terraform.InstanceState{ 2218 Attributes: map[string]string{ 2219 "tags.#": "0", 2220 }, 2221 }, 2222 2223 Diff: nil, 2224 2225 Err: false, 2226 }, 2227 2228 // #58 Set with hyphen keys 2229 { 2230 Schema: map[string]*Schema{ 2231 "route": &Schema{ 2232 Type: TypeSet, 2233 Optional: true, 2234 Elem: &Resource{ 2235 Schema: map[string]*Schema{ 2236 "index": &Schema{ 2237 Type: TypeInt, 2238 Required: true, 2239 }, 2240 2241 "gateway-name": &Schema{ 2242 Type: TypeString, 2243 Optional: true, 2244 }, 2245 }, 2246 }, 2247 Set: func(v interface{}) int { 2248 m := v.(map[string]interface{}) 2249 return m["index"].(int) 2250 }, 2251 }, 2252 }, 2253 2254 State: nil, 2255 2256 Config: map[string]interface{}{ 2257 "route": []map[string]interface{}{ 2258 map[string]interface{}{ 2259 "index": "1", 2260 "gateway-name": "hello", 2261 }, 2262 }, 2263 }, 2264 2265 Diff: &terraform.InstanceDiff{ 2266 Attributes: map[string]*terraform.ResourceAttrDiff{ 2267 "route.#": &terraform.ResourceAttrDiff{ 2268 Old: "0", 2269 New: "1", 2270 }, 2271 "route.1.index": &terraform.ResourceAttrDiff{ 2272 Old: "", 2273 New: "1", 2274 }, 2275 "route.1.gateway-name": &terraform.ResourceAttrDiff{ 2276 Old: "", 2277 New: "hello", 2278 }, 2279 }, 2280 }, 2281 2282 Err: false, 2283 }, 2284 } 2285 2286 for i, tc := range cases { 2287 c, err := config.NewRawConfig(tc.Config) 2288 if err != nil { 2289 t.Fatalf("#%d err: %s", i, err) 2290 } 2291 2292 if len(tc.ConfigVariables) > 0 { 2293 vars := make(map[string]ast.Variable) 2294 for k, v := range tc.ConfigVariables { 2295 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 2296 } 2297 2298 if err := c.Interpolate(vars); err != nil { 2299 t.Fatalf("#%d err: %s", i, err) 2300 } 2301 } 2302 2303 d, err := schemaMap(tc.Schema).Diff( 2304 tc.State, terraform.NewResourceConfig(c)) 2305 if (err != nil) != tc.Err { 2306 t.Fatalf("#%d err: %s", i, err) 2307 } 2308 2309 if !reflect.DeepEqual(tc.Diff, d) { 2310 t.Fatalf("#%d: bad:\n\n%#v", i, d) 2311 } 2312 } 2313 } 2314 2315 func TestSchemaMap_Input(t *testing.T) { 2316 cases := map[string]struct { 2317 Schema map[string]*Schema 2318 Config map[string]interface{} 2319 Input map[string]string 2320 Result map[string]interface{} 2321 Err bool 2322 }{ 2323 /* 2324 * String decode 2325 */ 2326 2327 "uses input on optional field with no config": { 2328 Schema: map[string]*Schema{ 2329 "availability_zone": &Schema{ 2330 Type: TypeString, 2331 Optional: true, 2332 }, 2333 }, 2334 2335 Input: map[string]string{ 2336 "availability_zone": "foo", 2337 }, 2338 2339 Result: map[string]interface{}{ 2340 "availability_zone": "foo", 2341 }, 2342 2343 Err: false, 2344 }, 2345 2346 "input ignored when config has a value": { 2347 Schema: map[string]*Schema{ 2348 "availability_zone": &Schema{ 2349 Type: TypeString, 2350 Optional: true, 2351 }, 2352 }, 2353 2354 Config: map[string]interface{}{ 2355 "availability_zone": "bar", 2356 }, 2357 2358 Input: map[string]string{ 2359 "availability_zone": "foo", 2360 }, 2361 2362 Result: map[string]interface{}{}, 2363 2364 Err: false, 2365 }, 2366 2367 "input ignored when schema has a default": { 2368 Schema: map[string]*Schema{ 2369 "availability_zone": &Schema{ 2370 Type: TypeString, 2371 Default: "foo", 2372 Optional: true, 2373 }, 2374 }, 2375 2376 Input: map[string]string{ 2377 "availability_zone": "bar", 2378 }, 2379 2380 Result: map[string]interface{}{}, 2381 2382 Err: false, 2383 }, 2384 2385 "input ignored when default function returns a value": { 2386 Schema: map[string]*Schema{ 2387 "availability_zone": &Schema{ 2388 Type: TypeString, 2389 DefaultFunc: func() (interface{}, error) { 2390 return "foo", nil 2391 }, 2392 Optional: true, 2393 }, 2394 }, 2395 2396 Input: map[string]string{ 2397 "availability_zone": "bar", 2398 }, 2399 2400 Result: map[string]interface{}{}, 2401 2402 Err: false, 2403 }, 2404 2405 "input ignored when default function returns an empty string": { 2406 Schema: map[string]*Schema{ 2407 "availability_zone": &Schema{ 2408 Type: TypeString, 2409 Default: "", 2410 Optional: true, 2411 }, 2412 }, 2413 2414 Input: map[string]string{ 2415 "availability_zone": "bar", 2416 }, 2417 2418 Result: map[string]interface{}{}, 2419 2420 Err: false, 2421 }, 2422 2423 "input used when default function returns nil": { 2424 Schema: map[string]*Schema{ 2425 "availability_zone": &Schema{ 2426 Type: TypeString, 2427 DefaultFunc: func() (interface{}, error) { 2428 return nil, nil 2429 }, 2430 Optional: true, 2431 }, 2432 }, 2433 2434 Input: map[string]string{ 2435 "availability_zone": "bar", 2436 }, 2437 2438 Result: map[string]interface{}{ 2439 "availability_zone": "bar", 2440 }, 2441 2442 Err: false, 2443 }, 2444 } 2445 2446 for i, tc := range cases { 2447 if tc.Config == nil { 2448 tc.Config = make(map[string]interface{}) 2449 } 2450 2451 c, err := config.NewRawConfig(tc.Config) 2452 if err != nil { 2453 t.Fatalf("err: %s", err) 2454 } 2455 2456 input := new(terraform.MockUIInput) 2457 input.InputReturnMap = tc.Input 2458 2459 rc := terraform.NewResourceConfig(c) 2460 rc.Config = make(map[string]interface{}) 2461 2462 actual, err := schemaMap(tc.Schema).Input(input, rc) 2463 if (err != nil) != tc.Err { 2464 t.Fatalf("#%v err: %s", i, err) 2465 } 2466 2467 if !reflect.DeepEqual(tc.Result, actual.Config) { 2468 t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result) 2469 } 2470 } 2471 } 2472 2473 func TestSchemaMap_InternalValidate(t *testing.T) { 2474 cases := []struct { 2475 In map[string]*Schema 2476 Err bool 2477 }{ 2478 { 2479 nil, 2480 false, 2481 }, 2482 2483 // No optional and no required 2484 { 2485 map[string]*Schema{ 2486 "foo": &Schema{ 2487 Type: TypeInt, 2488 Optional: true, 2489 Required: true, 2490 }, 2491 }, 2492 true, 2493 }, 2494 2495 // No optional and no required 2496 { 2497 map[string]*Schema{ 2498 "foo": &Schema{ 2499 Type: TypeInt, 2500 }, 2501 }, 2502 true, 2503 }, 2504 2505 // Missing Type 2506 { 2507 map[string]*Schema{ 2508 "foo": &Schema{ 2509 Required: true, 2510 }, 2511 }, 2512 true, 2513 }, 2514 2515 // Required but computed 2516 { 2517 map[string]*Schema{ 2518 "foo": &Schema{ 2519 Type: TypeInt, 2520 Required: true, 2521 Computed: true, 2522 }, 2523 }, 2524 true, 2525 }, 2526 2527 // Looks good 2528 { 2529 map[string]*Schema{ 2530 "foo": &Schema{ 2531 Type: TypeString, 2532 Required: true, 2533 }, 2534 }, 2535 false, 2536 }, 2537 2538 // Computed but has default 2539 { 2540 map[string]*Schema{ 2541 "foo": &Schema{ 2542 Type: TypeInt, 2543 Optional: true, 2544 Computed: true, 2545 Default: "foo", 2546 }, 2547 }, 2548 true, 2549 }, 2550 2551 // Required but has default 2552 { 2553 map[string]*Schema{ 2554 "foo": &Schema{ 2555 Type: TypeInt, 2556 Optional: true, 2557 Required: true, 2558 Default: "foo", 2559 }, 2560 }, 2561 true, 2562 }, 2563 2564 // List element not set 2565 { 2566 map[string]*Schema{ 2567 "foo": &Schema{ 2568 Type: TypeList, 2569 }, 2570 }, 2571 true, 2572 }, 2573 2574 // List default 2575 { 2576 map[string]*Schema{ 2577 "foo": &Schema{ 2578 Type: TypeList, 2579 Elem: &Schema{Type: TypeInt}, 2580 Default: "foo", 2581 }, 2582 }, 2583 true, 2584 }, 2585 2586 // List element computed 2587 { 2588 map[string]*Schema{ 2589 "foo": &Schema{ 2590 Type: TypeList, 2591 Optional: true, 2592 Elem: &Schema{ 2593 Type: TypeInt, 2594 Computed: true, 2595 }, 2596 }, 2597 }, 2598 true, 2599 }, 2600 2601 // List element with Set set 2602 { 2603 map[string]*Schema{ 2604 "foo": &Schema{ 2605 Type: TypeList, 2606 Elem: &Schema{Type: TypeInt}, 2607 Set: func(interface{}) int { return 0 }, 2608 Optional: true, 2609 }, 2610 }, 2611 true, 2612 }, 2613 2614 // Set element with no Set set 2615 { 2616 map[string]*Schema{ 2617 "foo": &Schema{ 2618 Type: TypeSet, 2619 Elem: &Schema{Type: TypeInt}, 2620 Optional: true, 2621 }, 2622 }, 2623 true, 2624 }, 2625 2626 // Required but computed 2627 { 2628 map[string]*Schema{ 2629 "foo": &Schema{ 2630 Type: TypeInt, 2631 Required: true, 2632 ComputedWhen: []string{"foo"}, 2633 }, 2634 }, 2635 true, 2636 }, 2637 2638 // Conflicting attributes cannot be required 2639 { 2640 map[string]*Schema{ 2641 "blacklist": &Schema{ 2642 Type: TypeBool, 2643 Required: true, 2644 }, 2645 "whitelist": &Schema{ 2646 Type: TypeBool, 2647 Optional: true, 2648 ConflictsWith: []string{"blacklist"}, 2649 }, 2650 }, 2651 true, 2652 }, 2653 2654 // Attribute with conflicts cannot be required 2655 { 2656 map[string]*Schema{ 2657 "whitelist": &Schema{ 2658 Type: TypeBool, 2659 Required: true, 2660 ConflictsWith: []string{"blacklist"}, 2661 }, 2662 }, 2663 true, 2664 }, 2665 2666 // ConflictsWith cannot be used w/ Computed 2667 { 2668 map[string]*Schema{ 2669 "blacklist": &Schema{ 2670 Type: TypeBool, 2671 Computed: true, 2672 }, 2673 "whitelist": &Schema{ 2674 Type: TypeBool, 2675 Optional: true, 2676 ConflictsWith: []string{"blacklist"}, 2677 }, 2678 }, 2679 true, 2680 }, 2681 2682 // ConflictsWith cannot be used w/ ComputedWhen 2683 { 2684 map[string]*Schema{ 2685 "blacklist": &Schema{ 2686 Type: TypeBool, 2687 ComputedWhen: []string{"foor"}, 2688 }, 2689 "whitelist": &Schema{ 2690 Type: TypeBool, 2691 Required: true, 2692 ConflictsWith: []string{"blacklist"}, 2693 }, 2694 }, 2695 true, 2696 }, 2697 2698 // Sub-resource invalid 2699 { 2700 map[string]*Schema{ 2701 "foo": &Schema{ 2702 Type: TypeList, 2703 Optional: true, 2704 Elem: &Resource{ 2705 Schema: map[string]*Schema{ 2706 "foo": new(Schema), 2707 }, 2708 }, 2709 }, 2710 }, 2711 true, 2712 }, 2713 2714 // Sub-resource valid 2715 { 2716 map[string]*Schema{ 2717 "foo": &Schema{ 2718 Type: TypeList, 2719 Optional: true, 2720 Elem: &Resource{ 2721 Schema: map[string]*Schema{ 2722 "foo": &Schema{ 2723 Type: TypeInt, 2724 Optional: true, 2725 }, 2726 }, 2727 }, 2728 }, 2729 }, 2730 false, 2731 }, 2732 } 2733 2734 for i, tc := range cases { 2735 err := schemaMap(tc.In).InternalValidate() 2736 if (err != nil) != tc.Err { 2737 if tc.Err { 2738 t.Fatalf("%d: Expected error did not occur:\n\n%#v", i, tc.In) 2739 } 2740 t.Fatalf("%d: Unexpected error occured:\n\n%#v", i, tc.In) 2741 } 2742 } 2743 2744 } 2745 2746 func TestSchemaMap_Validate(t *testing.T) { 2747 cases := map[string]struct { 2748 Schema map[string]*Schema 2749 Config map[string]interface{} 2750 Vars map[string]string 2751 Err bool 2752 Errors []error 2753 Warnings []string 2754 }{ 2755 "Good": { 2756 Schema: map[string]*Schema{ 2757 "availability_zone": &Schema{ 2758 Type: TypeString, 2759 Optional: true, 2760 Computed: true, 2761 ForceNew: true, 2762 }, 2763 }, 2764 2765 Config: map[string]interface{}{ 2766 "availability_zone": "foo", 2767 }, 2768 }, 2769 2770 "Good, because the var is not set and that error will come elsewhere": { 2771 Schema: map[string]*Schema{ 2772 "size": &Schema{ 2773 Type: TypeInt, 2774 Required: true, 2775 }, 2776 }, 2777 2778 Config: map[string]interface{}{ 2779 "size": "${var.foo}", 2780 }, 2781 2782 Vars: map[string]string{ 2783 "var.foo": config.UnknownVariableValue, 2784 }, 2785 }, 2786 2787 "Required field not set": { 2788 Schema: map[string]*Schema{ 2789 "availability_zone": &Schema{ 2790 Type: TypeString, 2791 Required: true, 2792 }, 2793 }, 2794 2795 Config: map[string]interface{}{}, 2796 2797 Err: true, 2798 }, 2799 2800 "Invalid basic type": { 2801 Schema: map[string]*Schema{ 2802 "port": &Schema{ 2803 Type: TypeInt, 2804 Required: true, 2805 }, 2806 }, 2807 2808 Config: map[string]interface{}{ 2809 "port": "I am invalid", 2810 }, 2811 2812 Err: true, 2813 }, 2814 2815 "Invalid complex type": { 2816 Schema: map[string]*Schema{ 2817 "user_data": &Schema{ 2818 Type: TypeString, 2819 Optional: true, 2820 }, 2821 }, 2822 2823 Config: map[string]interface{}{ 2824 "user_data": []interface{}{ 2825 map[string]interface{}{ 2826 "foo": "bar", 2827 }, 2828 }, 2829 }, 2830 2831 Err: true, 2832 }, 2833 2834 "Bad type, interpolated": { 2835 Schema: map[string]*Schema{ 2836 "size": &Schema{ 2837 Type: TypeInt, 2838 Required: true, 2839 }, 2840 }, 2841 2842 Config: map[string]interface{}{ 2843 "size": "${var.foo}", 2844 }, 2845 2846 Vars: map[string]string{ 2847 "var.foo": "nope", 2848 }, 2849 2850 Err: true, 2851 }, 2852 2853 "Required but has DefaultFunc": { 2854 Schema: map[string]*Schema{ 2855 "availability_zone": &Schema{ 2856 Type: TypeString, 2857 Required: true, 2858 DefaultFunc: func() (interface{}, error) { 2859 return "foo", nil 2860 }, 2861 }, 2862 }, 2863 2864 Config: nil, 2865 }, 2866 2867 "Required but has DefaultFunc return nil": { 2868 Schema: map[string]*Schema{ 2869 "availability_zone": &Schema{ 2870 Type: TypeString, 2871 Required: true, 2872 DefaultFunc: func() (interface{}, error) { 2873 return nil, nil 2874 }, 2875 }, 2876 }, 2877 2878 Config: nil, 2879 2880 Err: true, 2881 }, 2882 2883 "Optional sub-resource": { 2884 Schema: map[string]*Schema{ 2885 "ingress": &Schema{ 2886 Type: TypeList, 2887 Elem: &Resource{ 2888 Schema: map[string]*Schema{ 2889 "from": &Schema{ 2890 Type: TypeInt, 2891 Required: true, 2892 }, 2893 }, 2894 }, 2895 }, 2896 }, 2897 2898 Config: map[string]interface{}{}, 2899 2900 Err: false, 2901 }, 2902 2903 "Not a list": { 2904 Schema: map[string]*Schema{ 2905 "ingress": &Schema{ 2906 Type: TypeList, 2907 Elem: &Resource{ 2908 Schema: map[string]*Schema{ 2909 "from": &Schema{ 2910 Type: TypeInt, 2911 Required: true, 2912 }, 2913 }, 2914 }, 2915 }, 2916 }, 2917 2918 Config: map[string]interface{}{ 2919 "ingress": "foo", 2920 }, 2921 2922 Err: true, 2923 }, 2924 2925 "Required sub-resource field": { 2926 Schema: map[string]*Schema{ 2927 "ingress": &Schema{ 2928 Type: TypeList, 2929 Elem: &Resource{ 2930 Schema: map[string]*Schema{ 2931 "from": &Schema{ 2932 Type: TypeInt, 2933 Required: true, 2934 }, 2935 }, 2936 }, 2937 }, 2938 }, 2939 2940 Config: map[string]interface{}{ 2941 "ingress": []interface{}{ 2942 map[string]interface{}{}, 2943 }, 2944 }, 2945 2946 Err: true, 2947 }, 2948 2949 "Good sub-resource": { 2950 Schema: map[string]*Schema{ 2951 "ingress": &Schema{ 2952 Type: TypeList, 2953 Optional: true, 2954 Elem: &Resource{ 2955 Schema: map[string]*Schema{ 2956 "from": &Schema{ 2957 Type: TypeInt, 2958 Required: true, 2959 }, 2960 }, 2961 }, 2962 }, 2963 }, 2964 2965 Config: map[string]interface{}{ 2966 "ingress": []interface{}{ 2967 map[string]interface{}{ 2968 "from": 80, 2969 }, 2970 }, 2971 }, 2972 2973 Err: false, 2974 }, 2975 2976 "Invalid/unknown field": { 2977 Schema: map[string]*Schema{ 2978 "availability_zone": &Schema{ 2979 Type: TypeString, 2980 Optional: true, 2981 Computed: true, 2982 ForceNew: true, 2983 }, 2984 }, 2985 2986 Config: map[string]interface{}{ 2987 "foo": "bar", 2988 }, 2989 2990 Err: true, 2991 }, 2992 2993 "Invalid/unknown field with computed value": { 2994 Schema: map[string]*Schema{ 2995 "availability_zone": &Schema{ 2996 Type: TypeString, 2997 Optional: true, 2998 Computed: true, 2999 ForceNew: true, 3000 }, 3001 }, 3002 3003 Config: map[string]interface{}{ 3004 "foo": "${var.foo}", 3005 }, 3006 3007 Vars: map[string]string{ 3008 "var.foo": config.UnknownVariableValue, 3009 }, 3010 3011 Err: true, 3012 }, 3013 3014 "Computed field set": { 3015 Schema: map[string]*Schema{ 3016 "availability_zone": &Schema{ 3017 Type: TypeString, 3018 Computed: true, 3019 }, 3020 }, 3021 3022 Config: map[string]interface{}{ 3023 "availability_zone": "bar", 3024 }, 3025 3026 Err: true, 3027 }, 3028 3029 "Not a set": { 3030 Schema: map[string]*Schema{ 3031 "ports": &Schema{ 3032 Type: TypeSet, 3033 Required: true, 3034 Elem: &Schema{Type: TypeInt}, 3035 Set: func(a interface{}) int { 3036 return a.(int) 3037 }, 3038 }, 3039 }, 3040 3041 Config: map[string]interface{}{ 3042 "ports": "foo", 3043 }, 3044 3045 Err: true, 3046 }, 3047 3048 "Maps": { 3049 Schema: map[string]*Schema{ 3050 "user_data": &Schema{ 3051 Type: TypeMap, 3052 Optional: true, 3053 }, 3054 }, 3055 3056 Config: map[string]interface{}{ 3057 "user_data": "foo", 3058 }, 3059 3060 Err: true, 3061 }, 3062 3063 "Good map: data surrounded by extra slice": { 3064 Schema: map[string]*Schema{ 3065 "user_data": &Schema{ 3066 Type: TypeMap, 3067 Optional: true, 3068 }, 3069 }, 3070 3071 Config: map[string]interface{}{ 3072 "user_data": []interface{}{ 3073 map[string]interface{}{ 3074 "foo": "bar", 3075 }, 3076 }, 3077 }, 3078 }, 3079 3080 "Good map": { 3081 Schema: map[string]*Schema{ 3082 "user_data": &Schema{ 3083 Type: TypeMap, 3084 Optional: true, 3085 }, 3086 }, 3087 3088 Config: map[string]interface{}{ 3089 "user_data": map[string]interface{}{ 3090 "foo": "bar", 3091 }, 3092 }, 3093 }, 3094 3095 "Bad map: just a slice": { 3096 Schema: map[string]*Schema{ 3097 "user_data": &Schema{ 3098 Type: TypeMap, 3099 Optional: true, 3100 }, 3101 }, 3102 3103 Config: map[string]interface{}{ 3104 "user_data": []interface{}{ 3105 "foo", 3106 }, 3107 }, 3108 3109 Err: true, 3110 }, 3111 3112 "Good set: config has slice with single interpolated value": { 3113 Schema: map[string]*Schema{ 3114 "security_groups": &Schema{ 3115 Type: TypeSet, 3116 Optional: true, 3117 Computed: true, 3118 ForceNew: true, 3119 Elem: &Schema{Type: TypeString}, 3120 Set: func(v interface{}) int { 3121 return len(v.(string)) 3122 }, 3123 }, 3124 }, 3125 3126 Config: map[string]interface{}{ 3127 "security_groups": []interface{}{"${var.foo}"}, 3128 }, 3129 3130 Err: false, 3131 }, 3132 3133 "Bad set: config has single interpolated value": { 3134 Schema: map[string]*Schema{ 3135 "security_groups": &Schema{ 3136 Type: TypeSet, 3137 Optional: true, 3138 Computed: true, 3139 ForceNew: true, 3140 Elem: &Schema{Type: TypeString}, 3141 }, 3142 }, 3143 3144 Config: map[string]interface{}{ 3145 "security_groups": "${var.foo}", 3146 }, 3147 3148 Err: true, 3149 }, 3150 3151 "Bad, subresource should not allow unknown elements": { 3152 Schema: map[string]*Schema{ 3153 "ingress": &Schema{ 3154 Type: TypeList, 3155 Optional: true, 3156 Elem: &Resource{ 3157 Schema: map[string]*Schema{ 3158 "port": &Schema{ 3159 Type: TypeInt, 3160 Required: true, 3161 }, 3162 }, 3163 }, 3164 }, 3165 }, 3166 3167 Config: map[string]interface{}{ 3168 "ingress": []interface{}{ 3169 map[string]interface{}{ 3170 "port": 80, 3171 "other": "yes", 3172 }, 3173 }, 3174 }, 3175 3176 Err: true, 3177 }, 3178 3179 "Bad, subresource should not allow invalid types": { 3180 Schema: map[string]*Schema{ 3181 "ingress": &Schema{ 3182 Type: TypeList, 3183 Optional: true, 3184 Elem: &Resource{ 3185 Schema: map[string]*Schema{ 3186 "port": &Schema{ 3187 Type: TypeInt, 3188 Required: true, 3189 }, 3190 }, 3191 }, 3192 }, 3193 }, 3194 3195 Config: map[string]interface{}{ 3196 "ingress": []interface{}{ 3197 map[string]interface{}{ 3198 "port": "bad", 3199 }, 3200 }, 3201 }, 3202 3203 Err: true, 3204 }, 3205 3206 "Deprecated attribute usage generates warning, but not error": { 3207 Schema: map[string]*Schema{ 3208 "old_news": &Schema{ 3209 Type: TypeString, 3210 Optional: true, 3211 Deprecated: "please use 'new_news' instead", 3212 }, 3213 }, 3214 3215 Config: map[string]interface{}{ 3216 "old_news": "extra extra!", 3217 }, 3218 3219 Err: false, 3220 3221 Warnings: []string{ 3222 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 3223 }, 3224 }, 3225 3226 "Deprecated generates no warnings if attr not used": { 3227 Schema: map[string]*Schema{ 3228 "old_news": &Schema{ 3229 Type: TypeString, 3230 Optional: true, 3231 Deprecated: "please use 'new_news' instead", 3232 }, 3233 }, 3234 3235 Err: false, 3236 3237 Warnings: nil, 3238 }, 3239 3240 "Removed attribute usage generates error": { 3241 Schema: map[string]*Schema{ 3242 "long_gone": &Schema{ 3243 Type: TypeString, 3244 Optional: true, 3245 Removed: "no longer supported by Cloud API", 3246 }, 3247 }, 3248 3249 Config: map[string]interface{}{ 3250 "long_gone": "still here!", 3251 }, 3252 3253 Err: true, 3254 Errors: []error{ 3255 fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), 3256 }, 3257 }, 3258 3259 "Removed generates no errors if attr not used": { 3260 Schema: map[string]*Schema{ 3261 "long_gone": &Schema{ 3262 Type: TypeString, 3263 Optional: true, 3264 Removed: "no longer supported by Cloud API", 3265 }, 3266 }, 3267 3268 Err: false, 3269 }, 3270 3271 "Conflicting attributes generate error": { 3272 Schema: map[string]*Schema{ 3273 "whitelist": &Schema{ 3274 Type: TypeString, 3275 Optional: true, 3276 }, 3277 "blacklist": &Schema{ 3278 Type: TypeString, 3279 Optional: true, 3280 ConflictsWith: []string{"whitelist"}, 3281 }, 3282 }, 3283 3284 Config: map[string]interface{}{ 3285 "whitelist": "white-val", 3286 "blacklist": "black-val", 3287 }, 3288 3289 Err: true, 3290 Errors: []error{ 3291 fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"), 3292 }, 3293 }, 3294 3295 "Required attribute & undefined conflicting optional are good": { 3296 Schema: map[string]*Schema{ 3297 "required_att": &Schema{ 3298 Type: TypeString, 3299 Required: true, 3300 }, 3301 "optional_att": &Schema{ 3302 Type: TypeString, 3303 Optional: true, 3304 ConflictsWith: []string{"required_att"}, 3305 }, 3306 }, 3307 3308 Config: map[string]interface{}{ 3309 "required_att": "required-val", 3310 }, 3311 3312 Err: false, 3313 }, 3314 3315 "Required conflicting attribute & defined optional generate error": { 3316 Schema: map[string]*Schema{ 3317 "required_att": &Schema{ 3318 Type: TypeString, 3319 Required: true, 3320 }, 3321 "optional_att": &Schema{ 3322 Type: TypeString, 3323 Optional: true, 3324 ConflictsWith: []string{"required_att"}, 3325 }, 3326 }, 3327 3328 Config: map[string]interface{}{ 3329 "required_att": "required-val", 3330 "optional_att": "optional-val", 3331 }, 3332 3333 Err: true, 3334 Errors: []error{ 3335 fmt.Errorf("\"optional_att\": conflicts with required_att (\"required-val\")"), 3336 }, 3337 }, 3338 } 3339 3340 for tn, tc := range cases { 3341 c, err := config.NewRawConfig(tc.Config) 3342 if err != nil { 3343 t.Fatalf("err: %s", err) 3344 } 3345 if tc.Vars != nil { 3346 vars := make(map[string]ast.Variable) 3347 for k, v := range tc.Vars { 3348 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 3349 } 3350 3351 if err := c.Interpolate(vars); err != nil { 3352 t.Fatalf("err: %s", err) 3353 } 3354 } 3355 3356 ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 3357 if (len(es) > 0) != tc.Err { 3358 if len(es) == 0 { 3359 t.Errorf("%q: no errors", tn) 3360 } 3361 3362 for _, e := range es { 3363 t.Errorf("%q: err: %s", tn, e) 3364 } 3365 3366 t.FailNow() 3367 } 3368 3369 if !reflect.DeepEqual(ws, tc.Warnings) { 3370 t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) 3371 } 3372 3373 if tc.Errors != nil { 3374 if !reflect.DeepEqual(es, tc.Errors) { 3375 t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) 3376 } 3377 } 3378 } 3379 }