github.com/hobbeswalsh/terraform@v0.3.7-0.20150619183303-ad17cf55a0fa/helper/schema/schema_test.go (about) 1 package schema 2 3 import ( 4 "bytes" 5 "fmt" 6 "os" 7 "reflect" 8 "strconv" 9 "testing" 10 11 "github.com/hashicorp/terraform/config" 12 "github.com/hashicorp/terraform/config/lang/ast" 13 "github.com/hashicorp/terraform/helper/hashcode" 14 "github.com/hashicorp/terraform/terraform" 15 ) 16 17 func TestEnvDefaultFunc(t *testing.T) { 18 key := "TF_TEST_ENV_DEFAULT_FUNC" 19 defer os.Unsetenv(key) 20 21 f := EnvDefaultFunc(key, "42") 22 if err := os.Setenv(key, "foo"); err != nil { 23 t.Fatalf("err: %s", err) 24 } 25 26 actual, err := f() 27 if err != nil { 28 t.Fatalf("err: %s", err) 29 } 30 if actual != "foo" { 31 t.Fatalf("bad: %#v", actual) 32 } 33 34 if err := os.Unsetenv(key); err != nil { 35 t.Fatalf("err: %s", err) 36 } 37 38 actual, err = f() 39 if err != nil { 40 t.Fatalf("err: %s", err) 41 } 42 if actual != "42" { 43 t.Fatalf("bad: %#v", actual) 44 } 45 } 46 47 func TestMultiEnvDefaultFunc(t *testing.T) { 48 keys := []string{ 49 "TF_TEST_MULTI_ENV_DEFAULT_FUNC1", 50 "TF_TEST_MULTI_ENV_DEFAULT_FUNC2", 51 } 52 defer func() { 53 for _, k := range keys { 54 os.Unsetenv(k) 55 } 56 }() 57 58 // Test that the first key is returned first 59 f := MultiEnvDefaultFunc(keys, "42") 60 if err := os.Setenv(keys[0], "foo"); err != nil { 61 t.Fatalf("err: %s", err) 62 } 63 64 actual, err := f() 65 if err != nil { 66 t.Fatalf("err: %s", err) 67 } 68 if actual != "foo" { 69 t.Fatalf("bad: %#v", actual) 70 } 71 72 if err := os.Unsetenv(keys[0]); err != nil { 73 t.Fatalf("err: %s", err) 74 } 75 76 // Test that the second key is returned if the first one is empty 77 f = MultiEnvDefaultFunc(keys, "42") 78 if err := os.Setenv(keys[1], "foo"); err != nil { 79 t.Fatalf("err: %s", err) 80 } 81 82 actual, err = f() 83 if err != nil { 84 t.Fatalf("err: %s", err) 85 } 86 if actual != "foo" { 87 t.Fatalf("bad: %#v", actual) 88 } 89 90 if err := os.Unsetenv(keys[1]); err != nil { 91 t.Fatalf("err: %s", err) 92 } 93 94 // Test that the default value is returned when no keys are set 95 actual, err = f() 96 if err != nil { 97 t.Fatalf("err: %s", err) 98 } 99 if actual != "42" { 100 t.Fatalf("bad: %#v", actual) 101 } 102 } 103 104 func TestValueType_Zero(t *testing.T) { 105 cases := []struct { 106 Type ValueType 107 Value interface{} 108 }{ 109 {TypeBool, false}, 110 {TypeInt, 0}, 111 {TypeFloat, 0.0}, 112 {TypeString, ""}, 113 {TypeList, []interface{}{}}, 114 {TypeMap, map[string]interface{}{}}, 115 {TypeSet, new(Set)}, 116 } 117 118 for i, tc := range cases { 119 actual := tc.Type.Zero() 120 if !reflect.DeepEqual(actual, tc.Value) { 121 t.Fatalf("%d: %#v != %#v", i, actual, tc.Value) 122 } 123 } 124 } 125 126 func TestSchemaMap_Diff(t *testing.T) { 127 cases := []struct { 128 Schema map[string]*Schema 129 State *terraform.InstanceState 130 Config map[string]interface{} 131 ConfigVariables map[string]string 132 Diff *terraform.InstanceDiff 133 Err bool 134 }{ 135 /* 136 * String decode 137 */ 138 139 // #0 140 { 141 Schema: map[string]*Schema{ 142 "availability_zone": &Schema{ 143 Type: TypeString, 144 Optional: true, 145 Computed: true, 146 ForceNew: true, 147 }, 148 }, 149 150 State: nil, 151 152 Config: map[string]interface{}{ 153 "availability_zone": "foo", 154 }, 155 156 Diff: &terraform.InstanceDiff{ 157 Attributes: map[string]*terraform.ResourceAttrDiff{ 158 "availability_zone": &terraform.ResourceAttrDiff{ 159 Old: "", 160 New: "foo", 161 RequiresNew: true, 162 }, 163 }, 164 }, 165 166 Err: false, 167 }, 168 169 // #1 170 { 171 Schema: map[string]*Schema{ 172 "availability_zone": &Schema{ 173 Type: TypeString, 174 Optional: true, 175 Computed: true, 176 ForceNew: true, 177 }, 178 }, 179 180 State: nil, 181 182 Config: map[string]interface{}{}, 183 184 Diff: &terraform.InstanceDiff{ 185 Attributes: map[string]*terraform.ResourceAttrDiff{ 186 "availability_zone": &terraform.ResourceAttrDiff{ 187 Old: "", 188 NewComputed: true, 189 RequiresNew: true, 190 }, 191 }, 192 }, 193 194 Err: false, 195 }, 196 197 // #2 198 { 199 Schema: map[string]*Schema{ 200 "availability_zone": &Schema{ 201 Type: TypeString, 202 Optional: true, 203 Computed: true, 204 ForceNew: true, 205 }, 206 }, 207 208 State: &terraform.InstanceState{ 209 ID: "foo", 210 }, 211 212 Config: map[string]interface{}{}, 213 214 Diff: nil, 215 216 Err: false, 217 }, 218 219 // #3 Computed, but set in config 220 { 221 Schema: map[string]*Schema{ 222 "availability_zone": &Schema{ 223 Type: TypeString, 224 Optional: true, 225 Computed: true, 226 }, 227 }, 228 229 State: &terraform.InstanceState{ 230 Attributes: map[string]string{ 231 "availability_zone": "foo", 232 }, 233 }, 234 235 Config: map[string]interface{}{ 236 "availability_zone": "bar", 237 }, 238 239 Diff: &terraform.InstanceDiff{ 240 Attributes: map[string]*terraform.ResourceAttrDiff{ 241 "availability_zone": &terraform.ResourceAttrDiff{ 242 Old: "foo", 243 New: "bar", 244 }, 245 }, 246 }, 247 248 Err: false, 249 }, 250 251 // #4 Default 252 { 253 Schema: map[string]*Schema{ 254 "availability_zone": &Schema{ 255 Type: TypeString, 256 Optional: true, 257 Default: "foo", 258 }, 259 }, 260 261 State: nil, 262 263 Config: nil, 264 265 Diff: &terraform.InstanceDiff{ 266 Attributes: map[string]*terraform.ResourceAttrDiff{ 267 "availability_zone": &terraform.ResourceAttrDiff{ 268 Old: "", 269 New: "foo", 270 }, 271 }, 272 }, 273 274 Err: false, 275 }, 276 277 // #5 DefaultFunc, value 278 { 279 Schema: map[string]*Schema{ 280 "availability_zone": &Schema{ 281 Type: TypeString, 282 Optional: true, 283 DefaultFunc: func() (interface{}, error) { 284 return "foo", nil 285 }, 286 }, 287 }, 288 289 State: nil, 290 291 Config: nil, 292 293 Diff: &terraform.InstanceDiff{ 294 Attributes: map[string]*terraform.ResourceAttrDiff{ 295 "availability_zone": &terraform.ResourceAttrDiff{ 296 Old: "", 297 New: "foo", 298 }, 299 }, 300 }, 301 302 Err: false, 303 }, 304 305 // #6 DefaultFunc, configuration set 306 { 307 Schema: map[string]*Schema{ 308 "availability_zone": &Schema{ 309 Type: TypeString, 310 Optional: true, 311 DefaultFunc: func() (interface{}, error) { 312 return "foo", nil 313 }, 314 }, 315 }, 316 317 State: nil, 318 319 Config: map[string]interface{}{ 320 "availability_zone": "bar", 321 }, 322 323 Diff: &terraform.InstanceDiff{ 324 Attributes: map[string]*terraform.ResourceAttrDiff{ 325 "availability_zone": &terraform.ResourceAttrDiff{ 326 Old: "", 327 New: "bar", 328 }, 329 }, 330 }, 331 332 Err: false, 333 }, 334 335 // #7 String with StateFunc 336 { 337 Schema: map[string]*Schema{ 338 "availability_zone": &Schema{ 339 Type: TypeString, 340 Optional: true, 341 Computed: true, 342 StateFunc: func(a interface{}) string { 343 return a.(string) + "!" 344 }, 345 }, 346 }, 347 348 State: nil, 349 350 Config: map[string]interface{}{ 351 "availability_zone": "foo", 352 }, 353 354 Diff: &terraform.InstanceDiff{ 355 Attributes: map[string]*terraform.ResourceAttrDiff{ 356 "availability_zone": &terraform.ResourceAttrDiff{ 357 Old: "", 358 New: "foo!", 359 NewExtra: "foo", 360 }, 361 }, 362 }, 363 364 Err: false, 365 }, 366 367 // #8 Variable (just checking) 368 { 369 Schema: map[string]*Schema{ 370 "availability_zone": &Schema{ 371 Type: TypeString, 372 Optional: true, 373 }, 374 }, 375 376 State: nil, 377 378 Config: map[string]interface{}{ 379 "availability_zone": "${var.foo}", 380 }, 381 382 ConfigVariables: map[string]string{ 383 "var.foo": "bar", 384 }, 385 386 Diff: &terraform.InstanceDiff{ 387 Attributes: map[string]*terraform.ResourceAttrDiff{ 388 "availability_zone": &terraform.ResourceAttrDiff{ 389 Old: "", 390 New: "bar", 391 }, 392 }, 393 }, 394 395 Err: false, 396 }, 397 398 // #9 Variable computed 399 { 400 Schema: map[string]*Schema{ 401 "availability_zone": &Schema{ 402 Type: TypeString, 403 Optional: true, 404 }, 405 }, 406 407 State: nil, 408 409 Config: map[string]interface{}{ 410 "availability_zone": "${var.foo}", 411 }, 412 413 ConfigVariables: map[string]string{ 414 "var.foo": config.UnknownVariableValue, 415 }, 416 417 Diff: &terraform.InstanceDiff{ 418 Attributes: map[string]*terraform.ResourceAttrDiff{ 419 "availability_zone": &terraform.ResourceAttrDiff{ 420 Old: "", 421 New: "${var.foo}", 422 }, 423 }, 424 }, 425 426 Err: false, 427 }, 428 429 /* 430 * Int decode 431 */ 432 433 // #10 434 { 435 Schema: map[string]*Schema{ 436 "port": &Schema{ 437 Type: TypeInt, 438 Optional: true, 439 Computed: true, 440 ForceNew: true, 441 }, 442 }, 443 444 State: nil, 445 446 Config: map[string]interface{}{ 447 "port": 27, 448 }, 449 450 Diff: &terraform.InstanceDiff{ 451 Attributes: map[string]*terraform.ResourceAttrDiff{ 452 "port": &terraform.ResourceAttrDiff{ 453 Old: "", 454 New: "27", 455 RequiresNew: true, 456 }, 457 }, 458 }, 459 460 Err: false, 461 }, 462 463 /* 464 * Bool decode 465 */ 466 467 // #11 468 { 469 Schema: map[string]*Schema{ 470 "port": &Schema{ 471 Type: TypeBool, 472 Optional: true, 473 Computed: true, 474 ForceNew: true, 475 }, 476 }, 477 478 State: nil, 479 480 Config: map[string]interface{}{ 481 "port": false, 482 }, 483 484 Diff: &terraform.InstanceDiff{ 485 Attributes: map[string]*terraform.ResourceAttrDiff{ 486 "port": &terraform.ResourceAttrDiff{ 487 Old: "", 488 New: "0", 489 RequiresNew: true, 490 }, 491 }, 492 }, 493 494 Err: false, 495 }, 496 497 /* 498 * Bool 499 */ 500 501 // #12 502 { 503 Schema: map[string]*Schema{ 504 "delete": &Schema{ 505 Type: TypeBool, 506 Optional: true, 507 Default: false, 508 }, 509 }, 510 511 State: &terraform.InstanceState{ 512 Attributes: map[string]string{ 513 "delete": "false", 514 }, 515 }, 516 517 Config: nil, 518 519 Diff: nil, 520 521 Err: false, 522 }, 523 524 /* 525 * List decode 526 */ 527 528 // #13 529 { 530 Schema: map[string]*Schema{ 531 "ports": &Schema{ 532 Type: TypeList, 533 Required: true, 534 Elem: &Schema{Type: TypeInt}, 535 }, 536 }, 537 538 State: nil, 539 540 Config: map[string]interface{}{ 541 "ports": []interface{}{1, 2, 5}, 542 }, 543 544 Diff: &terraform.InstanceDiff{ 545 Attributes: map[string]*terraform.ResourceAttrDiff{ 546 "ports.#": &terraform.ResourceAttrDiff{ 547 Old: "0", 548 New: "3", 549 }, 550 "ports.0": &terraform.ResourceAttrDiff{ 551 Old: "", 552 New: "1", 553 }, 554 "ports.1": &terraform.ResourceAttrDiff{ 555 Old: "", 556 New: "2", 557 }, 558 "ports.2": &terraform.ResourceAttrDiff{ 559 Old: "", 560 New: "5", 561 }, 562 }, 563 }, 564 565 Err: false, 566 }, 567 568 // #14 569 { 570 Schema: map[string]*Schema{ 571 "ports": &Schema{ 572 Type: TypeList, 573 Required: true, 574 Elem: &Schema{Type: TypeInt}, 575 }, 576 }, 577 578 State: nil, 579 580 Config: map[string]interface{}{ 581 "ports": []interface{}{1, "${var.foo}"}, 582 }, 583 584 ConfigVariables: map[string]string{ 585 "var.foo": "2" + config.InterpSplitDelim + "5", 586 }, 587 588 Diff: &terraform.InstanceDiff{ 589 Attributes: map[string]*terraform.ResourceAttrDiff{ 590 "ports.#": &terraform.ResourceAttrDiff{ 591 Old: "0", 592 New: "3", 593 }, 594 "ports.0": &terraform.ResourceAttrDiff{ 595 Old: "", 596 New: "1", 597 }, 598 "ports.1": &terraform.ResourceAttrDiff{ 599 Old: "", 600 New: "2", 601 }, 602 "ports.2": &terraform.ResourceAttrDiff{ 603 Old: "", 604 New: "5", 605 }, 606 }, 607 }, 608 609 Err: false, 610 }, 611 612 // #15 613 { 614 Schema: map[string]*Schema{ 615 "ports": &Schema{ 616 Type: TypeList, 617 Required: true, 618 Elem: &Schema{Type: TypeInt}, 619 }, 620 }, 621 622 State: nil, 623 624 Config: map[string]interface{}{ 625 "ports": []interface{}{1, "${var.foo}"}, 626 }, 627 628 ConfigVariables: map[string]string{ 629 "var.foo": config.UnknownVariableValue + 630 config.InterpSplitDelim + "5", 631 }, 632 633 Diff: &terraform.InstanceDiff{ 634 Attributes: map[string]*terraform.ResourceAttrDiff{ 635 "ports.#": &terraform.ResourceAttrDiff{ 636 Old: "0", 637 New: "", 638 NewComputed: true, 639 }, 640 }, 641 }, 642 643 Err: false, 644 }, 645 646 // #16 647 { 648 Schema: map[string]*Schema{ 649 "ports": &Schema{ 650 Type: TypeList, 651 Required: true, 652 Elem: &Schema{Type: TypeInt}, 653 }, 654 }, 655 656 State: &terraform.InstanceState{ 657 Attributes: map[string]string{ 658 "ports.#": "3", 659 "ports.0": "1", 660 "ports.1": "2", 661 "ports.2": "5", 662 }, 663 }, 664 665 Config: map[string]interface{}{ 666 "ports": []interface{}{1, 2, 5}, 667 }, 668 669 Diff: nil, 670 671 Err: false, 672 }, 673 674 // #17 675 { 676 Schema: map[string]*Schema{ 677 "ports": &Schema{ 678 Type: TypeList, 679 Required: true, 680 Elem: &Schema{Type: TypeInt}, 681 }, 682 }, 683 684 State: &terraform.InstanceState{ 685 Attributes: map[string]string{ 686 "ports.#": "2", 687 "ports.0": "1", 688 "ports.1": "2", 689 }, 690 }, 691 692 Config: map[string]interface{}{ 693 "ports": []interface{}{1, 2, 5}, 694 }, 695 696 Diff: &terraform.InstanceDiff{ 697 Attributes: map[string]*terraform.ResourceAttrDiff{ 698 "ports.#": &terraform.ResourceAttrDiff{ 699 Old: "2", 700 New: "3", 701 }, 702 "ports.2": &terraform.ResourceAttrDiff{ 703 Old: "", 704 New: "5", 705 }, 706 }, 707 }, 708 709 Err: false, 710 }, 711 712 // #18 713 { 714 Schema: map[string]*Schema{ 715 "ports": &Schema{ 716 Type: TypeList, 717 Required: true, 718 Elem: &Schema{Type: TypeInt}, 719 ForceNew: true, 720 }, 721 }, 722 723 State: nil, 724 725 Config: map[string]interface{}{ 726 "ports": []interface{}{1, 2, 5}, 727 }, 728 729 Diff: &terraform.InstanceDiff{ 730 Attributes: map[string]*terraform.ResourceAttrDiff{ 731 "ports.#": &terraform.ResourceAttrDiff{ 732 Old: "0", 733 New: "3", 734 RequiresNew: true, 735 }, 736 "ports.0": &terraform.ResourceAttrDiff{ 737 Old: "", 738 New: "1", 739 RequiresNew: true, 740 }, 741 "ports.1": &terraform.ResourceAttrDiff{ 742 Old: "", 743 New: "2", 744 RequiresNew: true, 745 }, 746 "ports.2": &terraform.ResourceAttrDiff{ 747 Old: "", 748 New: "5", 749 RequiresNew: true, 750 }, 751 }, 752 }, 753 754 Err: false, 755 }, 756 757 // #19 758 { 759 Schema: map[string]*Schema{ 760 "ports": &Schema{ 761 Type: TypeList, 762 Optional: true, 763 Computed: true, 764 Elem: &Schema{Type: TypeInt}, 765 }, 766 }, 767 768 State: nil, 769 770 Config: map[string]interface{}{}, 771 772 Diff: &terraform.InstanceDiff{ 773 Attributes: map[string]*terraform.ResourceAttrDiff{ 774 "ports.#": &terraform.ResourceAttrDiff{ 775 Old: "", 776 NewComputed: true, 777 }, 778 }, 779 }, 780 781 Err: false, 782 }, 783 784 /* 785 * Set 786 */ 787 788 // #20 789 { 790 Schema: map[string]*Schema{ 791 "ports": &Schema{ 792 Type: TypeSet, 793 Required: true, 794 Elem: &Schema{Type: TypeInt}, 795 Set: func(a interface{}) int { 796 return a.(int) 797 }, 798 }, 799 }, 800 801 State: nil, 802 803 Config: map[string]interface{}{ 804 "ports": []interface{}{5, 2, 1}, 805 }, 806 807 Diff: &terraform.InstanceDiff{ 808 Attributes: map[string]*terraform.ResourceAttrDiff{ 809 "ports.#": &terraform.ResourceAttrDiff{ 810 Old: "0", 811 New: "3", 812 }, 813 "ports.1": &terraform.ResourceAttrDiff{ 814 Old: "", 815 New: "1", 816 }, 817 "ports.2": &terraform.ResourceAttrDiff{ 818 Old: "", 819 New: "2", 820 }, 821 "ports.5": &terraform.ResourceAttrDiff{ 822 Old: "", 823 New: "5", 824 }, 825 }, 826 }, 827 828 Err: false, 829 }, 830 831 // #21 832 { 833 Schema: map[string]*Schema{ 834 "ports": &Schema{ 835 Type: TypeSet, 836 Computed: true, 837 Required: true, 838 Elem: &Schema{Type: TypeInt}, 839 Set: func(a interface{}) int { 840 return a.(int) 841 }, 842 }, 843 }, 844 845 State: &terraform.InstanceState{ 846 Attributes: map[string]string{ 847 "ports.#": "0", 848 }, 849 }, 850 851 Config: nil, 852 853 Diff: nil, 854 855 Err: false, 856 }, 857 858 // #22 859 { 860 Schema: map[string]*Schema{ 861 "ports": &Schema{ 862 Type: TypeSet, 863 Optional: true, 864 Computed: true, 865 Elem: &Schema{Type: TypeInt}, 866 Set: func(a interface{}) int { 867 return a.(int) 868 }, 869 }, 870 }, 871 872 State: nil, 873 874 Config: nil, 875 876 Diff: &terraform.InstanceDiff{ 877 Attributes: map[string]*terraform.ResourceAttrDiff{ 878 "ports.#": &terraform.ResourceAttrDiff{ 879 Old: "", 880 NewComputed: true, 881 }, 882 }, 883 }, 884 885 Err: false, 886 }, 887 888 // #23 889 { 890 Schema: map[string]*Schema{ 891 "ports": &Schema{ 892 Type: TypeSet, 893 Required: true, 894 Elem: &Schema{Type: TypeInt}, 895 Set: func(a interface{}) int { 896 return a.(int) 897 }, 898 }, 899 }, 900 901 State: nil, 902 903 Config: map[string]interface{}{ 904 "ports": []interface{}{"${var.foo}", 1}, 905 }, 906 907 ConfigVariables: map[string]string{ 908 "var.foo": "2" + config.InterpSplitDelim + "5", 909 }, 910 911 Diff: &terraform.InstanceDiff{ 912 Attributes: map[string]*terraform.ResourceAttrDiff{ 913 "ports.#": &terraform.ResourceAttrDiff{ 914 Old: "0", 915 New: "3", 916 }, 917 "ports.1": &terraform.ResourceAttrDiff{ 918 Old: "", 919 New: "1", 920 }, 921 "ports.2": &terraform.ResourceAttrDiff{ 922 Old: "", 923 New: "2", 924 }, 925 "ports.5": &terraform.ResourceAttrDiff{ 926 Old: "", 927 New: "5", 928 }, 929 }, 930 }, 931 932 Err: false, 933 }, 934 935 // #24 936 { 937 Schema: map[string]*Schema{ 938 "ports": &Schema{ 939 Type: TypeSet, 940 Required: true, 941 Elem: &Schema{Type: TypeInt}, 942 Set: func(a interface{}) int { 943 return a.(int) 944 }, 945 }, 946 }, 947 948 State: nil, 949 950 Config: map[string]interface{}{ 951 "ports": []interface{}{1, "${var.foo}"}, 952 }, 953 954 ConfigVariables: map[string]string{ 955 "var.foo": config.UnknownVariableValue + 956 config.InterpSplitDelim + "5", 957 }, 958 959 Diff: &terraform.InstanceDiff{ 960 Attributes: map[string]*terraform.ResourceAttrDiff{ 961 "ports.#": &terraform.ResourceAttrDiff{ 962 Old: "", 963 New: "", 964 NewComputed: true, 965 }, 966 }, 967 }, 968 969 Err: false, 970 }, 971 972 // #25 973 { 974 Schema: map[string]*Schema{ 975 "ports": &Schema{ 976 Type: TypeSet, 977 Required: true, 978 Elem: &Schema{Type: TypeInt}, 979 Set: func(a interface{}) int { 980 return a.(int) 981 }, 982 }, 983 }, 984 985 State: &terraform.InstanceState{ 986 Attributes: map[string]string{ 987 "ports.#": "2", 988 "ports.1": "1", 989 "ports.2": "2", 990 }, 991 }, 992 993 Config: map[string]interface{}{ 994 "ports": []interface{}{5, 2, 1}, 995 }, 996 997 Diff: &terraform.InstanceDiff{ 998 Attributes: map[string]*terraform.ResourceAttrDiff{ 999 "ports.#": &terraform.ResourceAttrDiff{ 1000 Old: "2", 1001 New: "3", 1002 }, 1003 "ports.1": &terraform.ResourceAttrDiff{ 1004 Old: "1", 1005 New: "1", 1006 }, 1007 "ports.2": &terraform.ResourceAttrDiff{ 1008 Old: "2", 1009 New: "2", 1010 }, 1011 "ports.5": &terraform.ResourceAttrDiff{ 1012 Old: "", 1013 New: "5", 1014 }, 1015 }, 1016 }, 1017 1018 Err: false, 1019 }, 1020 1021 // #26 1022 { 1023 Schema: map[string]*Schema{ 1024 "ports": &Schema{ 1025 Type: TypeSet, 1026 Required: true, 1027 Elem: &Schema{Type: TypeInt}, 1028 Set: func(a interface{}) int { 1029 return a.(int) 1030 }, 1031 }, 1032 }, 1033 1034 State: &terraform.InstanceState{ 1035 Attributes: map[string]string{ 1036 "ports.#": "2", 1037 "ports.1": "1", 1038 "ports.2": "2", 1039 }, 1040 }, 1041 1042 Config: map[string]interface{}{}, 1043 1044 Diff: &terraform.InstanceDiff{ 1045 Attributes: map[string]*terraform.ResourceAttrDiff{ 1046 "ports.#": &terraform.ResourceAttrDiff{ 1047 Old: "2", 1048 New: "0", 1049 }, 1050 }, 1051 }, 1052 1053 Err: false, 1054 }, 1055 1056 // #27 1057 { 1058 Schema: map[string]*Schema{ 1059 "ports": &Schema{ 1060 Type: TypeSet, 1061 Optional: true, 1062 Computed: true, 1063 Elem: &Schema{Type: TypeInt}, 1064 Set: func(a interface{}) int { 1065 return a.(int) 1066 }, 1067 }, 1068 }, 1069 1070 State: &terraform.InstanceState{ 1071 Attributes: map[string]string{ 1072 "availability_zone": "bar", 1073 "ports.#": "1", 1074 "ports.80": "80", 1075 }, 1076 }, 1077 1078 Config: map[string]interface{}{}, 1079 1080 Diff: nil, 1081 1082 Err: false, 1083 }, 1084 1085 // #28 1086 { 1087 Schema: map[string]*Schema{ 1088 "ingress": &Schema{ 1089 Type: TypeSet, 1090 Required: true, 1091 Elem: &Resource{ 1092 Schema: map[string]*Schema{ 1093 "ports": &Schema{ 1094 Type: TypeList, 1095 Optional: true, 1096 Elem: &Schema{Type: TypeInt}, 1097 }, 1098 }, 1099 }, 1100 Set: func(v interface{}) int { 1101 m := v.(map[string]interface{}) 1102 ps := m["ports"].([]interface{}) 1103 result := 0 1104 for _, p := range ps { 1105 result += p.(int) 1106 } 1107 return result 1108 }, 1109 }, 1110 }, 1111 1112 State: &terraform.InstanceState{ 1113 Attributes: map[string]string{ 1114 "ingress.#": "2", 1115 "ingress.80.ports.#": "1", 1116 "ingress.80.ports.0": "80", 1117 "ingress.443.ports.#": "1", 1118 "ingress.443.ports.0": "443", 1119 }, 1120 }, 1121 1122 Config: map[string]interface{}{ 1123 "ingress": []map[string]interface{}{ 1124 map[string]interface{}{ 1125 "ports": []interface{}{443}, 1126 }, 1127 map[string]interface{}{ 1128 "ports": []interface{}{80}, 1129 }, 1130 }, 1131 }, 1132 1133 Diff: nil, 1134 1135 Err: false, 1136 }, 1137 1138 /* 1139 * List of structure decode 1140 */ 1141 1142 // #29 1143 { 1144 Schema: map[string]*Schema{ 1145 "ingress": &Schema{ 1146 Type: TypeList, 1147 Required: true, 1148 Elem: &Resource{ 1149 Schema: map[string]*Schema{ 1150 "from": &Schema{ 1151 Type: TypeInt, 1152 Required: true, 1153 }, 1154 }, 1155 }, 1156 }, 1157 }, 1158 1159 State: nil, 1160 1161 Config: map[string]interface{}{ 1162 "ingress": []interface{}{ 1163 map[string]interface{}{ 1164 "from": 8080, 1165 }, 1166 }, 1167 }, 1168 1169 Diff: &terraform.InstanceDiff{ 1170 Attributes: map[string]*terraform.ResourceAttrDiff{ 1171 "ingress.#": &terraform.ResourceAttrDiff{ 1172 Old: "0", 1173 New: "1", 1174 }, 1175 "ingress.0.from": &terraform.ResourceAttrDiff{ 1176 Old: "", 1177 New: "8080", 1178 }, 1179 }, 1180 }, 1181 1182 Err: false, 1183 }, 1184 1185 /* 1186 * ComputedWhen 1187 */ 1188 1189 // #30 1190 { 1191 Schema: map[string]*Schema{ 1192 "availability_zone": &Schema{ 1193 Type: TypeString, 1194 Computed: true, 1195 ComputedWhen: []string{"port"}, 1196 }, 1197 1198 "port": &Schema{ 1199 Type: TypeInt, 1200 Optional: true, 1201 }, 1202 }, 1203 1204 State: &terraform.InstanceState{ 1205 Attributes: map[string]string{ 1206 "availability_zone": "foo", 1207 "port": "80", 1208 }, 1209 }, 1210 1211 Config: map[string]interface{}{ 1212 "port": 80, 1213 }, 1214 1215 Diff: nil, 1216 1217 Err: false, 1218 }, 1219 1220 // #31 1221 { 1222 Schema: map[string]*Schema{ 1223 "availability_zone": &Schema{ 1224 Type: TypeString, 1225 Computed: true, 1226 ComputedWhen: []string{"port"}, 1227 }, 1228 1229 "port": &Schema{ 1230 Type: TypeInt, 1231 Optional: true, 1232 }, 1233 }, 1234 1235 State: &terraform.InstanceState{ 1236 Attributes: map[string]string{ 1237 "port": "80", 1238 }, 1239 }, 1240 1241 Config: map[string]interface{}{ 1242 "port": 80, 1243 }, 1244 1245 Diff: &terraform.InstanceDiff{ 1246 Attributes: map[string]*terraform.ResourceAttrDiff{ 1247 "availability_zone": &terraform.ResourceAttrDiff{ 1248 NewComputed: true, 1249 }, 1250 }, 1251 }, 1252 1253 Err: false, 1254 }, 1255 1256 /* TODO 1257 { 1258 Schema: map[string]*Schema{ 1259 "availability_zone": &Schema{ 1260 Type: TypeString, 1261 Computed: true, 1262 ComputedWhen: []string{"port"}, 1263 }, 1264 1265 "port": &Schema{ 1266 Type: TypeInt, 1267 Optional: true, 1268 }, 1269 }, 1270 1271 State: &terraform.InstanceState{ 1272 Attributes: map[string]string{ 1273 "availability_zone": "foo", 1274 "port": "80", 1275 }, 1276 }, 1277 1278 Config: map[string]interface{}{ 1279 "port": 8080, 1280 }, 1281 1282 Diff: &terraform.ResourceDiff{ 1283 Attributes: map[string]*terraform.ResourceAttrDiff{ 1284 "availability_zone": &terraform.ResourceAttrDiff{ 1285 Old: "foo", 1286 NewComputed: true, 1287 }, 1288 "port": &terraform.ResourceAttrDiff{ 1289 Old: "80", 1290 New: "8080", 1291 }, 1292 }, 1293 }, 1294 1295 Err: false, 1296 }, 1297 */ 1298 1299 /* 1300 * Maps 1301 */ 1302 1303 // #32 1304 { 1305 Schema: map[string]*Schema{ 1306 "config_vars": &Schema{ 1307 Type: TypeMap, 1308 }, 1309 }, 1310 1311 State: nil, 1312 1313 Config: map[string]interface{}{ 1314 "config_vars": []interface{}{ 1315 map[string]interface{}{ 1316 "bar": "baz", 1317 }, 1318 }, 1319 }, 1320 1321 Diff: &terraform.InstanceDiff{ 1322 Attributes: map[string]*terraform.ResourceAttrDiff{ 1323 "config_vars.#": &terraform.ResourceAttrDiff{ 1324 Old: "0", 1325 New: "1", 1326 }, 1327 1328 "config_vars.bar": &terraform.ResourceAttrDiff{ 1329 Old: "", 1330 New: "baz", 1331 }, 1332 }, 1333 }, 1334 1335 Err: false, 1336 }, 1337 1338 // #33 1339 { 1340 Schema: map[string]*Schema{ 1341 "config_vars": &Schema{ 1342 Type: TypeMap, 1343 }, 1344 }, 1345 1346 State: &terraform.InstanceState{ 1347 Attributes: map[string]string{ 1348 "config_vars.foo": "bar", 1349 }, 1350 }, 1351 1352 Config: map[string]interface{}{ 1353 "config_vars": []interface{}{ 1354 map[string]interface{}{ 1355 "bar": "baz", 1356 }, 1357 }, 1358 }, 1359 1360 Diff: &terraform.InstanceDiff{ 1361 Attributes: map[string]*terraform.ResourceAttrDiff{ 1362 "config_vars.foo": &terraform.ResourceAttrDiff{ 1363 Old: "bar", 1364 NewRemoved: true, 1365 }, 1366 "config_vars.bar": &terraform.ResourceAttrDiff{ 1367 Old: "", 1368 New: "baz", 1369 }, 1370 }, 1371 }, 1372 1373 Err: false, 1374 }, 1375 1376 // #34 1377 { 1378 Schema: map[string]*Schema{ 1379 "vars": &Schema{ 1380 Type: TypeMap, 1381 Optional: true, 1382 Computed: true, 1383 }, 1384 }, 1385 1386 State: &terraform.InstanceState{ 1387 Attributes: map[string]string{ 1388 "vars.foo": "bar", 1389 }, 1390 }, 1391 1392 Config: map[string]interface{}{ 1393 "vars": []interface{}{ 1394 map[string]interface{}{ 1395 "bar": "baz", 1396 }, 1397 }, 1398 }, 1399 1400 Diff: &terraform.InstanceDiff{ 1401 Attributes: map[string]*terraform.ResourceAttrDiff{ 1402 "vars.foo": &terraform.ResourceAttrDiff{ 1403 Old: "bar", 1404 New: "", 1405 NewRemoved: true, 1406 }, 1407 "vars.bar": &terraform.ResourceAttrDiff{ 1408 Old: "", 1409 New: "baz", 1410 }, 1411 }, 1412 }, 1413 1414 Err: false, 1415 }, 1416 1417 // #35 1418 { 1419 Schema: map[string]*Schema{ 1420 "vars": &Schema{ 1421 Type: TypeMap, 1422 Computed: true, 1423 }, 1424 }, 1425 1426 State: &terraform.InstanceState{ 1427 Attributes: map[string]string{ 1428 "vars.foo": "bar", 1429 }, 1430 }, 1431 1432 Config: nil, 1433 1434 Diff: nil, 1435 1436 Err: false, 1437 }, 1438 1439 // #36 1440 { 1441 Schema: map[string]*Schema{ 1442 "config_vars": &Schema{ 1443 Type: TypeList, 1444 Elem: &Schema{Type: TypeMap}, 1445 }, 1446 }, 1447 1448 State: &terraform.InstanceState{ 1449 Attributes: map[string]string{ 1450 "config_vars.#": "1", 1451 "config_vars.0.foo": "bar", 1452 }, 1453 }, 1454 1455 Config: map[string]interface{}{ 1456 "config_vars": []interface{}{ 1457 map[string]interface{}{ 1458 "bar": "baz", 1459 }, 1460 }, 1461 }, 1462 1463 Diff: &terraform.InstanceDiff{ 1464 Attributes: map[string]*terraform.ResourceAttrDiff{ 1465 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1466 Old: "bar", 1467 NewRemoved: true, 1468 }, 1469 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1470 Old: "", 1471 New: "baz", 1472 }, 1473 }, 1474 }, 1475 1476 Err: false, 1477 }, 1478 1479 // #37 1480 { 1481 Schema: map[string]*Schema{ 1482 "config_vars": &Schema{ 1483 Type: TypeList, 1484 Elem: &Schema{Type: TypeMap}, 1485 }, 1486 }, 1487 1488 State: &terraform.InstanceState{ 1489 Attributes: map[string]string{ 1490 "config_vars.#": "1", 1491 "config_vars.0.foo": "bar", 1492 "config_vars.0.bar": "baz", 1493 }, 1494 }, 1495 1496 Config: map[string]interface{}{}, 1497 1498 Diff: &terraform.InstanceDiff{ 1499 Attributes: map[string]*terraform.ResourceAttrDiff{ 1500 "config_vars.#": &terraform.ResourceAttrDiff{ 1501 Old: "1", 1502 New: "0", 1503 }, 1504 "config_vars.0.#": &terraform.ResourceAttrDiff{ 1505 Old: "2", 1506 New: "0", 1507 }, 1508 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1509 Old: "bar", 1510 NewRemoved: true, 1511 }, 1512 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1513 Old: "baz", 1514 NewRemoved: true, 1515 }, 1516 }, 1517 }, 1518 1519 Err: false, 1520 }, 1521 1522 /* 1523 * ForceNews 1524 */ 1525 1526 // #38 1527 { 1528 Schema: map[string]*Schema{ 1529 "availability_zone": &Schema{ 1530 Type: TypeString, 1531 Optional: true, 1532 ForceNew: true, 1533 }, 1534 1535 "address": &Schema{ 1536 Type: TypeString, 1537 Optional: true, 1538 Computed: true, 1539 }, 1540 }, 1541 1542 State: &terraform.InstanceState{ 1543 Attributes: map[string]string{ 1544 "availability_zone": "bar", 1545 "address": "foo", 1546 }, 1547 }, 1548 1549 Config: map[string]interface{}{ 1550 "availability_zone": "foo", 1551 }, 1552 1553 Diff: &terraform.InstanceDiff{ 1554 Attributes: map[string]*terraform.ResourceAttrDiff{ 1555 "availability_zone": &terraform.ResourceAttrDiff{ 1556 Old: "bar", 1557 New: "foo", 1558 RequiresNew: true, 1559 }, 1560 1561 "address": &terraform.ResourceAttrDiff{ 1562 Old: "foo", 1563 New: "", 1564 NewComputed: true, 1565 }, 1566 }, 1567 }, 1568 1569 Err: false, 1570 }, 1571 1572 // #39 Set 1573 { 1574 Schema: map[string]*Schema{ 1575 "availability_zone": &Schema{ 1576 Type: TypeString, 1577 Optional: true, 1578 ForceNew: true, 1579 }, 1580 1581 "ports": &Schema{ 1582 Type: TypeSet, 1583 Optional: true, 1584 Computed: true, 1585 Elem: &Schema{Type: TypeInt}, 1586 Set: func(a interface{}) int { 1587 return a.(int) 1588 }, 1589 }, 1590 }, 1591 1592 State: &terraform.InstanceState{ 1593 Attributes: map[string]string{ 1594 "availability_zone": "bar", 1595 "ports.#": "1", 1596 "ports.80": "80", 1597 }, 1598 }, 1599 1600 Config: map[string]interface{}{ 1601 "availability_zone": "foo", 1602 }, 1603 1604 Diff: &terraform.InstanceDiff{ 1605 Attributes: map[string]*terraform.ResourceAttrDiff{ 1606 "availability_zone": &terraform.ResourceAttrDiff{ 1607 Old: "bar", 1608 New: "foo", 1609 RequiresNew: true, 1610 }, 1611 1612 "ports.#": &terraform.ResourceAttrDiff{ 1613 Old: "1", 1614 New: "", 1615 NewComputed: true, 1616 }, 1617 }, 1618 }, 1619 1620 Err: false, 1621 }, 1622 1623 // #40 Set 1624 { 1625 Schema: map[string]*Schema{ 1626 "instances": &Schema{ 1627 Type: TypeSet, 1628 Elem: &Schema{Type: TypeString}, 1629 Optional: true, 1630 Computed: true, 1631 Set: func(v interface{}) int { 1632 return len(v.(string)) 1633 }, 1634 }, 1635 }, 1636 1637 State: &terraform.InstanceState{ 1638 Attributes: map[string]string{ 1639 "instances.#": "0", 1640 }, 1641 }, 1642 1643 Config: map[string]interface{}{ 1644 "instances": []interface{}{"${var.foo}"}, 1645 }, 1646 1647 ConfigVariables: map[string]string{ 1648 "var.foo": config.UnknownVariableValue, 1649 }, 1650 1651 Diff: &terraform.InstanceDiff{ 1652 Attributes: map[string]*terraform.ResourceAttrDiff{ 1653 "instances.#": &terraform.ResourceAttrDiff{ 1654 NewComputed: true, 1655 }, 1656 }, 1657 }, 1658 1659 Err: false, 1660 }, 1661 1662 // #41 Set 1663 { 1664 Schema: map[string]*Schema{ 1665 "route": &Schema{ 1666 Type: TypeSet, 1667 Optional: true, 1668 Elem: &Resource{ 1669 Schema: map[string]*Schema{ 1670 "index": &Schema{ 1671 Type: TypeInt, 1672 Required: true, 1673 }, 1674 1675 "gateway": &Schema{ 1676 Type: TypeString, 1677 Optional: true, 1678 }, 1679 }, 1680 }, 1681 Set: func(v interface{}) int { 1682 m := v.(map[string]interface{}) 1683 return m["index"].(int) 1684 }, 1685 }, 1686 }, 1687 1688 State: nil, 1689 1690 Config: map[string]interface{}{ 1691 "route": []map[string]interface{}{ 1692 map[string]interface{}{ 1693 "index": "1", 1694 "gateway": "${var.foo}", 1695 }, 1696 }, 1697 }, 1698 1699 ConfigVariables: map[string]string{ 1700 "var.foo": config.UnknownVariableValue, 1701 }, 1702 1703 Diff: &terraform.InstanceDiff{ 1704 Attributes: map[string]*terraform.ResourceAttrDiff{ 1705 "route.#": &terraform.ResourceAttrDiff{ 1706 Old: "0", 1707 New: "1", 1708 }, 1709 "route.~1.index": &terraform.ResourceAttrDiff{ 1710 Old: "", 1711 New: "1", 1712 }, 1713 "route.~1.gateway": &terraform.ResourceAttrDiff{ 1714 Old: "", 1715 New: "${var.foo}", 1716 }, 1717 }, 1718 }, 1719 1720 Err: false, 1721 }, 1722 1723 // #42 Set 1724 { 1725 Schema: map[string]*Schema{ 1726 "route": &Schema{ 1727 Type: TypeSet, 1728 Optional: true, 1729 Elem: &Resource{ 1730 Schema: map[string]*Schema{ 1731 "index": &Schema{ 1732 Type: TypeInt, 1733 Required: true, 1734 }, 1735 1736 "gateway": &Schema{ 1737 Type: TypeSet, 1738 Optional: true, 1739 Elem: &Schema{Type: TypeInt}, 1740 Set: func(a interface{}) int { 1741 return a.(int) 1742 }, 1743 }, 1744 }, 1745 }, 1746 Set: func(v interface{}) int { 1747 m := v.(map[string]interface{}) 1748 return m["index"].(int) 1749 }, 1750 }, 1751 }, 1752 1753 State: nil, 1754 1755 Config: map[string]interface{}{ 1756 "route": []map[string]interface{}{ 1757 map[string]interface{}{ 1758 "index": "1", 1759 "gateway": []interface{}{ 1760 "${var.foo}", 1761 }, 1762 }, 1763 }, 1764 }, 1765 1766 ConfigVariables: map[string]string{ 1767 "var.foo": config.UnknownVariableValue, 1768 }, 1769 1770 Diff: &terraform.InstanceDiff{ 1771 Attributes: map[string]*terraform.ResourceAttrDiff{ 1772 "route.#": &terraform.ResourceAttrDiff{ 1773 Old: "0", 1774 New: "1", 1775 }, 1776 "route.~1.index": &terraform.ResourceAttrDiff{ 1777 Old: "", 1778 New: "1", 1779 }, 1780 "route.~1.gateway.#": &terraform.ResourceAttrDiff{ 1781 NewComputed: true, 1782 }, 1783 }, 1784 }, 1785 1786 Err: false, 1787 }, 1788 1789 // #43 - Computed maps 1790 { 1791 Schema: map[string]*Schema{ 1792 "vars": &Schema{ 1793 Type: TypeMap, 1794 Computed: true, 1795 }, 1796 }, 1797 1798 State: nil, 1799 1800 Config: nil, 1801 1802 Diff: &terraform.InstanceDiff{ 1803 Attributes: map[string]*terraform.ResourceAttrDiff{ 1804 "vars.#": &terraform.ResourceAttrDiff{ 1805 Old: "", 1806 NewComputed: true, 1807 }, 1808 }, 1809 }, 1810 1811 Err: false, 1812 }, 1813 1814 // #44 - Computed maps 1815 { 1816 Schema: map[string]*Schema{ 1817 "vars": &Schema{ 1818 Type: TypeMap, 1819 Computed: true, 1820 }, 1821 }, 1822 1823 State: &terraform.InstanceState{ 1824 Attributes: map[string]string{ 1825 "vars.#": "0", 1826 }, 1827 }, 1828 1829 Config: map[string]interface{}{ 1830 "vars": map[string]interface{}{ 1831 "bar": "${var.foo}", 1832 }, 1833 }, 1834 1835 ConfigVariables: map[string]string{ 1836 "var.foo": config.UnknownVariableValue, 1837 }, 1838 1839 Diff: &terraform.InstanceDiff{ 1840 Attributes: map[string]*terraform.ResourceAttrDiff{ 1841 "vars.#": &terraform.ResourceAttrDiff{ 1842 Old: "", 1843 NewComputed: true, 1844 }, 1845 }, 1846 }, 1847 1848 Err: false, 1849 }, 1850 1851 // #45 - Empty 1852 { 1853 Schema: map[string]*Schema{}, 1854 1855 State: &terraform.InstanceState{}, 1856 1857 Config: map[string]interface{}{}, 1858 1859 Diff: nil, 1860 1861 Err: false, 1862 }, 1863 1864 // #46 - Float 1865 { 1866 Schema: map[string]*Schema{ 1867 "some_threshold": &Schema{ 1868 Type: TypeFloat, 1869 }, 1870 }, 1871 1872 State: &terraform.InstanceState{ 1873 Attributes: map[string]string{ 1874 "some_threshold": "567.8", 1875 }, 1876 }, 1877 1878 Config: map[string]interface{}{ 1879 "some_threshold": 12.34, 1880 }, 1881 1882 Diff: &terraform.InstanceDiff{ 1883 Attributes: map[string]*terraform.ResourceAttrDiff{ 1884 "some_threshold": &terraform.ResourceAttrDiff{ 1885 Old: "567.8", 1886 New: "12.34", 1887 }, 1888 }, 1889 }, 1890 1891 Err: false, 1892 }, 1893 1894 // #47 - https://github.com/hashicorp/terraform/issues/824 1895 { 1896 Schema: map[string]*Schema{ 1897 "block_device": &Schema{ 1898 Type: TypeSet, 1899 Optional: true, 1900 Computed: true, 1901 Elem: &Resource{ 1902 Schema: map[string]*Schema{ 1903 "device_name": &Schema{ 1904 Type: TypeString, 1905 Required: true, 1906 }, 1907 "delete_on_termination": &Schema{ 1908 Type: TypeBool, 1909 Optional: true, 1910 Default: true, 1911 }, 1912 }, 1913 }, 1914 Set: func(v interface{}) int { 1915 var buf bytes.Buffer 1916 m := v.(map[string]interface{}) 1917 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 1918 buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) 1919 return hashcode.String(buf.String()) 1920 }, 1921 }, 1922 }, 1923 1924 State: &terraform.InstanceState{ 1925 Attributes: map[string]string{ 1926 "block_device.#": "2", 1927 "block_device.616397234.delete_on_termination": "true", 1928 "block_device.616397234.device_name": "/dev/sda1", 1929 "block_device.2801811477.delete_on_termination": "true", 1930 "block_device.2801811477.device_name": "/dev/sdx", 1931 }, 1932 }, 1933 1934 Config: map[string]interface{}{ 1935 "block_device": []map[string]interface{}{ 1936 map[string]interface{}{ 1937 "device_name": "/dev/sda1", 1938 }, 1939 map[string]interface{}{ 1940 "device_name": "/dev/sdx", 1941 }, 1942 }, 1943 }, 1944 Diff: nil, 1945 Err: false, 1946 }, 1947 1948 // #48 - Zero value in state shouldn't result in diff 1949 { 1950 Schema: map[string]*Schema{ 1951 "port": &Schema{ 1952 Type: TypeBool, 1953 Optional: true, 1954 ForceNew: true, 1955 }, 1956 }, 1957 1958 State: &terraform.InstanceState{ 1959 Attributes: map[string]string{ 1960 "port": "false", 1961 }, 1962 }, 1963 1964 Config: map[string]interface{}{}, 1965 1966 Diff: nil, 1967 1968 Err: false, 1969 }, 1970 1971 // #49 Set - Same as #48 but for sets 1972 { 1973 Schema: map[string]*Schema{ 1974 "route": &Schema{ 1975 Type: TypeSet, 1976 Optional: true, 1977 Elem: &Resource{ 1978 Schema: map[string]*Schema{ 1979 "index": &Schema{ 1980 Type: TypeInt, 1981 Required: true, 1982 }, 1983 1984 "gateway": &Schema{ 1985 Type: TypeSet, 1986 Optional: true, 1987 Elem: &Schema{Type: TypeInt}, 1988 Set: func(a interface{}) int { 1989 return a.(int) 1990 }, 1991 }, 1992 }, 1993 }, 1994 Set: func(v interface{}) int { 1995 m := v.(map[string]interface{}) 1996 return m["index"].(int) 1997 }, 1998 }, 1999 }, 2000 2001 State: &terraform.InstanceState{ 2002 Attributes: map[string]string{ 2003 "route.#": "0", 2004 }, 2005 }, 2006 2007 Config: map[string]interface{}{}, 2008 2009 Diff: nil, 2010 2011 Err: false, 2012 }, 2013 2014 // #50 - A set computed element shouldn't cause a diff 2015 { 2016 Schema: map[string]*Schema{ 2017 "active": &Schema{ 2018 Type: TypeBool, 2019 Computed: true, 2020 ForceNew: true, 2021 }, 2022 }, 2023 2024 State: &terraform.InstanceState{ 2025 Attributes: map[string]string{ 2026 "active": "true", 2027 }, 2028 }, 2029 2030 Config: map[string]interface{}{}, 2031 2032 Diff: nil, 2033 2034 Err: false, 2035 }, 2036 2037 // #51 - An empty set should show up in the diff 2038 { 2039 Schema: map[string]*Schema{ 2040 "instances": &Schema{ 2041 Type: TypeSet, 2042 Elem: &Schema{Type: TypeString}, 2043 Optional: true, 2044 ForceNew: true, 2045 Set: func(v interface{}) int { 2046 return len(v.(string)) 2047 }, 2048 }, 2049 }, 2050 2051 State: &terraform.InstanceState{ 2052 Attributes: map[string]string{ 2053 "instances.#": "1", 2054 "instances.3": "foo", 2055 }, 2056 }, 2057 2058 Config: map[string]interface{}{}, 2059 2060 Diff: &terraform.InstanceDiff{ 2061 Attributes: map[string]*terraform.ResourceAttrDiff{ 2062 "instances.#": &terraform.ResourceAttrDiff{ 2063 Old: "1", 2064 New: "0", 2065 RequiresNew: true, 2066 }, 2067 }, 2068 }, 2069 2070 Err: false, 2071 }, 2072 2073 // #52 - Map with empty value 2074 { 2075 Schema: map[string]*Schema{ 2076 "vars": &Schema{ 2077 Type: TypeMap, 2078 }, 2079 }, 2080 2081 State: nil, 2082 2083 Config: map[string]interface{}{ 2084 "vars": map[string]interface{}{ 2085 "foo": "", 2086 }, 2087 }, 2088 2089 Diff: &terraform.InstanceDiff{ 2090 Attributes: map[string]*terraform.ResourceAttrDiff{ 2091 "vars.#": &terraform.ResourceAttrDiff{ 2092 Old: "0", 2093 New: "1", 2094 }, 2095 "vars.foo": &terraform.ResourceAttrDiff{ 2096 Old: "", 2097 New: "", 2098 }, 2099 }, 2100 }, 2101 2102 Err: false, 2103 }, 2104 2105 // #53 - Unset bool, not in state 2106 { 2107 Schema: map[string]*Schema{ 2108 "force": &Schema{ 2109 Type: TypeBool, 2110 Optional: true, 2111 ForceNew: true, 2112 }, 2113 }, 2114 2115 State: nil, 2116 2117 Config: map[string]interface{}{}, 2118 2119 Diff: nil, 2120 2121 Err: false, 2122 }, 2123 2124 // #54 - Unset set, not in state 2125 { 2126 Schema: map[string]*Schema{ 2127 "metadata_keys": &Schema{ 2128 Type: TypeSet, 2129 Optional: true, 2130 ForceNew: true, 2131 Elem: &Schema{Type: TypeInt}, 2132 Set: func(interface{}) int { return 0 }, 2133 }, 2134 }, 2135 2136 State: nil, 2137 2138 Config: map[string]interface{}{}, 2139 2140 Diff: nil, 2141 2142 Err: false, 2143 }, 2144 2145 // #55 - Unset list in state, should not show up computed 2146 { 2147 Schema: map[string]*Schema{ 2148 "metadata_keys": &Schema{ 2149 Type: TypeList, 2150 Optional: true, 2151 Computed: true, 2152 ForceNew: true, 2153 Elem: &Schema{Type: TypeInt}, 2154 }, 2155 }, 2156 2157 State: &terraform.InstanceState{ 2158 Attributes: map[string]string{ 2159 "metadata_keys.#": "0", 2160 }, 2161 }, 2162 2163 Config: map[string]interface{}{}, 2164 2165 Diff: nil, 2166 2167 Err: false, 2168 }, 2169 2170 // #56 - Set element computed substring 2171 { 2172 Schema: map[string]*Schema{ 2173 "ports": &Schema{ 2174 Type: TypeSet, 2175 Required: true, 2176 Elem: &Schema{Type: TypeInt}, 2177 Set: func(a interface{}) int { 2178 return a.(int) 2179 }, 2180 }, 2181 }, 2182 2183 State: nil, 2184 2185 Config: map[string]interface{}{ 2186 "ports": []interface{}{1, "${var.foo}32"}, 2187 }, 2188 2189 ConfigVariables: map[string]string{ 2190 "var.foo": config.UnknownVariableValue, 2191 }, 2192 2193 Diff: &terraform.InstanceDiff{ 2194 Attributes: map[string]*terraform.ResourceAttrDiff{ 2195 "ports.#": &terraform.ResourceAttrDiff{ 2196 Old: "", 2197 New: "", 2198 NewComputed: true, 2199 }, 2200 }, 2201 }, 2202 2203 Err: false, 2204 }, 2205 2206 // #57 - Computed map without config that's known to be empty does not 2207 // generate diff 2208 { 2209 Schema: map[string]*Schema{ 2210 "tags": &Schema{ 2211 Type: TypeMap, 2212 Computed: true, 2213 }, 2214 }, 2215 2216 Config: nil, 2217 2218 State: &terraform.InstanceState{ 2219 Attributes: map[string]string{ 2220 "tags.#": "0", 2221 }, 2222 }, 2223 2224 Diff: nil, 2225 2226 Err: false, 2227 }, 2228 2229 // #58 Set with hyphen keys 2230 { 2231 Schema: map[string]*Schema{ 2232 "route": &Schema{ 2233 Type: TypeSet, 2234 Optional: true, 2235 Elem: &Resource{ 2236 Schema: map[string]*Schema{ 2237 "index": &Schema{ 2238 Type: TypeInt, 2239 Required: true, 2240 }, 2241 2242 "gateway-name": &Schema{ 2243 Type: TypeString, 2244 Optional: true, 2245 }, 2246 }, 2247 }, 2248 Set: func(v interface{}) int { 2249 m := v.(map[string]interface{}) 2250 return m["index"].(int) 2251 }, 2252 }, 2253 }, 2254 2255 State: nil, 2256 2257 Config: map[string]interface{}{ 2258 "route": []map[string]interface{}{ 2259 map[string]interface{}{ 2260 "index": "1", 2261 "gateway-name": "hello", 2262 }, 2263 }, 2264 }, 2265 2266 Diff: &terraform.InstanceDiff{ 2267 Attributes: map[string]*terraform.ResourceAttrDiff{ 2268 "route.#": &terraform.ResourceAttrDiff{ 2269 Old: "0", 2270 New: "1", 2271 }, 2272 "route.1.index": &terraform.ResourceAttrDiff{ 2273 Old: "", 2274 New: "1", 2275 }, 2276 "route.1.gateway-name": &terraform.ResourceAttrDiff{ 2277 Old: "", 2278 New: "hello", 2279 }, 2280 }, 2281 }, 2282 2283 Err: false, 2284 }, 2285 2286 // #59: StateFunc in nested set (#1759) 2287 { 2288 Schema: map[string]*Schema{ 2289 "service_account": &Schema{ 2290 Type: TypeList, 2291 Optional: true, 2292 ForceNew: true, 2293 Elem: &Resource{ 2294 Schema: map[string]*Schema{ 2295 "scopes": &Schema{ 2296 Type: TypeSet, 2297 Required: true, 2298 ForceNew: true, 2299 Elem: &Schema{ 2300 Type: TypeString, 2301 StateFunc: func(v interface{}) string { 2302 return v.(string) + "!" 2303 }, 2304 }, 2305 Set: func(v interface{}) int { 2306 i, err := strconv.Atoi(v.(string)) 2307 if err != nil { 2308 t.Fatalf("err: %s", err) 2309 } 2310 return i 2311 }, 2312 }, 2313 }, 2314 }, 2315 }, 2316 }, 2317 2318 State: nil, 2319 2320 Config: map[string]interface{}{ 2321 "service_account": []map[string]interface{}{ 2322 { 2323 "scopes": []interface{}{"123"}, 2324 }, 2325 }, 2326 }, 2327 2328 Diff: &terraform.InstanceDiff{ 2329 Attributes: map[string]*terraform.ResourceAttrDiff{ 2330 "service_account.#": &terraform.ResourceAttrDiff{ 2331 Old: "0", 2332 New: "1", 2333 RequiresNew: true, 2334 }, 2335 "service_account.0.scopes.#": &terraform.ResourceAttrDiff{ 2336 Old: "0", 2337 New: "1", 2338 RequiresNew: true, 2339 }, 2340 "service_account.0.scopes.123": &terraform.ResourceAttrDiff{ 2341 Old: "", 2342 New: "123!", 2343 NewExtra: "123", 2344 RequiresNew: true, 2345 }, 2346 }, 2347 }, 2348 2349 Err: false, 2350 }, 2351 } 2352 2353 for i, tc := range cases { 2354 c, err := config.NewRawConfig(tc.Config) 2355 if err != nil { 2356 t.Fatalf("#%d err: %s", i, err) 2357 } 2358 2359 if len(tc.ConfigVariables) > 0 { 2360 vars := make(map[string]ast.Variable) 2361 for k, v := range tc.ConfigVariables { 2362 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 2363 } 2364 2365 if err := c.Interpolate(vars); err != nil { 2366 t.Fatalf("#%d err: %s", i, err) 2367 } 2368 } 2369 2370 d, err := schemaMap(tc.Schema).Diff( 2371 tc.State, terraform.NewResourceConfig(c)) 2372 if (err != nil) != tc.Err { 2373 t.Fatalf("#%d err: %s", i, err) 2374 } 2375 2376 if !reflect.DeepEqual(tc.Diff, d) { 2377 t.Fatalf("#%d:\n\nexpected: %#v\n\ngot:\n\n%#v", i, tc.Diff, d) 2378 } 2379 } 2380 } 2381 2382 func TestSchemaMap_Input(t *testing.T) { 2383 cases := map[string]struct { 2384 Schema map[string]*Schema 2385 Config map[string]interface{} 2386 Input map[string]string 2387 Result map[string]interface{} 2388 Err bool 2389 }{ 2390 /* 2391 * String decode 2392 */ 2393 2394 "uses input on optional field with no config": { 2395 Schema: map[string]*Schema{ 2396 "availability_zone": &Schema{ 2397 Type: TypeString, 2398 Optional: true, 2399 }, 2400 }, 2401 2402 Input: map[string]string{ 2403 "availability_zone": "foo", 2404 }, 2405 2406 Result: map[string]interface{}{ 2407 "availability_zone": "foo", 2408 }, 2409 2410 Err: false, 2411 }, 2412 2413 "input ignored when config has a value": { 2414 Schema: map[string]*Schema{ 2415 "availability_zone": &Schema{ 2416 Type: TypeString, 2417 Optional: true, 2418 }, 2419 }, 2420 2421 Config: map[string]interface{}{ 2422 "availability_zone": "bar", 2423 }, 2424 2425 Input: map[string]string{ 2426 "availability_zone": "foo", 2427 }, 2428 2429 Result: map[string]interface{}{}, 2430 2431 Err: false, 2432 }, 2433 2434 "input ignored when schema has a default": { 2435 Schema: map[string]*Schema{ 2436 "availability_zone": &Schema{ 2437 Type: TypeString, 2438 Default: "foo", 2439 Optional: true, 2440 }, 2441 }, 2442 2443 Input: map[string]string{ 2444 "availability_zone": "bar", 2445 }, 2446 2447 Result: map[string]interface{}{}, 2448 2449 Err: false, 2450 }, 2451 2452 "input ignored when default function returns a value": { 2453 Schema: map[string]*Schema{ 2454 "availability_zone": &Schema{ 2455 Type: TypeString, 2456 DefaultFunc: func() (interface{}, error) { 2457 return "foo", nil 2458 }, 2459 Optional: true, 2460 }, 2461 }, 2462 2463 Input: map[string]string{ 2464 "availability_zone": "bar", 2465 }, 2466 2467 Result: map[string]interface{}{}, 2468 2469 Err: false, 2470 }, 2471 2472 "input ignored when default function returns an empty string": { 2473 Schema: map[string]*Schema{ 2474 "availability_zone": &Schema{ 2475 Type: TypeString, 2476 Default: "", 2477 Optional: true, 2478 }, 2479 }, 2480 2481 Input: map[string]string{ 2482 "availability_zone": "bar", 2483 }, 2484 2485 Result: map[string]interface{}{}, 2486 2487 Err: false, 2488 }, 2489 2490 "input used when default function returns nil": { 2491 Schema: map[string]*Schema{ 2492 "availability_zone": &Schema{ 2493 Type: TypeString, 2494 DefaultFunc: func() (interface{}, error) { 2495 return nil, nil 2496 }, 2497 Optional: true, 2498 }, 2499 }, 2500 2501 Input: map[string]string{ 2502 "availability_zone": "bar", 2503 }, 2504 2505 Result: map[string]interface{}{ 2506 "availability_zone": "bar", 2507 }, 2508 2509 Err: false, 2510 }, 2511 } 2512 2513 for i, tc := range cases { 2514 if tc.Config == nil { 2515 tc.Config = make(map[string]interface{}) 2516 } 2517 2518 c, err := config.NewRawConfig(tc.Config) 2519 if err != nil { 2520 t.Fatalf("err: %s", err) 2521 } 2522 2523 input := new(terraform.MockUIInput) 2524 input.InputReturnMap = tc.Input 2525 2526 rc := terraform.NewResourceConfig(c) 2527 rc.Config = make(map[string]interface{}) 2528 2529 actual, err := schemaMap(tc.Schema).Input(input, rc) 2530 if (err != nil) != tc.Err { 2531 t.Fatalf("#%v err: %s", i, err) 2532 } 2533 2534 if !reflect.DeepEqual(tc.Result, actual.Config) { 2535 t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result) 2536 } 2537 } 2538 } 2539 2540 func TestSchemaMap_InternalValidate(t *testing.T) { 2541 cases := []struct { 2542 In map[string]*Schema 2543 Err bool 2544 }{ 2545 { 2546 nil, 2547 false, 2548 }, 2549 2550 // No optional and no required 2551 { 2552 map[string]*Schema{ 2553 "foo": &Schema{ 2554 Type: TypeInt, 2555 Optional: true, 2556 Required: true, 2557 }, 2558 }, 2559 true, 2560 }, 2561 2562 // No optional and no required 2563 { 2564 map[string]*Schema{ 2565 "foo": &Schema{ 2566 Type: TypeInt, 2567 }, 2568 }, 2569 true, 2570 }, 2571 2572 // Missing Type 2573 { 2574 map[string]*Schema{ 2575 "foo": &Schema{ 2576 Required: true, 2577 }, 2578 }, 2579 true, 2580 }, 2581 2582 // Required but computed 2583 { 2584 map[string]*Schema{ 2585 "foo": &Schema{ 2586 Type: TypeInt, 2587 Required: true, 2588 Computed: true, 2589 }, 2590 }, 2591 true, 2592 }, 2593 2594 // Looks good 2595 { 2596 map[string]*Schema{ 2597 "foo": &Schema{ 2598 Type: TypeString, 2599 Required: true, 2600 }, 2601 }, 2602 false, 2603 }, 2604 2605 // Computed but has default 2606 { 2607 map[string]*Schema{ 2608 "foo": &Schema{ 2609 Type: TypeInt, 2610 Optional: true, 2611 Computed: true, 2612 Default: "foo", 2613 }, 2614 }, 2615 true, 2616 }, 2617 2618 // Required but has default 2619 { 2620 map[string]*Schema{ 2621 "foo": &Schema{ 2622 Type: TypeInt, 2623 Optional: true, 2624 Required: true, 2625 Default: "foo", 2626 }, 2627 }, 2628 true, 2629 }, 2630 2631 // List element not set 2632 { 2633 map[string]*Schema{ 2634 "foo": &Schema{ 2635 Type: TypeList, 2636 }, 2637 }, 2638 true, 2639 }, 2640 2641 // List default 2642 { 2643 map[string]*Schema{ 2644 "foo": &Schema{ 2645 Type: TypeList, 2646 Elem: &Schema{Type: TypeInt}, 2647 Default: "foo", 2648 }, 2649 }, 2650 true, 2651 }, 2652 2653 // List element computed 2654 { 2655 map[string]*Schema{ 2656 "foo": &Schema{ 2657 Type: TypeList, 2658 Optional: true, 2659 Elem: &Schema{ 2660 Type: TypeInt, 2661 Computed: true, 2662 }, 2663 }, 2664 }, 2665 true, 2666 }, 2667 2668 // List element with Set set 2669 { 2670 map[string]*Schema{ 2671 "foo": &Schema{ 2672 Type: TypeList, 2673 Elem: &Schema{Type: TypeInt}, 2674 Set: func(interface{}) int { return 0 }, 2675 Optional: true, 2676 }, 2677 }, 2678 true, 2679 }, 2680 2681 // Set element with no Set set 2682 { 2683 map[string]*Schema{ 2684 "foo": &Schema{ 2685 Type: TypeSet, 2686 Elem: &Schema{Type: TypeInt}, 2687 Optional: true, 2688 }, 2689 }, 2690 true, 2691 }, 2692 2693 // Required but computed 2694 { 2695 map[string]*Schema{ 2696 "foo": &Schema{ 2697 Type: TypeInt, 2698 Required: true, 2699 ComputedWhen: []string{"foo"}, 2700 }, 2701 }, 2702 true, 2703 }, 2704 2705 // Conflicting attributes cannot be required 2706 { 2707 map[string]*Schema{ 2708 "blacklist": &Schema{ 2709 Type: TypeBool, 2710 Required: true, 2711 }, 2712 "whitelist": &Schema{ 2713 Type: TypeBool, 2714 Optional: true, 2715 ConflictsWith: []string{"blacklist"}, 2716 }, 2717 }, 2718 true, 2719 }, 2720 2721 // Attribute with conflicts cannot be required 2722 { 2723 map[string]*Schema{ 2724 "whitelist": &Schema{ 2725 Type: TypeBool, 2726 Required: true, 2727 ConflictsWith: []string{"blacklist"}, 2728 }, 2729 }, 2730 true, 2731 }, 2732 2733 // ConflictsWith cannot be used w/ Computed 2734 { 2735 map[string]*Schema{ 2736 "blacklist": &Schema{ 2737 Type: TypeBool, 2738 Computed: true, 2739 }, 2740 "whitelist": &Schema{ 2741 Type: TypeBool, 2742 Optional: true, 2743 ConflictsWith: []string{"blacklist"}, 2744 }, 2745 }, 2746 true, 2747 }, 2748 2749 // ConflictsWith cannot be used w/ ComputedWhen 2750 { 2751 map[string]*Schema{ 2752 "blacklist": &Schema{ 2753 Type: TypeBool, 2754 ComputedWhen: []string{"foor"}, 2755 }, 2756 "whitelist": &Schema{ 2757 Type: TypeBool, 2758 Required: true, 2759 ConflictsWith: []string{"blacklist"}, 2760 }, 2761 }, 2762 true, 2763 }, 2764 2765 // Sub-resource invalid 2766 { 2767 map[string]*Schema{ 2768 "foo": &Schema{ 2769 Type: TypeList, 2770 Optional: true, 2771 Elem: &Resource{ 2772 Schema: map[string]*Schema{ 2773 "foo": new(Schema), 2774 }, 2775 }, 2776 }, 2777 }, 2778 true, 2779 }, 2780 2781 // Sub-resource valid 2782 { 2783 map[string]*Schema{ 2784 "foo": &Schema{ 2785 Type: TypeList, 2786 Optional: true, 2787 Elem: &Resource{ 2788 Schema: map[string]*Schema{ 2789 "foo": &Schema{ 2790 Type: TypeInt, 2791 Optional: true, 2792 }, 2793 }, 2794 }, 2795 }, 2796 }, 2797 false, 2798 }, 2799 } 2800 2801 for i, tc := range cases { 2802 err := schemaMap(tc.In).InternalValidate(schemaMap{}) 2803 if (err != nil) != tc.Err { 2804 if tc.Err { 2805 t.Fatalf("%d: Expected error did not occur:\n\n%#v", i, tc.In) 2806 } 2807 t.Fatalf("%d: Unexpected error occured:\n\n%#v", i, tc.In) 2808 } 2809 } 2810 2811 } 2812 2813 func TestSchemaMap_Validate(t *testing.T) { 2814 cases := map[string]struct { 2815 Schema map[string]*Schema 2816 Config map[string]interface{} 2817 Vars map[string]string 2818 Err bool 2819 Errors []error 2820 Warnings []string 2821 }{ 2822 "Good": { 2823 Schema: map[string]*Schema{ 2824 "availability_zone": &Schema{ 2825 Type: TypeString, 2826 Optional: true, 2827 Computed: true, 2828 ForceNew: true, 2829 }, 2830 }, 2831 2832 Config: map[string]interface{}{ 2833 "availability_zone": "foo", 2834 }, 2835 }, 2836 2837 "Good, because the var is not set and that error will come elsewhere": { 2838 Schema: map[string]*Schema{ 2839 "size": &Schema{ 2840 Type: TypeInt, 2841 Required: true, 2842 }, 2843 }, 2844 2845 Config: map[string]interface{}{ 2846 "size": "${var.foo}", 2847 }, 2848 2849 Vars: map[string]string{ 2850 "var.foo": config.UnknownVariableValue, 2851 }, 2852 }, 2853 2854 "Required field not set": { 2855 Schema: map[string]*Schema{ 2856 "availability_zone": &Schema{ 2857 Type: TypeString, 2858 Required: true, 2859 }, 2860 }, 2861 2862 Config: map[string]interface{}{}, 2863 2864 Err: true, 2865 }, 2866 2867 "Invalid basic type": { 2868 Schema: map[string]*Schema{ 2869 "port": &Schema{ 2870 Type: TypeInt, 2871 Required: true, 2872 }, 2873 }, 2874 2875 Config: map[string]interface{}{ 2876 "port": "I am invalid", 2877 }, 2878 2879 Err: true, 2880 }, 2881 2882 "Invalid complex type": { 2883 Schema: map[string]*Schema{ 2884 "user_data": &Schema{ 2885 Type: TypeString, 2886 Optional: true, 2887 }, 2888 }, 2889 2890 Config: map[string]interface{}{ 2891 "user_data": []interface{}{ 2892 map[string]interface{}{ 2893 "foo": "bar", 2894 }, 2895 }, 2896 }, 2897 2898 Err: true, 2899 }, 2900 2901 "Bad type, interpolated": { 2902 Schema: map[string]*Schema{ 2903 "size": &Schema{ 2904 Type: TypeInt, 2905 Required: true, 2906 }, 2907 }, 2908 2909 Config: map[string]interface{}{ 2910 "size": "${var.foo}", 2911 }, 2912 2913 Vars: map[string]string{ 2914 "var.foo": "nope", 2915 }, 2916 2917 Err: true, 2918 }, 2919 2920 "Required but has DefaultFunc": { 2921 Schema: map[string]*Schema{ 2922 "availability_zone": &Schema{ 2923 Type: TypeString, 2924 Required: true, 2925 DefaultFunc: func() (interface{}, error) { 2926 return "foo", nil 2927 }, 2928 }, 2929 }, 2930 2931 Config: nil, 2932 }, 2933 2934 "Required but has DefaultFunc return nil": { 2935 Schema: map[string]*Schema{ 2936 "availability_zone": &Schema{ 2937 Type: TypeString, 2938 Required: true, 2939 DefaultFunc: func() (interface{}, error) { 2940 return nil, nil 2941 }, 2942 }, 2943 }, 2944 2945 Config: nil, 2946 2947 Err: true, 2948 }, 2949 2950 "Optional sub-resource": { 2951 Schema: map[string]*Schema{ 2952 "ingress": &Schema{ 2953 Type: TypeList, 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 2967 Err: false, 2968 }, 2969 2970 "Not a list": { 2971 Schema: map[string]*Schema{ 2972 "ingress": &Schema{ 2973 Type: TypeList, 2974 Elem: &Resource{ 2975 Schema: map[string]*Schema{ 2976 "from": &Schema{ 2977 Type: TypeInt, 2978 Required: true, 2979 }, 2980 }, 2981 }, 2982 }, 2983 }, 2984 2985 Config: map[string]interface{}{ 2986 "ingress": "foo", 2987 }, 2988 2989 Err: true, 2990 }, 2991 2992 "Required sub-resource field": { 2993 Schema: map[string]*Schema{ 2994 "ingress": &Schema{ 2995 Type: TypeList, 2996 Elem: &Resource{ 2997 Schema: map[string]*Schema{ 2998 "from": &Schema{ 2999 Type: TypeInt, 3000 Required: true, 3001 }, 3002 }, 3003 }, 3004 }, 3005 }, 3006 3007 Config: map[string]interface{}{ 3008 "ingress": []interface{}{ 3009 map[string]interface{}{}, 3010 }, 3011 }, 3012 3013 Err: true, 3014 }, 3015 3016 "Good sub-resource": { 3017 Schema: map[string]*Schema{ 3018 "ingress": &Schema{ 3019 Type: TypeList, 3020 Optional: true, 3021 Elem: &Resource{ 3022 Schema: map[string]*Schema{ 3023 "from": &Schema{ 3024 Type: TypeInt, 3025 Required: true, 3026 }, 3027 }, 3028 }, 3029 }, 3030 }, 3031 3032 Config: map[string]interface{}{ 3033 "ingress": []interface{}{ 3034 map[string]interface{}{ 3035 "from": 80, 3036 }, 3037 }, 3038 }, 3039 3040 Err: false, 3041 }, 3042 3043 "Invalid/unknown field": { 3044 Schema: map[string]*Schema{ 3045 "availability_zone": &Schema{ 3046 Type: TypeString, 3047 Optional: true, 3048 Computed: true, 3049 ForceNew: true, 3050 }, 3051 }, 3052 3053 Config: map[string]interface{}{ 3054 "foo": "bar", 3055 }, 3056 3057 Err: true, 3058 }, 3059 3060 "Invalid/unknown field with computed value": { 3061 Schema: map[string]*Schema{ 3062 "availability_zone": &Schema{ 3063 Type: TypeString, 3064 Optional: true, 3065 Computed: true, 3066 ForceNew: true, 3067 }, 3068 }, 3069 3070 Config: map[string]interface{}{ 3071 "foo": "${var.foo}", 3072 }, 3073 3074 Vars: map[string]string{ 3075 "var.foo": config.UnknownVariableValue, 3076 }, 3077 3078 Err: true, 3079 }, 3080 3081 "Computed field set": { 3082 Schema: map[string]*Schema{ 3083 "availability_zone": &Schema{ 3084 Type: TypeString, 3085 Computed: true, 3086 }, 3087 }, 3088 3089 Config: map[string]interface{}{ 3090 "availability_zone": "bar", 3091 }, 3092 3093 Err: true, 3094 }, 3095 3096 "Not a set": { 3097 Schema: map[string]*Schema{ 3098 "ports": &Schema{ 3099 Type: TypeSet, 3100 Required: true, 3101 Elem: &Schema{Type: TypeInt}, 3102 Set: func(a interface{}) int { 3103 return a.(int) 3104 }, 3105 }, 3106 }, 3107 3108 Config: map[string]interface{}{ 3109 "ports": "foo", 3110 }, 3111 3112 Err: true, 3113 }, 3114 3115 "Maps": { 3116 Schema: map[string]*Schema{ 3117 "user_data": &Schema{ 3118 Type: TypeMap, 3119 Optional: true, 3120 }, 3121 }, 3122 3123 Config: map[string]interface{}{ 3124 "user_data": "foo", 3125 }, 3126 3127 Err: true, 3128 }, 3129 3130 "Good map: data surrounded by extra slice": { 3131 Schema: map[string]*Schema{ 3132 "user_data": &Schema{ 3133 Type: TypeMap, 3134 Optional: true, 3135 }, 3136 }, 3137 3138 Config: map[string]interface{}{ 3139 "user_data": []interface{}{ 3140 map[string]interface{}{ 3141 "foo": "bar", 3142 }, 3143 }, 3144 }, 3145 }, 3146 3147 "Good map": { 3148 Schema: map[string]*Schema{ 3149 "user_data": &Schema{ 3150 Type: TypeMap, 3151 Optional: true, 3152 }, 3153 }, 3154 3155 Config: map[string]interface{}{ 3156 "user_data": map[string]interface{}{ 3157 "foo": "bar", 3158 }, 3159 }, 3160 }, 3161 3162 "Bad map: just a slice": { 3163 Schema: map[string]*Schema{ 3164 "user_data": &Schema{ 3165 Type: TypeMap, 3166 Optional: true, 3167 }, 3168 }, 3169 3170 Config: map[string]interface{}{ 3171 "user_data": []interface{}{ 3172 "foo", 3173 }, 3174 }, 3175 3176 Err: true, 3177 }, 3178 3179 "Good set: config has slice with single interpolated value": { 3180 Schema: map[string]*Schema{ 3181 "security_groups": &Schema{ 3182 Type: TypeSet, 3183 Optional: true, 3184 Computed: true, 3185 ForceNew: true, 3186 Elem: &Schema{Type: TypeString}, 3187 Set: func(v interface{}) int { 3188 return len(v.(string)) 3189 }, 3190 }, 3191 }, 3192 3193 Config: map[string]interface{}{ 3194 "security_groups": []interface{}{"${var.foo}"}, 3195 }, 3196 3197 Err: false, 3198 }, 3199 3200 "Bad set: config has single interpolated value": { 3201 Schema: map[string]*Schema{ 3202 "security_groups": &Schema{ 3203 Type: TypeSet, 3204 Optional: true, 3205 Computed: true, 3206 ForceNew: true, 3207 Elem: &Schema{Type: TypeString}, 3208 }, 3209 }, 3210 3211 Config: map[string]interface{}{ 3212 "security_groups": "${var.foo}", 3213 }, 3214 3215 Err: true, 3216 }, 3217 3218 "Bad, subresource should not allow unknown elements": { 3219 Schema: map[string]*Schema{ 3220 "ingress": &Schema{ 3221 Type: TypeList, 3222 Optional: true, 3223 Elem: &Resource{ 3224 Schema: map[string]*Schema{ 3225 "port": &Schema{ 3226 Type: TypeInt, 3227 Required: true, 3228 }, 3229 }, 3230 }, 3231 }, 3232 }, 3233 3234 Config: map[string]interface{}{ 3235 "ingress": []interface{}{ 3236 map[string]interface{}{ 3237 "port": 80, 3238 "other": "yes", 3239 }, 3240 }, 3241 }, 3242 3243 Err: true, 3244 }, 3245 3246 "Bad, subresource should not allow invalid types": { 3247 Schema: map[string]*Schema{ 3248 "ingress": &Schema{ 3249 Type: TypeList, 3250 Optional: true, 3251 Elem: &Resource{ 3252 Schema: map[string]*Schema{ 3253 "port": &Schema{ 3254 Type: TypeInt, 3255 Required: true, 3256 }, 3257 }, 3258 }, 3259 }, 3260 }, 3261 3262 Config: map[string]interface{}{ 3263 "ingress": []interface{}{ 3264 map[string]interface{}{ 3265 "port": "bad", 3266 }, 3267 }, 3268 }, 3269 3270 Err: true, 3271 }, 3272 3273 "Deprecated attribute usage generates warning, but not error": { 3274 Schema: map[string]*Schema{ 3275 "old_news": &Schema{ 3276 Type: TypeString, 3277 Optional: true, 3278 Deprecated: "please use 'new_news' instead", 3279 }, 3280 }, 3281 3282 Config: map[string]interface{}{ 3283 "old_news": "extra extra!", 3284 }, 3285 3286 Err: false, 3287 3288 Warnings: []string{ 3289 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 3290 }, 3291 }, 3292 3293 "Deprecated generates no warnings if attr not used": { 3294 Schema: map[string]*Schema{ 3295 "old_news": &Schema{ 3296 Type: TypeString, 3297 Optional: true, 3298 Deprecated: "please use 'new_news' instead", 3299 }, 3300 }, 3301 3302 Err: false, 3303 3304 Warnings: nil, 3305 }, 3306 3307 "Removed attribute usage generates error": { 3308 Schema: map[string]*Schema{ 3309 "long_gone": &Schema{ 3310 Type: TypeString, 3311 Optional: true, 3312 Removed: "no longer supported by Cloud API", 3313 }, 3314 }, 3315 3316 Config: map[string]interface{}{ 3317 "long_gone": "still here!", 3318 }, 3319 3320 Err: true, 3321 Errors: []error{ 3322 fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), 3323 }, 3324 }, 3325 3326 "Removed generates no errors if attr not used": { 3327 Schema: map[string]*Schema{ 3328 "long_gone": &Schema{ 3329 Type: TypeString, 3330 Optional: true, 3331 Removed: "no longer supported by Cloud API", 3332 }, 3333 }, 3334 3335 Err: false, 3336 }, 3337 3338 "Conflicting attributes generate error": { 3339 Schema: map[string]*Schema{ 3340 "whitelist": &Schema{ 3341 Type: TypeString, 3342 Optional: true, 3343 }, 3344 "blacklist": &Schema{ 3345 Type: TypeString, 3346 Optional: true, 3347 ConflictsWith: []string{"whitelist"}, 3348 }, 3349 }, 3350 3351 Config: map[string]interface{}{ 3352 "whitelist": "white-val", 3353 "blacklist": "black-val", 3354 }, 3355 3356 Err: true, 3357 Errors: []error{ 3358 fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"), 3359 }, 3360 }, 3361 3362 "Required attribute & undefined conflicting optional are good": { 3363 Schema: map[string]*Schema{ 3364 "required_att": &Schema{ 3365 Type: TypeString, 3366 Required: true, 3367 }, 3368 "optional_att": &Schema{ 3369 Type: TypeString, 3370 Optional: true, 3371 ConflictsWith: []string{"required_att"}, 3372 }, 3373 }, 3374 3375 Config: map[string]interface{}{ 3376 "required_att": "required-val", 3377 }, 3378 3379 Err: false, 3380 }, 3381 3382 "Required conflicting attribute & defined optional generate error": { 3383 Schema: map[string]*Schema{ 3384 "required_att": &Schema{ 3385 Type: TypeString, 3386 Required: true, 3387 }, 3388 "optional_att": &Schema{ 3389 Type: TypeString, 3390 Optional: true, 3391 ConflictsWith: []string{"required_att"}, 3392 }, 3393 }, 3394 3395 Config: map[string]interface{}{ 3396 "required_att": "required-val", 3397 "optional_att": "optional-val", 3398 }, 3399 3400 Err: true, 3401 Errors: []error{ 3402 fmt.Errorf("\"optional_att\": conflicts with required_att (\"required-val\")"), 3403 }, 3404 }, 3405 } 3406 3407 for tn, tc := range cases { 3408 c, err := config.NewRawConfig(tc.Config) 3409 if err != nil { 3410 t.Fatalf("err: %s", err) 3411 } 3412 if tc.Vars != nil { 3413 vars := make(map[string]ast.Variable) 3414 for k, v := range tc.Vars { 3415 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 3416 } 3417 3418 if err := c.Interpolate(vars); err != nil { 3419 t.Fatalf("err: %s", err) 3420 } 3421 } 3422 3423 ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 3424 if (len(es) > 0) != tc.Err { 3425 if len(es) == 0 { 3426 t.Errorf("%q: no errors", tn) 3427 } 3428 3429 for _, e := range es { 3430 t.Errorf("%q: err: %s", tn, e) 3431 } 3432 3433 t.FailNow() 3434 } 3435 3436 if !reflect.DeepEqual(ws, tc.Warnings) { 3437 t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) 3438 } 3439 3440 if tc.Errors != nil { 3441 if !reflect.DeepEqual(es, tc.Errors) { 3442 t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) 3443 } 3444 } 3445 } 3446 }