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