use crate::cmd;
use crate::result::Result;
use crate::Container;
use anyhow::{bail, Context};
use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::str::from_utf8;
use std::str::FromStr;
#[derive(Clone, Debug)]
pub(crate) struct Kubernetes {}
pub(crate) const DEFAULT_NAMESPACE: &str = "default";
impl Container for Kubernetes {
fn lookup(&self, container_id: &str) -> Result<libc::pid_t> {
let (namespace, pod_name, container_name) = parse_userinput(container_id)
.with_context(|| format!("failed to parse container ID '{}'", container_id))?;
let containerdid =
get_containerd_id(namespace, pod_name, container_name).with_context(|| {
format!(
"failed to get containerd ID for pod '{}' in namespace '{}'",
pod_name, namespace
)
})?;
let cgroup = find_cgroup(containerdid.clone()).with_context(|| {
format!("failed to find cgroup for containerd ID '{}'", containerdid)
})?;
let pid = get_cgroup_pid(&cgroup)
.with_context(|| format!("failed to get PID from cgroup '{}'", cgroup.display()))?;
Ok(pid)
}
fn check_required_tools(&self) -> Result<()> {
if cmd::which("kubectl").is_some() {
Ok(())
} else {
bail!("kubernetes runtime not found: 'kubectl' command is not available")
}
}
}
pub(crate) fn parse_userinput(container_id: &str) -> Result<(&str, &str, Option<&str>)> {
let fields = container_id.splitn(3, '/').collect::<Vec<&str>>();
if fields.len() == 1 {
return Ok((DEFAULT_NAMESPACE, container_id, None));
} else if fields.len() == 2 {
return Ok((fields[0], fields[1], None));
} else if fields.len() == 3 {
return Ok((fields[0], fields[1], Some(fields[2])));
}
unreachable!();
}
pub(crate) fn get_containerd_id(
namespace: &str,
pod_name: &str,
container_name: Option<&str>,
) -> Result<String> {
let jsonpath = format!("jsonpath='{{range .items[?(@.metadata.name==\"{}\")].status.containerStatuses[*]}}{{.name}}{{\"\\t\"}}{{.containerID}}{{\"\\n\"}}{{end}}'", pod_name);
let result = Command::new("kubectl")
.arg("get")
.arg("pod")
.arg("-o")
.arg(jsonpath)
.arg("-n")
.arg(namespace)
.output()
.context("failed to execute 'kubectl get pod'")?;
if !result.status.success() {
let stderr = String::from_utf8_lossy(&result.stderr);
bail!(
"kubectl get pod failed (exit status {}): {}",
result.status,
stderr
);
}
let containers =
from_utf8(&result.stdout).context("kubectl response contains non-UTF8 data")?;
let containerid = containers.split('\n').find_map(|line| {
let cols: Vec<&str> = line.split('\t').collect();
if cols.len() != 2 {
return None;
}
if let Some(name) = container_name {
if cols[0] == name {
return Some(cols[1]);
}
} else {
return Some(cols[1]);
}
None
});
let containerid = containerid.ok_or_else(|| {
if let Some(name) = container_name {
anyhow::anyhow!("no container named '{}' found in pod '{}'", name, pod_name)
} else {
anyhow::anyhow!("no containers found in pod '{}'", pod_name)
}
})?;
let containerid = containerid.strip_prefix("containerd://").ok_or_else(|| {
anyhow::anyhow!(
"container ID does not have expected 'containerd://' prefix: {}",
containerid
)
})?;
Ok(String::from(containerid))
}
pub(crate) fn find_cgroup(containerdid: String) -> Result<PathBuf> {
let path = visit_dirs(
&PathBuf::from("/sys/fs/cgroup"),
&OsString::from(containerdid),
)?;
Ok(path)
}
fn visit_dirs(dir: &Path, containerdid: &OsString) -> Result<PathBuf> {
for entry in std::fs::read_dir(dir)
.with_context(|| format!("failed to read directory '{}'", dir.display()))?
{
let entry = entry
.with_context(|| format!("failed to read entry in directory '{}'", dir.display()))?;
if &entry.file_name() == containerdid {
return Ok(entry.path());
}
let path = entry.path();
if path.is_dir() {
if let Ok(path) = visit_dirs(&path, containerdid) {
return Ok(path);
}
}
}
bail!("cgroup not found in directory tree");
}
pub(crate) fn get_cgroup_pid(cgroup: &Path) -> Result<libc::pid_t> {
let path = cgroup.join("cgroup.procs");
let bytes = fs::read(&path)
.with_context(|| format!("failed to read cgroup.procs file at '{}'", path.display()))?;
let pids = String::from_utf8(bytes).context("cgroup.procs contains non-UTF8 data")?;
let pids = pids.splitn(2, '\n').collect::<Vec<&str>>()[0]; let pid: u64 = u64::from_str(pids)
.with_context(|| format!("invalid PID value '{}' in cgroup.procs", pids))?;
Ok(pid as libc::pid_t)
}