github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/resource_data_test.go (about) 1 // Copyright (c) HashiCorp, Inc. 2 // SPDX-License-Identifier: MPL-2.0 3 4 package schema 5 6 import ( 7 "fmt" 8 "math" 9 "os" 10 "reflect" 11 "testing" 12 "time" 13 14 "github.com/terramate-io/tf/legacy/terraform" 15 ) 16 17 func TestResourceDataGet(t *testing.T) { 18 cases := []struct { 19 Schema map[string]*Schema 20 State *terraform.InstanceState 21 Diff *terraform.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: &terraform.InstanceDiff{ 39 Attributes: map[string]*terraform.ResourceAttrDiff{ 40 "availability_zone": &terraform.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: &terraform.InstanceDiff{ 66 Attributes: map[string]*terraform.ResourceAttrDiff{ 67 "availability_zone": &terraform.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: &terraform.InstanceDiff{ 94 Attributes: map[string]*terraform.ResourceAttrDiff{ 95 "availability_zone": &terraform.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: &terraform.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: &terraform.InstanceState{ 143 Attributes: map[string]string{ 144 "availability_zone": "foo", 145 }, 146 }, 147 148 Diff: &terraform.InstanceDiff{ 149 Attributes: map[string]*terraform.ResourceAttrDiff{ 150 "availability_zone": &terraform.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: &terraform.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: &terraform.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: &terraform.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: &terraform.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: &terraform.InstanceDiff{ 295 Attributes: map[string]*terraform.ResourceAttrDiff{ 296 "ingress.#": &terraform.ResourceAttrDiff{ 297 Old: "", 298 New: "1", 299 }, 300 "ingress.0.from": &terraform.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: &terraform.InstanceDiff{ 334 Attributes: map[string]*terraform.ResourceAttrDiff{ 335 "ingress.#": &terraform.ResourceAttrDiff{ 336 Old: "", 337 New: "1", 338 }, 339 "ingress.0.from": &terraform.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: &terraform.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: &terraform.InstanceDiff{ 389 Attributes: map[string]*terraform.ResourceAttrDiff{ 390 "availability_zone": &terraform.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: &terraform.InstanceDiff{ 421 Attributes: map[string]*terraform.ResourceAttrDiff{ 422 "config_vars.#": &terraform.ResourceAttrDiff{ 423 Old: "0", 424 New: "2", 425 }, 426 "config_vars.0.foo": &terraform.ResourceAttrDiff{ 427 Old: "", 428 New: "bar", 429 }, 430 "config_vars.1.bar": &terraform.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: &terraform.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: &terraform.InstanceState{ 498 Attributes: map[string]string{ 499 "config_vars.#": "1", 500 "config_vars.0.FOO": "bar", 501 }, 502 }, 503 504 Diff: &terraform.InstanceDiff{ 505 Attributes: map[string]*terraform.ResourceAttrDiff{ 506 "config_vars.#": &terraform.ResourceAttrDiff{ 507 Old: "1", 508 New: "0", 509 }, 510 "config_vars.0.FOO": &terraform.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: &terraform.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: &terraform.InstanceState{ 577 Attributes: map[string]string{ 578 "data.#": "1", 579 "data.10.index": "10", 580 "data.10.value": "50", 581 }, 582 }, 583 584 Diff: &terraform.InstanceDiff{ 585 Attributes: map[string]*terraform.ResourceAttrDiff{ 586 "data.10.value": &terraform.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: &terraform.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: &terraform.InstanceState{ 679 Attributes: map[string]string{ 680 "ratio": "-0.5", 681 }, 682 }, 683 684 Diff: &terraform.InstanceDiff{ 685 Attributes: map[string]*terraform.ResourceAttrDiff{ 686 "ratio": &terraform.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: &terraform.InstanceState{ 713 Attributes: map[string]string{ 714 "ports.#": "1", 715 "ports.80": "80", 716 }, 717 }, 718 719 Diff: &terraform.InstanceDiff{ 720 Attributes: map[string]*terraform.ResourceAttrDiff{ 721 "ports.#": &terraform.ResourceAttrDiff{ 722 Old: "2", 723 New: "1", 724 }, 725 "ports.80": &terraform.ResourceAttrDiff{ 726 Old: "80", 727 New: "80", 728 }, 729 "ports.8080": &terraform.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 *terraform.InstanceState 764 Diff *terraform.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: &terraform.InstanceDiff{ 782 Attributes: map[string]*terraform.ResourceAttrDiff{ 783 "availability_zone": &terraform.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: &terraform.InstanceState{ 808 Attributes: map[string]string{ 809 "availability_zone": "foo", 810 }, 811 }, 812 813 Diff: &terraform.InstanceDiff{ 814 Attributes: map[string]*terraform.ResourceAttrDiff{ 815 "availability_zone": &terraform.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 *terraform.InstanceState 850 Diff *terraform.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: &terraform.InstanceDiff{ 871 Attributes: map[string]*terraform.ResourceAttrDiff{ 872 "availability_zone": &terraform.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: &terraform.InstanceDiff{ 897 Attributes: map[string]*terraform.ResourceAttrDiff{ 898 "availability_zone": &terraform.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: &terraform.InstanceDiff{ 1028 Attributes: map[string]*terraform.ResourceAttrDiff{ 1029 "ports.#": &terraform.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: &terraform.InstanceDiff{ 1055 Attributes: map[string]*terraform.ResourceAttrDiff{ 1056 "from_port": &terraform.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 *terraform.InstanceState 1094 Diff *terraform.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: &terraform.InstanceDiff{ 1116 Attributes: map[string]*terraform.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: &terraform.InstanceDiff{ 1143 Attributes: map[string]*terraform.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: &terraform.InstanceDiff{ 1279 Attributes: map[string]*terraform.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: &terraform.InstanceDiff{ 1306 Attributes: map[string]*terraform.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 *terraform.InstanceState 1422 Diff *terraform.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: &terraform.InstanceDiff{ 1439 Attributes: map[string]*terraform.ResourceAttrDiff{ 1440 "availability_zone": &terraform.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: &terraform.InstanceState{ 1464 Attributes: map[string]string{ 1465 "availability_zone": "foo", 1466 }, 1467 }, 1468 1469 Diff: &terraform.InstanceDiff{ 1470 Attributes: map[string]*terraform.ResourceAttrDiff{ 1471 "availability_zone": &terraform.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: &terraform.InstanceDiff{ 1496 Attributes: map[string]*terraform.ResourceAttrDiff{ 1497 "tags.Name": &terraform.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: &terraform.InstanceState{ 1520 Attributes: map[string]string{ 1521 "ports.#": "1", 1522 "ports.80": "80", 1523 }, 1524 }, 1525 1526 Diff: &terraform.InstanceDiff{ 1527 Attributes: map[string]*terraform.ResourceAttrDiff{ 1528 "ports.#": &terraform.ResourceAttrDiff{ 1529 Old: "1", 1530 New: "0", 1531 }, 1532 }, 1533 }, 1534 1535 Key: "ports", 1536 1537 Change: true, 1538 }, 1539 1540 // https://github.com/terramate-io/tf/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: &terraform.InstanceState{ 1552 Attributes: map[string]string{ 1553 "ports.#": "1", 1554 "ports.80": "80", 1555 }, 1556 }, 1557 1558 Diff: &terraform.InstanceDiff{ 1559 Attributes: map[string]*terraform.ResourceAttrDiff{ 1560 "tags.foo": &terraform.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 Schema map[string]*Schema 1591 State *terraform.InstanceState 1592 Diff *terraform.InstanceDiff 1593 Key string 1594 Value interface{} 1595 Err bool 1596 GetKey string 1597 GetValue interface{} 1598 1599 // GetPreProcess can be set to munge the return value before being 1600 // compared to GetValue 1601 GetPreProcess func(interface{}) interface{} 1602 }{ 1603 // #0: Basic good 1604 { 1605 Schema: map[string]*Schema{ 1606 "availability_zone": &Schema{ 1607 Type: TypeString, 1608 Optional: true, 1609 Computed: true, 1610 ForceNew: true, 1611 }, 1612 }, 1613 1614 State: nil, 1615 1616 Diff: nil, 1617 1618 Key: "availability_zone", 1619 Value: "foo", 1620 1621 GetKey: "availability_zone", 1622 GetValue: "foo", 1623 }, 1624 1625 // #1: Basic int 1626 { 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 // #2: Basic bool 1648 { 1649 Schema: map[string]*Schema{ 1650 "vpc": &Schema{ 1651 Type: TypeBool, 1652 Optional: true, 1653 }, 1654 }, 1655 1656 State: nil, 1657 1658 Diff: nil, 1659 1660 Key: "vpc", 1661 Value: true, 1662 1663 GetKey: "vpc", 1664 GetValue: true, 1665 }, 1666 1667 // #3 1668 { 1669 Schema: map[string]*Schema{ 1670 "vpc": &Schema{ 1671 Type: TypeBool, 1672 Optional: true, 1673 }, 1674 }, 1675 1676 State: nil, 1677 1678 Diff: nil, 1679 1680 Key: "vpc", 1681 Value: false, 1682 1683 GetKey: "vpc", 1684 GetValue: false, 1685 }, 1686 1687 // #4: Invalid type 1688 { 1689 Schema: map[string]*Schema{ 1690 "availability_zone": &Schema{ 1691 Type: TypeString, 1692 Optional: true, 1693 Computed: true, 1694 ForceNew: true, 1695 }, 1696 }, 1697 1698 State: nil, 1699 1700 Diff: nil, 1701 1702 Key: "availability_zone", 1703 Value: 80, 1704 Err: true, 1705 1706 GetKey: "availability_zone", 1707 GetValue: "", 1708 }, 1709 1710 // #5: List of primitives, set list 1711 { 1712 Schema: map[string]*Schema{ 1713 "ports": &Schema{ 1714 Type: TypeList, 1715 Computed: true, 1716 Elem: &Schema{Type: TypeInt}, 1717 }, 1718 }, 1719 1720 State: nil, 1721 1722 Diff: nil, 1723 1724 Key: "ports", 1725 Value: []int{1, 2, 5}, 1726 1727 GetKey: "ports", 1728 GetValue: []interface{}{1, 2, 5}, 1729 }, 1730 1731 // #6: List of primitives, set list with error 1732 { 1733 Schema: map[string]*Schema{ 1734 "ports": &Schema{ 1735 Type: TypeList, 1736 Computed: true, 1737 Elem: &Schema{Type: TypeInt}, 1738 }, 1739 }, 1740 1741 State: nil, 1742 1743 Diff: nil, 1744 1745 Key: "ports", 1746 Value: []interface{}{1, "NOPE", 5}, 1747 Err: true, 1748 1749 GetKey: "ports", 1750 GetValue: []interface{}{}, 1751 }, 1752 1753 // #7: Set a list of maps 1754 { 1755 Schema: map[string]*Schema{ 1756 "config_vars": &Schema{ 1757 Type: TypeList, 1758 Optional: true, 1759 Computed: true, 1760 Elem: &Schema{ 1761 Type: TypeMap, 1762 }, 1763 }, 1764 }, 1765 1766 State: nil, 1767 1768 Diff: nil, 1769 1770 Key: "config_vars", 1771 Value: []interface{}{ 1772 map[string]interface{}{ 1773 "foo": "bar", 1774 }, 1775 map[string]interface{}{ 1776 "bar": "baz", 1777 }, 1778 }, 1779 Err: false, 1780 1781 GetKey: "config_vars", 1782 GetValue: []interface{}{ 1783 map[string]interface{}{ 1784 "foo": "bar", 1785 }, 1786 map[string]interface{}{ 1787 "bar": "baz", 1788 }, 1789 }, 1790 }, 1791 1792 // #8: Set, with list 1793 { 1794 Schema: map[string]*Schema{ 1795 "ports": &Schema{ 1796 Type: TypeSet, 1797 Optional: true, 1798 Computed: true, 1799 Elem: &Schema{Type: TypeInt}, 1800 Set: func(a interface{}) int { 1801 return a.(int) 1802 }, 1803 }, 1804 }, 1805 1806 State: &terraform.InstanceState{ 1807 Attributes: map[string]string{ 1808 "ports.#": "3", 1809 "ports.0": "100", 1810 "ports.1": "80", 1811 "ports.2": "80", 1812 }, 1813 }, 1814 1815 Key: "ports", 1816 Value: []interface{}{100, 125, 125}, 1817 1818 GetKey: "ports", 1819 GetValue: []interface{}{100, 125}, 1820 }, 1821 1822 // #9: Set, with Set 1823 { 1824 Schema: map[string]*Schema{ 1825 "ports": &Schema{ 1826 Type: TypeSet, 1827 Optional: true, 1828 Computed: true, 1829 Elem: &Schema{Type: TypeInt}, 1830 Set: func(a interface{}) int { 1831 return a.(int) 1832 }, 1833 }, 1834 }, 1835 1836 State: &terraform.InstanceState{ 1837 Attributes: map[string]string{ 1838 "ports.#": "3", 1839 "ports.100": "100", 1840 "ports.80": "80", 1841 "ports.81": "81", 1842 }, 1843 }, 1844 1845 Key: "ports", 1846 Value: &Set{ 1847 m: map[string]interface{}{ 1848 "1": 1, 1849 "2": 2, 1850 }, 1851 }, 1852 1853 GetKey: "ports", 1854 GetValue: []interface{}{1, 2}, 1855 }, 1856 1857 // #10: Set single item 1858 { 1859 Schema: map[string]*Schema{ 1860 "ports": &Schema{ 1861 Type: TypeSet, 1862 Optional: true, 1863 Computed: true, 1864 Elem: &Schema{Type: TypeInt}, 1865 Set: func(a interface{}) int { 1866 return a.(int) 1867 }, 1868 }, 1869 }, 1870 1871 State: &terraform.InstanceState{ 1872 Attributes: map[string]string{ 1873 "ports.#": "2", 1874 "ports.100": "100", 1875 "ports.80": "80", 1876 }, 1877 }, 1878 1879 Key: "ports.100", 1880 Value: 256, 1881 Err: true, 1882 1883 GetKey: "ports", 1884 GetValue: []interface{}{100, 80}, 1885 }, 1886 1887 // #11: Set with nested set 1888 { 1889 Schema: map[string]*Schema{ 1890 "ports": &Schema{ 1891 Type: TypeSet, 1892 Elem: &Resource{ 1893 Schema: map[string]*Schema{ 1894 "port": &Schema{ 1895 Type: TypeInt, 1896 }, 1897 1898 "set": &Schema{ 1899 Type: TypeSet, 1900 Elem: &Schema{Type: TypeInt}, 1901 Set: func(a interface{}) int { 1902 return a.(int) 1903 }, 1904 }, 1905 }, 1906 }, 1907 Set: func(a interface{}) int { 1908 return a.(map[string]interface{})["port"].(int) 1909 }, 1910 }, 1911 }, 1912 1913 State: nil, 1914 1915 Key: "ports", 1916 Value: []interface{}{ 1917 map[string]interface{}{ 1918 "port": 80, 1919 }, 1920 }, 1921 1922 GetKey: "ports", 1923 GetValue: []interface{}{ 1924 map[string]interface{}{ 1925 "port": 80, 1926 "set": []interface{}{}, 1927 }, 1928 }, 1929 1930 GetPreProcess: func(v interface{}) interface{} { 1931 if v == nil { 1932 return v 1933 } 1934 s, ok := v.([]interface{}) 1935 if !ok { 1936 return v 1937 } 1938 for _, v := range s { 1939 m, ok := v.(map[string]interface{}) 1940 if !ok { 1941 continue 1942 } 1943 if m["set"] == nil { 1944 continue 1945 } 1946 if s, ok := m["set"].(*Set); ok { 1947 m["set"] = s.List() 1948 } 1949 } 1950 1951 return v 1952 }, 1953 }, 1954 1955 // #12: List of floats, set list 1956 { 1957 Schema: map[string]*Schema{ 1958 "ratios": &Schema{ 1959 Type: TypeList, 1960 Computed: true, 1961 Elem: &Schema{Type: TypeFloat}, 1962 }, 1963 }, 1964 1965 State: nil, 1966 1967 Diff: nil, 1968 1969 Key: "ratios", 1970 Value: []float64{1.0, 2.2, 5.5}, 1971 1972 GetKey: "ratios", 1973 GetValue: []interface{}{1.0, 2.2, 5.5}, 1974 }, 1975 1976 // #12: Set of floats, set list 1977 { 1978 Schema: map[string]*Schema{ 1979 "ratios": &Schema{ 1980 Type: TypeSet, 1981 Computed: true, 1982 Elem: &Schema{Type: TypeFloat}, 1983 Set: func(a interface{}) int { 1984 return int(math.Float64bits(a.(float64))) 1985 }, 1986 }, 1987 }, 1988 1989 State: nil, 1990 1991 Diff: nil, 1992 1993 Key: "ratios", 1994 Value: []float64{1.0, 2.2, 5.5}, 1995 1996 GetKey: "ratios", 1997 GetValue: []interface{}{1.0, 2.2, 5.5}, 1998 }, 1999 2000 // #13: Basic pointer 2001 { 2002 Schema: map[string]*Schema{ 2003 "availability_zone": &Schema{ 2004 Type: TypeString, 2005 Optional: true, 2006 Computed: true, 2007 ForceNew: true, 2008 }, 2009 }, 2010 2011 State: nil, 2012 2013 Diff: nil, 2014 2015 Key: "availability_zone", 2016 Value: testPtrTo("foo"), 2017 2018 GetKey: "availability_zone", 2019 GetValue: "foo", 2020 }, 2021 2022 // #14: Basic nil value 2023 { 2024 Schema: map[string]*Schema{ 2025 "availability_zone": &Schema{ 2026 Type: TypeString, 2027 Optional: true, 2028 Computed: true, 2029 ForceNew: true, 2030 }, 2031 }, 2032 2033 State: nil, 2034 2035 Diff: nil, 2036 2037 Key: "availability_zone", 2038 Value: testPtrTo(nil), 2039 2040 GetKey: "availability_zone", 2041 GetValue: "", 2042 }, 2043 2044 // #15: Basic nil pointer 2045 { 2046 Schema: map[string]*Schema{ 2047 "availability_zone": &Schema{ 2048 Type: TypeString, 2049 Optional: true, 2050 Computed: true, 2051 ForceNew: true, 2052 }, 2053 }, 2054 2055 State: nil, 2056 2057 Diff: nil, 2058 2059 Key: "availability_zone", 2060 Value: testNilPtr, 2061 2062 GetKey: "availability_zone", 2063 GetValue: "", 2064 }, 2065 2066 // #16: Set in a list 2067 { 2068 Schema: map[string]*Schema{ 2069 "ports": &Schema{ 2070 Type: TypeList, 2071 Elem: &Resource{ 2072 Schema: map[string]*Schema{ 2073 "set": &Schema{ 2074 Type: TypeSet, 2075 Elem: &Schema{Type: TypeInt}, 2076 Set: func(a interface{}) int { 2077 return a.(int) 2078 }, 2079 }, 2080 }, 2081 }, 2082 }, 2083 }, 2084 2085 State: nil, 2086 2087 Key: "ports", 2088 Value: []interface{}{ 2089 map[string]interface{}{ 2090 "set": []interface{}{ 2091 1, 2092 }, 2093 }, 2094 }, 2095 2096 GetKey: "ports", 2097 GetValue: []interface{}{ 2098 map[string]interface{}{ 2099 "set": []interface{}{ 2100 1, 2101 }, 2102 }, 2103 }, 2104 GetPreProcess: func(v interface{}) interface{} { 2105 if v == nil { 2106 return v 2107 } 2108 s, ok := v.([]interface{}) 2109 if !ok { 2110 return v 2111 } 2112 for _, v := range s { 2113 m, ok := v.(map[string]interface{}) 2114 if !ok { 2115 continue 2116 } 2117 if m["set"] == nil { 2118 continue 2119 } 2120 if s, ok := m["set"].(*Set); ok { 2121 m["set"] = s.List() 2122 } 2123 } 2124 2125 return v 2126 }, 2127 }, 2128 } 2129 2130 oldEnv := os.Getenv(PanicOnErr) 2131 os.Setenv(PanicOnErr, "") 2132 defer os.Setenv(PanicOnErr, oldEnv) 2133 2134 for i, tc := range cases { 2135 d, err := schemaMap(tc.Schema).Data(tc.State, tc.Diff) 2136 if err != nil { 2137 t.Fatalf("err: %s", err) 2138 } 2139 2140 err = d.Set(tc.Key, tc.Value) 2141 if err != nil != tc.Err { 2142 t.Fatalf("%d err: %s", i, err) 2143 } 2144 2145 v := d.Get(tc.GetKey) 2146 if s, ok := v.(*Set); ok { 2147 v = s.List() 2148 } 2149 2150 if tc.GetPreProcess != nil { 2151 v = tc.GetPreProcess(v) 2152 } 2153 2154 if !reflect.DeepEqual(v, tc.GetValue) { 2155 t.Fatalf("Get Bad: %d\n\n%#v", i, v) 2156 } 2157 } 2158 } 2159 2160 func TestResourceDataState_dynamicAttributes(t *testing.T) { 2161 cases := []struct { 2162 Schema map[string]*Schema 2163 State *terraform.InstanceState 2164 Diff *terraform.InstanceDiff 2165 Set map[string]interface{} 2166 UnsafeSet map[string]string 2167 Result *terraform.InstanceState 2168 }{ 2169 { 2170 Schema: map[string]*Schema{ 2171 "__has_dynamic_attributes": { 2172 Type: TypeString, 2173 Optional: true, 2174 }, 2175 2176 "schema_field": { 2177 Type: TypeString, 2178 Required: true, 2179 }, 2180 }, 2181 2182 State: nil, 2183 2184 Diff: nil, 2185 2186 Set: map[string]interface{}{ 2187 "schema_field": "present", 2188 }, 2189 2190 UnsafeSet: map[string]string{ 2191 "test1": "value", 2192 "test2": "value", 2193 }, 2194 2195 Result: &terraform.InstanceState{ 2196 Attributes: map[string]string{ 2197 "schema_field": "present", 2198 "test1": "value", 2199 "test2": "value", 2200 }, 2201 }, 2202 }, 2203 } 2204 2205 for i, tc := range cases { 2206 d, err := schemaMap(tc.Schema).Data(tc.State, tc.Diff) 2207 if err != nil { 2208 t.Fatalf("err: %s", err) 2209 } 2210 2211 for k, v := range tc.Set { 2212 d.Set(k, v) 2213 } 2214 2215 for k, v := range tc.UnsafeSet { 2216 d.UnsafeSetFieldRaw(k, v) 2217 } 2218 2219 // Set an ID so that the state returned is not nil 2220 idSet := false 2221 if d.Id() == "" { 2222 idSet = true 2223 d.SetId("foo") 2224 } 2225 2226 actual := d.State() 2227 2228 // If we set an ID, then undo what we did so the comparison works 2229 if actual != nil && idSet { 2230 actual.ID = "" 2231 delete(actual.Attributes, "id") 2232 } 2233 2234 if !reflect.DeepEqual(actual, tc.Result) { 2235 t.Fatalf("Bad: %d\n\n%#v\n\nExpected:\n\n%#v", i, actual, tc.Result) 2236 } 2237 } 2238 } 2239 2240 func TestResourceDataState_schema(t *testing.T) { 2241 cases := []struct { 2242 Schema map[string]*Schema 2243 State *terraform.InstanceState 2244 Diff *terraform.InstanceDiff 2245 Set map[string]interface{} 2246 Result *terraform.InstanceState 2247 Partial []string 2248 }{ 2249 // #0 Basic primitive in diff 2250 { 2251 Schema: map[string]*Schema{ 2252 "availability_zone": &Schema{ 2253 Type: TypeString, 2254 Optional: true, 2255 Computed: true, 2256 ForceNew: true, 2257 }, 2258 }, 2259 2260 State: nil, 2261 2262 Diff: &terraform.InstanceDiff{ 2263 Attributes: map[string]*terraform.ResourceAttrDiff{ 2264 "availability_zone": &terraform.ResourceAttrDiff{ 2265 Old: "", 2266 New: "foo", 2267 RequiresNew: true, 2268 }, 2269 }, 2270 }, 2271 2272 Result: &terraform.InstanceState{ 2273 Attributes: map[string]string{ 2274 "availability_zone": "foo", 2275 }, 2276 }, 2277 }, 2278 2279 // #1 Basic primitive set override 2280 { 2281 Schema: map[string]*Schema{ 2282 "availability_zone": &Schema{ 2283 Type: TypeString, 2284 Optional: true, 2285 Computed: true, 2286 ForceNew: true, 2287 }, 2288 }, 2289 2290 State: nil, 2291 2292 Diff: &terraform.InstanceDiff{ 2293 Attributes: map[string]*terraform.ResourceAttrDiff{ 2294 "availability_zone": &terraform.ResourceAttrDiff{ 2295 Old: "", 2296 New: "foo", 2297 RequiresNew: true, 2298 }, 2299 }, 2300 }, 2301 2302 Set: map[string]interface{}{ 2303 "availability_zone": "bar", 2304 }, 2305 2306 Result: &terraform.InstanceState{ 2307 Attributes: map[string]string{ 2308 "availability_zone": "bar", 2309 }, 2310 }, 2311 }, 2312 2313 // #2 2314 { 2315 Schema: map[string]*Schema{ 2316 "vpc": &Schema{ 2317 Type: TypeBool, 2318 Optional: true, 2319 }, 2320 }, 2321 2322 State: nil, 2323 2324 Diff: nil, 2325 2326 Set: map[string]interface{}{ 2327 "vpc": true, 2328 }, 2329 2330 Result: &terraform.InstanceState{ 2331 Attributes: map[string]string{ 2332 "vpc": "true", 2333 }, 2334 }, 2335 }, 2336 2337 // #3 Basic primitive with StateFunc set 2338 { 2339 Schema: map[string]*Schema{ 2340 "availability_zone": &Schema{ 2341 Type: TypeString, 2342 Optional: true, 2343 Computed: true, 2344 StateFunc: func(interface{}) string { return "" }, 2345 }, 2346 }, 2347 2348 State: nil, 2349 2350 Diff: &terraform.InstanceDiff{ 2351 Attributes: map[string]*terraform.ResourceAttrDiff{ 2352 "availability_zone": &terraform.ResourceAttrDiff{ 2353 Old: "", 2354 New: "foo", 2355 NewExtra: "foo!", 2356 }, 2357 }, 2358 }, 2359 2360 Result: &terraform.InstanceState{ 2361 Attributes: map[string]string{ 2362 "availability_zone": "foo", 2363 }, 2364 }, 2365 }, 2366 2367 // #4 List 2368 { 2369 Schema: map[string]*Schema{ 2370 "ports": &Schema{ 2371 Type: TypeList, 2372 Required: true, 2373 Elem: &Schema{Type: TypeInt}, 2374 }, 2375 }, 2376 2377 State: &terraform.InstanceState{ 2378 Attributes: map[string]string{ 2379 "ports.#": "1", 2380 "ports.0": "80", 2381 }, 2382 }, 2383 2384 Diff: &terraform.InstanceDiff{ 2385 Attributes: map[string]*terraform.ResourceAttrDiff{ 2386 "ports.#": &terraform.ResourceAttrDiff{ 2387 Old: "1", 2388 New: "2", 2389 }, 2390 "ports.1": &terraform.ResourceAttrDiff{ 2391 Old: "", 2392 New: "100", 2393 }, 2394 }, 2395 }, 2396 2397 Result: &terraform.InstanceState{ 2398 Attributes: map[string]string{ 2399 "ports.#": "2", 2400 "ports.0": "80", 2401 "ports.1": "100", 2402 }, 2403 }, 2404 }, 2405 2406 // #5 List of resources 2407 { 2408 Schema: map[string]*Schema{ 2409 "ingress": &Schema{ 2410 Type: TypeList, 2411 Required: true, 2412 Elem: &Resource{ 2413 Schema: map[string]*Schema{ 2414 "from": &Schema{ 2415 Type: TypeInt, 2416 Required: true, 2417 }, 2418 }, 2419 }, 2420 }, 2421 }, 2422 2423 State: &terraform.InstanceState{ 2424 Attributes: map[string]string{ 2425 "ingress.#": "1", 2426 "ingress.0.from": "80", 2427 }, 2428 }, 2429 2430 Diff: &terraform.InstanceDiff{ 2431 Attributes: map[string]*terraform.ResourceAttrDiff{ 2432 "ingress.#": &terraform.ResourceAttrDiff{ 2433 Old: "1", 2434 New: "2", 2435 }, 2436 "ingress.0.from": &terraform.ResourceAttrDiff{ 2437 Old: "80", 2438 New: "150", 2439 }, 2440 "ingress.1.from": &terraform.ResourceAttrDiff{ 2441 Old: "", 2442 New: "100", 2443 }, 2444 }, 2445 }, 2446 2447 Result: &terraform.InstanceState{ 2448 Attributes: map[string]string{ 2449 "ingress.#": "2", 2450 "ingress.0.from": "150", 2451 "ingress.1.from": "100", 2452 }, 2453 }, 2454 }, 2455 2456 // #6 List of maps 2457 { 2458 Schema: map[string]*Schema{ 2459 "config_vars": &Schema{ 2460 Type: TypeList, 2461 Optional: true, 2462 Computed: true, 2463 Elem: &Schema{ 2464 Type: TypeMap, 2465 }, 2466 }, 2467 }, 2468 2469 State: &terraform.InstanceState{ 2470 Attributes: map[string]string{ 2471 "config_vars.#": "2", 2472 "config_vars.0.%": "2", 2473 "config_vars.0.foo": "bar", 2474 "config_vars.0.bar": "bar", 2475 "config_vars.1.%": "1", 2476 "config_vars.1.bar": "baz", 2477 }, 2478 }, 2479 2480 Diff: &terraform.InstanceDiff{ 2481 Attributes: map[string]*terraform.ResourceAttrDiff{ 2482 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 2483 NewRemoved: true, 2484 }, 2485 }, 2486 }, 2487 2488 Set: map[string]interface{}{ 2489 "config_vars": []map[string]interface{}{ 2490 map[string]interface{}{ 2491 "foo": "bar", 2492 }, 2493 map[string]interface{}{ 2494 "baz": "bang", 2495 }, 2496 }, 2497 }, 2498 2499 Result: &terraform.InstanceState{ 2500 Attributes: map[string]string{ 2501 "config_vars.#": "2", 2502 "config_vars.0.%": "1", 2503 "config_vars.0.foo": "bar", 2504 "config_vars.1.%": "1", 2505 "config_vars.1.baz": "bang", 2506 }, 2507 }, 2508 }, 2509 2510 // #7 List of maps with removal in diff 2511 { 2512 Schema: map[string]*Schema{ 2513 "config_vars": &Schema{ 2514 Type: TypeList, 2515 Optional: true, 2516 Computed: true, 2517 Elem: &Schema{ 2518 Type: TypeMap, 2519 }, 2520 }, 2521 }, 2522 2523 State: &terraform.InstanceState{ 2524 Attributes: map[string]string{ 2525 "config_vars.#": "1", 2526 "config_vars.0.FOO": "bar", 2527 }, 2528 }, 2529 2530 Diff: &terraform.InstanceDiff{ 2531 Attributes: map[string]*terraform.ResourceAttrDiff{ 2532 "config_vars.#": &terraform.ResourceAttrDiff{ 2533 Old: "1", 2534 New: "0", 2535 }, 2536 "config_vars.0.FOO": &terraform.ResourceAttrDiff{ 2537 Old: "bar", 2538 NewRemoved: true, 2539 }, 2540 }, 2541 }, 2542 2543 Result: &terraform.InstanceState{ 2544 Attributes: map[string]string{ 2545 "config_vars.#": "0", 2546 }, 2547 }, 2548 }, 2549 2550 // #8 Basic state with other keys 2551 { 2552 Schema: map[string]*Schema{ 2553 "availability_zone": &Schema{ 2554 Type: TypeString, 2555 Optional: true, 2556 Computed: true, 2557 ForceNew: true, 2558 }, 2559 }, 2560 2561 State: &terraform.InstanceState{ 2562 ID: "bar", 2563 Attributes: map[string]string{ 2564 "id": "bar", 2565 }, 2566 }, 2567 2568 Diff: &terraform.InstanceDiff{ 2569 Attributes: map[string]*terraform.ResourceAttrDiff{ 2570 "availability_zone": &terraform.ResourceAttrDiff{ 2571 Old: "", 2572 New: "foo", 2573 RequiresNew: true, 2574 }, 2575 }, 2576 }, 2577 2578 Result: &terraform.InstanceState{ 2579 ID: "bar", 2580 Attributes: map[string]string{ 2581 "id": "bar", 2582 "availability_zone": "foo", 2583 }, 2584 }, 2585 }, 2586 2587 // #9 Sets 2588 { 2589 Schema: map[string]*Schema{ 2590 "ports": &Schema{ 2591 Type: TypeSet, 2592 Optional: true, 2593 Computed: true, 2594 Elem: &Schema{Type: TypeInt}, 2595 Set: func(a interface{}) int { 2596 return a.(int) 2597 }, 2598 }, 2599 }, 2600 2601 State: &terraform.InstanceState{ 2602 Attributes: map[string]string{ 2603 "ports.#": "3", 2604 "ports.100": "100", 2605 "ports.80": "80", 2606 "ports.81": "81", 2607 }, 2608 }, 2609 2610 Diff: nil, 2611 2612 Result: &terraform.InstanceState{ 2613 Attributes: map[string]string{ 2614 "ports.#": "3", 2615 "ports.80": "80", 2616 "ports.81": "81", 2617 "ports.100": "100", 2618 }, 2619 }, 2620 }, 2621 2622 // #10 2623 { 2624 Schema: map[string]*Schema{ 2625 "ports": &Schema{ 2626 Type: TypeSet, 2627 Optional: true, 2628 Computed: true, 2629 Elem: &Schema{Type: TypeInt}, 2630 Set: func(a interface{}) int { 2631 return a.(int) 2632 }, 2633 }, 2634 }, 2635 2636 State: nil, 2637 2638 Diff: nil, 2639 2640 Set: map[string]interface{}{ 2641 "ports": []interface{}{100, 80}, 2642 }, 2643 2644 Result: &terraform.InstanceState{ 2645 Attributes: map[string]string{ 2646 "ports.#": "2", 2647 "ports.80": "80", 2648 "ports.100": "100", 2649 }, 2650 }, 2651 }, 2652 2653 // #11 2654 { 2655 Schema: map[string]*Schema{ 2656 "ports": &Schema{ 2657 Type: TypeSet, 2658 Optional: true, 2659 Computed: true, 2660 Elem: &Resource{ 2661 Schema: map[string]*Schema{ 2662 "order": &Schema{ 2663 Type: TypeInt, 2664 }, 2665 2666 "a": &Schema{ 2667 Type: TypeList, 2668 Elem: &Schema{Type: TypeInt}, 2669 }, 2670 2671 "b": &Schema{ 2672 Type: TypeList, 2673 Elem: &Schema{Type: TypeInt}, 2674 }, 2675 }, 2676 }, 2677 Set: func(a interface{}) int { 2678 m := a.(map[string]interface{}) 2679 return m["order"].(int) 2680 }, 2681 }, 2682 }, 2683 2684 State: &terraform.InstanceState{ 2685 Attributes: map[string]string{ 2686 "ports.#": "2", 2687 "ports.10.order": "10", 2688 "ports.10.a.#": "1", 2689 "ports.10.a.0": "80", 2690 "ports.20.order": "20", 2691 "ports.20.b.#": "1", 2692 "ports.20.b.0": "100", 2693 }, 2694 }, 2695 2696 Set: map[string]interface{}{ 2697 "ports": []interface{}{ 2698 map[string]interface{}{ 2699 "order": 20, 2700 "b": []interface{}{100}, 2701 }, 2702 map[string]interface{}{ 2703 "order": 10, 2704 "a": []interface{}{80}, 2705 }, 2706 }, 2707 }, 2708 2709 Result: &terraform.InstanceState{ 2710 Attributes: map[string]string{ 2711 "ports.#": "2", 2712 "ports.10.order": "10", 2713 "ports.10.a.#": "1", 2714 "ports.10.a.0": "80", 2715 "ports.10.b.#": "0", 2716 "ports.20.order": "20", 2717 "ports.20.a.#": "0", 2718 "ports.20.b.#": "1", 2719 "ports.20.b.0": "100", 2720 }, 2721 }, 2722 }, 2723 2724 /* 2725 * PARTIAL STATES 2726 */ 2727 2728 // #12 Basic primitive 2729 { 2730 Schema: map[string]*Schema{ 2731 "availability_zone": &Schema{ 2732 Type: TypeString, 2733 Optional: true, 2734 Computed: true, 2735 ForceNew: true, 2736 }, 2737 }, 2738 2739 State: nil, 2740 2741 Diff: &terraform.InstanceDiff{ 2742 Attributes: map[string]*terraform.ResourceAttrDiff{ 2743 "availability_zone": &terraform.ResourceAttrDiff{ 2744 Old: "", 2745 New: "foo", 2746 RequiresNew: true, 2747 }, 2748 }, 2749 }, 2750 2751 Partial: []string{}, 2752 2753 Result: &terraform.InstanceState{ 2754 Attributes: map[string]string{}, 2755 }, 2756 }, 2757 2758 // #13 List 2759 { 2760 Schema: map[string]*Schema{ 2761 "ports": &Schema{ 2762 Type: TypeList, 2763 Required: true, 2764 Elem: &Schema{Type: TypeInt}, 2765 }, 2766 }, 2767 2768 State: &terraform.InstanceState{ 2769 Attributes: map[string]string{ 2770 "ports.#": "1", 2771 "ports.0": "80", 2772 }, 2773 }, 2774 2775 Diff: &terraform.InstanceDiff{ 2776 Attributes: map[string]*terraform.ResourceAttrDiff{ 2777 "ports.#": &terraform.ResourceAttrDiff{ 2778 Old: "1", 2779 New: "2", 2780 }, 2781 "ports.1": &terraform.ResourceAttrDiff{ 2782 Old: "", 2783 New: "100", 2784 }, 2785 }, 2786 }, 2787 2788 Partial: []string{}, 2789 2790 Result: &terraform.InstanceState{ 2791 Attributes: map[string]string{ 2792 "ports.#": "1", 2793 "ports.0": "80", 2794 }, 2795 }, 2796 }, 2797 2798 // #14 2799 { 2800 Schema: map[string]*Schema{ 2801 "ports": &Schema{ 2802 Type: TypeList, 2803 Optional: true, 2804 Computed: true, 2805 Elem: &Schema{Type: TypeInt}, 2806 }, 2807 }, 2808 2809 State: nil, 2810 2811 Diff: &terraform.InstanceDiff{ 2812 Attributes: map[string]*terraform.ResourceAttrDiff{ 2813 "ports.#": &terraform.ResourceAttrDiff{ 2814 Old: "", 2815 NewComputed: true, 2816 }, 2817 }, 2818 }, 2819 2820 Partial: []string{}, 2821 2822 Set: map[string]interface{}{ 2823 "ports": []interface{}{}, 2824 }, 2825 2826 Result: &terraform.InstanceState{ 2827 Attributes: map[string]string{}, 2828 }, 2829 }, 2830 2831 // #15 List of resources 2832 { 2833 Schema: map[string]*Schema{ 2834 "ingress": &Schema{ 2835 Type: TypeList, 2836 Required: true, 2837 Elem: &Resource{ 2838 Schema: map[string]*Schema{ 2839 "from": &Schema{ 2840 Type: TypeInt, 2841 Required: true, 2842 }, 2843 }, 2844 }, 2845 }, 2846 }, 2847 2848 State: &terraform.InstanceState{ 2849 Attributes: map[string]string{ 2850 "ingress.#": "1", 2851 "ingress.0.from": "80", 2852 }, 2853 }, 2854 2855 Diff: &terraform.InstanceDiff{ 2856 Attributes: map[string]*terraform.ResourceAttrDiff{ 2857 "ingress.#": &terraform.ResourceAttrDiff{ 2858 Old: "1", 2859 New: "2", 2860 }, 2861 "ingress.0.from": &terraform.ResourceAttrDiff{ 2862 Old: "80", 2863 New: "150", 2864 }, 2865 "ingress.1.from": &terraform.ResourceAttrDiff{ 2866 Old: "", 2867 New: "100", 2868 }, 2869 }, 2870 }, 2871 2872 Partial: []string{}, 2873 2874 Result: &terraform.InstanceState{ 2875 Attributes: map[string]string{ 2876 "ingress.#": "1", 2877 "ingress.0.from": "80", 2878 }, 2879 }, 2880 }, 2881 2882 // #16 List of maps 2883 { 2884 Schema: map[string]*Schema{ 2885 "config_vars": &Schema{ 2886 Type: TypeList, 2887 Optional: true, 2888 Computed: true, 2889 Elem: &Schema{ 2890 Type: TypeMap, 2891 }, 2892 }, 2893 }, 2894 2895 State: &terraform.InstanceState{ 2896 Attributes: map[string]string{ 2897 "config_vars.#": "2", 2898 "config_vars.0.foo": "bar", 2899 "config_vars.0.bar": "bar", 2900 "config_vars.1.bar": "baz", 2901 }, 2902 }, 2903 2904 Diff: &terraform.InstanceDiff{ 2905 Attributes: map[string]*terraform.ResourceAttrDiff{ 2906 "config_vars.0.bar": &terraform.ResourceAttrDiff{ 2907 NewRemoved: true, 2908 }, 2909 }, 2910 }, 2911 2912 Set: map[string]interface{}{ 2913 "config_vars": []map[string]interface{}{ 2914 map[string]interface{}{ 2915 "foo": "bar", 2916 }, 2917 map[string]interface{}{ 2918 "baz": "bang", 2919 }, 2920 }, 2921 }, 2922 2923 Partial: []string{}, 2924 2925 Result: &terraform.InstanceState{ 2926 Attributes: map[string]string{ 2927 // TODO: broken, shouldn't bar be removed? 2928 "config_vars.#": "2", 2929 "config_vars.0.%": "2", 2930 "config_vars.0.foo": "bar", 2931 "config_vars.0.bar": "bar", 2932 "config_vars.1.%": "1", 2933 "config_vars.1.bar": "baz", 2934 }, 2935 }, 2936 }, 2937 2938 // #17 Sets 2939 { 2940 Schema: map[string]*Schema{ 2941 "ports": &Schema{ 2942 Type: TypeSet, 2943 Optional: true, 2944 Computed: true, 2945 Elem: &Schema{Type: TypeInt}, 2946 Set: func(a interface{}) int { 2947 return a.(int) 2948 }, 2949 }, 2950 }, 2951 2952 State: &terraform.InstanceState{ 2953 Attributes: map[string]string{ 2954 "ports.#": "3", 2955 "ports.100": "100", 2956 "ports.80": "80", 2957 "ports.81": "81", 2958 }, 2959 }, 2960 2961 Diff: &terraform.InstanceDiff{ 2962 Attributes: map[string]*terraform.ResourceAttrDiff{ 2963 "ports.120": &terraform.ResourceAttrDiff{ 2964 New: "120", 2965 }, 2966 }, 2967 }, 2968 2969 Partial: []string{}, 2970 2971 Result: &terraform.InstanceState{ 2972 Attributes: map[string]string{ 2973 "ports.#": "3", 2974 "ports.80": "80", 2975 "ports.81": "81", 2976 "ports.100": "100", 2977 }, 2978 }, 2979 }, 2980 2981 // #18 2982 { 2983 Schema: map[string]*Schema{ 2984 "ports": &Schema{ 2985 Type: TypeSet, 2986 Optional: true, 2987 Computed: true, 2988 Elem: &Schema{Type: TypeInt}, 2989 Set: func(a interface{}) int { 2990 return a.(int) 2991 }, 2992 }, 2993 }, 2994 2995 State: nil, 2996 2997 Diff: &terraform.InstanceDiff{ 2998 Attributes: map[string]*terraform.ResourceAttrDiff{ 2999 "ports.#": &terraform.ResourceAttrDiff{ 3000 Old: "", 3001 NewComputed: true, 3002 }, 3003 }, 3004 }, 3005 3006 Partial: []string{}, 3007 3008 Result: &terraform.InstanceState{ 3009 Attributes: map[string]string{}, 3010 }, 3011 }, 3012 3013 // #19 Maps 3014 { 3015 Schema: map[string]*Schema{ 3016 "tags": &Schema{ 3017 Type: TypeMap, 3018 Optional: true, 3019 Computed: true, 3020 }, 3021 }, 3022 3023 State: nil, 3024 3025 Diff: &terraform.InstanceDiff{ 3026 Attributes: map[string]*terraform.ResourceAttrDiff{ 3027 "tags.Name": &terraform.ResourceAttrDiff{ 3028 Old: "", 3029 New: "foo", 3030 }, 3031 }, 3032 }, 3033 3034 Result: &terraform.InstanceState{ 3035 Attributes: map[string]string{ 3036 "tags.%": "1", 3037 "tags.Name": "foo", 3038 }, 3039 }, 3040 }, 3041 3042 // #20 empty computed map 3043 { 3044 Schema: map[string]*Schema{ 3045 "tags": &Schema{ 3046 Type: TypeMap, 3047 Optional: true, 3048 Computed: true, 3049 }, 3050 }, 3051 3052 State: nil, 3053 3054 Diff: &terraform.InstanceDiff{ 3055 Attributes: map[string]*terraform.ResourceAttrDiff{ 3056 "tags.Name": &terraform.ResourceAttrDiff{ 3057 Old: "", 3058 New: "foo", 3059 }, 3060 }, 3061 }, 3062 3063 Set: map[string]interface{}{ 3064 "tags": map[string]string{}, 3065 }, 3066 3067 Result: &terraform.InstanceState{ 3068 Attributes: map[string]string{ 3069 "tags.%": "0", 3070 }, 3071 }, 3072 }, 3073 3074 // #21 3075 { 3076 Schema: map[string]*Schema{ 3077 "foo": &Schema{ 3078 Type: TypeString, 3079 Optional: true, 3080 Computed: true, 3081 }, 3082 }, 3083 3084 State: nil, 3085 3086 Diff: &terraform.InstanceDiff{ 3087 Attributes: map[string]*terraform.ResourceAttrDiff{ 3088 "foo": &terraform.ResourceAttrDiff{ 3089 NewComputed: true, 3090 }, 3091 }, 3092 }, 3093 3094 Result: &terraform.InstanceState{ 3095 Attributes: map[string]string{}, 3096 }, 3097 }, 3098 3099 // #22 3100 { 3101 Schema: map[string]*Schema{ 3102 "foo": &Schema{ 3103 Type: TypeString, 3104 Optional: true, 3105 Computed: true, 3106 }, 3107 }, 3108 3109 State: nil, 3110 3111 Diff: &terraform.InstanceDiff{ 3112 Attributes: map[string]*terraform.ResourceAttrDiff{ 3113 "foo": &terraform.ResourceAttrDiff{ 3114 NewComputed: true, 3115 }, 3116 }, 3117 }, 3118 3119 Set: map[string]interface{}{ 3120 "foo": "bar", 3121 }, 3122 3123 Result: &terraform.InstanceState{ 3124 Attributes: map[string]string{ 3125 "foo": "bar", 3126 }, 3127 }, 3128 }, 3129 3130 // #23 Set of maps 3131 { 3132 Schema: map[string]*Schema{ 3133 "ports": &Schema{ 3134 Type: TypeSet, 3135 Optional: true, 3136 Computed: true, 3137 Elem: &Resource{ 3138 Schema: map[string]*Schema{ 3139 "index": &Schema{Type: TypeInt}, 3140 "uuids": &Schema{Type: TypeMap}, 3141 }, 3142 }, 3143 Set: func(a interface{}) int { 3144 m := a.(map[string]interface{}) 3145 return m["index"].(int) 3146 }, 3147 }, 3148 }, 3149 3150 State: nil, 3151 3152 Diff: &terraform.InstanceDiff{ 3153 Attributes: map[string]*terraform.ResourceAttrDiff{ 3154 "ports.10.uuids.#": &terraform.ResourceAttrDiff{ 3155 NewComputed: true, 3156 }, 3157 }, 3158 }, 3159 3160 Set: map[string]interface{}{ 3161 "ports": []interface{}{ 3162 map[string]interface{}{ 3163 "index": 10, 3164 "uuids": map[string]interface{}{ 3165 "80": "value", 3166 }, 3167 }, 3168 }, 3169 }, 3170 3171 Result: &terraform.InstanceState{ 3172 Attributes: map[string]string{ 3173 "ports.#": "1", 3174 "ports.10.index": "10", 3175 "ports.10.uuids.%": "1", 3176 "ports.10.uuids.80": "value", 3177 }, 3178 }, 3179 }, 3180 3181 // #24 3182 { 3183 Schema: map[string]*Schema{ 3184 "ports": &Schema{ 3185 Type: TypeSet, 3186 Optional: true, 3187 Computed: true, 3188 Elem: &Schema{Type: TypeInt}, 3189 Set: func(a interface{}) int { 3190 return a.(int) 3191 }, 3192 }, 3193 }, 3194 3195 State: &terraform.InstanceState{ 3196 Attributes: map[string]string{ 3197 "ports.#": "3", 3198 "ports.100": "100", 3199 "ports.80": "80", 3200 "ports.81": "81", 3201 }, 3202 }, 3203 3204 Diff: &terraform.InstanceDiff{ 3205 Attributes: map[string]*terraform.ResourceAttrDiff{ 3206 "ports.#": &terraform.ResourceAttrDiff{ 3207 Old: "3", 3208 New: "0", 3209 }, 3210 }, 3211 }, 3212 3213 Result: &terraform.InstanceState{ 3214 Attributes: map[string]string{ 3215 "ports.#": "0", 3216 }, 3217 }, 3218 }, 3219 3220 // #25 3221 { 3222 Schema: map[string]*Schema{ 3223 "ports": &Schema{ 3224 Type: TypeSet, 3225 Optional: true, 3226 Computed: true, 3227 Elem: &Schema{Type: TypeInt}, 3228 Set: func(a interface{}) int { 3229 return a.(int) 3230 }, 3231 }, 3232 }, 3233 3234 State: nil, 3235 3236 Diff: nil, 3237 3238 Set: map[string]interface{}{ 3239 "ports": []interface{}{}, 3240 }, 3241 3242 Result: &terraform.InstanceState{ 3243 Attributes: map[string]string{ 3244 "ports.#": "0", 3245 }, 3246 }, 3247 }, 3248 3249 // #26 3250 { 3251 Schema: map[string]*Schema{ 3252 "ports": &Schema{ 3253 Type: TypeList, 3254 Optional: true, 3255 Computed: true, 3256 Elem: &Schema{Type: TypeInt}, 3257 }, 3258 }, 3259 3260 State: nil, 3261 3262 Diff: nil, 3263 3264 Set: map[string]interface{}{ 3265 "ports": []interface{}{}, 3266 }, 3267 3268 Result: &terraform.InstanceState{ 3269 Attributes: map[string]string{ 3270 "ports.#": "0", 3271 }, 3272 }, 3273 }, 3274 3275 // #27 Set lists 3276 { 3277 Schema: map[string]*Schema{ 3278 "ports": &Schema{ 3279 Type: TypeList, 3280 Optional: true, 3281 Computed: true, 3282 Elem: &Resource{ 3283 Schema: map[string]*Schema{ 3284 "index": &Schema{Type: TypeInt}, 3285 "uuids": &Schema{Type: TypeMap}, 3286 }, 3287 }, 3288 }, 3289 }, 3290 3291 State: nil, 3292 3293 Diff: &terraform.InstanceDiff{ 3294 Attributes: map[string]*terraform.ResourceAttrDiff{ 3295 "ports.#": &terraform.ResourceAttrDiff{ 3296 NewComputed: true, 3297 }, 3298 }, 3299 }, 3300 3301 Set: map[string]interface{}{ 3302 "ports": []interface{}{ 3303 map[string]interface{}{ 3304 "index": 10, 3305 "uuids": map[string]interface{}{ 3306 "80": "value", 3307 }, 3308 }, 3309 }, 3310 }, 3311 3312 Result: &terraform.InstanceState{ 3313 Attributes: map[string]string{ 3314 "ports.#": "1", 3315 "ports.0.index": "10", 3316 "ports.0.uuids.%": "1", 3317 "ports.0.uuids.80": "value", 3318 }, 3319 }, 3320 }, 3321 } 3322 3323 for i, tc := range cases { 3324 d, err := schemaMap(tc.Schema).Data(tc.State, tc.Diff) 3325 if err != nil { 3326 t.Fatalf("err: %s", err) 3327 } 3328 3329 for k, v := range tc.Set { 3330 if err := d.Set(k, v); err != nil { 3331 t.Fatalf("%d err: %s", i, err) 3332 } 3333 } 3334 3335 // Set an ID so that the state returned is not nil 3336 idSet := false 3337 if d.Id() == "" { 3338 idSet = true 3339 d.SetId("foo") 3340 } 3341 3342 // If we have partial, then enable partial state mode. 3343 if tc.Partial != nil { 3344 d.Partial(true) 3345 for _, k := range tc.Partial { 3346 d.SetPartial(k) 3347 } 3348 } 3349 3350 actual := d.State() 3351 3352 // If we set an ID, then undo what we did so the comparison works 3353 if actual != nil && idSet { 3354 actual.ID = "" 3355 delete(actual.Attributes, "id") 3356 } 3357 3358 if !reflect.DeepEqual(actual, tc.Result) { 3359 t.Fatalf("Bad: %d\n\n%#v\n\nExpected:\n\n%#v", i, actual, tc.Result) 3360 } 3361 } 3362 } 3363 3364 func TestResourceData_nonStringValuesInMap(t *testing.T) { 3365 cases := []struct { 3366 Schema map[string]*Schema 3367 Diff *terraform.InstanceDiff 3368 MapFieldName string 3369 ItemName string 3370 ExpectedType string 3371 }{ 3372 { 3373 Schema: map[string]*Schema{ 3374 "boolMap": &Schema{ 3375 Type: TypeMap, 3376 Elem: TypeBool, 3377 Optional: true, 3378 }, 3379 }, 3380 Diff: &terraform.InstanceDiff{ 3381 Attributes: map[string]*terraform.ResourceAttrDiff{ 3382 "boolMap.%": &terraform.ResourceAttrDiff{ 3383 Old: "", 3384 New: "1", 3385 }, 3386 "boolMap.boolField": &terraform.ResourceAttrDiff{ 3387 Old: "", 3388 New: "true", 3389 }, 3390 }, 3391 }, 3392 MapFieldName: "boolMap", 3393 ItemName: "boolField", 3394 ExpectedType: "bool", 3395 }, 3396 { 3397 Schema: map[string]*Schema{ 3398 "intMap": &Schema{ 3399 Type: TypeMap, 3400 Elem: TypeInt, 3401 Optional: true, 3402 }, 3403 }, 3404 Diff: &terraform.InstanceDiff{ 3405 Attributes: map[string]*terraform.ResourceAttrDiff{ 3406 "intMap.%": &terraform.ResourceAttrDiff{ 3407 Old: "", 3408 New: "1", 3409 }, 3410 "intMap.intField": &terraform.ResourceAttrDiff{ 3411 Old: "", 3412 New: "8", 3413 }, 3414 }, 3415 }, 3416 MapFieldName: "intMap", 3417 ItemName: "intField", 3418 ExpectedType: "int", 3419 }, 3420 { 3421 Schema: map[string]*Schema{ 3422 "floatMap": &Schema{ 3423 Type: TypeMap, 3424 Elem: TypeFloat, 3425 Optional: true, 3426 }, 3427 }, 3428 Diff: &terraform.InstanceDiff{ 3429 Attributes: map[string]*terraform.ResourceAttrDiff{ 3430 "floatMap.%": &terraform.ResourceAttrDiff{ 3431 Old: "", 3432 New: "1", 3433 }, 3434 "floatMap.floatField": &terraform.ResourceAttrDiff{ 3435 Old: "", 3436 New: "8.22", 3437 }, 3438 }, 3439 }, 3440 MapFieldName: "floatMap", 3441 ItemName: "floatField", 3442 ExpectedType: "float64", 3443 }, 3444 } 3445 3446 for _, c := range cases { 3447 d, err := schemaMap(c.Schema).Data(nil, c.Diff) 3448 if err != nil { 3449 t.Fatalf("err: %s", err) 3450 } 3451 3452 m, ok := d.Get(c.MapFieldName).(map[string]interface{}) 3453 if !ok { 3454 t.Fatalf("expected %q to be castable to a map", c.MapFieldName) 3455 } 3456 field, ok := m[c.ItemName] 3457 if !ok { 3458 t.Fatalf("expected %q in the map", c.ItemName) 3459 } 3460 3461 typeName := reflect.TypeOf(field).Name() 3462 if typeName != c.ExpectedType { 3463 t.Fatalf("expected %q to be %q, it is %q.", 3464 c.ItemName, c.ExpectedType, typeName) 3465 } 3466 } 3467 } 3468 3469 func TestResourceDataSetConnInfo(t *testing.T) { 3470 d := &ResourceData{} 3471 d.SetId("foo") 3472 d.SetConnInfo(map[string]string{ 3473 "foo": "bar", 3474 }) 3475 3476 expected := map[string]string{ 3477 "foo": "bar", 3478 } 3479 3480 actual := d.State() 3481 if !reflect.DeepEqual(actual.Ephemeral.ConnInfo, expected) { 3482 t.Fatalf("bad: %#v", actual) 3483 } 3484 } 3485 3486 func TestResourceDataSetMeta_Timeouts(t *testing.T) { 3487 d := &ResourceData{} 3488 d.SetId("foo") 3489 3490 rt := ResourceTimeout{ 3491 Create: DefaultTimeout(7 * time.Minute), 3492 } 3493 3494 d.timeouts = &rt 3495 3496 expected := expectedForValues(7, 0, 0, 0, 0) 3497 3498 actual := d.State() 3499 if !reflect.DeepEqual(actual.Meta[TimeoutKey], expected) { 3500 t.Fatalf("Bad Meta_timeout match:\n\texpected: %#v\n\tgot: %#v", expected, actual.Meta[TimeoutKey]) 3501 } 3502 } 3503 3504 func TestResourceDataSetId(t *testing.T) { 3505 d := &ResourceData{ 3506 state: &terraform.InstanceState{ 3507 ID: "test", 3508 Attributes: map[string]string{ 3509 "id": "test", 3510 }, 3511 }, 3512 } 3513 d.SetId("foo") 3514 3515 actual := d.State() 3516 3517 // SetId should set both the ID field as well as the attribute, to aid in 3518 // transitioning to the new type system. 3519 if actual.ID != "foo" || actual.Attributes["id"] != "foo" { 3520 t.Fatalf("bad: %#v", actual) 3521 } 3522 3523 d.SetId("") 3524 actual = d.State() 3525 if actual != nil { 3526 t.Fatalf("bad: %#v", actual) 3527 } 3528 } 3529 3530 func TestResourceDataSetId_clear(t *testing.T) { 3531 d := &ResourceData{ 3532 state: &terraform.InstanceState{ID: "bar"}, 3533 } 3534 d.SetId("") 3535 3536 actual := d.State() 3537 if actual != nil { 3538 t.Fatalf("bad: %#v", actual) 3539 } 3540 } 3541 3542 func TestResourceDataSetId_override(t *testing.T) { 3543 d := &ResourceData{ 3544 state: &terraform.InstanceState{ID: "bar"}, 3545 } 3546 d.SetId("foo") 3547 3548 actual := d.State() 3549 if actual.ID != "foo" { 3550 t.Fatalf("bad: %#v", actual) 3551 } 3552 } 3553 3554 func TestResourceDataSetType(t *testing.T) { 3555 d := &ResourceData{} 3556 d.SetId("foo") 3557 d.SetType("bar") 3558 3559 actual := d.State() 3560 if v := actual.Ephemeral.Type; v != "bar" { 3561 t.Fatalf("bad: %#v", actual) 3562 } 3563 } 3564 3565 func testPtrTo(raw interface{}) interface{} { 3566 return &raw 3567 }