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