use crate::DockerClient;
use crate::error::{DockerError, Result};
use bollard::container::*;
use bollard::exec::CreateExecOptions;
use bollard::models::*;
use futures_util::StreamExt;
use std::collections::HashMap;
use tracing::{debug, info};
mod builder;
pub use builder::*;
pub struct Containers<'a> {
client: &'a DockerClient,
}
impl<'a> Containers<'a> {
pub(crate) fn new(client: &'a DockerClient) -> Self {
Self { client }
}
pub fn create(&self, image: impl Into<String>) -> ContainerBuilder<'a> {
ContainerBuilder::new(self.client, image)
}
pub fn get(&self, name_or_id: impl Into<String>) -> ContainerRef<'a> {
ContainerRef::new(self.client, name_or_id.into())
}
pub async fn list(&self, all: bool) -> Result<Vec<ContainerSummary>> {
let mut filters = HashMap::new();
if all {
filters.insert("all", vec!["true"]);
}
let options: Option<ListContainersOptions<String>> = Some(ListContainersOptions {
all,
..Default::default()
});
self.client
.docker
.list_containers(options)
.await
.map_err(|e| DockerError::Other(format!("Failed to list containers: {}", e)))
}
pub async fn prune(&self) -> Result<ContainerPruneResponse> {
info!("Pruning stopped containers...");
self.client
.docker
.prune_containers(Some(
bollard::query_parameters::PruneContainersOptions::default(),
))
.await
.map_err(|e| DockerError::Other(format!("Failed to prune containers: {}", e)))
}
}
pub struct ContainerRef<'a> {
client: &'a DockerClient,
id: String,
}
impl<'a> ContainerRef<'a> {
pub(crate) fn new(client: &'a DockerClient, id: String) -> Self {
Self { client, id }
}
pub fn id(&self) -> &str {
&self.id
}
pub async fn start(&self) -> Result<()> {
info!("Starting container: {}", self.id);
self.client
.docker
.start_container(
&self.id,
Some(bollard::query_parameters::StartContainerOptions::default()),
)
.await
.map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to start: {}", e)))
}
pub async fn stop(&self, timeout: Option<i64>) -> Result<()> {
info!("Stopping container: {}", self.id);
let options = timeout.map(|t| StopContainerOptions { t });
self.client
.docker
.stop_container(&self.id, options)
.await
.map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to stop: {}", e)))
}
pub async fn restart(&self, timeout: Option<i64>) -> Result<()> {
info!("Restarting container: {}", self.id);
let options = timeout.map(|t| RestartContainerOptions { t: t as isize });
self.client
.docker
.restart_container(&self.id, options)
.await
.map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to restart: {}", e)))
}
pub async fn kill(&self, signal: Option<&str>) -> Result<()> {
info!("Killing container: {}", self.id);
let options = signal.map(|s| KillContainerOptions { signal: s });
self.client
.docker
.kill_container(&self.id, options)
.await
.map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to kill: {}", e)))
}
pub async fn pause(&self) -> Result<()> {
info!("Pausing container: {}", self.id);
self.client
.docker
.pause_container(&self.id)
.await
.map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to pause: {}", e)))
}
pub async fn unpause(&self) -> Result<()> {
info!("Unpausing container: {}", self.id);
self.client
.docker
.unpause_container(&self.id)
.await
.map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to unpause: {}", e)))
}
pub async fn remove(&self, force: bool, remove_volumes: bool) -> Result<()> {
info!("Removing container: {}", self.id);
let options = Some(RemoveContainerOptions {
force,
v: remove_volumes,
..Default::default()
});
self.client
.docker
.remove_container(&self.id, options)
.await
.map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to remove: {}", e)))
}
pub async fn inspect(&self) -> Result<ContainerInspectResponse> {
debug!("Inspecting container: {}", self.id);
self.client
.docker
.inspect_container(
&self.id,
Some(bollard::query_parameters::InspectContainerOptions::default()),
)
.await
.map_err(|e| DockerError::ContainerNotFound(format!("{}: {}", self.id, e)))
}
pub async fn logs(
&self,
follow: bool,
stdout: bool,
stderr: bool,
tail: Option<&str>,
) -> Result<Vec<String>> {
debug!("Getting logs for container: {}", self.id);
let options: Option<LogsOptions<String>> = Some(LogsOptions {
follow,
stdout,
stderr,
tail: tail.unwrap_or("all").to_string(),
..Default::default()
});
let mut stream = self.client.docker.logs(&self.id, options);
let mut logs = Vec::new();
while let Some(chunk) = stream.next().await {
match chunk {
Ok(output) => {
logs.push(output.to_string());
}
Err(e) => {
return Err(DockerError::ContainerOperationFailed(format!(
"Failed to get logs: {}",
e
)));
}
}
}
Ok(logs)
}
pub async fn rename(&self, new_name: impl Into<String>) -> Result<()> {
let new_name = new_name.into();
info!("Renaming container {} to {}", self.id, new_name);
let options = RenameContainerOptions { name: new_name };
self.client
.docker
.rename_container(&self.id, options)
.await
.map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to rename: {}", e)))
}
pub async fn exec(&self, cmd: Vec<String>, tty: bool) -> Result<String> {
debug!("Executing command in container {}: {:?}", self.id, cmd);
let config = CreateExecOptions {
cmd: Some(cmd),
attach_stdout: Some(true),
attach_stderr: Some(true),
tty: Some(tty),
..Default::default()
};
let response = self
.client
.docker
.create_exec(&self.id, config)
.await
.map_err(|e| DockerError::ContainerOperationFailed(format!("Failed to exec: {}", e)))?;
Ok(response.id)
}
}
impl DockerClient {
pub fn containers(&self) -> Containers<'_> {
Containers::new(self)
}
}