github.com/gerbenjacobs/terraform@v0.9.5-0.20170630130047-e6ddd62583d8/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) 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 TestResourceApply_destroy(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 called := false 268 r.Delete = func(d *ResourceData, m interface{}) error { 269 called = true 270 return nil 271 } 272 273 s := &terraform.InstanceState{ 274 ID: "bar", 275 } 276 277 d := &terraform.InstanceDiff{ 278 Destroy: true, 279 } 280 281 actual, err := r.Apply(s, d, nil) 282 if err != nil { 283 t.Fatalf("err: %s", err) 284 } 285 286 if !called { 287 t.Fatal("delete not called") 288 } 289 290 if actual != nil { 291 t.Fatalf("bad: %#v", actual) 292 } 293 } 294 295 func TestResourceApply_destroyCreate(t *testing.T) { 296 r := &Resource{ 297 Schema: map[string]*Schema{ 298 "foo": &Schema{ 299 Type: TypeInt, 300 Optional: true, 301 }, 302 303 "tags": &Schema{ 304 Type: TypeMap, 305 Optional: true, 306 Computed: true, 307 }, 308 }, 309 } 310 311 change := false 312 r.Create = func(d *ResourceData, m interface{}) error { 313 change = d.HasChange("tags") 314 d.SetId("foo") 315 return nil 316 } 317 r.Delete = func(d *ResourceData, m interface{}) error { 318 return nil 319 } 320 321 var s *terraform.InstanceState = &terraform.InstanceState{ 322 ID: "bar", 323 Attributes: map[string]string{ 324 "foo": "bar", 325 "tags.Name": "foo", 326 }, 327 } 328 329 d := &terraform.InstanceDiff{ 330 Attributes: map[string]*terraform.ResourceAttrDiff{ 331 "foo": &terraform.ResourceAttrDiff{ 332 New: "42", 333 RequiresNew: true, 334 }, 335 "tags.Name": &terraform.ResourceAttrDiff{ 336 Old: "foo", 337 New: "foo", 338 RequiresNew: true, 339 }, 340 }, 341 } 342 343 actual, err := r.Apply(s, d, nil) 344 if err != nil { 345 t.Fatalf("err: %s", err) 346 } 347 348 if !change { 349 t.Fatal("should have change") 350 } 351 352 expected := &terraform.InstanceState{ 353 ID: "foo", 354 Attributes: map[string]string{ 355 "id": "foo", 356 "foo": "42", 357 "tags.%": "1", 358 "tags.Name": "foo", 359 }, 360 } 361 362 if !reflect.DeepEqual(actual, expected) { 363 t.Fatalf("bad: %#v", actual) 364 } 365 } 366 367 func TestResourceApply_destroyPartial(t *testing.T) { 368 r := &Resource{ 369 Schema: map[string]*Schema{ 370 "foo": &Schema{ 371 Type: TypeInt, 372 Optional: true, 373 }, 374 }, 375 SchemaVersion: 3, 376 } 377 378 r.Delete = func(d *ResourceData, m interface{}) error { 379 d.Set("foo", 42) 380 return fmt.Errorf("some error") 381 } 382 383 s := &terraform.InstanceState{ 384 ID: "bar", 385 Attributes: map[string]string{ 386 "foo": "12", 387 }, 388 } 389 390 d := &terraform.InstanceDiff{ 391 Destroy: true, 392 } 393 394 actual, err := r.Apply(s, d, nil) 395 if err == nil { 396 t.Fatal("should error") 397 } 398 399 expected := &terraform.InstanceState{ 400 ID: "bar", 401 Attributes: map[string]string{ 402 "id": "bar", 403 "foo": "42", 404 }, 405 Meta: map[string]interface{}{ 406 "schema_version": "3", 407 }, 408 } 409 410 if !reflect.DeepEqual(actual, expected) { 411 t.Fatalf("expected:\n%#v\n\ngot:\n%#v", expected, actual) 412 } 413 } 414 415 func TestResourceApply_update(t *testing.T) { 416 r := &Resource{ 417 Schema: map[string]*Schema{ 418 "foo": &Schema{ 419 Type: TypeInt, 420 Optional: true, 421 }, 422 }, 423 } 424 425 r.Update = func(d *ResourceData, m interface{}) error { 426 d.Set("foo", 42) 427 return nil 428 } 429 430 s := &terraform.InstanceState{ 431 ID: "foo", 432 Attributes: map[string]string{ 433 "foo": "12", 434 }, 435 } 436 437 d := &terraform.InstanceDiff{ 438 Attributes: map[string]*terraform.ResourceAttrDiff{ 439 "foo": &terraform.ResourceAttrDiff{ 440 New: "13", 441 }, 442 }, 443 } 444 445 actual, err := r.Apply(s, d, nil) 446 if err != nil { 447 t.Fatalf("err: %s", err) 448 } 449 450 expected := &terraform.InstanceState{ 451 ID: "foo", 452 Attributes: map[string]string{ 453 "id": "foo", 454 "foo": "42", 455 }, 456 } 457 458 if !reflect.DeepEqual(actual, expected) { 459 t.Fatalf("bad: %#v", actual) 460 } 461 } 462 463 func TestResourceApply_updateNoCallback(t *testing.T) { 464 r := &Resource{ 465 Schema: map[string]*Schema{ 466 "foo": &Schema{ 467 Type: TypeInt, 468 Optional: true, 469 }, 470 }, 471 } 472 473 r.Update = nil 474 475 s := &terraform.InstanceState{ 476 ID: "foo", 477 Attributes: map[string]string{ 478 "foo": "12", 479 }, 480 } 481 482 d := &terraform.InstanceDiff{ 483 Attributes: map[string]*terraform.ResourceAttrDiff{ 484 "foo": &terraform.ResourceAttrDiff{ 485 New: "13", 486 }, 487 }, 488 } 489 490 actual, err := r.Apply(s, d, nil) 491 if err == nil { 492 t.Fatal("should error") 493 } 494 495 expected := &terraform.InstanceState{ 496 ID: "foo", 497 Attributes: map[string]string{ 498 "foo": "12", 499 }, 500 } 501 502 if !reflect.DeepEqual(actual, expected) { 503 t.Fatalf("bad: %#v", actual) 504 } 505 } 506 507 func TestResourceApply_isNewResource(t *testing.T) { 508 r := &Resource{ 509 Schema: map[string]*Schema{ 510 "foo": &Schema{ 511 Type: TypeString, 512 Optional: true, 513 }, 514 }, 515 } 516 517 updateFunc := func(d *ResourceData, m interface{}) error { 518 d.Set("foo", "updated") 519 if d.IsNewResource() { 520 d.Set("foo", "new-resource") 521 } 522 return nil 523 } 524 r.Create = func(d *ResourceData, m interface{}) error { 525 d.SetId("foo") 526 d.Set("foo", "created") 527 return updateFunc(d, m) 528 } 529 r.Update = updateFunc 530 531 d := &terraform.InstanceDiff{ 532 Attributes: map[string]*terraform.ResourceAttrDiff{ 533 "foo": &terraform.ResourceAttrDiff{ 534 New: "bla-blah", 535 }, 536 }, 537 } 538 539 // positive test 540 var s *terraform.InstanceState = nil 541 542 actual, err := r.Apply(s, d, nil) 543 if err != nil { 544 t.Fatalf("err: %s", err) 545 } 546 547 expected := &terraform.InstanceState{ 548 ID: "foo", 549 Attributes: map[string]string{ 550 "id": "foo", 551 "foo": "new-resource", 552 }, 553 } 554 555 if !reflect.DeepEqual(actual, expected) { 556 t.Fatalf("actual: %#v\nexpected: %#v", 557 actual, expected) 558 } 559 560 // negative test 561 s = &terraform.InstanceState{ 562 ID: "foo", 563 Attributes: map[string]string{ 564 "id": "foo", 565 "foo": "new-resource", 566 }, 567 } 568 569 actual, err = r.Apply(s, d, nil) 570 if err != nil { 571 t.Fatalf("err: %s", err) 572 } 573 574 expected = &terraform.InstanceState{ 575 ID: "foo", 576 Attributes: map[string]string{ 577 "id": "foo", 578 "foo": "updated", 579 }, 580 } 581 582 if !reflect.DeepEqual(actual, expected) { 583 t.Fatalf("actual: %#v\nexpected: %#v", 584 actual, expected) 585 } 586 } 587 588 func TestResourceInternalValidate(t *testing.T) { 589 cases := []struct { 590 In *Resource 591 Writable bool 592 Err bool 593 }{ 594 { 595 nil, 596 true, 597 true, 598 }, 599 600 // No optional and no required 601 { 602 &Resource{ 603 Schema: map[string]*Schema{ 604 "foo": &Schema{ 605 Type: TypeInt, 606 Optional: true, 607 Required: true, 608 }, 609 }, 610 }, 611 true, 612 true, 613 }, 614 615 // Update undefined for non-ForceNew field 616 { 617 &Resource{ 618 Create: func(d *ResourceData, meta interface{}) error { return nil }, 619 Schema: map[string]*Schema{ 620 "boo": &Schema{ 621 Type: TypeInt, 622 Optional: true, 623 }, 624 }, 625 }, 626 true, 627 true, 628 }, 629 630 // Update defined for ForceNew field 631 { 632 &Resource{ 633 Create: func(d *ResourceData, meta interface{}) error { return nil }, 634 Update: func(d *ResourceData, meta interface{}) error { return nil }, 635 Schema: map[string]*Schema{ 636 "goo": &Schema{ 637 Type: TypeInt, 638 Optional: true, 639 ForceNew: true, 640 }, 641 }, 642 }, 643 true, 644 true, 645 }, 646 647 // non-writable doesn't need Update, Create or Delete 648 { 649 &Resource{ 650 Schema: map[string]*Schema{ 651 "goo": &Schema{ 652 Type: TypeInt, 653 Optional: true, 654 }, 655 }, 656 }, 657 false, 658 false, 659 }, 660 661 // non-writable *must not* have Create 662 { 663 &Resource{ 664 Create: func(d *ResourceData, meta interface{}) error { return nil }, 665 Schema: map[string]*Schema{ 666 "goo": &Schema{ 667 Type: TypeInt, 668 Optional: true, 669 }, 670 }, 671 }, 672 false, 673 true, 674 }, 675 676 // writable must have Read 677 { 678 &Resource{ 679 Create: func(d *ResourceData, meta interface{}) error { return nil }, 680 Update: func(d *ResourceData, meta interface{}) error { return nil }, 681 Delete: func(d *ResourceData, meta interface{}) error { return nil }, 682 Schema: map[string]*Schema{ 683 "goo": &Schema{ 684 Type: TypeInt, 685 Optional: true, 686 }, 687 }, 688 }, 689 true, 690 true, 691 }, 692 693 // writable must have Delete 694 { 695 &Resource{ 696 Create: func(d *ResourceData, meta interface{}) error { return nil }, 697 Read: func(d *ResourceData, meta interface{}) error { return nil }, 698 Update: func(d *ResourceData, meta interface{}) error { return nil }, 699 Schema: map[string]*Schema{ 700 "goo": &Schema{ 701 Type: TypeInt, 702 Optional: true, 703 }, 704 }, 705 }, 706 true, 707 true, 708 }, 709 } 710 711 for i, tc := range cases { 712 t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 713 err := tc.In.InternalValidate(schemaMap{}, tc.Writable) 714 if err != nil != tc.Err { 715 t.Fatalf("%d: bad: %s", i, err) 716 } 717 }) 718 } 719 } 720 721 func TestResourceRefresh(t *testing.T) { 722 r := &Resource{ 723 SchemaVersion: 2, 724 Schema: map[string]*Schema{ 725 "foo": &Schema{ 726 Type: TypeInt, 727 Optional: true, 728 }, 729 }, 730 } 731 732 r.Read = func(d *ResourceData, m interface{}) error { 733 if m != 42 { 734 return fmt.Errorf("meta not passed") 735 } 736 737 return d.Set("foo", d.Get("foo").(int)+1) 738 } 739 740 s := &terraform.InstanceState{ 741 ID: "bar", 742 Attributes: map[string]string{ 743 "foo": "12", 744 }, 745 } 746 747 expected := &terraform.InstanceState{ 748 ID: "bar", 749 Attributes: map[string]string{ 750 "id": "bar", 751 "foo": "13", 752 }, 753 Meta: map[string]interface{}{ 754 "schema_version": "2", 755 }, 756 } 757 758 actual, err := r.Refresh(s, 42) 759 if err != nil { 760 t.Fatalf("err: %s", err) 761 } 762 763 if !reflect.DeepEqual(actual, expected) { 764 t.Fatalf("bad: %#v", actual) 765 } 766 } 767 768 func TestResourceRefresh_blankId(t *testing.T) { 769 r := &Resource{ 770 Schema: map[string]*Schema{ 771 "foo": &Schema{ 772 Type: TypeInt, 773 Optional: true, 774 }, 775 }, 776 } 777 778 r.Read = func(d *ResourceData, m interface{}) error { 779 d.SetId("foo") 780 return nil 781 } 782 783 s := &terraform.InstanceState{ 784 ID: "", 785 Attributes: map[string]string{}, 786 } 787 788 actual, err := r.Refresh(s, 42) 789 if err != nil { 790 t.Fatalf("err: %s", err) 791 } 792 if actual != nil { 793 t.Fatalf("bad: %#v", actual) 794 } 795 } 796 797 func TestResourceRefresh_delete(t *testing.T) { 798 r := &Resource{ 799 Schema: map[string]*Schema{ 800 "foo": &Schema{ 801 Type: TypeInt, 802 Optional: true, 803 }, 804 }, 805 } 806 807 r.Read = func(d *ResourceData, m interface{}) error { 808 d.SetId("") 809 return nil 810 } 811 812 s := &terraform.InstanceState{ 813 ID: "bar", 814 Attributes: map[string]string{ 815 "foo": "12", 816 }, 817 } 818 819 actual, err := r.Refresh(s, 42) 820 if err != nil { 821 t.Fatalf("err: %s", err) 822 } 823 824 if actual != nil { 825 t.Fatalf("bad: %#v", actual) 826 } 827 } 828 829 func TestResourceRefresh_existsError(t *testing.T) { 830 r := &Resource{ 831 Schema: map[string]*Schema{ 832 "foo": &Schema{ 833 Type: TypeInt, 834 Optional: true, 835 }, 836 }, 837 } 838 839 r.Exists = func(*ResourceData, interface{}) (bool, error) { 840 return false, fmt.Errorf("error") 841 } 842 843 r.Read = func(d *ResourceData, m interface{}) error { 844 panic("shouldn't be called") 845 } 846 847 s := &terraform.InstanceState{ 848 ID: "bar", 849 Attributes: map[string]string{ 850 "foo": "12", 851 }, 852 } 853 854 actual, err := r.Refresh(s, 42) 855 if err == nil { 856 t.Fatalf("should error") 857 } 858 if !reflect.DeepEqual(actual, s) { 859 t.Fatalf("bad: %#v", actual) 860 } 861 } 862 863 func TestResourceRefresh_noExists(t *testing.T) { 864 r := &Resource{ 865 Schema: map[string]*Schema{ 866 "foo": &Schema{ 867 Type: TypeInt, 868 Optional: true, 869 }, 870 }, 871 } 872 873 r.Exists = func(*ResourceData, interface{}) (bool, error) { 874 return false, nil 875 } 876 877 r.Read = func(d *ResourceData, m interface{}) error { 878 panic("shouldn't be called") 879 } 880 881 s := &terraform.InstanceState{ 882 ID: "bar", 883 Attributes: map[string]string{ 884 "foo": "12", 885 }, 886 } 887 888 actual, err := r.Refresh(s, 42) 889 if err != nil { 890 t.Fatalf("err: %s", err) 891 } 892 if actual != nil { 893 t.Fatalf("should have no state") 894 } 895 } 896 897 func TestResourceRefresh_needsMigration(t *testing.T) { 898 // Schema v2 it deals only in newfoo, which tracks foo as an int 899 r := &Resource{ 900 SchemaVersion: 2, 901 Schema: map[string]*Schema{ 902 "newfoo": &Schema{ 903 Type: TypeInt, 904 Optional: true, 905 }, 906 }, 907 } 908 909 r.Read = func(d *ResourceData, m interface{}) error { 910 return d.Set("newfoo", d.Get("newfoo").(int)+1) 911 } 912 913 r.MigrateState = func( 914 v int, 915 s *terraform.InstanceState, 916 meta interface{}) (*terraform.InstanceState, error) { 917 // Real state migration functions will probably switch on this value, 918 // but we'll just assert on it for now. 919 if v != 1 { 920 t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v) 921 } 922 923 if meta != 42 { 924 t.Fatal("Expected meta to be passed through to the migration function") 925 } 926 927 oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64) 928 if err != nil { 929 t.Fatalf("err: %#v", err) 930 } 931 s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10)) 932 delete(s.Attributes, "oldfoo") 933 934 return s, nil 935 } 936 937 // State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th 938 // the scale of newfoo 939 s := &terraform.InstanceState{ 940 ID: "bar", 941 Attributes: map[string]string{ 942 "oldfoo": "1.2", 943 }, 944 Meta: map[string]interface{}{ 945 "schema_version": "1", 946 }, 947 } 948 949 actual, err := r.Refresh(s, 42) 950 if err != nil { 951 t.Fatalf("err: %s", err) 952 } 953 954 expected := &terraform.InstanceState{ 955 ID: "bar", 956 Attributes: map[string]string{ 957 "id": "bar", 958 "newfoo": "13", 959 }, 960 Meta: map[string]interface{}{ 961 "schema_version": "2", 962 }, 963 } 964 965 if !reflect.DeepEqual(actual, expected) { 966 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 967 } 968 } 969 970 func TestResourceRefresh_noMigrationNeeded(t *testing.T) { 971 r := &Resource{ 972 SchemaVersion: 2, 973 Schema: map[string]*Schema{ 974 "newfoo": &Schema{ 975 Type: TypeInt, 976 Optional: true, 977 }, 978 }, 979 } 980 981 r.Read = func(d *ResourceData, m interface{}) error { 982 return d.Set("newfoo", d.Get("newfoo").(int)+1) 983 } 984 985 r.MigrateState = func( 986 v int, 987 s *terraform.InstanceState, 988 meta interface{}) (*terraform.InstanceState, error) { 989 t.Fatal("Migrate function shouldn't be called!") 990 return nil, nil 991 } 992 993 s := &terraform.InstanceState{ 994 ID: "bar", 995 Attributes: map[string]string{ 996 "newfoo": "12", 997 }, 998 Meta: map[string]interface{}{ 999 "schema_version": "2", 1000 }, 1001 } 1002 1003 actual, err := r.Refresh(s, nil) 1004 if err != nil { 1005 t.Fatalf("err: %s", err) 1006 } 1007 1008 expected := &terraform.InstanceState{ 1009 ID: "bar", 1010 Attributes: map[string]string{ 1011 "id": "bar", 1012 "newfoo": "13", 1013 }, 1014 Meta: map[string]interface{}{ 1015 "schema_version": "2", 1016 }, 1017 } 1018 1019 if !reflect.DeepEqual(actual, expected) { 1020 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 1021 } 1022 } 1023 1024 func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) { 1025 r := &Resource{ 1026 // Version 1 > Version 0 1027 SchemaVersion: 1, 1028 Schema: map[string]*Schema{ 1029 "newfoo": &Schema{ 1030 Type: TypeInt, 1031 Optional: true, 1032 }, 1033 }, 1034 } 1035 1036 r.Read = func(d *ResourceData, m interface{}) error { 1037 return d.Set("newfoo", d.Get("newfoo").(int)+1) 1038 } 1039 1040 r.MigrateState = func( 1041 v int, 1042 s *terraform.InstanceState, 1043 meta interface{}) (*terraform.InstanceState, error) { 1044 s.Attributes["newfoo"] = s.Attributes["oldfoo"] 1045 return s, nil 1046 } 1047 1048 s := &terraform.InstanceState{ 1049 ID: "bar", 1050 Attributes: map[string]string{ 1051 "oldfoo": "12", 1052 }, 1053 } 1054 1055 actual, err := r.Refresh(s, nil) 1056 if err != nil { 1057 t.Fatalf("err: %s", err) 1058 } 1059 1060 expected := &terraform.InstanceState{ 1061 ID: "bar", 1062 Attributes: map[string]string{ 1063 "id": "bar", 1064 "newfoo": "13", 1065 }, 1066 Meta: map[string]interface{}{ 1067 "schema_version": "1", 1068 }, 1069 } 1070 1071 if !reflect.DeepEqual(actual, expected) { 1072 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 1073 } 1074 } 1075 1076 func TestResourceRefresh_migrateStateErr(t *testing.T) { 1077 r := &Resource{ 1078 SchemaVersion: 2, 1079 Schema: map[string]*Schema{ 1080 "newfoo": &Schema{ 1081 Type: TypeInt, 1082 Optional: true, 1083 }, 1084 }, 1085 } 1086 1087 r.Read = func(d *ResourceData, m interface{}) error { 1088 t.Fatal("Read should never be called!") 1089 return nil 1090 } 1091 1092 r.MigrateState = func( 1093 v int, 1094 s *terraform.InstanceState, 1095 meta interface{}) (*terraform.InstanceState, error) { 1096 return s, fmt.Errorf("triggering an error") 1097 } 1098 1099 s := &terraform.InstanceState{ 1100 ID: "bar", 1101 Attributes: map[string]string{ 1102 "oldfoo": "12", 1103 }, 1104 } 1105 1106 _, err := r.Refresh(s, nil) 1107 if err == nil { 1108 t.Fatal("expected error, but got none!") 1109 } 1110 } 1111 1112 func TestResourceData(t *testing.T) { 1113 r := &Resource{ 1114 SchemaVersion: 2, 1115 Schema: map[string]*Schema{ 1116 "foo": &Schema{ 1117 Type: TypeInt, 1118 Optional: true, 1119 }, 1120 }, 1121 } 1122 1123 state := &terraform.InstanceState{ 1124 ID: "foo", 1125 Attributes: map[string]string{ 1126 "id": "foo", 1127 "foo": "42", 1128 }, 1129 } 1130 1131 data := r.Data(state) 1132 if data.Id() != "foo" { 1133 t.Fatalf("err: %s", data.Id()) 1134 } 1135 if v := data.Get("foo"); v != 42 { 1136 t.Fatalf("bad: %#v", v) 1137 } 1138 1139 // Set expectations 1140 state.Meta = map[string]interface{}{ 1141 "schema_version": "2", 1142 } 1143 1144 result := data.State() 1145 if !reflect.DeepEqual(result, state) { 1146 t.Fatalf("bad: %#v", result) 1147 } 1148 } 1149 1150 func TestResourceData_blank(t *testing.T) { 1151 r := &Resource{ 1152 SchemaVersion: 2, 1153 Schema: map[string]*Schema{ 1154 "foo": &Schema{ 1155 Type: TypeInt, 1156 Optional: true, 1157 }, 1158 }, 1159 } 1160 1161 data := r.Data(nil) 1162 if data.Id() != "" { 1163 t.Fatalf("err: %s", data.Id()) 1164 } 1165 if v := data.Get("foo"); v != 0 { 1166 t.Fatalf("bad: %#v", v) 1167 } 1168 }