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