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