Skip to main content

torrust_tracker_deployer_lib/application/steps/rendering/
tracker_templates.rs

1//! Tracker template rendering step
2//!
3//! This module provides the `RenderTrackerTemplatesStep` which handles rendering
4//! of Tracker configuration templates to the build directory. This step prepares
5//! tracker.toml configuration file for deployment to the remote host.
6//!
7//! ## Key Features
8//!
9//! - Template rendering for Tracker configuration
10//! - Integration with the `TrackerProjectGenerator` for file generation
11//! - Build directory preparation for deployment operations
12//! - Comprehensive error handling for template processing
13//!
14//! ## Usage Context
15//!
16//! This step is typically executed during the release workflow, after
17//! infrastructure provisioning and software installation, to prepare
18//! the Tracker configuration files for deployment.
19//!
20//! ## Architecture
21//!
22//! This step follows the three-level architecture:
23//! - **Command** (Level 1): `ReleaseCommandHandler` orchestrates the release workflow
24//! - **Step** (Level 2): This `RenderTrackerTemplatesStep` handles template rendering
25//! - The templates are rendered locally, no remote action is needed
26//!
27//! ## Phase 4 Implementation
28//!
29//! For Phase 4, all tracker configuration values are hardcoded in the tracker.toml.tera
30//! template. No environment configuration is used yet.
31//!
32//! In Phase 6, this will be extended to extract configuration from `EnvironmentConfig`.
33
34use std::path::PathBuf;
35use std::sync::Arc;
36
37use tracing::{info, instrument};
38
39use crate::application::services::rendering::TrackerTemplateRenderingService;
40use crate::application::services::rendering::TrackerTemplateRenderingServiceError;
41use crate::domain::environment::Environment;
42use crate::shared::Clock;
43
44/// Step that renders Tracker configuration templates to the build directory
45///
46/// This step handles the preparation of Tracker configuration files
47/// by rendering templates to the build directory. The rendered files are
48/// then ready to be deployed to the remote host by the `DeployTrackerConfigStep`.
49pub struct RenderTrackerTemplatesStep<S> {
50    environment: Arc<Environment<S>>,
51    templates_dir: PathBuf,
52    build_dir: PathBuf,
53    clock: Arc<dyn Clock>,
54}
55
56impl<S> RenderTrackerTemplatesStep<S> {
57    /// Creates a new `RenderTrackerTemplatesStep`
58    ///
59    /// # Arguments
60    ///
61    /// * `environment` - The deployment environment
62    /// * `templates_dir` - The templates directory
63    /// * `build_dir` - The build directory where templates will be rendered
64    /// * `clock` - Clock service for generating timestamps
65    #[must_use]
66    pub fn new(
67        environment: Arc<Environment<S>>,
68        templates_dir: PathBuf,
69        build_dir: PathBuf,
70        clock: Arc<dyn Clock>,
71    ) -> Self {
72        Self {
73            environment,
74            templates_dir,
75            build_dir,
76            clock,
77        }
78    }
79
80    /// Execute the template rendering step
81    ///
82    /// This will render Tracker configuration templates to the build directory.
83    ///
84    /// # Returns
85    ///
86    /// Returns the path to the tracker build directory on success.
87    ///
88    /// # Errors
89    ///
90    /// Returns an error if:
91    /// * Template rendering fails
92    /// * Directory creation fails
93    /// * File writing fails
94    #[instrument(
95        name = "render_tracker_templates",
96        skip_all,
97        fields(
98            step_type = "rendering",
99            template_type = "tracker",
100            build_dir = %self.build_dir.display()
101        )
102    )]
103    pub fn execute(&self) -> Result<PathBuf, TrackerTemplateRenderingServiceError> {
104        info!(
105            step = "render_tracker_templates",
106            templates_dir = %self.templates_dir.display(),
107            build_dir = %self.build_dir.display(),
108            "Rendering Tracker configuration templates"
109        );
110
111        let service = TrackerTemplateRenderingService::from_paths(
112            self.templates_dir.clone(),
113            self.build_dir.clone(),
114            self.clock.clone(),
115        );
116
117        // Extract tracker config from environment
118        let tracker_config = self.environment.context().user_inputs.tracker();
119        let tracker_build_dir = service.render(tracker_config)?;
120
121        info!(
122            step = "render_tracker_templates",
123            tracker_build_dir = %tracker_build_dir.display(),
124            status = "success",
125            "Tracker configuration templates rendered successfully"
126        );
127
128        Ok(tracker_build_dir)
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use std::fs;
135
136    use tempfile::TempDir;
137
138    use super::*;
139    use crate::domain::environment::testing::EnvironmentTestBuilder;
140    use crate::shared::SystemClock;
141
142    #[test]
143    fn it_should_render_tracker_templates_to_build_directory() {
144        let temp_dir = TempDir::new().expect("Failed to create temp dir");
145        let templates_dir = temp_dir.path().join("templates");
146        let build_dir = temp_dir.path().join("build");
147        let tracker_templates_dir = templates_dir.join("tracker");
148
149        fs::create_dir_all(&tracker_templates_dir).expect("Failed to create tracker templates dir");
150
151        // Create test tracker.toml.tera template
152        let tracker_template = r#"[metadata]
153app = "torrust-tracker"
154schema_version = "2.0.0"
155
156[logging]
157threshold = "info"
158"#;
159        fs::write(
160            tracker_templates_dir.join("tracker.toml.tera"),
161            tracker_template,
162        )
163        .expect("Failed to write tracker template");
164
165        let (environment, _, _, _temp_dir) =
166            EnvironmentTestBuilder::new().build_with_custom_paths();
167        let environment = Arc::new(environment);
168
169        let step = RenderTrackerTemplatesStep::new(
170            environment,
171            templates_dir.clone(),
172            build_dir.clone(),
173            Arc::new(SystemClock),
174        );
175
176        let result = step.execute();
177        assert!(
178            result.is_ok(),
179            "Template rendering should succeed: {:?}",
180            result.err()
181        );
182
183        let tracker_build_dir = result.unwrap();
184        assert_eq!(tracker_build_dir, build_dir.join("tracker"));
185
186        // Verify tracker.toml was created
187        let tracker_toml = tracker_build_dir.join("tracker.toml");
188        assert!(
189            tracker_toml.exists(),
190            "tracker.toml should be created in build directory"
191        );
192
193        let content = fs::read_to_string(&tracker_toml).expect("Failed to read tracker.toml");
194        assert!(content.contains(r#"app = "torrust-tracker""#));
195        assert!(content.contains(r#"schema_version = "2.0.0""#));
196        assert!(content.contains(r#"threshold = "info""#));
197    }
198
199    #[test]
200    fn it_should_use_embedded_template_when_not_in_external_dir() {
201        let temp_dir = TempDir::new().expect("Failed to create temp dir");
202        let templates_dir = temp_dir.path().join("templates");
203        let build_dir = temp_dir.path().join("build");
204
205        // Create empty templates directory (no tracker templates)
206        fs::create_dir_all(&templates_dir).expect("Failed to create templates dir");
207
208        let (environment, _, _, _temp_dir) =
209            EnvironmentTestBuilder::new().build_with_custom_paths();
210        let environment = Arc::new(environment);
211
212        let step = RenderTrackerTemplatesStep::new(
213            environment,
214            templates_dir.clone(),
215            build_dir.clone(),
216            Arc::new(SystemClock),
217        );
218
219        let result = step.execute();
220        assert!(
221            result.is_ok(),
222            "Should succeed using embedded template: {:?}",
223            result.err()
224        );
225
226        // Verify tracker.toml was created using embedded template
227        let tracker_toml = build_dir.join("tracker/tracker.toml");
228        assert!(
229            tracker_toml.exists(),
230            "tracker.toml should be created from embedded template"
231        );
232    }
233
234    #[test]
235    fn it_should_create_tracker_subdirectory_in_build_dir() {
236        let temp_dir = TempDir::new().expect("Failed to create temp dir");
237        let templates_dir = temp_dir.path().join("templates");
238        let build_dir = temp_dir.path().join("build");
239        let tracker_templates_dir = templates_dir.join("tracker");
240
241        fs::create_dir_all(&tracker_templates_dir).expect("Failed to create tracker templates dir");
242
243        let tracker_template = "[metadata]\napp = \"torrust-tracker\"";
244        fs::write(
245            tracker_templates_dir.join("tracker.toml.tera"),
246            tracker_template,
247        )
248        .expect("Failed to write tracker template");
249
250        let (environment, _, _, _temp_dir) =
251            EnvironmentTestBuilder::new().build_with_custom_paths();
252        let environment = Arc::new(environment);
253
254        let step = RenderTrackerTemplatesStep::new(
255            environment,
256            templates_dir.clone(),
257            build_dir.clone(),
258            Arc::new(SystemClock),
259        );
260
261        step.execute().expect("Template rendering should succeed");
262
263        let tracker_dir = build_dir.join("tracker");
264        assert!(tracker_dir.exists(), "tracker/ subdirectory should exist");
265        assert!(tracker_dir.is_dir(), "tracker/ should be a directory");
266    }
267}