use std::{fs::File, io::BufRead as _, path::Path};
use bollard::models::ContainerSummary;
use bollard::Docker;
use tokio::sync::OnceCell;
use crate::error::TestError;
static CURRENT: OnceCell<Option<HostContainer>> = OnceCell::const_new();
#[derive(Debug)]
pub struct HostContainer {
id: String,
}
impl HostContainer {
pub fn id(&self) -> &str {
&self.id
}
async fn init_current(docker: &Docker) -> Result<Option<Self>, TestError> {
log::info!("Checking running mode.");
let path = Path::new("/etc/hostname");
if !path.exists() || !path.is_file() {
return Ok(None);
}
let file = File::open(path)?;
let Some(hostname) = std::io::BufReader::new(file).lines().next() else {
return Ok(None);
};
let hostname = hostname?;
let running_containers = docker
.list_containers(Some(bollard::container::ListContainersOptions::<String> {
all: false,
..Default::default()
}))
.await?;
let hostname = Self::find_matching_container_name(&running_containers, &hostname).await?;
let inspection = match docker.inspect_container(&hostname, None).await {
Ok(response) => response,
Err(bollard::errors::Error::DockerResponseServerError {
status_code: 404, ..
}) => return Ok(None),
Err(e) => return Err(e.into()),
};
Ok(Some(Self {
id: inspection.id.unwrap_or_default(),
}))
}
async fn find_matching_container_name(
running_containers: &Vec<ContainerSummary>,
hostname: &str,
) -> Result<String, TestError> {
let mut container_name = hostname.to_string();
for container in running_containers {
if let Some(names) = &container.names {
if let Some(name) = names.first() {
log::debug!("Available container: {name}");
if name.contains(hostname) {
container_name = name.trim_start_matches('/').to_string();
log::info!("Found matching container for DinD: {container_name}");
return Ok(container_name);
}
}
}
}
log::debug!("No matching container found for DinD. Defaulting to: {container_name}");
Ok(container_name)
}
pub(crate) async fn current(docker: &Docker) -> Result<Option<Self>, TestError> {
let current = CURRENT
.get_or_try_init(|| Self::init_current(docker))
.await?
.as_ref()
.map(|c| c.private_clone());
Ok(current)
}
pub(crate) fn private_clone(&self) -> Self {
Self {
id: self.id.clone(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_find_matching_container_name() {
let running_containers = vec![
ContainerSummary {
names: Some(vec!["/a-container-8976549123".to_string()]),
..Default::default()
},
ContainerSummary {
names: Some(vec!["/another-container-201349124".to_string()]),
..Default::default()
},
ContainerSummary {
names: Some(vec!["/another-container-201349125".to_string()]),
..Default::default()
},
];
let result =
HostContainer::find_matching_container_name(&running_containers, "another-container")
.await
.unwrap();
assert_eq!(result, "another-container-201349124");
}
#[tokio::test]
async fn test_no_matching_container_name() {
let running_containers = vec![
ContainerSummary {
names: Some(vec!["/a-container-8976549123".to_string()]),
..Default::default()
},
ContainerSummary {
names: Some(vec!["/another-container-201349124".to_string()]),
..Default::default()
},
ContainerSummary {
names: Some(vec!["/another-container-201349125".to_string()]),
..Default::default()
},
];
let result =
HostContainer::find_matching_container_name(&running_containers, "container-not-found")
.await
.unwrap();
assert_eq!(result, "container-not-found");
}
}