use std::fmt::{self, Debug, Display};
use std::net::IpAddr;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
use tracing::info;
use crate::{Container, Network, RunnableContainer, ToRunnableContainer, VolumeName};
mod docker;
pub use self::docker::Docker;
mod nerdctl;
pub use self::nerdctl::Nerdctl;
mod podman;
pub use self::podman::Podman;
mod error;
pub use self::error::*;
mod inner;
pub(crate) use self::inner::*;
mod options;
pub use self::options::*;
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Runner {
Docker(Docker),
Podman(Podman),
Nerdctl(Nerdctl),
}
impl Display for Runner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Docker(runner) => write!(f, "{runner}"),
Self::Podman(runner) => write!(f, "{runner}"),
Self::Nerdctl(runner) => write!(f, "{runner}"),
}
}
}
impl Runner {
pub fn auto() -> Result<Self, RunnerError> {
if let Ok(runner) = Self::docker() {
info!("🐳 Using docker");
return Ok(runner);
}
if let Ok(runner) = Self::podman() {
info!("Using podman");
return Ok(runner);
}
if let Ok(runner) = Self::nerdctl() {
info!("Using nerdctl");
return Ok(runner);
}
Err(RunnerError::NoRunnerAvailable)
}
pub fn docker() -> Result<Self, RunnerError> {
let runner = docker::create()?;
Ok(Self::Docker(runner))
}
pub fn podman() -> Result<Self, RunnerError> {
let runner = podman::create()?;
Ok(Self::Podman(runner))
}
pub fn nerdctl() -> Result<Self, RunnerError> {
let runner = nerdctl::create()?;
Ok(Self::Nerdctl(runner))
}
}
impl Runner {
pub async fn start<I>(&self, image: I) -> Result<Container<I>, RunnerError>
where
I: ToRunnableContainer,
{
let options = RunOption::default();
self.start_with_options(image, options).await
}
pub async fn start_with_options<I>(
&self,
image: I,
options: RunOption,
) -> Result<Container<I>, RunnerError>
where
I: ToRunnableContainer,
{
let mut container = image.to_runnable(RunnableContainer::builder());
let image_ref = container.image.clone();
let id = match self {
Self::Docker(runner) => runner.start_container(&mut container, options).await,
Self::Podman(runner) => runner.start_container(&mut container, options).await,
Self::Nerdctl(runner) => runner.start_container(&mut container, options).await,
}
.map_err(|source| RunnerError::StartError {
runner: self.clone(),
container: Box::new(container),
source: Box::new(source),
})?;
Ok(Container {
runner: self.clone(),
image,
image_ref,
id,
detached: Arc::new(AtomicBool::new(false)),
})
}
pub async fn create_network(&self, name: impl Into<String>) -> Result<Network, RunnerError> {
let name = name.into();
match self {
Self::Docker(runner) => runner.create_network(&name).await,
Self::Podman(runner) => runner.create_network(&name).await,
Self::Nerdctl(runner) => runner.create_network(&name).await,
}
.map_err(|source| RunnerError::CreateNetworkError {
runner: self.clone(),
name: name.clone(),
source: Box::new(source),
})?;
Ok(Network::Custom(name))
}
pub async fn create_volume(&self, name: impl Into<String>) -> Result<VolumeName, RunnerError> {
let name = name.into();
match self {
Self::Docker(runner) => runner.create_volume(&name).await,
Self::Podman(runner) => runner.create_volume(&name).await,
Self::Nerdctl(runner) => runner.create_volume(&name).await,
}
.map_err(|source| RunnerError::CreateVolumeError {
runner: self.clone(),
name: name.clone(),
source: Box::new(source),
})?;
Ok(VolumeName(name))
}
fn guard_runner<I>(&self, container: &Container<I>) -> Result<(), RunnerError>
where
I: ToRunnableContainer,
{
if &container.runner != self {
return Err(RunnerError::DifferentRunner {
runner: self.clone(),
container_runner: Box::new(container.runner.clone()),
});
}
Ok(())
}
pub async fn network_ip<I>(
&self,
container: &Container<I>,
network: &Network,
) -> Result<IpAddr, RunnerError>
where
I: ToRunnableContainer,
{
self.guard_runner(container)?;
let id = container.id;
let Some(net) = network.name() else {
return Err(RunnerError::ExpectedNetworkNameError {
runner: self.clone(),
network: Box::new(network.clone()),
container: id,
});
};
let container_network = match self {
Self::Docker(runner) => runner.network_ip(id, net).await,
Self::Podman(runner) => runner.network_ip(id, net).await,
Self::Nerdctl(runner) => runner.network_ip(id, net).await,
}
.map_err(|source| RunnerError::FindNetworkIpError {
runner: self.clone(),
network: Box::new(network.clone()),
container: Box::new(id),
source: Box::new(source),
})?;
let Some(ip) = container_network.ip_address else {
return Err(RunnerError::NoNetworkIp {
runner: self.clone(),
network: Box::new(network.clone()),
container: id,
});
};
Ok(ip.0)
}
pub async fn container_host_ip(&self) -> Result<IpAddr, RunnerError> {
let host_ip = match self {
Self::Docker(runner) => runner.host().await,
Self::Podman(runner) => runner.host().await,
Self::Nerdctl(runner) => runner.host().await,
}
.map_err(|source| RunnerError::HostIpError {
runner: self.clone(),
source: Box::new(source),
})?;
Ok(host_ip.0)
}
pub async fn exec<I, S>(
&self,
container: &Container<I>,
exec_command: impl IntoIterator<Item = S> + Debug,
) -> Result<String, RunnerError>
where
S: Into<String>,
I: ToRunnableContainer,
{
self.guard_runner(container)?;
let id = container.id;
let exec_command = exec_command.into_iter().map(Into::into).collect();
match self {
Self::Docker(runner) => runner.exec(id, exec_command).await,
Self::Podman(runner) => runner.exec(id, exec_command).await,
Self::Nerdctl(runner) => runner.exec(id, exec_command).await,
}
.map_err(|source| RunnerError::ExecError {
runner: self.clone(),
id,
source: Box::new(source),
})
}
pub fn stop<I>(&self, container: &Container<I>) -> Result<(), RunnerError>
where
I: ToRunnableContainer,
{
self.guard_runner(container)?;
let id = container.id;
match self {
Self::Docker(runner) => runner.stop(id),
Self::Podman(runner) => runner.stop(id),
Self::Nerdctl(runner) => runner.stop(id),
}
.map_err(|source| RunnerError::StopError {
runner: self.clone(),
id,
source: Box::new(source),
})
}
}