use super::ExecOutput;
use crate::core::types::Machine;
use std::process::{Command, Stdio};
pub fn exec_container(
machine: &Machine,
script: &str,
pid_slot: Option<&super::ChildPidSlot>,
) -> Result<ExecOutput, String> {
let config = machine
.container
.as_ref()
.ok_or_else(|| format!("machine '{}' has no container config", machine.hostname))?;
let container_name = machine.container_name();
let mut cmd = Command::new(&config.runtime);
cmd.args(["exec", "-i", &container_name, "bash"])
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
super::configure_process_group(&mut cmd);
let mut child = cmd
.spawn()
.map_err(|e| format!("failed to exec in container '{container_name}': {e}"))?;
super::record_child_pid(pid_slot, &child);
super::write_stdin_or_reap(&mut child, script)?;
let output = child
.wait_with_output()
.map_err(|e| format!("wait error: {e}"))?;
Ok(ExecOutput {
exit_code: output.status.code().unwrap_or(-1),
stdout: String::from_utf8_lossy(&output.stdout).to_string(),
stderr: String::from_utf8_lossy(&output.stderr).to_string(),
})
}
pub fn ensure_container(machine: &Machine) -> Result<(), String> {
let config = machine
.container
.as_ref()
.ok_or_else(|| format!("machine '{}' has no container config", machine.hostname))?;
let container_name = machine.container_name();
let check = Command::new(&config.runtime)
.args(["inspect", "-f", "{{.State.Running}}", &container_name])
.output()
.map_err(|e| format!("failed to inspect container '{container_name}': {e}"))?;
if check.status.success() {
let running = String::from_utf8_lossy(&check.stdout);
if running.trim() == "true" {
return Ok(());
}
}
let image = config.image.as_deref().ok_or_else(|| {
format!(
"machine '{}' container has no image (required to create)",
machine.hostname
)
})?;
let mut args = vec!["run", "-d", "--name", &container_name];
if config.init {
args.push("--init");
}
if config.privileged {
args.push("--privileged");
}
let gpus_value;
if let Some(ref gpus) = config.gpus {
gpus_value = gpus.clone();
args.push("--gpus");
args.push(&gpus_value);
}
let device_values: Vec<String> = config.devices.clone();
for dev in &device_values {
args.push("--device");
args.push(dev);
}
let group_values: Vec<String> = config.group_add.clone();
for grp in &group_values {
args.push("--group-add");
args.push(grp);
}
let env_pairs: Vec<String> = config.env.iter().map(|(k, v)| format!("{k}={v}")).collect();
for pair in &env_pairs {
args.push("--env");
args.push(pair);
}
let volume_values: Vec<String> = config.volumes.clone();
for vol in &volume_values {
args.push("-v");
args.push(vol);
}
args.push(image);
args.push("sleep");
args.push("infinity");
let run = Command::new(&config.runtime)
.args(&args)
.output()
.map_err(|e| format!("failed to start container '{container_name}': {e}"))?;
if !run.status.success() {
return Err(format!(
"failed to start container '{}': {}",
container_name,
String::from_utf8_lossy(&run.stderr).trim()
));
}
Ok(())
}
pub fn cleanup_container(machine: &Machine) -> Result<(), String> {
let config = machine
.container
.as_ref()
.ok_or_else(|| format!("machine '{}' has no container config", machine.hostname))?;
let container_name = machine.container_name();
let rm = Command::new(&config.runtime)
.args(["rm", "-f", &container_name])
.output()
.map_err(|e| format!("failed to remove container '{container_name}': {e}"))?;
if !rm.status.success() {
return Err(format!(
"failed to remove container '{}': {}",
container_name,
String::from_utf8_lossy(&rm.stderr).trim()
));
}
Ok(())
}