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