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