use std::process::{Command, Stdio};
use std::time::Duration;
use super::local::LocalPty;
use super::Transport;
use crate::error::{Error, Result};
pub struct DockerExec {
inner: LocalPty,
container: String,
marker: String,
}
impl DockerExec {
pub fn spawn(container: &str, marker: &str) -> Result<Self> {
check_running(container)?;
let env = format!("EXECKIT_SID={marker}");
let inner = LocalPty::spawn(
"docker",
&["exec", "-it", "-e", &env, "--", container, "/bin/sh"],
)?;
Ok(Self {
inner,
container: container.to_string(),
marker: marker.to_string(),
})
}
}
fn check_running(container: &str) -> Result<()> {
let out = Command::new("docker")
.args(["inspect", "-f", "{{.State.Running}}", "--", container])
.stdin(Stdio::null())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.output();
match out {
Ok(o) if o.status.success() && String::from_utf8_lossy(&o.stdout).trim() == "true" => {
Ok(())
}
Ok(_) => Err(Error::Transport(format!(
"docker: container '{container}' not found or not running"
))),
Err(e) if e.kind() == std::io::ErrorKind::NotFound => Err(Error::Transport(
"docker: 'docker' CLI not found on PATH".into(),
)),
Err(_) => Err(Error::Transport(format!(
"docker: container '{container}' not found or not running"
))),
}
}
impl Transport for DockerExec {
fn write_all(&mut self, bytes: &[u8]) -> Result<()> {
self.inner.write_all(bytes)
}
fn recv_timeout(&self, dur: Duration) -> Option<Vec<u8>> {
self.inner.recv_timeout(dur)
}
}
impl Drop for DockerExec {
fn drop(&mut self) {
let script = format!(
"for p in /proc/[0-9]*; do \
tr '\\0' '\\n' < \"$p/environ\" 2>/dev/null | grep -qx 'EXECKIT_SID={m}' && \
kill -9 \"${{p#/proc/}}\" 2>/dev/null; \
done",
m = self.marker
);
let _ = Command::new("docker")
.args(["exec", "--", &self.container, "/bin/sh", "-c", &script])
.stdin(Stdio::null())
.stdout(Stdio::null())
.stderr(Stdio::null())
.status();
}
}