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}