docker_wrapper/template/web/
nginx.rs

1//! Nginx template for quick web server container setup
2
3#![allow(clippy::doc_markdown)]
4#![allow(clippy::must_use_candidate)]
5#![allow(clippy::return_self_not_must_use)]
6
7use crate::template::{HealthCheck, Template, TemplateConfig, VolumeMount};
8use async_trait::async_trait;
9use std::collections::HashMap;
10
11/// Nginx container template with sensible defaults
12pub struct NginxTemplate {
13    config: TemplateConfig,
14}
15
16impl NginxTemplate {
17    /// Create a new Nginx template with default settings
18    pub fn new(name: impl Into<String>) -> Self {
19        let name = name.into();
20        let env = HashMap::new();
21
22        let config = TemplateConfig {
23            name: name.clone(),
24            image: "nginx".to_string(),
25            tag: "alpine".to_string(),
26            ports: vec![(80, 80)],
27            env,
28            volumes: Vec::new(),
29            network: None,
30            health_check: Some(HealthCheck {
31                test: vec![
32                    "wget".to_string(),
33                    "--no-verbose".to_string(),
34                    "--tries=1".to_string(),
35                    "--spider".to_string(),
36                    "http://localhost/".to_string(),
37                ],
38                interval: "30s".to_string(),
39                timeout: "10s".to_string(),
40                retries: 3,
41                start_period: "10s".to_string(),
42            }),
43            auto_remove: false,
44            memory_limit: None,
45            cpu_limit: None,
46            platform: None,
47        };
48
49        Self { config }
50    }
51
52    /// Set HTTP port
53    pub fn port(mut self, port: u16) -> Self {
54        // Update or add HTTP port
55        if let Some(pos) = self.config.ports.iter().position(|(_, c)| *c == 80) {
56            self.config.ports[pos] = (port, 80);
57        } else {
58            self.config.ports.push((port, 80));
59        }
60        self
61    }
62
63    /// Set HTTPS port
64    pub fn https_port(mut self, port: u16) -> Self {
65        // Add HTTPS port mapping
66        self.config.ports.push((port, 443));
67        self
68    }
69
70    /// Mount content directory
71    pub fn content(mut self, content_path: impl Into<String>) -> Self {
72        self.config.volumes.push(VolumeMount {
73            source: content_path.into(),
74            target: "/usr/share/nginx/html".to_string(),
75            read_only: true,
76        });
77        self
78    }
79
80    /// Mount custom nginx configuration
81    pub fn config_file(mut self, config_path: impl Into<String>) -> Self {
82        self.config.volumes.push(VolumeMount {
83            source: config_path.into(),
84            target: "/etc/nginx/nginx.conf".to_string(),
85            read_only: true,
86        });
87        self
88    }
89
90    /// Mount sites configuration directory
91    pub fn sites_config(mut self, sites_path: impl Into<String>) -> Self {
92        self.config.volumes.push(VolumeMount {
93            source: sites_path.into(),
94            target: "/etc/nginx/conf.d".to_string(),
95            read_only: true,
96        });
97        self
98    }
99
100    /// Mount SSL certificates directory
101    pub fn ssl_certs(mut self, certs_path: impl Into<String>) -> Self {
102        self.config.volumes.push(VolumeMount {
103            source: certs_path.into(),
104            target: "/etc/nginx/ssl".to_string(),
105            read_only: true,
106        });
107        self
108    }
109
110    /// Mount logs directory
111    pub fn logs(mut self, logs_path: impl Into<String>) -> Self {
112        self.config.volumes.push(VolumeMount {
113            source: logs_path.into(),
114            target: "/var/log/nginx".to_string(),
115            read_only: false,
116        });
117        self
118    }
119
120    /// Set memory limit for Nginx
121    pub fn memory_limit(mut self, limit: impl Into<String>) -> Self {
122        self.config.memory_limit = Some(limit.into());
123        self
124    }
125
126    /// Set worker processes count
127    pub fn worker_processes(mut self, count: impl Into<String>) -> Self {
128        self.config
129            .env
130            .insert("NGINX_WORKER_PROCESSES".to_string(), count.into());
131        self
132    }
133
134    /// Set worker connections count
135    pub fn worker_connections(mut self, count: impl Into<String>) -> Self {
136        self.config
137            .env
138            .insert("NGINX_WORKER_CONNECTIONS".to_string(), count.into());
139        self
140    }
141
142    /// Use a specific Nginx version
143    pub fn version(mut self, version: impl Into<String>) -> Self {
144        self.config.tag = format!("{}-alpine", version.into());
145        self
146    }
147
148    /// Connect to a specific network
149    pub fn network(mut self, network: impl Into<String>) -> Self {
150        self.config.network = Some(network.into());
151        self
152    }
153
154    /// Enable auto-remove when stopped
155    pub fn auto_remove(mut self) -> Self {
156        self.config.auto_remove = true;
157        self
158    }
159
160    /// Configure as reverse proxy
161    pub fn as_reverse_proxy(mut self) -> Self {
162        // Adjust health check for reverse proxy
163        if let Some(health) = &mut self.config.health_check {
164            health.test = vec!["nginx".to_string(), "-t".to_string()];
165        }
166        self
167    }
168
169    /// Enable debug mode
170    pub fn debug(mut self) -> Self {
171        self.config
172            .env
173            .insert("NGINX_DEBUG".to_string(), "true".to_string());
174        self
175    }
176
177    /// Use a custom image and tag
178    pub fn custom_image(mut self, image: impl Into<String>, tag: impl Into<String>) -> Self {
179        self.config.image = image.into();
180        self.config.tag = tag.into();
181        self
182    }
183
184    /// Set the platform for the container (e.g., "linux/arm64", "linux/amd64")
185    pub fn platform(mut self, platform: impl Into<String>) -> Self {
186        self.config.platform = Some(platform.into());
187        self
188    }
189}
190
191#[async_trait]
192impl Template for NginxTemplate {
193    fn name(&self) -> &str {
194        &self.config.name
195    }
196
197    fn config(&self) -> &TemplateConfig {
198        &self.config
199    }
200
201    fn config_mut(&mut self) -> &mut TemplateConfig {
202        &mut self.config
203    }
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209
210    #[test]
211    fn test_nginx_template_basic() {
212        let template = NginxTemplate::new("test-nginx");
213        assert_eq!(template.name(), "test-nginx");
214        assert_eq!(template.config().image, "nginx");
215        assert_eq!(template.config().tag, "alpine");
216        assert_eq!(template.config().ports, vec![(80, 80)]);
217    }
218
219    #[test]
220    fn test_nginx_template_with_ports() {
221        let template = NginxTemplate::new("test-nginx").port(8080).https_port(8443);
222
223        assert_eq!(template.config().ports.len(), 2);
224        assert!(template.config().ports.contains(&(8080, 80)));
225        assert!(template.config().ports.contains(&(8443, 443)));
226    }
227
228    #[test]
229    fn test_nginx_template_with_content() {
230        let template = NginxTemplate::new("test-nginx")
231            .content("./website")
232            .config_file("./nginx.conf");
233
234        assert_eq!(template.config().volumes.len(), 2);
235        assert_eq!(template.config().volumes[0].source, "./website");
236        assert_eq!(template.config().volumes[0].target, "/usr/share/nginx/html");
237        assert!(template.config().volumes[0].read_only);
238
239        assert_eq!(template.config().volumes[1].source, "./nginx.conf");
240        assert_eq!(template.config().volumes[1].target, "/etc/nginx/nginx.conf");
241        assert!(template.config().volumes[1].read_only);
242    }
243
244    #[test]
245    fn test_nginx_template_reverse_proxy() {
246        let template = NginxTemplate::new("test-nginx").as_reverse_proxy();
247
248        if let Some(health) = &template.config().health_check {
249            assert_eq!(health.test, vec!["nginx".to_string(), "-t".to_string()]);
250        }
251    }
252
253    #[test]
254    fn test_nginx_template_with_ssl() {
255        let template = NginxTemplate::new("test-nginx")
256            .port(80)
257            .https_port(443)
258            .ssl_certs("./certs");
259
260        assert!(template.config().ports.contains(&(80, 80)));
261        assert!(template.config().ports.contains(&(443, 443)));
262
263        let ssl_volume = template
264            .config()
265            .volumes
266            .iter()
267            .find(|v| v.target == "/etc/nginx/ssl")
268            .expect("SSL volume should be present");
269        assert_eq!(ssl_volume.source, "./certs");
270    }
271}