Skip to content

Commit d9efffe

Browse files
Copilotjosecelano
andcommitted
feat: fix destroy command to handle Created state gracefully
Co-authored-by: josecelano <58816+josecelano@users.noreply.github.com>
1 parent 37b6627 commit d9efffe

File tree

1 file changed

+137
-4
lines changed

1 file changed

+137
-4
lines changed

src/application/command_handlers/destroy.rs

Lines changed: 137 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -287,11 +287,23 @@ impl DestroyCommandHandler {
287287
> {
288288
use crate::domain::environment::state::DestroyStep;
289289

290-
// Step 1: Destroy infrastructure via OpenTofu
291-
Self::destroy_infrastructure(opentofu_client)
292-
.map_err(|e| (e, DestroyStep::DestroyInfrastructure))?;
290+
// Step 1: Conditionally destroy infrastructure via OpenTofu
291+
// Only attempt infrastructure destruction if infrastructure was provisioned
292+
if Self::should_destroy_infrastructure(environment) {
293+
info!(
294+
environment = %environment.name(),
295+
"Destroying provisioned infrastructure"
296+
);
297+
Self::destroy_infrastructure(opentofu_client)
298+
.map_err(|e| (e, DestroyStep::DestroyInfrastructure))?;
299+
} else {
300+
info!(
301+
environment = %environment.name(),
302+
"Skipping infrastructure destruction (environment was never provisioned)"
303+
);
304+
}
293305

294-
// Step 2: Clean up state files
306+
// Step 2: Clean up state files (always happens regardless of infrastructure state)
295307
Self::cleanup_state_files(environment).map_err(|e| (e, DestroyStep::CleanupStateFiles))?;
296308

297309
Ok(())
@@ -357,6 +369,28 @@ impl DestroyCommandHandler {
357369

358370
// Private helper methods
359371

372+
/// Check if infrastructure should be destroyed
373+
///
374+
/// Determines whether to attempt infrastructure destruction based on whether
375+
/// the `OpenTofu` build directory exists. If the directory doesn't exist, it means
376+
/// no infrastructure was ever provisioned (e.g., environment in Created state).
377+
///
378+
/// # Arguments
379+
///
380+
/// * `environment` - The environment being destroyed
381+
///
382+
/// # Returns
383+
///
384+
/// Returns `true` if infrastructure destruction should be attempted, `false` otherwise
385+
fn should_destroy_infrastructure(
386+
environment: &crate::domain::environment::Environment<
387+
crate::domain::environment::Destroying,
388+
>,
389+
) -> bool {
390+
let tofu_build_dir = environment.tofu_build_dir();
391+
tofu_build_dir.exists()
392+
}
393+
360394
/// Destroy the infrastructure using `OpenTofu`
361395
///
362396
/// Executes the `OpenTofu` destroy workflow to remove all managed infrastructure.
@@ -497,4 +531,103 @@ mod tests {
497531
let destroy_error: DestroyCommandHandlerError = command_error_direct.into();
498532
drop(destroy_error);
499533
}
534+
535+
#[test]
536+
fn it_should_skip_infrastructure_destruction_when_tofu_build_dir_does_not_exist() {
537+
use crate::domain::environment::testing::EnvironmentTestBuilder;
538+
539+
// Arrange: Create environment in Created state with no OpenTofu build directory
540+
let (created_env, _data_dir, _build_dir, _temp_dir) =
541+
EnvironmentTestBuilder::new().build_with_custom_paths();
542+
543+
// Transition to Destroying state
544+
let destroying_env = created_env.start_destroying();
545+
546+
// Verify tofu_build_dir does not exist
547+
assert!(
548+
!destroying_env.tofu_build_dir().exists(),
549+
"OpenTofu build directory should not exist for Created state"
550+
);
551+
552+
// Act: Check if infrastructure should be destroyed
553+
let should_destroy = DestroyCommandHandler::should_destroy_infrastructure(&destroying_env);
554+
555+
// Assert: Infrastructure destruction should be skipped
556+
assert!(
557+
!should_destroy,
558+
"Infrastructure destruction should be skipped when tofu_build_dir does not exist"
559+
);
560+
}
561+
562+
#[test]
563+
fn it_should_attempt_infrastructure_destruction_when_tofu_build_dir_exists() {
564+
use crate::domain::environment::testing::EnvironmentTestBuilder;
565+
566+
// Arrange: Create environment with OpenTofu build directory
567+
let (created_env, _data_dir, _build_dir, _temp_dir) =
568+
EnvironmentTestBuilder::new().build_with_custom_paths();
569+
570+
// Create the OpenTofu build directory to simulate provisioned state
571+
let tofu_build_dir = created_env.tofu_build_dir();
572+
std::fs::create_dir_all(&tofu_build_dir).expect("Failed to create tofu build dir");
573+
574+
// Transition to Destroying state
575+
let destroying_env = created_env.start_destroying();
576+
577+
// Verify tofu_build_dir exists
578+
assert!(
579+
destroying_env.tofu_build_dir().exists(),
580+
"OpenTofu build directory should exist for provisioned environment"
581+
);
582+
583+
// Act: Check if infrastructure should be destroyed
584+
let should_destroy = DestroyCommandHandler::should_destroy_infrastructure(&destroying_env);
585+
586+
// Assert: Infrastructure destruction should be attempted
587+
assert!(
588+
should_destroy,
589+
"Infrastructure destruction should be attempted when tofu_build_dir exists"
590+
);
591+
}
592+
593+
#[test]
594+
fn it_should_clean_up_state_files_regardless_of_infrastructure_state() {
595+
use crate::domain::environment::testing::EnvironmentTestBuilder;
596+
597+
// Arrange: Create environment with data and build directories
598+
let (created_env, data_dir, build_dir, _temp_dir) =
599+
EnvironmentTestBuilder::new().build_with_custom_paths();
600+
601+
// Create the directories
602+
std::fs::create_dir_all(&data_dir).expect("Failed to create data dir");
603+
std::fs::create_dir_all(&build_dir).expect("Failed to create build dir");
604+
605+
// Create some files in the directories
606+
std::fs::write(data_dir.join("environment.json"), "{}").expect("Failed to write file");
607+
std::fs::write(build_dir.join("test.txt"), "test").expect("Failed to write file");
608+
609+
// Verify directories exist before cleanup
610+
assert!(data_dir.exists(), "Data directory should exist");
611+
assert!(build_dir.exists(), "Build directory should exist");
612+
613+
// Act: Clean up state files
614+
let result = DestroyCommandHandler::cleanup_state_files(&created_env);
615+
616+
// Assert: Cleanup succeeded
617+
assert!(
618+
result.is_ok(),
619+
"State file cleanup should succeed: {:?}",
620+
result.err()
621+
);
622+
623+
// Assert: Directories were removed
624+
assert!(
625+
!data_dir.exists(),
626+
"Data directory should be removed after cleanup"
627+
);
628+
assert!(
629+
!build_dir.exists(),
630+
"Build directory should be removed after cleanup"
631+
);
632+
}
500633
}

0 commit comments

Comments
 (0)