github.com/ndarilek/terraform@v0.3.8-0.20150320140257-d3135c1b2bac/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() 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_delete(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("") 403 return nil 404 } 405 406 s := &terraform.InstanceState{ 407 ID: "bar", 408 Attributes: map[string]string{ 409 "foo": "12", 410 }, 411 } 412 413 actual, err := r.Refresh(s, 42) 414 if err != nil { 415 t.Fatalf("err: %s", err) 416 } 417 418 if actual != nil { 419 t.Fatalf("bad: %#v", actual) 420 } 421 } 422 423 func TestResourceRefresh_existsError(t *testing.T) { 424 r := &Resource{ 425 Schema: map[string]*Schema{ 426 "foo": &Schema{ 427 Type: TypeInt, 428 Optional: true, 429 }, 430 }, 431 } 432 433 r.Exists = func(*ResourceData, interface{}) (bool, error) { 434 return false, fmt.Errorf("error") 435 } 436 437 r.Read = func(d *ResourceData, m interface{}) error { 438 panic("shouldn't be called") 439 } 440 441 s := &terraform.InstanceState{ 442 ID: "bar", 443 Attributes: map[string]string{ 444 "foo": "12", 445 }, 446 } 447 448 actual, err := r.Refresh(s, 42) 449 if err == nil { 450 t.Fatalf("should error") 451 } 452 if !reflect.DeepEqual(actual, s) { 453 t.Fatalf("bad: %#v", actual) 454 } 455 } 456 457 func TestResourceRefresh_noExists(t *testing.T) { 458 r := &Resource{ 459 Schema: map[string]*Schema{ 460 "foo": &Schema{ 461 Type: TypeInt, 462 Optional: true, 463 }, 464 }, 465 } 466 467 r.Exists = func(*ResourceData, interface{}) (bool, error) { 468 return false, nil 469 } 470 471 r.Read = func(d *ResourceData, m interface{}) error { 472 panic("shouldn't be called") 473 } 474 475 s := &terraform.InstanceState{ 476 ID: "bar", 477 Attributes: map[string]string{ 478 "foo": "12", 479 }, 480 } 481 482 actual, err := r.Refresh(s, 42) 483 if err != nil { 484 t.Fatalf("err: %s", err) 485 } 486 if actual != nil { 487 t.Fatalf("should have no state") 488 } 489 } 490 491 func TestResourceRefresh_needsMigration(t *testing.T) { 492 // Schema v2 it deals only in newfoo, which tracks foo as an int 493 r := &Resource{ 494 SchemaVersion: 2, 495 Schema: map[string]*Schema{ 496 "newfoo": &Schema{ 497 Type: TypeInt, 498 Optional: true, 499 }, 500 }, 501 } 502 503 r.Read = func(d *ResourceData, m interface{}) error { 504 return d.Set("newfoo", d.Get("newfoo").(int)+1) 505 } 506 507 r.MigrateState = func( 508 v int, 509 s *terraform.InstanceState, 510 meta interface{}) (*terraform.InstanceState, error) { 511 // Real state migration functions will probably switch on this value, 512 // but we'll just assert on it for now. 513 if v != 1 { 514 t.Fatalf("Expected StateSchemaVersion to be 1, got %d", v) 515 } 516 517 if meta != 42 { 518 t.Fatal("Expected meta to be passed through to the migration function") 519 } 520 521 oldfoo, err := strconv.ParseFloat(s.Attributes["oldfoo"], 64) 522 if err != nil { 523 t.Fatalf("err: %#v", err) 524 } 525 s.Attributes["newfoo"] = strconv.Itoa((int(oldfoo * 10))) 526 delete(s.Attributes, "oldfoo") 527 528 return s, nil 529 } 530 531 // State is v1 and deals in oldfoo, which tracked foo as a float at 1/10th 532 // the scale of newfoo 533 s := &terraform.InstanceState{ 534 ID: "bar", 535 Attributes: map[string]string{ 536 "oldfoo": "1.2", 537 }, 538 Meta: map[string]string{ 539 "schema_version": "1", 540 }, 541 } 542 543 actual, err := r.Refresh(s, 42) 544 if err != nil { 545 t.Fatalf("err: %s", err) 546 } 547 548 expected := &terraform.InstanceState{ 549 ID: "bar", 550 Attributes: map[string]string{ 551 "id": "bar", 552 "newfoo": "13", 553 }, 554 Meta: map[string]string{ 555 "schema_version": "2", 556 }, 557 } 558 559 if !reflect.DeepEqual(actual, expected) { 560 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 561 } 562 } 563 564 func TestResourceRefresh_noMigrationNeeded(t *testing.T) { 565 r := &Resource{ 566 SchemaVersion: 2, 567 Schema: map[string]*Schema{ 568 "newfoo": &Schema{ 569 Type: TypeInt, 570 Optional: true, 571 }, 572 }, 573 } 574 575 r.Read = func(d *ResourceData, m interface{}) error { 576 return d.Set("newfoo", d.Get("newfoo").(int)+1) 577 } 578 579 r.MigrateState = func( 580 v int, 581 s *terraform.InstanceState, 582 meta interface{}) (*terraform.InstanceState, error) { 583 t.Fatal("Migrate function shouldn't be called!") 584 return nil, nil 585 } 586 587 s := &terraform.InstanceState{ 588 ID: "bar", 589 Attributes: map[string]string{ 590 "newfoo": "12", 591 }, 592 Meta: map[string]string{ 593 "schema_version": "2", 594 }, 595 } 596 597 actual, err := r.Refresh(s, nil) 598 if err != nil { 599 t.Fatalf("err: %s", err) 600 } 601 602 expected := &terraform.InstanceState{ 603 ID: "bar", 604 Attributes: map[string]string{ 605 "id": "bar", 606 "newfoo": "13", 607 }, 608 Meta: map[string]string{ 609 "schema_version": "2", 610 }, 611 } 612 613 if !reflect.DeepEqual(actual, expected) { 614 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 615 } 616 } 617 618 func TestResourceRefresh_stateSchemaVersionUnset(t *testing.T) { 619 r := &Resource{ 620 // Version 1 > Version 0 621 SchemaVersion: 1, 622 Schema: map[string]*Schema{ 623 "newfoo": &Schema{ 624 Type: TypeInt, 625 Optional: true, 626 }, 627 }, 628 } 629 630 r.Read = func(d *ResourceData, m interface{}) error { 631 return d.Set("newfoo", d.Get("newfoo").(int)+1) 632 } 633 634 r.MigrateState = func( 635 v int, 636 s *terraform.InstanceState, 637 meta interface{}) (*terraform.InstanceState, error) { 638 s.Attributes["newfoo"] = s.Attributes["oldfoo"] 639 return s, nil 640 } 641 642 s := &terraform.InstanceState{ 643 ID: "bar", 644 Attributes: map[string]string{ 645 "oldfoo": "12", 646 }, 647 } 648 649 actual, err := r.Refresh(s, nil) 650 if err != nil { 651 t.Fatalf("err: %s", err) 652 } 653 654 expected := &terraform.InstanceState{ 655 ID: "bar", 656 Attributes: map[string]string{ 657 "id": "bar", 658 "newfoo": "13", 659 }, 660 Meta: map[string]string{ 661 "schema_version": "1", 662 }, 663 } 664 665 if !reflect.DeepEqual(actual, expected) { 666 t.Fatalf("bad:\n\nexpected: %#v\ngot: %#v", expected, actual) 667 } 668 } 669 670 func TestResourceRefresh_migrateStateErr(t *testing.T) { 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 t.Fatal("Read should never be called!") 683 return nil 684 } 685 686 r.MigrateState = func( 687 v int, 688 s *terraform.InstanceState, 689 meta interface{}) (*terraform.InstanceState, error) { 690 return s, fmt.Errorf("triggering an error") 691 } 692 693 s := &terraform.InstanceState{ 694 ID: "bar", 695 Attributes: map[string]string{ 696 "oldfoo": "12", 697 }, 698 } 699 700 _, err := r.Refresh(s, nil) 701 if err == nil { 702 t.Fatal("expected error, but got none!") 703 } 704 }