torrust_tracker_deployer_lib/application/services/rendering/
caddy.rs1use std::path::PathBuf;
7use std::sync::Arc;
8
9use tracing::{info, instrument};
10
11use crate::domain::TemplateManager;
12use crate::infrastructure::templating::caddy::{
13 CaddyContext, CaddyProjectGenerator, CaddyProjectGeneratorError, CaddyService,
14};
15use crate::infrastructure::templating::TemplateMetadata;
16use crate::shared::Clock;
17
18use crate::domain::environment::user_inputs::UserInputs;
19
20pub struct CaddyTemplateRenderingService {
28 templates_dir: PathBuf,
29 build_dir: PathBuf,
30 clock: Arc<dyn Clock>,
31}
32
33impl CaddyTemplateRenderingService {
34 #[must_use]
42 pub fn from_paths(templates_dir: PathBuf, build_dir: PathBuf, clock: Arc<dyn Clock>) -> Self {
43 Self {
44 templates_dir,
45 build_dir,
46 clock,
47 }
48 }
49
50 #[instrument(
70 name = "caddy_rendering_service",
71 skip_all,
72 fields(
73 templates_dir = %self.templates_dir.display(),
74 build_dir = %self.build_dir.display()
75 )
76 )]
77 pub fn render(
78 &self,
79 user_inputs: &UserInputs,
80 ) -> Result<Option<PathBuf>, CaddyTemplateRenderingServiceError> {
81 let Some(https_config) = user_inputs.https() else {
83 info!(
84 reason = "https_not_configured",
85 "Skipping Caddy template rendering - HTTPS not configured"
86 );
87 return Ok(None);
88 };
89
90 let caddy_context = self.build_caddy_context(user_inputs, https_config);
92
93 if !caddy_context.has_any_tls() {
95 info!(
96 reason = "no_tls_services",
97 "Skipping Caddy template rendering - no services have TLS configured"
98 );
99 return Ok(None);
100 }
101
102 info!(
103 templates_dir = %self.templates_dir.display(),
104 build_dir = %self.build_dir.display(),
105 admin_email = %https_config.admin_email(),
106 use_staging = https_config.use_staging(),
107 "Rendering Caddy configuration templates"
108 );
109
110 let template_manager = Arc::new(TemplateManager::new(self.templates_dir.clone()));
111 let generator = CaddyProjectGenerator::new(&self.build_dir, template_manager);
112
113 generator
114 .render(&caddy_context)
115 .map_err(CaddyTemplateRenderingServiceError::RenderingFailed)?;
116
117 let caddy_build_dir = self.build_dir.join("caddy");
118
119 info!(
120 caddy_build_dir = %caddy_build_dir.display(),
121 "Caddy templates rendered successfully"
122 );
123
124 Ok(Some(caddy_build_dir))
125 }
126
127 fn build_caddy_context(
132 &self,
133 user_inputs: &UserInputs,
134 https_config: &crate::domain::https::HttpsConfig,
135 ) -> CaddyContext {
136 let tracker = user_inputs.tracker();
137
138 let metadata = TemplateMetadata::new(self.clock.now());
139
140 let mut context = CaddyContext::new(
141 metadata,
142 https_config.admin_email(),
143 https_config.use_staging(),
144 );
145
146 if let Some(tls_config) = tracker.http_api_tls_domain() {
148 let port = tracker.http_api_port();
149 context = context.with_tracker_api(CaddyService::new(tls_config, port));
150 }
151
152 for (domain, port) in tracker.http_trackers_with_tls() {
154 context = context.with_http_tracker(CaddyService::new(domain, port));
155 }
156
157 if let Some(tls_domain) = tracker.health_check_api_tls_domain() {
159 let port = tracker.health_check_api_port();
160 context = context.with_health_check_api(CaddyService::new(tls_domain, port));
161 }
162
163 if let Some(grafana) = user_inputs.grafana() {
165 if let Some(tls_domain) = grafana.tls_domain() {
166 context = context.with_grafana(CaddyService::new(tls_domain, 3000));
168 }
169 }
170
171 context
172 }
173}
174
175#[derive(Debug, thiserror::Error)]
177pub enum CaddyTemplateRenderingServiceError {
178 #[error("Caddy template rendering failed: {0}")]
180 RenderingFailed(#[from] CaddyProjectGeneratorError),
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use tempfile::TempDir;
187
188 use crate::domain::environment::testing::EnvironmentTestBuilder;
189 use crate::shared::SystemClock;
190
191 #[test]
192 fn it_should_create_service_with_from_paths() {
193 let templates_dir = TempDir::new().expect("Failed to create temp dir");
194 let build_dir = TempDir::new().expect("Failed to create temp dir");
195 let clock: Arc<dyn Clock> = Arc::new(SystemClock);
196
197 let service = CaddyTemplateRenderingService::from_paths(
198 templates_dir.path().to_path_buf(),
199 build_dir.path().to_path_buf(),
200 clock,
201 );
202
203 assert_eq!(service.templates_dir, templates_dir.path());
204 assert_eq!(service.build_dir, build_dir.path());
205 }
206
207 #[test]
208 fn it_should_return_none_when_https_not_configured() {
209 let templates_dir = TempDir::new().expect("Failed to create temp dir");
210 let build_dir = TempDir::new().expect("Failed to create temp dir");
211 let clock: Arc<dyn Clock> = Arc::new(SystemClock);
212
213 let service = CaddyTemplateRenderingService::from_paths(
214 templates_dir.path().to_path_buf(),
215 build_dir.path().to_path_buf(),
216 clock,
217 );
218
219 let (environment, _, _, _temp_dir) =
220 EnvironmentTestBuilder::new().build_with_custom_paths();
221 let user_inputs = &environment.context().user_inputs;
222
223 let result = service.render(user_inputs);
224
225 assert!(result.is_ok());
226 assert!(result.unwrap().is_none());
227 }
228
229 }