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