github.com/ticketmaster/terraform@v0.10.0-beta2.0.20170711045249-a12daf5aba4f/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 8: { // Reserved name at root should be disallowed 711 &Resource{ 712 Create: func(d *ResourceData, meta interface{}) error { return nil }, 713 Read: func(d *ResourceData, meta interface{}) error { return nil }, 714 Update: func(d *ResourceData, meta interface{}) error { return nil }, 715 Delete: func(d *ResourceData, meta interface{}) error { return nil }, 716 Schema: map[string]*Schema{ 717 "count": { 718 Type: TypeInt, 719 Optional: true, 720 }, 721 }, 722 }, 723 true, 724 true, 725 }, 726 727 9: { // Reserved name at nested levels should be allowed 728 &Resource{ 729 Create: func(d *ResourceData, meta interface{}) error { return nil }, 730 Read: func(d *ResourceData, meta interface{}) error { return nil }, 731 Update: func(d *ResourceData, meta interface{}) error { return nil }, 732 Delete: func(d *ResourceData, meta interface{}) error { return nil }, 733 Schema: map[string]*Schema{ 734 "parent_list": &Schema{ 735 Type: TypeString, 736 Optional: true, 737 Elem: &Resource{ 738 Schema: map[string]*Schema{ 739 "provisioner": { 740 Type: TypeString, 741 Optional: true, 742 }, 743 }, 744 }, 745 }, 746 }, 747 }, 748 true, 749 false, 750 }, 751 752 10: { // Provider reserved name should be allowed in resource 753 &Resource{ 754 Create: func(d *ResourceData, meta interface{}) error { return nil }, 755 Read: func(d *ResourceData, meta interface{}) error { return nil }, 756 Update: func(d *ResourceData, meta interface{}) error { return nil }, 757 Delete: func(d *ResourceData, meta interface{}) error { return nil }, 758 Schema: map[string]*Schema{ 759 "alias": &Schema{ 760 Type: TypeString, 761 Optional: true, 762 }, 763 }, 764 }, 765 true, 766 false, 767 }, 768 } 769 770 for i, tc := range cases { 771 t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 772 sm := schemaMap{} 773 if tc.In != nil { 774 sm = schemaMap(tc.In.Schema) 775 } 776 777 err := tc.In.InternalValidate(sm, tc.Writable) 778 if err != nil != tc.Err { 779 t.Fatalf("%d: bad: %s", i, err) 780 } 781 }) 782 } 783 } 784 785 func TestResourceRefresh(t *testing.T) { 786 r := &Resource{ 787 SchemaVersion: 2, 788 Schema: map[string]*Schema{ 789 "foo": &Schema{ 790 Type: TypeInt, 791 Optional: true, 792 }, 793 }, 794 } 795 796 r.Read = func(d *ResourceData, m interface{}) error { 797 if m != 42 { 798 return fmt.Errorf("meta not passed") 799 } 800 801 return d.Set("foo", d.Get("foo").(int)+1) 802 } 803 804 s := &terraform.InstanceState{ 805 ID: "bar", 806 Attributes: map[string]string{ 807 "foo": "12", 808 }, 809 } 810 811 expected := &terraform.InstanceState{ 812 ID: "bar", 813 Attributes: map[string]string{ 814 "id": "bar", 815 "foo": "13", 816 }, 817 Meta: map[string]interface{}{ 818 "schema_version": "2", 819 }, 820 } 821 822 actual, err := r.Refresh(s, 42) 823 if err != nil { 824 t.Fatalf("err: %s", err) 825 } 826 827 if !reflect.DeepEqual(actual, expected) { 828 t.Fatalf("bad: %#v", actual) 829 } 830 } 831 832 func TestResourceRefresh_blankId(t *testing.T) { 833 r := &Resource{ 834 Schema: map[string]*Schema{ 835 "foo": &Schema{ 836 Type: TypeInt, 837 Optional: true, 838 }, 839 }, 840 } 841 842 r.Read = func(d *ResourceData, m interface{}) error { 843 d.SetId("foo") 844 return nil 845 } 846 847 s := &terraform.InstanceState{ 848 ID: "", 849 Attributes: map[string]string{}, 850 } 851 852 actual, err := r.Refresh(s, 42) 853 if err != nil { 854 t.Fatalf("err: %s", err) 855 } 856 if actual != nil { 857 t.Fatalf("bad: %#v", actual) 858 } 859 } 860 861 func TestResourceRefresh_delete(t *testing.T) { 862 r := &Resource{ 863 Schema: map[string]*Schema{ 864 "foo": &Schema{ 865 Type: TypeInt, 866 Optional: true, 867 }, 868 }, 869 } 870 871 r.Read = func(d *ResourceData, m interface{}) error { 872 d.SetId("") 873 return nil 874 } 875 876 s := &terraform.InstanceState{ 877 ID: "bar", 878 Attributes: map[string]string{ 879 "foo": "12", 880 }, 881 } 882 883 actual, err := r.Refresh(s, 42) 884 if err != nil { 885 t.Fatalf("err: %s", err) 886 } 887 888 if actual != nil { 889 t.Fatalf("bad: %#v", actual) 890 } 891 } 892 893 func TestResourceRefresh_existsError(t *testing.T) { 894 r := &Resource{ 895 Schema: map[string]*Schema{ 896 "foo": &Schema{ 897 Type: TypeInt, 898 Optional: true, 899 }, 900 }, 901 } 902 903 r.Exists = func(*ResourceData, interface{}) (bool, error) { 904 return false, fmt.Errorf("error") 905 } 906 907 r.Read = func(d *ResourceData, m interface{}) error { 908 panic("shouldn't be called") 909 } 910 911 s := &terraform.InstanceState{ 912 ID: "bar", 913 Attributes: map[string]string{ 914 "foo": "12", 915 }, 916 } 917 918 actual, err := r.Refresh(s, 42) 919 if err == nil { 920 t.Fatalf("should error") 921 } 922 if !reflect.DeepEqual(actual, s) { 923 t.Fatalf("bad: %#v", actual) 924 } 925 } 926 927 func TestResourceRefresh_noExists(t *testing.T) { 928 r := &Resource{ 929 Schema: map[string]*Schema{ 930 "foo": &Schema{ 931 Type: TypeInt, 932 Optional: true, 933 }, 934 }, 935 } 936 937 r.Exists = func(*ResourceData, interface{}) (bool, error) { 938 return false, nil 939 } 940 941 r.Read = func(d *ResourceData, m interface{}) error { 942 panic("shouldn't be called") 943 } 944 945 s := &terraform.InstanceState{ 946 ID: "bar", 947 Attributes: map[string]string{ 948 "foo": "12", 949 }, 950 } 951 952 actual, err := r.Refresh(s, 42) 953 if err != nil { 954 t.Fatalf("err: %s", err) 955 } 956 if actual != nil { 957 t.Fatalf("should have no state") 958 } 959 } 960 961 func TestResourceRefresh_needsMigration(t *testing.T) { 962 // Schema v2 it deals only in newfoo, which tracks foo as an int 963 r := &Resource{ 964 SchemaVersion: 2, 965 Schema: map[string]*Schema{ 966 "newfoo": &Schema{ 967 Type: TypeInt, 968 Optional: true, 969 }, 970 }, 971 } 972 973 r.Read = func(d *ResourceData, m interface{}) error { 974 return d.Set("newfoo", d.Get("newfoo").(int)+1) 975 } 976 977 r.MigrateState = func( 978 v int, 979 s *terraform.InstanceState, 980 meta interface{}) (*terraform.InstanceState, error) { 981 // Real state migration functions will probably switch on this value, 982 // but we'll just assert on it for now. 983 if v != 1 { 984 t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v) 985 } 986 987 if meta != 42 { 988 t.Fatal("Expected meta to be passed through to the migration function") 989 } 990 991 oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64) 992 if err != nil { 993 t.Fatalf("err: %#v", err) 994 } 995 s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10)) 996 delete(s.Attributes, "oldfoo") 997 998 return s, nil 999 } 1000 1001 // State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th 1002 // the scale of newfoo 1003 s := &terraform.InstanceState{ 1004 ID: "bar", 1005 Attributes: map[string]string{ 1006 "oldfoo": "1.2", 1007 }, 1008 Meta: map[string]interface{}{ 1009 "schema_version": "1", 1010 }, 1011 } 1012 1013 actual, err := r.Refresh(s, 42) 1014 if err != nil { 1015 t.Fatalf("err: %s", err) 1016 } 1017 1018 expected := &terraform.InstanceState{ 1019 ID: "bar", 1020 Attributes: map[string]string{ 1021 "id": "bar", 1022 "newfoo": "13", 1023 }, 1024 Meta: map[string]interface{}{ 1025 "schema_version": "2", 1026 }, 1027 } 1028 1029 if !reflect.DeepEqual(actual, expected) { 1030 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 1031 } 1032 } 1033 1034 func TestResourceRefresh_noMigrationNeeded(t *testing.T) { 1035 r := &Resource{ 1036 SchemaVersion: 2, 1037 Schema: map[string]*Schema{ 1038 "newfoo": &Schema{ 1039 Type: TypeInt, 1040 Optional: true, 1041 }, 1042 }, 1043 } 1044 1045 r.Read = func(d *ResourceData, m interface{}) error { 1046 return d.Set("newfoo", d.Get("newfoo").(int)+1) 1047 } 1048 1049 r.MigrateState = func( 1050 v int, 1051 s *terraform.InstanceState, 1052 meta interface{}) (*terraform.InstanceState, error) { 1053 t.Fatal("Migrate function shouldn't be called!") 1054 return nil, nil 1055 } 1056 1057 s := &terraform.InstanceState{ 1058 ID: "bar", 1059 Attributes: map[string]string{ 1060 "newfoo": "12", 1061 }, 1062 Meta: map[string]interface{}{ 1063 "schema_version": "2", 1064 }, 1065 } 1066 1067 actual, err := r.Refresh(s, nil) 1068 if err != nil { 1069 t.Fatalf("err: %s", err) 1070 } 1071 1072 expected := &terraform.InstanceState{ 1073 ID: "bar", 1074 Attributes: map[string]string{ 1075 "id": "bar", 1076 "newfoo": "13", 1077 }, 1078 Meta: map[string]interface{}{ 1079 "schema_version": "2", 1080 }, 1081 } 1082 1083 if !reflect.DeepEqual(actual, expected) { 1084 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 1085 } 1086 } 1087 1088 func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) { 1089 r := &Resource{ 1090 // Version 1 > Version 0 1091 SchemaVersion: 1, 1092 Schema: map[string]*Schema{ 1093 "newfoo": &Schema{ 1094 Type: TypeInt, 1095 Optional: true, 1096 }, 1097 }, 1098 } 1099 1100 r.Read = func(d *ResourceData, m interface{}) error { 1101 return d.Set("newfoo", d.Get("newfoo").(int)+1) 1102 } 1103 1104 r.MigrateState = func( 1105 v int, 1106 s *terraform.InstanceState, 1107 meta interface{}) (*terraform.InstanceState, error) { 1108 s.Attributes["newfoo"] = s.Attributes["oldfoo"] 1109 return s, nil 1110 } 1111 1112 s := &terraform.InstanceState{ 1113 ID: "bar", 1114 Attributes: map[string]string{ 1115 "oldfoo": "12", 1116 }, 1117 } 1118 1119 actual, err := r.Refresh(s, nil) 1120 if err != nil { 1121 t.Fatalf("err: %s", err) 1122 } 1123 1124 expected := &terraform.InstanceState{ 1125 ID: "bar", 1126 Attributes: map[string]string{ 1127 "id": "bar", 1128 "newfoo": "13", 1129 }, 1130 Meta: map[string]interface{}{ 1131 "schema_version": "1", 1132 }, 1133 } 1134 1135 if !reflect.DeepEqual(actual, expected) { 1136 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 1137 } 1138 } 1139 1140 func TestResourceRefresh_migrateStateErr(t *testing.T) { 1141 r := &Resource{ 1142 SchemaVersion: 2, 1143 Schema: map[string]*Schema{ 1144 "newfoo": &Schema{ 1145 Type: TypeInt, 1146 Optional: true, 1147 }, 1148 }, 1149 } 1150 1151 r.Read = func(d *ResourceData, m interface{}) error { 1152 t.Fatal("Read should never be called!") 1153 return nil 1154 } 1155 1156 r.MigrateState = func( 1157 v int, 1158 s *terraform.InstanceState, 1159 meta interface{}) (*terraform.InstanceState, error) { 1160 return s, fmt.Errorf("triggering an error") 1161 } 1162 1163 s := &terraform.InstanceState{ 1164 ID: "bar", 1165 Attributes: map[string]string{ 1166 "oldfoo": "12", 1167 }, 1168 } 1169 1170 _, err := r.Refresh(s, nil) 1171 if err == nil { 1172 t.Fatal("expected error, but got none!") 1173 } 1174 } 1175 1176 func TestResourceData(t *testing.T) { 1177 r := &Resource{ 1178 SchemaVersion: 2, 1179 Schema: map[string]*Schema{ 1180 "foo": &Schema{ 1181 Type: TypeInt, 1182 Optional: true, 1183 }, 1184 }, 1185 } 1186 1187 state := &terraform.InstanceState{ 1188 ID: "foo", 1189 Attributes: map[string]string{ 1190 "id": "foo", 1191 "foo": "42", 1192 }, 1193 } 1194 1195 data := r.Data(state) 1196 if data.Id() != "foo" { 1197 t.Fatalf("err: %s", data.Id()) 1198 } 1199 if v := data.Get("foo"); v != 42 { 1200 t.Fatalf("bad: %#v", v) 1201 } 1202 1203 // Set expectations 1204 state.Meta = map[string]interface{}{ 1205 "schema_version": "2", 1206 } 1207 1208 result := data.State() 1209 if !reflect.DeepEqual(result, state) { 1210 t.Fatalf("bad: %#v", result) 1211 } 1212 } 1213 1214 func TestResourceData_blank(t *testing.T) { 1215 r := &Resource{ 1216 SchemaVersion: 2, 1217 Schema: map[string]*Schema{ 1218 "foo": &Schema{ 1219 Type: TypeInt, 1220 Optional: true, 1221 }, 1222 }, 1223 } 1224 1225 data := r.Data(nil) 1226 if data.Id() != "" { 1227 t.Fatalf("err: %s", data.Id()) 1228 } 1229 if v := data.Get("foo"); v != 0 { 1230 t.Fatalf("bad: %#v", v) 1231 } 1232 }