Skip to main content

torrust_tracker_deployer_lib/application/steps/rendering/
caddy_templates.rs

1//! Caddy template rendering step
2//!
3//! This module provides the `RenderCaddyTemplatesStep` which handles rendering
4//! of Caddy configuration templates to the build directory. This step prepares
5//! Caddy Caddyfile for deployment to the remote host.
6//!
7//! ## Key Features
8//!
9//! - Template rendering for Caddy TLS proxy configuration
10//! - Integration with the `CaddyProjectGenerator` for file generation
11//! - Build directory preparation for deployment operations
12//! - Automatic extraction of TLS-enabled services from tracker config
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 Caddy 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 `RenderCaddyTemplatesStep` handles template rendering
25//! - The templates are rendered locally, no remote action is needed
26
27use std::path::PathBuf;
28use std::sync::Arc;
29
30use tracing::{info, instrument};
31
32use crate::application::services::rendering::CaddyTemplateRenderingService;
33use crate::application::services::rendering::CaddyTemplateRenderingServiceError;
34use crate::domain::environment::Environment;
35use crate::shared::clock::Clock;
36
37/// Step that renders Caddy templates to the build directory
38///
39/// This step handles the preparation of Caddy configuration files
40/// by rendering templates to the build directory. The rendered files are
41/// then ready to be deployed to the remote host.
42///
43/// Caddy is only rendered when:
44/// 1. HTTPS configuration is present in the environment
45/// 2. At least one service has TLS configured
46pub struct RenderCaddyTemplatesStep<S> {
47    environment: Arc<Environment<S>>,
48    templates_dir: PathBuf,
49    build_dir: PathBuf,
50    clock: Arc<dyn Clock>,
51}
52
53impl<S> RenderCaddyTemplatesStep<S> {
54    /// Creates a new `RenderCaddyTemplatesStep`
55    ///
56    /// # Arguments
57    ///
58    /// * `environment` - The deployment environment
59    /// * `templates_dir` - The templates directory
60    /// * `build_dir` - The build directory where templates will be rendered
61    /// * `clock` - Clock service for generating timestamps
62    #[must_use]
63    pub fn new(
64        environment: Arc<Environment<S>>,
65        templates_dir: PathBuf,
66        build_dir: PathBuf,
67        clock: Arc<dyn Clock>,
68    ) -> Self {
69        Self {
70            environment,
71            templates_dir,
72            build_dir,
73            clock,
74        }
75    }
76
77    /// Execute the template rendering step
78    ///
79    /// This will render Caddy templates to the build directory if HTTPS
80    /// configuration is present in the environment and at least one service
81    /// has TLS configured.
82    ///
83    /// # Returns
84    ///
85    /// Returns the path to the Caddy build directory on success, or `None`
86    /// if HTTPS/TLS is not configured.
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_caddy_templates",
96        skip_all,
97        fields(
98            step_type = "rendering",
99            template_type = "caddy",
100            build_dir = %self.build_dir.display()
101        )
102    )]
103    pub fn execute(&self) -> Result<Option<PathBuf>, CaddyTemplateRenderingServiceError> {
104        // Check if HTTPS is configured
105        if self.environment.context().user_inputs.https().is_none() {
106            info!(
107                step = "render_caddy_templates",
108                status = "skipped",
109                reason = "https_not_configured",
110                "Skipping Caddy template rendering - HTTPS not configured"
111            );
112            return Ok(None);
113        }
114
115        info!(
116            step = "render_caddy_templates",
117            templates_dir = %self.templates_dir.display(),
118            build_dir = %self.build_dir.display(),
119            "Rendering Caddy configuration templates"
120        );
121
122        let service = CaddyTemplateRenderingService::from_paths(
123            self.templates_dir.clone(),
124            self.build_dir.clone(),
125            self.clock.clone(),
126        );
127
128        let user_inputs = &self.environment.context().user_inputs;
129        let Some(caddy_build_dir) = service.render(user_inputs)? else {
130            info!(
131                step = "render_caddy_templates",
132                status = "skipped",
133                reason = "no_tls_services",
134                "Skipping Caddy template rendering - no services have TLS configured"
135            );
136            return Ok(None);
137        };
138
139        info!(
140            step = "render_caddy_templates",
141            caddy_build_dir = %caddy_build_dir.display(),
142            status = "success",
143            "Caddy templates rendered successfully"
144        );
145
146        Ok(Some(caddy_build_dir))
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use tempfile::TempDir;
153
154    use super::*;
155    use crate::domain::environment::testing::EnvironmentTestBuilder;
156    use crate::shared::clock::SystemClock;
157
158    #[test]
159    fn it_should_create_render_caddy_templates_step() {
160        let templates_dir = TempDir::new().expect("Failed to create templates dir");
161        let build_dir = TempDir::new().expect("Failed to create build dir");
162
163        let (environment, _, _, _temp_dir) =
164            EnvironmentTestBuilder::new().build_with_custom_paths();
165        let environment = Arc::new(environment);
166
167        let clock = Arc::new(SystemClock);
168        let step = RenderCaddyTemplatesStep::new(
169            environment.clone(),
170            templates_dir.path().to_path_buf(),
171            build_dir.path().to_path_buf(),
172            clock,
173        );
174
175        assert_eq!(step.build_dir, build_dir.path());
176        assert_eq!(step.templates_dir, templates_dir.path());
177    }
178
179    #[test]
180    fn it_should_skip_rendering_when_https_not_configured() {
181        let templates_dir = TempDir::new().expect("Failed to create templates dir");
182        let build_dir = TempDir::new().expect("Failed to create build dir");
183
184        // Build environment without HTTPS config (default)
185        let (environment, _, _, _temp_dir) =
186            EnvironmentTestBuilder::new().build_with_custom_paths();
187        let environment = Arc::new(environment);
188
189        let clock = Arc::new(SystemClock);
190        let step = RenderCaddyTemplatesStep::new(
191            environment,
192            templates_dir.path().to_path_buf(),
193            build_dir.path().to_path_buf(),
194            clock,
195        );
196
197        let result = step.execute();
198        assert!(result.is_ok(), "Should succeed when HTTPS not configured");
199        assert!(
200            result.unwrap().is_none(),
201            "Should return None when HTTPS not configured"
202        );
203    }
204}