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