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