github.com/acm1/terraform@v0.6.2-0.20150729164239-1f314444f45c/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": config.NewStringList([]string{"2", "5"}).String(), 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.NewStringList([]string{ 630 config.UnknownVariableValue, "5"}).String(), 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": config.NewStringList([]string{"2", "5"}).String(), 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.NewStringList([]string{ 956 config.UnknownVariableValue, "5"}).String(), 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 "ports.1": &terraform.ResourceAttrDiff{ 1051 Old: "1", 1052 New: "0", 1053 NewRemoved: true, 1054 }, 1055 "ports.2": &terraform.ResourceAttrDiff{ 1056 Old: "2", 1057 New: "0", 1058 NewRemoved: true, 1059 }, 1060 }, 1061 }, 1062 1063 Err: false, 1064 }, 1065 1066 // #27 1067 { 1068 Schema: map[string]*Schema{ 1069 "ports": &Schema{ 1070 Type: TypeSet, 1071 Optional: true, 1072 Computed: true, 1073 Elem: &Schema{Type: TypeInt}, 1074 Set: func(a interface{}) int { 1075 return a.(int) 1076 }, 1077 }, 1078 }, 1079 1080 State: &terraform.InstanceState{ 1081 Attributes: map[string]string{ 1082 "availability_zone": "bar", 1083 "ports.#": "1", 1084 "ports.80": "80", 1085 }, 1086 }, 1087 1088 Config: map[string]interface{}{}, 1089 1090 Diff: nil, 1091 1092 Err: false, 1093 }, 1094 1095 // #28 1096 { 1097 Schema: map[string]*Schema{ 1098 "ingress": &Schema{ 1099 Type: TypeSet, 1100 Required: true, 1101 Elem: &Resource{ 1102 Schema: map[string]*Schema{ 1103 "ports": &Schema{ 1104 Type: TypeList, 1105 Optional: true, 1106 Elem: &Schema{Type: TypeInt}, 1107 }, 1108 }, 1109 }, 1110 Set: func(v interface{}) int { 1111 m := v.(map[string]interface{}) 1112 ps := m["ports"].([]interface{}) 1113 result := 0 1114 for _, p := range ps { 1115 result += p.(int) 1116 } 1117 return result 1118 }, 1119 }, 1120 }, 1121 1122 State: &terraform.InstanceState{ 1123 Attributes: map[string]string{ 1124 "ingress.#": "2", 1125 "ingress.80.ports.#": "1", 1126 "ingress.80.ports.0": "80", 1127 "ingress.443.ports.#": "1", 1128 "ingress.443.ports.0": "443", 1129 }, 1130 }, 1131 1132 Config: map[string]interface{}{ 1133 "ingress": []map[string]interface{}{ 1134 map[string]interface{}{ 1135 "ports": []interface{}{443}, 1136 }, 1137 map[string]interface{}{ 1138 "ports": []interface{}{80}, 1139 }, 1140 }, 1141 }, 1142 1143 Diff: nil, 1144 1145 Err: false, 1146 }, 1147 1148 /* 1149 * List of structure decode 1150 */ 1151 1152 // #29 1153 { 1154 Schema: map[string]*Schema{ 1155 "ingress": &Schema{ 1156 Type: TypeList, 1157 Required: true, 1158 Elem: &Resource{ 1159 Schema: map[string]*Schema{ 1160 "from": &Schema{ 1161 Type: TypeInt, 1162 Required: true, 1163 }, 1164 }, 1165 }, 1166 }, 1167 }, 1168 1169 State: nil, 1170 1171 Config: map[string]interface{}{ 1172 "ingress": []interface{}{ 1173 map[string]interface{}{ 1174 "from": 8080, 1175 }, 1176 }, 1177 }, 1178 1179 Diff: &terraform.InstanceDiff{ 1180 Attributes: map[string]*terraform.ResourceAttrDiff{ 1181 "ingress.#": &terraform.ResourceAttrDiff{ 1182 Old: "0", 1183 New: "1", 1184 }, 1185 "ingress.0.from": &terraform.ResourceAttrDiff{ 1186 Old: "", 1187 New: "8080", 1188 }, 1189 }, 1190 }, 1191 1192 Err: false, 1193 }, 1194 1195 /* 1196 * ComputedWhen 1197 */ 1198 1199 // #30 1200 { 1201 Schema: map[string]*Schema{ 1202 "availability_zone": &Schema{ 1203 Type: TypeString, 1204 Computed: true, 1205 ComputedWhen: []string{"port"}, 1206 }, 1207 1208 "port": &Schema{ 1209 Type: TypeInt, 1210 Optional: true, 1211 }, 1212 }, 1213 1214 State: &terraform.InstanceState{ 1215 Attributes: map[string]string{ 1216 "availability_zone": "foo", 1217 "port": "80", 1218 }, 1219 }, 1220 1221 Config: map[string]interface{}{ 1222 "port": 80, 1223 }, 1224 1225 Diff: nil, 1226 1227 Err: false, 1228 }, 1229 1230 // #31 1231 { 1232 Schema: map[string]*Schema{ 1233 "availability_zone": &Schema{ 1234 Type: TypeString, 1235 Computed: true, 1236 ComputedWhen: []string{"port"}, 1237 }, 1238 1239 "port": &Schema{ 1240 Type: TypeInt, 1241 Optional: true, 1242 }, 1243 }, 1244 1245 State: &terraform.InstanceState{ 1246 Attributes: map[string]string{ 1247 "port": "80", 1248 }, 1249 }, 1250 1251 Config: map[string]interface{}{ 1252 "port": 80, 1253 }, 1254 1255 Diff: &terraform.InstanceDiff{ 1256 Attributes: map[string]*terraform.ResourceAttrDiff{ 1257 "availability_zone": &terraform.ResourceAttrDiff{ 1258 NewComputed: true, 1259 }, 1260 }, 1261 }, 1262 1263 Err: false, 1264 }, 1265 1266 /* TODO 1267 { 1268 Schema: map[string]*Schema{ 1269 "availability_zone": &Schema{ 1270 Type: TypeString, 1271 Computed: true, 1272 ComputedWhen: []string{"port"}, 1273 }, 1274 1275 "port": &Schema{ 1276 Type: TypeInt, 1277 Optional: true, 1278 }, 1279 }, 1280 1281 State: &terraform.InstanceState{ 1282 Attributes: map[string]string{ 1283 "availability_zone": "foo", 1284 "port": "80", 1285 }, 1286 }, 1287 1288 Config: map[string]interface{}{ 1289 "port": 8080, 1290 }, 1291 1292 Diff: &terraform.ResourceDiff{ 1293 Attributes: map[string]*terraform.ResourceAttrDiff{ 1294 "availability_zone": &terraform.ResourceAttrDiff{ 1295 Old: "foo", 1296 NewComputed: true, 1297 }, 1298 "port": &terraform.ResourceAttrDiff{ 1299 Old: "80", 1300 New: "8080", 1301 }, 1302 }, 1303 }, 1304 1305 Err: false, 1306 }, 1307 */ 1308 1309 /* 1310 * Maps 1311 */ 1312 1313 // #32 1314 { 1315 Schema: map[string]*Schema{ 1316 "config_vars": &Schema{ 1317 Type: TypeMap, 1318 }, 1319 }, 1320 1321 State: nil, 1322 1323 Config: map[string]interface{}{ 1324 "config_vars": []interface{}{ 1325 map[string]interface{}{ 1326 "bar": "baz", 1327 }, 1328 }, 1329 }, 1330 1331 Diff: &terraform.InstanceDiff{ 1332 Attributes: map[string]*terraform.ResourceAttrDiff{ 1333 "config_vars.#": &terraform.ResourceAttrDiff{ 1334 Old: "0", 1335 New: "1", 1336 }, 1337 1338 "config_vars.bar": &terraform.ResourceAttrDiff{ 1339 Old: "", 1340 New: "baz", 1341 }, 1342 }, 1343 }, 1344 1345 Err: false, 1346 }, 1347 1348 // #33 1349 { 1350 Schema: map[string]*Schema{ 1351 "config_vars": &Schema{ 1352 Type: TypeMap, 1353 }, 1354 }, 1355 1356 State: &terraform.InstanceState{ 1357 Attributes: map[string]string{ 1358 "config_vars.foo": "bar", 1359 }, 1360 }, 1361 1362 Config: map[string]interface{}{ 1363 "config_vars": []interface{}{ 1364 map[string]interface{}{ 1365 "bar": "baz", 1366 }, 1367 }, 1368 }, 1369 1370 Diff: &terraform.InstanceDiff{ 1371 Attributes: map[string]*terraform.ResourceAttrDiff{ 1372 "config_vars.foo": &terraform.ResourceAttrDiff{ 1373 Old: "bar", 1374 NewRemoved: true, 1375 }, 1376 "config_vars.bar": &terraform.ResourceAttrDiff{ 1377 Old: "", 1378 New: "baz", 1379 }, 1380 }, 1381 }, 1382 1383 Err: false, 1384 }, 1385 1386 // #34 1387 { 1388 Schema: map[string]*Schema{ 1389 "vars": &Schema{ 1390 Type: TypeMap, 1391 Optional: true, 1392 Computed: true, 1393 }, 1394 }, 1395 1396 State: &terraform.InstanceState{ 1397 Attributes: map[string]string{ 1398 "vars.foo": "bar", 1399 }, 1400 }, 1401 1402 Config: map[string]interface{}{ 1403 "vars": []interface{}{ 1404 map[string]interface{}{ 1405 "bar": "baz", 1406 }, 1407 }, 1408 }, 1409 1410 Diff: &terraform.InstanceDiff{ 1411 Attributes: map[string]*terraform.ResourceAttrDiff{ 1412 "vars.foo": &terraform.ResourceAttrDiff{ 1413 Old: "bar", 1414 New: "", 1415 NewRemoved: true, 1416 }, 1417 "vars.bar": &terraform.ResourceAttrDiff{ 1418 Old: "", 1419 New: "baz", 1420 }, 1421 }, 1422 }, 1423 1424 Err: false, 1425 }, 1426 1427 // #35 1428 { 1429 Schema: map[string]*Schema{ 1430 "vars": &Schema{ 1431 Type: TypeMap, 1432 Computed: true, 1433 }, 1434 }, 1435 1436 State: &terraform.InstanceState{ 1437 Attributes: map[string]string{ 1438 "vars.foo": "bar", 1439 }, 1440 }, 1441 1442 Config: nil, 1443 1444 Diff: nil, 1445 1446 Err: false, 1447 }, 1448 1449 // #36 1450 { 1451 Schema: map[string]*Schema{ 1452 "config_vars": &Schema{ 1453 Type: TypeList, 1454 Elem: &Schema{Type: TypeMap}, 1455 }, 1456 }, 1457 1458 State: &terraform.InstanceState{ 1459 Attributes: map[string]string{ 1460 "config_vars.#": "1", 1461 "config_vars.0.foo": "bar", 1462 }, 1463 }, 1464 1465 Config: map[string]interface{}{ 1466 "config_vars": []interface{}{ 1467 map[string]interface{}{ 1468 "bar": "baz", 1469 }, 1470 }, 1471 }, 1472 1473 Diff: &terraform.InstanceDiff{ 1474 Attributes: map[string]*terraform.ResourceAttrDiff{ 1475 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1476 Old: "bar", 1477 NewRemoved: true, 1478 }, 1479 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1480 Old: "", 1481 New: "baz", 1482 }, 1483 }, 1484 }, 1485 1486 Err: false, 1487 }, 1488 1489 // #37 1490 { 1491 Schema: map[string]*Schema{ 1492 "config_vars": &Schema{ 1493 Type: TypeList, 1494 Elem: &Schema{Type: TypeMap}, 1495 }, 1496 }, 1497 1498 State: &terraform.InstanceState{ 1499 Attributes: map[string]string{ 1500 "config_vars.#": "1", 1501 "config_vars.0.foo": "bar", 1502 "config_vars.0.bar": "baz", 1503 }, 1504 }, 1505 1506 Config: map[string]interface{}{}, 1507 1508 Diff: &terraform.InstanceDiff{ 1509 Attributes: map[string]*terraform.ResourceAttrDiff{ 1510 "config_vars.#": &terraform.ResourceAttrDiff{ 1511 Old: "1", 1512 New: "0", 1513 }, 1514 "config_vars.0.#": &terraform.ResourceAttrDiff{ 1515 Old: "2", 1516 New: "0", 1517 }, 1518 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1519 Old: "bar", 1520 NewRemoved: true, 1521 }, 1522 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1523 Old: "baz", 1524 NewRemoved: true, 1525 }, 1526 }, 1527 }, 1528 1529 Err: false, 1530 }, 1531 1532 /* 1533 * ForceNews 1534 */ 1535 1536 // #38 1537 { 1538 Schema: map[string]*Schema{ 1539 "availability_zone": &Schema{ 1540 Type: TypeString, 1541 Optional: true, 1542 ForceNew: true, 1543 }, 1544 1545 "address": &Schema{ 1546 Type: TypeString, 1547 Optional: true, 1548 Computed: true, 1549 }, 1550 }, 1551 1552 State: &terraform.InstanceState{ 1553 Attributes: map[string]string{ 1554 "availability_zone": "bar", 1555 "address": "foo", 1556 }, 1557 }, 1558 1559 Config: map[string]interface{}{ 1560 "availability_zone": "foo", 1561 }, 1562 1563 Diff: &terraform.InstanceDiff{ 1564 Attributes: map[string]*terraform.ResourceAttrDiff{ 1565 "availability_zone": &terraform.ResourceAttrDiff{ 1566 Old: "bar", 1567 New: "foo", 1568 RequiresNew: true, 1569 }, 1570 1571 "address": &terraform.ResourceAttrDiff{ 1572 Old: "foo", 1573 New: "", 1574 NewComputed: true, 1575 }, 1576 }, 1577 }, 1578 1579 Err: false, 1580 }, 1581 1582 // #39 Set 1583 { 1584 Schema: map[string]*Schema{ 1585 "availability_zone": &Schema{ 1586 Type: TypeString, 1587 Optional: true, 1588 ForceNew: true, 1589 }, 1590 1591 "ports": &Schema{ 1592 Type: TypeSet, 1593 Optional: true, 1594 Computed: true, 1595 Elem: &Schema{Type: TypeInt}, 1596 Set: func(a interface{}) int { 1597 return a.(int) 1598 }, 1599 }, 1600 }, 1601 1602 State: &terraform.InstanceState{ 1603 Attributes: map[string]string{ 1604 "availability_zone": "bar", 1605 "ports.#": "1", 1606 "ports.80": "80", 1607 }, 1608 }, 1609 1610 Config: map[string]interface{}{ 1611 "availability_zone": "foo", 1612 }, 1613 1614 Diff: &terraform.InstanceDiff{ 1615 Attributes: map[string]*terraform.ResourceAttrDiff{ 1616 "availability_zone": &terraform.ResourceAttrDiff{ 1617 Old: "bar", 1618 New: "foo", 1619 RequiresNew: true, 1620 }, 1621 1622 "ports.#": &terraform.ResourceAttrDiff{ 1623 Old: "1", 1624 New: "", 1625 NewComputed: true, 1626 }, 1627 }, 1628 }, 1629 1630 Err: false, 1631 }, 1632 1633 // #40 Set 1634 { 1635 Schema: map[string]*Schema{ 1636 "instances": &Schema{ 1637 Type: TypeSet, 1638 Elem: &Schema{Type: TypeString}, 1639 Optional: true, 1640 Computed: true, 1641 Set: func(v interface{}) int { 1642 return len(v.(string)) 1643 }, 1644 }, 1645 }, 1646 1647 State: &terraform.InstanceState{ 1648 Attributes: map[string]string{ 1649 "instances.#": "0", 1650 }, 1651 }, 1652 1653 Config: map[string]interface{}{ 1654 "instances": []interface{}{"${var.foo}"}, 1655 }, 1656 1657 ConfigVariables: map[string]string{ 1658 "var.foo": config.UnknownVariableValue, 1659 }, 1660 1661 Diff: &terraform.InstanceDiff{ 1662 Attributes: map[string]*terraform.ResourceAttrDiff{ 1663 "instances.#": &terraform.ResourceAttrDiff{ 1664 NewComputed: true, 1665 }, 1666 }, 1667 }, 1668 1669 Err: false, 1670 }, 1671 1672 // #41 Set 1673 { 1674 Schema: map[string]*Schema{ 1675 "route": &Schema{ 1676 Type: TypeSet, 1677 Optional: true, 1678 Elem: &Resource{ 1679 Schema: map[string]*Schema{ 1680 "index": &Schema{ 1681 Type: TypeInt, 1682 Required: true, 1683 }, 1684 1685 "gateway": &Schema{ 1686 Type: TypeString, 1687 Optional: true, 1688 }, 1689 }, 1690 }, 1691 Set: func(v interface{}) int { 1692 m := v.(map[string]interface{}) 1693 return m["index"].(int) 1694 }, 1695 }, 1696 }, 1697 1698 State: nil, 1699 1700 Config: map[string]interface{}{ 1701 "route": []map[string]interface{}{ 1702 map[string]interface{}{ 1703 "index": "1", 1704 "gateway": "${var.foo}", 1705 }, 1706 }, 1707 }, 1708 1709 ConfigVariables: map[string]string{ 1710 "var.foo": config.UnknownVariableValue, 1711 }, 1712 1713 Diff: &terraform.InstanceDiff{ 1714 Attributes: map[string]*terraform.ResourceAttrDiff{ 1715 "route.#": &terraform.ResourceAttrDiff{ 1716 Old: "0", 1717 New: "1", 1718 }, 1719 "route.~1.index": &terraform.ResourceAttrDiff{ 1720 Old: "", 1721 New: "1", 1722 }, 1723 "route.~1.gateway": &terraform.ResourceAttrDiff{ 1724 Old: "", 1725 New: "${var.foo}", 1726 }, 1727 }, 1728 }, 1729 1730 Err: false, 1731 }, 1732 1733 // #42 Set 1734 { 1735 Schema: map[string]*Schema{ 1736 "route": &Schema{ 1737 Type: TypeSet, 1738 Optional: true, 1739 Elem: &Resource{ 1740 Schema: map[string]*Schema{ 1741 "index": &Schema{ 1742 Type: TypeInt, 1743 Required: true, 1744 }, 1745 1746 "gateway": &Schema{ 1747 Type: TypeSet, 1748 Optional: true, 1749 Elem: &Schema{Type: TypeInt}, 1750 Set: func(a interface{}) int { 1751 return a.(int) 1752 }, 1753 }, 1754 }, 1755 }, 1756 Set: func(v interface{}) int { 1757 m := v.(map[string]interface{}) 1758 return m["index"].(int) 1759 }, 1760 }, 1761 }, 1762 1763 State: nil, 1764 1765 Config: map[string]interface{}{ 1766 "route": []map[string]interface{}{ 1767 map[string]interface{}{ 1768 "index": "1", 1769 "gateway": []interface{}{ 1770 "${var.foo}", 1771 }, 1772 }, 1773 }, 1774 }, 1775 1776 ConfigVariables: map[string]string{ 1777 "var.foo": config.UnknownVariableValue, 1778 }, 1779 1780 Diff: &terraform.InstanceDiff{ 1781 Attributes: map[string]*terraform.ResourceAttrDiff{ 1782 "route.#": &terraform.ResourceAttrDiff{ 1783 Old: "0", 1784 New: "1", 1785 }, 1786 "route.~1.index": &terraform.ResourceAttrDiff{ 1787 Old: "", 1788 New: "1", 1789 }, 1790 "route.~1.gateway.#": &terraform.ResourceAttrDiff{ 1791 NewComputed: true, 1792 }, 1793 }, 1794 }, 1795 1796 Err: false, 1797 }, 1798 1799 // #43 - Computed maps 1800 { 1801 Schema: map[string]*Schema{ 1802 "vars": &Schema{ 1803 Type: TypeMap, 1804 Computed: true, 1805 }, 1806 }, 1807 1808 State: nil, 1809 1810 Config: nil, 1811 1812 Diff: &terraform.InstanceDiff{ 1813 Attributes: map[string]*terraform.ResourceAttrDiff{ 1814 "vars.#": &terraform.ResourceAttrDiff{ 1815 Old: "", 1816 NewComputed: true, 1817 }, 1818 }, 1819 }, 1820 1821 Err: false, 1822 }, 1823 1824 // #44 - Computed maps 1825 { 1826 Schema: map[string]*Schema{ 1827 "vars": &Schema{ 1828 Type: TypeMap, 1829 Computed: true, 1830 }, 1831 }, 1832 1833 State: &terraform.InstanceState{ 1834 Attributes: map[string]string{ 1835 "vars.#": "0", 1836 }, 1837 }, 1838 1839 Config: map[string]interface{}{ 1840 "vars": map[string]interface{}{ 1841 "bar": "${var.foo}", 1842 }, 1843 }, 1844 1845 ConfigVariables: map[string]string{ 1846 "var.foo": config.UnknownVariableValue, 1847 }, 1848 1849 Diff: &terraform.InstanceDiff{ 1850 Attributes: map[string]*terraform.ResourceAttrDiff{ 1851 "vars.#": &terraform.ResourceAttrDiff{ 1852 Old: "", 1853 NewComputed: true, 1854 }, 1855 }, 1856 }, 1857 1858 Err: false, 1859 }, 1860 1861 // #45 - Empty 1862 { 1863 Schema: map[string]*Schema{}, 1864 1865 State: &terraform.InstanceState{}, 1866 1867 Config: map[string]interface{}{}, 1868 1869 Diff: nil, 1870 1871 Err: false, 1872 }, 1873 1874 // #46 - Float 1875 { 1876 Schema: map[string]*Schema{ 1877 "some_threshold": &Schema{ 1878 Type: TypeFloat, 1879 }, 1880 }, 1881 1882 State: &terraform.InstanceState{ 1883 Attributes: map[string]string{ 1884 "some_threshold": "567.8", 1885 }, 1886 }, 1887 1888 Config: map[string]interface{}{ 1889 "some_threshold": 12.34, 1890 }, 1891 1892 Diff: &terraform.InstanceDiff{ 1893 Attributes: map[string]*terraform.ResourceAttrDiff{ 1894 "some_threshold": &terraform.ResourceAttrDiff{ 1895 Old: "567.8", 1896 New: "12.34", 1897 }, 1898 }, 1899 }, 1900 1901 Err: false, 1902 }, 1903 1904 // #47 - https://github.com/hashicorp/terraform/issues/824 1905 { 1906 Schema: map[string]*Schema{ 1907 "block_device": &Schema{ 1908 Type: TypeSet, 1909 Optional: true, 1910 Computed: true, 1911 Elem: &Resource{ 1912 Schema: map[string]*Schema{ 1913 "device_name": &Schema{ 1914 Type: TypeString, 1915 Required: true, 1916 }, 1917 "delete_on_termination": &Schema{ 1918 Type: TypeBool, 1919 Optional: true, 1920 Default: true, 1921 }, 1922 }, 1923 }, 1924 Set: func(v interface{}) int { 1925 var buf bytes.Buffer 1926 m := v.(map[string]interface{}) 1927 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 1928 buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) 1929 return hashcode.String(buf.String()) 1930 }, 1931 }, 1932 }, 1933 1934 State: &terraform.InstanceState{ 1935 Attributes: map[string]string{ 1936 "block_device.#": "2", 1937 "block_device.616397234.delete_on_termination": "true", 1938 "block_device.616397234.device_name": "/dev/sda1", 1939 "block_device.2801811477.delete_on_termination": "true", 1940 "block_device.2801811477.device_name": "/dev/sdx", 1941 }, 1942 }, 1943 1944 Config: map[string]interface{}{ 1945 "block_device": []map[string]interface{}{ 1946 map[string]interface{}{ 1947 "device_name": "/dev/sda1", 1948 }, 1949 map[string]interface{}{ 1950 "device_name": "/dev/sdx", 1951 }, 1952 }, 1953 }, 1954 Diff: nil, 1955 Err: false, 1956 }, 1957 1958 // #48 - Zero value in state shouldn't result in diff 1959 { 1960 Schema: map[string]*Schema{ 1961 "port": &Schema{ 1962 Type: TypeBool, 1963 Optional: true, 1964 ForceNew: true, 1965 }, 1966 }, 1967 1968 State: &terraform.InstanceState{ 1969 Attributes: map[string]string{ 1970 "port": "false", 1971 }, 1972 }, 1973 1974 Config: map[string]interface{}{}, 1975 1976 Diff: nil, 1977 1978 Err: false, 1979 }, 1980 1981 // #49 Set - Same as #48 but for sets 1982 { 1983 Schema: map[string]*Schema{ 1984 "route": &Schema{ 1985 Type: TypeSet, 1986 Optional: true, 1987 Elem: &Resource{ 1988 Schema: map[string]*Schema{ 1989 "index": &Schema{ 1990 Type: TypeInt, 1991 Required: true, 1992 }, 1993 1994 "gateway": &Schema{ 1995 Type: TypeSet, 1996 Optional: true, 1997 Elem: &Schema{Type: TypeInt}, 1998 Set: func(a interface{}) int { 1999 return a.(int) 2000 }, 2001 }, 2002 }, 2003 }, 2004 Set: func(v interface{}) int { 2005 m := v.(map[string]interface{}) 2006 return m["index"].(int) 2007 }, 2008 }, 2009 }, 2010 2011 State: &terraform.InstanceState{ 2012 Attributes: map[string]string{ 2013 "route.#": "0", 2014 }, 2015 }, 2016 2017 Config: map[string]interface{}{}, 2018 2019 Diff: nil, 2020 2021 Err: false, 2022 }, 2023 2024 // #50 - A set computed element shouldn't cause a diff 2025 { 2026 Schema: map[string]*Schema{ 2027 "active": &Schema{ 2028 Type: TypeBool, 2029 Computed: true, 2030 ForceNew: true, 2031 }, 2032 }, 2033 2034 State: &terraform.InstanceState{ 2035 Attributes: map[string]string{ 2036 "active": "true", 2037 }, 2038 }, 2039 2040 Config: map[string]interface{}{}, 2041 2042 Diff: nil, 2043 2044 Err: false, 2045 }, 2046 2047 // #51 - An empty set should show up in the diff 2048 { 2049 Schema: map[string]*Schema{ 2050 "instances": &Schema{ 2051 Type: TypeSet, 2052 Elem: &Schema{Type: TypeString}, 2053 Optional: true, 2054 ForceNew: true, 2055 Set: func(v interface{}) int { 2056 return len(v.(string)) 2057 }, 2058 }, 2059 }, 2060 2061 State: &terraform.InstanceState{ 2062 Attributes: map[string]string{ 2063 "instances.#": "1", 2064 "instances.3": "foo", 2065 }, 2066 }, 2067 2068 Config: map[string]interface{}{}, 2069 2070 Diff: &terraform.InstanceDiff{ 2071 Attributes: map[string]*terraform.ResourceAttrDiff{ 2072 "instances.#": &terraform.ResourceAttrDiff{ 2073 Old: "1", 2074 New: "0", 2075 RequiresNew: true, 2076 }, 2077 "instances.3": &terraform.ResourceAttrDiff{ 2078 Old: "foo", 2079 New: "", 2080 NewRemoved: true, 2081 }, 2082 }, 2083 }, 2084 2085 Err: false, 2086 }, 2087 2088 // #52 - Map with empty value 2089 { 2090 Schema: map[string]*Schema{ 2091 "vars": &Schema{ 2092 Type: TypeMap, 2093 }, 2094 }, 2095 2096 State: nil, 2097 2098 Config: map[string]interface{}{ 2099 "vars": map[string]interface{}{ 2100 "foo": "", 2101 }, 2102 }, 2103 2104 Diff: &terraform.InstanceDiff{ 2105 Attributes: map[string]*terraform.ResourceAttrDiff{ 2106 "vars.#": &terraform.ResourceAttrDiff{ 2107 Old: "0", 2108 New: "1", 2109 }, 2110 "vars.foo": &terraform.ResourceAttrDiff{ 2111 Old: "", 2112 New: "", 2113 }, 2114 }, 2115 }, 2116 2117 Err: false, 2118 }, 2119 2120 // #53 - Unset bool, not in state 2121 { 2122 Schema: map[string]*Schema{ 2123 "force": &Schema{ 2124 Type: TypeBool, 2125 Optional: true, 2126 ForceNew: true, 2127 }, 2128 }, 2129 2130 State: nil, 2131 2132 Config: map[string]interface{}{}, 2133 2134 Diff: nil, 2135 2136 Err: false, 2137 }, 2138 2139 // #54 - Unset set, not in state 2140 { 2141 Schema: map[string]*Schema{ 2142 "metadata_keys": &Schema{ 2143 Type: TypeSet, 2144 Optional: true, 2145 ForceNew: true, 2146 Elem: &Schema{Type: TypeInt}, 2147 Set: func(interface{}) int { return 0 }, 2148 }, 2149 }, 2150 2151 State: nil, 2152 2153 Config: map[string]interface{}{}, 2154 2155 Diff: nil, 2156 2157 Err: false, 2158 }, 2159 2160 // #55 - Unset list in state, should not show up computed 2161 { 2162 Schema: map[string]*Schema{ 2163 "metadata_keys": &Schema{ 2164 Type: TypeList, 2165 Optional: true, 2166 Computed: true, 2167 ForceNew: true, 2168 Elem: &Schema{Type: TypeInt}, 2169 }, 2170 }, 2171 2172 State: &terraform.InstanceState{ 2173 Attributes: map[string]string{ 2174 "metadata_keys.#": "0", 2175 }, 2176 }, 2177 2178 Config: map[string]interface{}{}, 2179 2180 Diff: nil, 2181 2182 Err: false, 2183 }, 2184 2185 // #56 - Set element computed substring 2186 { 2187 Schema: map[string]*Schema{ 2188 "ports": &Schema{ 2189 Type: TypeSet, 2190 Required: true, 2191 Elem: &Schema{Type: TypeInt}, 2192 Set: func(a interface{}) int { 2193 return a.(int) 2194 }, 2195 }, 2196 }, 2197 2198 State: nil, 2199 2200 Config: map[string]interface{}{ 2201 "ports": []interface{}{1, "${var.foo}32"}, 2202 }, 2203 2204 ConfigVariables: map[string]string{ 2205 "var.foo": config.UnknownVariableValue, 2206 }, 2207 2208 Diff: &terraform.InstanceDiff{ 2209 Attributes: map[string]*terraform.ResourceAttrDiff{ 2210 "ports.#": &terraform.ResourceAttrDiff{ 2211 Old: "", 2212 New: "", 2213 NewComputed: true, 2214 }, 2215 }, 2216 }, 2217 2218 Err: false, 2219 }, 2220 2221 // #57 - Computed map without config that's known to be empty does not 2222 // generate diff 2223 { 2224 Schema: map[string]*Schema{ 2225 "tags": &Schema{ 2226 Type: TypeMap, 2227 Computed: true, 2228 }, 2229 }, 2230 2231 Config: nil, 2232 2233 State: &terraform.InstanceState{ 2234 Attributes: map[string]string{ 2235 "tags.#": "0", 2236 }, 2237 }, 2238 2239 Diff: nil, 2240 2241 Err: false, 2242 }, 2243 2244 // #58 Set with hyphen keys 2245 { 2246 Schema: map[string]*Schema{ 2247 "route": &Schema{ 2248 Type: TypeSet, 2249 Optional: true, 2250 Elem: &Resource{ 2251 Schema: map[string]*Schema{ 2252 "index": &Schema{ 2253 Type: TypeInt, 2254 Required: true, 2255 }, 2256 2257 "gateway-name": &Schema{ 2258 Type: TypeString, 2259 Optional: true, 2260 }, 2261 }, 2262 }, 2263 Set: func(v interface{}) int { 2264 m := v.(map[string]interface{}) 2265 return m["index"].(int) 2266 }, 2267 }, 2268 }, 2269 2270 State: nil, 2271 2272 Config: map[string]interface{}{ 2273 "route": []map[string]interface{}{ 2274 map[string]interface{}{ 2275 "index": "1", 2276 "gateway-name": "hello", 2277 }, 2278 }, 2279 }, 2280 2281 Diff: &terraform.InstanceDiff{ 2282 Attributes: map[string]*terraform.ResourceAttrDiff{ 2283 "route.#": &terraform.ResourceAttrDiff{ 2284 Old: "0", 2285 New: "1", 2286 }, 2287 "route.1.index": &terraform.ResourceAttrDiff{ 2288 Old: "", 2289 New: "1", 2290 }, 2291 "route.1.gateway-name": &terraform.ResourceAttrDiff{ 2292 Old: "", 2293 New: "hello", 2294 }, 2295 }, 2296 }, 2297 2298 Err: false, 2299 }, 2300 2301 // #59: StateFunc in nested set (#1759) 2302 { 2303 Schema: map[string]*Schema{ 2304 "service_account": &Schema{ 2305 Type: TypeList, 2306 Optional: true, 2307 ForceNew: true, 2308 Elem: &Resource{ 2309 Schema: map[string]*Schema{ 2310 "scopes": &Schema{ 2311 Type: TypeSet, 2312 Required: true, 2313 ForceNew: true, 2314 Elem: &Schema{ 2315 Type: TypeString, 2316 StateFunc: func(v interface{}) string { 2317 return v.(string) + "!" 2318 }, 2319 }, 2320 Set: func(v interface{}) int { 2321 i, err := strconv.Atoi(v.(string)) 2322 if err != nil { 2323 t.Fatalf("err: %s", err) 2324 } 2325 return i 2326 }, 2327 }, 2328 }, 2329 }, 2330 }, 2331 }, 2332 2333 State: nil, 2334 2335 Config: map[string]interface{}{ 2336 "service_account": []map[string]interface{}{ 2337 { 2338 "scopes": []interface{}{"123"}, 2339 }, 2340 }, 2341 }, 2342 2343 Diff: &terraform.InstanceDiff{ 2344 Attributes: map[string]*terraform.ResourceAttrDiff{ 2345 "service_account.#": &terraform.ResourceAttrDiff{ 2346 Old: "0", 2347 New: "1", 2348 RequiresNew: true, 2349 }, 2350 "service_account.0.scopes.#": &terraform.ResourceAttrDiff{ 2351 Old: "0", 2352 New: "1", 2353 RequiresNew: true, 2354 }, 2355 "service_account.0.scopes.123": &terraform.ResourceAttrDiff{ 2356 Old: "", 2357 New: "123!", 2358 NewExtra: "123", 2359 RequiresNew: true, 2360 }, 2361 }, 2362 }, 2363 2364 Err: false, 2365 }, 2366 2367 // #60 - Removing set elements 2368 { 2369 Schema: map[string]*Schema{ 2370 "instances": &Schema{ 2371 Type: TypeSet, 2372 Elem: &Schema{Type: TypeString}, 2373 Optional: true, 2374 ForceNew: true, 2375 Set: func(v interface{}) int { 2376 return len(v.(string)) 2377 }, 2378 }, 2379 }, 2380 2381 State: &terraform.InstanceState{ 2382 Attributes: map[string]string{ 2383 "instances.#": "2", 2384 "instances.3": "333", 2385 "instances.2": "22", 2386 }, 2387 }, 2388 2389 Config: map[string]interface{}{ 2390 "instances": []interface{}{"333", "4444"}, 2391 }, 2392 2393 Diff: &terraform.InstanceDiff{ 2394 Attributes: map[string]*terraform.ResourceAttrDiff{ 2395 "instances.#": &terraform.ResourceAttrDiff{ 2396 Old: "2", 2397 New: "2", 2398 }, 2399 "instances.2": &terraform.ResourceAttrDiff{ 2400 Old: "22", 2401 New: "", 2402 NewRemoved: true, 2403 }, 2404 "instances.3": &terraform.ResourceAttrDiff{ 2405 Old: "333", 2406 New: "333", 2407 RequiresNew: true, 2408 }, 2409 "instances.4": &terraform.ResourceAttrDiff{ 2410 Old: "", 2411 New: "4444", 2412 RequiresNew: true, 2413 }, 2414 }, 2415 }, 2416 2417 Err: false, 2418 }, 2419 } 2420 2421 for i, tc := range cases { 2422 c, err := config.NewRawConfig(tc.Config) 2423 if err != nil { 2424 t.Fatalf("#%d err: %s", i, err) 2425 } 2426 2427 if len(tc.ConfigVariables) > 0 { 2428 vars := make(map[string]ast.Variable) 2429 for k, v := range tc.ConfigVariables { 2430 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 2431 } 2432 2433 if err := c.Interpolate(vars); err != nil { 2434 t.Fatalf("#%d err: %s", i, err) 2435 } 2436 } 2437 2438 d, err := schemaMap(tc.Schema).Diff( 2439 tc.State, terraform.NewResourceConfig(c)) 2440 if (err != nil) != tc.Err { 2441 t.Fatalf("#%d err: %s", i, err) 2442 } 2443 2444 if !reflect.DeepEqual(tc.Diff, d) { 2445 t.Fatalf("#%d:\n\nexpected: %#v\n\ngot:\n\n%#v", i, tc.Diff, d) 2446 } 2447 } 2448 } 2449 2450 func TestSchemaMap_Input(t *testing.T) { 2451 cases := map[string]struct { 2452 Schema map[string]*Schema 2453 Config map[string]interface{} 2454 Input map[string]string 2455 Result map[string]interface{} 2456 Err bool 2457 }{ 2458 /* 2459 * String decode 2460 */ 2461 2462 "uses input on optional field with no config": { 2463 Schema: map[string]*Schema{ 2464 "availability_zone": &Schema{ 2465 Type: TypeString, 2466 Optional: true, 2467 }, 2468 }, 2469 2470 Input: map[string]string{ 2471 "availability_zone": "foo", 2472 }, 2473 2474 Result: map[string]interface{}{ 2475 "availability_zone": "foo", 2476 }, 2477 2478 Err: false, 2479 }, 2480 2481 "input ignored when config has a value": { 2482 Schema: map[string]*Schema{ 2483 "availability_zone": &Schema{ 2484 Type: TypeString, 2485 Optional: true, 2486 }, 2487 }, 2488 2489 Config: map[string]interface{}{ 2490 "availability_zone": "bar", 2491 }, 2492 2493 Input: map[string]string{ 2494 "availability_zone": "foo", 2495 }, 2496 2497 Result: map[string]interface{}{}, 2498 2499 Err: false, 2500 }, 2501 2502 "input ignored when schema has a default": { 2503 Schema: map[string]*Schema{ 2504 "availability_zone": &Schema{ 2505 Type: TypeString, 2506 Default: "foo", 2507 Optional: true, 2508 }, 2509 }, 2510 2511 Input: map[string]string{ 2512 "availability_zone": "bar", 2513 }, 2514 2515 Result: map[string]interface{}{}, 2516 2517 Err: false, 2518 }, 2519 2520 "input ignored when default function returns a value": { 2521 Schema: map[string]*Schema{ 2522 "availability_zone": &Schema{ 2523 Type: TypeString, 2524 DefaultFunc: func() (interface{}, error) { 2525 return "foo", nil 2526 }, 2527 Optional: true, 2528 }, 2529 }, 2530 2531 Input: map[string]string{ 2532 "availability_zone": "bar", 2533 }, 2534 2535 Result: map[string]interface{}{}, 2536 2537 Err: false, 2538 }, 2539 2540 "input ignored when default function returns an empty string": { 2541 Schema: map[string]*Schema{ 2542 "availability_zone": &Schema{ 2543 Type: TypeString, 2544 Default: "", 2545 Optional: true, 2546 }, 2547 }, 2548 2549 Input: map[string]string{ 2550 "availability_zone": "bar", 2551 }, 2552 2553 Result: map[string]interface{}{}, 2554 2555 Err: false, 2556 }, 2557 2558 "input used when default function returns nil": { 2559 Schema: map[string]*Schema{ 2560 "availability_zone": &Schema{ 2561 Type: TypeString, 2562 DefaultFunc: func() (interface{}, error) { 2563 return nil, nil 2564 }, 2565 Optional: true, 2566 }, 2567 }, 2568 2569 Input: map[string]string{ 2570 "availability_zone": "bar", 2571 }, 2572 2573 Result: map[string]interface{}{ 2574 "availability_zone": "bar", 2575 }, 2576 2577 Err: false, 2578 }, 2579 } 2580 2581 for i, tc := range cases { 2582 if tc.Config == nil { 2583 tc.Config = make(map[string]interface{}) 2584 } 2585 2586 c, err := config.NewRawConfig(tc.Config) 2587 if err != nil { 2588 t.Fatalf("err: %s", err) 2589 } 2590 2591 input := new(terraform.MockUIInput) 2592 input.InputReturnMap = tc.Input 2593 2594 rc := terraform.NewResourceConfig(c) 2595 rc.Config = make(map[string]interface{}) 2596 2597 actual, err := schemaMap(tc.Schema).Input(input, rc) 2598 if (err != nil) != tc.Err { 2599 t.Fatalf("#%v err: %s", i, err) 2600 } 2601 2602 if !reflect.DeepEqual(tc.Result, actual.Config) { 2603 t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result) 2604 } 2605 } 2606 } 2607 2608 func TestSchemaMap_InputDefault(t *testing.T) { 2609 emptyConfig := make(map[string]interface{}) 2610 c, err := config.NewRawConfig(emptyConfig) 2611 if err != nil { 2612 t.Fatalf("err: %s", err) 2613 } 2614 rc := terraform.NewResourceConfig(c) 2615 rc.Config = make(map[string]interface{}) 2616 2617 input := new(terraform.MockUIInput) 2618 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 2619 t.Fatalf("InputFn should not be called on: %#v", opts) 2620 return "", nil 2621 } 2622 2623 schema := map[string]*Schema{ 2624 "availability_zone": &Schema{ 2625 Type: TypeString, 2626 Default: "foo", 2627 Optional: true, 2628 }, 2629 } 2630 actual, err := schemaMap(schema).Input(input, rc) 2631 if err != nil { 2632 t.Fatalf("err: %s", err) 2633 } 2634 2635 expected := map[string]interface{}{} 2636 2637 if !reflect.DeepEqual(expected, actual.Config) { 2638 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 2639 } 2640 } 2641 2642 func TestSchemaMap_InternalValidate(t *testing.T) { 2643 cases := []struct { 2644 In map[string]*Schema 2645 Err bool 2646 }{ 2647 { 2648 nil, 2649 false, 2650 }, 2651 2652 // No optional and no required 2653 { 2654 map[string]*Schema{ 2655 "foo": &Schema{ 2656 Type: TypeInt, 2657 Optional: true, 2658 Required: true, 2659 }, 2660 }, 2661 true, 2662 }, 2663 2664 // No optional and no required 2665 { 2666 map[string]*Schema{ 2667 "foo": &Schema{ 2668 Type: TypeInt, 2669 }, 2670 }, 2671 true, 2672 }, 2673 2674 // Missing Type 2675 { 2676 map[string]*Schema{ 2677 "foo": &Schema{ 2678 Required: true, 2679 }, 2680 }, 2681 true, 2682 }, 2683 2684 // Required but computed 2685 { 2686 map[string]*Schema{ 2687 "foo": &Schema{ 2688 Type: TypeInt, 2689 Required: true, 2690 Computed: true, 2691 }, 2692 }, 2693 true, 2694 }, 2695 2696 // Looks good 2697 { 2698 map[string]*Schema{ 2699 "foo": &Schema{ 2700 Type: TypeString, 2701 Required: true, 2702 }, 2703 }, 2704 false, 2705 }, 2706 2707 // Computed but has default 2708 { 2709 map[string]*Schema{ 2710 "foo": &Schema{ 2711 Type: TypeInt, 2712 Optional: true, 2713 Computed: true, 2714 Default: "foo", 2715 }, 2716 }, 2717 true, 2718 }, 2719 2720 // Required but has default 2721 { 2722 map[string]*Schema{ 2723 "foo": &Schema{ 2724 Type: TypeInt, 2725 Optional: true, 2726 Required: true, 2727 Default: "foo", 2728 }, 2729 }, 2730 true, 2731 }, 2732 2733 // List element not set 2734 { 2735 map[string]*Schema{ 2736 "foo": &Schema{ 2737 Type: TypeList, 2738 }, 2739 }, 2740 true, 2741 }, 2742 2743 // List default 2744 { 2745 map[string]*Schema{ 2746 "foo": &Schema{ 2747 Type: TypeList, 2748 Elem: &Schema{Type: TypeInt}, 2749 Default: "foo", 2750 }, 2751 }, 2752 true, 2753 }, 2754 2755 // List element computed 2756 { 2757 map[string]*Schema{ 2758 "foo": &Schema{ 2759 Type: TypeList, 2760 Optional: true, 2761 Elem: &Schema{ 2762 Type: TypeInt, 2763 Computed: true, 2764 }, 2765 }, 2766 }, 2767 true, 2768 }, 2769 2770 // List element with Set set 2771 { 2772 map[string]*Schema{ 2773 "foo": &Schema{ 2774 Type: TypeList, 2775 Elem: &Schema{Type: TypeInt}, 2776 Set: func(interface{}) int { return 0 }, 2777 Optional: true, 2778 }, 2779 }, 2780 true, 2781 }, 2782 2783 // Set element with no Set set 2784 { 2785 map[string]*Schema{ 2786 "foo": &Schema{ 2787 Type: TypeSet, 2788 Elem: &Schema{Type: TypeInt}, 2789 Optional: true, 2790 }, 2791 }, 2792 true, 2793 }, 2794 2795 // Required but computed 2796 { 2797 map[string]*Schema{ 2798 "foo": &Schema{ 2799 Type: TypeInt, 2800 Required: true, 2801 ComputedWhen: []string{"foo"}, 2802 }, 2803 }, 2804 true, 2805 }, 2806 2807 // Conflicting attributes cannot be required 2808 { 2809 map[string]*Schema{ 2810 "blacklist": &Schema{ 2811 Type: TypeBool, 2812 Required: true, 2813 }, 2814 "whitelist": &Schema{ 2815 Type: TypeBool, 2816 Optional: true, 2817 ConflictsWith: []string{"blacklist"}, 2818 }, 2819 }, 2820 true, 2821 }, 2822 2823 // Attribute with conflicts cannot be required 2824 { 2825 map[string]*Schema{ 2826 "whitelist": &Schema{ 2827 Type: TypeBool, 2828 Required: true, 2829 ConflictsWith: []string{"blacklist"}, 2830 }, 2831 }, 2832 true, 2833 }, 2834 2835 // ConflictsWith cannot be used w/ Computed 2836 { 2837 map[string]*Schema{ 2838 "blacklist": &Schema{ 2839 Type: TypeBool, 2840 Computed: true, 2841 }, 2842 "whitelist": &Schema{ 2843 Type: TypeBool, 2844 Optional: true, 2845 ConflictsWith: []string{"blacklist"}, 2846 }, 2847 }, 2848 true, 2849 }, 2850 2851 // ConflictsWith cannot be used w/ ComputedWhen 2852 { 2853 map[string]*Schema{ 2854 "blacklist": &Schema{ 2855 Type: TypeBool, 2856 ComputedWhen: []string{"foor"}, 2857 }, 2858 "whitelist": &Schema{ 2859 Type: TypeBool, 2860 Required: true, 2861 ConflictsWith: []string{"blacklist"}, 2862 }, 2863 }, 2864 true, 2865 }, 2866 2867 // Sub-resource invalid 2868 { 2869 map[string]*Schema{ 2870 "foo": &Schema{ 2871 Type: TypeList, 2872 Optional: true, 2873 Elem: &Resource{ 2874 Schema: map[string]*Schema{ 2875 "foo": new(Schema), 2876 }, 2877 }, 2878 }, 2879 }, 2880 true, 2881 }, 2882 2883 // Sub-resource valid 2884 { 2885 map[string]*Schema{ 2886 "foo": &Schema{ 2887 Type: TypeList, 2888 Optional: true, 2889 Elem: &Resource{ 2890 Schema: map[string]*Schema{ 2891 "foo": &Schema{ 2892 Type: TypeInt, 2893 Optional: true, 2894 }, 2895 }, 2896 }, 2897 }, 2898 }, 2899 false, 2900 }, 2901 2902 // ValidateFunc on non-primitive 2903 { 2904 map[string]*Schema{ 2905 "foo": &Schema{ 2906 Type: TypeMap, 2907 Required: true, 2908 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 2909 return 2910 }, 2911 }, 2912 }, 2913 true, 2914 }, 2915 } 2916 2917 for i, tc := range cases { 2918 err := schemaMap(tc.In).InternalValidate(schemaMap{}) 2919 if (err != nil) != tc.Err { 2920 if tc.Err { 2921 t.Fatalf("%d: Expected error did not occur:\n\n%#v", i, tc.In) 2922 } 2923 t.Fatalf("%d: Unexpected error occured:\n\n%#v", i, tc.In) 2924 } 2925 } 2926 2927 } 2928 2929 func TestSchemaMap_Validate(t *testing.T) { 2930 cases := map[string]struct { 2931 Schema map[string]*Schema 2932 Config map[string]interface{} 2933 Vars map[string]string 2934 Err bool 2935 Errors []error 2936 Warnings []string 2937 }{ 2938 "Good": { 2939 Schema: map[string]*Schema{ 2940 "availability_zone": &Schema{ 2941 Type: TypeString, 2942 Optional: true, 2943 Computed: true, 2944 ForceNew: true, 2945 }, 2946 }, 2947 2948 Config: map[string]interface{}{ 2949 "availability_zone": "foo", 2950 }, 2951 }, 2952 2953 "Good, because the var is not set and that error will come elsewhere": { 2954 Schema: map[string]*Schema{ 2955 "size": &Schema{ 2956 Type: TypeInt, 2957 Required: true, 2958 }, 2959 }, 2960 2961 Config: map[string]interface{}{ 2962 "size": "${var.foo}", 2963 }, 2964 2965 Vars: map[string]string{ 2966 "var.foo": config.UnknownVariableValue, 2967 }, 2968 }, 2969 2970 "Required field not set": { 2971 Schema: map[string]*Schema{ 2972 "availability_zone": &Schema{ 2973 Type: TypeString, 2974 Required: true, 2975 }, 2976 }, 2977 2978 Config: map[string]interface{}{}, 2979 2980 Err: true, 2981 }, 2982 2983 "Invalid basic type": { 2984 Schema: map[string]*Schema{ 2985 "port": &Schema{ 2986 Type: TypeInt, 2987 Required: true, 2988 }, 2989 }, 2990 2991 Config: map[string]interface{}{ 2992 "port": "I am invalid", 2993 }, 2994 2995 Err: true, 2996 }, 2997 2998 "Invalid complex type": { 2999 Schema: map[string]*Schema{ 3000 "user_data": &Schema{ 3001 Type: TypeString, 3002 Optional: true, 3003 }, 3004 }, 3005 3006 Config: map[string]interface{}{ 3007 "user_data": []interface{}{ 3008 map[string]interface{}{ 3009 "foo": "bar", 3010 }, 3011 }, 3012 }, 3013 3014 Err: true, 3015 }, 3016 3017 "Bad type, interpolated": { 3018 Schema: map[string]*Schema{ 3019 "size": &Schema{ 3020 Type: TypeInt, 3021 Required: true, 3022 }, 3023 }, 3024 3025 Config: map[string]interface{}{ 3026 "size": "${var.foo}", 3027 }, 3028 3029 Vars: map[string]string{ 3030 "var.foo": "nope", 3031 }, 3032 3033 Err: true, 3034 }, 3035 3036 "Required but has DefaultFunc": { 3037 Schema: map[string]*Schema{ 3038 "availability_zone": &Schema{ 3039 Type: TypeString, 3040 Required: true, 3041 DefaultFunc: func() (interface{}, error) { 3042 return "foo", nil 3043 }, 3044 }, 3045 }, 3046 3047 Config: nil, 3048 }, 3049 3050 "Required but has DefaultFunc return nil": { 3051 Schema: map[string]*Schema{ 3052 "availability_zone": &Schema{ 3053 Type: TypeString, 3054 Required: true, 3055 DefaultFunc: func() (interface{}, error) { 3056 return nil, nil 3057 }, 3058 }, 3059 }, 3060 3061 Config: nil, 3062 3063 Err: true, 3064 }, 3065 3066 "Optional sub-resource": { 3067 Schema: map[string]*Schema{ 3068 "ingress": &Schema{ 3069 Type: TypeList, 3070 Elem: &Resource{ 3071 Schema: map[string]*Schema{ 3072 "from": &Schema{ 3073 Type: TypeInt, 3074 Required: true, 3075 }, 3076 }, 3077 }, 3078 }, 3079 }, 3080 3081 Config: map[string]interface{}{}, 3082 3083 Err: false, 3084 }, 3085 3086 "Sub-resource is the wrong type": { 3087 Schema: map[string]*Schema{ 3088 "ingress": &Schema{ 3089 Type: TypeList, 3090 Required: true, 3091 Elem: &Resource{ 3092 Schema: map[string]*Schema{ 3093 "from": &Schema{ 3094 Type: TypeInt, 3095 Required: true, 3096 }, 3097 }, 3098 }, 3099 }, 3100 }, 3101 3102 Config: map[string]interface{}{ 3103 "ingress": []interface{}{"foo"}, 3104 }, 3105 3106 Err: true, 3107 }, 3108 3109 "Not a list": { 3110 Schema: map[string]*Schema{ 3111 "ingress": &Schema{ 3112 Type: TypeList, 3113 Elem: &Resource{ 3114 Schema: map[string]*Schema{ 3115 "from": &Schema{ 3116 Type: TypeInt, 3117 Required: true, 3118 }, 3119 }, 3120 }, 3121 }, 3122 }, 3123 3124 Config: map[string]interface{}{ 3125 "ingress": "foo", 3126 }, 3127 3128 Err: true, 3129 }, 3130 3131 "Required sub-resource field": { 3132 Schema: map[string]*Schema{ 3133 "ingress": &Schema{ 3134 Type: TypeList, 3135 Elem: &Resource{ 3136 Schema: map[string]*Schema{ 3137 "from": &Schema{ 3138 Type: TypeInt, 3139 Required: true, 3140 }, 3141 }, 3142 }, 3143 }, 3144 }, 3145 3146 Config: map[string]interface{}{ 3147 "ingress": []interface{}{ 3148 map[string]interface{}{}, 3149 }, 3150 }, 3151 3152 Err: true, 3153 }, 3154 3155 "Good sub-resource": { 3156 Schema: map[string]*Schema{ 3157 "ingress": &Schema{ 3158 Type: TypeList, 3159 Optional: true, 3160 Elem: &Resource{ 3161 Schema: map[string]*Schema{ 3162 "from": &Schema{ 3163 Type: TypeInt, 3164 Required: true, 3165 }, 3166 }, 3167 }, 3168 }, 3169 }, 3170 3171 Config: map[string]interface{}{ 3172 "ingress": []interface{}{ 3173 map[string]interface{}{ 3174 "from": 80, 3175 }, 3176 }, 3177 }, 3178 3179 Err: false, 3180 }, 3181 3182 "Invalid/unknown field": { 3183 Schema: map[string]*Schema{ 3184 "availability_zone": &Schema{ 3185 Type: TypeString, 3186 Optional: true, 3187 Computed: true, 3188 ForceNew: true, 3189 }, 3190 }, 3191 3192 Config: map[string]interface{}{ 3193 "foo": "bar", 3194 }, 3195 3196 Err: true, 3197 }, 3198 3199 "Invalid/unknown field with computed value": { 3200 Schema: map[string]*Schema{ 3201 "availability_zone": &Schema{ 3202 Type: TypeString, 3203 Optional: true, 3204 Computed: true, 3205 ForceNew: true, 3206 }, 3207 }, 3208 3209 Config: map[string]interface{}{ 3210 "foo": "${var.foo}", 3211 }, 3212 3213 Vars: map[string]string{ 3214 "var.foo": config.UnknownVariableValue, 3215 }, 3216 3217 Err: true, 3218 }, 3219 3220 "Computed field set": { 3221 Schema: map[string]*Schema{ 3222 "availability_zone": &Schema{ 3223 Type: TypeString, 3224 Computed: true, 3225 }, 3226 }, 3227 3228 Config: map[string]interface{}{ 3229 "availability_zone": "bar", 3230 }, 3231 3232 Err: true, 3233 }, 3234 3235 "Not a set": { 3236 Schema: map[string]*Schema{ 3237 "ports": &Schema{ 3238 Type: TypeSet, 3239 Required: true, 3240 Elem: &Schema{Type: TypeInt}, 3241 Set: func(a interface{}) int { 3242 return a.(int) 3243 }, 3244 }, 3245 }, 3246 3247 Config: map[string]interface{}{ 3248 "ports": "foo", 3249 }, 3250 3251 Err: true, 3252 }, 3253 3254 "Maps": { 3255 Schema: map[string]*Schema{ 3256 "user_data": &Schema{ 3257 Type: TypeMap, 3258 Optional: true, 3259 }, 3260 }, 3261 3262 Config: map[string]interface{}{ 3263 "user_data": "foo", 3264 }, 3265 3266 Err: true, 3267 }, 3268 3269 "Good map: data surrounded by extra slice": { 3270 Schema: map[string]*Schema{ 3271 "user_data": &Schema{ 3272 Type: TypeMap, 3273 Optional: true, 3274 }, 3275 }, 3276 3277 Config: map[string]interface{}{ 3278 "user_data": []interface{}{ 3279 map[string]interface{}{ 3280 "foo": "bar", 3281 }, 3282 }, 3283 }, 3284 }, 3285 3286 "Good map": { 3287 Schema: map[string]*Schema{ 3288 "user_data": &Schema{ 3289 Type: TypeMap, 3290 Optional: true, 3291 }, 3292 }, 3293 3294 Config: map[string]interface{}{ 3295 "user_data": map[string]interface{}{ 3296 "foo": "bar", 3297 }, 3298 }, 3299 }, 3300 3301 "Bad map: just a slice": { 3302 Schema: map[string]*Schema{ 3303 "user_data": &Schema{ 3304 Type: TypeMap, 3305 Optional: true, 3306 }, 3307 }, 3308 3309 Config: map[string]interface{}{ 3310 "user_data": []interface{}{ 3311 "foo", 3312 }, 3313 }, 3314 3315 Err: true, 3316 }, 3317 3318 "Good set: config has slice with single interpolated value": { 3319 Schema: map[string]*Schema{ 3320 "security_groups": &Schema{ 3321 Type: TypeSet, 3322 Optional: true, 3323 Computed: true, 3324 ForceNew: true, 3325 Elem: &Schema{Type: TypeString}, 3326 Set: func(v interface{}) int { 3327 return len(v.(string)) 3328 }, 3329 }, 3330 }, 3331 3332 Config: map[string]interface{}{ 3333 "security_groups": []interface{}{"${var.foo}"}, 3334 }, 3335 3336 Err: false, 3337 }, 3338 3339 "Bad set: config has single interpolated value": { 3340 Schema: map[string]*Schema{ 3341 "security_groups": &Schema{ 3342 Type: TypeSet, 3343 Optional: true, 3344 Computed: true, 3345 ForceNew: true, 3346 Elem: &Schema{Type: TypeString}, 3347 }, 3348 }, 3349 3350 Config: map[string]interface{}{ 3351 "security_groups": "${var.foo}", 3352 }, 3353 3354 Err: true, 3355 }, 3356 3357 "Bad, subresource should not allow unknown elements": { 3358 Schema: map[string]*Schema{ 3359 "ingress": &Schema{ 3360 Type: TypeList, 3361 Optional: true, 3362 Elem: &Resource{ 3363 Schema: map[string]*Schema{ 3364 "port": &Schema{ 3365 Type: TypeInt, 3366 Required: true, 3367 }, 3368 }, 3369 }, 3370 }, 3371 }, 3372 3373 Config: map[string]interface{}{ 3374 "ingress": []interface{}{ 3375 map[string]interface{}{ 3376 "port": 80, 3377 "other": "yes", 3378 }, 3379 }, 3380 }, 3381 3382 Err: true, 3383 }, 3384 3385 "Bad, subresource should not allow invalid types": { 3386 Schema: map[string]*Schema{ 3387 "ingress": &Schema{ 3388 Type: TypeList, 3389 Optional: true, 3390 Elem: &Resource{ 3391 Schema: map[string]*Schema{ 3392 "port": &Schema{ 3393 Type: TypeInt, 3394 Required: true, 3395 }, 3396 }, 3397 }, 3398 }, 3399 }, 3400 3401 Config: map[string]interface{}{ 3402 "ingress": []interface{}{ 3403 map[string]interface{}{ 3404 "port": "bad", 3405 }, 3406 }, 3407 }, 3408 3409 Err: true, 3410 }, 3411 3412 "Deprecated attribute usage generates warning, but not error": { 3413 Schema: map[string]*Schema{ 3414 "old_news": &Schema{ 3415 Type: TypeString, 3416 Optional: true, 3417 Deprecated: "please use 'new_news' instead", 3418 }, 3419 }, 3420 3421 Config: map[string]interface{}{ 3422 "old_news": "extra extra!", 3423 }, 3424 3425 Err: false, 3426 3427 Warnings: []string{ 3428 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 3429 }, 3430 }, 3431 3432 "Deprecated generates no warnings if attr not used": { 3433 Schema: map[string]*Schema{ 3434 "old_news": &Schema{ 3435 Type: TypeString, 3436 Optional: true, 3437 Deprecated: "please use 'new_news' instead", 3438 }, 3439 }, 3440 3441 Err: false, 3442 3443 Warnings: nil, 3444 }, 3445 3446 "Removed attribute usage generates error": { 3447 Schema: map[string]*Schema{ 3448 "long_gone": &Schema{ 3449 Type: TypeString, 3450 Optional: true, 3451 Removed: "no longer supported by Cloud API", 3452 }, 3453 }, 3454 3455 Config: map[string]interface{}{ 3456 "long_gone": "still here!", 3457 }, 3458 3459 Err: true, 3460 Errors: []error{ 3461 fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), 3462 }, 3463 }, 3464 3465 "Removed generates no errors if attr not used": { 3466 Schema: map[string]*Schema{ 3467 "long_gone": &Schema{ 3468 Type: TypeString, 3469 Optional: true, 3470 Removed: "no longer supported by Cloud API", 3471 }, 3472 }, 3473 3474 Err: false, 3475 }, 3476 3477 "Conflicting attributes generate error": { 3478 Schema: map[string]*Schema{ 3479 "whitelist": &Schema{ 3480 Type: TypeString, 3481 Optional: true, 3482 }, 3483 "blacklist": &Schema{ 3484 Type: TypeString, 3485 Optional: true, 3486 ConflictsWith: []string{"whitelist"}, 3487 }, 3488 }, 3489 3490 Config: map[string]interface{}{ 3491 "whitelist": "white-val", 3492 "blacklist": "black-val", 3493 }, 3494 3495 Err: true, 3496 Errors: []error{ 3497 fmt.Errorf("\"blacklist\": conflicts with whitelist (\"white-val\")"), 3498 }, 3499 }, 3500 3501 "Required attribute & undefined conflicting optional are good": { 3502 Schema: map[string]*Schema{ 3503 "required_att": &Schema{ 3504 Type: TypeString, 3505 Required: true, 3506 }, 3507 "optional_att": &Schema{ 3508 Type: TypeString, 3509 Optional: true, 3510 ConflictsWith: []string{"required_att"}, 3511 }, 3512 }, 3513 3514 Config: map[string]interface{}{ 3515 "required_att": "required-val", 3516 }, 3517 3518 Err: false, 3519 }, 3520 3521 "Required conflicting attribute & defined optional generate error": { 3522 Schema: map[string]*Schema{ 3523 "required_att": &Schema{ 3524 Type: TypeString, 3525 Required: true, 3526 }, 3527 "optional_att": &Schema{ 3528 Type: TypeString, 3529 Optional: true, 3530 ConflictsWith: []string{"required_att"}, 3531 }, 3532 }, 3533 3534 Config: map[string]interface{}{ 3535 "required_att": "required-val", 3536 "optional_att": "optional-val", 3537 }, 3538 3539 Err: true, 3540 Errors: []error{ 3541 fmt.Errorf(`"optional_att": conflicts with required_att ("required-val")`), 3542 }, 3543 }, 3544 3545 "Good with ValidateFunc": { 3546 Schema: map[string]*Schema{ 3547 "validate_me": &Schema{ 3548 Type: TypeString, 3549 Required: true, 3550 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3551 return 3552 }, 3553 }, 3554 }, 3555 Config: map[string]interface{}{ 3556 "validate_me": "valid", 3557 }, 3558 Err: false, 3559 }, 3560 3561 "Bad with ValidateFunc": { 3562 Schema: map[string]*Schema{ 3563 "validate_me": &Schema{ 3564 Type: TypeString, 3565 Required: true, 3566 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3567 es = append(es, fmt.Errorf("something is not right here")) 3568 return 3569 }, 3570 }, 3571 }, 3572 Config: map[string]interface{}{ 3573 "validate_me": "invalid", 3574 }, 3575 Err: true, 3576 Errors: []error{ 3577 fmt.Errorf(`something is not right here`), 3578 }, 3579 }, 3580 3581 "ValidateFunc not called when type does not match": { 3582 Schema: map[string]*Schema{ 3583 "number": &Schema{ 3584 Type: TypeInt, 3585 Required: true, 3586 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3587 t.Fatalf("Should not have gotten validate call") 3588 return 3589 }, 3590 }, 3591 }, 3592 Config: map[string]interface{}{ 3593 "number": "NaN", 3594 }, 3595 Err: true, 3596 }, 3597 3598 "ValidateFunc gets decoded type": { 3599 Schema: map[string]*Schema{ 3600 "maybe": &Schema{ 3601 Type: TypeBool, 3602 Required: true, 3603 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3604 if _, ok := v.(bool); !ok { 3605 t.Fatalf("Expected bool, got: %#v", v) 3606 } 3607 return 3608 }, 3609 }, 3610 }, 3611 Config: map[string]interface{}{ 3612 "maybe": "true", 3613 }, 3614 }, 3615 3616 "ValidateFunc is not called with a computed value": { 3617 Schema: map[string]*Schema{ 3618 "validate_me": &Schema{ 3619 Type: TypeString, 3620 Required: true, 3621 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3622 es = append(es, fmt.Errorf("something is not right here")) 3623 return 3624 }, 3625 }, 3626 }, 3627 Config: map[string]interface{}{ 3628 "validate_me": "${var.foo}", 3629 }, 3630 Vars: map[string]string{ 3631 "var.foo": config.UnknownVariableValue, 3632 }, 3633 3634 Err: false, 3635 }, 3636 } 3637 3638 for tn, tc := range cases { 3639 c, err := config.NewRawConfig(tc.Config) 3640 if err != nil { 3641 t.Fatalf("err: %s", err) 3642 } 3643 if tc.Vars != nil { 3644 vars := make(map[string]ast.Variable) 3645 for k, v := range tc.Vars { 3646 vars[k] = ast.Variable{Value: v, Type: ast.TypeString} 3647 } 3648 3649 if err := c.Interpolate(vars); err != nil { 3650 t.Fatalf("err: %s", err) 3651 } 3652 } 3653 3654 ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 3655 if (len(es) > 0) != tc.Err { 3656 if len(es) == 0 { 3657 t.Errorf("%q: no errors", tn) 3658 } 3659 3660 for _, e := range es { 3661 t.Errorf("%q: err: %s", tn, e) 3662 } 3663 3664 t.FailNow() 3665 } 3666 3667 if !reflect.DeepEqual(ws, tc.Warnings) { 3668 t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) 3669 } 3670 3671 if tc.Errors != nil { 3672 if !reflect.DeepEqual(es, tc.Errors) { 3673 t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) 3674 } 3675 } 3676 } 3677 }