github.com/xsb/terraform@v0.6.13-0.20160314145438-fe415c2f09d7/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 Err bool 400 }{ 401 { 402 nil, 403 true, 404 }, 405 406 // No optional and no required 407 { 408 &Resource{ 409 Schema: map[string]*Schema{ 410 "foo": &Schema{ 411 Type: TypeInt, 412 Optional: true, 413 Required: true, 414 }, 415 }, 416 }, 417 true, 418 }, 419 420 // Update undefined for non-ForceNew field 421 { 422 &Resource{ 423 Create: func(d *ResourceData, meta interface{}) error { return nil }, 424 Schema: map[string]*Schema{ 425 "boo": &Schema{ 426 Type: TypeInt, 427 Optional: true, 428 }, 429 }, 430 }, 431 true, 432 }, 433 434 // Update defined for ForceNew field 435 { 436 &Resource{ 437 Create: func(d *ResourceData, meta interface{}) error { return nil }, 438 Update: func(d *ResourceData, meta interface{}) error { return nil }, 439 Schema: map[string]*Schema{ 440 "goo": &Schema{ 441 Type: TypeInt, 442 Optional: true, 443 ForceNew: true, 444 }, 445 }, 446 }, 447 true, 448 }, 449 } 450 451 for i, tc := range cases { 452 err := tc.In.InternalValidate(schemaMap{}) 453 if err != nil != tc.Err { 454 t.Fatalf("%d: bad: %s", i, err) 455 } 456 } 457 } 458 459 func TestResourceRefresh(t *testing.T) { 460 r := &Resource{ 461 SchemaVersion: 2, 462 Schema: map[string]*Schema{ 463 "foo": &Schema{ 464 Type: TypeInt, 465 Optional: true, 466 }, 467 }, 468 } 469 470 r.Read = func(d *ResourceData, m interface{}) error { 471 if m != 42 { 472 return fmt.Errorf("meta not passed") 473 } 474 475 return d.Set("foo", d.Get("foo").(int)+1) 476 } 477 478 s := &terraform.InstanceState{ 479 ID: "bar", 480 Attributes: map[string]string{ 481 "foo": "12", 482 }, 483 } 484 485 expected := &terraform.InstanceState{ 486 ID: "bar", 487 Attributes: map[string]string{ 488 "id": "bar", 489 "foo": "13", 490 }, 491 Meta: map[string]string{ 492 "schema_version": "2", 493 }, 494 } 495 496 actual, err := r.Refresh(s, 42) 497 if err != nil { 498 t.Fatalf("err: %s", err) 499 } 500 501 if !reflect.DeepEqual(actual, expected) { 502 t.Fatalf("bad: %#v", actual) 503 } 504 } 505 506 func TestResourceRefresh_blankId(t *testing.T) { 507 r := &Resource{ 508 Schema: map[string]*Schema{ 509 "foo": &Schema{ 510 Type: TypeInt, 511 Optional: true, 512 }, 513 }, 514 } 515 516 r.Read = func(d *ResourceData, m interface{}) error { 517 d.SetId("foo") 518 return nil 519 } 520 521 s := &terraform.InstanceState{ 522 ID: "", 523 Attributes: map[string]string{}, 524 } 525 526 actual, err := r.Refresh(s, 42) 527 if err != nil { 528 t.Fatalf("err: %s", err) 529 } 530 if actual != nil { 531 t.Fatalf("bad: %#v", actual) 532 } 533 } 534 535 func TestResourceRefresh_delete(t *testing.T) { 536 r := &Resource{ 537 Schema: map[string]*Schema{ 538 "foo": &Schema{ 539 Type: TypeInt, 540 Optional: true, 541 }, 542 }, 543 } 544 545 r.Read = func(d *ResourceData, m interface{}) error { 546 d.SetId("") 547 return nil 548 } 549 550 s := &terraform.InstanceState{ 551 ID: "bar", 552 Attributes: map[string]string{ 553 "foo": "12", 554 }, 555 } 556 557 actual, err := r.Refresh(s, 42) 558 if err != nil { 559 t.Fatalf("err: %s", err) 560 } 561 562 if actual != nil { 563 t.Fatalf("bad: %#v", actual) 564 } 565 } 566 567 func TestResourceRefresh_existsError(t *testing.T) { 568 r := &Resource{ 569 Schema: map[string]*Schema{ 570 "foo": &Schema{ 571 Type: TypeInt, 572 Optional: true, 573 }, 574 }, 575 } 576 577 r.Exists = func(*ResourceData, interface{}) (bool, error) { 578 return false, fmt.Errorf("error") 579 } 580 581 r.Read = func(d *ResourceData, m interface{}) error { 582 panic("shouldn't be called") 583 } 584 585 s := &terraform.InstanceState{ 586 ID: "bar", 587 Attributes: map[string]string{ 588 "foo": "12", 589 }, 590 } 591 592 actual, err := r.Refresh(s, 42) 593 if err == nil { 594 t.Fatalf("should error") 595 } 596 if !reflect.DeepEqual(actual, s) { 597 t.Fatalf("bad: %#v", actual) 598 } 599 } 600 601 func TestResourceRefresh_noExists(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, nil 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("err: %s", err) 629 } 630 if actual != nil { 631 t.Fatalf("should have no state") 632 } 633 } 634 635 func TestResourceRefresh_needsMigration(t *testing.T) { 636 // Schema v2 it deals only in newfoo, which tracks foo as an int 637 r := &Resource{ 638 SchemaVersion: 2, 639 Schema: map[string]*Schema{ 640 "newfoo": &Schema{ 641 Type: TypeInt, 642 Optional: true, 643 }, 644 }, 645 } 646 647 r.Read = func(d *ResourceData, m interface{}) error { 648 return d.Set("newfoo", d.Get("newfoo").(int)+1) 649 } 650 651 r.MigrateState = func( 652 v int, 653 s *terraform.InstanceState, 654 meta interface{}) (*terraform.InstanceState, error) { 655 // Real state migration functions will probably switch on this value, 656 // but we'll just assert on it for now. 657 if v != 1 { 658 t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v) 659 } 660 661 if meta != 42 { 662 t.Fatal("Expected meta to be passed through to the migration function") 663 } 664 665 oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64) 666 if err != nil { 667 t.Fatalf("err: %#v", err) 668 } 669 s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10)) 670 delete(s.Attributes, "oldfoo") 671 672 return s, nil 673 } 674 675 // State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th 676 // the scale of newfoo 677 s := &terraform.InstanceState{ 678 ID: "bar", 679 Attributes: map[string]string{ 680 "oldfoo": "1.2", 681 }, 682 Meta: map[string]string{ 683 "schema_version": "1", 684 }, 685 } 686 687 actual, err := r.Refresh(s, 42) 688 if err != nil { 689 t.Fatalf("err: %s", err) 690 } 691 692 expected := &terraform.InstanceState{ 693 ID: "bar", 694 Attributes: map[string]string{ 695 "id": "bar", 696 "newfoo": "13", 697 }, 698 Meta: map[string]string{ 699 "schema_version": "2", 700 }, 701 } 702 703 if !reflect.DeepEqual(actual, expected) { 704 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 705 } 706 } 707 708 func TestResourceRefresh_noMigrationNeeded(t *testing.T) { 709 r := &Resource{ 710 SchemaVersion: 2, 711 Schema: map[string]*Schema{ 712 "newfoo": &Schema{ 713 Type: TypeInt, 714 Optional: true, 715 }, 716 }, 717 } 718 719 r.Read = func(d *ResourceData, m interface{}) error { 720 return d.Set("newfoo", d.Get("newfoo").(int)+1) 721 } 722 723 r.MigrateState = func( 724 v int, 725 s *terraform.InstanceState, 726 meta interface{}) (*terraform.InstanceState, error) { 727 t.Fatal("Migrate function shouldn't be called!") 728 return nil, nil 729 } 730 731 s := &terraform.InstanceState{ 732 ID: "bar", 733 Attributes: map[string]string{ 734 "newfoo": "12", 735 }, 736 Meta: map[string]string{ 737 "schema_version": "2", 738 }, 739 } 740 741 actual, err := r.Refresh(s, nil) 742 if err != nil { 743 t.Fatalf("err: %s", err) 744 } 745 746 expected := &terraform.InstanceState{ 747 ID: "bar", 748 Attributes: map[string]string{ 749 "id": "bar", 750 "newfoo": "13", 751 }, 752 Meta: map[string]string{ 753 "schema_version": "2", 754 }, 755 } 756 757 if !reflect.DeepEqual(actual, expected) { 758 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 759 } 760 } 761 762 func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) { 763 r := &Resource{ 764 // Version 1 > Version 0 765 SchemaVersion: 1, 766 Schema: map[string]*Schema{ 767 "newfoo": &Schema{ 768 Type: TypeInt, 769 Optional: true, 770 }, 771 }, 772 } 773 774 r.Read = func(d *ResourceData, m interface{}) error { 775 return d.Set("newfoo", d.Get("newfoo").(int)+1) 776 } 777 778 r.MigrateState = func( 779 v int, 780 s *terraform.InstanceState, 781 meta interface{}) (*terraform.InstanceState, error) { 782 s.Attributes["newfoo"] = s.Attributes["oldfoo"] 783 return s, nil 784 } 785 786 s := &terraform.InstanceState{ 787 ID: "bar", 788 Attributes: map[string]string{ 789 "oldfoo": "12", 790 }, 791 } 792 793 actual, err := r.Refresh(s, nil) 794 if err != nil { 795 t.Fatalf("err: %s", err) 796 } 797 798 expected := &terraform.InstanceState{ 799 ID: "bar", 800 Attributes: map[string]string{ 801 "id": "bar", 802 "newfoo": "13", 803 }, 804 Meta: map[string]string{ 805 "schema_version": "1", 806 }, 807 } 808 809 if !reflect.DeepEqual(actual, expected) { 810 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 811 } 812 } 813 814 func TestResourceRefresh_migrateStateErr(t *testing.T) { 815 r := &Resource{ 816 SchemaVersion: 2, 817 Schema: map[string]*Schema{ 818 "newfoo": &Schema{ 819 Type: TypeInt, 820 Optional: true, 821 }, 822 }, 823 } 824 825 r.Read = func(d *ResourceData, m interface{}) error { 826 t.Fatal("Read should never be called!") 827 return nil 828 } 829 830 r.MigrateState = func( 831 v int, 832 s *terraform.InstanceState, 833 meta interface{}) (*terraform.InstanceState, error) { 834 return s, fmt.Errorf("triggering an error") 835 } 836 837 s := &terraform.InstanceState{ 838 ID: "bar", 839 Attributes: map[string]string{ 840 "oldfoo": "12", 841 }, 842 } 843 844 _, err := r.Refresh(s, nil) 845 if err == nil { 846 t.Fatal("expected error, but got none!") 847 } 848 }