github.com/acm1/terraform@v0.6.2-0.20150729164239-1f314444f45c/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 } 184 185 r.Delete = func(d *ResourceData, m interface{}) error { 186 d.Set("foo", 42) 187 return fmt.Errorf("some error") 188 } 189 190 s := &terraform.InstanceState{ 191 ID: "bar", 192 Attributes: map[string]string{ 193 "foo": "12", 194 }, 195 } 196 197 d := &terraform.InstanceDiff{ 198 Destroy: true, 199 } 200 201 actual, err := r.Apply(s, d, nil) 202 if err == nil { 203 t.Fatal("should error") 204 } 205 206 expected := &terraform.InstanceState{ 207 ID: "bar", 208 Attributes: map[string]string{ 209 "id": "bar", 210 "foo": "42", 211 }, 212 } 213 214 if !reflect.DeepEqual(actual, expected) { 215 t.Fatalf("bad: %#v", actual) 216 } 217 } 218 219 func TestResourceApply_update(t *testing.T) { 220 r := &Resource{ 221 Schema: map[string]*Schema{ 222 "foo": &Schema{ 223 Type: TypeInt, 224 Optional: true, 225 }, 226 }, 227 } 228 229 r.Update = func(d *ResourceData, m interface{}) error { 230 d.Set("foo", 42) 231 return nil 232 } 233 234 s := &terraform.InstanceState{ 235 ID: "foo", 236 Attributes: map[string]string{ 237 "foo": "12", 238 }, 239 } 240 241 d := &terraform.InstanceDiff{ 242 Attributes: map[string]*terraform.ResourceAttrDiff{ 243 "foo": &terraform.ResourceAttrDiff{ 244 New: "13", 245 }, 246 }, 247 } 248 249 actual, err := r.Apply(s, d, nil) 250 if err != nil { 251 t.Fatalf("err: %s", err) 252 } 253 254 expected := &terraform.InstanceState{ 255 ID: "foo", 256 Attributes: map[string]string{ 257 "id": "foo", 258 "foo": "42", 259 }, 260 } 261 262 if !reflect.DeepEqual(actual, expected) { 263 t.Fatalf("bad: %#v", actual) 264 } 265 } 266 267 func TestResourceApply_updateNoCallback(t *testing.T) { 268 r := &Resource{ 269 Schema: map[string]*Schema{ 270 "foo": &Schema{ 271 Type: TypeInt, 272 Optional: true, 273 }, 274 }, 275 } 276 277 r.Update = nil 278 279 s := &terraform.InstanceState{ 280 ID: "foo", 281 Attributes: map[string]string{ 282 "foo": "12", 283 }, 284 } 285 286 d := &terraform.InstanceDiff{ 287 Attributes: map[string]*terraform.ResourceAttrDiff{ 288 "foo": &terraform.ResourceAttrDiff{ 289 New: "13", 290 }, 291 }, 292 } 293 294 actual, err := r.Apply(s, d, nil) 295 if err == nil { 296 t.Fatal("should error") 297 } 298 299 expected := &terraform.InstanceState{ 300 ID: "foo", 301 Attributes: map[string]string{ 302 "foo": "12", 303 }, 304 } 305 306 if !reflect.DeepEqual(actual, expected) { 307 t.Fatalf("bad: %#v", actual) 308 } 309 } 310 311 func TestResourceInternalValidate(t *testing.T) { 312 cases := []struct { 313 In *Resource 314 Err bool 315 }{ 316 { 317 nil, 318 true, 319 }, 320 321 // No optional and no required 322 { 323 &Resource{ 324 Schema: map[string]*Schema{ 325 "foo": &Schema{ 326 Type: TypeInt, 327 Optional: true, 328 Required: true, 329 }, 330 }, 331 }, 332 true, 333 }, 334 } 335 336 for i, tc := range cases { 337 err := tc.In.InternalValidate(schemaMap{}) 338 if (err != nil) != tc.Err { 339 t.Fatalf("%d: bad: %s", i, err) 340 } 341 } 342 } 343 344 func TestResourceRefresh(t *testing.T) { 345 r := &Resource{ 346 SchemaVersion: 2, 347 Schema: map[string]*Schema{ 348 "foo": &Schema{ 349 Type: TypeInt, 350 Optional: true, 351 }, 352 }, 353 } 354 355 r.Read = func(d *ResourceData, m interface{}) error { 356 if m != 42 { 357 return fmt.Errorf("meta not passed") 358 } 359 360 return d.Set("foo", d.Get("foo").(int)+1) 361 } 362 363 s := &terraform.InstanceState{ 364 ID: "bar", 365 Attributes: map[string]string{ 366 "foo": "12", 367 }, 368 } 369 370 expected := &terraform.InstanceState{ 371 ID: "bar", 372 Attributes: map[string]string{ 373 "id": "bar", 374 "foo": "13", 375 }, 376 Meta: map[string]string{ 377 "schema_version": "2", 378 }, 379 } 380 381 actual, err := r.Refresh(s, 42) 382 if err != nil { 383 t.Fatalf("err: %s", err) 384 } 385 386 if !reflect.DeepEqual(actual, expected) { 387 t.Fatalf("bad: %#v", actual) 388 } 389 } 390 391 func TestResourceRefresh_blankId(t *testing.T) { 392 r := &Resource{ 393 Schema: map[string]*Schema{ 394 "foo": &Schema{ 395 Type: TypeInt, 396 Optional: true, 397 }, 398 }, 399 } 400 401 r.Read = func(d *ResourceData, m interface{}) error { 402 d.SetId("foo") 403 return nil 404 } 405 406 s := &terraform.InstanceState{ 407 ID: "", 408 Attributes: map[string]string{}, 409 } 410 411 actual, err := r.Refresh(s, 42) 412 if err != nil { 413 t.Fatalf("err: %s", err) 414 } 415 if actual != nil { 416 t.Fatalf("bad: %#v", actual) 417 } 418 } 419 420 func TestResourceRefresh_delete(t *testing.T) { 421 r := &Resource{ 422 Schema: map[string]*Schema{ 423 "foo": &Schema{ 424 Type: TypeInt, 425 Optional: true, 426 }, 427 }, 428 } 429 430 r.Read = func(d *ResourceData, m interface{}) error { 431 d.SetId("") 432 return nil 433 } 434 435 s := &terraform.InstanceState{ 436 ID: "bar", 437 Attributes: map[string]string{ 438 "foo": "12", 439 }, 440 } 441 442 actual, err := r.Refresh(s, 42) 443 if err != nil { 444 t.Fatalf("err: %s", err) 445 } 446 447 if actual != nil { 448 t.Fatalf("bad: %#v", actual) 449 } 450 } 451 452 func TestResourceRefresh_existsError(t *testing.T) { 453 r := &Resource{ 454 Schema: map[string]*Schema{ 455 "foo": &Schema{ 456 Type: TypeInt, 457 Optional: true, 458 }, 459 }, 460 } 461 462 r.Exists = func(*ResourceData, interface{}) (bool, error) { 463 return false, fmt.Errorf("error") 464 } 465 466 r.Read = func(d *ResourceData, m interface{}) error { 467 panic("shouldn't be called") 468 } 469 470 s := &terraform.InstanceState{ 471 ID: "bar", 472 Attributes: map[string]string{ 473 "foo": "12", 474 }, 475 } 476 477 actual, err := r.Refresh(s, 42) 478 if err == nil { 479 t.Fatalf("should error") 480 } 481 if !reflect.DeepEqual(actual, s) { 482 t.Fatalf("bad: %#v", actual) 483 } 484 } 485 486 func TestResourceRefresh_noExists(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, nil 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("err: %s", err) 514 } 515 if actual != nil { 516 t.Fatalf("should have no state") 517 } 518 } 519 520 func TestResourceRefresh_needsMigration(t *testing.T) { 521 // Schema v2 it deals only in newfoo, which tracks foo as an int 522 r := &Resource{ 523 SchemaVersion: 2, 524 Schema: map[string]*Schema{ 525 "newfoo": &Schema{ 526 Type: TypeInt, 527 Optional: true, 528 }, 529 }, 530 } 531 532 r.Read = func(d *ResourceData, m interface{}) error { 533 return d.Set("newfoo", d.Get("newfoo").(int)+1) 534 } 535 536 r.MigrateState = func( 537 v int, 538 s *terraform.InstanceState, 539 meta interface{}) (*terraform.InstanceState, error) { 540 // Real state migration functions will probably switch on this value, 541 // but we'll just assert on it for now. 542 if v != 1 { 543 t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v) 544 } 545 546 if meta != 42 { 547 t.Fatal("Expected meta to be passed through to the migration function") 548 } 549 550 oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64) 551 if err != nil { 552 t.Fatalf("err: %#v", err) 553 } 554 s.Attributes["newfoo"] = strconv.Itoa((int(oldfoo * 10))) 555 delete(s.Attributes, "oldfoo") 556 557 return s, nil 558 } 559 560 // State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th 561 // the scale of newfoo 562 s := &terraform.InstanceState{ 563 ID: "bar", 564 Attributes: map[string]string{ 565 "oldfoo": "1.2", 566 }, 567 Meta: map[string]string{ 568 "schema_version": "1", 569 }, 570 } 571 572 actual, err := r.Refresh(s, 42) 573 if err != nil { 574 t.Fatalf("err: %s", err) 575 } 576 577 expected := &terraform.InstanceState{ 578 ID: "bar", 579 Attributes: map[string]string{ 580 "id": "bar", 581 "newfoo": "13", 582 }, 583 Meta: map[string]string{ 584 "schema_version": "2", 585 }, 586 } 587 588 if !reflect.DeepEqual(actual, expected) { 589 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 590 } 591 } 592 593 func TestResourceRefresh_noMigrationNeeded(t *testing.T) { 594 r := &Resource{ 595 SchemaVersion: 2, 596 Schema: map[string]*Schema{ 597 "newfoo": &Schema{ 598 Type: TypeInt, 599 Optional: true, 600 }, 601 }, 602 } 603 604 r.Read = func(d *ResourceData, m interface{}) error { 605 return d.Set("newfoo", d.Get("newfoo").(int)+1) 606 } 607 608 r.MigrateState = func( 609 v int, 610 s *terraform.InstanceState, 611 meta interface{}) (*terraform.InstanceState, error) { 612 t.Fatal("Migrate function shouldn't be called!") 613 return nil, nil 614 } 615 616 s := &terraform.InstanceState{ 617 ID: "bar", 618 Attributes: map[string]string{ 619 "newfoo": "12", 620 }, 621 Meta: map[string]string{ 622 "schema_version": "2", 623 }, 624 } 625 626 actual, err := r.Refresh(s, nil) 627 if err != nil { 628 t.Fatalf("err: %s", err) 629 } 630 631 expected := &terraform.InstanceState{ 632 ID: "bar", 633 Attributes: map[string]string{ 634 "id": "bar", 635 "newfoo": "13", 636 }, 637 Meta: map[string]string{ 638 "schema_version": "2", 639 }, 640 } 641 642 if !reflect.DeepEqual(actual, expected) { 643 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 644 } 645 } 646 647 func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) { 648 r := &Resource{ 649 // Version 1 > Version 0 650 SchemaVersion: 1, 651 Schema: map[string]*Schema{ 652 "newfoo": &Schema{ 653 Type: TypeInt, 654 Optional: true, 655 }, 656 }, 657 } 658 659 r.Read = func(d *ResourceData, m interface{}) error { 660 return d.Set("newfoo", d.Get("newfoo").(int)+1) 661 } 662 663 r.MigrateState = func( 664 v int, 665 s *terraform.InstanceState, 666 meta interface{}) (*terraform.InstanceState, error) { 667 s.Attributes["newfoo"] = s.Attributes["oldfoo"] 668 return s, nil 669 } 670 671 s := &terraform.InstanceState{ 672 ID: "bar", 673 Attributes: map[string]string{ 674 "oldfoo": "12", 675 }, 676 } 677 678 actual, err := r.Refresh(s, nil) 679 if err != nil { 680 t.Fatalf("err: %s", err) 681 } 682 683 expected := &terraform.InstanceState{ 684 ID: "bar", 685 Attributes: map[string]string{ 686 "id": "bar", 687 "newfoo": "13", 688 }, 689 Meta: map[string]string{ 690 "schema_version": "1", 691 }, 692 } 693 694 if !reflect.DeepEqual(actual, expected) { 695 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 696 } 697 } 698 699 func TestResourceRefresh_migrateStateErr(t *testing.T) { 700 r := &Resource{ 701 SchemaVersion: 2, 702 Schema: map[string]*Schema{ 703 "newfoo": &Schema{ 704 Type: TypeInt, 705 Optional: true, 706 }, 707 }, 708 } 709 710 r.Read = func(d *ResourceData, m interface{}) error { 711 t.Fatal("Read should never be called!") 712 return nil 713 } 714 715 r.MigrateState = func( 716 v int, 717 s *terraform.InstanceState, 718 meta interface{}) (*terraform.InstanceState, error) { 719 return s, fmt.Errorf("triggering an error") 720 } 721 722 s := &terraform.InstanceState{ 723 ID: "bar", 724 Attributes: map[string]string{ 725 "oldfoo": "12", 726 }, 727 } 728 729 _, err := r.Refresh(s, nil) 730 if err == nil { 731 t.Fatal("expected error, but got none!") 732 } 733 }