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