github.com/bigkraig/terraform@v0.6.4-0.20151219155159-c90d1b074e31/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 TestResourceInternalValidate(t *testing.T) { 316 cases := []struct { 317 In *Resource 318 Err bool 319 }{ 320 { 321 nil, 322 true, 323 }, 324 325 // No optional and no required 326 { 327 &Resource{ 328 Schema: map[string]*Schema{ 329 "foo": &Schema{ 330 Type: TypeInt, 331 Optional: true, 332 Required: true, 333 }, 334 }, 335 }, 336 true, 337 }, 338 339 // Update undefined for non-ForceNew field 340 { 341 &Resource{ 342 Create: func(d *ResourceData, meta interface{}) error { return nil }, 343 Schema: map[string]*Schema{ 344 "boo": &Schema{ 345 Type: TypeInt, 346 Optional: true, 347 }, 348 }, 349 }, 350 true, 351 }, 352 353 // Update defined for ForceNew field 354 { 355 &Resource{ 356 Create: func(d *ResourceData, meta interface{}) error { return nil }, 357 Update: func(d *ResourceData, meta interface{}) error { return nil }, 358 Schema: map[string]*Schema{ 359 "goo": &Schema{ 360 Type: TypeInt, 361 Optional: true, 362 ForceNew: true, 363 }, 364 }, 365 }, 366 true, 367 }, 368 } 369 370 for i, tc := range cases { 371 err := tc.In.InternalValidate(schemaMap{}) 372 if err != nil != tc.Err { 373 t.Fatalf("%d: bad: %s", i, err) 374 } 375 } 376 } 377 378 func TestResourceRefresh(t *testing.T) { 379 r := &Resource{ 380 SchemaVersion: 2, 381 Schema: map[string]*Schema{ 382 "foo": &Schema{ 383 Type: TypeInt, 384 Optional: true, 385 }, 386 }, 387 } 388 389 r.Read = func(d *ResourceData, m interface{}) error { 390 if m != 42 { 391 return fmt.Errorf("meta not passed") 392 } 393 394 return d.Set("foo", d.Get("foo").(int)+1) 395 } 396 397 s := &terraform.InstanceState{ 398 ID: "bar", 399 Attributes: map[string]string{ 400 "foo": "12", 401 }, 402 } 403 404 expected := &terraform.InstanceState{ 405 ID: "bar", 406 Attributes: map[string]string{ 407 "id": "bar", 408 "foo": "13", 409 }, 410 Meta: map[string]string{ 411 "schema_version": "2", 412 }, 413 } 414 415 actual, err := r.Refresh(s, 42) 416 if err != nil { 417 t.Fatalf("err: %s", err) 418 } 419 420 if !reflect.DeepEqual(actual, expected) { 421 t.Fatalf("bad: %#v", actual) 422 } 423 } 424 425 func TestResourceRefresh_blankId(t *testing.T) { 426 r := &Resource{ 427 Schema: map[string]*Schema{ 428 "foo": &Schema{ 429 Type: TypeInt, 430 Optional: true, 431 }, 432 }, 433 } 434 435 r.Read = func(d *ResourceData, m interface{}) error { 436 d.SetId("foo") 437 return nil 438 } 439 440 s := &terraform.InstanceState{ 441 ID: "", 442 Attributes: map[string]string{}, 443 } 444 445 actual, err := r.Refresh(s, 42) 446 if err != nil { 447 t.Fatalf("err: %s", err) 448 } 449 if actual != nil { 450 t.Fatalf("bad: %#v", actual) 451 } 452 } 453 454 func TestResourceRefresh_delete(t *testing.T) { 455 r := &Resource{ 456 Schema: map[string]*Schema{ 457 "foo": &Schema{ 458 Type: TypeInt, 459 Optional: true, 460 }, 461 }, 462 } 463 464 r.Read = func(d *ResourceData, m interface{}) error { 465 d.SetId("") 466 return nil 467 } 468 469 s := &terraform.InstanceState{ 470 ID: "bar", 471 Attributes: map[string]string{ 472 "foo": "12", 473 }, 474 } 475 476 actual, err := r.Refresh(s, 42) 477 if err != nil { 478 t.Fatalf("err: %s", err) 479 } 480 481 if actual != nil { 482 t.Fatalf("bad: %#v", actual) 483 } 484 } 485 486 func TestResourceRefresh_existsError(t *testing.T) { 487 r := &Resource{ 488 Schema: map[string]*Schema{ 489 "foo": &Schema{ 490 Type: TypeInt, 491 Optional: true, 492 }, 493 }, 494 } 495 496 r.Exists = func(*ResourceData, interface{}) (bool, error) { 497 return false, fmt.Errorf("error") 498 } 499 500 r.Read = func(d *ResourceData, m interface{}) error { 501 panic("shouldn't be called") 502 } 503 504 s := &terraform.InstanceState{ 505 ID: "bar", 506 Attributes: map[string]string{ 507 "foo": "12", 508 }, 509 } 510 511 actual, err := r.Refresh(s, 42) 512 if err == nil { 513 t.Fatalf("should error") 514 } 515 if !reflect.DeepEqual(actual, s) { 516 t.Fatalf("bad: %#v", actual) 517 } 518 } 519 520 func TestResourceRefresh_noExists(t *testing.T) { 521 r := &Resource{ 522 Schema: map[string]*Schema{ 523 "foo": &Schema{ 524 Type: TypeInt, 525 Optional: true, 526 }, 527 }, 528 } 529 530 r.Exists = func(*ResourceData, interface{}) (bool, error) { 531 return false, nil 532 } 533 534 r.Read = func(d *ResourceData, m interface{}) error { 535 panic("shouldn't be called") 536 } 537 538 s := &terraform.InstanceState{ 539 ID: "bar", 540 Attributes: map[string]string{ 541 "foo": "12", 542 }, 543 } 544 545 actual, err := r.Refresh(s, 42) 546 if err != nil { 547 t.Fatalf("err: %s", err) 548 } 549 if actual != nil { 550 t.Fatalf("should have no state") 551 } 552 } 553 554 func TestResourceRefresh_needsMigration(t *testing.T) { 555 // Schema v2 it deals only in newfoo, which tracks foo as an int 556 r := &Resource{ 557 SchemaVersion: 2, 558 Schema: map[string]*Schema{ 559 "newfoo": &Schema{ 560 Type: TypeInt, 561 Optional: true, 562 }, 563 }, 564 } 565 566 r.Read = func(d *ResourceData, m interface{}) error { 567 return d.Set("newfoo", d.Get("newfoo").(int)+1) 568 } 569 570 r.MigrateState = func( 571 v int, 572 s *terraform.InstanceState, 573 meta interface{}) (*terraform.InstanceState, error) { 574 // Real state migration functions will probably switch on this value, 575 // but we'll just assert on it for now. 576 if v != 1 { 577 t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v) 578 } 579 580 if meta != 42 { 581 t.Fatal("Expected meta to be passed through to the migration function") 582 } 583 584 oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64) 585 if err != nil { 586 t.Fatalf("err: %#v", err) 587 } 588 s.Attributes["newfoo"] = strconv.Itoa(int(oldfoo * 10)) 589 delete(s.Attributes, "oldfoo") 590 591 return s, nil 592 } 593 594 // State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th 595 // the scale of newfoo 596 s := &terraform.InstanceState{ 597 ID: "bar", 598 Attributes: map[string]string{ 599 "oldfoo": "1.2", 600 }, 601 Meta: map[string]string{ 602 "schema_version": "1", 603 }, 604 } 605 606 actual, err := r.Refresh(s, 42) 607 if err != nil { 608 t.Fatalf("err: %s", err) 609 } 610 611 expected := &terraform.InstanceState{ 612 ID: "bar", 613 Attributes: map[string]string{ 614 "id": "bar", 615 "newfoo": "13", 616 }, 617 Meta: map[string]string{ 618 "schema_version": "2", 619 }, 620 } 621 622 if !reflect.DeepEqual(actual, expected) { 623 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 624 } 625 } 626 627 func TestResourceRefresh_noMigrationNeeded(t *testing.T) { 628 r := &Resource{ 629 SchemaVersion: 2, 630 Schema: map[string]*Schema{ 631 "newfoo": &Schema{ 632 Type: TypeInt, 633 Optional: true, 634 }, 635 }, 636 } 637 638 r.Read = func(d *ResourceData, m interface{}) error { 639 return d.Set("newfoo", d.Get("newfoo").(int)+1) 640 } 641 642 r.MigrateState = func( 643 v int, 644 s *terraform.InstanceState, 645 meta interface{}) (*terraform.InstanceState, error) { 646 t.Fatal("Migrate function shouldn't be called!") 647 return nil, nil 648 } 649 650 s := &terraform.InstanceState{ 651 ID: "bar", 652 Attributes: map[string]string{ 653 "newfoo": "12", 654 }, 655 Meta: map[string]string{ 656 "schema_version": "2", 657 }, 658 } 659 660 actual, err := r.Refresh(s, nil) 661 if err != nil { 662 t.Fatalf("err: %s", err) 663 } 664 665 expected := &terraform.InstanceState{ 666 ID: "bar", 667 Attributes: map[string]string{ 668 "id": "bar", 669 "newfoo": "13", 670 }, 671 Meta: map[string]string{ 672 "schema_version": "2", 673 }, 674 } 675 676 if !reflect.DeepEqual(actual, expected) { 677 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 678 } 679 } 680 681 func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) { 682 r := &Resource{ 683 // Version 1 > Version 0 684 SchemaVersion: 1, 685 Schema: map[string]*Schema{ 686 "newfoo": &Schema{ 687 Type: TypeInt, 688 Optional: true, 689 }, 690 }, 691 } 692 693 r.Read = func(d *ResourceData, m interface{}) error { 694 return d.Set("newfoo", d.Get("newfoo").(int)+1) 695 } 696 697 r.MigrateState = func( 698 v int, 699 s *terraform.InstanceState, 700 meta interface{}) (*terraform.InstanceState, error) { 701 s.Attributes["newfoo"] = s.Attributes["oldfoo"] 702 return s, nil 703 } 704 705 s := &terraform.InstanceState{ 706 ID: "bar", 707 Attributes: map[string]string{ 708 "oldfoo": "12", 709 }, 710 } 711 712 actual, err := r.Refresh(s, nil) 713 if err != nil { 714 t.Fatalf("err: %s", err) 715 } 716 717 expected := &terraform.InstanceState{ 718 ID: "bar", 719 Attributes: map[string]string{ 720 "id": "bar", 721 "newfoo": "13", 722 }, 723 Meta: map[string]string{ 724 "schema_version": "1", 725 }, 726 } 727 728 if !reflect.DeepEqual(actual, expected) { 729 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 730 } 731 } 732 733 func TestResourceRefresh_migrateStateErr(t *testing.T) { 734 r := &Resource{ 735 SchemaVersion: 2, 736 Schema: map[string]*Schema{ 737 "newfoo": &Schema{ 738 Type: TypeInt, 739 Optional: true, 740 }, 741 }, 742 } 743 744 r.Read = func(d *ResourceData, m interface{}) error { 745 t.Fatal("Read should never be called!") 746 return nil 747 } 748 749 r.MigrateState = func( 750 v int, 751 s *terraform.InstanceState, 752 meta interface{}) (*terraform.InstanceState, error) { 753 return s, fmt.Errorf("triggering an error") 754 } 755 756 s := &terraform.InstanceState{ 757 ID: "bar", 758 Attributes: map[string]string{ 759 "oldfoo": "12", 760 }, 761 } 762 763 _, err := r.Refresh(s, nil) 764 if err == nil { 765 t.Fatal("expected error, but got none!") 766 } 767 }