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