github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/legacy/helper/schema/schema_test.go (about) 1 package schema 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "os" 8 "reflect" 9 "sort" 10 "strconv" 11 "strings" 12 "testing" 13 14 "github.com/hashicorp/terraform/internal/configs/hcl2shim" 15 "github.com/hashicorp/terraform/internal/legacy/helper/hashcode" 16 "github.com/hashicorp/terraform/internal/legacy/terraform" 17 ) 18 19 func TestEnvDefaultFunc(t *testing.T) { 20 key := "TF_TEST_ENV_DEFAULT_FUNC" 21 defer os.Unsetenv(key) 22 23 f := EnvDefaultFunc(key, "42") 24 if err := os.Setenv(key, "foo"); err != nil { 25 t.Fatalf("err: %s", err) 26 } 27 28 actual, err := f() 29 if err != nil { 30 t.Fatalf("err: %s", err) 31 } 32 if actual != "foo" { 33 t.Fatalf("bad: %#v", actual) 34 } 35 36 if err := os.Unsetenv(key); err != nil { 37 t.Fatalf("err: %s", err) 38 } 39 40 actual, err = f() 41 if err != nil { 42 t.Fatalf("err: %s", err) 43 } 44 if actual != "42" { 45 t.Fatalf("bad: %#v", actual) 46 } 47 } 48 49 func TestMultiEnvDefaultFunc(t *testing.T) { 50 keys := []string{ 51 "TF_TEST_MULTI_ENV_DEFAULT_FUNC1", 52 "TF_TEST_MULTI_ENV_DEFAULT_FUNC2", 53 } 54 defer func() { 55 for _, k := range keys { 56 os.Unsetenv(k) 57 } 58 }() 59 60 // Test that the first key is returned first 61 f := MultiEnvDefaultFunc(keys, "42") 62 if err := os.Setenv(keys[0], "foo"); err != nil { 63 t.Fatalf("err: %s", err) 64 } 65 66 actual, err := f() 67 if err != nil { 68 t.Fatalf("err: %s", err) 69 } 70 if actual != "foo" { 71 t.Fatalf("bad: %#v", actual) 72 } 73 74 if err := os.Unsetenv(keys[0]); err != nil { 75 t.Fatalf("err: %s", err) 76 } 77 78 // Test that the second key is returned if the first one is empty 79 f = MultiEnvDefaultFunc(keys, "42") 80 if err := os.Setenv(keys[1], "foo"); err != nil { 81 t.Fatalf("err: %s", err) 82 } 83 84 actual, err = f() 85 if err != nil { 86 t.Fatalf("err: %s", err) 87 } 88 if actual != "foo" { 89 t.Fatalf("bad: %#v", actual) 90 } 91 92 if err := os.Unsetenv(keys[1]); err != nil { 93 t.Fatalf("err: %s", err) 94 } 95 96 // Test that the default value is returned when no keys are set 97 actual, err = f() 98 if err != nil { 99 t.Fatalf("err: %s", err) 100 } 101 if actual != "42" { 102 t.Fatalf("bad: %#v", actual) 103 } 104 } 105 106 func TestValueType_Zero(t *testing.T) { 107 cases := []struct { 108 Type ValueType 109 Value interface{} 110 }{ 111 {TypeBool, false}, 112 {TypeInt, 0}, 113 {TypeFloat, 0.0}, 114 {TypeString, ""}, 115 {TypeList, []interface{}{}}, 116 {TypeMap, map[string]interface{}{}}, 117 {TypeSet, new(Set)}, 118 } 119 120 for i, tc := range cases { 121 actual := tc.Type.Zero() 122 if !reflect.DeepEqual(actual, tc.Value) { 123 t.Fatalf("%d: %#v != %#v", i, actual, tc.Value) 124 } 125 } 126 } 127 128 func TestSchemaMap_Diff(t *testing.T) { 129 cases := []struct { 130 Name string 131 Schema map[string]*Schema 132 State *terraform.InstanceState 133 Config map[string]interface{} 134 CustomizeDiff CustomizeDiffFunc 135 Diff *terraform.InstanceDiff 136 Err bool 137 }{ 138 { 139 Schema: map[string]*Schema{ 140 "availability_zone": &Schema{ 141 Type: TypeString, 142 Optional: true, 143 Computed: true, 144 ForceNew: true, 145 }, 146 }, 147 148 State: nil, 149 150 Config: map[string]interface{}{ 151 "availability_zone": "foo", 152 }, 153 154 Diff: &terraform.InstanceDiff{ 155 Attributes: map[string]*terraform.ResourceAttrDiff{ 156 "availability_zone": &terraform.ResourceAttrDiff{ 157 Old: "", 158 New: "foo", 159 RequiresNew: true, 160 }, 161 }, 162 }, 163 164 Err: false, 165 }, 166 167 { 168 Schema: map[string]*Schema{ 169 "availability_zone": &Schema{ 170 Type: TypeString, 171 Optional: true, 172 Computed: true, 173 ForceNew: true, 174 }, 175 }, 176 177 State: nil, 178 179 Config: map[string]interface{}{}, 180 181 Diff: &terraform.InstanceDiff{ 182 Attributes: map[string]*terraform.ResourceAttrDiff{ 183 "availability_zone": &terraform.ResourceAttrDiff{ 184 Old: "", 185 NewComputed: true, 186 RequiresNew: true, 187 }, 188 }, 189 }, 190 191 Err: false, 192 }, 193 194 { 195 Schema: map[string]*Schema{ 196 "availability_zone": &Schema{ 197 Type: TypeString, 198 Optional: true, 199 Computed: true, 200 ForceNew: true, 201 }, 202 }, 203 204 State: &terraform.InstanceState{ 205 ID: "foo", 206 }, 207 208 Config: map[string]interface{}{}, 209 210 Diff: nil, 211 212 Err: false, 213 }, 214 215 { 216 Name: "Computed, but set in config", 217 Schema: map[string]*Schema{ 218 "availability_zone": &Schema{ 219 Type: TypeString, 220 Optional: true, 221 Computed: true, 222 }, 223 }, 224 225 State: &terraform.InstanceState{ 226 Attributes: map[string]string{ 227 "availability_zone": "foo", 228 }, 229 }, 230 231 Config: map[string]interface{}{ 232 "availability_zone": "bar", 233 }, 234 235 Diff: &terraform.InstanceDiff{ 236 Attributes: map[string]*terraform.ResourceAttrDiff{ 237 "availability_zone": &terraform.ResourceAttrDiff{ 238 Old: "foo", 239 New: "bar", 240 }, 241 }, 242 }, 243 244 Err: false, 245 }, 246 247 { 248 Name: "Default", 249 Schema: map[string]*Schema{ 250 "availability_zone": &Schema{ 251 Type: TypeString, 252 Optional: true, 253 Default: "foo", 254 }, 255 }, 256 257 State: nil, 258 259 Config: nil, 260 261 Diff: &terraform.InstanceDiff{ 262 Attributes: map[string]*terraform.ResourceAttrDiff{ 263 "availability_zone": &terraform.ResourceAttrDiff{ 264 Old: "", 265 New: "foo", 266 }, 267 }, 268 }, 269 270 Err: false, 271 }, 272 273 { 274 Name: "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 { 302 Name: "DefaultFunc, configuration set", 303 Schema: map[string]*Schema{ 304 "availability_zone": &Schema{ 305 Type: TypeString, 306 Optional: true, 307 DefaultFunc: func() (interface{}, error) { 308 return "foo", nil 309 }, 310 }, 311 }, 312 313 State: nil, 314 315 Config: map[string]interface{}{ 316 "availability_zone": "bar", 317 }, 318 319 Diff: &terraform.InstanceDiff{ 320 Attributes: map[string]*terraform.ResourceAttrDiff{ 321 "availability_zone": &terraform.ResourceAttrDiff{ 322 Old: "", 323 New: "bar", 324 }, 325 }, 326 }, 327 328 Err: false, 329 }, 330 331 { 332 Name: "String with StateFunc", 333 Schema: map[string]*Schema{ 334 "availability_zone": &Schema{ 335 Type: TypeString, 336 Optional: true, 337 Computed: true, 338 StateFunc: func(a interface{}) string { 339 return a.(string) + "!" 340 }, 341 }, 342 }, 343 344 State: nil, 345 346 Config: map[string]interface{}{ 347 "availability_zone": "foo", 348 }, 349 350 Diff: &terraform.InstanceDiff{ 351 Attributes: map[string]*terraform.ResourceAttrDiff{ 352 "availability_zone": &terraform.ResourceAttrDiff{ 353 Old: "", 354 New: "foo!", 355 NewExtra: "foo", 356 }, 357 }, 358 }, 359 360 Err: false, 361 }, 362 363 { 364 Name: "StateFunc not called with nil value", 365 Schema: map[string]*Schema{ 366 "availability_zone": &Schema{ 367 Type: TypeString, 368 Optional: true, 369 Computed: true, 370 StateFunc: func(a interface{}) string { 371 t.Fatalf("should not get here!") 372 return "" 373 }, 374 }, 375 }, 376 377 State: nil, 378 379 Config: map[string]interface{}{}, 380 381 Diff: &terraform.InstanceDiff{ 382 Attributes: map[string]*terraform.ResourceAttrDiff{ 383 "availability_zone": &terraform.ResourceAttrDiff{ 384 Old: "", 385 New: "", 386 NewComputed: true, 387 }, 388 }, 389 }, 390 391 Err: false, 392 }, 393 394 { 395 Name: "Variable computed", 396 Schema: map[string]*Schema{ 397 "availability_zone": &Schema{ 398 Type: TypeString, 399 Optional: true, 400 }, 401 }, 402 403 State: nil, 404 405 Config: map[string]interface{}{ 406 "availability_zone": hcl2shim.UnknownVariableValue, 407 }, 408 409 Diff: &terraform.InstanceDiff{ 410 Attributes: map[string]*terraform.ResourceAttrDiff{ 411 "availability_zone": &terraform.ResourceAttrDiff{ 412 Old: "", 413 New: hcl2shim.UnknownVariableValue, 414 NewComputed: true, 415 }, 416 }, 417 }, 418 419 Err: false, 420 }, 421 422 { 423 Name: "Int decode", 424 Schema: map[string]*Schema{ 425 "port": &Schema{ 426 Type: TypeInt, 427 Optional: true, 428 Computed: true, 429 ForceNew: true, 430 }, 431 }, 432 433 State: nil, 434 435 Config: map[string]interface{}{ 436 "port": 27, 437 }, 438 439 Diff: &terraform.InstanceDiff{ 440 Attributes: map[string]*terraform.ResourceAttrDiff{ 441 "port": &terraform.ResourceAttrDiff{ 442 Old: "", 443 New: "27", 444 RequiresNew: true, 445 }, 446 }, 447 }, 448 449 Err: false, 450 }, 451 452 { 453 Name: "bool decode", 454 Schema: map[string]*Schema{ 455 "port": &Schema{ 456 Type: TypeBool, 457 Optional: true, 458 Computed: true, 459 ForceNew: true, 460 }, 461 }, 462 463 State: nil, 464 465 Config: map[string]interface{}{ 466 "port": false, 467 }, 468 469 Diff: &terraform.InstanceDiff{ 470 Attributes: map[string]*terraform.ResourceAttrDiff{ 471 "port": &terraform.ResourceAttrDiff{ 472 Old: "", 473 New: "false", 474 RequiresNew: true, 475 }, 476 }, 477 }, 478 479 Err: false, 480 }, 481 482 { 483 Name: "Bool", 484 Schema: map[string]*Schema{ 485 "delete": &Schema{ 486 Type: TypeBool, 487 Optional: true, 488 Default: false, 489 }, 490 }, 491 492 State: &terraform.InstanceState{ 493 Attributes: map[string]string{ 494 "delete": "false", 495 }, 496 }, 497 498 Config: nil, 499 500 Diff: nil, 501 502 Err: false, 503 }, 504 505 { 506 Name: "List decode", 507 Schema: map[string]*Schema{ 508 "ports": &Schema{ 509 Type: TypeList, 510 Required: true, 511 Elem: &Schema{Type: TypeInt}, 512 }, 513 }, 514 515 State: nil, 516 517 Config: map[string]interface{}{ 518 "ports": []interface{}{1, 2, 5}, 519 }, 520 521 Diff: &terraform.InstanceDiff{ 522 Attributes: map[string]*terraform.ResourceAttrDiff{ 523 "ports.#": &terraform.ResourceAttrDiff{ 524 Old: "0", 525 New: "3", 526 }, 527 "ports.0": &terraform.ResourceAttrDiff{ 528 Old: "", 529 New: "1", 530 }, 531 "ports.1": &terraform.ResourceAttrDiff{ 532 Old: "", 533 New: "2", 534 }, 535 "ports.2": &terraform.ResourceAttrDiff{ 536 Old: "", 537 New: "5", 538 }, 539 }, 540 }, 541 542 Err: false, 543 }, 544 545 { 546 Name: "List decode with promotion", 547 Schema: map[string]*Schema{ 548 "ports": &Schema{ 549 Type: TypeList, 550 Required: true, 551 Elem: &Schema{Type: TypeInt}, 552 PromoteSingle: true, 553 }, 554 }, 555 556 State: nil, 557 558 Config: map[string]interface{}{ 559 "ports": "5", 560 }, 561 562 Diff: &terraform.InstanceDiff{ 563 Attributes: map[string]*terraform.ResourceAttrDiff{ 564 "ports.#": &terraform.ResourceAttrDiff{ 565 Old: "0", 566 New: "1", 567 }, 568 "ports.0": &terraform.ResourceAttrDiff{ 569 Old: "", 570 New: "5", 571 }, 572 }, 573 }, 574 575 Err: false, 576 }, 577 578 { 579 Name: "List decode with promotion with list", 580 Schema: map[string]*Schema{ 581 "ports": &Schema{ 582 Type: TypeList, 583 Required: true, 584 Elem: &Schema{Type: TypeInt}, 585 PromoteSingle: true, 586 }, 587 }, 588 589 State: nil, 590 591 Config: map[string]interface{}{ 592 "ports": []interface{}{"5"}, 593 }, 594 595 Diff: &terraform.InstanceDiff{ 596 Attributes: map[string]*terraform.ResourceAttrDiff{ 597 "ports.#": &terraform.ResourceAttrDiff{ 598 Old: "0", 599 New: "1", 600 }, 601 "ports.0": &terraform.ResourceAttrDiff{ 602 Old: "", 603 New: "5", 604 }, 605 }, 606 }, 607 608 Err: false, 609 }, 610 611 { 612 Schema: map[string]*Schema{ 613 "ports": &Schema{ 614 Type: TypeList, 615 Required: true, 616 Elem: &Schema{Type: TypeInt}, 617 }, 618 }, 619 620 State: nil, 621 622 Config: map[string]interface{}{ 623 "ports": []interface{}{1, 2, 5}, 624 }, 625 626 Diff: &terraform.InstanceDiff{ 627 Attributes: map[string]*terraform.ResourceAttrDiff{ 628 "ports.#": &terraform.ResourceAttrDiff{ 629 Old: "0", 630 New: "3", 631 }, 632 "ports.0": &terraform.ResourceAttrDiff{ 633 Old: "", 634 New: "1", 635 }, 636 "ports.1": &terraform.ResourceAttrDiff{ 637 Old: "", 638 New: "2", 639 }, 640 "ports.2": &terraform.ResourceAttrDiff{ 641 Old: "", 642 New: "5", 643 }, 644 }, 645 }, 646 647 Err: false, 648 }, 649 650 { 651 Schema: map[string]*Schema{ 652 "ports": &Schema{ 653 Type: TypeList, 654 Required: true, 655 Elem: &Schema{Type: TypeInt}, 656 }, 657 }, 658 659 State: nil, 660 661 Config: map[string]interface{}{ 662 "ports": []interface{}{1, hcl2shim.UnknownVariableValue, 5}, 663 }, 664 665 Diff: &terraform.InstanceDiff{ 666 Attributes: map[string]*terraform.ResourceAttrDiff{ 667 "ports.#": &terraform.ResourceAttrDiff{ 668 Old: "0", 669 New: "", 670 NewComputed: true, 671 }, 672 }, 673 }, 674 675 Err: false, 676 }, 677 678 { 679 Schema: map[string]*Schema{ 680 "ports": &Schema{ 681 Type: TypeList, 682 Required: true, 683 Elem: &Schema{Type: TypeInt}, 684 }, 685 }, 686 687 State: &terraform.InstanceState{ 688 Attributes: map[string]string{ 689 "ports.#": "3", 690 "ports.0": "1", 691 "ports.1": "2", 692 "ports.2": "5", 693 }, 694 }, 695 696 Config: map[string]interface{}{ 697 "ports": []interface{}{1, 2, 5}, 698 }, 699 700 Diff: nil, 701 702 Err: false, 703 }, 704 705 { 706 Name: "", 707 Schema: map[string]*Schema{ 708 "ports": &Schema{ 709 Type: TypeList, 710 Required: true, 711 Elem: &Schema{Type: TypeInt}, 712 }, 713 }, 714 715 State: &terraform.InstanceState{ 716 Attributes: map[string]string{ 717 "ports.#": "2", 718 "ports.0": "1", 719 "ports.1": "2", 720 }, 721 }, 722 723 Config: map[string]interface{}{ 724 "ports": []interface{}{1, 2, 5}, 725 }, 726 727 Diff: &terraform.InstanceDiff{ 728 Attributes: map[string]*terraform.ResourceAttrDiff{ 729 "ports.#": &terraform.ResourceAttrDiff{ 730 Old: "2", 731 New: "3", 732 }, 733 "ports.2": &terraform.ResourceAttrDiff{ 734 Old: "", 735 New: "5", 736 }, 737 }, 738 }, 739 740 Err: false, 741 }, 742 743 { 744 Name: "", 745 Schema: map[string]*Schema{ 746 "ports": &Schema{ 747 Type: TypeList, 748 Required: true, 749 Elem: &Schema{Type: TypeInt}, 750 ForceNew: true, 751 }, 752 }, 753 754 State: nil, 755 756 Config: map[string]interface{}{ 757 "ports": []interface{}{1, 2, 5}, 758 }, 759 760 Diff: &terraform.InstanceDiff{ 761 Attributes: map[string]*terraform.ResourceAttrDiff{ 762 "ports.#": &terraform.ResourceAttrDiff{ 763 Old: "0", 764 New: "3", 765 RequiresNew: true, 766 }, 767 "ports.0": &terraform.ResourceAttrDiff{ 768 Old: "", 769 New: "1", 770 RequiresNew: true, 771 }, 772 "ports.1": &terraform.ResourceAttrDiff{ 773 Old: "", 774 New: "2", 775 RequiresNew: true, 776 }, 777 "ports.2": &terraform.ResourceAttrDiff{ 778 Old: "", 779 New: "5", 780 RequiresNew: true, 781 }, 782 }, 783 }, 784 785 Err: false, 786 }, 787 788 { 789 Name: "", 790 Schema: map[string]*Schema{ 791 "ports": &Schema{ 792 Type: TypeList, 793 Optional: true, 794 Computed: true, 795 Elem: &Schema{Type: TypeInt}, 796 }, 797 }, 798 799 State: nil, 800 801 Config: map[string]interface{}{}, 802 803 Diff: &terraform.InstanceDiff{ 804 Attributes: map[string]*terraform.ResourceAttrDiff{ 805 "ports.#": &terraform.ResourceAttrDiff{ 806 Old: "", 807 NewComputed: true, 808 }, 809 }, 810 }, 811 812 Err: false, 813 }, 814 815 { 816 Name: "List with computed set", 817 Schema: map[string]*Schema{ 818 "config": &Schema{ 819 Type: TypeList, 820 Optional: true, 821 ForceNew: true, 822 MinItems: 1, 823 Elem: &Resource{ 824 Schema: map[string]*Schema{ 825 "name": { 826 Type: TypeString, 827 Required: true, 828 }, 829 830 "rules": { 831 Type: TypeSet, 832 Computed: true, 833 Elem: &Schema{Type: TypeString}, 834 Set: HashString, 835 }, 836 }, 837 }, 838 }, 839 }, 840 841 State: nil, 842 843 Config: map[string]interface{}{ 844 "config": []interface{}{ 845 map[string]interface{}{ 846 "name": "hello", 847 }, 848 }, 849 }, 850 851 Diff: &terraform.InstanceDiff{ 852 Attributes: map[string]*terraform.ResourceAttrDiff{ 853 "config.#": &terraform.ResourceAttrDiff{ 854 Old: "0", 855 New: "1", 856 RequiresNew: true, 857 }, 858 859 "config.0.name": &terraform.ResourceAttrDiff{ 860 Old: "", 861 New: "hello", 862 }, 863 864 "config.0.rules.#": &terraform.ResourceAttrDiff{ 865 Old: "", 866 NewComputed: true, 867 }, 868 }, 869 }, 870 871 Err: false, 872 }, 873 874 { 875 Name: "Set", 876 Schema: map[string]*Schema{ 877 "ports": &Schema{ 878 Type: TypeSet, 879 Required: true, 880 Elem: &Schema{Type: TypeInt}, 881 Set: func(a interface{}) int { 882 return a.(int) 883 }, 884 }, 885 }, 886 887 State: nil, 888 889 Config: map[string]interface{}{ 890 "ports": []interface{}{5, 2, 1}, 891 }, 892 893 Diff: &terraform.InstanceDiff{ 894 Attributes: map[string]*terraform.ResourceAttrDiff{ 895 "ports.#": &terraform.ResourceAttrDiff{ 896 Old: "0", 897 New: "3", 898 }, 899 "ports.1": &terraform.ResourceAttrDiff{ 900 Old: "", 901 New: "1", 902 }, 903 "ports.2": &terraform.ResourceAttrDiff{ 904 Old: "", 905 New: "2", 906 }, 907 "ports.5": &terraform.ResourceAttrDiff{ 908 Old: "", 909 New: "5", 910 }, 911 }, 912 }, 913 914 Err: false, 915 }, 916 917 { 918 Name: "Set", 919 Schema: map[string]*Schema{ 920 "ports": &Schema{ 921 Type: TypeSet, 922 Computed: true, 923 Required: true, 924 Elem: &Schema{Type: TypeInt}, 925 Set: func(a interface{}) int { 926 return a.(int) 927 }, 928 }, 929 }, 930 931 State: &terraform.InstanceState{ 932 Attributes: map[string]string{ 933 "ports.#": "0", 934 }, 935 }, 936 937 Config: nil, 938 939 Diff: nil, 940 941 Err: false, 942 }, 943 944 { 945 Name: "Set", 946 Schema: map[string]*Schema{ 947 "ports": &Schema{ 948 Type: TypeSet, 949 Optional: true, 950 Computed: true, 951 Elem: &Schema{Type: TypeInt}, 952 Set: func(a interface{}) int { 953 return a.(int) 954 }, 955 }, 956 }, 957 958 State: nil, 959 960 Config: nil, 961 962 Diff: &terraform.InstanceDiff{ 963 Attributes: map[string]*terraform.ResourceAttrDiff{ 964 "ports.#": &terraform.ResourceAttrDiff{ 965 Old: "", 966 NewComputed: true, 967 }, 968 }, 969 }, 970 971 Err: false, 972 }, 973 974 { 975 Name: "Set", 976 Schema: map[string]*Schema{ 977 "ports": &Schema{ 978 Type: TypeSet, 979 Required: true, 980 Elem: &Schema{Type: TypeInt}, 981 Set: func(a interface{}) int { 982 return a.(int) 983 }, 984 }, 985 }, 986 987 State: nil, 988 989 Config: map[string]interface{}{ 990 "ports": []interface{}{"2", "5", 1}, 991 }, 992 993 Diff: &terraform.InstanceDiff{ 994 Attributes: map[string]*terraform.ResourceAttrDiff{ 995 "ports.#": &terraform.ResourceAttrDiff{ 996 Old: "0", 997 New: "3", 998 }, 999 "ports.1": &terraform.ResourceAttrDiff{ 1000 Old: "", 1001 New: "1", 1002 }, 1003 "ports.2": &terraform.ResourceAttrDiff{ 1004 Old: "", 1005 New: "2", 1006 }, 1007 "ports.5": &terraform.ResourceAttrDiff{ 1008 Old: "", 1009 New: "5", 1010 }, 1011 }, 1012 }, 1013 1014 Err: false, 1015 }, 1016 1017 { 1018 Name: "Set", 1019 Schema: map[string]*Schema{ 1020 "ports": &Schema{ 1021 Type: TypeSet, 1022 Required: true, 1023 Elem: &Schema{Type: TypeInt}, 1024 Set: func(a interface{}) int { 1025 return a.(int) 1026 }, 1027 }, 1028 }, 1029 1030 State: nil, 1031 1032 Config: map[string]interface{}{ 1033 "ports": []interface{}{1, hcl2shim.UnknownVariableValue, "5"}, 1034 }, 1035 1036 Diff: &terraform.InstanceDiff{ 1037 Attributes: map[string]*terraform.ResourceAttrDiff{ 1038 "ports.#": &terraform.ResourceAttrDiff{ 1039 Old: "", 1040 New: "", 1041 NewComputed: true, 1042 }, 1043 }, 1044 }, 1045 1046 Err: false, 1047 }, 1048 1049 { 1050 Name: "Set", 1051 Schema: map[string]*Schema{ 1052 "ports": &Schema{ 1053 Type: TypeSet, 1054 Required: true, 1055 Elem: &Schema{Type: TypeInt}, 1056 Set: func(a interface{}) int { 1057 return a.(int) 1058 }, 1059 }, 1060 }, 1061 1062 State: &terraform.InstanceState{ 1063 Attributes: map[string]string{ 1064 "ports.#": "2", 1065 "ports.1": "1", 1066 "ports.2": "2", 1067 }, 1068 }, 1069 1070 Config: map[string]interface{}{ 1071 "ports": []interface{}{5, 2, 1}, 1072 }, 1073 1074 Diff: &terraform.InstanceDiff{ 1075 Attributes: map[string]*terraform.ResourceAttrDiff{ 1076 "ports.#": &terraform.ResourceAttrDiff{ 1077 Old: "2", 1078 New: "3", 1079 }, 1080 "ports.1": &terraform.ResourceAttrDiff{ 1081 Old: "1", 1082 New: "1", 1083 }, 1084 "ports.2": &terraform.ResourceAttrDiff{ 1085 Old: "2", 1086 New: "2", 1087 }, 1088 "ports.5": &terraform.ResourceAttrDiff{ 1089 Old: "", 1090 New: "5", 1091 }, 1092 }, 1093 }, 1094 1095 Err: false, 1096 }, 1097 1098 { 1099 Name: "Set", 1100 Schema: map[string]*Schema{ 1101 "ports": &Schema{ 1102 Type: TypeSet, 1103 Required: true, 1104 Elem: &Schema{Type: TypeInt}, 1105 Set: func(a interface{}) int { 1106 return a.(int) 1107 }, 1108 }, 1109 }, 1110 1111 State: &terraform.InstanceState{ 1112 Attributes: map[string]string{ 1113 "ports.#": "2", 1114 "ports.1": "1", 1115 "ports.2": "2", 1116 }, 1117 }, 1118 1119 Config: map[string]interface{}{}, 1120 1121 Diff: &terraform.InstanceDiff{ 1122 Attributes: map[string]*terraform.ResourceAttrDiff{ 1123 "ports.#": &terraform.ResourceAttrDiff{ 1124 Old: "2", 1125 New: "0", 1126 }, 1127 "ports.1": &terraform.ResourceAttrDiff{ 1128 Old: "1", 1129 New: "0", 1130 NewRemoved: true, 1131 }, 1132 "ports.2": &terraform.ResourceAttrDiff{ 1133 Old: "2", 1134 New: "0", 1135 NewRemoved: true, 1136 }, 1137 }, 1138 }, 1139 1140 Err: false, 1141 }, 1142 1143 { 1144 Name: "Set", 1145 Schema: map[string]*Schema{ 1146 "ports": &Schema{ 1147 Type: TypeSet, 1148 Optional: true, 1149 Computed: true, 1150 Elem: &Schema{Type: TypeInt}, 1151 Set: func(a interface{}) int { 1152 return a.(int) 1153 }, 1154 }, 1155 }, 1156 1157 State: &terraform.InstanceState{ 1158 Attributes: map[string]string{ 1159 "availability_zone": "bar", 1160 "ports.#": "1", 1161 "ports.80": "80", 1162 }, 1163 }, 1164 1165 Config: map[string]interface{}{}, 1166 1167 Diff: nil, 1168 1169 Err: false, 1170 }, 1171 1172 { 1173 Name: "Set", 1174 Schema: map[string]*Schema{ 1175 "ingress": &Schema{ 1176 Type: TypeSet, 1177 Required: true, 1178 Elem: &Resource{ 1179 Schema: map[string]*Schema{ 1180 "ports": &Schema{ 1181 Type: TypeList, 1182 Optional: true, 1183 Elem: &Schema{Type: TypeInt}, 1184 }, 1185 }, 1186 }, 1187 Set: func(v interface{}) int { 1188 m := v.(map[string]interface{}) 1189 ps := m["ports"].([]interface{}) 1190 result := 0 1191 for _, p := range ps { 1192 result += p.(int) 1193 } 1194 return result 1195 }, 1196 }, 1197 }, 1198 1199 State: &terraform.InstanceState{ 1200 Attributes: map[string]string{ 1201 "ingress.#": "2", 1202 "ingress.80.ports.#": "1", 1203 "ingress.80.ports.0": "80", 1204 "ingress.443.ports.#": "1", 1205 "ingress.443.ports.0": "443", 1206 }, 1207 }, 1208 1209 Config: map[string]interface{}{ 1210 "ingress": []interface{}{ 1211 map[string]interface{}{ 1212 "ports": []interface{}{443}, 1213 }, 1214 map[string]interface{}{ 1215 "ports": []interface{}{80}, 1216 }, 1217 }, 1218 }, 1219 1220 Diff: nil, 1221 1222 Err: false, 1223 }, 1224 1225 { 1226 Name: "List of structure decode", 1227 Schema: map[string]*Schema{ 1228 "ingress": &Schema{ 1229 Type: TypeList, 1230 Required: true, 1231 Elem: &Resource{ 1232 Schema: map[string]*Schema{ 1233 "from": &Schema{ 1234 Type: TypeInt, 1235 Required: true, 1236 }, 1237 }, 1238 }, 1239 }, 1240 }, 1241 1242 State: nil, 1243 1244 Config: map[string]interface{}{ 1245 "ingress": []interface{}{ 1246 map[string]interface{}{ 1247 "from": 8080, 1248 }, 1249 }, 1250 }, 1251 1252 Diff: &terraform.InstanceDiff{ 1253 Attributes: map[string]*terraform.ResourceAttrDiff{ 1254 "ingress.#": &terraform.ResourceAttrDiff{ 1255 Old: "0", 1256 New: "1", 1257 }, 1258 "ingress.0.from": &terraform.ResourceAttrDiff{ 1259 Old: "", 1260 New: "8080", 1261 }, 1262 }, 1263 }, 1264 1265 Err: false, 1266 }, 1267 1268 { 1269 Name: "ComputedWhen", 1270 Schema: map[string]*Schema{ 1271 "availability_zone": &Schema{ 1272 Type: TypeString, 1273 Computed: true, 1274 ComputedWhen: []string{"port"}, 1275 }, 1276 1277 "port": &Schema{ 1278 Type: TypeInt, 1279 Optional: true, 1280 }, 1281 }, 1282 1283 State: &terraform.InstanceState{ 1284 Attributes: map[string]string{ 1285 "availability_zone": "foo", 1286 "port": "80", 1287 }, 1288 }, 1289 1290 Config: map[string]interface{}{ 1291 "port": 80, 1292 }, 1293 1294 Diff: nil, 1295 1296 Err: false, 1297 }, 1298 1299 { 1300 Name: "", 1301 Schema: map[string]*Schema{ 1302 "availability_zone": &Schema{ 1303 Type: TypeString, 1304 Computed: true, 1305 ComputedWhen: []string{"port"}, 1306 }, 1307 1308 "port": &Schema{ 1309 Type: TypeInt, 1310 Optional: true, 1311 }, 1312 }, 1313 1314 State: &terraform.InstanceState{ 1315 Attributes: map[string]string{ 1316 "port": "80", 1317 }, 1318 }, 1319 1320 Config: map[string]interface{}{ 1321 "port": 80, 1322 }, 1323 1324 Diff: &terraform.InstanceDiff{ 1325 Attributes: map[string]*terraform.ResourceAttrDiff{ 1326 "availability_zone": &terraform.ResourceAttrDiff{ 1327 NewComputed: true, 1328 }, 1329 }, 1330 }, 1331 1332 Err: false, 1333 }, 1334 1335 /* TODO 1336 { 1337 Schema: map[string]*Schema{ 1338 "availability_zone": &Schema{ 1339 Type: TypeString, 1340 Computed: true, 1341 ComputedWhen: []string{"port"}, 1342 }, 1343 1344 "port": &Schema{ 1345 Type: TypeInt, 1346 Optional: true, 1347 }, 1348 }, 1349 1350 State: &terraform.InstanceState{ 1351 Attributes: map[string]string{ 1352 "availability_zone": "foo", 1353 "port": "80", 1354 }, 1355 }, 1356 1357 Config: map[string]interface{}{ 1358 "port": 8080, 1359 }, 1360 1361 Diff: &terraform.ResourceDiff{ 1362 Attributes: map[string]*terraform.ResourceAttrDiff{ 1363 "availability_zone": &terraform.ResourceAttrDiff{ 1364 Old: "foo", 1365 NewComputed: true, 1366 }, 1367 "port": &terraform.ResourceAttrDiff{ 1368 Old: "80", 1369 New: "8080", 1370 }, 1371 }, 1372 }, 1373 1374 Err: false, 1375 }, 1376 */ 1377 1378 { 1379 Name: "Maps", 1380 Schema: map[string]*Schema{ 1381 "config_vars": &Schema{ 1382 Type: TypeMap, 1383 }, 1384 }, 1385 1386 State: nil, 1387 1388 Config: map[string]interface{}{ 1389 "config_vars": []interface{}{ 1390 map[string]interface{}{ 1391 "bar": "baz", 1392 }, 1393 }, 1394 }, 1395 1396 Diff: &terraform.InstanceDiff{ 1397 Attributes: map[string]*terraform.ResourceAttrDiff{ 1398 "config_vars.%": &terraform.ResourceAttrDiff{ 1399 Old: "0", 1400 New: "1", 1401 }, 1402 1403 "config_vars.bar": &terraform.ResourceAttrDiff{ 1404 Old: "", 1405 New: "baz", 1406 }, 1407 }, 1408 }, 1409 1410 Err: false, 1411 }, 1412 1413 { 1414 Name: "Maps", 1415 Schema: map[string]*Schema{ 1416 "config_vars": &Schema{ 1417 Type: TypeMap, 1418 }, 1419 }, 1420 1421 State: &terraform.InstanceState{ 1422 Attributes: map[string]string{ 1423 "config_vars.foo": "bar", 1424 }, 1425 }, 1426 1427 Config: map[string]interface{}{ 1428 "config_vars": []interface{}{ 1429 map[string]interface{}{ 1430 "bar": "baz", 1431 }, 1432 }, 1433 }, 1434 1435 Diff: &terraform.InstanceDiff{ 1436 Attributes: map[string]*terraform.ResourceAttrDiff{ 1437 "config_vars.foo": &terraform.ResourceAttrDiff{ 1438 Old: "bar", 1439 NewRemoved: true, 1440 }, 1441 "config_vars.bar": &terraform.ResourceAttrDiff{ 1442 Old: "", 1443 New: "baz", 1444 }, 1445 }, 1446 }, 1447 1448 Err: false, 1449 }, 1450 1451 { 1452 Name: "Maps", 1453 Schema: map[string]*Schema{ 1454 "vars": &Schema{ 1455 Type: TypeMap, 1456 Optional: true, 1457 Computed: true, 1458 }, 1459 }, 1460 1461 State: &terraform.InstanceState{ 1462 Attributes: map[string]string{ 1463 "vars.foo": "bar", 1464 }, 1465 }, 1466 1467 Config: map[string]interface{}{ 1468 "vars": []interface{}{ 1469 map[string]interface{}{ 1470 "bar": "baz", 1471 }, 1472 }, 1473 }, 1474 1475 Diff: &terraform.InstanceDiff{ 1476 Attributes: map[string]*terraform.ResourceAttrDiff{ 1477 "vars.foo": &terraform.ResourceAttrDiff{ 1478 Old: "bar", 1479 New: "", 1480 NewRemoved: true, 1481 }, 1482 "vars.bar": &terraform.ResourceAttrDiff{ 1483 Old: "", 1484 New: "baz", 1485 }, 1486 }, 1487 }, 1488 1489 Err: false, 1490 }, 1491 1492 { 1493 Name: "Maps", 1494 Schema: map[string]*Schema{ 1495 "vars": &Schema{ 1496 Type: TypeMap, 1497 Computed: true, 1498 }, 1499 }, 1500 1501 State: &terraform.InstanceState{ 1502 Attributes: map[string]string{ 1503 "vars.foo": "bar", 1504 }, 1505 }, 1506 1507 Config: nil, 1508 1509 Diff: nil, 1510 1511 Err: false, 1512 }, 1513 1514 { 1515 Name: "Maps", 1516 Schema: map[string]*Schema{ 1517 "config_vars": &Schema{ 1518 Type: TypeList, 1519 Elem: &Schema{Type: TypeMap}, 1520 }, 1521 }, 1522 1523 State: &terraform.InstanceState{ 1524 Attributes: map[string]string{ 1525 "config_vars.#": "1", 1526 "config_vars.0.foo": "bar", 1527 }, 1528 }, 1529 1530 Config: map[string]interface{}{ 1531 "config_vars": []interface{}{ 1532 map[string]interface{}{ 1533 "bar": "baz", 1534 }, 1535 }, 1536 }, 1537 1538 Diff: &terraform.InstanceDiff{ 1539 Attributes: map[string]*terraform.ResourceAttrDiff{ 1540 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1541 Old: "bar", 1542 NewRemoved: true, 1543 }, 1544 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1545 Old: "", 1546 New: "baz", 1547 }, 1548 }, 1549 }, 1550 1551 Err: false, 1552 }, 1553 1554 { 1555 Name: "Maps", 1556 Schema: map[string]*Schema{ 1557 "config_vars": &Schema{ 1558 Type: TypeList, 1559 Elem: &Schema{Type: TypeMap}, 1560 }, 1561 }, 1562 1563 State: &terraform.InstanceState{ 1564 Attributes: map[string]string{ 1565 "config_vars.#": "1", 1566 "config_vars.0.foo": "bar", 1567 "config_vars.0.bar": "baz", 1568 }, 1569 }, 1570 1571 Config: map[string]interface{}{}, 1572 1573 Diff: &terraform.InstanceDiff{ 1574 Attributes: map[string]*terraform.ResourceAttrDiff{ 1575 "config_vars.#": &terraform.ResourceAttrDiff{ 1576 Old: "1", 1577 New: "0", 1578 }, 1579 "config_vars.0.%": &terraform.ResourceAttrDiff{ 1580 Old: "2", 1581 New: "0", 1582 }, 1583 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 1584 Old: "bar", 1585 NewRemoved: true, 1586 }, 1587 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 1588 Old: "baz", 1589 NewRemoved: true, 1590 }, 1591 }, 1592 }, 1593 1594 Err: false, 1595 }, 1596 1597 { 1598 Name: "ForceNews", 1599 Schema: map[string]*Schema{ 1600 "availability_zone": &Schema{ 1601 Type: TypeString, 1602 Optional: true, 1603 ForceNew: true, 1604 }, 1605 1606 "address": &Schema{ 1607 Type: TypeString, 1608 Optional: true, 1609 Computed: true, 1610 }, 1611 }, 1612 1613 State: &terraform.InstanceState{ 1614 Attributes: map[string]string{ 1615 "availability_zone": "bar", 1616 "address": "foo", 1617 }, 1618 }, 1619 1620 Config: map[string]interface{}{ 1621 "availability_zone": "foo", 1622 }, 1623 1624 Diff: &terraform.InstanceDiff{ 1625 Attributes: map[string]*terraform.ResourceAttrDiff{ 1626 "availability_zone": &terraform.ResourceAttrDiff{ 1627 Old: "bar", 1628 New: "foo", 1629 RequiresNew: true, 1630 }, 1631 1632 "address": &terraform.ResourceAttrDiff{ 1633 Old: "foo", 1634 New: "", 1635 NewComputed: true, 1636 }, 1637 }, 1638 }, 1639 1640 Err: false, 1641 }, 1642 1643 { 1644 Name: "Set", 1645 Schema: map[string]*Schema{ 1646 "availability_zone": &Schema{ 1647 Type: TypeString, 1648 Optional: true, 1649 ForceNew: true, 1650 }, 1651 1652 "ports": &Schema{ 1653 Type: TypeSet, 1654 Optional: true, 1655 Computed: true, 1656 Elem: &Schema{Type: TypeInt}, 1657 Set: func(a interface{}) int { 1658 return a.(int) 1659 }, 1660 }, 1661 }, 1662 1663 State: &terraform.InstanceState{ 1664 Attributes: map[string]string{ 1665 "availability_zone": "bar", 1666 "ports.#": "1", 1667 "ports.80": "80", 1668 }, 1669 }, 1670 1671 Config: map[string]interface{}{ 1672 "availability_zone": "foo", 1673 }, 1674 1675 Diff: &terraform.InstanceDiff{ 1676 Attributes: map[string]*terraform.ResourceAttrDiff{ 1677 "availability_zone": &terraform.ResourceAttrDiff{ 1678 Old: "bar", 1679 New: "foo", 1680 RequiresNew: true, 1681 }, 1682 1683 "ports.#": &terraform.ResourceAttrDiff{ 1684 Old: "1", 1685 New: "", 1686 NewComputed: true, 1687 }, 1688 }, 1689 }, 1690 1691 Err: false, 1692 }, 1693 1694 { 1695 Name: "Set", 1696 Schema: map[string]*Schema{ 1697 "instances": &Schema{ 1698 Type: TypeSet, 1699 Elem: &Schema{Type: TypeString}, 1700 Optional: true, 1701 Computed: true, 1702 Set: func(v interface{}) int { 1703 return len(v.(string)) 1704 }, 1705 }, 1706 }, 1707 1708 State: &terraform.InstanceState{ 1709 Attributes: map[string]string{ 1710 "instances.#": "0", 1711 }, 1712 }, 1713 1714 Config: map[string]interface{}{ 1715 "instances": []interface{}{hcl2shim.UnknownVariableValue}, 1716 }, 1717 1718 Diff: &terraform.InstanceDiff{ 1719 Attributes: map[string]*terraform.ResourceAttrDiff{ 1720 "instances.#": &terraform.ResourceAttrDiff{ 1721 NewComputed: true, 1722 }, 1723 }, 1724 }, 1725 1726 Err: false, 1727 }, 1728 1729 { 1730 Name: "Set", 1731 Schema: map[string]*Schema{ 1732 "route": &Schema{ 1733 Type: TypeSet, 1734 Optional: true, 1735 Elem: &Resource{ 1736 Schema: map[string]*Schema{ 1737 "index": &Schema{ 1738 Type: TypeInt, 1739 Required: true, 1740 }, 1741 1742 "gateway": &Schema{ 1743 Type: TypeString, 1744 Optional: true, 1745 }, 1746 }, 1747 }, 1748 Set: func(v interface{}) int { 1749 m := v.(map[string]interface{}) 1750 return m["index"].(int) 1751 }, 1752 }, 1753 }, 1754 1755 State: nil, 1756 1757 Config: map[string]interface{}{ 1758 "route": []interface{}{ 1759 map[string]interface{}{ 1760 "index": "1", 1761 "gateway": hcl2shim.UnknownVariableValue, 1762 }, 1763 }, 1764 }, 1765 1766 Diff: &terraform.InstanceDiff{ 1767 Attributes: map[string]*terraform.ResourceAttrDiff{ 1768 "route.#": &terraform.ResourceAttrDiff{ 1769 Old: "0", 1770 New: "1", 1771 }, 1772 "route.~1.index": &terraform.ResourceAttrDiff{ 1773 Old: "", 1774 New: "1", 1775 }, 1776 "route.~1.gateway": &terraform.ResourceAttrDiff{ 1777 Old: "", 1778 New: hcl2shim.UnknownVariableValue, 1779 NewComputed: true, 1780 }, 1781 }, 1782 }, 1783 1784 Err: false, 1785 }, 1786 1787 { 1788 Name: "Set", 1789 Schema: map[string]*Schema{ 1790 "route": &Schema{ 1791 Type: TypeSet, 1792 Optional: true, 1793 Elem: &Resource{ 1794 Schema: map[string]*Schema{ 1795 "index": &Schema{ 1796 Type: TypeInt, 1797 Required: true, 1798 }, 1799 1800 "gateway": &Schema{ 1801 Type: TypeSet, 1802 Optional: true, 1803 Elem: &Schema{Type: TypeInt}, 1804 Set: func(a interface{}) int { 1805 return a.(int) 1806 }, 1807 }, 1808 }, 1809 }, 1810 Set: func(v interface{}) int { 1811 m := v.(map[string]interface{}) 1812 return m["index"].(int) 1813 }, 1814 }, 1815 }, 1816 1817 State: nil, 1818 1819 Config: map[string]interface{}{ 1820 "route": []interface{}{ 1821 map[string]interface{}{ 1822 "index": "1", 1823 "gateway": []interface{}{ 1824 hcl2shim.UnknownVariableValue, 1825 }, 1826 }, 1827 }, 1828 }, 1829 1830 Diff: &terraform.InstanceDiff{ 1831 Attributes: map[string]*terraform.ResourceAttrDiff{ 1832 "route.#": &terraform.ResourceAttrDiff{ 1833 Old: "0", 1834 New: "1", 1835 }, 1836 "route.~1.index": &terraform.ResourceAttrDiff{ 1837 Old: "", 1838 New: "1", 1839 }, 1840 "route.~1.gateway.#": &terraform.ResourceAttrDiff{ 1841 NewComputed: true, 1842 }, 1843 }, 1844 }, 1845 1846 Err: false, 1847 }, 1848 1849 { 1850 Name: "Computed maps", 1851 Schema: map[string]*Schema{ 1852 "vars": &Schema{ 1853 Type: TypeMap, 1854 Computed: true, 1855 }, 1856 }, 1857 1858 State: nil, 1859 1860 Config: nil, 1861 1862 Diff: &terraform.InstanceDiff{ 1863 Attributes: map[string]*terraform.ResourceAttrDiff{ 1864 "vars.%": &terraform.ResourceAttrDiff{ 1865 Old: "", 1866 NewComputed: true, 1867 }, 1868 }, 1869 }, 1870 1871 Err: false, 1872 }, 1873 1874 { 1875 Name: "Computed maps", 1876 Schema: map[string]*Schema{ 1877 "vars": &Schema{ 1878 Type: TypeMap, 1879 Computed: true, 1880 }, 1881 }, 1882 1883 State: &terraform.InstanceState{ 1884 Attributes: map[string]string{ 1885 "vars.%": "0", 1886 }, 1887 }, 1888 1889 Config: map[string]interface{}{ 1890 "vars": map[string]interface{}{ 1891 "bar": hcl2shim.UnknownVariableValue, 1892 }, 1893 }, 1894 1895 Diff: &terraform.InstanceDiff{ 1896 Attributes: map[string]*terraform.ResourceAttrDiff{ 1897 "vars.%": &terraform.ResourceAttrDiff{ 1898 Old: "", 1899 NewComputed: true, 1900 }, 1901 }, 1902 }, 1903 1904 Err: false, 1905 }, 1906 1907 { 1908 Name: " - Empty", 1909 Schema: map[string]*Schema{}, 1910 1911 State: &terraform.InstanceState{}, 1912 1913 Config: map[string]interface{}{}, 1914 1915 Diff: nil, 1916 1917 Err: false, 1918 }, 1919 1920 { 1921 Name: "Float", 1922 Schema: map[string]*Schema{ 1923 "some_threshold": &Schema{ 1924 Type: TypeFloat, 1925 }, 1926 }, 1927 1928 State: &terraform.InstanceState{ 1929 Attributes: map[string]string{ 1930 "some_threshold": "567.8", 1931 }, 1932 }, 1933 1934 Config: map[string]interface{}{ 1935 "some_threshold": 12.34, 1936 }, 1937 1938 Diff: &terraform.InstanceDiff{ 1939 Attributes: map[string]*terraform.ResourceAttrDiff{ 1940 "some_threshold": &terraform.ResourceAttrDiff{ 1941 Old: "567.8", 1942 New: "12.34", 1943 }, 1944 }, 1945 }, 1946 1947 Err: false, 1948 }, 1949 1950 { 1951 Name: "https://github.com/hashicorp/terraform/issues/824", 1952 Schema: map[string]*Schema{ 1953 "block_device": &Schema{ 1954 Type: TypeSet, 1955 Optional: true, 1956 Computed: true, 1957 Elem: &Resource{ 1958 Schema: map[string]*Schema{ 1959 "device_name": &Schema{ 1960 Type: TypeString, 1961 Required: true, 1962 }, 1963 "delete_on_termination": &Schema{ 1964 Type: TypeBool, 1965 Optional: true, 1966 Default: true, 1967 }, 1968 }, 1969 }, 1970 Set: func(v interface{}) int { 1971 var buf bytes.Buffer 1972 m := v.(map[string]interface{}) 1973 buf.WriteString(fmt.Sprintf("%s-", m["device_name"].(string))) 1974 buf.WriteString(fmt.Sprintf("%t-", m["delete_on_termination"].(bool))) 1975 return hashcode.String(buf.String()) 1976 }, 1977 }, 1978 }, 1979 1980 State: &terraform.InstanceState{ 1981 Attributes: map[string]string{ 1982 "block_device.#": "2", 1983 "block_device.616397234.delete_on_termination": "true", 1984 "block_device.616397234.device_name": "/dev/sda1", 1985 "block_device.2801811477.delete_on_termination": "true", 1986 "block_device.2801811477.device_name": "/dev/sdx", 1987 }, 1988 }, 1989 1990 Config: map[string]interface{}{ 1991 "block_device": []interface{}{ 1992 map[string]interface{}{ 1993 "device_name": "/dev/sda1", 1994 }, 1995 map[string]interface{}{ 1996 "device_name": "/dev/sdx", 1997 }, 1998 }, 1999 }, 2000 Diff: nil, 2001 Err: false, 2002 }, 2003 2004 { 2005 Name: "Zero value in state shouldn't result in diff", 2006 Schema: map[string]*Schema{ 2007 "port": &Schema{ 2008 Type: TypeBool, 2009 Optional: true, 2010 ForceNew: true, 2011 }, 2012 }, 2013 2014 State: &terraform.InstanceState{ 2015 Attributes: map[string]string{ 2016 "port": "false", 2017 }, 2018 }, 2019 2020 Config: map[string]interface{}{}, 2021 2022 Diff: nil, 2023 2024 Err: false, 2025 }, 2026 2027 { 2028 Name: "Same as prev, but for sets", 2029 Schema: map[string]*Schema{ 2030 "route": &Schema{ 2031 Type: TypeSet, 2032 Optional: true, 2033 Elem: &Resource{ 2034 Schema: map[string]*Schema{ 2035 "index": &Schema{ 2036 Type: TypeInt, 2037 Required: true, 2038 }, 2039 2040 "gateway": &Schema{ 2041 Type: TypeSet, 2042 Optional: true, 2043 Elem: &Schema{Type: TypeInt}, 2044 Set: func(a interface{}) int { 2045 return a.(int) 2046 }, 2047 }, 2048 }, 2049 }, 2050 Set: func(v interface{}) int { 2051 m := v.(map[string]interface{}) 2052 return m["index"].(int) 2053 }, 2054 }, 2055 }, 2056 2057 State: &terraform.InstanceState{ 2058 Attributes: map[string]string{ 2059 "route.#": "0", 2060 }, 2061 }, 2062 2063 Config: map[string]interface{}{}, 2064 2065 Diff: nil, 2066 2067 Err: false, 2068 }, 2069 2070 { 2071 Name: "A set computed element shouldn't cause a diff", 2072 Schema: map[string]*Schema{ 2073 "active": &Schema{ 2074 Type: TypeBool, 2075 Computed: true, 2076 ForceNew: true, 2077 }, 2078 }, 2079 2080 State: &terraform.InstanceState{ 2081 Attributes: map[string]string{ 2082 "active": "true", 2083 }, 2084 }, 2085 2086 Config: map[string]interface{}{}, 2087 2088 Diff: nil, 2089 2090 Err: false, 2091 }, 2092 2093 { 2094 Name: "An empty set should show up in the diff", 2095 Schema: map[string]*Schema{ 2096 "instances": &Schema{ 2097 Type: TypeSet, 2098 Elem: &Schema{Type: TypeString}, 2099 Optional: true, 2100 ForceNew: true, 2101 Set: func(v interface{}) int { 2102 return len(v.(string)) 2103 }, 2104 }, 2105 }, 2106 2107 State: &terraform.InstanceState{ 2108 Attributes: map[string]string{ 2109 "instances.#": "1", 2110 "instances.3": "foo", 2111 }, 2112 }, 2113 2114 Config: map[string]interface{}{}, 2115 2116 Diff: &terraform.InstanceDiff{ 2117 Attributes: map[string]*terraform.ResourceAttrDiff{ 2118 "instances.#": &terraform.ResourceAttrDiff{ 2119 Old: "1", 2120 New: "0", 2121 RequiresNew: true, 2122 }, 2123 "instances.3": &terraform.ResourceAttrDiff{ 2124 Old: "foo", 2125 New: "", 2126 NewRemoved: true, 2127 RequiresNew: true, 2128 }, 2129 }, 2130 }, 2131 2132 Err: false, 2133 }, 2134 2135 { 2136 Name: "Map with empty value", 2137 Schema: map[string]*Schema{ 2138 "vars": &Schema{ 2139 Type: TypeMap, 2140 }, 2141 }, 2142 2143 State: nil, 2144 2145 Config: map[string]interface{}{ 2146 "vars": map[string]interface{}{ 2147 "foo": "", 2148 }, 2149 }, 2150 2151 Diff: &terraform.InstanceDiff{ 2152 Attributes: map[string]*terraform.ResourceAttrDiff{ 2153 "vars.%": &terraform.ResourceAttrDiff{ 2154 Old: "0", 2155 New: "1", 2156 }, 2157 "vars.foo": &terraform.ResourceAttrDiff{ 2158 Old: "", 2159 New: "", 2160 }, 2161 }, 2162 }, 2163 2164 Err: false, 2165 }, 2166 2167 { 2168 Name: "Unset bool, not in state", 2169 Schema: map[string]*Schema{ 2170 "force": &Schema{ 2171 Type: TypeBool, 2172 Optional: true, 2173 ForceNew: true, 2174 }, 2175 }, 2176 2177 State: nil, 2178 2179 Config: map[string]interface{}{}, 2180 2181 Diff: nil, 2182 2183 Err: false, 2184 }, 2185 2186 { 2187 Name: "Unset set, not in state", 2188 Schema: map[string]*Schema{ 2189 "metadata_keys": &Schema{ 2190 Type: TypeSet, 2191 Optional: true, 2192 ForceNew: true, 2193 Elem: &Schema{Type: TypeInt}, 2194 Set: func(interface{}) int { return 0 }, 2195 }, 2196 }, 2197 2198 State: nil, 2199 2200 Config: map[string]interface{}{}, 2201 2202 Diff: nil, 2203 2204 Err: false, 2205 }, 2206 2207 { 2208 Name: "Unset list in state, should not show up computed", 2209 Schema: map[string]*Schema{ 2210 "metadata_keys": &Schema{ 2211 Type: TypeList, 2212 Optional: true, 2213 Computed: true, 2214 ForceNew: true, 2215 Elem: &Schema{Type: TypeInt}, 2216 }, 2217 }, 2218 2219 State: &terraform.InstanceState{ 2220 Attributes: map[string]string{ 2221 "metadata_keys.#": "0", 2222 }, 2223 }, 2224 2225 Config: map[string]interface{}{}, 2226 2227 Diff: nil, 2228 2229 Err: false, 2230 }, 2231 2232 { 2233 Name: "Set element computed element", 2234 Schema: map[string]*Schema{ 2235 "ports": &Schema{ 2236 Type: TypeSet, 2237 Required: true, 2238 Elem: &Schema{Type: TypeInt}, 2239 Set: func(a interface{}) int { 2240 return a.(int) 2241 }, 2242 }, 2243 }, 2244 2245 State: nil, 2246 2247 Config: map[string]interface{}{ 2248 "ports": []interface{}{1, hcl2shim.UnknownVariableValue}, 2249 }, 2250 2251 Diff: &terraform.InstanceDiff{ 2252 Attributes: map[string]*terraform.ResourceAttrDiff{ 2253 "ports.#": &terraform.ResourceAttrDiff{ 2254 Old: "", 2255 New: "", 2256 NewComputed: true, 2257 }, 2258 }, 2259 }, 2260 2261 Err: false, 2262 }, 2263 2264 { 2265 Name: "Computed map without config that's known to be empty does not generate diff", 2266 Schema: map[string]*Schema{ 2267 "tags": &Schema{ 2268 Type: TypeMap, 2269 Computed: true, 2270 }, 2271 }, 2272 2273 Config: nil, 2274 2275 State: &terraform.InstanceState{ 2276 Attributes: map[string]string{ 2277 "tags.%": "0", 2278 }, 2279 }, 2280 2281 Diff: nil, 2282 2283 Err: false, 2284 }, 2285 2286 { 2287 Name: "Set with hyphen keys", 2288 Schema: map[string]*Schema{ 2289 "route": &Schema{ 2290 Type: TypeSet, 2291 Optional: true, 2292 Elem: &Resource{ 2293 Schema: map[string]*Schema{ 2294 "index": &Schema{ 2295 Type: TypeInt, 2296 Required: true, 2297 }, 2298 2299 "gateway-name": &Schema{ 2300 Type: TypeString, 2301 Optional: true, 2302 }, 2303 }, 2304 }, 2305 Set: func(v interface{}) int { 2306 m := v.(map[string]interface{}) 2307 return m["index"].(int) 2308 }, 2309 }, 2310 }, 2311 2312 State: nil, 2313 2314 Config: map[string]interface{}{ 2315 "route": []interface{}{ 2316 map[string]interface{}{ 2317 "index": "1", 2318 "gateway-name": "hello", 2319 }, 2320 }, 2321 }, 2322 2323 Diff: &terraform.InstanceDiff{ 2324 Attributes: map[string]*terraform.ResourceAttrDiff{ 2325 "route.#": &terraform.ResourceAttrDiff{ 2326 Old: "0", 2327 New: "1", 2328 }, 2329 "route.1.index": &terraform.ResourceAttrDiff{ 2330 Old: "", 2331 New: "1", 2332 }, 2333 "route.1.gateway-name": &terraform.ResourceAttrDiff{ 2334 Old: "", 2335 New: "hello", 2336 }, 2337 }, 2338 }, 2339 2340 Err: false, 2341 }, 2342 2343 { 2344 Name: ": StateFunc in nested set (#1759)", 2345 Schema: map[string]*Schema{ 2346 "service_account": &Schema{ 2347 Type: TypeList, 2348 Optional: true, 2349 ForceNew: true, 2350 Elem: &Resource{ 2351 Schema: map[string]*Schema{ 2352 "scopes": &Schema{ 2353 Type: TypeSet, 2354 Required: true, 2355 ForceNew: true, 2356 Elem: &Schema{ 2357 Type: TypeString, 2358 StateFunc: func(v interface{}) string { 2359 return v.(string) + "!" 2360 }, 2361 }, 2362 Set: func(v interface{}) int { 2363 i, err := strconv.Atoi(v.(string)) 2364 if err != nil { 2365 t.Fatalf("err: %s", err) 2366 } 2367 return i 2368 }, 2369 }, 2370 }, 2371 }, 2372 }, 2373 }, 2374 2375 State: nil, 2376 2377 Config: map[string]interface{}{ 2378 "service_account": []interface{}{ 2379 map[string]interface{}{ 2380 "scopes": []interface{}{"123"}, 2381 }, 2382 }, 2383 }, 2384 2385 Diff: &terraform.InstanceDiff{ 2386 Attributes: map[string]*terraform.ResourceAttrDiff{ 2387 "service_account.#": &terraform.ResourceAttrDiff{ 2388 Old: "0", 2389 New: "1", 2390 RequiresNew: true, 2391 }, 2392 "service_account.0.scopes.#": &terraform.ResourceAttrDiff{ 2393 Old: "0", 2394 New: "1", 2395 RequiresNew: true, 2396 }, 2397 "service_account.0.scopes.123": &terraform.ResourceAttrDiff{ 2398 Old: "", 2399 New: "123!", 2400 NewExtra: "123", 2401 RequiresNew: true, 2402 }, 2403 }, 2404 }, 2405 2406 Err: false, 2407 }, 2408 2409 { 2410 Name: "Removing set elements", 2411 Schema: map[string]*Schema{ 2412 "instances": &Schema{ 2413 Type: TypeSet, 2414 Elem: &Schema{Type: TypeString}, 2415 Optional: true, 2416 ForceNew: true, 2417 Set: func(v interface{}) int { 2418 return len(v.(string)) 2419 }, 2420 }, 2421 }, 2422 2423 State: &terraform.InstanceState{ 2424 Attributes: map[string]string{ 2425 "instances.#": "2", 2426 "instances.3": "333", 2427 "instances.2": "22", 2428 }, 2429 }, 2430 2431 Config: map[string]interface{}{ 2432 "instances": []interface{}{"333", "4444"}, 2433 }, 2434 2435 Diff: &terraform.InstanceDiff{ 2436 Attributes: map[string]*terraform.ResourceAttrDiff{ 2437 "instances.#": &terraform.ResourceAttrDiff{ 2438 Old: "2", 2439 New: "2", 2440 }, 2441 "instances.2": &terraform.ResourceAttrDiff{ 2442 Old: "22", 2443 New: "", 2444 NewRemoved: true, 2445 RequiresNew: true, 2446 }, 2447 "instances.3": &terraform.ResourceAttrDiff{ 2448 Old: "333", 2449 New: "333", 2450 }, 2451 "instances.4": &terraform.ResourceAttrDiff{ 2452 Old: "", 2453 New: "4444", 2454 RequiresNew: true, 2455 }, 2456 }, 2457 }, 2458 2459 Err: false, 2460 }, 2461 2462 { 2463 Name: "Bools can be set with 0/1 in config, still get true/false", 2464 Schema: map[string]*Schema{ 2465 "one": &Schema{ 2466 Type: TypeBool, 2467 Optional: true, 2468 }, 2469 "two": &Schema{ 2470 Type: TypeBool, 2471 Optional: true, 2472 }, 2473 "three": &Schema{ 2474 Type: TypeBool, 2475 Optional: true, 2476 }, 2477 }, 2478 2479 State: &terraform.InstanceState{ 2480 Attributes: map[string]string{ 2481 "one": "false", 2482 "two": "true", 2483 "three": "true", 2484 }, 2485 }, 2486 2487 Config: map[string]interface{}{ 2488 "one": "1", 2489 "two": "0", 2490 }, 2491 2492 Diff: &terraform.InstanceDiff{ 2493 Attributes: map[string]*terraform.ResourceAttrDiff{ 2494 "one": &terraform.ResourceAttrDiff{ 2495 Old: "false", 2496 New: "true", 2497 }, 2498 "two": &terraform.ResourceAttrDiff{ 2499 Old: "true", 2500 New: "false", 2501 }, 2502 "three": &terraform.ResourceAttrDiff{ 2503 Old: "true", 2504 New: "false", 2505 NewRemoved: true, 2506 }, 2507 }, 2508 }, 2509 2510 Err: false, 2511 }, 2512 2513 { 2514 Name: "tainted in state w/ no attr changes is still a replacement", 2515 Schema: map[string]*Schema{}, 2516 2517 State: &terraform.InstanceState{ 2518 Attributes: map[string]string{ 2519 "id": "someid", 2520 }, 2521 Tainted: true, 2522 }, 2523 2524 Config: map[string]interface{}{}, 2525 2526 Diff: &terraform.InstanceDiff{ 2527 Attributes: map[string]*terraform.ResourceAttrDiff{}, 2528 DestroyTainted: true, 2529 }, 2530 2531 Err: false, 2532 }, 2533 2534 { 2535 Name: "Set ForceNew only marks the changing element as ForceNew", 2536 Schema: map[string]*Schema{ 2537 "ports": &Schema{ 2538 Type: TypeSet, 2539 Required: true, 2540 ForceNew: true, 2541 Elem: &Schema{Type: TypeInt}, 2542 Set: func(a interface{}) int { 2543 return a.(int) 2544 }, 2545 }, 2546 }, 2547 2548 State: &terraform.InstanceState{ 2549 Attributes: map[string]string{ 2550 "ports.#": "3", 2551 "ports.1": "1", 2552 "ports.2": "2", 2553 "ports.4": "4", 2554 }, 2555 }, 2556 2557 Config: map[string]interface{}{ 2558 "ports": []interface{}{5, 2, 1}, 2559 }, 2560 2561 Diff: &terraform.InstanceDiff{ 2562 Attributes: map[string]*terraform.ResourceAttrDiff{ 2563 "ports.#": &terraform.ResourceAttrDiff{ 2564 Old: "3", 2565 New: "3", 2566 }, 2567 "ports.1": &terraform.ResourceAttrDiff{ 2568 Old: "1", 2569 New: "1", 2570 }, 2571 "ports.2": &terraform.ResourceAttrDiff{ 2572 Old: "2", 2573 New: "2", 2574 }, 2575 "ports.5": &terraform.ResourceAttrDiff{ 2576 Old: "", 2577 New: "5", 2578 RequiresNew: true, 2579 }, 2580 "ports.4": &terraform.ResourceAttrDiff{ 2581 Old: "4", 2582 New: "0", 2583 NewRemoved: true, 2584 RequiresNew: true, 2585 }, 2586 }, 2587 }, 2588 }, 2589 2590 { 2591 Name: "removed optional items should trigger ForceNew", 2592 Schema: map[string]*Schema{ 2593 "description": &Schema{ 2594 Type: TypeString, 2595 ForceNew: true, 2596 Optional: true, 2597 }, 2598 }, 2599 2600 State: &terraform.InstanceState{ 2601 Attributes: map[string]string{ 2602 "description": "foo", 2603 }, 2604 }, 2605 2606 Config: map[string]interface{}{}, 2607 2608 Diff: &terraform.InstanceDiff{ 2609 Attributes: map[string]*terraform.ResourceAttrDiff{ 2610 "description": &terraform.ResourceAttrDiff{ 2611 Old: "foo", 2612 New: "", 2613 RequiresNew: true, 2614 NewRemoved: true, 2615 }, 2616 }, 2617 }, 2618 2619 Err: false, 2620 }, 2621 2622 // GH-7715 2623 { 2624 Name: "computed value for boolean field", 2625 Schema: map[string]*Schema{ 2626 "foo": &Schema{ 2627 Type: TypeBool, 2628 ForceNew: true, 2629 Computed: true, 2630 Optional: true, 2631 }, 2632 }, 2633 2634 State: &terraform.InstanceState{}, 2635 2636 Config: map[string]interface{}{ 2637 "foo": hcl2shim.UnknownVariableValue, 2638 }, 2639 2640 Diff: &terraform.InstanceDiff{ 2641 Attributes: map[string]*terraform.ResourceAttrDiff{ 2642 "foo": &terraform.ResourceAttrDiff{ 2643 Old: "", 2644 New: "false", 2645 NewComputed: true, 2646 RequiresNew: true, 2647 }, 2648 }, 2649 }, 2650 2651 Err: false, 2652 }, 2653 2654 { 2655 Name: "Set ForceNew marks count as ForceNew if computed", 2656 Schema: map[string]*Schema{ 2657 "ports": &Schema{ 2658 Type: TypeSet, 2659 Required: true, 2660 ForceNew: true, 2661 Elem: &Schema{Type: TypeInt}, 2662 Set: func(a interface{}) int { 2663 return a.(int) 2664 }, 2665 }, 2666 }, 2667 2668 State: &terraform.InstanceState{ 2669 Attributes: map[string]string{ 2670 "ports.#": "3", 2671 "ports.1": "1", 2672 "ports.2": "2", 2673 "ports.4": "4", 2674 }, 2675 }, 2676 2677 Config: map[string]interface{}{ 2678 "ports": []interface{}{hcl2shim.UnknownVariableValue, 2, 1}, 2679 }, 2680 2681 Diff: &terraform.InstanceDiff{ 2682 Attributes: map[string]*terraform.ResourceAttrDiff{ 2683 "ports.#": &terraform.ResourceAttrDiff{ 2684 Old: "3", 2685 New: "", 2686 NewComputed: true, 2687 RequiresNew: true, 2688 }, 2689 }, 2690 }, 2691 }, 2692 2693 { 2694 Name: "List with computed schema and ForceNew", 2695 Schema: map[string]*Schema{ 2696 "config": &Schema{ 2697 Type: TypeList, 2698 Optional: true, 2699 ForceNew: true, 2700 Elem: &Schema{ 2701 Type: TypeString, 2702 }, 2703 }, 2704 }, 2705 2706 State: &terraform.InstanceState{ 2707 Attributes: map[string]string{ 2708 "config.#": "2", 2709 "config.0": "a", 2710 "config.1": "b", 2711 }, 2712 }, 2713 2714 Config: map[string]interface{}{ 2715 "config": []interface{}{hcl2shim.UnknownVariableValue, hcl2shim.UnknownVariableValue}, 2716 }, 2717 2718 Diff: &terraform.InstanceDiff{ 2719 Attributes: map[string]*terraform.ResourceAttrDiff{ 2720 "config.#": &terraform.ResourceAttrDiff{ 2721 Old: "2", 2722 New: "", 2723 RequiresNew: true, 2724 NewComputed: true, 2725 }, 2726 }, 2727 }, 2728 2729 Err: false, 2730 }, 2731 2732 { 2733 Name: "overridden diff with a CustomizeDiff function, ForceNew not in schema", 2734 Schema: map[string]*Schema{ 2735 "availability_zone": &Schema{ 2736 Type: TypeString, 2737 Optional: true, 2738 Computed: true, 2739 }, 2740 }, 2741 2742 State: nil, 2743 2744 Config: map[string]interface{}{ 2745 "availability_zone": "foo", 2746 }, 2747 2748 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2749 if err := d.SetNew("availability_zone", "bar"); err != nil { 2750 return err 2751 } 2752 if err := d.ForceNew("availability_zone"); err != nil { 2753 return err 2754 } 2755 return nil 2756 }, 2757 2758 Diff: &terraform.InstanceDiff{ 2759 Attributes: map[string]*terraform.ResourceAttrDiff{ 2760 "availability_zone": &terraform.ResourceAttrDiff{ 2761 Old: "", 2762 New: "bar", 2763 RequiresNew: true, 2764 }, 2765 }, 2766 }, 2767 2768 Err: false, 2769 }, 2770 2771 { 2772 // NOTE: This case is technically impossible in the current 2773 // implementation, because optional+computed values never show up in the 2774 // diff. In the event behavior changes this test should ensure that the 2775 // intended diff still shows up. 2776 Name: "overridden removed attribute diff with a CustomizeDiff function, ForceNew not in schema", 2777 Schema: map[string]*Schema{ 2778 "availability_zone": &Schema{ 2779 Type: TypeString, 2780 Optional: true, 2781 Computed: true, 2782 }, 2783 }, 2784 2785 State: nil, 2786 2787 Config: map[string]interface{}{}, 2788 2789 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2790 if err := d.SetNew("availability_zone", "bar"); err != nil { 2791 return err 2792 } 2793 if err := d.ForceNew("availability_zone"); err != nil { 2794 return err 2795 } 2796 return nil 2797 }, 2798 2799 Diff: &terraform.InstanceDiff{ 2800 Attributes: map[string]*terraform.ResourceAttrDiff{ 2801 "availability_zone": &terraform.ResourceAttrDiff{ 2802 Old: "", 2803 New: "bar", 2804 RequiresNew: true, 2805 }, 2806 }, 2807 }, 2808 2809 Err: false, 2810 }, 2811 2812 { 2813 2814 Name: "overridden diff with a CustomizeDiff function, ForceNew in schema", 2815 Schema: map[string]*Schema{ 2816 "availability_zone": &Schema{ 2817 Type: TypeString, 2818 Optional: true, 2819 Computed: true, 2820 ForceNew: true, 2821 }, 2822 }, 2823 2824 State: nil, 2825 2826 Config: map[string]interface{}{ 2827 "availability_zone": "foo", 2828 }, 2829 2830 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2831 if err := d.SetNew("availability_zone", "bar"); err != nil { 2832 return err 2833 } 2834 return nil 2835 }, 2836 2837 Diff: &terraform.InstanceDiff{ 2838 Attributes: map[string]*terraform.ResourceAttrDiff{ 2839 "availability_zone": &terraform.ResourceAttrDiff{ 2840 Old: "", 2841 New: "bar", 2842 RequiresNew: true, 2843 }, 2844 }, 2845 }, 2846 2847 Err: false, 2848 }, 2849 2850 { 2851 Name: "required field with computed diff added with CustomizeDiff function", 2852 Schema: map[string]*Schema{ 2853 "ami_id": &Schema{ 2854 Type: TypeString, 2855 Required: true, 2856 }, 2857 "instance_id": &Schema{ 2858 Type: TypeString, 2859 Computed: true, 2860 }, 2861 }, 2862 2863 State: nil, 2864 2865 Config: map[string]interface{}{ 2866 "ami_id": "foo", 2867 }, 2868 2869 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2870 if err := d.SetNew("instance_id", "bar"); err != nil { 2871 return err 2872 } 2873 return nil 2874 }, 2875 2876 Diff: &terraform.InstanceDiff{ 2877 Attributes: map[string]*terraform.ResourceAttrDiff{ 2878 "ami_id": &terraform.ResourceAttrDiff{ 2879 Old: "", 2880 New: "foo", 2881 }, 2882 "instance_id": &terraform.ResourceAttrDiff{ 2883 Old: "", 2884 New: "bar", 2885 }, 2886 }, 2887 }, 2888 2889 Err: false, 2890 }, 2891 2892 { 2893 Name: "Set ForceNew only marks the changing element as ForceNew - CustomizeDiffFunc edition", 2894 Schema: map[string]*Schema{ 2895 "ports": &Schema{ 2896 Type: TypeSet, 2897 Optional: true, 2898 Computed: true, 2899 Elem: &Schema{Type: TypeInt}, 2900 Set: func(a interface{}) int { 2901 return a.(int) 2902 }, 2903 }, 2904 }, 2905 2906 State: &terraform.InstanceState{ 2907 Attributes: map[string]string{ 2908 "ports.#": "3", 2909 "ports.1": "1", 2910 "ports.2": "2", 2911 "ports.4": "4", 2912 }, 2913 }, 2914 2915 Config: map[string]interface{}{ 2916 "ports": []interface{}{5, 2, 6}, 2917 }, 2918 2919 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2920 if err := d.SetNew("ports", []interface{}{5, 2, 1}); err != nil { 2921 return err 2922 } 2923 if err := d.ForceNew("ports"); err != nil { 2924 return err 2925 } 2926 return nil 2927 }, 2928 2929 Diff: &terraform.InstanceDiff{ 2930 Attributes: map[string]*terraform.ResourceAttrDiff{ 2931 "ports.#": &terraform.ResourceAttrDiff{ 2932 Old: "3", 2933 New: "3", 2934 }, 2935 "ports.1": &terraform.ResourceAttrDiff{ 2936 Old: "1", 2937 New: "1", 2938 }, 2939 "ports.2": &terraform.ResourceAttrDiff{ 2940 Old: "2", 2941 New: "2", 2942 }, 2943 "ports.5": &terraform.ResourceAttrDiff{ 2944 Old: "", 2945 New: "5", 2946 RequiresNew: true, 2947 }, 2948 "ports.4": &terraform.ResourceAttrDiff{ 2949 Old: "4", 2950 New: "0", 2951 NewRemoved: true, 2952 RequiresNew: true, 2953 }, 2954 }, 2955 }, 2956 }, 2957 2958 { 2959 Name: "tainted resource does not run CustomizeDiffFunc", 2960 Schema: map[string]*Schema{}, 2961 2962 State: &terraform.InstanceState{ 2963 Attributes: map[string]string{ 2964 "id": "someid", 2965 }, 2966 Tainted: true, 2967 }, 2968 2969 Config: map[string]interface{}{}, 2970 2971 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 2972 return errors.New("diff customization should not have run") 2973 }, 2974 2975 Diff: &terraform.InstanceDiff{ 2976 Attributes: map[string]*terraform.ResourceAttrDiff{}, 2977 DestroyTainted: true, 2978 }, 2979 2980 Err: false, 2981 }, 2982 2983 { 2984 Name: "NewComputed based on a conditional with CustomizeDiffFunc", 2985 Schema: map[string]*Schema{ 2986 "etag": &Schema{ 2987 Type: TypeString, 2988 Optional: true, 2989 Computed: true, 2990 }, 2991 "version_id": &Schema{ 2992 Type: TypeString, 2993 Computed: true, 2994 }, 2995 }, 2996 2997 State: &terraform.InstanceState{ 2998 Attributes: map[string]string{ 2999 "etag": "foo", 3000 "version_id": "1", 3001 }, 3002 }, 3003 3004 Config: map[string]interface{}{ 3005 "etag": "bar", 3006 }, 3007 3008 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3009 if d.HasChange("etag") { 3010 d.SetNewComputed("version_id") 3011 } 3012 return nil 3013 }, 3014 3015 Diff: &terraform.InstanceDiff{ 3016 Attributes: map[string]*terraform.ResourceAttrDiff{ 3017 "etag": &terraform.ResourceAttrDiff{ 3018 Old: "foo", 3019 New: "bar", 3020 }, 3021 "version_id": &terraform.ResourceAttrDiff{ 3022 Old: "1", 3023 New: "", 3024 NewComputed: true, 3025 }, 3026 }, 3027 }, 3028 3029 Err: false, 3030 }, 3031 3032 { 3033 Name: "NewComputed should always propagate with CustomizeDiff", 3034 Schema: map[string]*Schema{ 3035 "foo": &Schema{ 3036 Type: TypeString, 3037 Computed: true, 3038 }, 3039 }, 3040 3041 State: &terraform.InstanceState{ 3042 Attributes: map[string]string{ 3043 "foo": "", 3044 }, 3045 ID: "pre-existing", 3046 }, 3047 3048 Config: map[string]interface{}{}, 3049 3050 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3051 d.SetNewComputed("foo") 3052 return nil 3053 }, 3054 3055 Diff: &terraform.InstanceDiff{ 3056 Attributes: map[string]*terraform.ResourceAttrDiff{ 3057 "foo": &terraform.ResourceAttrDiff{ 3058 NewComputed: true, 3059 }, 3060 }, 3061 }, 3062 3063 Err: false, 3064 }, 3065 3066 { 3067 Name: "vetoing a diff", 3068 Schema: map[string]*Schema{ 3069 "foo": &Schema{ 3070 Type: TypeString, 3071 Optional: true, 3072 Computed: true, 3073 }, 3074 }, 3075 3076 State: &terraform.InstanceState{ 3077 Attributes: map[string]string{ 3078 "foo": "bar", 3079 }, 3080 }, 3081 3082 Config: map[string]interface{}{ 3083 "foo": "baz", 3084 }, 3085 3086 CustomizeDiff: func(d *ResourceDiff, meta interface{}) error { 3087 return fmt.Errorf("diff vetoed") 3088 }, 3089 3090 Err: true, 3091 }, 3092 3093 // A lot of resources currently depended on using the empty string as a 3094 // nil/unset value. 3095 // FIXME: We want this to eventually produce a diff, since there 3096 // technically is a new value in the config. 3097 { 3098 Name: "optional, computed, empty string", 3099 Schema: map[string]*Schema{ 3100 "attr": &Schema{ 3101 Type: TypeString, 3102 Optional: true, 3103 Computed: true, 3104 }, 3105 }, 3106 3107 State: &terraform.InstanceState{ 3108 Attributes: map[string]string{ 3109 "attr": "bar", 3110 }, 3111 }, 3112 3113 Config: map[string]interface{}{ 3114 "attr": "", 3115 }, 3116 }, 3117 3118 { 3119 Name: "optional, computed, empty string should not crash in CustomizeDiff", 3120 Schema: map[string]*Schema{ 3121 "unrelated_set": { 3122 Type: TypeSet, 3123 Optional: true, 3124 Elem: &Schema{Type: TypeString}, 3125 }, 3126 "stream_enabled": { 3127 Type: TypeBool, 3128 Optional: true, 3129 }, 3130 "stream_view_type": { 3131 Type: TypeString, 3132 Optional: true, 3133 Computed: true, 3134 }, 3135 }, 3136 3137 State: &terraform.InstanceState{ 3138 Attributes: map[string]string{ 3139 "unrelated_set.#": "0", 3140 "stream_enabled": "true", 3141 "stream_view_type": "KEYS_ONLY", 3142 }, 3143 }, 3144 Config: map[string]interface{}{ 3145 "stream_enabled": false, 3146 "stream_view_type": "", 3147 }, 3148 CustomizeDiff: func(diff *ResourceDiff, v interface{}) error { 3149 v, ok := diff.GetOk("unrelated_set") 3150 if ok { 3151 return fmt.Errorf("Didn't expect unrelated_set: %#v", v) 3152 } 3153 return nil 3154 }, 3155 Diff: &terraform.InstanceDiff{ 3156 Attributes: map[string]*terraform.ResourceAttrDiff{ 3157 "stream_enabled": { 3158 Old: "true", 3159 New: "false", 3160 }, 3161 }, 3162 }, 3163 }, 3164 } 3165 3166 for i, tc := range cases { 3167 t.Run(fmt.Sprintf("%d-%s", i, tc.Name), func(t *testing.T) { 3168 c := terraform.NewResourceConfigRaw(tc.Config) 3169 3170 d, err := schemaMap(tc.Schema).Diff(tc.State, c, tc.CustomizeDiff, nil, true) 3171 if err != nil != tc.Err { 3172 t.Fatalf("err: %s", err) 3173 } 3174 3175 if !reflect.DeepEqual(tc.Diff, d) { 3176 t.Fatalf("expected:\n%#v\n\ngot:\n%#v", tc.Diff, d) 3177 } 3178 }) 3179 } 3180 } 3181 3182 func TestSchemaMap_Input(t *testing.T) { 3183 cases := map[string]struct { 3184 Schema map[string]*Schema 3185 Config map[string]interface{} 3186 Input map[string]string 3187 Result map[string]interface{} 3188 Err bool 3189 }{ 3190 /* 3191 * String decode 3192 */ 3193 3194 "no input on optional field with no config": { 3195 Schema: map[string]*Schema{ 3196 "availability_zone": &Schema{ 3197 Type: TypeString, 3198 Optional: true, 3199 }, 3200 }, 3201 3202 Input: map[string]string{}, 3203 Result: map[string]interface{}{}, 3204 Err: false, 3205 }, 3206 3207 "input ignored when config has a value": { 3208 Schema: map[string]*Schema{ 3209 "availability_zone": &Schema{ 3210 Type: TypeString, 3211 Optional: true, 3212 }, 3213 }, 3214 3215 Config: map[string]interface{}{ 3216 "availability_zone": "bar", 3217 }, 3218 3219 Input: map[string]string{ 3220 "availability_zone": "foo", 3221 }, 3222 3223 Result: map[string]interface{}{}, 3224 3225 Err: false, 3226 }, 3227 3228 "input ignored when schema has a default": { 3229 Schema: map[string]*Schema{ 3230 "availability_zone": &Schema{ 3231 Type: TypeString, 3232 Default: "foo", 3233 Optional: true, 3234 }, 3235 }, 3236 3237 Input: map[string]string{ 3238 "availability_zone": "bar", 3239 }, 3240 3241 Result: map[string]interface{}{}, 3242 3243 Err: false, 3244 }, 3245 3246 "input ignored when default function returns a value": { 3247 Schema: map[string]*Schema{ 3248 "availability_zone": &Schema{ 3249 Type: TypeString, 3250 DefaultFunc: func() (interface{}, error) { 3251 return "foo", nil 3252 }, 3253 Optional: true, 3254 }, 3255 }, 3256 3257 Input: map[string]string{ 3258 "availability_zone": "bar", 3259 }, 3260 3261 Result: map[string]interface{}{}, 3262 3263 Err: false, 3264 }, 3265 3266 "input ignored when default function returns an empty string": { 3267 Schema: map[string]*Schema{ 3268 "availability_zone": &Schema{ 3269 Type: TypeString, 3270 Default: "", 3271 Optional: true, 3272 }, 3273 }, 3274 3275 Input: map[string]string{ 3276 "availability_zone": "bar", 3277 }, 3278 3279 Result: map[string]interface{}{}, 3280 3281 Err: false, 3282 }, 3283 3284 "input used when default function returns nil": { 3285 Schema: map[string]*Schema{ 3286 "availability_zone": &Schema{ 3287 Type: TypeString, 3288 DefaultFunc: func() (interface{}, error) { 3289 return nil, nil 3290 }, 3291 Required: true, 3292 }, 3293 }, 3294 3295 Input: map[string]string{ 3296 "availability_zone": "bar", 3297 }, 3298 3299 Result: map[string]interface{}{ 3300 "availability_zone": "bar", 3301 }, 3302 3303 Err: false, 3304 }, 3305 3306 "input not used when optional default function returns nil": { 3307 Schema: map[string]*Schema{ 3308 "availability_zone": &Schema{ 3309 Type: TypeString, 3310 DefaultFunc: func() (interface{}, error) { 3311 return nil, nil 3312 }, 3313 Optional: true, 3314 }, 3315 }, 3316 3317 Input: map[string]string{}, 3318 Result: map[string]interface{}{}, 3319 Err: false, 3320 }, 3321 } 3322 3323 for i, tc := range cases { 3324 if tc.Config == nil { 3325 tc.Config = make(map[string]interface{}) 3326 } 3327 3328 input := new(terraform.MockUIInput) 3329 input.InputReturnMap = tc.Input 3330 3331 rc := terraform.NewResourceConfigRaw(tc.Config) 3332 rc.Config = make(map[string]interface{}) 3333 3334 actual, err := schemaMap(tc.Schema).Input(input, rc) 3335 if err != nil != tc.Err { 3336 t.Fatalf("#%v err: %s", i, err) 3337 } 3338 3339 if !reflect.DeepEqual(tc.Result, actual.Config) { 3340 t.Fatalf("#%v: bad:\n\ngot: %#v\nexpected: %#v", i, actual.Config, tc.Result) 3341 } 3342 } 3343 } 3344 3345 func TestSchemaMap_InputDefault(t *testing.T) { 3346 emptyConfig := make(map[string]interface{}) 3347 rc := terraform.NewResourceConfigRaw(emptyConfig) 3348 rc.Config = make(map[string]interface{}) 3349 3350 input := new(terraform.MockUIInput) 3351 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 3352 t.Fatalf("InputFn should not be called on: %#v", opts) 3353 return "", nil 3354 } 3355 3356 schema := map[string]*Schema{ 3357 "availability_zone": &Schema{ 3358 Type: TypeString, 3359 Default: "foo", 3360 Optional: true, 3361 }, 3362 } 3363 actual, err := schemaMap(schema).Input(input, rc) 3364 if err != nil { 3365 t.Fatalf("err: %s", err) 3366 } 3367 3368 expected := map[string]interface{}{} 3369 3370 if !reflect.DeepEqual(expected, actual.Config) { 3371 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 3372 } 3373 } 3374 3375 func TestSchemaMap_InputDeprecated(t *testing.T) { 3376 emptyConfig := make(map[string]interface{}) 3377 rc := terraform.NewResourceConfigRaw(emptyConfig) 3378 rc.Config = make(map[string]interface{}) 3379 3380 input := new(terraform.MockUIInput) 3381 input.InputFn = func(opts *terraform.InputOpts) (string, error) { 3382 t.Fatalf("InputFn should not be called on: %#v", opts) 3383 return "", nil 3384 } 3385 3386 schema := map[string]*Schema{ 3387 "availability_zone": &Schema{ 3388 Type: TypeString, 3389 Deprecated: "long gone", 3390 Optional: true, 3391 }, 3392 } 3393 actual, err := schemaMap(schema).Input(input, rc) 3394 if err != nil { 3395 t.Fatalf("err: %s", err) 3396 } 3397 3398 expected := map[string]interface{}{} 3399 3400 if !reflect.DeepEqual(expected, actual.Config) { 3401 t.Fatalf("got: %#v\nexpected: %#v", actual.Config, expected) 3402 } 3403 } 3404 3405 func TestSchemaMap_InternalValidate(t *testing.T) { 3406 cases := map[string]struct { 3407 In map[string]*Schema 3408 Err bool 3409 }{ 3410 "nothing": { 3411 nil, 3412 false, 3413 }, 3414 3415 "Both optional and required": { 3416 map[string]*Schema{ 3417 "foo": &Schema{ 3418 Type: TypeInt, 3419 Optional: true, 3420 Required: true, 3421 }, 3422 }, 3423 true, 3424 }, 3425 3426 "No optional and no required": { 3427 map[string]*Schema{ 3428 "foo": &Schema{ 3429 Type: TypeInt, 3430 }, 3431 }, 3432 true, 3433 }, 3434 3435 "Missing Type": { 3436 map[string]*Schema{ 3437 "foo": &Schema{ 3438 Required: true, 3439 }, 3440 }, 3441 true, 3442 }, 3443 3444 "Required but computed": { 3445 map[string]*Schema{ 3446 "foo": &Schema{ 3447 Type: TypeInt, 3448 Required: true, 3449 Computed: true, 3450 }, 3451 }, 3452 true, 3453 }, 3454 3455 "Looks good": { 3456 map[string]*Schema{ 3457 "foo": &Schema{ 3458 Type: TypeString, 3459 Required: true, 3460 }, 3461 }, 3462 false, 3463 }, 3464 3465 "Computed but has default": { 3466 map[string]*Schema{ 3467 "foo": &Schema{ 3468 Type: TypeInt, 3469 Optional: true, 3470 Computed: true, 3471 Default: "foo", 3472 }, 3473 }, 3474 true, 3475 }, 3476 3477 "Required but has default": { 3478 map[string]*Schema{ 3479 "foo": &Schema{ 3480 Type: TypeInt, 3481 Optional: true, 3482 Required: true, 3483 Default: "foo", 3484 }, 3485 }, 3486 true, 3487 }, 3488 3489 "List element not set": { 3490 map[string]*Schema{ 3491 "foo": &Schema{ 3492 Type: TypeList, 3493 }, 3494 }, 3495 true, 3496 }, 3497 3498 "List default": { 3499 map[string]*Schema{ 3500 "foo": &Schema{ 3501 Type: TypeList, 3502 Elem: &Schema{Type: TypeInt}, 3503 Default: "foo", 3504 }, 3505 }, 3506 true, 3507 }, 3508 3509 "List element computed": { 3510 map[string]*Schema{ 3511 "foo": &Schema{ 3512 Type: TypeList, 3513 Optional: true, 3514 Elem: &Schema{ 3515 Type: TypeInt, 3516 Computed: true, 3517 }, 3518 }, 3519 }, 3520 true, 3521 }, 3522 3523 "List element with Set set": { 3524 map[string]*Schema{ 3525 "foo": &Schema{ 3526 Type: TypeList, 3527 Elem: &Schema{Type: TypeInt}, 3528 Set: func(interface{}) int { return 0 }, 3529 Optional: true, 3530 }, 3531 }, 3532 true, 3533 }, 3534 3535 "Set element with no Set set": { 3536 map[string]*Schema{ 3537 "foo": &Schema{ 3538 Type: TypeSet, 3539 Elem: &Schema{Type: TypeInt}, 3540 Optional: true, 3541 }, 3542 }, 3543 false, 3544 }, 3545 3546 "Required but computedWhen": { 3547 map[string]*Schema{ 3548 "foo": &Schema{ 3549 Type: TypeInt, 3550 Required: true, 3551 ComputedWhen: []string{"foo"}, 3552 }, 3553 }, 3554 true, 3555 }, 3556 3557 "Conflicting attributes cannot be required": { 3558 map[string]*Schema{ 3559 "a": &Schema{ 3560 Type: TypeBool, 3561 Required: true, 3562 }, 3563 "b": &Schema{ 3564 Type: TypeBool, 3565 Optional: true, 3566 ConflictsWith: []string{"a"}, 3567 }, 3568 }, 3569 true, 3570 }, 3571 3572 "Attribute with conflicts cannot be required": { 3573 map[string]*Schema{ 3574 "b": &Schema{ 3575 Type: TypeBool, 3576 Required: true, 3577 ConflictsWith: []string{"a"}, 3578 }, 3579 }, 3580 true, 3581 }, 3582 3583 "ConflictsWith cannot be used w/ ComputedWhen": { 3584 map[string]*Schema{ 3585 "a": &Schema{ 3586 Type: TypeBool, 3587 ComputedWhen: []string{"foor"}, 3588 }, 3589 "b": &Schema{ 3590 Type: TypeBool, 3591 Required: true, 3592 ConflictsWith: []string{"a"}, 3593 }, 3594 }, 3595 true, 3596 }, 3597 3598 "Sub-resource invalid": { 3599 map[string]*Schema{ 3600 "foo": &Schema{ 3601 Type: TypeList, 3602 Optional: true, 3603 Elem: &Resource{ 3604 Schema: map[string]*Schema{ 3605 "foo": new(Schema), 3606 }, 3607 }, 3608 }, 3609 }, 3610 true, 3611 }, 3612 3613 "Sub-resource valid": { 3614 map[string]*Schema{ 3615 "foo": &Schema{ 3616 Type: TypeList, 3617 Optional: true, 3618 Elem: &Resource{ 3619 Schema: map[string]*Schema{ 3620 "foo": &Schema{ 3621 Type: TypeInt, 3622 Optional: true, 3623 }, 3624 }, 3625 }, 3626 }, 3627 }, 3628 false, 3629 }, 3630 3631 "ValidateFunc on non-primitive": { 3632 map[string]*Schema{ 3633 "foo": &Schema{ 3634 Type: TypeSet, 3635 Required: true, 3636 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3637 return 3638 }, 3639 }, 3640 }, 3641 true, 3642 }, 3643 3644 "computed-only field with validateFunc": { 3645 map[string]*Schema{ 3646 "string": &Schema{ 3647 Type: TypeString, 3648 Computed: true, 3649 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 3650 es = append(es, fmt.Errorf("this is not fine")) 3651 return 3652 }, 3653 }, 3654 }, 3655 true, 3656 }, 3657 3658 "computed-only field with diffSuppressFunc": { 3659 map[string]*Schema{ 3660 "string": &Schema{ 3661 Type: TypeString, 3662 Computed: true, 3663 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3664 // Always suppress any diff 3665 return false 3666 }, 3667 }, 3668 }, 3669 true, 3670 }, 3671 3672 "invalid field name format #1": { 3673 map[string]*Schema{ 3674 "with space": &Schema{ 3675 Type: TypeString, 3676 Optional: true, 3677 }, 3678 }, 3679 true, 3680 }, 3681 3682 "invalid field name format #2": { 3683 map[string]*Schema{ 3684 "WithCapitals": &Schema{ 3685 Type: TypeString, 3686 Optional: true, 3687 }, 3688 }, 3689 true, 3690 }, 3691 3692 "invalid field name format of a Deprecated field": { 3693 map[string]*Schema{ 3694 "WithCapitals": &Schema{ 3695 Type: TypeString, 3696 Optional: true, 3697 Deprecated: "Use with_underscores instead", 3698 }, 3699 }, 3700 false, 3701 }, 3702 3703 "invalid field name format of a Removed field": { 3704 map[string]*Schema{ 3705 "WithCapitals": &Schema{ 3706 Type: TypeString, 3707 Optional: true, 3708 Removed: "Use with_underscores instead", 3709 }, 3710 }, 3711 false, 3712 }, 3713 3714 "ConfigModeBlock with Elem *Resource": { 3715 map[string]*Schema{ 3716 "block": &Schema{ 3717 Type: TypeList, 3718 ConfigMode: SchemaConfigModeBlock, 3719 Optional: true, 3720 Elem: &Resource{}, 3721 }, 3722 }, 3723 false, 3724 }, 3725 3726 "ConfigModeBlock Computed with Elem *Resource": { 3727 map[string]*Schema{ 3728 "block": &Schema{ 3729 Type: TypeList, 3730 ConfigMode: SchemaConfigModeBlock, 3731 Computed: true, 3732 Elem: &Resource{}, 3733 }, 3734 }, 3735 true, // ConfigMode of block cannot be used for computed schema 3736 }, 3737 3738 "ConfigModeBlock with Elem *Schema": { 3739 map[string]*Schema{ 3740 "block": &Schema{ 3741 Type: TypeList, 3742 ConfigMode: SchemaConfigModeBlock, 3743 Optional: true, 3744 Elem: &Schema{ 3745 Type: TypeString, 3746 }, 3747 }, 3748 }, 3749 true, 3750 }, 3751 3752 "ConfigModeBlock with no Elem": { 3753 map[string]*Schema{ 3754 "block": &Schema{ 3755 Type: TypeString, 3756 ConfigMode: SchemaConfigModeBlock, 3757 Optional: true, 3758 }, 3759 }, 3760 true, 3761 }, 3762 3763 "ConfigModeBlock inside ConfigModeAttr": { 3764 map[string]*Schema{ 3765 "block": &Schema{ 3766 Type: TypeList, 3767 ConfigMode: SchemaConfigModeAttr, 3768 Optional: true, 3769 Elem: &Resource{ 3770 Schema: map[string]*Schema{ 3771 "sub": &Schema{ 3772 Type: TypeList, 3773 ConfigMode: SchemaConfigModeBlock, 3774 Elem: &Resource{}, 3775 }, 3776 }, 3777 }, 3778 }, 3779 }, 3780 true, // ConfigMode of block cannot be used in child of schema with ConfigMode of attribute 3781 }, 3782 3783 "ConfigModeAuto with *Resource inside ConfigModeAttr": { 3784 map[string]*Schema{ 3785 "block": &Schema{ 3786 Type: TypeList, 3787 ConfigMode: SchemaConfigModeAttr, 3788 Optional: true, 3789 Elem: &Resource{ 3790 Schema: map[string]*Schema{ 3791 "sub": &Schema{ 3792 Type: TypeList, 3793 Elem: &Resource{}, 3794 }, 3795 }, 3796 }, 3797 }, 3798 }, 3799 true, // in *schema.Resource with ConfigMode of attribute, so must also have ConfigMode of attribute 3800 }, 3801 } 3802 3803 for tn, tc := range cases { 3804 t.Run(tn, func(t *testing.T) { 3805 err := schemaMap(tc.In).InternalValidate(nil) 3806 if err != nil != tc.Err { 3807 if tc.Err { 3808 t.Fatalf("%q: Expected error did not occur:\n\n%#v", tn, tc.In) 3809 } 3810 t.Fatalf("%q: Unexpected error occurred: %s\n\n%#v", tn, err, tc.In) 3811 } 3812 }) 3813 } 3814 3815 } 3816 3817 func TestSchemaMap_DiffSuppress(t *testing.T) { 3818 cases := map[string]struct { 3819 Schema map[string]*Schema 3820 State *terraform.InstanceState 3821 Config map[string]interface{} 3822 ExpectedDiff *terraform.InstanceDiff 3823 Err bool 3824 }{ 3825 "#0 - Suppress otherwise valid diff by returning true": { 3826 Schema: map[string]*Schema{ 3827 "availability_zone": { 3828 Type: TypeString, 3829 Optional: true, 3830 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3831 // Always suppress any diff 3832 return true 3833 }, 3834 }, 3835 }, 3836 3837 State: nil, 3838 3839 Config: map[string]interface{}{ 3840 "availability_zone": "foo", 3841 }, 3842 3843 ExpectedDiff: nil, 3844 3845 Err: false, 3846 }, 3847 3848 "#1 - Don't suppress diff by returning false": { 3849 Schema: map[string]*Schema{ 3850 "availability_zone": { 3851 Type: TypeString, 3852 Optional: true, 3853 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3854 // Always suppress any diff 3855 return false 3856 }, 3857 }, 3858 }, 3859 3860 State: nil, 3861 3862 Config: map[string]interface{}{ 3863 "availability_zone": "foo", 3864 }, 3865 3866 ExpectedDiff: &terraform.InstanceDiff{ 3867 Attributes: map[string]*terraform.ResourceAttrDiff{ 3868 "availability_zone": { 3869 Old: "", 3870 New: "foo", 3871 }, 3872 }, 3873 }, 3874 3875 Err: false, 3876 }, 3877 3878 "Default with suppress makes no diff": { 3879 Schema: map[string]*Schema{ 3880 "availability_zone": { 3881 Type: TypeString, 3882 Optional: true, 3883 Default: "foo", 3884 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3885 return true 3886 }, 3887 }, 3888 }, 3889 3890 State: nil, 3891 3892 Config: map[string]interface{}{}, 3893 3894 ExpectedDiff: nil, 3895 3896 Err: false, 3897 }, 3898 3899 "Default with false suppress makes diff": { 3900 Schema: map[string]*Schema{ 3901 "availability_zone": { 3902 Type: TypeString, 3903 Optional: true, 3904 Default: "foo", 3905 DiffSuppressFunc: func(k, old, new string, d *ResourceData) bool { 3906 return false 3907 }, 3908 }, 3909 }, 3910 3911 State: nil, 3912 3913 Config: map[string]interface{}{}, 3914 3915 ExpectedDiff: &terraform.InstanceDiff{ 3916 Attributes: map[string]*terraform.ResourceAttrDiff{ 3917 "availability_zone": { 3918 Old: "", 3919 New: "foo", 3920 }, 3921 }, 3922 }, 3923 3924 Err: false, 3925 }, 3926 3927 "Complex structure with set of computed string should mark root set as computed": { 3928 Schema: map[string]*Schema{ 3929 "outer": &Schema{ 3930 Type: TypeSet, 3931 Optional: true, 3932 Elem: &Resource{ 3933 Schema: map[string]*Schema{ 3934 "outer_str": &Schema{ 3935 Type: TypeString, 3936 Optional: true, 3937 }, 3938 "inner": &Schema{ 3939 Type: TypeSet, 3940 Optional: true, 3941 Elem: &Resource{ 3942 Schema: map[string]*Schema{ 3943 "inner_str": &Schema{ 3944 Type: TypeString, 3945 Optional: true, 3946 }, 3947 }, 3948 }, 3949 Set: func(v interface{}) int { 3950 return 2 3951 }, 3952 }, 3953 }, 3954 }, 3955 Set: func(v interface{}) int { 3956 return 1 3957 }, 3958 }, 3959 }, 3960 3961 State: nil, 3962 3963 Config: map[string]interface{}{ 3964 "outer": []interface{}{ 3965 map[string]interface{}{ 3966 "outer_str": "foo", 3967 "inner": []interface{}{ 3968 map[string]interface{}{ 3969 "inner_str": hcl2shim.UnknownVariableValue, 3970 }, 3971 }, 3972 }, 3973 }, 3974 }, 3975 3976 ExpectedDiff: &terraform.InstanceDiff{ 3977 Attributes: map[string]*terraform.ResourceAttrDiff{ 3978 "outer.#": &terraform.ResourceAttrDiff{ 3979 Old: "0", 3980 New: "1", 3981 }, 3982 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 3983 Old: "", 3984 New: "foo", 3985 }, 3986 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 3987 Old: "0", 3988 New: "1", 3989 }, 3990 "outer.~1.inner.~2.inner_str": &terraform.ResourceAttrDiff{ 3991 Old: "", 3992 New: hcl2shim.UnknownVariableValue, 3993 NewComputed: true, 3994 }, 3995 }, 3996 }, 3997 3998 Err: false, 3999 }, 4000 4001 "Complex structure with complex list of computed string should mark root set as computed": { 4002 Schema: map[string]*Schema{ 4003 "outer": &Schema{ 4004 Type: TypeSet, 4005 Optional: true, 4006 Elem: &Resource{ 4007 Schema: map[string]*Schema{ 4008 "outer_str": &Schema{ 4009 Type: TypeString, 4010 Optional: true, 4011 }, 4012 "inner": &Schema{ 4013 Type: TypeList, 4014 Optional: true, 4015 Elem: &Resource{ 4016 Schema: map[string]*Schema{ 4017 "inner_str": &Schema{ 4018 Type: TypeString, 4019 Optional: true, 4020 }, 4021 }, 4022 }, 4023 }, 4024 }, 4025 }, 4026 Set: func(v interface{}) int { 4027 return 1 4028 }, 4029 }, 4030 }, 4031 4032 State: nil, 4033 4034 Config: map[string]interface{}{ 4035 "outer": []interface{}{ 4036 map[string]interface{}{ 4037 "outer_str": "foo", 4038 "inner": []interface{}{ 4039 map[string]interface{}{ 4040 "inner_str": hcl2shim.UnknownVariableValue, 4041 }, 4042 }, 4043 }, 4044 }, 4045 }, 4046 4047 ExpectedDiff: &terraform.InstanceDiff{ 4048 Attributes: map[string]*terraform.ResourceAttrDiff{ 4049 "outer.#": &terraform.ResourceAttrDiff{ 4050 Old: "0", 4051 New: "1", 4052 }, 4053 "outer.~1.outer_str": &terraform.ResourceAttrDiff{ 4054 Old: "", 4055 New: "foo", 4056 }, 4057 "outer.~1.inner.#": &terraform.ResourceAttrDiff{ 4058 Old: "0", 4059 New: "1", 4060 }, 4061 "outer.~1.inner.0.inner_str": &terraform.ResourceAttrDiff{ 4062 Old: "", 4063 New: hcl2shim.UnknownVariableValue, 4064 NewComputed: true, 4065 }, 4066 }, 4067 }, 4068 4069 Err: false, 4070 }, 4071 } 4072 4073 for tn, tc := range cases { 4074 t.Run(tn, func(t *testing.T) { 4075 c := terraform.NewResourceConfigRaw(tc.Config) 4076 4077 d, err := schemaMap(tc.Schema).Diff(tc.State, c, nil, nil, true) 4078 if err != nil != tc.Err { 4079 t.Fatalf("#%q err: %s", tn, err) 4080 } 4081 4082 if !reflect.DeepEqual(tc.ExpectedDiff, d) { 4083 t.Fatalf("#%q:\n\nexpected:\n%#v\n\ngot:\n%#v", tn, tc.ExpectedDiff, d) 4084 } 4085 }) 4086 } 4087 } 4088 4089 func TestSchemaMap_Validate(t *testing.T) { 4090 cases := map[string]struct { 4091 Schema map[string]*Schema 4092 Config map[string]interface{} 4093 Err bool 4094 Errors []error 4095 Warnings []string 4096 }{ 4097 "Good": { 4098 Schema: map[string]*Schema{ 4099 "availability_zone": &Schema{ 4100 Type: TypeString, 4101 Optional: true, 4102 Computed: true, 4103 ForceNew: true, 4104 }, 4105 }, 4106 4107 Config: map[string]interface{}{ 4108 "availability_zone": "foo", 4109 }, 4110 }, 4111 4112 "Good, because the var is not set and that error will come elsewhere": { 4113 Schema: map[string]*Schema{ 4114 "size": &Schema{ 4115 Type: TypeInt, 4116 Required: true, 4117 }, 4118 }, 4119 4120 Config: map[string]interface{}{ 4121 "size": hcl2shim.UnknownVariableValue, 4122 }, 4123 }, 4124 4125 "Required field not set": { 4126 Schema: map[string]*Schema{ 4127 "availability_zone": &Schema{ 4128 Type: TypeString, 4129 Required: true, 4130 }, 4131 }, 4132 4133 Config: map[string]interface{}{}, 4134 4135 Err: true, 4136 }, 4137 4138 "Invalid basic type": { 4139 Schema: map[string]*Schema{ 4140 "port": &Schema{ 4141 Type: TypeInt, 4142 Required: true, 4143 }, 4144 }, 4145 4146 Config: map[string]interface{}{ 4147 "port": "I am invalid", 4148 }, 4149 4150 Err: true, 4151 }, 4152 4153 "Invalid complex type": { 4154 Schema: map[string]*Schema{ 4155 "user_data": &Schema{ 4156 Type: TypeString, 4157 Optional: true, 4158 }, 4159 }, 4160 4161 Config: map[string]interface{}{ 4162 "user_data": []interface{}{ 4163 map[string]interface{}{ 4164 "foo": "bar", 4165 }, 4166 }, 4167 }, 4168 4169 Err: true, 4170 }, 4171 4172 "Bad type": { 4173 Schema: map[string]*Schema{ 4174 "size": &Schema{ 4175 Type: TypeInt, 4176 Required: true, 4177 }, 4178 }, 4179 4180 Config: map[string]interface{}{ 4181 "size": "nope", 4182 }, 4183 4184 Err: true, 4185 }, 4186 4187 "Required but has DefaultFunc": { 4188 Schema: map[string]*Schema{ 4189 "availability_zone": &Schema{ 4190 Type: TypeString, 4191 Required: true, 4192 DefaultFunc: func() (interface{}, error) { 4193 return "foo", nil 4194 }, 4195 }, 4196 }, 4197 4198 Config: nil, 4199 }, 4200 4201 "Required but has DefaultFunc return nil": { 4202 Schema: map[string]*Schema{ 4203 "availability_zone": &Schema{ 4204 Type: TypeString, 4205 Required: true, 4206 DefaultFunc: func() (interface{}, error) { 4207 return nil, nil 4208 }, 4209 }, 4210 }, 4211 4212 Config: nil, 4213 4214 Err: true, 4215 }, 4216 4217 "List with promotion": { 4218 Schema: map[string]*Schema{ 4219 "ingress": &Schema{ 4220 Type: TypeList, 4221 Elem: &Schema{Type: TypeInt}, 4222 PromoteSingle: true, 4223 Optional: true, 4224 }, 4225 }, 4226 4227 Config: map[string]interface{}{ 4228 "ingress": "5", 4229 }, 4230 4231 Err: false, 4232 }, 4233 4234 "List with promotion set as list": { 4235 Schema: map[string]*Schema{ 4236 "ingress": &Schema{ 4237 Type: TypeList, 4238 Elem: &Schema{Type: TypeInt}, 4239 PromoteSingle: true, 4240 Optional: true, 4241 }, 4242 }, 4243 4244 Config: map[string]interface{}{ 4245 "ingress": []interface{}{"5"}, 4246 }, 4247 4248 Err: false, 4249 }, 4250 4251 "Optional sub-resource": { 4252 Schema: map[string]*Schema{ 4253 "ingress": &Schema{ 4254 Type: TypeList, 4255 Elem: &Resource{ 4256 Schema: map[string]*Schema{ 4257 "from": &Schema{ 4258 Type: TypeInt, 4259 Required: true, 4260 }, 4261 }, 4262 }, 4263 }, 4264 }, 4265 4266 Config: map[string]interface{}{}, 4267 4268 Err: false, 4269 }, 4270 4271 "Sub-resource is the wrong type": { 4272 Schema: map[string]*Schema{ 4273 "ingress": &Schema{ 4274 Type: TypeList, 4275 Required: true, 4276 Elem: &Resource{ 4277 Schema: map[string]*Schema{ 4278 "from": &Schema{ 4279 Type: TypeInt, 4280 Required: true, 4281 }, 4282 }, 4283 }, 4284 }, 4285 }, 4286 4287 Config: map[string]interface{}{ 4288 "ingress": []interface{}{"foo"}, 4289 }, 4290 4291 Err: true, 4292 }, 4293 4294 "Not a list nested block": { 4295 Schema: map[string]*Schema{ 4296 "ingress": &Schema{ 4297 Type: TypeList, 4298 Optional: true, 4299 Elem: &Resource{ 4300 Schema: map[string]*Schema{ 4301 "from": &Schema{ 4302 Type: TypeInt, 4303 Required: true, 4304 }, 4305 }, 4306 }, 4307 }, 4308 }, 4309 4310 Config: map[string]interface{}{ 4311 "ingress": "foo", 4312 }, 4313 4314 Err: true, 4315 Errors: []error{ 4316 fmt.Errorf(`ingress: should be a list`), 4317 }, 4318 }, 4319 4320 "Not a list primitive": { 4321 Schema: map[string]*Schema{ 4322 "strings": &Schema{ 4323 Type: TypeList, 4324 Optional: true, 4325 Elem: &Schema{ 4326 Type: TypeString, 4327 }, 4328 }, 4329 }, 4330 4331 Config: map[string]interface{}{ 4332 "strings": "foo", 4333 }, 4334 4335 Err: true, 4336 Errors: []error{ 4337 fmt.Errorf(`strings: should be a list`), 4338 }, 4339 }, 4340 4341 "Unknown list": { 4342 Schema: map[string]*Schema{ 4343 "strings": &Schema{ 4344 Type: TypeList, 4345 Optional: true, 4346 Elem: &Schema{ 4347 Type: TypeString, 4348 }, 4349 }, 4350 }, 4351 4352 Config: map[string]interface{}{ 4353 "strings": hcl2shim.UnknownVariableValue, 4354 }, 4355 4356 Err: false, 4357 }, 4358 4359 "Unknown + Deprecation": { 4360 Schema: map[string]*Schema{ 4361 "old_news": &Schema{ 4362 Type: TypeString, 4363 Optional: true, 4364 Deprecated: "please use 'new_news' instead", 4365 }, 4366 }, 4367 4368 Config: map[string]interface{}{ 4369 "old_news": hcl2shim.UnknownVariableValue, 4370 }, 4371 4372 Warnings: []string{ 4373 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 4374 }, 4375 }, 4376 4377 "Required sub-resource field": { 4378 Schema: map[string]*Schema{ 4379 "ingress": &Schema{ 4380 Type: TypeList, 4381 Elem: &Resource{ 4382 Schema: map[string]*Schema{ 4383 "from": &Schema{ 4384 Type: TypeInt, 4385 Required: true, 4386 }, 4387 }, 4388 }, 4389 }, 4390 }, 4391 4392 Config: map[string]interface{}{ 4393 "ingress": []interface{}{ 4394 map[string]interface{}{}, 4395 }, 4396 }, 4397 4398 Err: true, 4399 }, 4400 4401 "Good sub-resource": { 4402 Schema: map[string]*Schema{ 4403 "ingress": &Schema{ 4404 Type: TypeList, 4405 Optional: true, 4406 Elem: &Resource{ 4407 Schema: map[string]*Schema{ 4408 "from": &Schema{ 4409 Type: TypeInt, 4410 Required: true, 4411 }, 4412 }, 4413 }, 4414 }, 4415 }, 4416 4417 Config: map[string]interface{}{ 4418 "ingress": []interface{}{ 4419 map[string]interface{}{ 4420 "from": 80, 4421 }, 4422 }, 4423 }, 4424 4425 Err: false, 4426 }, 4427 4428 "Good sub-resource, computed value": { 4429 Schema: map[string]*Schema{ 4430 "ingress": &Schema{ 4431 Type: TypeList, 4432 Optional: true, 4433 Elem: &Resource{ 4434 Schema: map[string]*Schema{ 4435 "from": &Schema{ 4436 Type: TypeInt, 4437 Optional: true, 4438 }, 4439 }, 4440 }, 4441 }, 4442 }, 4443 4444 Config: map[string]interface{}{ 4445 "ingress": []interface{}{ 4446 map[string]interface{}{ 4447 "from": hcl2shim.UnknownVariableValue, 4448 }, 4449 }, 4450 }, 4451 4452 Err: false, 4453 }, 4454 4455 "Invalid/unknown field": { 4456 Schema: map[string]*Schema{ 4457 "availability_zone": &Schema{ 4458 Type: TypeString, 4459 Optional: true, 4460 Computed: true, 4461 ForceNew: true, 4462 }, 4463 }, 4464 4465 Config: map[string]interface{}{ 4466 "foo": "bar", 4467 }, 4468 4469 Err: true, 4470 }, 4471 4472 "Invalid/unknown field with computed value": { 4473 Schema: map[string]*Schema{ 4474 "availability_zone": &Schema{ 4475 Type: TypeString, 4476 Optional: true, 4477 Computed: true, 4478 ForceNew: true, 4479 }, 4480 }, 4481 4482 Config: map[string]interface{}{ 4483 "foo": hcl2shim.UnknownVariableValue, 4484 }, 4485 4486 Err: true, 4487 }, 4488 4489 "Computed field set": { 4490 Schema: map[string]*Schema{ 4491 "availability_zone": &Schema{ 4492 Type: TypeString, 4493 Computed: true, 4494 }, 4495 }, 4496 4497 Config: map[string]interface{}{ 4498 "availability_zone": "bar", 4499 }, 4500 4501 Err: true, 4502 }, 4503 4504 "Not a set": { 4505 Schema: map[string]*Schema{ 4506 "ports": &Schema{ 4507 Type: TypeSet, 4508 Required: true, 4509 Elem: &Schema{Type: TypeInt}, 4510 Set: func(a interface{}) int { 4511 return a.(int) 4512 }, 4513 }, 4514 }, 4515 4516 Config: map[string]interface{}{ 4517 "ports": "foo", 4518 }, 4519 4520 Err: true, 4521 }, 4522 4523 "Maps": { 4524 Schema: map[string]*Schema{ 4525 "user_data": &Schema{ 4526 Type: TypeMap, 4527 Optional: true, 4528 }, 4529 }, 4530 4531 Config: map[string]interface{}{ 4532 "user_data": "foo", 4533 }, 4534 4535 Err: true, 4536 }, 4537 4538 "Good map: data surrounded by extra slice": { 4539 Schema: map[string]*Schema{ 4540 "user_data": &Schema{ 4541 Type: TypeMap, 4542 Optional: true, 4543 }, 4544 }, 4545 4546 Config: map[string]interface{}{ 4547 "user_data": []interface{}{ 4548 map[string]interface{}{ 4549 "foo": "bar", 4550 }, 4551 }, 4552 }, 4553 }, 4554 4555 "Good map": { 4556 Schema: map[string]*Schema{ 4557 "user_data": &Schema{ 4558 Type: TypeMap, 4559 Optional: true, 4560 }, 4561 }, 4562 4563 Config: map[string]interface{}{ 4564 "user_data": map[string]interface{}{ 4565 "foo": "bar", 4566 }, 4567 }, 4568 }, 4569 4570 "Map with type specified as value type": { 4571 Schema: map[string]*Schema{ 4572 "user_data": &Schema{ 4573 Type: TypeMap, 4574 Optional: true, 4575 Elem: TypeBool, 4576 }, 4577 }, 4578 4579 Config: map[string]interface{}{ 4580 "user_data": map[string]interface{}{ 4581 "foo": "not_a_bool", 4582 }, 4583 }, 4584 4585 Err: true, 4586 }, 4587 4588 "Map with type specified as nested Schema": { 4589 Schema: map[string]*Schema{ 4590 "user_data": &Schema{ 4591 Type: TypeMap, 4592 Optional: true, 4593 Elem: &Schema{Type: TypeBool}, 4594 }, 4595 }, 4596 4597 Config: map[string]interface{}{ 4598 "user_data": map[string]interface{}{ 4599 "foo": "not_a_bool", 4600 }, 4601 }, 4602 4603 Err: true, 4604 }, 4605 4606 "Bad map: just a slice": { 4607 Schema: map[string]*Schema{ 4608 "user_data": &Schema{ 4609 Type: TypeMap, 4610 Optional: true, 4611 }, 4612 }, 4613 4614 Config: map[string]interface{}{ 4615 "user_data": []interface{}{ 4616 "foo", 4617 }, 4618 }, 4619 4620 Err: true, 4621 }, 4622 4623 "Good set: config has slice with single interpolated value": { 4624 Schema: map[string]*Schema{ 4625 "security_groups": &Schema{ 4626 Type: TypeSet, 4627 Optional: true, 4628 Computed: true, 4629 ForceNew: true, 4630 Elem: &Schema{Type: TypeString}, 4631 Set: func(v interface{}) int { 4632 return len(v.(string)) 4633 }, 4634 }, 4635 }, 4636 4637 Config: map[string]interface{}{ 4638 "security_groups": []interface{}{"${var.foo}"}, 4639 }, 4640 4641 Err: false, 4642 }, 4643 4644 "Bad set: config has single interpolated value": { 4645 Schema: map[string]*Schema{ 4646 "security_groups": &Schema{ 4647 Type: TypeSet, 4648 Optional: true, 4649 Computed: true, 4650 ForceNew: true, 4651 Elem: &Schema{Type: TypeString}, 4652 }, 4653 }, 4654 4655 Config: map[string]interface{}{ 4656 "security_groups": "${var.foo}", 4657 }, 4658 4659 Err: true, 4660 }, 4661 4662 "Bad, subresource should not allow unknown elements": { 4663 Schema: map[string]*Schema{ 4664 "ingress": &Schema{ 4665 Type: TypeList, 4666 Optional: true, 4667 Elem: &Resource{ 4668 Schema: map[string]*Schema{ 4669 "port": &Schema{ 4670 Type: TypeInt, 4671 Required: true, 4672 }, 4673 }, 4674 }, 4675 }, 4676 }, 4677 4678 Config: map[string]interface{}{ 4679 "ingress": []interface{}{ 4680 map[string]interface{}{ 4681 "port": 80, 4682 "other": "yes", 4683 }, 4684 }, 4685 }, 4686 4687 Err: true, 4688 }, 4689 4690 "Bad, subresource should not allow invalid types": { 4691 Schema: map[string]*Schema{ 4692 "ingress": &Schema{ 4693 Type: TypeList, 4694 Optional: true, 4695 Elem: &Resource{ 4696 Schema: map[string]*Schema{ 4697 "port": &Schema{ 4698 Type: TypeInt, 4699 Required: true, 4700 }, 4701 }, 4702 }, 4703 }, 4704 }, 4705 4706 Config: map[string]interface{}{ 4707 "ingress": []interface{}{ 4708 map[string]interface{}{ 4709 "port": "bad", 4710 }, 4711 }, 4712 }, 4713 4714 Err: true, 4715 }, 4716 4717 "Bad, should not allow lists to be assigned to string attributes": { 4718 Schema: map[string]*Schema{ 4719 "availability_zone": &Schema{ 4720 Type: TypeString, 4721 Required: true, 4722 }, 4723 }, 4724 4725 Config: map[string]interface{}{ 4726 "availability_zone": []interface{}{"foo", "bar", "baz"}, 4727 }, 4728 4729 Err: true, 4730 }, 4731 4732 "Bad, should not allow maps to be assigned to string attributes": { 4733 Schema: map[string]*Schema{ 4734 "availability_zone": &Schema{ 4735 Type: TypeString, 4736 Required: true, 4737 }, 4738 }, 4739 4740 Config: map[string]interface{}{ 4741 "availability_zone": map[string]interface{}{"foo": "bar", "baz": "thing"}, 4742 }, 4743 4744 Err: true, 4745 }, 4746 4747 "Deprecated attribute usage generates warning, but not error": { 4748 Schema: map[string]*Schema{ 4749 "old_news": &Schema{ 4750 Type: TypeString, 4751 Optional: true, 4752 Deprecated: "please use 'new_news' instead", 4753 }, 4754 }, 4755 4756 Config: map[string]interface{}{ 4757 "old_news": "extra extra!", 4758 }, 4759 4760 Err: false, 4761 4762 Warnings: []string{ 4763 "\"old_news\": [DEPRECATED] please use 'new_news' instead", 4764 }, 4765 }, 4766 4767 "Deprecated generates no warnings if attr not used": { 4768 Schema: map[string]*Schema{ 4769 "old_news": &Schema{ 4770 Type: TypeString, 4771 Optional: true, 4772 Deprecated: "please use 'new_news' instead", 4773 }, 4774 }, 4775 4776 Err: false, 4777 4778 Warnings: nil, 4779 }, 4780 4781 "Removed attribute usage generates error": { 4782 Schema: map[string]*Schema{ 4783 "long_gone": &Schema{ 4784 Type: TypeString, 4785 Optional: true, 4786 Removed: "no longer supported by Cloud API", 4787 }, 4788 }, 4789 4790 Config: map[string]interface{}{ 4791 "long_gone": "still here!", 4792 }, 4793 4794 Err: true, 4795 Errors: []error{ 4796 fmt.Errorf("\"long_gone\": [REMOVED] no longer supported by Cloud API"), 4797 }, 4798 }, 4799 4800 "Removed generates no errors if attr not used": { 4801 Schema: map[string]*Schema{ 4802 "long_gone": &Schema{ 4803 Type: TypeString, 4804 Optional: true, 4805 Removed: "no longer supported by Cloud API", 4806 }, 4807 }, 4808 4809 Err: false, 4810 }, 4811 4812 "Conflicting attributes generate error": { 4813 Schema: map[string]*Schema{ 4814 "b": &Schema{ 4815 Type: TypeString, 4816 Optional: true, 4817 }, 4818 "a": &Schema{ 4819 Type: TypeString, 4820 Optional: true, 4821 ConflictsWith: []string{"b"}, 4822 }, 4823 }, 4824 4825 Config: map[string]interface{}{ 4826 "b": "b-val", 4827 "a": "a-val", 4828 }, 4829 4830 Err: true, 4831 Errors: []error{ 4832 fmt.Errorf("\"a\": conflicts with b"), 4833 }, 4834 }, 4835 4836 "Conflicting attributes okay when unknown 1": { 4837 Schema: map[string]*Schema{ 4838 "b": &Schema{ 4839 Type: TypeString, 4840 Optional: true, 4841 }, 4842 "a": &Schema{ 4843 Type: TypeString, 4844 Optional: true, 4845 ConflictsWith: []string{"b"}, 4846 }, 4847 }, 4848 4849 Config: map[string]interface{}{ 4850 "b": "b-val", 4851 "a": hcl2shim.UnknownVariableValue, 4852 }, 4853 4854 Err: false, 4855 }, 4856 4857 "Conflicting attributes okay when unknown 2": { 4858 Schema: map[string]*Schema{ 4859 "b": &Schema{ 4860 Type: TypeString, 4861 Optional: true, 4862 }, 4863 "a": &Schema{ 4864 Type: TypeString, 4865 Optional: true, 4866 ConflictsWith: []string{"b"}, 4867 }, 4868 }, 4869 4870 Config: map[string]interface{}{ 4871 "b": hcl2shim.UnknownVariableValue, 4872 "a": "a-val", 4873 }, 4874 4875 Err: false, 4876 }, 4877 4878 "Conflicting attributes generate error even if one is unknown": { 4879 Schema: map[string]*Schema{ 4880 "b": &Schema{ 4881 Type: TypeString, 4882 Optional: true, 4883 ConflictsWith: []string{"a", "c"}, 4884 }, 4885 "a": &Schema{ 4886 Type: TypeString, 4887 Optional: true, 4888 ConflictsWith: []string{"b", "c"}, 4889 }, 4890 "c": &Schema{ 4891 Type: TypeString, 4892 Optional: true, 4893 ConflictsWith: []string{"b", "a"}, 4894 }, 4895 }, 4896 4897 Config: map[string]interface{}{ 4898 "b": hcl2shim.UnknownVariableValue, 4899 "a": "a-val", 4900 "c": "c-val", 4901 }, 4902 4903 Err: true, 4904 Errors: []error{ 4905 fmt.Errorf("\"a\": conflicts with c"), 4906 fmt.Errorf("\"c\": conflicts with a"), 4907 }, 4908 }, 4909 4910 "Required attribute & undefined conflicting optional are good": { 4911 Schema: map[string]*Schema{ 4912 "required_att": &Schema{ 4913 Type: TypeString, 4914 Required: true, 4915 }, 4916 "optional_att": &Schema{ 4917 Type: TypeString, 4918 Optional: true, 4919 ConflictsWith: []string{"required_att"}, 4920 }, 4921 }, 4922 4923 Config: map[string]interface{}{ 4924 "required_att": "required-val", 4925 }, 4926 4927 Err: false, 4928 }, 4929 4930 "Required conflicting attribute & defined optional generate error": { 4931 Schema: map[string]*Schema{ 4932 "required_att": &Schema{ 4933 Type: TypeString, 4934 Required: true, 4935 }, 4936 "optional_att": &Schema{ 4937 Type: TypeString, 4938 Optional: true, 4939 ConflictsWith: []string{"required_att"}, 4940 }, 4941 }, 4942 4943 Config: map[string]interface{}{ 4944 "required_att": "required-val", 4945 "optional_att": "optional-val", 4946 }, 4947 4948 Err: true, 4949 Errors: []error{ 4950 fmt.Errorf(`"optional_att": conflicts with required_att`), 4951 }, 4952 }, 4953 4954 "Computed + Optional fields conflicting with each other": { 4955 Schema: map[string]*Schema{ 4956 "foo_att": &Schema{ 4957 Type: TypeString, 4958 Optional: true, 4959 Computed: true, 4960 ConflictsWith: []string{"bar_att"}, 4961 }, 4962 "bar_att": &Schema{ 4963 Type: TypeString, 4964 Optional: true, 4965 Computed: true, 4966 ConflictsWith: []string{"foo_att"}, 4967 }, 4968 }, 4969 4970 Config: map[string]interface{}{ 4971 "foo_att": "foo-val", 4972 "bar_att": "bar-val", 4973 }, 4974 4975 Err: true, 4976 Errors: []error{ 4977 fmt.Errorf(`"foo_att": conflicts with bar_att`), 4978 fmt.Errorf(`"bar_att": conflicts with foo_att`), 4979 }, 4980 }, 4981 4982 "Computed + Optional fields NOT conflicting with each other": { 4983 Schema: map[string]*Schema{ 4984 "foo_att": &Schema{ 4985 Type: TypeString, 4986 Optional: true, 4987 Computed: true, 4988 ConflictsWith: []string{"bar_att"}, 4989 }, 4990 "bar_att": &Schema{ 4991 Type: TypeString, 4992 Optional: true, 4993 Computed: true, 4994 ConflictsWith: []string{"foo_att"}, 4995 }, 4996 }, 4997 4998 Config: map[string]interface{}{ 4999 "foo_att": "foo-val", 5000 }, 5001 5002 Err: false, 5003 }, 5004 5005 "Computed + Optional fields that conflict with none set": { 5006 Schema: map[string]*Schema{ 5007 "foo_att": &Schema{ 5008 Type: TypeString, 5009 Optional: true, 5010 Computed: true, 5011 ConflictsWith: []string{"bar_att"}, 5012 }, 5013 "bar_att": &Schema{ 5014 Type: TypeString, 5015 Optional: true, 5016 Computed: true, 5017 ConflictsWith: []string{"foo_att"}, 5018 }, 5019 }, 5020 5021 Config: map[string]interface{}{}, 5022 5023 Err: false, 5024 }, 5025 5026 "Good with ValidateFunc": { 5027 Schema: map[string]*Schema{ 5028 "validate_me": &Schema{ 5029 Type: TypeString, 5030 Required: true, 5031 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5032 return 5033 }, 5034 }, 5035 }, 5036 Config: map[string]interface{}{ 5037 "validate_me": "valid", 5038 }, 5039 Err: false, 5040 }, 5041 5042 "Bad with ValidateFunc": { 5043 Schema: map[string]*Schema{ 5044 "validate_me": &Schema{ 5045 Type: TypeString, 5046 Required: true, 5047 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5048 es = append(es, fmt.Errorf("something is not right here")) 5049 return 5050 }, 5051 }, 5052 }, 5053 Config: map[string]interface{}{ 5054 "validate_me": "invalid", 5055 }, 5056 Err: true, 5057 Errors: []error{ 5058 fmt.Errorf(`something is not right here`), 5059 }, 5060 }, 5061 5062 "ValidateFunc not called when type does not match": { 5063 Schema: map[string]*Schema{ 5064 "number": &Schema{ 5065 Type: TypeInt, 5066 Required: true, 5067 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5068 t.Fatalf("Should not have gotten validate call") 5069 return 5070 }, 5071 }, 5072 }, 5073 Config: map[string]interface{}{ 5074 "number": "NaN", 5075 }, 5076 Err: true, 5077 }, 5078 5079 "ValidateFunc gets decoded type": { 5080 Schema: map[string]*Schema{ 5081 "maybe": &Schema{ 5082 Type: TypeBool, 5083 Required: true, 5084 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5085 if _, ok := v.(bool); !ok { 5086 t.Fatalf("Expected bool, got: %#v", v) 5087 } 5088 return 5089 }, 5090 }, 5091 }, 5092 Config: map[string]interface{}{ 5093 "maybe": "true", 5094 }, 5095 }, 5096 5097 "ValidateFunc is not called with a computed value": { 5098 Schema: map[string]*Schema{ 5099 "validate_me": &Schema{ 5100 Type: TypeString, 5101 Required: true, 5102 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5103 es = append(es, fmt.Errorf("something is not right here")) 5104 return 5105 }, 5106 }, 5107 }, 5108 Config: map[string]interface{}{ 5109 "validate_me": hcl2shim.UnknownVariableValue, 5110 }, 5111 5112 Err: false, 5113 }, 5114 5115 "special timeouts field": { 5116 Schema: map[string]*Schema{ 5117 "availability_zone": &Schema{ 5118 Type: TypeString, 5119 Optional: true, 5120 Computed: true, 5121 ForceNew: true, 5122 }, 5123 }, 5124 5125 Config: map[string]interface{}{ 5126 TimeoutsConfigKey: "bar", 5127 }, 5128 5129 Err: false, 5130 }, 5131 5132 "invalid bool field": { 5133 Schema: map[string]*Schema{ 5134 "bool_field": { 5135 Type: TypeBool, 5136 Optional: true, 5137 }, 5138 }, 5139 Config: map[string]interface{}{ 5140 "bool_field": "abcdef", 5141 }, 5142 Err: true, 5143 }, 5144 "invalid integer field": { 5145 Schema: map[string]*Schema{ 5146 "integer_field": { 5147 Type: TypeInt, 5148 Optional: true, 5149 }, 5150 }, 5151 Config: map[string]interface{}{ 5152 "integer_field": "abcdef", 5153 }, 5154 Err: true, 5155 }, 5156 "invalid float field": { 5157 Schema: map[string]*Schema{ 5158 "float_field": { 5159 Type: TypeFloat, 5160 Optional: true, 5161 }, 5162 }, 5163 Config: map[string]interface{}{ 5164 "float_field": "abcdef", 5165 }, 5166 Err: true, 5167 }, 5168 5169 // Invalid map values 5170 "invalid bool map value": { 5171 Schema: map[string]*Schema{ 5172 "boolMap": &Schema{ 5173 Type: TypeMap, 5174 Elem: TypeBool, 5175 Optional: true, 5176 }, 5177 }, 5178 Config: map[string]interface{}{ 5179 "boolMap": map[string]interface{}{ 5180 "boolField": "notbool", 5181 }, 5182 }, 5183 Err: true, 5184 }, 5185 "invalid int map value": { 5186 Schema: map[string]*Schema{ 5187 "intMap": &Schema{ 5188 Type: TypeMap, 5189 Elem: TypeInt, 5190 Optional: true, 5191 }, 5192 }, 5193 Config: map[string]interface{}{ 5194 "intMap": map[string]interface{}{ 5195 "intField": "notInt", 5196 }, 5197 }, 5198 Err: true, 5199 }, 5200 "invalid float map value": { 5201 Schema: map[string]*Schema{ 5202 "floatMap": &Schema{ 5203 Type: TypeMap, 5204 Elem: TypeFloat, 5205 Optional: true, 5206 }, 5207 }, 5208 Config: map[string]interface{}{ 5209 "floatMap": map[string]interface{}{ 5210 "floatField": "notFloat", 5211 }, 5212 }, 5213 Err: true, 5214 }, 5215 5216 "map with positive validate function": { 5217 Schema: map[string]*Schema{ 5218 "floatInt": &Schema{ 5219 Type: TypeMap, 5220 Elem: TypeInt, 5221 Optional: true, 5222 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5223 return 5224 }, 5225 }, 5226 }, 5227 Config: map[string]interface{}{ 5228 "floatInt": map[string]interface{}{ 5229 "rightAnswer": "42", 5230 "tooMuch": "43", 5231 }, 5232 }, 5233 Err: false, 5234 }, 5235 "map with negative validate function": { 5236 Schema: map[string]*Schema{ 5237 "floatInt": &Schema{ 5238 Type: TypeMap, 5239 Elem: TypeInt, 5240 Optional: true, 5241 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5242 es = append(es, fmt.Errorf("this is not fine")) 5243 return 5244 }, 5245 }, 5246 }, 5247 Config: map[string]interface{}{ 5248 "floatInt": map[string]interface{}{ 5249 "rightAnswer": "42", 5250 "tooMuch": "43", 5251 }, 5252 }, 5253 Err: true, 5254 }, 5255 5256 // The Validation function should not see interpolation strings from 5257 // non-computed values. 5258 "set with partially computed list and map": { 5259 Schema: map[string]*Schema{ 5260 "outer": &Schema{ 5261 Type: TypeSet, 5262 Optional: true, 5263 Computed: true, 5264 Elem: &Resource{ 5265 Schema: map[string]*Schema{ 5266 "list": &Schema{ 5267 Type: TypeList, 5268 Optional: true, 5269 Elem: &Schema{ 5270 Type: TypeString, 5271 ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { 5272 if strings.HasPrefix(v.(string), "${") { 5273 es = append(es, fmt.Errorf("should not have interpolations")) 5274 } 5275 return 5276 }, 5277 }, 5278 }, 5279 }, 5280 }, 5281 }, 5282 }, 5283 Config: map[string]interface{}{ 5284 "outer": []interface{}{ 5285 map[string]interface{}{ 5286 "list": []interface{}{"A", hcl2shim.UnknownVariableValue, "c"}, 5287 }, 5288 }, 5289 }, 5290 Err: false, 5291 }, 5292 "unexpected nils values": { 5293 Schema: map[string]*Schema{ 5294 "strings": &Schema{ 5295 Type: TypeList, 5296 Optional: true, 5297 Elem: &Schema{ 5298 Type: TypeString, 5299 }, 5300 }, 5301 "block": &Schema{ 5302 Type: TypeList, 5303 Optional: true, 5304 Elem: &Resource{ 5305 Schema: map[string]*Schema{ 5306 "int": &Schema{ 5307 Type: TypeInt, 5308 Required: true, 5309 }, 5310 }, 5311 }, 5312 }, 5313 }, 5314 5315 Config: map[string]interface{}{ 5316 "strings": []interface{}{"1", nil}, 5317 "block": []interface{}{map[string]interface{}{ 5318 "int": nil, 5319 }, 5320 nil, 5321 }, 5322 }, 5323 Err: true, 5324 }, 5325 } 5326 5327 for tn, tc := range cases { 5328 t.Run(tn, func(t *testing.T) { 5329 c := terraform.NewResourceConfigRaw(tc.Config) 5330 5331 ws, es := schemaMap(tc.Schema).Validate(c) 5332 if len(es) > 0 != tc.Err { 5333 if len(es) == 0 { 5334 t.Errorf("%q: no errors", tn) 5335 } 5336 5337 for _, e := range es { 5338 t.Errorf("%q: err: %s", tn, e) 5339 } 5340 5341 t.FailNow() 5342 } 5343 5344 if !reflect.DeepEqual(ws, tc.Warnings) { 5345 t.Fatalf("%q: warnings:\n\nexpected: %#v\ngot:%#v", tn, tc.Warnings, ws) 5346 } 5347 5348 if tc.Errors != nil { 5349 sort.Sort(errorSort(es)) 5350 sort.Sort(errorSort(tc.Errors)) 5351 5352 if !reflect.DeepEqual(es, tc.Errors) { 5353 t.Fatalf("%q: errors:\n\nexpected: %q\ngot: %q", tn, tc.Errors, es) 5354 } 5355 } 5356 }) 5357 5358 } 5359 } 5360 5361 func TestSchemaSet_ValidateMaxItems(t *testing.T) { 5362 cases := map[string]struct { 5363 Schema map[string]*Schema 5364 State *terraform.InstanceState 5365 Config map[string]interface{} 5366 ConfigVariables map[string]string 5367 Diff *terraform.InstanceDiff 5368 Err bool 5369 Errors []error 5370 }{ 5371 "#0": { 5372 Schema: map[string]*Schema{ 5373 "aliases": &Schema{ 5374 Type: TypeSet, 5375 Optional: true, 5376 MaxItems: 1, 5377 Elem: &Schema{Type: TypeString}, 5378 }, 5379 }, 5380 State: nil, 5381 Config: map[string]interface{}{ 5382 "aliases": []interface{}{"foo", "bar"}, 5383 }, 5384 Diff: nil, 5385 Err: true, 5386 Errors: []error{ 5387 fmt.Errorf("aliases: attribute supports 1 item maximum, config has 2 declared"), 5388 }, 5389 }, 5390 "#1": { 5391 Schema: map[string]*Schema{ 5392 "aliases": &Schema{ 5393 Type: TypeSet, 5394 Optional: true, 5395 Elem: &Schema{Type: TypeString}, 5396 }, 5397 }, 5398 State: nil, 5399 Config: map[string]interface{}{ 5400 "aliases": []interface{}{"foo", "bar"}, 5401 }, 5402 Diff: nil, 5403 Err: false, 5404 Errors: nil, 5405 }, 5406 "#2": { 5407 Schema: map[string]*Schema{ 5408 "aliases": &Schema{ 5409 Type: TypeSet, 5410 Optional: true, 5411 MaxItems: 1, 5412 Elem: &Schema{Type: TypeString}, 5413 }, 5414 }, 5415 State: nil, 5416 Config: map[string]interface{}{ 5417 "aliases": []interface{}{"foo"}, 5418 }, 5419 Diff: nil, 5420 Err: false, 5421 Errors: nil, 5422 }, 5423 } 5424 5425 for tn, tc := range cases { 5426 c := terraform.NewResourceConfigRaw(tc.Config) 5427 _, es := schemaMap(tc.Schema).Validate(c) 5428 5429 if len(es) > 0 != tc.Err { 5430 if len(es) == 0 { 5431 t.Errorf("%q: no errors", tn) 5432 } 5433 5434 for _, e := range es { 5435 t.Errorf("%q: err: %s", tn, e) 5436 } 5437 5438 t.FailNow() 5439 } 5440 5441 if tc.Errors != nil { 5442 if !reflect.DeepEqual(es, tc.Errors) { 5443 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 5444 } 5445 } 5446 } 5447 } 5448 5449 func TestSchemaSet_ValidateMinItems(t *testing.T) { 5450 cases := map[string]struct { 5451 Schema map[string]*Schema 5452 State *terraform.InstanceState 5453 Config map[string]interface{} 5454 ConfigVariables map[string]string 5455 Diff *terraform.InstanceDiff 5456 Err bool 5457 Errors []error 5458 }{ 5459 "#0": { 5460 Schema: map[string]*Schema{ 5461 "aliases": &Schema{ 5462 Type: TypeSet, 5463 Optional: true, 5464 MinItems: 2, 5465 Elem: &Schema{Type: TypeString}, 5466 }, 5467 }, 5468 State: nil, 5469 Config: map[string]interface{}{ 5470 "aliases": []interface{}{"foo", "bar"}, 5471 }, 5472 Diff: nil, 5473 Err: false, 5474 Errors: nil, 5475 }, 5476 "#1": { 5477 Schema: map[string]*Schema{ 5478 "aliases": &Schema{ 5479 Type: TypeSet, 5480 Optional: true, 5481 Elem: &Schema{Type: TypeString}, 5482 }, 5483 }, 5484 State: nil, 5485 Config: map[string]interface{}{ 5486 "aliases": []interface{}{"foo", "bar"}, 5487 }, 5488 Diff: nil, 5489 Err: false, 5490 Errors: nil, 5491 }, 5492 "#2": { 5493 Schema: map[string]*Schema{ 5494 "aliases": &Schema{ 5495 Type: TypeSet, 5496 Optional: true, 5497 MinItems: 2, 5498 Elem: &Schema{Type: TypeString}, 5499 }, 5500 }, 5501 State: nil, 5502 Config: map[string]interface{}{ 5503 "aliases": []interface{}{"foo"}, 5504 }, 5505 Diff: nil, 5506 Err: true, 5507 Errors: []error{ 5508 fmt.Errorf("aliases: attribute supports 2 item as a minimum, config has 1 declared"), 5509 }, 5510 }, 5511 } 5512 5513 for tn, tc := range cases { 5514 c := terraform.NewResourceConfigRaw(tc.Config) 5515 _, es := schemaMap(tc.Schema).Validate(c) 5516 5517 if len(es) > 0 != tc.Err { 5518 if len(es) == 0 { 5519 t.Errorf("%q: no errors", tn) 5520 } 5521 5522 for _, e := range es { 5523 t.Errorf("%q: err: %s", tn, e) 5524 } 5525 5526 t.FailNow() 5527 } 5528 5529 if tc.Errors != nil { 5530 if !reflect.DeepEqual(es, tc.Errors) { 5531 t.Fatalf("%q: expected: %q\ngot: %q", tn, tc.Errors, es) 5532 } 5533 } 5534 } 5535 } 5536 5537 // errorSort implements sort.Interface to sort errors by their error message 5538 type errorSort []error 5539 5540 func (e errorSort) Len() int { return len(e) } 5541 func (e errorSort) Swap(i, j int) { e[i], e[j] = e[j], e[i] } 5542 func (e errorSort) Less(i, j int) bool { 5543 return e[i].Error() < e[j].Error() 5544 } 5545 5546 func TestSchemaMapDeepCopy(t *testing.T) { 5547 schema := map[string]*Schema{ 5548 "foo": &Schema{ 5549 Type: TypeString, 5550 }, 5551 } 5552 source := schemaMap(schema) 5553 dest := source.DeepCopy() 5554 dest["foo"].ForceNew = true 5555 if reflect.DeepEqual(source, dest) { 5556 t.Fatalf("source and dest should not match") 5557 } 5558 }