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