Skip to main content

torrust_tracker_deployer_lib/application/steps/rendering/
backup_templates.rs

1//! Backup template rendering step
2//!
3//! This module provides the `RenderBackupTemplatesStep` which handles rendering
4//! of backup configuration templates to the build directory. This step prepares
5//! backup configuration files for deployment to the remote host.
6//!
7//! ## Key Features
8//!
9//! - Template rendering for backup configurations (`backup.conf`)
10//! - Static file copying for backup path lists (`backup-paths.txt`)
11//! - Integration with the `BackupProjectGenerator` for file generation
12//! - Build directory preparation for deployment operations
13//! - Comprehensive error handling for template processing
14//!
15//! ## Usage Context
16//!
17//! This step is typically executed during the release workflow, after
18//! infrastructure provisioning and software installation, to prepare
19//! the backup configuration files for deployment.
20//!
21//! ## Architecture
22//!
23//! This step follows the three-level architecture:
24//! - **Command** (Level 1): `ReleaseCommandHandler` orchestrates the release workflow
25//! - **Step** (Level 2): This `RenderBackupTemplatesStep` handles template rendering
26//! - The templates are rendered locally, no remote action is needed
27
28use std::path::PathBuf;
29use std::sync::Arc;
30
31use tracing::{info, instrument};
32
33use crate::application::services::rendering::BackupTemplateRenderingService;
34use crate::application::services::rendering::BackupTemplateRenderingServiceError;
35use crate::domain::environment::Environment;
36
37/// Step that renders Backup templates to the build directory
38///
39/// This step handles the preparation of backup configuration files
40/// by rendering templates to the build directory. The rendered files are
41/// then ready to be deployed to the remote host.
42pub struct RenderBackupTemplatesStep<S> {
43    environment: Arc<Environment<S>>,
44    templates_dir: PathBuf,
45    build_dir: PathBuf,
46}
47
48impl<S> RenderBackupTemplatesStep<S> {
49    /// Creates a new `RenderBackupTemplatesStep`
50    ///
51    /// # Arguments
52    ///
53    /// * `environment` - The deployment environment
54    /// * `templates_dir` - The templates directory
55    /// * `build_dir` - The build directory where templates will be rendered
56    #[must_use]
57    pub fn new(
58        environment: Arc<Environment<S>>,
59        templates_dir: PathBuf,
60        build_dir: PathBuf,
61    ) -> Self {
62        Self {
63            environment,
64            templates_dir,
65            build_dir,
66        }
67    }
68
69    /// Execute the template rendering step
70    ///
71    /// This will render backup templates to the build directory if backup
72    /// configuration is present in the environment.
73    ///
74    /// # Returns
75    ///
76    /// Returns the path to the backup build directory on success, or `None`
77    /// if backup is not configured.
78    ///
79    /// # Errors
80    ///
81    /// Returns an error if:
82    /// * Template rendering fails
83    /// * Directory creation fails
84    /// * File writing fails
85    #[instrument(
86        name = "render_backup_templates",
87        skip_all,
88        fields(
89            step_type = "rendering",
90            template_type = "backup",
91            build_dir = %self.build_dir.display()
92        )
93    )]
94    pub async fn execute(&self) -> Result<Option<PathBuf>, BackupTemplateRenderingServiceError> {
95        info!(
96            step = "render_backup_templates",
97            action = "render_templates",
98            "Rendering backup templates"
99        );
100
101        // Check if backup configuration exists
102        let Some(backup_config) = &self.environment.context().user_inputs.backup() else {
103            info!(
104                step = "render_backup_templates",
105                status = "skipped",
106                reason = "backup_not_configured",
107                "Backup is not configured in environment"
108            );
109            return Ok(None);
110        };
111
112        let service = BackupTemplateRenderingService::from_paths(
113            self.templates_dir.clone(),
114            self.build_dir.clone(),
115        );
116
117        let database_config = self
118            .environment
119            .context()
120            .user_inputs
121            .tracker()
122            .core()
123            .database();
124        let created_at = self.environment.context().created_at();
125
126        let Some(backup_dir_path) = service
127            .render(Some(backup_config), database_config, created_at)
128            .await?
129        else {
130            return Ok(None);
131        };
132
133        info!(
134            step = "render_backup_templates",
135            status = "success",
136            output_dir = %backup_dir_path.display(),
137            "Backup templates rendered successfully"
138        );
139
140        Ok(Some(backup_dir_path))
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use tempfile::TempDir;
147
148    use super::*;
149    use crate::domain::environment::testing::EnvironmentTestBuilder;
150    use std::sync::Arc;
151
152    #[tokio::test]
153    async fn it_should_skip_rendering_when_backup_is_not_configured() {
154        // Arrange
155        let templates_dir = TempDir::new().expect("Failed to create templates dir");
156        let build_dir = TempDir::new().expect("Failed to create build dir");
157
158        // Build environment without Backup config
159        let (environment, _, _, _temp_dir) =
160            EnvironmentTestBuilder::new().build_with_custom_paths();
161        let environment = Arc::new(environment);
162
163        let step = RenderBackupTemplatesStep::new(
164            environment,
165            templates_dir.path().to_path_buf(),
166            build_dir.path().to_path_buf(),
167        );
168
169        // Act
170        let result = step.execute().await;
171
172        // Assert
173        assert!(result.is_ok());
174        assert!(
175            result.unwrap().is_none(),
176            "Should return None when backup not configured"
177        );
178    }
179
180    #[tokio::test]
181    async fn it_should_render_backup_templates_when_backup_is_configured_with_sqlite() {
182        // Arrange
183        let templates_dir = TempDir::new().expect("Failed to create templates dir");
184        let build_dir = TempDir::new().expect("Failed to create build dir");
185
186        let (environment, _, _, _temp_dir) = EnvironmentTestBuilder::new()
187            .with_backup_config(Some(crate::domain::backup::BackupConfig::default()))
188            .build_with_custom_paths();
189        let environment = Arc::new(environment);
190
191        let step = RenderBackupTemplatesStep::new(
192            environment,
193            templates_dir.path().to_path_buf(),
194            build_dir.path().to_path_buf(),
195        );
196
197        // Act
198        let result = step.execute().await;
199
200        // Assert
201        // With backup configured, templates should render
202        assert!(result.is_ok());
203        assert!(
204            result.unwrap().is_some(),
205            "Should return Some when backup is configured"
206        );
207    }
208
209    #[tokio::test]
210    async fn it_should_render_backup_templates_when_backup_is_configured_with_mysql() {
211        // Arrange
212        let templates_dir = TempDir::new().expect("Failed to create templates dir");
213        let build_dir = TempDir::new().expect("Failed to create build dir");
214
215        let (environment, _, _, _temp_dir) = EnvironmentTestBuilder::new()
216            .with_backup_config(Some(crate::domain::backup::BackupConfig::default()))
217            .build_with_custom_paths();
218        let environment = Arc::new(environment);
219
220        let step = RenderBackupTemplatesStep::new(
221            environment,
222            templates_dir.path().to_path_buf(),
223            build_dir.path().to_path_buf(),
224        );
225
226        // Act
227        let result = step.execute().await;
228
229        // Assert
230        // With backup configured, templates should render
231        assert!(result.is_ok());
232        assert!(
233            result.unwrap().is_some(),
234            "Should return Some when backup is configured"
235        );
236    }
237}