github.com/bengesoff/terraform@v0.3.1-0.20141018223233-b25a53629922/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 { 25 Schema: map[string]*Schema{ 26 "availability_zone": &Schema{ 27 Type: TypeString, 28 Optional: true, 29 Computed: true, 30 ForceNew: true, 31 }, 32 }, 33 34 State: nil, 35 36 Config: map[string]interface{}{ 37 "availability_zone": "foo", 38 }, 39 40 Diff: &terraform.InstanceDiff{ 41 Attributes: map[string]*terraform.ResourceAttrDiff{ 42 "availability_zone": &terraform.ResourceAttrDiff{ 43 Old: "", 44 New: "foo", 45 RequiresNew: true, 46 }, 47 }, 48 }, 49 50 Err: false, 51 }, 52 53 { 54 Schema: map[string]*Schema{ 55 "availability_zone": &Schema{ 56 Type: TypeString, 57 Optional: true, 58 Computed: true, 59 ForceNew: true, 60 }, 61 }, 62 63 State: nil, 64 65 Config: map[string]interface{}{}, 66 67 Diff: &terraform.InstanceDiff{ 68 Attributes: map[string]*terraform.ResourceAttrDiff{ 69 "availability_zone": &terraform.ResourceAttrDiff{ 70 Old: "", 71 NewComputed: true, 72 RequiresNew: true, 73 }, 74 }, 75 }, 76 77 Err: false, 78 }, 79 80 { 81 Schema: map[string]*Schema{ 82 "availability_zone": &Schema{ 83 Type: TypeString, 84 Optional: true, 85 Computed: true, 86 ForceNew: true, 87 }, 88 }, 89 90 State: &terraform.InstanceState{ 91 ID: "foo", 92 }, 93 94 Config: map[string]interface{}{}, 95 96 Diff: nil, 97 98 Err: false, 99 }, 100 101 // Default 102 { 103 Schema: map[string]*Schema{ 104 "availability_zone": &Schema{ 105 Type: TypeString, 106 Optional: true, 107 Default: "foo", 108 }, 109 }, 110 111 State: nil, 112 113 Config: nil, 114 115 Diff: &terraform.InstanceDiff{ 116 Attributes: map[string]*terraform.ResourceAttrDiff{ 117 "availability_zone": &terraform.ResourceAttrDiff{ 118 Old: "", 119 New: "foo", 120 }, 121 }, 122 }, 123 124 Err: false, 125 }, 126 127 // DefaultFunc, value 128 { 129 Schema: map[string]*Schema{ 130 "availability_zone": &Schema{ 131 Type: TypeString, 132 Optional: true, 133 DefaultFunc: func() (interface{}, error) { 134 return "foo", nil 135 }, 136 }, 137 }, 138 139 State: nil, 140 141 Config: nil, 142 143 Diff: &terraform.InstanceDiff{ 144 Attributes: map[string]*terraform.ResourceAttrDiff{ 145 "availability_zone": &terraform.ResourceAttrDiff{ 146 Old: "", 147 New: "foo", 148 }, 149 }, 150 }, 151 152 Err: false, 153 }, 154 155 // DefaultFunc, configuration set 156 { 157 Schema: map[string]*Schema{ 158 "availability_zone": &Schema{ 159 Type: TypeString, 160 Optional: true, 161 DefaultFunc: func() (interface{}, error) { 162 return "foo", nil 163 }, 164 }, 165 }, 166 167 State: nil, 168 169 Config: map[string]interface{}{ 170 "availability_zone": "bar", 171 }, 172 173 Diff: &terraform.InstanceDiff{ 174 Attributes: map[string]*terraform.ResourceAttrDiff{ 175 "availability_zone": &terraform.ResourceAttrDiff{ 176 Old: "", 177 New: "bar", 178 }, 179 }, 180 }, 181 182 Err: false, 183 }, 184 185 // String with StateFunc 186 { 187 Schema: map[string]*Schema{ 188 "availability_zone": &Schema{ 189 Type: TypeString, 190 Optional: true, 191 Computed: true, 192 StateFunc: func(a interface{}) string { 193 return a.(string) + "!" 194 }, 195 }, 196 }, 197 198 State: nil, 199 200 Config: map[string]interface{}{ 201 "availability_zone": "foo", 202 }, 203 204 Diff: &terraform.InstanceDiff{ 205 Attributes: map[string]*terraform.ResourceAttrDiff{ 206 "availability_zone": &terraform.ResourceAttrDiff{ 207 Old: "", 208 New: "foo!", 209 NewExtra: "foo", 210 }, 211 }, 212 }, 213 214 Err: false, 215 }, 216 217 // Variable (just checking) 218 { 219 Schema: map[string]*Schema{ 220 "availability_zone": &Schema{ 221 Type: TypeString, 222 Optional: true, 223 }, 224 }, 225 226 State: nil, 227 228 Config: map[string]interface{}{ 229 "availability_zone": "${var.foo}", 230 }, 231 232 ConfigVariables: map[string]string{ 233 "var.foo": "bar", 234 }, 235 236 Diff: &terraform.InstanceDiff{ 237 Attributes: map[string]*terraform.ResourceAttrDiff{ 238 "availability_zone": &terraform.ResourceAttrDiff{ 239 Old: "", 240 New: "bar", 241 }, 242 }, 243 }, 244 245 Err: false, 246 }, 247 248 // Variable computed 249 { 250 Schema: map[string]*Schema{ 251 "availability_zone": &Schema{ 252 Type: TypeString, 253 Optional: true, 254 }, 255 }, 256 257 State: nil, 258 259 Config: map[string]interface{}{ 260 "availability_zone": "${var.foo}", 261 }, 262 263 ConfigVariables: map[string]string{ 264 "var.foo": config.UnknownVariableValue, 265 }, 266 267 Diff: &terraform.InstanceDiff{ 268 Attributes: map[string]*terraform.ResourceAttrDiff{ 269 "availability_zone": &terraform.ResourceAttrDiff{ 270 Old: "", 271 New: "${var.foo}", 272 }, 273 }, 274 }, 275 276 Err: false, 277 }, 278 279 /* 280 * Int decode 281 */ 282 283 { 284 Schema: map[string]*Schema{ 285 "port": &Schema{ 286 Type: TypeInt, 287 Optional: true, 288 Computed: true, 289 ForceNew: true, 290 }, 291 }, 292 293 State: nil, 294 295 Config: map[string]interface{}{ 296 "port": 27, 297 }, 298 299 Diff: &terraform.InstanceDiff{ 300 Attributes: map[string]*terraform.ResourceAttrDiff{ 301 "port": &terraform.ResourceAttrDiff{ 302 Old: "", 303 New: "27", 304 RequiresNew: true, 305 }, 306 }, 307 }, 308 309 Err: false, 310 }, 311 312 /* 313 * Bool decode 314 */ 315 316 { 317 Schema: map[string]*Schema{ 318 "port": &Schema{ 319 Type: TypeBool, 320 Optional: true, 321 Computed: true, 322 ForceNew: true, 323 }, 324 }, 325 326 State: nil, 327 328 Config: map[string]interface{}{ 329 "port": false, 330 }, 331 332 Diff: &terraform.InstanceDiff{ 333 Attributes: map[string]*terraform.ResourceAttrDiff{ 334 "port": &terraform.ResourceAttrDiff{ 335 Old: "", 336 New: "0", 337 RequiresNew: true, 338 }, 339 }, 340 }, 341 342 Err: false, 343 }, 344 345 /* 346 * List decode 347 */ 348 349 { 350 Schema: map[string]*Schema{ 351 "ports": &Schema{ 352 Type: TypeList, 353 Required: true, 354 Elem: &Schema{Type: TypeInt}, 355 }, 356 }, 357 358 State: nil, 359 360 Config: map[string]interface{}{ 361 "ports": []interface{}{1, 2, 5}, 362 }, 363 364 Diff: &terraform.InstanceDiff{ 365 Attributes: map[string]*terraform.ResourceAttrDiff{ 366 "ports.#": &terraform.ResourceAttrDiff{ 367 Old: "0", 368 New: "3", 369 }, 370 "ports.0": &terraform.ResourceAttrDiff{ 371 Old: "", 372 New: "1", 373 }, 374 "ports.1": &terraform.ResourceAttrDiff{ 375 Old: "", 376 New: "2", 377 }, 378 "ports.2": &terraform.ResourceAttrDiff{ 379 Old: "", 380 New: "5", 381 }, 382 }, 383 }, 384 385 Err: false, 386 }, 387 388 { 389 Schema: map[string]*Schema{ 390 "ports": &Schema{ 391 Type: TypeList, 392 Required: true, 393 Elem: &Schema{Type: TypeInt}, 394 }, 395 }, 396 397 State: nil, 398 399 Config: map[string]interface{}{ 400 "ports": []interface{}{1, "${var.foo}"}, 401 }, 402 403 ConfigVariables: map[string]string{ 404 "var.foo": "2" + config.InterpSplitDelim + "5", 405 }, 406 407 Diff: &terraform.InstanceDiff{ 408 Attributes: map[string]*terraform.ResourceAttrDiff{ 409 "ports.#": &terraform.ResourceAttrDiff{ 410 Old: "0", 411 New: "3", 412 }, 413 "ports.0": &terraform.ResourceAttrDiff{ 414 Old: "", 415 New: "1", 416 }, 417 "ports.1": &terraform.ResourceAttrDiff{ 418 Old: "", 419 New: "2", 420 }, 421 "ports.2": &terraform.ResourceAttrDiff{ 422 Old: "", 423 New: "5", 424 }, 425 }, 426 }, 427 428 Err: false, 429 }, 430 431 { 432 Schema: map[string]*Schema{ 433 "ports": &Schema{ 434 Type: TypeList, 435 Required: true, 436 Elem: &Schema{Type: TypeInt}, 437 }, 438 }, 439 440 State: nil, 441 442 Config: map[string]interface{}{ 443 "ports": []interface{}{1, "${var.foo}"}, 444 }, 445 446 ConfigVariables: map[string]string{ 447 "var.foo": config.UnknownVariableValue + 448 config.InterpSplitDelim + "5", 449 }, 450 451 Diff: &terraform.InstanceDiff{ 452 Attributes: map[string]*terraform.ResourceAttrDiff{ 453 "ports.#": &terraform.ResourceAttrDiff{ 454 Old: "0", 455 New: "", 456 NewComputed: true, 457 }, 458 }, 459 }, 460 461 Err: false, 462 }, 463 464 { 465 Schema: map[string]*Schema{ 466 "ports": &Schema{ 467 Type: TypeList, 468 Required: true, 469 Elem: &Schema{Type: TypeInt}, 470 }, 471 }, 472 473 State: &terraform.InstanceState{ 474 Attributes: map[string]string{ 475 "ports.#": "3", 476 "ports.0": "1", 477 "ports.1": "2", 478 "ports.2": "5", 479 }, 480 }, 481 482 Config: map[string]interface{}{ 483 "ports": []interface{}{1, 2, 5}, 484 }, 485 486 Diff: nil, 487 488 Err: false, 489 }, 490 491 { 492 Schema: map[string]*Schema{ 493 "ports": &Schema{ 494 Type: TypeList, 495 Required: true, 496 Elem: &Schema{Type: TypeInt}, 497 }, 498 }, 499 500 State: &terraform.InstanceState{ 501 Attributes: map[string]string{ 502 "ports.#": "2", 503 "ports.0": "1", 504 "ports.1": "2", 505 }, 506 }, 507 508 Config: map[string]interface{}{ 509 "ports": []interface{}{1, 2, 5}, 510 }, 511 512 Diff: &terraform.InstanceDiff{ 513 Attributes: map[string]*terraform.ResourceAttrDiff{ 514 "ports.#": &terraform.ResourceAttrDiff{ 515 Old: "2", 516 New: "3", 517 }, 518 "ports.2": &terraform.ResourceAttrDiff{ 519 Old: "", 520 New: "5", 521 }, 522 }, 523 }, 524 525 Err: false, 526 }, 527 528 { 529 Schema: map[string]*Schema{ 530 "ports": &Schema{ 531 Type: TypeList, 532 Required: true, 533 Elem: &Schema{Type: TypeInt}, 534 ForceNew: true, 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 RequiresNew: true, 550 }, 551 "ports.0": &terraform.ResourceAttrDiff{ 552 Old: "", 553 New: "1", 554 RequiresNew: true, 555 }, 556 "ports.1": &terraform.ResourceAttrDiff{ 557 Old: "", 558 New: "2", 559 RequiresNew: true, 560 }, 561 "ports.2": &terraform.ResourceAttrDiff{ 562 Old: "", 563 New: "5", 564 RequiresNew: true, 565 }, 566 }, 567 }, 568 569 Err: false, 570 }, 571 572 { 573 Schema: map[string]*Schema{ 574 "ports": &Schema{ 575 Type: TypeList, 576 Optional: true, 577 Computed: true, 578 Elem: &Schema{Type: TypeInt}, 579 }, 580 }, 581 582 State: nil, 583 584 Config: map[string]interface{}{}, 585 586 Diff: &terraform.InstanceDiff{ 587 Attributes: map[string]*terraform.ResourceAttrDiff{ 588 "ports.#": &terraform.ResourceAttrDiff{ 589 Old: "", 590 NewComputed: true, 591 }, 592 }, 593 }, 594 595 Err: false, 596 }, 597 598 /* 599 * Set 600 */ 601 602 { 603 Schema: map[string]*Schema{ 604 "ports": &Schema{ 605 Type: TypeSet, 606 Required: true, 607 Elem: &Schema{Type: TypeInt}, 608 Set: func(a interface{}) int { 609 return a.(int) 610 }, 611 }, 612 }, 613 614 State: nil, 615 616 Config: map[string]interface{}{ 617 "ports": []interface{}{5, 2, 1}, 618 }, 619 620 Diff: &terraform.InstanceDiff{ 621 Attributes: map[string]*terraform.ResourceAttrDiff{ 622 "ports.#": &terraform.ResourceAttrDiff{ 623 Old: "0", 624 New: "3", 625 }, 626 "ports.0": &terraform.ResourceAttrDiff{ 627 Old: "", 628 New: "1", 629 }, 630 "ports.1": &terraform.ResourceAttrDiff{ 631 Old: "", 632 New: "2", 633 }, 634 "ports.2": &terraform.ResourceAttrDiff{ 635 Old: "", 636 New: "5", 637 }, 638 }, 639 }, 640 641 Err: false, 642 }, 643 644 { 645 Schema: map[string]*Schema{ 646 "ports": &Schema{ 647 Type: TypeSet, 648 Computed: true, 649 Required: true, 650 Elem: &Schema{Type: TypeInt}, 651 Set: func(a interface{}) int { 652 return a.(int) 653 }, 654 }, 655 }, 656 657 State: &terraform.InstanceState{ 658 Attributes: map[string]string{ 659 "ports.#": "0", 660 }, 661 }, 662 663 Config: nil, 664 665 Diff: nil, 666 667 Err: false, 668 }, 669 670 { 671 Schema: map[string]*Schema{ 672 "ports": &Schema{ 673 Type: TypeSet, 674 Optional: true, 675 Computed: true, 676 Elem: &Schema{Type: TypeInt}, 677 Set: func(a interface{}) int { 678 return a.(int) 679 }, 680 }, 681 }, 682 683 State: nil, 684 685 Config: nil, 686 687 Diff: &terraform.InstanceDiff{ 688 Attributes: map[string]*terraform.ResourceAttrDiff{ 689 "ports.#": &terraform.ResourceAttrDiff{ 690 Old: "", 691 NewComputed: true, 692 }, 693 }, 694 }, 695 696 Err: false, 697 }, 698 699 { 700 Schema: map[string]*Schema{ 701 "ports": &Schema{ 702 Type: TypeSet, 703 Required: true, 704 Elem: &Schema{Type: TypeInt}, 705 Set: func(a interface{}) int { 706 return a.(int) 707 }, 708 }, 709 }, 710 711 State: nil, 712 713 Config: map[string]interface{}{ 714 "ports": []interface{}{"${var.foo}", 1}, 715 }, 716 717 ConfigVariables: map[string]string{ 718 "var.foo": "2" + config.InterpSplitDelim + "5", 719 }, 720 721 Diff: &terraform.InstanceDiff{ 722 Attributes: map[string]*terraform.ResourceAttrDiff{ 723 "ports.#": &terraform.ResourceAttrDiff{ 724 Old: "0", 725 New: "3", 726 }, 727 "ports.0": &terraform.ResourceAttrDiff{ 728 Old: "", 729 New: "1", 730 }, 731 "ports.1": &terraform.ResourceAttrDiff{ 732 Old: "", 733 New: "2", 734 }, 735 "ports.2": &terraform.ResourceAttrDiff{ 736 Old: "", 737 New: "5", 738 }, 739 }, 740 }, 741 742 Err: false, 743 }, 744 745 { 746 Schema: map[string]*Schema{ 747 "ports": &Schema{ 748 Type: TypeSet, 749 Required: 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: map[string]interface{}{ 760 "ports": []interface{}{1, "${var.foo}"}, 761 }, 762 763 ConfigVariables: map[string]string{ 764 "var.foo": config.UnknownVariableValue + 765 config.InterpSplitDelim + "5", 766 }, 767 768 Diff: &terraform.InstanceDiff{ 769 Attributes: map[string]*terraform.ResourceAttrDiff{ 770 "ports.#": &terraform.ResourceAttrDiff{ 771 Old: "0", 772 New: "", 773 NewComputed: true, 774 }, 775 }, 776 }, 777 778 Err: false, 779 }, 780 781 { 782 Schema: map[string]*Schema{ 783 "ports": &Schema{ 784 Type: TypeSet, 785 Required: true, 786 Elem: &Schema{Type: TypeInt}, 787 Set: func(a interface{}) int { 788 return a.(int) 789 }, 790 }, 791 }, 792 793 State: &terraform.InstanceState{ 794 Attributes: map[string]string{ 795 "ports.#": "2", 796 "ports.0": "2", 797 "ports.1": "1", 798 }, 799 }, 800 801 Config: map[string]interface{}{ 802 "ports": []interface{}{5, 2, 1}, 803 }, 804 805 Diff: &terraform.InstanceDiff{ 806 Attributes: map[string]*terraform.ResourceAttrDiff{ 807 "ports.#": &terraform.ResourceAttrDiff{ 808 Old: "2", 809 New: "3", 810 }, 811 "ports.2": &terraform.ResourceAttrDiff{ 812 Old: "", 813 New: "5", 814 }, 815 }, 816 }, 817 818 Err: false, 819 }, 820 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: &terraform.InstanceState{ 834 Attributes: map[string]string{ 835 "ports.#": "2", 836 "ports.0": "2", 837 "ports.1": "1", 838 }, 839 }, 840 841 Config: map[string]interface{}{}, 842 843 Diff: &terraform.InstanceDiff{ 844 Attributes: map[string]*terraform.ResourceAttrDiff{ 845 "ports.#": &terraform.ResourceAttrDiff{ 846 Old: "2", 847 New: "0", 848 }, 849 "ports.0": &terraform.ResourceAttrDiff{ 850 Old: "1", 851 NewRemoved: true, 852 }, 853 "ports.1": &terraform.ResourceAttrDiff{ 854 Old: "2", 855 NewRemoved: true, 856 }, 857 }, 858 }, 859 860 Err: false, 861 }, 862 863 { 864 Schema: map[string]*Schema{ 865 "ports": &Schema{ 866 Type: TypeSet, 867 Optional: true, 868 Computed: true, 869 Elem: &Schema{Type: TypeInt}, 870 Set: func(v interface{}) int { return v.(int) }, 871 }, 872 }, 873 874 State: &terraform.InstanceState{ 875 Attributes: map[string]string{ 876 "availability_zone": "bar", 877 "ports.#": "1", 878 "ports.0": "80", 879 }, 880 }, 881 882 Config: map[string]interface{}{}, 883 884 Diff: nil, 885 886 Err: false, 887 }, 888 889 { 890 Schema: map[string]*Schema{ 891 "ingress": &Schema{ 892 Type: TypeSet, 893 Required: true, 894 Elem: &Resource{ 895 Schema: map[string]*Schema{ 896 "ports": &Schema{ 897 Type: TypeList, 898 Optional: true, 899 Elem: &Schema{Type: TypeInt}, 900 }, 901 }, 902 }, 903 Set: func(v interface{}) int { 904 m := v.(map[string]interface{}) 905 ps := m["ports"].([]interface{}) 906 result := 0 907 for _, p := range ps { 908 result += p.(int) 909 } 910 return result 911 }, 912 }, 913 }, 914 915 State: &terraform.InstanceState{ 916 Attributes: map[string]string{ 917 "ingress.#": "2", 918 "ingress.0.ports.#": "1", 919 "ingress.0.ports.0": "80", 920 "ingress.1.ports.#": "1", 921 "ingress.1.ports.0": "443", 922 }, 923 }, 924 925 Config: map[string]interface{}{ 926 "ingress": []interface{}{ 927 map[string]interface{}{ 928 "ports": []interface{}{443}, 929 }, 930 map[string]interface{}{ 931 "ports": []interface{}{80}, 932 }, 933 }, 934 }, 935 936 Diff: nil, 937 938 Err: false, 939 }, 940 941 /* 942 * List of structure decode 943 */ 944 945 { 946 Schema: map[string]*Schema{ 947 "ingress": &Schema{ 948 Type: TypeList, 949 Required: true, 950 Elem: &Resource{ 951 Schema: map[string]*Schema{ 952 "from": &Schema{ 953 Type: TypeInt, 954 Required: true, 955 }, 956 }, 957 }, 958 }, 959 }, 960 961 State: nil, 962 963 Config: map[string]interface{}{ 964 "ingress": []interface{}{ 965 map[string]interface{}{ 966 "from": 8080, 967 }, 968 }, 969 }, 970 971 Diff: &terraform.InstanceDiff{ 972 Attributes: map[string]*terraform.ResourceAttrDiff{ 973 "ingress.#": &terraform.ResourceAttrDiff{ 974 Old: "0", 975 New: "1", 976 }, 977 "ingress.0.from": &terraform.ResourceAttrDiff{ 978 Old: "", 979 New: "8080", 980 }, 981 }, 982 }, 983 984 Err: false, 985 }, 986 987 /* 988 * ComputedWhen 989 */ 990 991 { 992 Schema: map[string]*Schema{ 993 "availability_zone": &Schema{ 994 Type: TypeString, 995 Computed: true, 996 ComputedWhen: []string{"port"}, 997 }, 998 999 "port": &Schema{ 1000 Type: TypeInt, 1001 Optional: true, 1002 }, 1003 }, 1004 1005 State: &terraform.InstanceState{ 1006 Attributes: map[string]string{ 1007 "availability_zone": "foo", 1008 "port": "80", 1009 }, 1010 }, 1011 1012 Config: map[string]interface{}{ 1013 "port": 80, 1014 }, 1015 1016 Diff: nil, 1017 1018 Err: false, 1019 }, 1020 1021 { 1022 Schema: map[string]*Schema{ 1023 "availability_zone": &Schema{ 1024 Type: TypeString, 1025 Computed: true, 1026 ComputedWhen: []string{"port"}, 1027 }, 1028 1029 "port": &Schema{ 1030 Type: TypeInt, 1031 Optional: true, 1032 }, 1033 }, 1034 1035 State: &terraform.InstanceState{ 1036 Attributes: map[string]string{ 1037 "port": "80", 1038 }, 1039 }, 1040 1041 Config: map[string]interface{}{ 1042 "port": 80, 1043 }, 1044 1045 Diff: &terraform.InstanceDiff{ 1046 Attributes: map[string]*terraform.ResourceAttrDiff{ 1047 "availability_zone": &terraform.ResourceAttrDiff{ 1048 NewComputed: true, 1049 }, 1050 }, 1051 }, 1052 1053 Err: false, 1054 }, 1055 1056 /* TODO 1057 { 1058 Schema: map[string]*Schema{ 1059 "availability_zone": &Schema{ 1060 Type: TypeString, 1061 Computed: true, 1062 ComputedWhen: []string{"port"}, 1063 }, 1064 1065 "port": &Schema{ 1066 Type: TypeInt, 1067 Optional: true, 1068 }, 1069 }, 1070 1071 State: &terraform.InstanceState{ 1072 Attributes: map[string]string{ 1073 "availability_zone": "foo", 1074 "port": "80", 1075 }, 1076 }, 1077 1078 Config: map[string]interface{}{ 1079 "port": 8080, 1080 }, 1081 1082 Diff: &terraform.ResourceDiff{ 1083 Attributes: map[string]*terraform.ResourceAttrDiff{ 1084 "availability_zone": &terraform.ResourceAttrDiff{ 1085 Old: "foo", 1086 NewComputed: true, 1087 }, 1088 "port": &terraform.ResourceAttrDiff{ 1089 Old: "80", 1090 New: "8080", 1091 }, 1092 }, 1093 }, 1094 1095 Err: false, 1096 }, 1097 */ 1098 1099 /* 1100 * Maps 1101 */ 1102 1103 { 1104 Schema: map[string]*Schema{ 1105 "config_vars": &Schema{ 1106 Type: TypeMap, 1107 }, 1108 }, 1109 1110 State: nil, 1111 1112 Config: map[string]interface{}{ 1113 "config_vars": []interface{}{ 1114 map[string]interface{}{ 1115 "bar": "baz", 1116 }, 1117 }, 1118 }, 1119 1120 Diff: &terraform.InstanceDiff{ 1121 Attributes: map[string]*terraform.ResourceAttrDiff{ 1122 "config_vars.bar": &terraform.ResourceAttrDiff{ 1123 Old: "", 1124 New: "baz", 1125 }, 1126 }, 1127 }, 1128 1129 Err: false, 1130 }, 1131 1132 { 1133 Schema: map[string]*Schema{ 1134 "config_vars": &Schema{ 1135 Type: TypeMap, 1136 }, 1137 }, 1138 1139 State: &terraform.InstanceState{ 1140 Attributes: map[string]string{ 1141 "config_vars.foo": "bar", 1142 }, 1143 }, 1144 1145 Config: map[string]interface{}{ 1146 "config_vars": []interface{}{ 1147 map[string]interface{}{ 1148 "bar": "baz", 1149 }, 1150 }, 1151 }, 1152 1153 Diff: &terraform.InstanceDiff{ 1154 Attributes: map[string]*terraform.ResourceAttrDiff{ 1155 "config_vars.foo": &terraform.ResourceAttrDiff{ 1156 Old: "bar", 1157 NewRemoved: true, 1158 }, 1159 "config_vars.bar": &terraform.ResourceAttrDiff{ 1160 Old: "", 1161 New: "baz", 1162 }, 1163 }, 1164 }, 1165 1166 Err: false, 1167 }, 1168 1169 { 1170 Schema: map[string]*Schema{ 1171 "config_vars": &Schema{ 1172 Type: TypeList, 1173 Elem: &Schema{Type: TypeMap}, 1174 }, 1175 }, 1176 1177 State: &terraform.InstanceState{ 1178 Attributes: map[string]string{ 1179 "config_vars.#": "1", 1180 "config_vars.0.foo": "bar", 1181 }, 1182 }, 1183 1184 Config: map[string]interface{}{ 1185 "config_vars": []interface{}{ 1186 map[string]interface{}{ 1187 "bar": "baz", 1188 }, 1189 }, 1190 }, 1191 1192 Diff: &terraform.InstanceDiff{ 1193 Attributes: map[string]*terraform.ResourceAttrDiff{ 1194 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1195 Old: "bar", 1196 NewRemoved: true, 1197 }, 1198 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1199 Old: "", 1200 New: "baz", 1201 }, 1202 }, 1203 }, 1204 1205 Err: false, 1206 }, 1207 1208 { 1209 Schema: map[string]*Schema{ 1210 "config_vars": &Schema{ 1211 Type: TypeList, 1212 Elem: &Schema{Type: TypeMap}, 1213 }, 1214 }, 1215 1216 State: &terraform.InstanceState{ 1217 Attributes: map[string]string{ 1218 "config_vars.#": "1", 1219 "config_vars.0.foo": "bar", 1220 "config_vars.0.bar": "baz", 1221 }, 1222 }, 1223 1224 Config: map[string]interface{}{}, 1225 1226 Diff: &terraform.InstanceDiff{ 1227 Attributes: map[string]*terraform.ResourceAttrDiff{ 1228 "config_vars.#": &terraform.ResourceAttrDiff{ 1229 Old: "1", 1230 New: "0", 1231 }, 1232 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1233 Old: "bar", 1234 NewRemoved: true, 1235 }, 1236 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1237 Old: "baz", 1238 NewRemoved: true, 1239 }, 1240 }, 1241 }, 1242 1243 Err: false, 1244 }, 1245 1246 /* 1247 * ForceNews 1248 */ 1249 1250 { 1251 Schema: map[string]*Schema{ 1252 "availability_zone": &Schema{ 1253 Type: TypeString, 1254 Optional: true, 1255 ForceNew: true, 1256 }, 1257 1258 "address": &Schema{ 1259 Type: TypeString, 1260 Optional: true, 1261 Computed: true, 1262 }, 1263 }, 1264 1265 State: &terraform.InstanceState{ 1266 Attributes: map[string]string{ 1267 "availability_zone": "bar", 1268 "address": "foo", 1269 }, 1270 }, 1271 1272 Config: map[string]interface{}{ 1273 "availability_zone": "foo", 1274 }, 1275 1276 Diff: &terraform.InstanceDiff{ 1277 Attributes: map[string]*terraform.ResourceAttrDiff{ 1278 "availability_zone": &terraform.ResourceAttrDiff{ 1279 Old: "bar", 1280 New: "foo", 1281 RequiresNew: true, 1282 }, 1283 1284 "address": &terraform.ResourceAttrDiff{ 1285 Old: "foo", 1286 New: "", 1287 NewComputed: true, 1288 }, 1289 }, 1290 }, 1291 1292 Err: false, 1293 }, 1294 1295 // Set 1296 { 1297 Schema: map[string]*Schema{ 1298 "availability_zone": &Schema{ 1299 Type: TypeString, 1300 Optional: true, 1301 ForceNew: true, 1302 }, 1303 1304 "ports": &Schema{ 1305 Type: TypeSet, 1306 Optional: true, 1307 Computed: true, 1308 Elem: &Schema{Type: TypeInt}, 1309 Set: func(v interface{}) int { return v.(int) }, 1310 }, 1311 }, 1312 1313 State: &terraform.InstanceState{ 1314 Attributes: map[string]string{ 1315 "availability_zone": "bar", 1316 "ports.#": "1", 1317 "ports.0": "80", 1318 }, 1319 }, 1320 1321 Config: map[string]interface{}{ 1322 "availability_zone": "foo", 1323 }, 1324 1325 Diff: &terraform.InstanceDiff{ 1326 Attributes: map[string]*terraform.ResourceAttrDiff{ 1327 "availability_zone": &terraform.ResourceAttrDiff{ 1328 Old: "bar", 1329 New: "foo", 1330 RequiresNew: true, 1331 }, 1332 1333 "ports.#": &terraform.ResourceAttrDiff{ 1334 Old: "1", 1335 New: "", 1336 NewComputed: true, 1337 }, 1338 }, 1339 }, 1340 1341 Err: false, 1342 }, 1343 } 1344 1345 for i, tc := range cases { 1346 c, err := config.NewRawConfig(tc.Config) 1347 if err != nil { 1348 t.Fatalf("err: %s", err) 1349 } 1350 1351 if len(tc.ConfigVariables) > 0 { 1352 if err := c.Interpolate(tc.ConfigVariables); err != nil { 1353 t.Fatalf("err: %s", err) 1354 } 1355 } 1356 1357 d, err := schemaMap(tc.Schema).Diff( 1358 tc.State, terraform.NewResourceConfig(c)) 1359 if (err != nil) != tc.Err { 1360 t.Fatalf("#%d err: %s", i, err) 1361 } 1362 1363 if !reflect.DeepEqual(tc.Diff, d) { 1364 t.Fatalf("#%d: bad:\n\n%#v", i, d) 1365 } 1366 } 1367 } 1368 1369 func TestSchemaMap_Input(t *testing.T) { 1370 cases := []struct { 1371 Schema map[string]*Schema 1372 Config map[string]interface{} 1373 Input map[string]string 1374 Result map[string]interface{} 1375 Err bool 1376 }{ 1377 /* 1378 * String decode 1379 */ 1380 1381 { 1382 Schema: map[string]*Schema{ 1383 "availability_zone": &Schema{ 1384 Type: TypeString, 1385 Optional: true, 1386 }, 1387 }, 1388 1389 Input: map[string]string{ 1390 "availability_zone": "foo", 1391 }, 1392 1393 Result: map[string]interface{}{ 1394 "availability_zone": "foo", 1395 }, 1396 1397 Err: false, 1398 }, 1399 1400 { 1401 Schema: map[string]*Schema{ 1402 "availability_zone": &Schema{ 1403 Type: TypeString, 1404 Optional: true, 1405 }, 1406 }, 1407 1408 Config: map[string]interface{}{ 1409 "availability_zone": "bar", 1410 }, 1411 1412 Input: map[string]string{ 1413 "availability_zone": "foo", 1414 }, 1415 1416 Result: map[string]interface{}{}, 1417 1418 Err: false, 1419 }, 1420 1421 { 1422 Schema: map[string]*Schema{ 1423 "availability_zone": &Schema{ 1424 Type: TypeString, 1425 Default: "foo", 1426 Optional: true, 1427 }, 1428 }, 1429 1430 Input: map[string]string{ 1431 "availability_zone": "bar", 1432 }, 1433 1434 Result: map[string]interface{}{}, 1435 1436 Err: false, 1437 }, 1438 1439 { 1440 Schema: map[string]*Schema{ 1441 "availability_zone": &Schema{ 1442 Type: TypeString, 1443 DefaultFunc: func() (interface{}, error) { 1444 return "foo", nil 1445 }, 1446 Optional: true, 1447 }, 1448 }, 1449 1450 Input: map[string]string{ 1451 "availability_zone": "bar", 1452 }, 1453 1454 Result: map[string]interface{}{}, 1455 1456 Err: false, 1457 }, 1458 1459 { 1460 Schema: map[string]*Schema{ 1461 "availability_zone": &Schema{ 1462 Type: TypeString, 1463 DefaultFunc: func() (interface{}, error) { 1464 return nil, nil 1465 }, 1466 Optional: true, 1467 }, 1468 }, 1469 1470 Input: map[string]string{ 1471 "availability_zone": "bar", 1472 }, 1473 1474 Result: map[string]interface{}{ 1475 "availability_zone": "bar", 1476 }, 1477 1478 Err: false, 1479 }, 1480 } 1481 1482 for i, tc := range cases { 1483 if tc.Config == nil { 1484 tc.Config = make(map[string]interface{}) 1485 } 1486 1487 c, err := config.NewRawConfig(tc.Config) 1488 if err != nil { 1489 t.Fatalf("err: %s", err) 1490 } 1491 1492 input := new(terraform.MockUIInput) 1493 input.InputReturnMap = tc.Input 1494 1495 rc := terraform.NewResourceConfig(c) 1496 rc.Config = make(map[string]interface{}) 1497 1498 actual, err := schemaMap(tc.Schema).Input(input, rc) 1499 if (err != nil) != tc.Err { 1500 t.Fatalf("#%d err: %s", i, err) 1501 } 1502 1503 if !reflect.DeepEqual(tc.Result, actual.Config) { 1504 t.Fatalf("#%d: bad:\n\n%#v", i, actual.Config) 1505 } 1506 } 1507 } 1508 1509 func TestSchemaMap_InternalValidate(t *testing.T) { 1510 cases := []struct { 1511 In map[string]*Schema 1512 Err bool 1513 }{ 1514 { 1515 nil, 1516 false, 1517 }, 1518 1519 // No optional and no required 1520 { 1521 map[string]*Schema{ 1522 "foo": &Schema{ 1523 Type: TypeInt, 1524 Optional: true, 1525 Required: true, 1526 }, 1527 }, 1528 true, 1529 }, 1530 1531 // No optional and no required 1532 { 1533 map[string]*Schema{ 1534 "foo": &Schema{ 1535 Type: TypeInt, 1536 }, 1537 }, 1538 true, 1539 }, 1540 1541 // Missing Type 1542 { 1543 map[string]*Schema{ 1544 "foo": &Schema{ 1545 Required: true, 1546 }, 1547 }, 1548 true, 1549 }, 1550 1551 // Required but computed 1552 { 1553 map[string]*Schema{ 1554 "foo": &Schema{ 1555 Type: TypeInt, 1556 Required: true, 1557 Computed: true, 1558 }, 1559 }, 1560 true, 1561 }, 1562 1563 // Looks good 1564 { 1565 map[string]*Schema{ 1566 "foo": &Schema{ 1567 Type: TypeString, 1568 Required: true, 1569 }, 1570 }, 1571 false, 1572 }, 1573 1574 // Computed but has default 1575 { 1576 map[string]*Schema{ 1577 "foo": &Schema{ 1578 Type: TypeInt, 1579 Optional: true, 1580 Computed: true, 1581 Default: "foo", 1582 }, 1583 }, 1584 true, 1585 }, 1586 1587 // Required but has default 1588 { 1589 map[string]*Schema{ 1590 "foo": &Schema{ 1591 Type: TypeInt, 1592 Optional: true, 1593 Required: true, 1594 Default: "foo", 1595 }, 1596 }, 1597 true, 1598 }, 1599 1600 // List element not set 1601 { 1602 map[string]*Schema{ 1603 "foo": &Schema{ 1604 Type: TypeList, 1605 }, 1606 }, 1607 true, 1608 }, 1609 1610 // List default 1611 { 1612 map[string]*Schema{ 1613 "foo": &Schema{ 1614 Type: TypeList, 1615 Elem: &Schema{Type: TypeInt}, 1616 Default: "foo", 1617 }, 1618 }, 1619 true, 1620 }, 1621 1622 // List element computed 1623 { 1624 map[string]*Schema{ 1625 "foo": &Schema{ 1626 Type: TypeList, 1627 Optional: true, 1628 Elem: &Schema{ 1629 Type: TypeInt, 1630 Computed: true, 1631 }, 1632 }, 1633 }, 1634 true, 1635 }, 1636 1637 // List element with Set set 1638 { 1639 map[string]*Schema{ 1640 "foo": &Schema{ 1641 Type: TypeList, 1642 Elem: &Schema{Type: TypeInt}, 1643 Set: func(interface{}) int { return 0 }, 1644 Optional: true, 1645 }, 1646 }, 1647 true, 1648 }, 1649 1650 // Set element with no Set set 1651 { 1652 map[string]*Schema{ 1653 "foo": &Schema{ 1654 Type: TypeSet, 1655 Elem: &Schema{Type: TypeInt}, 1656 Optional: true, 1657 }, 1658 }, 1659 true, 1660 }, 1661 1662 // Required but computed 1663 { 1664 map[string]*Schema{ 1665 "foo": &Schema{ 1666 Type: TypeInt, 1667 Required: true, 1668 ComputedWhen: []string{"foo"}, 1669 }, 1670 }, 1671 true, 1672 }, 1673 1674 // Sub-resource invalid 1675 { 1676 map[string]*Schema{ 1677 "foo": &Schema{ 1678 Type: TypeList, 1679 Optional: true, 1680 Elem: &Resource{ 1681 Schema: map[string]*Schema{ 1682 "foo": new(Schema), 1683 }, 1684 }, 1685 }, 1686 }, 1687 true, 1688 }, 1689 1690 // Sub-resource valid 1691 { 1692 map[string]*Schema{ 1693 "foo": &Schema{ 1694 Type: TypeList, 1695 Optional: true, 1696 Elem: &Resource{ 1697 Schema: map[string]*Schema{ 1698 "foo": &Schema{ 1699 Type: TypeInt, 1700 Optional: true, 1701 }, 1702 }, 1703 }, 1704 }, 1705 }, 1706 false, 1707 }, 1708 } 1709 1710 for i, tc := range cases { 1711 err := schemaMap(tc.In).InternalValidate() 1712 if (err != nil) != tc.Err { 1713 t.Fatalf("%d: bad: %s\n\n%#v", i, err, tc.In) 1714 } 1715 } 1716 1717 } 1718 1719 func TestSchemaMap_Validate(t *testing.T) { 1720 cases := []struct { 1721 Schema map[string]*Schema 1722 Config map[string]interface{} 1723 Vars map[string]string 1724 Warn bool 1725 Err bool 1726 }{ 1727 // Good 1728 { 1729 Schema: map[string]*Schema{ 1730 "availability_zone": &Schema{ 1731 Type: TypeString, 1732 Optional: true, 1733 Computed: true, 1734 ForceNew: true, 1735 }, 1736 }, 1737 1738 Config: map[string]interface{}{ 1739 "availability_zone": "foo", 1740 }, 1741 }, 1742 1743 // Good, because the var is not set and that error will come elsewhere 1744 { 1745 Schema: map[string]*Schema{ 1746 "size": &Schema{ 1747 Type: TypeInt, 1748 Required: true, 1749 }, 1750 }, 1751 1752 Config: map[string]interface{}{ 1753 "size": "${var.foo}", 1754 }, 1755 1756 Vars: map[string]string{ 1757 "var.foo": config.UnknownVariableValue, 1758 }, 1759 }, 1760 1761 // Required field not set 1762 { 1763 Schema: map[string]*Schema{ 1764 "availability_zone": &Schema{ 1765 Type: TypeString, 1766 Required: true, 1767 }, 1768 }, 1769 1770 Config: map[string]interface{}{}, 1771 1772 Err: true, 1773 }, 1774 1775 // Invalid type 1776 { 1777 Schema: map[string]*Schema{ 1778 "port": &Schema{ 1779 Type: TypeInt, 1780 Required: true, 1781 }, 1782 }, 1783 1784 Config: map[string]interface{}{ 1785 "port": "I am invalid", 1786 }, 1787 1788 Err: true, 1789 }, 1790 1791 // Bad type, interpolated 1792 { 1793 Schema: map[string]*Schema{ 1794 "size": &Schema{ 1795 Type: TypeInt, 1796 Required: true, 1797 }, 1798 }, 1799 1800 Config: map[string]interface{}{ 1801 "size": "${var.foo}", 1802 }, 1803 1804 Vars: map[string]string{ 1805 "var.foo": "nope", 1806 }, 1807 1808 Err: true, 1809 }, 1810 1811 // Required but has DefaultFunc 1812 { 1813 Schema: map[string]*Schema{ 1814 "availability_zone": &Schema{ 1815 Type: TypeString, 1816 Required: true, 1817 DefaultFunc: func() (interface{}, error) { 1818 return "foo", nil 1819 }, 1820 }, 1821 }, 1822 1823 Config: nil, 1824 }, 1825 1826 // Required but has DefaultFunc return nil 1827 { 1828 Schema: map[string]*Schema{ 1829 "availability_zone": &Schema{ 1830 Type: TypeString, 1831 Required: true, 1832 DefaultFunc: func() (interface{}, error) { 1833 return nil, nil 1834 }, 1835 }, 1836 }, 1837 1838 Config: nil, 1839 1840 Err: true, 1841 }, 1842 1843 // Optional sub-resource 1844 { 1845 Schema: map[string]*Schema{ 1846 "ingress": &Schema{ 1847 Type: TypeList, 1848 Elem: &Resource{ 1849 Schema: map[string]*Schema{ 1850 "from": &Schema{ 1851 Type: TypeInt, 1852 Required: true, 1853 }, 1854 }, 1855 }, 1856 }, 1857 }, 1858 1859 Config: map[string]interface{}{}, 1860 1861 Err: false, 1862 }, 1863 1864 // Not a list 1865 { 1866 Schema: map[string]*Schema{ 1867 "ingress": &Schema{ 1868 Type: TypeList, 1869 Elem: &Resource{ 1870 Schema: map[string]*Schema{ 1871 "from": &Schema{ 1872 Type: TypeInt, 1873 Required: true, 1874 }, 1875 }, 1876 }, 1877 }, 1878 }, 1879 1880 Config: map[string]interface{}{ 1881 "ingress": "foo", 1882 }, 1883 1884 Err: true, 1885 }, 1886 1887 // Required sub-resource field 1888 { 1889 Schema: map[string]*Schema{ 1890 "ingress": &Schema{ 1891 Type: TypeList, 1892 Elem: &Resource{ 1893 Schema: map[string]*Schema{ 1894 "from": &Schema{ 1895 Type: TypeInt, 1896 Required: true, 1897 }, 1898 }, 1899 }, 1900 }, 1901 }, 1902 1903 Config: map[string]interface{}{ 1904 "ingress": []interface{}{ 1905 map[string]interface{}{}, 1906 }, 1907 }, 1908 1909 Err: true, 1910 }, 1911 1912 // Good sub-resource 1913 { 1914 Schema: map[string]*Schema{ 1915 "ingress": &Schema{ 1916 Type: TypeList, 1917 Optional: true, 1918 Elem: &Resource{ 1919 Schema: map[string]*Schema{ 1920 "from": &Schema{ 1921 Type: TypeInt, 1922 Required: true, 1923 }, 1924 }, 1925 }, 1926 }, 1927 }, 1928 1929 Config: map[string]interface{}{ 1930 "ingress": []interface{}{ 1931 map[string]interface{}{ 1932 "from": 80, 1933 }, 1934 }, 1935 }, 1936 1937 Err: false, 1938 }, 1939 1940 // Invalid/unknown field 1941 { 1942 Schema: map[string]*Schema{ 1943 "availability_zone": &Schema{ 1944 Type: TypeString, 1945 Optional: true, 1946 Computed: true, 1947 ForceNew: true, 1948 }, 1949 }, 1950 1951 Config: map[string]interface{}{ 1952 "foo": "bar", 1953 }, 1954 1955 Err: true, 1956 }, 1957 1958 // Computed field set 1959 { 1960 Schema: map[string]*Schema{ 1961 "availability_zone": &Schema{ 1962 Type: TypeString, 1963 Computed: true, 1964 }, 1965 }, 1966 1967 Config: map[string]interface{}{ 1968 "availability_zone": "bar", 1969 }, 1970 1971 Err: true, 1972 }, 1973 1974 // Not a set 1975 { 1976 Schema: map[string]*Schema{ 1977 "ports": &Schema{ 1978 Type: TypeSet, 1979 Required: true, 1980 Elem: &Schema{Type: TypeInt}, 1981 Set: func(a interface{}) int { 1982 return a.(int) 1983 }, 1984 }, 1985 }, 1986 1987 Config: map[string]interface{}{ 1988 "ports": "foo", 1989 }, 1990 1991 Err: true, 1992 }, 1993 } 1994 1995 for i, tc := range cases { 1996 c, err := config.NewRawConfig(tc.Config) 1997 if err != nil { 1998 t.Fatalf("err: %s", err) 1999 } 2000 if tc.Vars != nil { 2001 if err := c.Interpolate(tc.Vars); err != nil { 2002 t.Fatalf("err: %s", err) 2003 } 2004 } 2005 2006 ws, es := schemaMap(tc.Schema).Validate(terraform.NewResourceConfig(c)) 2007 if (len(es) > 0) != tc.Err { 2008 if len(es) == 0 { 2009 t.Errorf("%d: no errors", i) 2010 } 2011 2012 for _, e := range es { 2013 t.Errorf("%d: err: %s", i, e) 2014 } 2015 2016 t.FailNow() 2017 } 2018 2019 if (len(ws) > 0) != tc.Warn { 2020 t.Fatalf("%d: ws: %#v", i, ws) 2021 } 2022 } 2023 }