github.com/hooklift/terraform@v0.11.0-beta1.0.20171117000744-6786c1361ffe/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 } 854 855 for i, tc := range cases { 856 t.Run(fmt.Sprintf("#%d", i), func(t *testing.T) { 857 sm := schemaMap{} 858 if tc.In != nil { 859 sm = schemaMap(tc.In.Schema) 860 } 861 862 err := tc.In.InternalValidate(sm, tc.Writable) 863 if err != nil && !tc.Err { 864 t.Fatalf("%d: expected validation to pass: %s", i, err) 865 } 866 if err == nil && tc.Err { 867 t.Fatalf("%d: expected validation to fail", i) 868 } 869 }) 870 } 871 } 872 873 func TestResourceRefresh(t *testing.T) { 874 r := &Resource{ 875 SchemaVersion: 2, 876 Schema: map[string]*Schema{ 877 "foo": &Schema{ 878 Type: TypeInt, 879 Optional: true, 880 }, 881 }, 882 } 883 884 r.Read = func(d *ResourceData, m interface{}) error { 885 if m != 42 { 886 return fmt.Errorf("meta not passed") 887 } 888 889 return d.Set("foo", d.Get("foo").(int)+1) 890 } 891 892 s := &terraform.InstanceState{ 893 ID: "bar", 894 Attributes: map[string]string{ 895 "foo": "12", 896 }, 897 } 898 899 expected := &terraform.InstanceState{ 900 ID: "bar", 901 Attributes: map[string]string{ 902 "id": "bar", 903 "foo": "13", 904 }, 905 Meta: map[string]interface{}{ 906 "schema_version": "2", 907 }, 908 } 909 910 actual, err := r.Refresh(s, 42) 911 if err != nil { 912 t.Fatalf("err: %s", err) 913 } 914 915 if !reflect.DeepEqual(actual, expected) { 916 t.Fatalf("bad: %#v", actual) 917 } 918 } 919 920 func TestResourceRefresh_blankId(t *testing.T) { 921 r := &Resource{ 922 Schema: map[string]*Schema{ 923 "foo": &Schema{ 924 Type: TypeInt, 925 Optional: true, 926 }, 927 }, 928 } 929 930 r.Read = func(d *ResourceData, m interface{}) error { 931 d.SetId("foo") 932 return nil 933 } 934 935 s := &terraform.InstanceState{ 936 ID: "", 937 Attributes: map[string]string{}, 938 } 939 940 actual, err := r.Refresh(s, 42) 941 if err != nil { 942 t.Fatalf("err: %s", err) 943 } 944 if actual != nil { 945 t.Fatalf("bad: %#v", actual) 946 } 947 } 948 949 func TestResourceRefresh_delete(t *testing.T) { 950 r := &Resource{ 951 Schema: map[string]*Schema{ 952 "foo": &Schema{ 953 Type: TypeInt, 954 Optional: true, 955 }, 956 }, 957 } 958 959 r.Read = func(d *ResourceData, m interface{}) error { 960 d.SetId("") 961 return nil 962 } 963 964 s := &terraform.InstanceState{ 965 ID: "bar", 966 Attributes: map[string]string{ 967 "foo": "12", 968 }, 969 } 970 971 actual, err := r.Refresh(s, 42) 972 if err != nil { 973 t.Fatalf("err: %s", err) 974 } 975 976 if actual != nil { 977 t.Fatalf("bad: %#v", actual) 978 } 979 } 980 981 func TestResourceRefresh_existsError(t *testing.T) { 982 r := &Resource{ 983 Schema: map[string]*Schema{ 984 "foo": &Schema{ 985 Type: TypeInt, 986 Optional: true, 987 }, 988 }, 989 } 990 991 r.Exists = func(*ResourceData, interface{}) (bool, error) { 992 return false, fmt.Errorf("error") 993 } 994 995 r.Read = func(d *ResourceData, m interface{}) error { 996 panic("shouldn't be called") 997 } 998 999 s := &terraform.InstanceState{ 1000 ID: "bar", 1001 Attributes: map[string]string{ 1002 "foo": "12", 1003 }, 1004 } 1005 1006 actual, err := r.Refresh(s, 42) 1007 if err == nil { 1008 t.Fatalf("should error") 1009 } 1010 if !reflect.DeepEqual(actual, s) { 1011 t.Fatalf("bad: %#v", actual) 1012 } 1013 } 1014 1015 func TestResourceRefresh_noExists(t *testing.T) { 1016 r := &Resource{ 1017 Schema: map[string]*Schema{ 1018 "foo": &Schema{ 1019 Type: TypeInt, 1020 Optional: true, 1021 }, 1022 }, 1023 } 1024 1025 r.Exists = func(*ResourceData, interface{}) (bool, error) { 1026 return false, nil 1027 } 1028 1029 r.Read = func(d *ResourceData, m interface{}) error { 1030 panic("shouldn't be called") 1031 } 1032 1033 s := &terraform.InstanceState{ 1034 ID: "bar", 1035 Attributes: map[string]string{ 1036 "foo": "12", 1037 }, 1038 } 1039 1040 actual, err := r.Refresh(s, 42) 1041 if err != nil { 1042 t.Fatalf("err: %s", err) 1043 } 1044 if actual != nil { 1045 t.Fatalf("should have no state") 1046 } 1047 } 1048 1049 func TestResourceRefresh_needsMigration(t *testing.T) { 1050 // Schema v2 it deals only in newfoo, which tracks foo as an int 1051 r := &Resource{ 1052 SchemaVersion: 2, 1053 Schema: map[string]*Schema{ 1054 "newfoo": &Schema{ 1055 Type: TypeInt, 1056 Optional: true, 1057 }, 1058 }, 1059 } 1060 1061 r.Read = func(d *ResourceData, m interface{}) error { 1062 return d.Set("newfoo", d.Get("newfoo").(int)+1) 1063 } 1064 1065 r.MigrateState = func( 1066 v int, 1067 s *terraform.InstanceState, 1068 meta interface{}) (*terraform.InstanceState, error) { 1069 // Real state migration functions will probably switch on this value, 1070 // but we'll just assert on it for now. 1071 if v != 1 { 1072 t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v) 1073 } 1074 1075 if meta != 42 { 1076 t.Fatal("Expected meta to be passed through to the migration function") 1077 } 1078 1079 oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64) 1080 if err != nil { 1081 t.Fatalf("err: %#v", err) 1082 } 1083 s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10)) 1084 delete(s.Attributes, "oldfoo") 1085 1086 return s, nil 1087 } 1088 1089 // State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th 1090 // the scale of newfoo 1091 s := &terraform.InstanceState{ 1092 ID: "bar", 1093 Attributes: map[string]string{ 1094 "oldfoo": "1.2", 1095 }, 1096 Meta: map[string]interface{}{ 1097 "schema_version": "1", 1098 }, 1099 } 1100 1101 actual, err := r.Refresh(s, 42) 1102 if err != nil { 1103 t.Fatalf("err: %s", err) 1104 } 1105 1106 expected := &terraform.InstanceState{ 1107 ID: "bar", 1108 Attributes: map[string]string{ 1109 "id": "bar", 1110 "newfoo": "13", 1111 }, 1112 Meta: map[string]interface{}{ 1113 "schema_version": "2", 1114 }, 1115 } 1116 1117 if !reflect.DeepEqual(actual, expected) { 1118 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 1119 } 1120 } 1121 1122 func TestResourceRefresh_noMigrationNeeded(t *testing.T) { 1123 r := &Resource{ 1124 SchemaVersion: 2, 1125 Schema: map[string]*Schema{ 1126 "newfoo": &Schema{ 1127 Type: TypeInt, 1128 Optional: true, 1129 }, 1130 }, 1131 } 1132 1133 r.Read = func(d *ResourceData, m interface{}) error { 1134 return d.Set("newfoo", d.Get("newfoo").(int)+1) 1135 } 1136 1137 r.MigrateState = func( 1138 v int, 1139 s *terraform.InstanceState, 1140 meta interface{}) (*terraform.InstanceState, error) { 1141 t.Fatal("Migrate function shouldn't be called!") 1142 return nil, nil 1143 } 1144 1145 s := &terraform.InstanceState{ 1146 ID: "bar", 1147 Attributes: map[string]string{ 1148 "newfoo": "12", 1149 }, 1150 Meta: map[string]interface{}{ 1151 "schema_version": "2", 1152 }, 1153 } 1154 1155 actual, err := r.Refresh(s, nil) 1156 if err != nil { 1157 t.Fatalf("err: %s", err) 1158 } 1159 1160 expected := &terraform.InstanceState{ 1161 ID: "bar", 1162 Attributes: map[string]string{ 1163 "id": "bar", 1164 "newfoo": "13", 1165 }, 1166 Meta: map[string]interface{}{ 1167 "schema_version": "2", 1168 }, 1169 } 1170 1171 if !reflect.DeepEqual(actual, expected) { 1172 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 1173 } 1174 } 1175 1176 func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) { 1177 r := &Resource{ 1178 // Version 1 > Version 0 1179 SchemaVersion: 1, 1180 Schema: map[string]*Schema{ 1181 "newfoo": &Schema{ 1182 Type: TypeInt, 1183 Optional: true, 1184 }, 1185 }, 1186 } 1187 1188 r.Read = func(d *ResourceData, m interface{}) error { 1189 return d.Set("newfoo", d.Get("newfoo").(int)+1) 1190 } 1191 1192 r.MigrateState = func( 1193 v int, 1194 s *terraform.InstanceState, 1195 meta interface{}) (*terraform.InstanceState, error) { 1196 s.Attributes["newfoo"] = s.Attributes["oldfoo"] 1197 return s, nil 1198 } 1199 1200 s := &terraform.InstanceState{ 1201 ID: "bar", 1202 Attributes: map[string]string{ 1203 "oldfoo": "12", 1204 }, 1205 } 1206 1207 actual, err := r.Refresh(s, nil) 1208 if err != nil { 1209 t.Fatalf("err: %s", err) 1210 } 1211 1212 expected := &terraform.InstanceState{ 1213 ID: "bar", 1214 Attributes: map[string]string{ 1215 "id": "bar", 1216 "newfoo": "13", 1217 }, 1218 Meta: map[string]interface{}{ 1219 "schema_version": "1", 1220 }, 1221 } 1222 1223 if !reflect.DeepEqual(actual, expected) { 1224 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 1225 } 1226 } 1227 1228 func TestResourceRefresh_migrateStateErr(t *testing.T) { 1229 r := &Resource{ 1230 SchemaVersion: 2, 1231 Schema: map[string]*Schema{ 1232 "newfoo": &Schema{ 1233 Type: TypeInt, 1234 Optional: true, 1235 }, 1236 }, 1237 } 1238 1239 r.Read = func(d *ResourceData, m interface{}) error { 1240 t.Fatal("Read should never be called!") 1241 return nil 1242 } 1243 1244 r.MigrateState = func( 1245 v int, 1246 s *terraform.InstanceState, 1247 meta interface{}) (*terraform.InstanceState, error) { 1248 return s, fmt.Errorf("triggering an error") 1249 } 1250 1251 s := &terraform.InstanceState{ 1252 ID: "bar", 1253 Attributes: map[string]string{ 1254 "oldfoo": "12", 1255 }, 1256 } 1257 1258 _, err := r.Refresh(s, nil) 1259 if err == nil { 1260 t.Fatal("expected error, but got none!") 1261 } 1262 } 1263 1264 func TestResourceData(t *testing.T) { 1265 r := &Resource{ 1266 SchemaVersion: 2, 1267 Schema: map[string]*Schema{ 1268 "foo": &Schema{ 1269 Type: TypeInt, 1270 Optional: true, 1271 }, 1272 }, 1273 } 1274 1275 state := &terraform.InstanceState{ 1276 ID: "foo", 1277 Attributes: map[string]string{ 1278 "id": "foo", 1279 "foo": "42", 1280 }, 1281 } 1282 1283 data := r.Data(state) 1284 if data.Id() != "foo" { 1285 t.Fatalf("err: %s", data.Id()) 1286 } 1287 if v := data.Get("foo"); v != 42 { 1288 t.Fatalf("bad: %#v", v) 1289 } 1290 1291 // Set expectations 1292 state.Meta = map[string]interface{}{ 1293 "schema_version": "2", 1294 } 1295 1296 result := data.State() 1297 if !reflect.DeepEqual(result, state) { 1298 t.Fatalf("bad: %#v", result) 1299 } 1300 } 1301 1302 func TestResourceData_blank(t *testing.T) { 1303 r := &Resource{ 1304 SchemaVersion: 2, 1305 Schema: map[string]*Schema{ 1306 "foo": &Schema{ 1307 Type: TypeInt, 1308 Optional: true, 1309 }, 1310 }, 1311 } 1312 1313 data := r.Data(nil) 1314 if data.Id() != "" { 1315 t.Fatalf("err: %s", data.Id()) 1316 } 1317 if v := data.Get("foo"); v != 0 { 1318 t.Fatalf("bad: %#v", v) 1319 } 1320 }