github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/apply_destroy_test.go (about) 1 package command 2 3 import ( 4 "os" 5 "strings" 6 "testing" 7 8 "github.com/davecgh/go-spew/spew" 9 "github.com/mitchellh/cli" 10 "github.com/zclconf/go-cty/cty" 11 12 "github.com/hashicorp/terraform/internal/addrs" 13 "github.com/hashicorp/terraform/internal/configs/configschema" 14 "github.com/hashicorp/terraform/internal/providers" 15 "github.com/hashicorp/terraform/internal/states" 16 "github.com/hashicorp/terraform/internal/states/statefile" 17 ) 18 19 func TestApply_destroy(t *testing.T) { 20 // Create a temporary working directory that is empty 21 td := t.TempDir() 22 testCopyDir(t, testFixturePath("apply"), td) 23 defer testChdir(t, td)() 24 25 originalState := states.BuildState(func(s *states.SyncState) { 26 s.SetResourceInstanceCurrent( 27 addrs.Resource{ 28 Mode: addrs.ManagedResourceMode, 29 Type: "test_instance", 30 Name: "foo", 31 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 32 &states.ResourceInstanceObjectSrc{ 33 AttrsJSON: []byte(`{"id":"bar"}`), 34 Status: states.ObjectReady, 35 }, 36 addrs.AbsProviderConfig{ 37 Provider: addrs.NewDefaultProvider("test"), 38 Module: addrs.RootModule, 39 }, 40 ) 41 }) 42 statePath := testStateFile(t, originalState) 43 44 p := testProvider() 45 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 46 ResourceTypes: map[string]providers.Schema{ 47 "test_instance": { 48 Block: &configschema.Block{ 49 Attributes: map[string]*configschema.Attribute{ 50 "id": {Type: cty.String, Computed: true}, 51 "ami": {Type: cty.String, Optional: true}, 52 }, 53 }, 54 }, 55 }, 56 } 57 58 view, done := testView(t) 59 c := &ApplyCommand{ 60 Destroy: true, 61 Meta: Meta{ 62 testingOverrides: metaOverridesForProvider(p), 63 View: view, 64 }, 65 } 66 67 // Run the apply command pointing to our existing state 68 args := []string{ 69 "-auto-approve", 70 "-state", statePath, 71 } 72 code := c.Run(args) 73 output := done(t) 74 if code != 0 { 75 t.Log(output.Stdout()) 76 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 77 } 78 79 // Verify a new state exists 80 if _, err := os.Stat(statePath); err != nil { 81 t.Fatalf("err: %s", err) 82 } 83 84 f, err := os.Open(statePath) 85 if err != nil { 86 t.Fatalf("err: %s", err) 87 } 88 defer f.Close() 89 90 stateFile, err := statefile.Read(f) 91 if err != nil { 92 t.Fatalf("err: %s", err) 93 } 94 if stateFile.State == nil { 95 t.Fatal("state should not be nil") 96 } 97 98 actualStr := strings.TrimSpace(stateFile.State.String()) 99 expectedStr := strings.TrimSpace(testApplyDestroyStr) 100 if actualStr != expectedStr { 101 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 102 } 103 104 // Should have a backup file 105 f, err = os.Open(statePath + DefaultBackupExtension) 106 if err != nil { 107 t.Fatalf("err: %s", err) 108 } 109 110 backupStateFile, err := statefile.Read(f) 111 f.Close() 112 if err != nil { 113 t.Fatalf("err: %s", err) 114 } 115 116 actualStr = strings.TrimSpace(backupStateFile.State.String()) 117 expectedStr = strings.TrimSpace(originalState.String()) 118 if actualStr != expectedStr { 119 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 120 } 121 } 122 123 func TestApply_destroyApproveNo(t *testing.T) { 124 // Create a temporary working directory that is empty 125 td := t.TempDir() 126 testCopyDir(t, testFixturePath("apply"), td) 127 defer testChdir(t, td)() 128 129 // Create some existing state 130 originalState := states.BuildState(func(s *states.SyncState) { 131 s.SetResourceInstanceCurrent( 132 addrs.Resource{ 133 Mode: addrs.ManagedResourceMode, 134 Type: "test_instance", 135 Name: "foo", 136 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 137 &states.ResourceInstanceObjectSrc{ 138 AttrsJSON: []byte(`{"id":"bar"}`), 139 Status: states.ObjectReady, 140 }, 141 addrs.AbsProviderConfig{ 142 Provider: addrs.NewDefaultProvider("test"), 143 Module: addrs.RootModule, 144 }, 145 ) 146 }) 147 statePath := testStateFile(t, originalState) 148 149 p := applyFixtureProvider() 150 151 defer testInputMap(t, map[string]string{ 152 "approve": "no", 153 })() 154 155 // Do not use the NewMockUi initializer here, as we want to delay 156 // the call to init until after setting up the input mocks 157 ui := new(cli.MockUi) 158 view, done := testView(t) 159 c := &ApplyCommand{ 160 Destroy: true, 161 Meta: Meta{ 162 testingOverrides: metaOverridesForProvider(p), 163 Ui: ui, 164 View: view, 165 }, 166 } 167 168 args := []string{ 169 "-state", statePath, 170 } 171 code := c.Run(args) 172 output := done(t) 173 if code != 1 { 174 t.Fatalf("bad: %d\n\n%s", code, output.Stdout()) 175 } 176 if got, want := output.Stdout(), "Destroy cancelled"; !strings.Contains(got, want) { 177 t.Fatalf("expected output to include %q, but was:\n%s", want, got) 178 } 179 180 state := testStateRead(t, statePath) 181 if state == nil { 182 t.Fatal("state should not be nil") 183 } 184 actualStr := strings.TrimSpace(state.String()) 185 expectedStr := strings.TrimSpace(originalState.String()) 186 if actualStr != expectedStr { 187 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 188 } 189 } 190 191 func TestApply_destroyApproveYes(t *testing.T) { 192 // Create a temporary working directory that is empty 193 td := t.TempDir() 194 testCopyDir(t, testFixturePath("apply"), td) 195 defer testChdir(t, td)() 196 197 // Create some existing state 198 originalState := states.BuildState(func(s *states.SyncState) { 199 s.SetResourceInstanceCurrent( 200 addrs.Resource{ 201 Mode: addrs.ManagedResourceMode, 202 Type: "test_instance", 203 Name: "foo", 204 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 205 &states.ResourceInstanceObjectSrc{ 206 AttrsJSON: []byte(`{"id":"bar"}`), 207 Status: states.ObjectReady, 208 }, 209 addrs.AbsProviderConfig{ 210 Provider: addrs.NewDefaultProvider("test"), 211 Module: addrs.RootModule, 212 }, 213 ) 214 }) 215 statePath := testStateFile(t, originalState) 216 217 p := applyFixtureProvider() 218 219 defer testInputMap(t, map[string]string{ 220 "approve": "yes", 221 })() 222 223 // Do not use the NewMockUi initializer here, as we want to delay 224 // the call to init until after setting up the input mocks 225 ui := new(cli.MockUi) 226 view, done := testView(t) 227 c := &ApplyCommand{ 228 Destroy: true, 229 Meta: Meta{ 230 testingOverrides: metaOverridesForProvider(p), 231 Ui: ui, 232 View: view, 233 }, 234 } 235 236 args := []string{ 237 "-state", statePath, 238 } 239 code := c.Run(args) 240 output := done(t) 241 if code != 0 { 242 t.Log(output.Stdout()) 243 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 244 } 245 246 if _, err := os.Stat(statePath); err != nil { 247 t.Fatalf("err: %s", err) 248 } 249 250 state := testStateRead(t, statePath) 251 if state == nil { 252 t.Fatal("state should not be nil") 253 } 254 255 actualStr := strings.TrimSpace(state.String()) 256 expectedStr := strings.TrimSpace(testApplyDestroyStr) 257 if actualStr != expectedStr { 258 t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) 259 } 260 } 261 262 func TestApply_destroyLockedState(t *testing.T) { 263 // Create a temporary working directory that is empty 264 td := t.TempDir() 265 testCopyDir(t, testFixturePath("apply"), td) 266 defer testChdir(t, td)() 267 268 originalState := states.BuildState(func(s *states.SyncState) { 269 s.SetResourceInstanceCurrent( 270 addrs.Resource{ 271 Mode: addrs.ManagedResourceMode, 272 Type: "test_instance", 273 Name: "foo", 274 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 275 &states.ResourceInstanceObjectSrc{ 276 AttrsJSON: []byte(`{"id":"bar"}`), 277 Status: states.ObjectReady, 278 }, 279 addrs.AbsProviderConfig{ 280 Provider: addrs.NewDefaultProvider("test"), 281 Module: addrs.RootModule, 282 }, 283 ) 284 }) 285 statePath := testStateFile(t, originalState) 286 287 unlock, err := testLockState(t, testDataDir, statePath) 288 if err != nil { 289 t.Fatal(err) 290 } 291 defer unlock() 292 293 p := testProvider() 294 view, done := testView(t) 295 c := &ApplyCommand{ 296 Destroy: true, 297 Meta: Meta{ 298 testingOverrides: metaOverridesForProvider(p), 299 View: view, 300 }, 301 } 302 303 // Run the apply command pointing to our existing state 304 args := []string{ 305 "-auto-approve", 306 "-state", statePath, 307 } 308 309 code := c.Run(args) 310 output := done(t) 311 if code == 0 { 312 t.Fatalf("bad: %d\n\n%s", code, output.Stdout()) 313 } 314 315 if !strings.Contains(output.Stderr(), "lock") { 316 t.Fatal("command output does not look like a lock error:", output.Stderr()) 317 } 318 } 319 320 func TestApply_destroyPlan(t *testing.T) { 321 // Create a temporary working directory that is empty 322 td := t.TempDir() 323 testCopyDir(t, testFixturePath("apply"), td) 324 defer testChdir(t, td)() 325 326 planPath := testPlanFileNoop(t) 327 328 p := testProvider() 329 view, done := testView(t) 330 c := &ApplyCommand{ 331 Destroy: true, 332 Meta: Meta{ 333 testingOverrides: metaOverridesForProvider(p), 334 View: view, 335 }, 336 } 337 338 // Run the apply command pointing to our existing state 339 args := []string{ 340 planPath, 341 } 342 code := c.Run(args) 343 output := done(t) 344 if code != 1 { 345 t.Fatalf("bad: %d\n\n%s", code, output.Stdout()) 346 } 347 if !strings.Contains(output.Stderr(), "plan file") { 348 t.Fatal("expected command output to refer to plan file, but got:", output.Stderr()) 349 } 350 } 351 352 func TestApply_destroyPath(t *testing.T) { 353 // Create a temporary working directory that is empty 354 td := t.TempDir() 355 testCopyDir(t, testFixturePath("apply"), td) 356 defer testChdir(t, td)() 357 358 p := applyFixtureProvider() 359 360 view, done := testView(t) 361 c := &ApplyCommand{ 362 Destroy: true, 363 Meta: Meta{ 364 testingOverrides: metaOverridesForProvider(p), 365 View: view, 366 }, 367 } 368 369 args := []string{ 370 "-auto-approve", 371 testFixturePath("apply"), 372 } 373 code := c.Run(args) 374 output := done(t) 375 if code != 1 { 376 t.Fatalf("bad: %d\n\n%s", code, output.Stdout()) 377 } 378 if !strings.Contains(output.Stderr(), "-chdir") { 379 t.Fatal("expected command output to refer to -chdir flag, but got:", output.Stderr()) 380 } 381 } 382 383 // Config with multiple resources with dependencies, targeting destroy of a 384 // root node, expecting all other resources to be destroyed due to 385 // dependencies. 386 func TestApply_destroyTargetedDependencies(t *testing.T) { 387 // Create a temporary working directory that is empty 388 td := t.TempDir() 389 testCopyDir(t, testFixturePath("apply-destroy-targeted"), td) 390 defer testChdir(t, td)() 391 392 originalState := states.BuildState(func(s *states.SyncState) { 393 s.SetResourceInstanceCurrent( 394 addrs.Resource{ 395 Mode: addrs.ManagedResourceMode, 396 Type: "test_instance", 397 Name: "foo", 398 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 399 &states.ResourceInstanceObjectSrc{ 400 AttrsJSON: []byte(`{"id":"i-ab123"}`), 401 Status: states.ObjectReady, 402 }, 403 addrs.AbsProviderConfig{ 404 Provider: addrs.NewDefaultProvider("test"), 405 Module: addrs.RootModule, 406 }, 407 ) 408 s.SetResourceInstanceCurrent( 409 addrs.Resource{ 410 Mode: addrs.ManagedResourceMode, 411 Type: "test_load_balancer", 412 Name: "foo", 413 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 414 &states.ResourceInstanceObjectSrc{ 415 AttrsJSON: []byte(`{"id":"i-abc123"}`), 416 Dependencies: []addrs.ConfigResource{mustResourceAddr("test_instance.foo")}, 417 Status: states.ObjectReady, 418 }, 419 addrs.AbsProviderConfig{ 420 Provider: addrs.NewDefaultProvider("test"), 421 Module: addrs.RootModule, 422 }, 423 ) 424 }) 425 statePath := testStateFile(t, originalState) 426 427 p := testProvider() 428 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 429 ResourceTypes: map[string]providers.Schema{ 430 "test_instance": { 431 Block: &configschema.Block{ 432 Attributes: map[string]*configschema.Attribute{ 433 "id": {Type: cty.String, Computed: true}, 434 }, 435 }, 436 }, 437 "test_load_balancer": { 438 Block: &configschema.Block{ 439 Attributes: map[string]*configschema.Attribute{ 440 "id": {Type: cty.String, Computed: true}, 441 "instances": {Type: cty.List(cty.String), Optional: true}, 442 }, 443 }, 444 }, 445 }, 446 } 447 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 448 return providers.PlanResourceChangeResponse{ 449 PlannedState: req.ProposedNewState, 450 } 451 } 452 453 view, done := testView(t) 454 c := &ApplyCommand{ 455 Destroy: true, 456 Meta: Meta{ 457 testingOverrides: metaOverridesForProvider(p), 458 View: view, 459 }, 460 } 461 462 // Run the apply command pointing to our existing state 463 args := []string{ 464 "-auto-approve", 465 "-target", "test_instance.foo", 466 "-state", statePath, 467 } 468 code := c.Run(args) 469 output := done(t) 470 if code != 0 { 471 t.Log(output.Stdout()) 472 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 473 } 474 475 // Verify a new state exists 476 if _, err := os.Stat(statePath); err != nil { 477 t.Fatalf("err: %s", err) 478 } 479 480 f, err := os.Open(statePath) 481 if err != nil { 482 t.Fatalf("err: %s", err) 483 } 484 defer f.Close() 485 486 stateFile, err := statefile.Read(f) 487 if err != nil { 488 t.Fatalf("err: %s", err) 489 } 490 if stateFile == nil || stateFile.State == nil { 491 t.Fatal("state should not be nil") 492 } 493 494 spew.Config.DisableMethods = true 495 if !stateFile.State.Empty() { 496 t.Fatalf("unexpected final state\ngot: %s\nwant: empty state", spew.Sdump(stateFile.State)) 497 } 498 499 // Should have a backup file 500 f, err = os.Open(statePath + DefaultBackupExtension) 501 if err != nil { 502 t.Fatalf("err: %s", err) 503 } 504 505 backupStateFile, err := statefile.Read(f) 506 f.Close() 507 if err != nil { 508 t.Fatalf("err: %s", err) 509 } 510 511 actualStr := strings.TrimSpace(backupStateFile.State.String()) 512 expectedStr := strings.TrimSpace(originalState.String()) 513 if actualStr != expectedStr { 514 t.Fatalf("bad:\n\nactual:\n%s\n\nexpected:\nb%s", actualStr, expectedStr) 515 } 516 } 517 518 // Config with multiple resources with dependencies, targeting destroy of a 519 // leaf node, expecting the other resources to remain. 520 func TestApply_destroyTargeted(t *testing.T) { 521 // Create a temporary working directory that is empty 522 td := t.TempDir() 523 testCopyDir(t, testFixturePath("apply-destroy-targeted"), td) 524 defer testChdir(t, td)() 525 526 originalState := states.BuildState(func(s *states.SyncState) { 527 s.SetResourceInstanceCurrent( 528 addrs.Resource{ 529 Mode: addrs.ManagedResourceMode, 530 Type: "test_instance", 531 Name: "foo", 532 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 533 &states.ResourceInstanceObjectSrc{ 534 AttrsJSON: []byte(`{"id":"i-ab123"}`), 535 Status: states.ObjectReady, 536 }, 537 addrs.AbsProviderConfig{ 538 Provider: addrs.NewDefaultProvider("test"), 539 Module: addrs.RootModule, 540 }, 541 ) 542 s.SetResourceInstanceCurrent( 543 addrs.Resource{ 544 Mode: addrs.ManagedResourceMode, 545 Type: "test_load_balancer", 546 Name: "foo", 547 }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), 548 &states.ResourceInstanceObjectSrc{ 549 AttrsJSON: []byte(`{"id":"i-abc123"}`), 550 Dependencies: []addrs.ConfigResource{mustResourceAddr("test_instance.foo")}, 551 Status: states.ObjectReady, 552 }, 553 addrs.AbsProviderConfig{ 554 Provider: addrs.NewDefaultProvider("test"), 555 Module: addrs.RootModule, 556 }, 557 ) 558 }) 559 wantState := states.BuildState(func(s *states.SyncState) { 560 s.SetResourceInstanceCurrent( 561 addrs.Resource{ 562 Mode: addrs.ManagedResourceMode, 563 Type: "test_instance", 564 Name: "foo", 565 }.Instance(addrs.IntKey(0)).Absolute(addrs.RootModuleInstance), 566 &states.ResourceInstanceObjectSrc{ 567 AttrsJSON: []byte(`{"id":"i-ab123"}`), 568 Status: states.ObjectReady, 569 }, 570 addrs.AbsProviderConfig{ 571 Provider: addrs.NewDefaultProvider("test"), 572 Module: addrs.RootModule, 573 }, 574 ) 575 }) 576 statePath := testStateFile(t, originalState) 577 578 p := testProvider() 579 p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{ 580 ResourceTypes: map[string]providers.Schema{ 581 "test_instance": { 582 Block: &configschema.Block{ 583 Attributes: map[string]*configschema.Attribute{ 584 "id": {Type: cty.String, Computed: true}, 585 }, 586 }, 587 }, 588 "test_load_balancer": { 589 Block: &configschema.Block{ 590 Attributes: map[string]*configschema.Attribute{ 591 "id": {Type: cty.String, Computed: true}, 592 "instances": {Type: cty.List(cty.String), Optional: true}, 593 }, 594 }, 595 }, 596 }, 597 } 598 p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse { 599 return providers.PlanResourceChangeResponse{ 600 PlannedState: req.ProposedNewState, 601 } 602 } 603 604 view, done := testView(t) 605 c := &ApplyCommand{ 606 Destroy: true, 607 Meta: Meta{ 608 testingOverrides: metaOverridesForProvider(p), 609 View: view, 610 }, 611 } 612 613 // Run the apply command pointing to our existing state 614 args := []string{ 615 "-auto-approve", 616 "-target", "test_load_balancer.foo", 617 "-state", statePath, 618 } 619 code := c.Run(args) 620 output := done(t) 621 if code != 0 { 622 t.Log(output.Stdout()) 623 t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) 624 } 625 626 // Verify a new state exists 627 if _, err := os.Stat(statePath); err != nil { 628 t.Fatalf("err: %s", err) 629 } 630 631 f, err := os.Open(statePath) 632 if err != nil { 633 t.Fatalf("err: %s", err) 634 } 635 defer f.Close() 636 637 stateFile, err := statefile.Read(f) 638 if err != nil { 639 t.Fatalf("err: %s", err) 640 } 641 if stateFile == nil || stateFile.State == nil { 642 t.Fatal("state should not be nil") 643 } 644 645 actualStr := strings.TrimSpace(stateFile.State.String()) 646 expectedStr := strings.TrimSpace(wantState.String()) 647 if actualStr != expectedStr { 648 t.Fatalf("bad:\n\nactual:\n%s\n\nexpected:\nb%s", actualStr, expectedStr) 649 } 650 651 // Should have a backup file 652 f, err = os.Open(statePath + DefaultBackupExtension) 653 if err != nil { 654 t.Fatalf("err: %s", err) 655 } 656 657 backupStateFile, err := statefile.Read(f) 658 f.Close() 659 if err != nil { 660 t.Fatalf("err: %s", err) 661 } 662 663 backupActualStr := strings.TrimSpace(backupStateFile.State.String()) 664 backupExpectedStr := strings.TrimSpace(originalState.String()) 665 if backupActualStr != backupExpectedStr { 666 t.Fatalf("bad:\n\nactual:\n%s\n\nexpected:\nb%s", backupActualStr, backupExpectedStr) 667 } 668 } 669 670 const testApplyDestroyStr = ` 671 <no state> 672 `