github.com/pmcatominey/terraform@v0.7.0-rc2.0.20160708105029-1401a52a5cc5/helper/schema/resource_test.go (about) 1 package schema 2 3 import ( 4 "fmt" 5 "reflect" 6 "strconv" 7 "testing" 8 9 "github.com/hashicorp/terraform/terraform" 10 ) 11 12 func TestResourceApply_create(t *testing.T) { 13 r := &Resource{ 14 SchemaVersion: 2, 15 Schema: map[string]*Schema{ 16 "foo": &Schema{ 17 Type: TypeInt, 18 Optional: true, 19 }, 20 }, 21 } 22 23 called := false 24 r.Create = func(d *ResourceData, m interface{}) error { 25 called = true 26 d.SetId("foo") 27 return nil 28 } 29 30 var s *terraform.InstanceState = nil 31 32 d := &terraform.InstanceDiff{ 33 Attributes: map[string]*terraform.ResourceAttrDiff{ 34 "foo": &terraform.ResourceAttrDiff{ 35 New: "42", 36 }, 37 }, 38 } 39 40 actual, err := r.Apply(s, d, nil) 41 if err != nil { 42 t.Fatalf("err: %s", err) 43 } 44 45 if !called { 46 t.Fatal("not called") 47 } 48 49 expected := &terraform.InstanceState{ 50 ID: "foo", 51 Attributes: map[string]string{ 52 "id": "foo", 53 "foo": "42", 54 }, 55 Meta: map[string]string{ 56 "schema_version": "2", 57 }, 58 } 59 60 if !reflect.DeepEqual(actual, expected) { 61 t.Fatalf("bad: %#v", actual) 62 } 63 } 64 65 func TestResourceApply_destroy(t *testing.T) { 66 r := &Resource{ 67 Schema: map[string]*Schema{ 68 "foo": &Schema{ 69 Type: TypeInt, 70 Optional: true, 71 }, 72 }, 73 } 74 75 called := false 76 r.Delete = func(d *ResourceData, m interface{}) error { 77 called = true 78 return nil 79 } 80 81 s := &terraform.InstanceState{ 82 ID: "bar", 83 } 84 85 d := &terraform.InstanceDiff{ 86 Destroy: true, 87 } 88 89 actual, err := r.Apply(s, d, nil) 90 if err != nil { 91 t.Fatalf("err: %s", err) 92 } 93 94 if !called { 95 t.Fatal("delete not called") 96 } 97 98 if actual != nil { 99 t.Fatalf("bad: %#v", actual) 100 } 101 } 102 103 func TestResourceApply_destroyCreate(t *testing.T) { 104 r := &Resource{ 105 Schema: map[string]*Schema{ 106 "foo": &Schema{ 107 Type: TypeInt, 108 Optional: true, 109 }, 110 111 "tags": &Schema{ 112 Type: TypeMap, 113 Optional: true, 114 Computed: true, 115 }, 116 }, 117 } 118 119 change := false 120 r.Create = func(d *ResourceData, m interface{}) error { 121 change = d.HasChange("tags") 122 d.SetId("foo") 123 return nil 124 } 125 r.Delete = func(d *ResourceData, m interface{}) error { 126 return nil 127 } 128 129 var s *terraform.InstanceState = &terraform.InstanceState{ 130 ID: "bar", 131 Attributes: map[string]string{ 132 "foo": "bar", 133 "tags.Name": "foo", 134 }, 135 } 136 137 d := &terraform.InstanceDiff{ 138 Attributes: map[string]*terraform.ResourceAttrDiff{ 139 "foo": &terraform.ResourceAttrDiff{ 140 New: "42", 141 RequiresNew: true, 142 }, 143 "tags.Name": &terraform.ResourceAttrDiff{ 144 Old: "foo", 145 New: "foo", 146 RequiresNew: true, 147 }, 148 }, 149 } 150 151 actual, err := r.Apply(s, d, nil) 152 if err != nil { 153 t.Fatalf("err: %s", err) 154 } 155 156 if !change { 157 t.Fatal("should have change") 158 } 159 160 expected := &terraform.InstanceState{ 161 ID: "foo", 162 Attributes: map[string]string{ 163 "id": "foo", 164 "foo": "42", 165 "tags.%": "1", 166 "tags.Name": "foo", 167 }, 168 } 169 170 if !reflect.DeepEqual(actual, expected) { 171 t.Fatalf("bad: %#v", actual) 172 } 173 } 174 175 func TestResourceApply_destroyPartial(t *testing.T) { 176 r := &Resource{ 177 Schema: map[string]*Schema{ 178 "foo": &Schema{ 179 Type: TypeInt, 180 Optional: true, 181 }, 182 }, 183 SchemaVersion: 3, 184 } 185 186 r.Delete = func(d *ResourceData, m interface{}) error { 187 d.Set("foo", 42) 188 return fmt.Errorf("some error") 189 } 190 191 s := &terraform.InstanceState{ 192 ID: "bar", 193 Attributes: map[string]string{ 194 "foo": "12", 195 }, 196 } 197 198 d := &terraform.InstanceDiff{ 199 Destroy: true, 200 } 201 202 actual, err := r.Apply(s, d, nil) 203 if err == nil { 204 t.Fatal("should error") 205 } 206 207 expected := &terraform.InstanceState{ 208 ID: "bar", 209 Attributes: map[string]string{ 210 "id": "bar", 211 "foo": "42", 212 }, 213 Meta: map[string]string{ 214 "schema_version": "3", 215 }, 216 } 217 218 if !reflect.DeepEqual(actual, expected) { 219 t.Fatalf("expected:\n%#v\n\ngot:\n%#v", expected, actual) 220 } 221 } 222 223 func TestResourceApply_update(t *testing.T) { 224 r := &Resource{ 225 Schema: map[string]*Schema{ 226 "foo": &Schema{ 227 Type: TypeInt, 228 Optional: true, 229 }, 230 }, 231 } 232 233 r.Update = func(d *ResourceData, m interface{}) error { 234 d.Set("foo", 42) 235 return nil 236 } 237 238 s := &terraform.InstanceState{ 239 ID: "foo", 240 Attributes: map[string]string{ 241 "foo": "12", 242 }, 243 } 244 245 d := &terraform.InstanceDiff{ 246 Attributes: map[string]*terraform.ResourceAttrDiff{ 247 "foo": &terraform.ResourceAttrDiff{ 248 New: "13", 249 }, 250 }, 251 } 252 253 actual, err := r.Apply(s, d, nil) 254 if err != nil { 255 t.Fatalf("err: %s", err) 256 } 257 258 expected := &terraform.InstanceState{ 259 ID: "foo", 260 Attributes: map[string]string{ 261 "id": "foo", 262 "foo": "42", 263 }, 264 } 265 266 if !reflect.DeepEqual(actual, expected) { 267 t.Fatalf("bad: %#v", actual) 268 } 269 } 270 271 func TestResourceApply_updateNoCallback(t *testing.T) { 272 r := &Resource{ 273 Schema: map[string]*Schema{ 274 "foo": &Schema{ 275 Type: TypeInt, 276 Optional: true, 277 }, 278 }, 279 } 280 281 r.Update = nil 282 283 s := &terraform.InstanceState{ 284 ID: "foo", 285 Attributes: map[string]string{ 286 "foo": "12", 287 }, 288 } 289 290 d := &terraform.InstanceDiff{ 291 Attributes: map[string]*terraform.ResourceAttrDiff{ 292 "foo": &terraform.ResourceAttrDiff{ 293 New: "13", 294 }, 295 }, 296 } 297 298 actual, err := r.Apply(s, d, nil) 299 if err == nil { 300 t.Fatal("should error") 301 } 302 303 expected := &terraform.InstanceState{ 304 ID: "foo", 305 Attributes: map[string]string{ 306 "foo": "12", 307 }, 308 } 309 310 if !reflect.DeepEqual(actual, expected) { 311 t.Fatalf("bad: %#v", actual) 312 } 313 } 314 315 func TestResourceApply_isNewResource(t *testing.T) { 316 r := &Resource{ 317 Schema: map[string]*Schema{ 318 "foo": &Schema{ 319 Type: TypeString, 320 Optional: true, 321 }, 322 }, 323 } 324 325 updateFunc := func(d *ResourceData, m interface{}) error { 326 d.Set("foo", "updated") 327 if d.IsNewResource() { 328 d.Set("foo", "new-resource") 329 } 330 return nil 331 } 332 r.Create = func(d *ResourceData, m interface{}) error { 333 d.SetId("foo") 334 d.Set("foo", "created") 335 return updateFunc(d, m) 336 } 337 r.Update = updateFunc 338 339 d := &terraform.InstanceDiff{ 340 Attributes: map[string]*terraform.ResourceAttrDiff{ 341 "foo": &terraform.ResourceAttrDiff{ 342 New: "bla-blah", 343 }, 344 }, 345 } 346 347 // positive test 348 var s *terraform.InstanceState = nil 349 350 actual, err := r.Apply(s, d, nil) 351 if err != nil { 352 t.Fatalf("err: %s", err) 353 } 354 355 expected := &terraform.InstanceState{ 356 ID: "foo", 357 Attributes: map[string]string{ 358 "id": "foo", 359 "foo": "new-resource", 360 }, 361 } 362 363 if !reflect.DeepEqual(actual, expected) { 364 t.Fatalf("actual: %#v\nexpected: %#v", 365 actual, expected) 366 } 367 368 // negative test 369 s = &terraform.InstanceState{ 370 ID: "foo", 371 Attributes: map[string]string{ 372 "id": "foo", 373 "foo": "new-resource", 374 }, 375 } 376 377 actual, err = r.Apply(s, d, nil) 378 if err != nil { 379 t.Fatalf("err: %s", err) 380 } 381 382 expected = &terraform.InstanceState{ 383 ID: "foo", 384 Attributes: map[string]string{ 385 "id": "foo", 386 "foo": "updated", 387 }, 388 } 389 390 if !reflect.DeepEqual(actual, expected) { 391 t.Fatalf("actual: %#v\nexpected: %#v", 392 actual, expected) 393 } 394 } 395 396 func TestResourceInternalValidate(t *testing.T) { 397 cases := []struct { 398 In *Resource 399 Writable bool 400 Err bool 401 }{ 402 { 403 nil, 404 true, 405 true, 406 }, 407 408 // No optional and no required 409 { 410 &Resource{ 411 Schema: map[string]*Schema{ 412 "foo": &Schema{ 413 Type: TypeInt, 414 Optional: true, 415 Required: true, 416 }, 417 }, 418 }, 419 true, 420 true, 421 }, 422 423 // Update undefined for non-ForceNew field 424 { 425 &Resource{ 426 Create: func(d *ResourceData, meta interface{}) error { return nil }, 427 Schema: map[string]*Schema{ 428 "boo": &Schema{ 429 Type: TypeInt, 430 Optional: true, 431 }, 432 }, 433 }, 434 true, 435 true, 436 }, 437 438 // Update defined for ForceNew field 439 { 440 &Resource{ 441 Create: func(d *ResourceData, meta interface{}) error { return nil }, 442 Update: func(d *ResourceData, meta interface{}) error { return nil }, 443 Schema: map[string]*Schema{ 444 "goo": &Schema{ 445 Type: TypeInt, 446 Optional: true, 447 ForceNew: true, 448 }, 449 }, 450 }, 451 true, 452 true, 453 }, 454 455 // non-writable doesn't need Update, Create or Delete 456 { 457 &Resource{ 458 Schema: map[string]*Schema{ 459 "goo": &Schema{ 460 Type: TypeInt, 461 Optional: true, 462 }, 463 }, 464 }, 465 false, 466 false, 467 }, 468 469 // non-writable *must not* have Create 470 { 471 &Resource{ 472 Create: func(d *ResourceData, meta interface{}) error { return nil }, 473 Schema: map[string]*Schema{ 474 "goo": &Schema{ 475 Type: TypeInt, 476 Optional: true, 477 }, 478 }, 479 }, 480 false, 481 true, 482 }, 483 } 484 485 for i, tc := range cases { 486 err := tc.In.InternalValidate(schemaMap{}, tc.Writable) 487 if err != nil != tc.Err { 488 t.Fatalf("%d: bad: %s", i, err) 489 } 490 } 491 } 492 493 func TestResourceRefresh(t *testing.T) { 494 r := &Resource{ 495 SchemaVersion: 2, 496 Schema: map[string]*Schema{ 497 "foo": &Schema{ 498 Type: TypeInt, 499 Optional: true, 500 }, 501 }, 502 } 503 504 r.Read = func(d *ResourceData, m interface{}) error { 505 if m != 42 { 506 return fmt.Errorf("meta not passed") 507 } 508 509 return d.Set("foo", d.Get("foo").(int)+1) 510 } 511 512 s := &terraform.InstanceState{ 513 ID: "bar", 514 Attributes: map[string]string{ 515 "foo": "12", 516 }, 517 } 518 519 expected := &terraform.InstanceState{ 520 ID: "bar", 521 Attributes: map[string]string{ 522 "id": "bar", 523 "foo": "13", 524 }, 525 Meta: map[string]string{ 526 "schema_version": "2", 527 }, 528 } 529 530 actual, err := r.Refresh(s, 42) 531 if err != nil { 532 t.Fatalf("err: %s", err) 533 } 534 535 if !reflect.DeepEqual(actual, expected) { 536 t.Fatalf("bad: %#v", actual) 537 } 538 } 539 540 func TestResourceRefresh_blankId(t *testing.T) { 541 r := &Resource{ 542 Schema: map[string]*Schema{ 543 "foo": &Schema{ 544 Type: TypeInt, 545 Optional: true, 546 }, 547 }, 548 } 549 550 r.Read = func(d *ResourceData, m interface{}) error { 551 d.SetId("foo") 552 return nil 553 } 554 555 s := &terraform.InstanceState{ 556 ID: "", 557 Attributes: map[string]string{}, 558 } 559 560 actual, err := r.Refresh(s, 42) 561 if err != nil { 562 t.Fatalf("err: %s", err) 563 } 564 if actual != nil { 565 t.Fatalf("bad: %#v", actual) 566 } 567 } 568 569 func TestResourceRefresh_delete(t *testing.T) { 570 r := &Resource{ 571 Schema: map[string]*Schema{ 572 "foo": &Schema{ 573 Type: TypeInt, 574 Optional: true, 575 }, 576 }, 577 } 578 579 r.Read = func(d *ResourceData, m interface{}) error { 580 d.SetId("") 581 return nil 582 } 583 584 s := &terraform.InstanceState{ 585 ID: "bar", 586 Attributes: map[string]string{ 587 "foo": "12", 588 }, 589 } 590 591 actual, err := r.Refresh(s, 42) 592 if err != nil { 593 t.Fatalf("err: %s", err) 594 } 595 596 if actual != nil { 597 t.Fatalf("bad: %#v", actual) 598 } 599 } 600 601 func TestResourceRefresh_existsError(t *testing.T) { 602 r := &Resource{ 603 Schema: map[string]*Schema{ 604 "foo": &Schema{ 605 Type: TypeInt, 606 Optional: true, 607 }, 608 }, 609 } 610 611 r.Exists = func(*ResourceData, interface{}) (bool, error) { 612 return false, fmt.Errorf("error") 613 } 614 615 r.Read = func(d *ResourceData, m interface{}) error { 616 panic("shouldn't be called") 617 } 618 619 s := &terraform.InstanceState{ 620 ID: "bar", 621 Attributes: map[string]string{ 622 "foo": "12", 623 }, 624 } 625 626 actual, err := r.Refresh(s, 42) 627 if err == nil { 628 t.Fatalf("should error") 629 } 630 if !reflect.DeepEqual(actual, s) { 631 t.Fatalf("bad: %#v", actual) 632 } 633 } 634 635 func TestResourceRefresh_noExists(t *testing.T) { 636 r := &Resource{ 637 Schema: map[string]*Schema{ 638 "foo": &Schema{ 639 Type: TypeInt, 640 Optional: true, 641 }, 642 }, 643 } 644 645 r.Exists = func(*ResourceData, interface{}) (bool, error) { 646 return false, nil 647 } 648 649 r.Read = func(d *ResourceData, m interface{}) error { 650 panic("shouldn't be called") 651 } 652 653 s := &terraform.InstanceState{ 654 ID: "bar", 655 Attributes: map[string]string{ 656 "foo": "12", 657 }, 658 } 659 660 actual, err := r.Refresh(s, 42) 661 if err != nil { 662 t.Fatalf("err: %s", err) 663 } 664 if actual != nil { 665 t.Fatalf("should have no state") 666 } 667 } 668 669 func TestResourceRefresh_needsMigration(t *testing.T) { 670 // Schema v2 it deals only in newfoo, which tracks foo as an int 671 r := &Resource{ 672 SchemaVersion: 2, 673 Schema: map[string]*Schema{ 674 "newfoo": &Schema{ 675 Type: TypeInt, 676 Optional: true, 677 }, 678 }, 679 } 680 681 r.Read = func(d *ResourceData, m interface{}) error { 682 return d.Set("newfoo", d.Get("newfoo").(int)+1) 683 } 684 685 r.MigrateState = func( 686 v int, 687 s *terraform.InstanceState, 688 meta interface{}) (*terraform.InstanceState, error) { 689 // Real state migration functions will probably switch on this value, 690 // but we'll just assert on it for now. 691 if v != 1 { 692 t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v) 693 } 694 695 if meta != 42 { 696 t.Fatal("Expected meta to be passed through to the migration function") 697 } 698 699 oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64) 700 if err != nil { 701 t.Fatalf("err: %#v", err) 702 } 703 s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10)) 704 delete(s.Attributes, "oldfoo") 705 706 return s, nil 707 } 708 709 // State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th 710 // the scale of newfoo 711 s := &terraform.InstanceState{ 712 ID: "bar", 713 Attributes: map[string]string{ 714 "oldfoo": "1.2", 715 }, 716 Meta: map[string]string{ 717 "schema_version": "1", 718 }, 719 } 720 721 actual, err := r.Refresh(s, 42) 722 if err != nil { 723 t.Fatalf("err: %s", err) 724 } 725 726 expected := &terraform.InstanceState{ 727 ID: "bar", 728 Attributes: map[string]string{ 729 "id": "bar", 730 "newfoo": "13", 731 }, 732 Meta: map[string]string{ 733 "schema_version": "2", 734 }, 735 } 736 737 if !reflect.DeepEqual(actual, expected) { 738 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 739 } 740 } 741 742 func TestResourceRefresh_noMigrationNeeded(t *testing.T) { 743 r := &Resource{ 744 SchemaVersion: 2, 745 Schema: map[string]*Schema{ 746 "newfoo": &Schema{ 747 Type: TypeInt, 748 Optional: true, 749 }, 750 }, 751 } 752 753 r.Read = func(d *ResourceData, m interface{}) error { 754 return d.Set("newfoo", d.Get("newfoo").(int)+1) 755 } 756 757 r.MigrateState = func( 758 v int, 759 s *terraform.InstanceState, 760 meta interface{}) (*terraform.InstanceState, error) { 761 t.Fatal("Migrate function shouldn't be called!") 762 return nil, nil 763 } 764 765 s := &terraform.InstanceState{ 766 ID: "bar", 767 Attributes: map[string]string{ 768 "newfoo": "12", 769 }, 770 Meta: map[string]string{ 771 "schema_version": "2", 772 }, 773 } 774 775 actual, err := r.Refresh(s, nil) 776 if err != nil { 777 t.Fatalf("err: %s", err) 778 } 779 780 expected := &terraform.InstanceState{ 781 ID: "bar", 782 Attributes: map[string]string{ 783 "id": "bar", 784 "newfoo": "13", 785 }, 786 Meta: map[string]string{ 787 "schema_version": "2", 788 }, 789 } 790 791 if !reflect.DeepEqual(actual, expected) { 792 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 793 } 794 } 795 796 func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) { 797 r := &Resource{ 798 // Version 1 > Version 0 799 SchemaVersion: 1, 800 Schema: map[string]*Schema{ 801 "newfoo": &Schema{ 802 Type: TypeInt, 803 Optional: true, 804 }, 805 }, 806 } 807 808 r.Read = func(d *ResourceData, m interface{}) error { 809 return d.Set("newfoo", d.Get("newfoo").(int)+1) 810 } 811 812 r.MigrateState = func( 813 v int, 814 s *terraform.InstanceState, 815 meta interface{}) (*terraform.InstanceState, error) { 816 s.Attributes["newfoo"] = s.Attributes["oldfoo"] 817 return s, nil 818 } 819 820 s := &terraform.InstanceState{ 821 ID: "bar", 822 Attributes: map[string]string{ 823 "oldfoo": "12", 824 }, 825 } 826 827 actual, err := r.Refresh(s, nil) 828 if err != nil { 829 t.Fatalf("err: %s", err) 830 } 831 832 expected := &terraform.InstanceState{ 833 ID: "bar", 834 Attributes: map[string]string{ 835 "id": "bar", 836 "newfoo": "13", 837 }, 838 Meta: map[string]string{ 839 "schema_version": "1", 840 }, 841 } 842 843 if !reflect.DeepEqual(actual, expected) { 844 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 845 } 846 } 847 848 func TestResourceRefresh_migrateStateErr(t *testing.T) { 849 r := &Resource{ 850 SchemaVersion: 2, 851 Schema: map[string]*Schema{ 852 "newfoo": &Schema{ 853 Type: TypeInt, 854 Optional: true, 855 }, 856 }, 857 } 858 859 r.Read = func(d *ResourceData, m interface{}) error { 860 t.Fatal("Read should never be called!") 861 return nil 862 } 863 864 r.MigrateState = func( 865 v int, 866 s *terraform.InstanceState, 867 meta interface{}) (*terraform.InstanceState, error) { 868 return s, fmt.Errorf("triggering an error") 869 } 870 871 s := &terraform.InstanceState{ 872 ID: "bar", 873 Attributes: map[string]string{ 874 "oldfoo": "12", 875 }, 876 } 877 878 _, err := r.Refresh(s, nil) 879 if err == nil { 880 t.Fatal("expected error, but got none!") 881 } 882 } 883 884 func TestResourceData(t *testing.T) { 885 r := &Resource{ 886 SchemaVersion: 2, 887 Schema: map[string]*Schema{ 888 "foo": &Schema{ 889 Type: TypeInt, 890 Optional: true, 891 }, 892 }, 893 } 894 895 state := &terraform.InstanceState{ 896 ID: "foo", 897 Attributes: map[string]string{ 898 "id": "foo", 899 "foo": "42", 900 }, 901 } 902 903 data := r.Data(state) 904 if data.Id() != "foo" { 905 t.Fatalf("err: %s", data.Id()) 906 } 907 if v := data.Get("foo"); v != 42 { 908 t.Fatalf("bad: %#v", v) 909 } 910 911 // Set expectations 912 state.Meta = map[string]string{ 913 "schema_version": "2", 914 } 915 916 result := data.State() 917 if !reflect.DeepEqual(result, state) { 918 t.Fatalf("bad: %#v", result) 919 } 920 } 921 922 func TestResourceData_blank(t *testing.T) { 923 r := &Resource{ 924 SchemaVersion: 2, 925 Schema: map[string]*Schema{ 926 "foo": &Schema{ 927 Type: TypeInt, 928 Optional: true, 929 }, 930 }, 931 } 932 933 data := r.Data(nil) 934 if data.Id() != "" { 935 t.Fatalf("err: %s", data.Id()) 936 } 937 if v := data.Get("foo"); v != 0 { 938 t.Fatalf("bad: %#v", v) 939 } 940 }