Skip to main content

torrust_tracker_deployer_lib/application/command_handlers/purge/
handler.rs

1//! Purge command handler implementation
2
3use std::path::PathBuf;
4use std::sync::Arc;
5
6use tracing::{info, instrument, warn};
7
8use super::errors::PurgeCommandHandlerError;
9use crate::domain::environment::repository::EnvironmentRepository;
10use crate::domain::EnvironmentName;
11
12/// `PurgeCommandHandler` orchestrates the removal of all local environment data
13///
14/// This command handler removes all local files associated with an environment:
15/// 1. Removes the `data/{env-name}/` directory (environment state, configs, etc.)
16/// 2. Removes the `build/{env-name}/` directory (generated templates, artifacts)
17/// 3. Removes the environment entry from the repository
18///
19/// # State Management
20///
21/// Unlike other commands, purge **does not transition environment state**:
22/// - Works on environments in any state
23/// - Removes all local data regardless of current state
24/// - Does not persist state after purge (the environment data is removed)
25///
26/// # Idempotency
27///
28/// The purge operation is idempotent. Running it multiple times on the same
29/// environment will:
30/// - Succeed if the directories are already removed
31/// - Not fail due to missing resources
32/// - Report appropriate status to the user
33///
34/// # Important Notes
35///
36/// - **Does NOT destroy infrastructure**: Only removes local files
37/// - **Irreversible operation**: All local environment data is permanently deleted
38/// - **Works in any state**: Can purge environments that are Created, Provisioned, Running, etc.
39pub struct PurgeCommandHandler {
40    repository: Arc<dyn EnvironmentRepository + Send + Sync>,
41    working_dir: PathBuf,
42}
43
44impl PurgeCommandHandler {
45    /// Create a new `PurgeCommandHandler`
46    ///
47    /// # Arguments
48    ///
49    /// * `repository` - Repository for accessing environment data
50    /// * `working_dir` - Root directory containing `data/` and `build/` subdirectories
51    #[must_use]
52    pub fn new(
53        repository: Arc<dyn EnvironmentRepository + Send + Sync>,
54        working_dir: PathBuf,
55    ) -> Self {
56        Self {
57            repository,
58            working_dir,
59        }
60    }
61
62    /// Execute the complete purge workflow
63    ///
64    /// # Arguments
65    ///
66    /// * `env_name` - The name of the environment to purge
67    ///
68    /// # Errors
69    ///
70    /// Returns an error if:
71    /// * Environment not found in repository
72    /// * Unable to remove data directory due to permissions or I/O errors
73    /// * Unable to remove build directory due to permissions or I/O errors
74    /// * Unable to remove environment from repository
75    ///
76    /// If directories are already removed, the operation succeeds (idempotent).
77    #[instrument(
78        name = "purge_command",
79        skip_all,
80        fields(
81            command_type = "purge",
82            environment = %env_name
83        )
84    )]
85    pub fn execute(&self, env_name: &EnvironmentName) -> Result<(), PurgeCommandHandlerError> {
86        // Verify environment exists
87        self.verify_environment_exists(env_name)?;
88
89        // Remove data directory
90        self.remove_data_directory(env_name)?;
91
92        // Remove build directory
93        self.remove_build_directory(env_name)?;
94
95        // Remove from repository (this also removes the environment.json file)
96        self.remove_from_repository(env_name)?;
97
98        info!(
99            command = "purge",
100            environment = %env_name,
101            "Environment purged successfully"
102        );
103
104        Ok(())
105    }
106
107    /// Verify environment exists in repository
108    fn verify_environment_exists(
109        &self,
110        env_name: &EnvironmentName,
111    ) -> Result<(), PurgeCommandHandlerError> {
112        match self.repository.exists(env_name) {
113            Ok(true) => Ok(()),
114            Ok(false) => Err(PurgeCommandHandlerError::EnvironmentNotFound {
115                name: env_name.to_string(),
116            }),
117            Err(e) => {
118                warn!(
119                    command = "purge",
120                    environment = %env_name,
121                    error = %e,
122                    "Failed to check if environment exists, proceeding anyway"
123                );
124                // Don't fail the purge if we can't check existence
125                // The user may be trying to clean up a corrupted environment
126                Ok(())
127            }
128        }
129    }
130
131    /// Remove the data directory for the environment
132    fn remove_data_directory(
133        &self,
134        env_name: &EnvironmentName,
135    ) -> Result<(), PurgeCommandHandlerError> {
136        let data_dir = self.working_dir.join("data").join(env_name.as_str());
137
138        if !data_dir.exists() {
139            info!(
140                command = "purge",
141                environment = %env_name,
142                path = %data_dir.display(),
143                "Data directory does not exist, skipping removal"
144            );
145            return Ok(());
146        }
147
148        info!(
149            command = "purge",
150            environment = %env_name,
151            path = %data_dir.display(),
152            "Removing data directory"
153        );
154
155        std::fs::remove_dir_all(&data_dir).map_err(|source| {
156            PurgeCommandHandlerError::DataDirectoryRemovalFailed {
157                path: data_dir,
158                source,
159            }
160        })?;
161
162        Ok(())
163    }
164
165    /// Remove the build directory for the environment
166    fn remove_build_directory(
167        &self,
168        env_name: &EnvironmentName,
169    ) -> Result<(), PurgeCommandHandlerError> {
170        let build_dir = self.working_dir.join("build").join(env_name.as_str());
171
172        if !build_dir.exists() {
173            info!(
174                command = "purge",
175                environment = %env_name,
176                path = %build_dir.display(),
177                "Build directory does not exist, skipping removal"
178            );
179            return Ok(());
180        }
181
182        info!(
183            command = "purge",
184            environment = %env_name,
185            path = %build_dir.display(),
186            "Removing build directory"
187        );
188
189        std::fs::remove_dir_all(&build_dir).map_err(|source| {
190            PurgeCommandHandlerError::BuildDirectoryRemovalFailed {
191                path: build_dir,
192                source,
193            }
194        })?;
195
196        Ok(())
197    }
198
199    /// Remove environment from repository
200    fn remove_from_repository(
201        &self,
202        env_name: &EnvironmentName,
203    ) -> Result<(), PurgeCommandHandlerError> {
204        info!(
205            command = "purge",
206            environment = %env_name,
207            "Removing environment from repository"
208        );
209
210        self.repository.delete(env_name)?;
211
212        Ok(())
213    }
214}