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