github.com/sylr/terraform@v0.11.12-beta1/helper/schema/resource_test.go (about) 1 package schema 2 3 import ( 4 "fmt" 5 "reflect" 6 "strconv" 7 "testing" 8 "time" 9 10 "github.com/hashicorp/terraform/config" 11 "github.com/hashicorp/terraform/terraform" 12 ) 13 14 func TestResourceApply_create(t *testing.T) { 15 r := &Resource{ 16 SchemaVersion: 2, 17 Schema: map[string]*Schema{ 18 "foo": &Schema{ 19 Type: TypeInt, 20 Optional: true, 21 }, 22 }, 23 } 24 25 called := false 26 r.Create = func(d *ResourceData, m interface{}) error { 27 called = true 28 d.SetId("foo") 29 return nil 30 } 31 32 var s *terraform.InstanceState = nil 33 34 d := &terraform.InstanceDiff{ 35 Attributes: map[string]*terraform.ResourceAttrDiff{ 36 "foo": &terraform.ResourceAttrDiff{ 37 New: "42", 38 }, 39 }, 40 } 41 42 actual, err := r.Apply(s, d, nil) 43 if err != nil { 44 t.Fatalf("err: %s", err) 45 } 46 47 if !called { 48 t.Fatal("not called") 49 } 50 51 expected := &terraform.InstanceState{ 52 ID: "foo", 53 Attributes: map[string]string{ 54 "id": "foo", 55 "foo": "42", 56 }, 57 Meta: map[string]interface{}{ 58 "schema_version": "2", 59 }, 60 } 61 62 if !reflect.DeepEqual(actual, expected) { 63 t.Fatalf("bad: %#v", actual) 64 } 65 } 66 67 func TestResourceApply_Timeout_state(t *testing.T) { 68 r := &Resource{ 69 SchemaVersion: 2, 70 Schema: map[string]*Schema{ 71 "foo": &Schema{ 72 Type: TypeInt, 73 Optional: true, 74 }, 75 }, 76 Timeouts: &ResourceTimeout{ 77 Create: DefaultTimeout(40 * time.Minute), 78 Update: DefaultTimeout(80 * time.Minute), 79 Delete: DefaultTimeout(40 * time.Minute), 80 }, 81 } 82 83 called := false 84 r.Create = func(d *ResourceData, m interface{}) error { 85 called = true 86 d.SetId("foo") 87 return nil 88 } 89 90 var s *terraform.InstanceState = nil 91 92 d := &terraform.InstanceDiff{ 93 Attributes: map[string]*terraform.ResourceAttrDiff{ 94 "foo": &terraform.ResourceAttrDiff{ 95 New: "42", 96 }, 97 }, 98 } 99 100 diffTimeout := &ResourceTimeout{ 101 Create: DefaultTimeout(40 * time.Minute), 102 Update: DefaultTimeout(80 * time.Minute), 103 Delete: DefaultTimeout(40 * time.Minute), 104 } 105 106 if err := diffTimeout.DiffEncode(d); err != nil { 107 t.Fatalf("Error encoding timeout to diff: %s", err) 108 } 109 110 actual, err := r.Apply(s, d, nil) 111 if err != nil { 112 t.Fatalf("err: %s", err) 113 } 114 115 if !called { 116 t.Fatal("not called") 117 } 118 119 expected := &terraform.InstanceState{ 120 ID: "foo", 121 Attributes: map[string]string{ 122 "id": "foo", 123 "foo": "42", 124 }, 125 Meta: map[string]interface{}{ 126 "schema_version": "2", 127 TimeoutKey: expectedForValues(40, 0, 80, 40, 0), 128 }, 129 } 130 131 if !reflect.DeepEqual(actual, expected) { 132 t.Fatalf("Not equal in Timeout State:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta) 133 } 134 } 135 136 // Regression test to ensure that the meta data is read from state, if a 137 // resource is destroyed and the timeout meta is no longer available from the 138 // config 139 func TestResourceApply_Timeout_destroy(t *testing.T) { 140 timeouts := &ResourceTimeout{ 141 Create: DefaultTimeout(40 * time.Minute), 142 Update: DefaultTimeout(80 * time.Minute), 143 Delete: DefaultTimeout(40 * time.Minute), 144 } 145 146 r := &Resource{ 147 Schema: map[string]*Schema{ 148 "foo": &Schema{ 149 Type: TypeInt, 150 Optional: true, 151 }, 152 }, 153 Timeouts: timeouts, 154 } 155 156 called := false 157 var delTimeout time.Duration 158 r.Delete = func(d *ResourceData, m interface{}) error { 159 delTimeout = d.Timeout(TimeoutDelete) 160 called = true 161 return nil 162 } 163 164 s := &terraform.InstanceState{ 165 ID: "bar", 166 } 167 168 if err := timeouts.StateEncode(s); err != nil { 169 t.Fatalf("Error encoding to state: %s", err) 170 } 171 172 d := &terraform.InstanceDiff{ 173 Destroy: true, 174 } 175 176 actual, err := r.Apply(s, d, nil) 177 if err != nil { 178 t.Fatalf("err: %s", err) 179 } 180 181 if !called { 182 t.Fatal("delete not called") 183 } 184 185 if *timeouts.Delete != delTimeout { 186 t.Fatalf("timeouts don't match, expected (%#v), got (%#v)", timeouts.Delete, delTimeout) 187 } 188 189 if actual != nil { 190 t.Fatalf("bad: %#v", actual) 191 } 192 } 193 194 func TestResourceDiff_Timeout_diff(t *testing.T) { 195 r := &Resource{ 196 Schema: map[string]*Schema{ 197 "foo": &Schema{ 198 Type: TypeInt, 199 Optional: true, 200 }, 201 }, 202 Timeouts: &ResourceTimeout{ 203 Create: DefaultTimeout(40 * time.Minute), 204 Update: DefaultTimeout(80 * time.Minute), 205 Delete: DefaultTimeout(40 * time.Minute), 206 }, 207 } 208 209 r.Create = func(d *ResourceData, m interface{}) error { 210 d.SetId("foo") 211 return nil 212 } 213 214 raw, err := config.NewRawConfig( 215 map[string]interface{}{ 216 "foo": 42, 217 "timeouts": []map[string]interface{}{ 218 map[string]interface{}{ 219 "create": "2h", 220 }}, 221 }) 222 if err != nil { 223 t.Fatalf("err: %s", err) 224 } 225 226 var s *terraform.InstanceState = nil 227 conf := terraform.NewResourceConfig(raw) 228 229 actual, err := r.Diff(s, conf, nil) 230 if err != nil { 231 t.Fatalf("err: %s", err) 232 } 233 234 expected := &terraform.InstanceDiff{ 235 Attributes: map[string]*terraform.ResourceAttrDiff{ 236 "foo": &terraform.ResourceAttrDiff{ 237 New: "42", 238 }, 239 }, 240 } 241 242 diffTimeout := &ResourceTimeout{ 243 Create: DefaultTimeout(120 * time.Minute), 244 Update: DefaultTimeout(80 * time.Minute), 245 Delete: DefaultTimeout(40 * time.Minute), 246 } 247 248 if err := diffTimeout.DiffEncode(expected); err != nil { 249 t.Fatalf("Error encoding timeout to diff: %s", err) 250 } 251 252 if !reflect.DeepEqual(actual, expected) { 253 t.Fatalf("Not equal in Timeout Diff:\n\texpected: %#v\n\tactual: %#v", expected.Meta, actual.Meta) 254 } 255 } 256 257 func TestResourceDiff_CustomizeFunc(t *testing.T) { 258 r := &Resource{ 259 Schema: map[string]*Schema{ 260 "foo": &Schema{ 261 Type: TypeInt, 262 Optional: true, 263 }, 264 }, 265 } 266 267 var called bool 268 269 r.CustomizeDiff = func(d *ResourceDiff, m interface{}) error { 270 called = true 271 return nil 272 } 273 274 raw, err := config.NewRawConfig( 275 map[string]interface{}{ 276 "foo": 42, 277 }) 278 if err != nil { 279 t.Fatalf("err: %s", err) 280 } 281 282 var s *terraform.InstanceState 283 conf := terraform.NewResourceConfig(raw) 284 285 _, err = r.Diff(s, conf, nil) 286 if err != nil { 287 t.Fatalf("err: %s", err) 288 } 289 290 if !called { 291 t.Fatalf("diff customization not called") 292 } 293 } 294 295 func TestResourceApply_destroy(t *testing.T) { 296 r := &Resource{ 297 Schema: map[string]*Schema{ 298 "foo": &Schema{ 299 Type: TypeInt, 300 Optional: true, 301 }, 302 }, 303 } 304 305 called := false 306 r.Delete = func(d *ResourceData, m interface{}) error { 307 called = true 308 return nil 309 } 310 311 s := &terraform.InstanceState{ 312 ID: "bar", 313 } 314 315 d := &terraform.InstanceDiff{ 316 Destroy: true, 317 } 318 319 actual, err := r.Apply(s, d, nil) 320 if err != nil { 321 t.Fatalf("err: %s", err) 322 } 323 324 if !called { 325 t.Fatal("delete not called") 326 } 327 328 if actual != nil { 329 t.Fatalf("bad: %#v", actual) 330 } 331 } 332 333 func TestResourceApply_destroyCreate(t *testing.T) { 334 r := &Resource{ 335 Schema: map[string]*Schema{ 336 "foo": &Schema{ 337 Type: TypeInt, 338 Optional: true, 339 }, 340 341 "tags": &Schema{ 342 Type: TypeMap, 343 Optional: true, 344 Computed: true, 345 }, 346 }, 347 } 348 349 change := false 350 r.Create = func(d *ResourceData, m interface{}) error { 351 change = d.HasChange("tags") 352 d.SetId("foo") 353 return nil 354 } 355 r.Delete = func(d *ResourceData, m interface{}) error { 356 return nil 357 } 358 359 var s *terraform.InstanceState = &terraform.InstanceState{ 360 ID: "bar", 361 Attributes: map[string]string{ 362 "foo": "bar", 363 "tags.Name": "foo", 364 }, 365 } 366 367 d := &terraform.InstanceDiff{ 368 Attributes: map[string]*terraform.ResourceAttrDiff{ 369 "foo": &terraform.ResourceAttrDiff{ 370 New: "42", 371 RequiresNew: true, 372 }, 373 "tags.Name": &terraform.ResourceAttrDiff{ 374 Old: "foo", 375 New: "foo", 376 RequiresNew: true, 377 }, 378 }, 379 } 380 381 actual, err := r.Apply(s, d, nil) 382 if err != nil { 383 t.Fatalf("err: %s", err) 384 } 385 386 if !change { 387 t.Fatal("should have change") 388 } 389 390 expected := &terraform.InstanceState{ 391 ID: "foo", 392 Attributes: map[string]string{ 393 "id": "foo", 394 "foo": "42", 395 "tags.%": "1", 396 "tags.Name": "foo", 397 }, 398 } 399 400 if !reflect.DeepEqual(actual, expected) { 401 t.Fatalf("bad: %#v", actual) 402 } 403 } 404 405 func TestResourceApply_destroyPartial(t *testing.T) { 406 r := &Resource{ 407 Schema: map[string]*Schema{ 408 "foo": &Schema{ 409 Type: TypeInt, 410 Optional: true, 411 }, 412 }, 413 SchemaVersion: 3, 414 } 415 416 r.Delete = func(d *ResourceData, m interface{}) error { 417 d.Set("foo", 42) 418 return fmt.Errorf("some error") 419 } 420 421 s := &terraform.InstanceState{ 422 ID: "bar", 423 Attributes: map[string]string{ 424 "foo": "12", 425 }, 426 } 427 428 d := &terraform.InstanceDiff{ 429 Destroy: true, 430 } 431 432 actual, err := r.Apply(s, d, nil) 433 if err == nil { 434 t.Fatal("should error") 435 } 436 437 expected := &terraform.InstanceState{ 438 ID: "bar", 439 Attributes: map[string]string{ 440 "id": "bar", 441 "foo": "42", 442 }, 443 Meta: map[string]interface{}{ 444 "schema_version": "3", 445 }, 446 } 447 448 if !reflect.DeepEqual(actual, expected) { 449 t.Fatalf("expected:\n%#v\n\ngot:\n%#v", expected, actual) 450 } 451 } 452 453 func TestResourceApply_update(t *testing.T) { 454 r := &Resource{ 455 Schema: map[string]*Schema{ 456 "foo": &Schema{ 457 Type: TypeInt, 458 Optional: true, 459 }, 460 }, 461 } 462 463 r.Update = func(d *ResourceData, m interface{}) error { 464 d.Set("foo", 42) 465 return nil 466 } 467 468 s := &terraform.InstanceState{ 469 ID: "foo", 470 Attributes: map[string]string{ 471 "foo": "12", 472 }, 473 } 474 475 d := &terraform.InstanceDiff{ 476 Attributes: map[string]*terraform.ResourceAttrDiff{ 477 "foo": &terraform.ResourceAttrDiff{ 478 New: "13", 479 }, 480 }, 481 } 482 483 actual, err := r.Apply(s, d, nil) 484 if err != nil { 485 t.Fatalf("err: %s", err) 486 } 487 488 expected := &terraform.InstanceState{ 489 ID: "foo", 490 Attributes: map[string]string{ 491 "id": "foo", 492 "foo": "42", 493 }, 494 } 495 496 if !reflect.DeepEqual(actual, expected) { 497 t.Fatalf("bad: %#v", actual) 498 } 499 } 500 501 func TestResourceApply_updateNoCallback(t *testing.T) { 502 r := &Resource{ 503 Schema: map[string]*Schema{ 504 "foo": &Schema{ 505 Type: TypeInt, 506 Optional: true, 507 }, 508 }, 509 } 510 511 r.Update = nil 512 513 s := &terraform.InstanceState{ 514 ID: "foo", 515 Attributes: map[string]string{ 516 "foo": "12", 517 }, 518 } 519 520 d := &terraform.InstanceDiff{ 521 Attributes: map[string]*terraform.ResourceAttrDiff{ 522 "foo": &terraform.ResourceAttrDiff{ 523 New: "13", 524 }, 525 }, 526 } 527 528 actual, err := r.Apply(s, d, nil) 529 if err == nil { 530 t.Fatal("should error") 531 } 532 533 expected := &terraform.InstanceState{ 534 ID: "foo", 535 Attributes: map[string]string{ 536 "foo": "12", 537 }, 538 } 539 540 if !reflect.DeepEqual(actual, expected) { 541 t.Fatalf("bad: %#v", actual) 542 } 543 } 544 545 func TestResourceApply_isNewResource(t *testing.T) { 546 r := &Resource{ 547 Schema: map[string]*Schema{ 548 "foo": &Schema{ 549 Type: TypeString, 550 Optional: true, 551 }, 552 }, 553 } 554 555 updateFunc := func(d *ResourceData, m interface{}) error { 556 d.Set("foo", "updated") 557 if d.IsNewResource() { 558 d.Set("foo", "new-resource") 559 } 560 return nil 561 } 562 r.Create = func(d *ResourceData, m interface{}) error { 563 d.SetId("foo") 564 d.Set("foo", "created") 565 return updateFunc(d, m) 566 } 567 r.Update = updateFunc 568 569 d := &terraform.InstanceDiff{ 570 Attributes: map[string]*terraform.ResourceAttrDiff{ 571 "foo": &terraform.ResourceAttrDiff{ 572 New: "bla-blah", 573 }, 574 }, 575 } 576 577 // positive test 578 var s *terraform.InstanceState = nil 579 580 actual, err := r.Apply(s, d, nil) 581 if err != nil { 582 t.Fatalf("err: %s", err) 583 } 584 585 expected := &terraform.InstanceState{ 586 ID: "foo", 587 Attributes: map[string]string{ 588 "id": "foo", 589 "foo": "new-resource", 590 }, 591 } 592 593 if !reflect.DeepEqual(actual, expected) { 594 t.Fatalf("actual: %#v\nexpected: %#v", 595 actual, expected) 596 } 597 598 // negative test 599 s = &terraform.InstanceState{ 600 ID: "foo", 601 Attributes: map[string]string{ 602 "id": "foo", 603 "foo": "new-resource", 604 }, 605 } 606 607 actual, err = r.Apply(s, d, nil) 608 if err != nil { 609 t.Fatalf("err: %s", err) 610 } 611 612 expected = &terraform.InstanceState{ 613 ID: "foo", 614 Attributes: map[string]string{ 615 "id": "foo", 616 "foo": "updated", 617 }, 618 } 619 620 if !reflect.DeepEqual(actual, expected) { 621 t.Fatalf("actual: %#v\nexpected: %#v", 622 actual, expected) 623 } 624 } 625 626 func TestResourceInternalValidate(t *testing.T) { 627 cases := []struct { 628 In *Resource 629 Writable bool 630 Err bool 631 }{ 632 0: { 633 nil, 634 true, 635 true, 636 }, 637 638 // No optional and no required 639 1: { 640 &Resource{ 641 Schema: map[string]*Schema{ 642 "foo": &Schema{ 643 Type: TypeInt, 644 Optional: true, 645 Required: true, 646 }, 647 }, 648 }, 649 true, 650 true, 651 }, 652 653 // Update undefined for non-ForceNew field 654 2: { 655 &Resource{ 656 Create: func(d *ResourceData, meta interface{}) error { return nil }, 657 Schema: map[string]*Schema{ 658 "boo": &Schema{ 659 Type: TypeInt, 660 Optional: true, 661 }, 662 }, 663 }, 664 true, 665 true, 666 }, 667 668 // Update defined for ForceNew field 669 3: { 670 &Resource{ 671 Create: func(d *ResourceData, meta interface{}) error { return nil }, 672 Update: func(d *ResourceData, meta interface{}) error { return nil }, 673 Schema: map[string]*Schema{ 674 "goo": &Schema{ 675 Type: TypeInt, 676 Optional: true, 677 ForceNew: true, 678 }, 679 }, 680 }, 681 true, 682 true, 683 }, 684 685 // non-writable doesn't need Update, Create or Delete 686 4: { 687 &Resource{ 688 Schema: map[string]*Schema{ 689 "goo": &Schema{ 690 Type: TypeInt, 691 Optional: true, 692 }, 693 }, 694 }, 695 false, 696 false, 697 }, 698 699 // non-writable *must not* have Create 700 5: { 701 &Resource{ 702 Create: func(d *ResourceData, meta interface{}) error { return nil }, 703 Schema: map[string]*Schema{ 704 "goo": &Schema{ 705 Type: TypeInt, 706 Optional: true, 707 }, 708 }, 709 }, 710 false, 711 true, 712 }, 713 714 // writable must have Read 715 6: { 716 &Resource{ 717 Create: func(d *ResourceData, meta interface{}) error { return nil }, 718 Update: func(d *ResourceData, meta interface{}) error { return nil }, 719 Delete: func(d *ResourceData, meta interface{}) error { return nil }, 720 Schema: map[string]*Schema{ 721 "goo": &Schema{ 722 Type: TypeInt, 723 Optional: true, 724 }, 725 }, 726 }, 727 true, 728 true, 729 }, 730 731 // writable must have Delete 732 7: { 733 &Resource{ 734 Create: func(d *ResourceData, meta interface{}) error { return nil }, 735 Read: func(d *ResourceData, meta interface{}) error { return nil }, 736 Update: func(d *ResourceData, meta interface{}) error { return nil }, 737 Schema: map[string]*Schema{ 738 "goo": &Schema{ 739 Type: TypeInt, 740 Optional: true, 741 }, 742 }, 743 }, 744 true, 745 true, 746 }, 747 748 8: { // Reserved name at root should be disallowed 749 &Resource{ 750 Create: func(d *ResourceData, meta interface{}) error { return nil }, 751 Read: func(d *ResourceData, meta interface{}) error { return nil }, 752 Update: func(d *ResourceData, meta interface{}) error { return nil }, 753 Delete: func(d *ResourceData, meta interface{}) error { return nil }, 754 Schema: map[string]*Schema{ 755 "count": { 756 Type: TypeInt, 757 Optional: true, 758 }, 759 }, 760 }, 761 true, 762 true, 763 }, 764 765 9: { // Reserved name at nested levels should be allowed 766 &Resource{ 767 Create: func(d *ResourceData, meta interface{}) error { return nil }, 768 Read: func(d *ResourceData, meta interface{}) error { return nil }, 769 Update: func(d *ResourceData, meta interface{}) error { return nil }, 770 Delete: func(d *ResourceData, meta interface{}) error { return nil }, 771 Schema: map[string]*Schema{ 772 "parent_list": &Schema{ 773 Type: TypeString, 774 Optional: true, 775 Elem: &Resource{ 776 Schema: map[string]*Schema{ 777 "provisioner": { 778 Type: TypeString, 779 Optional: true, 780 }, 781 }, 782 }, 783 }, 784 }, 785 }, 786 true, 787 false, 788 }, 789 790 10: { // Provider reserved name should be allowed in resource 791 &Resource{ 792 Create: func(d *ResourceData, meta interface{}) error { return nil }, 793 Read: func(d *ResourceData, meta interface{}) error { return nil }, 794 Update: func(d *ResourceData, meta interface{}) error { return nil }, 795 Delete: func(d *ResourceData, meta interface{}) error { return nil }, 796 Schema: map[string]*Schema{ 797 "alias": &Schema{ 798 Type: TypeString, 799 Optional: true, 800 }, 801 }, 802 }, 803 true, 804 false, 805 }, 806 807 11: { // ID should be allowed in data source 808 &Resource{ 809 Read: func(d *ResourceData, meta interface{}) error { return nil }, 810 Schema: map[string]*Schema{ 811 "id": &Schema{ 812 Type: TypeString, 813 Optional: true, 814 }, 815 }, 816 }, 817 false, 818 false, 819 }, 820 821 12: { // Deprecated ID should be allowed in resource 822 &Resource{ 823 Create: func(d *ResourceData, meta interface{}) error { return nil }, 824 Read: func(d *ResourceData, meta interface{}) error { return nil }, 825 Update: func(d *ResourceData, meta interface{}) error { return nil }, 826 Delete: func(d *ResourceData, meta interface{}) error { return nil }, 827 Schema: map[string]*Schema{ 828 "id": &Schema{ 829 Type: TypeString, 830 Optional: true, 831 Deprecated: "Use x_id instead", 832 }, 833 }, 834 }, 835 true, 836 false, 837 }, 838 839 13: { // non-writable must not define CustomizeDiff 840 &Resource{ 841 Read: func(d *ResourceData, meta interface{}) error { return nil }, 842 Schema: map[string]*Schema{ 843 "goo": &Schema{ 844 Type: TypeInt, 845 Optional: true, 846 }, 847 }, 848 CustomizeDiff: func(*ResourceDiff, interface{}) error { return nil }, 849 }, 850 false, 851 true, 852 }, 853 14: { // Deprecated resource 854 &Resource{ 855 Read: func(d *ResourceData, meta interface{}) error { return nil }, 856 Schema: map[string]*Schema{ 857 "goo": &Schema{ 858 Type: TypeInt, 859 Optional: true, 860 }, 861 }, 862 DeprecationMessage: "This resource has been deprecated.", 863 }, 864 true, 865 true, 866 }, 867 } 868 869 for i, tc := range cases { 870 t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 871 sm := schemaMap{} 872 if tc.In != nil { 873 sm = schemaMap(tc.In.Schema) 874 } 875 876 err := tc.In.InternalValidate(sm, tc.Writable) 877 if err != nil && !tc.Err { 878 t.Fatalf("%d: expected validation to pass: %s", i, err) 879 } 880 if err == nil && tc.Err { 881 t.Fatalf("%d: expected validation to fail", i) 882 } 883 }) 884 } 885 } 886 887 func TestResourceRefresh(t *testing.T) { 888 r := &Resource{ 889 SchemaVersion: 2, 890 Schema: map[string]*Schema{ 891 "foo": &Schema{ 892 Type: TypeInt, 893 Optional: true, 894 }, 895 }, 896 } 897 898 r.Read = func(d *ResourceData, m interface{}) error { 899 if m != 42 { 900 return fmt.Errorf("meta not passed") 901 } 902 903 return d.Set("foo", d.Get("foo").(int)+1) 904 } 905 906 s := &terraform.InstanceState{ 907 ID: "bar", 908 Attributes: map[string]string{ 909 "foo": "12", 910 }, 911 } 912 913 expected := &terraform.InstanceState{ 914 ID: "bar", 915 Attributes: map[string]string{ 916 "id": "bar", 917 "foo": "13", 918 }, 919 Meta: map[string]interface{}{ 920 "schema_version": "2", 921 }, 922 } 923 924 actual, err := r.Refresh(s, 42) 925 if err != nil { 926 t.Fatalf("err: %s", err) 927 } 928 929 if !reflect.DeepEqual(actual, expected) { 930 t.Fatalf("bad: %#v", actual) 931 } 932 } 933 934 func TestResourceRefresh_blankId(t *testing.T) { 935 r := &Resource{ 936 Schema: map[string]*Schema{ 937 "foo": &Schema{ 938 Type: TypeInt, 939 Optional: true, 940 }, 941 }, 942 } 943 944 r.Read = func(d *ResourceData, m interface{}) error { 945 d.SetId("foo") 946 return nil 947 } 948 949 s := &terraform.InstanceState{ 950 ID: "", 951 Attributes: map[string]string{}, 952 } 953 954 actual, err := r.Refresh(s, 42) 955 if err != nil { 956 t.Fatalf("err: %s", err) 957 } 958 if actual != nil { 959 t.Fatalf("bad: %#v", actual) 960 } 961 } 962 963 func TestResourceRefresh_delete(t *testing.T) { 964 r := &Resource{ 965 Schema: map[string]*Schema{ 966 "foo": &Schema{ 967 Type: TypeInt, 968 Optional: true, 969 }, 970 }, 971 } 972 973 r.Read = func(d *ResourceData, m interface{}) error { 974 d.SetId("") 975 return nil 976 } 977 978 s := &terraform.InstanceState{ 979 ID: "bar", 980 Attributes: map[string]string{ 981 "foo": "12", 982 }, 983 } 984 985 actual, err := r.Refresh(s, 42) 986 if err != nil { 987 t.Fatalf("err: %s", err) 988 } 989 990 if actual != nil { 991 t.Fatalf("bad: %#v", actual) 992 } 993 } 994 995 func TestResourceRefresh_existsError(t *testing.T) { 996 r := &Resource{ 997 Schema: map[string]*Schema{ 998 "foo": &Schema{ 999 Type: TypeInt, 1000 Optional: true, 1001 }, 1002 }, 1003 } 1004 1005 r.Exists = func(*ResourceData, interface{}) (bool, error) { 1006 return false, fmt.Errorf("error") 1007 } 1008 1009 r.Read = func(d *ResourceData, m interface{}) error { 1010 panic("shouldn't be called") 1011 } 1012 1013 s := &terraform.InstanceState{ 1014 ID: "bar", 1015 Attributes: map[string]string{ 1016 "foo": "12", 1017 }, 1018 } 1019 1020 actual, err := r.Refresh(s, 42) 1021 if err == nil { 1022 t.Fatalf("should error") 1023 } 1024 if !reflect.DeepEqual(actual, s) { 1025 t.Fatalf("bad: %#v", actual) 1026 } 1027 } 1028 1029 func TestResourceRefresh_noExists(t *testing.T) { 1030 r := &Resource{ 1031 Schema: map[string]*Schema{ 1032 "foo": &Schema{ 1033 Type: TypeInt, 1034 Optional: true, 1035 }, 1036 }, 1037 } 1038 1039 r.Exists = func(*ResourceData, interface{}) (bool, error) { 1040 return false, nil 1041 } 1042 1043 r.Read = func(d *ResourceData, m interface{}) error { 1044 panic("shouldn't be called") 1045 } 1046 1047 s := &terraform.InstanceState{ 1048 ID: "bar", 1049 Attributes: map[string]string{ 1050 "foo": "12", 1051 }, 1052 } 1053 1054 actual, err := r.Refresh(s, 42) 1055 if err != nil { 1056 t.Fatalf("err: %s", err) 1057 } 1058 if actual != nil { 1059 t.Fatalf("should have no state") 1060 } 1061 } 1062 1063 func TestResourceRefresh_needsMigration(t *testing.T) { 1064 // Schema v2 it deals only in newfoo, which tracks foo as an int 1065 r := &Resource{ 1066 SchemaVersion: 2, 1067 Schema: map[string]*Schema{ 1068 "newfoo": &Schema{ 1069 Type: TypeInt, 1070 Optional: true, 1071 }, 1072 }, 1073 } 1074 1075 r.Read = func(d *ResourceData, m interface{}) error { 1076 return d.Set("newfoo", d.Get("newfoo").(int)+1) 1077 } 1078 1079 r.MigrateState = func( 1080 v int, 1081 s *terraform.InstanceState, 1082 meta interface{}) (*terraform.InstanceState, error) { 1083 // Real state migration functions will probably switch on this value, 1084 // but we'll just assert on it for now. 1085 if v != 1 { 1086 t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v) 1087 } 1088 1089 if meta != 42 { 1090 t.Fatal("Expected meta to be passed through to the migration function") 1091 } 1092 1093 oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64) 1094 if err != nil { 1095 t.Fatalf("err: %#v", err) 1096 } 1097 s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10)) 1098 delete(s.Attributes, "oldfoo") 1099 1100 return s, nil 1101 } 1102 1103 // State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th 1104 // the scale of newfoo 1105 s := &terraform.InstanceState{ 1106 ID: "bar", 1107 Attributes: map[string]string{ 1108 "oldfoo": "1.2", 1109 }, 1110 Meta: map[string]interface{}{ 1111 "schema_version": "1", 1112 }, 1113 } 1114 1115 actual, err := r.Refresh(s, 42) 1116 if err != nil { 1117 t.Fatalf("err: %s", err) 1118 } 1119 1120 expected := &terraform.InstanceState{ 1121 ID: "bar", 1122 Attributes: map[string]string{ 1123 "id": "bar", 1124 "newfoo": "13", 1125 }, 1126 Meta: map[string]interface{}{ 1127 "schema_version": "2", 1128 }, 1129 } 1130 1131 if !reflect.DeepEqual(actual, expected) { 1132 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 1133 } 1134 } 1135 1136 func TestResourceRefresh_noMigrationNeeded(t *testing.T) { 1137 r := &Resource{ 1138 SchemaVersion: 2, 1139 Schema: map[string]*Schema{ 1140 "newfoo": &Schema{ 1141 Type: TypeInt, 1142 Optional: true, 1143 }, 1144 }, 1145 } 1146 1147 r.Read = func(d *ResourceData, m interface{}) error { 1148 return d.Set("newfoo", d.Get("newfoo").(int)+1) 1149 } 1150 1151 r.MigrateState = func( 1152 v int, 1153 s *terraform.InstanceState, 1154 meta interface{}) (*terraform.InstanceState, error) { 1155 t.Fatal("Migrate function shouldn't be called!") 1156 return nil, nil 1157 } 1158 1159 s := &terraform.InstanceState{ 1160 ID: "bar", 1161 Attributes: map[string]string{ 1162 "newfoo": "12", 1163 }, 1164 Meta: map[string]interface{}{ 1165 "schema_version": "2", 1166 }, 1167 } 1168 1169 actual, err := r.Refresh(s, nil) 1170 if err != nil { 1171 t.Fatalf("err: %s", err) 1172 } 1173 1174 expected := &terraform.InstanceState{ 1175 ID: "bar", 1176 Attributes: map[string]string{ 1177 "id": "bar", 1178 "newfoo": "13", 1179 }, 1180 Meta: map[string]interface{}{ 1181 "schema_version": "2", 1182 }, 1183 } 1184 1185 if !reflect.DeepEqual(actual, expected) { 1186 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 1187 } 1188 } 1189 1190 func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) { 1191 r := &Resource{ 1192 // Version 1 > Version 0 1193 SchemaVersion: 1, 1194 Schema: map[string]*Schema{ 1195 "newfoo": &Schema{ 1196 Type: TypeInt, 1197 Optional: true, 1198 }, 1199 }, 1200 } 1201 1202 r.Read = func(d *ResourceData, m interface{}) error { 1203 return d.Set("newfoo", d.Get("newfoo").(int)+1) 1204 } 1205 1206 r.MigrateState = func( 1207 v int, 1208 s *terraform.InstanceState, 1209 meta interface{}) (*terraform.InstanceState, error) { 1210 s.Attributes["newfoo"] = s.Attributes["oldfoo"] 1211 return s, nil 1212 } 1213 1214 s := &terraform.InstanceState{ 1215 ID: "bar", 1216 Attributes: map[string]string{ 1217 "oldfoo": "12", 1218 }, 1219 } 1220 1221 actual, err := r.Refresh(s, nil) 1222 if err != nil { 1223 t.Fatalf("err: %s", err) 1224 } 1225 1226 expected := &terraform.InstanceState{ 1227 ID: "bar", 1228 Attributes: map[string]string{ 1229 "id": "bar", 1230 "newfoo": "13", 1231 }, 1232 Meta: map[string]interface{}{ 1233 "schema_version": "1", 1234 }, 1235 } 1236 1237 if !reflect.DeepEqual(actual, expected) { 1238 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 1239 } 1240 } 1241 1242 func TestResourceRefresh_migrateStateErr(t *testing.T) { 1243 r := &Resource{ 1244 SchemaVersion: 2, 1245 Schema: map[string]*Schema{ 1246 "newfoo": &Schema{ 1247 Type: TypeInt, 1248 Optional: true, 1249 }, 1250 }, 1251 } 1252 1253 r.Read = func(d *ResourceData, m interface{}) error { 1254 t.Fatal("Read should never be called!") 1255 return nil 1256 } 1257 1258 r.MigrateState = func( 1259 v int, 1260 s *terraform.InstanceState, 1261 meta interface{}) (*terraform.InstanceState, error) { 1262 return s, fmt.Errorf("triggering an error") 1263 } 1264 1265 s := &terraform.InstanceState{ 1266 ID: "bar", 1267 Attributes: map[string]string{ 1268 "oldfoo": "12", 1269 }, 1270 } 1271 1272 _, err := r.Refresh(s, nil) 1273 if err == nil { 1274 t.Fatal("expected error, but got none!") 1275 } 1276 } 1277 1278 func TestResourceData(t *testing.T) { 1279 r := &Resource{ 1280 SchemaVersion: 2, 1281 Schema: map[string]*Schema{ 1282 "foo": &Schema{ 1283 Type: TypeInt, 1284 Optional: true, 1285 }, 1286 }, 1287 } 1288 1289 state := &terraform.InstanceState{ 1290 ID: "foo", 1291 Attributes: map[string]string{ 1292 "id": "foo", 1293 "foo": "42", 1294 }, 1295 } 1296 1297 data := r.Data(state) 1298 if data.Id() != "foo" { 1299 t.Fatalf("err: %s", data.Id()) 1300 } 1301 if v := data.Get("foo"); v != 42 { 1302 t.Fatalf("bad: %#v", v) 1303 } 1304 1305 // Set expectations 1306 state.Meta = map[string]interface{}{ 1307 "schema_version": "2", 1308 } 1309 1310 result := data.State() 1311 if !reflect.DeepEqual(result, state) { 1312 t.Fatalf("bad: %#v", result) 1313 } 1314 } 1315 1316 func TestResourceData_blank(t *testing.T) { 1317 r := &Resource{ 1318 SchemaVersion: 2, 1319 Schema: map[string]*Schema{ 1320 "foo": &Schema{ 1321 Type: TypeInt, 1322 Optional: true, 1323 }, 1324 }, 1325 } 1326 1327 data := r.Data(nil) 1328 if data.Id() != "" { 1329 t.Fatalf("err: %s", data.Id()) 1330 } 1331 if v := data.Get("foo"); v != 0 { 1332 t.Fatalf("bad: %#v", v) 1333 } 1334 } 1335 1336 func TestResourceData_timeouts(t *testing.T) { 1337 one := 1 * time.Second 1338 two := 2 * time.Second 1339 three := 3 * time.Second 1340 four := 4 * time.Second 1341 five := 5 * time.Second 1342 1343 timeouts := &ResourceTimeout{ 1344 Create: &one, 1345 Read: &two, 1346 Update: &three, 1347 Delete: &four, 1348 Default: &five, 1349 } 1350 1351 r := &Resource{ 1352 SchemaVersion: 2, 1353 Schema: map[string]*Schema{ 1354 "foo": &Schema{ 1355 Type: TypeInt, 1356 Optional: true, 1357 }, 1358 }, 1359 Timeouts: timeouts, 1360 } 1361 1362 data := r.Data(nil) 1363 if data.Id() != "" { 1364 t.Fatalf("err: %s", data.Id()) 1365 } 1366 1367 if !reflect.DeepEqual(timeouts, data.timeouts) { 1368 t.Fatalf("incorrect ResourceData timeouts: %#v\n", *data.timeouts) 1369 } 1370 }