stakpak_shared/
container.rs

1use std::collections::HashMap;
2use std::net::TcpListener;
3use std::process::Command;
4
5#[derive(Debug, Clone)]
6pub struct ContainerConfig {
7    pub image: String,
8    pub env_vars: HashMap<String, String>,
9    pub ports: Vec<String>,       // Format: "host_port:container_port"
10    pub extra_hosts: Vec<String>, // Format: "host:ip"
11    pub volumes: Vec<String>,     // Format: "host_path:container_path"
12}
13
14pub fn find_available_port() -> Option<u16> {
15    match TcpListener::bind("0.0.0.0:0") {
16        Ok(listener) => listener.local_addr().ok().map(|addr| addr.port()),
17        Err(_) => None,
18    }
19}
20
21/// Checks if Docker is installed and accessible
22pub fn is_docker_available() -> bool {
23    Command::new("docker")
24        .arg("--version")
25        .output()
26        .map(|output| output.status.success())
27        .unwrap_or(false)
28}
29
30/// Checks if a Docker image exists locally
31pub fn image_exists_locally(image: &str) -> Result<bool, String> {
32    let output = Command::new("docker")
33        .args(["images", "-q", image])
34        .output()
35        .map_err(|e| format!("Failed to execute docker images command: {}", e))?;
36
37    if output.status.success() {
38        let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
39        Ok(!stdout.is_empty())
40    } else {
41        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
42        Err(format!("Docker images command failed: {}", stderr))
43    }
44}
45
46pub fn run_container_detached(config: ContainerConfig) -> Result<String, String> {
47    let mut cmd = Command::new("docker");
48
49    cmd.arg("run").arg("-d").arg("--rm");
50
51    // Add ports
52    for port_mapping in &config.ports {
53        cmd.arg("-p").arg(port_mapping);
54    }
55
56    // Add environment variables
57    for (key, value) in &config.env_vars {
58        cmd.arg("-e").arg(format!("{}={}", key, value));
59    }
60
61    // Add extra hosts
62    for host_mapping in &config.extra_hosts {
63        cmd.arg("--add-host").arg(host_mapping);
64    }
65
66    // Add volumes
67    for volume_mapping in &config.volumes {
68        cmd.arg("-v").arg(volume_mapping);
69    }
70
71    // Add image
72    cmd.arg(&config.image);
73
74    let output = cmd
75        .output()
76        .map_err(|e| format!("Failed to execute docker command: {}", e))?;
77
78    if output.status.success() {
79        let container_id = String::from_utf8_lossy(&output.stdout).trim().to_string();
80        Ok(container_id)
81    } else {
82        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
83        Err(format!("Docker command failed: {}", stderr))
84    }
85}
86
87pub fn stop_container(container_id: &str) -> Result<(), String> {
88    let output = Command::new("docker")
89        .arg("stop")
90        .arg(container_id)
91        .output()
92        .map_err(|e| format!("Failed to execute docker stop: {}", e))?;
93
94    if output.status.success() {
95        Ok(())
96    } else {
97        let stderr = String::from_utf8_lossy(&output.stderr);
98        if stderr.contains("No such container") {
99            Ok(())
100        } else {
101            Err(format!("Failed to stop container: {}", stderr))
102        }
103    }
104}
105
106pub fn remove_container(
107    container_id: &str,
108    force: bool,
109    remove_volumes: bool,
110) -> Result<(), String> {
111    let mut cmd = Command::new("docker");
112
113    cmd.arg("rm");
114
115    if force {
116        cmd.arg("-f");
117    }
118
119    if remove_volumes {
120        cmd.arg("-v");
121    }
122
123    cmd.arg(container_id);
124
125    let output = cmd
126        .output()
127        .map_err(|e| format!("Failed to execute docker rm: {}", e))?;
128
129    if output.status.success() {
130        Ok(())
131    } else {
132        let stderr = String::from_utf8_lossy(&output.stderr);
133        if stderr.contains("No such container") {
134            Ok(())
135        } else {
136            Err(format!("Failed to remove container: {}", stderr))
137        }
138    }
139}
140
141pub fn get_container_host_port(container_id: &str, container_port: u16) -> Result<u16, String> {
142    let output = Command::new("docker")
143        .arg("port")
144        .arg(container_id)
145        .arg(container_port.to_string())
146        .output()
147        .map_err(|e| format!("Failed to get container port: {}", e))?;
148
149    if output.status.success() {
150        let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
151        let port = stdout.split(':').next_back().unwrap_or("");
152        Ok(port.parse().unwrap())
153    } else {
154        let stderr = String::from_utf8_lossy(&output.stderr).to_string();
155        Err(format!("Failed to get container port: {}", stderr))
156    }
157}