stakpak_shared/
container.rs1use 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>, pub extra_hosts: Vec<String>, pub volumes: Vec<String>, }
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
21pub 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
30pub 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 for port_mapping in &config.ports {
53 cmd.arg("-p").arg(port_mapping);
54 }
55
56 for (key, value) in &config.env_vars {
58 cmd.arg("-e").arg(format!("{}={}", key, value));
59 }
60
61 for host_mapping in &config.extra_hosts {
63 cmd.arg("--add-host").arg(host_mapping);
64 }
65
66 for volume_mapping in &config.volumes {
68 cmd.arg("-v").arg(volume_mapping);
69 }
70
71 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}