Skip to main content

herolib_virt/nerdctl/
container_builder.rs

1// File: /root/code/git.threefold.info/herocode/sal/src/virt/nerdctl/container_builder.rs
2
3use super::container_types::{Container, HealthCheck};
4use super::health_check_script::prepare_health_check_command;
5use super::{execute_nerdctl_command, NerdctlError};
6use std::collections::HashMap;
7
8impl Container {
9    /// Reset the container configuration to defaults while keeping the name and image
10    /// If the container exists, it will be stopped and removed.
11    ///
12    /// # Returns
13    ///
14    /// * `Self` - The container instance for method chaining
15    pub fn reset(self) -> Self {
16        let name = self.name;
17        let image = self.image.clone();
18
19        // If container exists, stop and remove it
20        if let Some(container_id) = &self.container_id {
21            println!(
22                "Container exists. Stopping and removing container '{}'...",
23                name
24            );
25
26            // Try to stop the container
27            let _ = execute_nerdctl_command(&["stop", container_id]);
28
29            // Try to remove the container
30            let _ = execute_nerdctl_command(&["rm", container_id]);
31        }
32
33        // Create a new container with just the name and image, but no container_id
34        Self {
35            name,
36            container_id: None, // Reset container_id to None since we removed the container
37            image,
38            config: std::collections::HashMap::new(),
39            ports: Vec::new(),
40            volumes: Vec::new(),
41            env_vars: std::collections::HashMap::new(),
42            network: None,
43            network_aliases: Vec::new(),
44            cpu_limit: None,
45            memory_limit: None,
46            memory_swap_limit: None,
47            cpu_shares: None,
48            restart_policy: None,
49            health_check: None,
50            detach: false,
51            snapshotter: None,
52            runtime: None,
53            disk_limit: None,
54            annotations: std::collections::HashMap::new(),
55            privileged: false,
56            devices: Vec::new(),
57        }
58    }
59
60    /// Add a port mapping
61    ///
62    /// # Arguments
63    ///
64    /// * `port` - Port mapping (e.g., "8080:80")
65    ///
66    /// # Returns
67    ///
68    /// * `Self` - The container instance for method chaining
69    pub fn with_port(mut self, port: &str) -> Self {
70        self.ports.push(port.to_string());
71        self
72    }
73
74    /// Add multiple port mappings
75    ///
76    /// # Arguments
77    ///
78    /// * `ports` - Array of port mappings (e.g., ["8080:80", "8443:443"])
79    ///
80    /// # Returns
81    ///
82    /// * `Self` - The container instance for method chaining
83    pub fn with_ports(mut self, ports: &[&str]) -> Self {
84        for port in ports {
85            self.ports.push(port.to_string());
86        }
87        self
88    }
89
90    /// Add a volume mount
91    ///
92    /// # Arguments
93    ///
94    /// * `volume` - Volume mount (e.g., "/host/path:/container/path")
95    ///
96    /// # Returns
97    ///
98    /// * `Self` - The container instance for method chaining
99    pub fn with_volume(mut self, volume: &str) -> Self {
100        self.volumes.push(volume.to_string());
101        self
102    }
103
104    /// Add multiple volume mounts
105    ///
106    /// # Arguments
107    ///
108    /// * `volumes` - Array of volume mounts (e.g., ["/host/path1:/container/path1", "/host/path2:/container/path2"])
109    ///
110    /// # Returns
111    ///
112    /// * `Self` - The container instance for method chaining
113    pub fn with_volumes(mut self, volumes: &[&str]) -> Self {
114        for volume in volumes {
115            self.volumes.push(volume.to_string());
116        }
117        self
118    }
119
120    /// Add an environment variable
121    ///
122    /// # Arguments
123    ///
124    /// * `key` - Environment variable name
125    /// * `value` - Environment variable value
126    ///
127    /// # Returns
128    ///
129    /// * `Self` - The container instance for method chaining
130    pub fn with_env(mut self, key: &str, value: &str) -> Self {
131        self.env_vars.insert(key.to_string(), value.to_string());
132        self
133    }
134
135    /// Add multiple environment variables
136    ///
137    /// # Arguments
138    ///
139    /// * `env_map` - Map of environment variable names to values
140    ///
141    /// # Returns
142    ///
143    /// * `Self` - The container instance for method chaining
144    pub fn with_envs(mut self, env_map: &HashMap<&str, &str>) -> Self {
145        for (key, value) in env_map {
146            self.env_vars.insert(key.to_string(), value.to_string());
147        }
148        self
149    }
150
151    /// Set the network for the container
152    ///
153    /// # Arguments
154    ///
155    /// * `network` - Network name
156    ///
157    /// # Returns
158    ///
159    /// * `Self` - The container instance for method chaining
160    pub fn with_network(mut self, network: &str) -> Self {
161        self.network = Some(network.to_string());
162        self
163    }
164
165    /// Add a network alias for the container
166    ///
167    /// # Arguments
168    ///
169    /// * `alias` - Network alias
170    ///
171    /// # Returns
172    ///
173    /// * `Self` - The container instance for method chaining
174    pub fn with_network_alias(mut self, alias: &str) -> Self {
175        self.network_aliases.push(alias.to_string());
176        self
177    }
178
179    /// Add multiple network aliases for the container
180    ///
181    /// # Arguments
182    ///
183    /// * `aliases` - Array of network aliases
184    ///
185    /// # Returns
186    ///
187    /// * `Self` - The container instance for method chaining
188    pub fn with_network_aliases(mut self, aliases: &[&str]) -> Self {
189        for alias in aliases {
190            self.network_aliases.push(alias.to_string());
191        }
192        self
193    }
194
195    /// Set CPU limit for the container
196    ///
197    /// # Arguments
198    ///
199    /// * `cpus` - CPU limit (e.g., "0.5" for half a CPU, "2" for 2 CPUs)
200    ///
201    /// # Returns
202    ///
203    /// * `Self` - The container instance for method chaining
204    pub fn with_cpu_limit(mut self, cpus: &str) -> Self {
205        self.cpu_limit = Some(cpus.to_string());
206        self
207    }
208
209    /// Set memory limit for the container
210    ///
211    /// # Arguments
212    ///
213    /// * `memory` - Memory limit (e.g., "512m" for 512MB, "1g" for 1GB)
214    ///
215    /// # Returns
216    ///
217    /// * `Self` - The container instance for method chaining
218    pub fn with_memory_limit(mut self, memory: &str) -> Self {
219        self.memory_limit = Some(memory.to_string());
220        self
221    }
222
223    /// Set memory swap limit for the container
224    ///
225    /// # Arguments
226    ///
227    /// * `memory_swap` - Memory swap limit (e.g., "1g" for 1GB)
228    ///
229    /// # Returns
230    ///
231    /// * `Self` - The container instance for method chaining
232    pub fn with_memory_swap_limit(mut self, memory_swap: &str) -> Self {
233        self.memory_swap_limit = Some(memory_swap.to_string());
234        self
235    }
236
237    /// Set CPU shares for the container (relative weight)
238    ///
239    /// # Arguments
240    ///
241    /// * `shares` - CPU shares (e.g., "1024" for default, "512" for half)
242    ///
243    /// # Returns
244    ///
245    /// * `Self` - The container instance for method chaining
246    pub fn with_cpu_shares(mut self, shares: &str) -> Self {
247        self.cpu_shares = Some(shares.to_string());
248        self
249    }
250
251    /// Set restart policy for the container
252    ///
253    /// # Arguments
254    ///
255    /// * `policy` - Restart policy (e.g., "no", "always", "on-failure", "unless-stopped")
256    ///
257    /// # Returns
258    ///
259    /// * `Self` - The container instance for method chaining
260    pub fn with_restart_policy(mut self, policy: &str) -> Self {
261        self.restart_policy = Some(policy.to_string());
262        self
263    }
264
265    /// Set a simple health check for the container
266    ///
267    /// # Arguments
268    ///
269    /// * `cmd` - Command to run for health check (e.g., "curl -f http://localhost/ || exit 1")
270    ///
271    /// # Returns
272    ///
273    /// * `Self` - The container instance for method chaining
274    pub fn with_health_check(mut self, cmd: &str) -> Self {
275        // Use the health check script module to prepare the command
276        let prepared_cmd = prepare_health_check_command(cmd, &self.name);
277
278        self.health_check = Some(HealthCheck {
279            cmd: prepared_cmd,
280            interval: None,
281            timeout: None,
282            retries: None,
283            start_period: None,
284        });
285        self
286    }
287
288    /// Set a health check with custom options for the container
289    ///
290    /// # Arguments
291    ///
292    /// * `cmd` - Command to run for health check
293    /// * `interval` - Optional time between running the check (e.g., "30s", "1m")
294    /// * `timeout` - Optional maximum time to wait for a check to complete (e.g., "30s", "1m")
295    /// * `retries` - Optional number of consecutive failures needed to consider unhealthy
296    /// * `start_period` - Optional start period for the container to initialize before counting retries (e.g., "30s", "1m")
297    ///
298    /// # Returns
299    ///
300    /// * `Self` - The container instance for method chaining
301    pub fn with_health_check_options(
302        mut self,
303        cmd: &str,
304        interval: Option<&str>,
305        timeout: Option<&str>,
306        retries: Option<u32>,
307        start_period: Option<&str>,
308    ) -> Self {
309        // Use the health check script module to prepare the command
310        let prepared_cmd = prepare_health_check_command(cmd, &self.name);
311
312        let mut health_check = HealthCheck {
313            cmd: prepared_cmd,
314            interval: None,
315            timeout: None,
316            retries: None,
317            start_period: None,
318        };
319
320        if let Some(interval_value) = interval {
321            health_check.interval = Some(interval_value.to_string());
322        }
323
324        if let Some(timeout_value) = timeout {
325            health_check.timeout = Some(timeout_value.to_string());
326        }
327
328        if let Some(retries_value) = retries {
329            health_check.retries = Some(retries_value);
330        }
331
332        if let Some(start_period_value) = start_period {
333            health_check.start_period = Some(start_period_value.to_string());
334        }
335
336        self.health_check = Some(health_check);
337        self
338    }
339
340    /// Set the snapshotter
341    ///
342    /// # Arguments
343    ///
344    /// * `snapshotter` - Snapshotter to use
345    ///
346    /// # Returns
347    ///
348    /// * `Self` - The container instance for method chaining
349    pub fn with_snapshotter(mut self, snapshotter: &str) -> Self {
350        self.snapshotter = Some(snapshotter.to_string());
351        self
352    }
353
354    /// Set whether to run in detached mode
355    ///
356    /// # Arguments
357    ///
358    /// * `detach` - Whether to run in detached mode
359    ///
360    /// # Returns
361    ///
362    /// * `Self` - The container instance for method chaining
363    pub fn with_detach(mut self, detach: bool) -> Self {
364        self.detach = detach;
365        self
366    }
367
368    /// Set the runtime to use for the container
369    ///
370    /// # Arguments
371    ///
372    /// * `runtime` - Runtime to use (e.g., "kata-runtime", "runc", "kata")
373    ///
374    /// # Returns
375    ///
376    /// * `Self` - The container instance for method chaining
377    pub fn with_runtime(mut self, runtime: &str) -> Self {
378        self.runtime = Some(runtime.to_string());
379        self
380    }
381
382    /// Set disk size limit for the container
383    ///
384    /// For Kata containers, this sets the VM block device size via OCI annotation.
385    /// The size should be specified in a format like "10G", "512M", etc.
386    /// The annotation will be automatically added when building if runtime is kata-related.
387    ///
388    /// # Arguments
389    ///
390    /// * `size` - Disk size limit (e.g., "10G" for 10GB, "512M" for 512MB)
391    ///
392    /// # Returns
393    ///
394    /// * `Self` - The container instance for method chaining
395    pub fn with_disk_limit(mut self, size: &str) -> Self {
396        self.disk_limit = Some(size.to_string());
397        self
398    }
399
400    /// Add an OCI annotation to the container
401    ///
402    /// # Arguments
403    ///
404    /// * `key` - Annotation key
405    /// * `value` - Annotation value
406    ///
407    /// # Returns
408    ///
409    /// * `Self` - The container instance for method chaining
410    pub fn with_annotation(mut self, key: &str, value: &str) -> Self {
411        self.annotations.insert(key.to_string(), value.to_string());
412        self
413    }
414
415    /// Add multiple OCI annotations to the container
416    ///
417    /// # Arguments
418    ///
419    /// * `annotations_map` - Map of annotation keys to values
420    ///
421    /// # Returns
422    ///
423    /// * `Self` - The container instance for method chaining
424    pub fn with_annotations(mut self, annotations_map: &HashMap<&str, &str>) -> Self {
425        for (key, value) in annotations_map {
426            self.annotations.insert(key.to_string(), value.to_string());
427        }
428        self
429    }
430
431    /// Set privileged mode for the container
432    ///
433    /// # Arguments
434    ///
435    /// * `privileged` - Whether to run the container in privileged mode
436    ///
437    /// # Returns
438    ///
439    /// * `Self` - The container instance for method chaining
440    pub fn with_privileged(mut self, privileged: bool) -> Self {
441        self.privileged = privileged;
442        self
443    }
444
445    /// Add a device to the container
446    ///
447    /// # Arguments
448    ///
449    /// * `device` - Device path (e.g., "/dev/net/tun")
450    ///
451    /// # Returns
452    ///
453    /// * `Self` - The container instance for method chaining
454    pub fn with_device(mut self, device: &str) -> Self {
455        self.devices.push(device.to_string());
456        self
457    }
458
459    /// Add multiple devices to the container
460    ///
461    /// # Arguments
462    ///
463    /// * `devices` - Array of device paths (e.g., ["/dev/net/tun", "/dev/kvm"])
464    ///
465    /// # Returns
466    ///
467    /// * `Self` - The container instance for method chaining
468    pub fn with_devices(mut self, devices: &[&str]) -> Self {
469        for device in devices {
470            self.devices.push(device.to_string());
471        }
472        self
473    }
474
475    /// Build the container
476    ///
477    /// # Returns
478    ///
479    /// * `Result<Self, NerdctlError>` - Container instance or error
480    pub fn build(self) -> Result<Self, NerdctlError> {
481        // If container already exists, return it
482        if self.container_id.is_some() {
483            return Ok(self);
484        }
485
486        // If no image is specified, return an error
487        let image = match &self.image {
488            Some(img) => img,
489            None => {
490                return Err(NerdctlError::Other(
491                    "No image specified for container creation".to_string(),
492                ))
493            }
494        };
495
496        // Build the command arguments as strings
497        let mut args_strings = Vec::new();
498        args_strings.push("run".to_string());
499
500        if self.detach {
501            args_strings.push("-d".to_string());
502        }
503
504        args_strings.push("--name".to_string());
505        args_strings.push(self.name.clone());
506
507        // Add port mappings
508        for port in &self.ports {
509            args_strings.push("-p".to_string());
510            args_strings.push(port.clone());
511        }
512
513        // Add volume mounts
514        for volume in &self.volumes {
515            args_strings.push("-v".to_string());
516            args_strings.push(volume.clone());
517        }
518
519        // Add environment variables
520        for (key, value) in &self.env_vars {
521            args_strings.push("-e".to_string());
522            args_strings.push(format!("{}={}", key, value));
523        }
524
525        // Add network configuration
526        if let Some(network) = &self.network {
527            args_strings.push("--network".to_string());
528            args_strings.push(network.clone());
529        }
530
531        // Add network aliases
532        for alias in &self.network_aliases {
533            args_strings.push("--network-alias".to_string());
534            args_strings.push(alias.clone());
535        }
536
537        // Add resource limits
538        if let Some(cpu_limit) = &self.cpu_limit {
539            args_strings.push("--cpus".to_string());
540            args_strings.push(cpu_limit.clone());
541        }
542
543        if let Some(memory_limit) = &self.memory_limit {
544            args_strings.push("--memory".to_string());
545            args_strings.push(memory_limit.clone());
546        }
547
548        if let Some(memory_swap_limit) = &self.memory_swap_limit {
549            args_strings.push("--memory-swap".to_string());
550            args_strings.push(memory_swap_limit.clone());
551        }
552
553        if let Some(cpu_shares) = &self.cpu_shares {
554            args_strings.push("--cpu-shares".to_string());
555            args_strings.push(cpu_shares.clone());
556        }
557
558        // Add restart policy
559        if let Some(restart_policy) = &self.restart_policy {
560            args_strings.push("--restart".to_string());
561            args_strings.push(restart_policy.clone());
562        }
563
564        // Add health check
565        if let Some(health_check) = &self.health_check {
566            args_strings.push("--health-cmd".to_string());
567            args_strings.push(health_check.cmd.clone());
568
569            if let Some(interval) = &health_check.interval {
570                args_strings.push("--health-interval".to_string());
571                args_strings.push(interval.clone());
572            }
573
574            if let Some(timeout) = &health_check.timeout {
575                args_strings.push("--health-timeout".to_string());
576                args_strings.push(timeout.clone());
577            }
578
579            if let Some(retries) = &health_check.retries {
580                args_strings.push("--health-retries".to_string());
581                args_strings.push(retries.to_string());
582            }
583
584            if let Some(start_period) = &health_check.start_period {
585                args_strings.push("--health-start-period".to_string());
586                args_strings.push(start_period.clone());
587            }
588        }
589
590        if let Some(snapshotter_value) = &self.snapshotter {
591            args_strings.push("--snapshotter".to_string());
592            args_strings.push(snapshotter_value.clone());
593        }
594
595        // Add runtime
596        if let Some(runtime_value) = &self.runtime {
597            args_strings.push("--runtime".to_string());
598            args_strings.push(runtime_value.clone());
599            
600            // If runtime is kata-related and disk_limit is set, automatically add the annotation
601            if runtime_value.contains("kata") {
602                if let Some(disk_size) = &self.disk_limit {
603                    // Check if annotation already exists to avoid duplicates
604                    if !self.annotations.contains_key("io.katacontainers.config.hypervisor.block_device_size") {
605                        args_strings.push("--annotation".to_string());
606                        args_strings.push(format!(
607                            "io.katacontainers.config.hypervisor.block_device_size={}",
608                            disk_size
609                        ));
610                    }
611                }
612            }
613        }
614
615        // Add OCI annotations
616        for (key, value) in &self.annotations {
617            args_strings.push("--annotation".to_string());
618            args_strings.push(format!("{}={}", key, value));
619        }
620
621        // Add privileged flag
622        if self.privileged {
623            args_strings.push("--privileged".to_string());
624        }
625
626        // Add device mounts
627        for device in &self.devices {
628            args_strings.push("--device".to_string());
629            args_strings.push(device.clone());
630        }
631
632        // Add flags to avoid BPF issues
633        args_strings.push("--cgroup-manager=cgroupfs".to_string());
634
635        args_strings.push(image.clone());
636
637        // Convert to string slices for the command
638        let args: Vec<&str> = args_strings.iter().map(|s| s.as_str()).collect();
639
640        // Execute the command
641        let result = execute_nerdctl_command(&args)?;
642
643        // Get the container ID from the output
644        let container_id = result.stdout.trim().to_string();
645
646        Ok(Self {
647            name: self.name,
648            container_id: Some(container_id),
649            image: self.image,
650            config: self.config,
651            ports: self.ports,
652            volumes: self.volumes,
653            env_vars: self.env_vars,
654            network: self.network,
655            network_aliases: self.network_aliases,
656            cpu_limit: self.cpu_limit,
657            memory_limit: self.memory_limit,
658            memory_swap_limit: self.memory_swap_limit,
659            cpu_shares: self.cpu_shares,
660            restart_policy: self.restart_policy,
661            health_check: self.health_check,
662            detach: self.detach,
663            snapshotter: self.snapshotter,
664            runtime: self.runtime,
665            disk_limit: self.disk_limit,
666            annotations: self.annotations,
667            privileged: self.privileged,
668            devices: self.devices,
669        })
670    }
671}