use foundation_netio::shared::http::SimpleHeader;
use foundation_netio::PreparedRequestBuilder;
use crate::client::body::{ByteBody, LogStream, StatsStream};
use crate::error::DockerError;
use crate::generated::archive::{container_archive_request, ContainerArchiveArgs};
use crate::generated::attach::{container_attach_request, ContainerAttachArgs};
use crate::generated::export::{container_export_request, ContainerExportArgs};
use crate::generated::logs::{container_logs_request, ContainerLogsArgs};
use crate::generated::shared::ApiError;
use crate::generated::update::{container_update_request, ContainerUpdateArgs, ContainerUpdateResponse};
use crate::generated::changes::{container_changes_request, ContainerChangesArgs};
use crate::generated::json::{container_list_request, ContainerListArgs};
use crate::generated::kill::{container_kill_request, ContainerKillArgs};
use crate::generated::prune::{container_prune_request, ContainerPruneArgs};
use crate::generated::rename::{container_rename_request, ContainerRenameArgs};
use crate::generated::resize::{container_resize_request, ContainerResizeArgs};
use crate::generated::restart::{container_restart_request, ContainerRestartArgs};
use crate::generated::stats::{container_stats_request, ContainerStatsArgs, ContainerStatsResponse};
use crate::generated::top::{container_top_request, ContainerTopArgs, ContainerTopResponse};
use super::DockerClient;
use super::NoMod;
#[derive(Debug, Clone)]
#[allow(
clippy::struct_excessive_bools,
reason = "one field per boolean query parameter of the endpoint"
)]
pub struct ContainerLogsOptions {
pub follow: bool,
pub stdout: bool,
pub stderr: bool,
pub since: Option<String>,
pub until: Option<String>,
pub timestamps: bool,
pub tail: Option<String>,
}
impl Default for ContainerLogsOptions {
fn default() -> Self {
Self {
follow: false,
stdout: true,
stderr: true,
since: None,
until: None,
timestamps: false,
tail: None,
}
}
}
#[derive(Debug, Clone)]
#[allow(
clippy::struct_excessive_bools,
reason = "one field per boolean query parameter of the endpoint"
)]
pub struct ContainerAttachOptions {
pub detach_keys: Option<String>,
pub logs: bool,
pub stream: bool,
pub stdin: bool,
pub stdout: bool,
pub stderr: bool,
}
impl Default for ContainerAttachOptions {
fn default() -> Self {
Self {
detach_keys: None,
logs: false,
stream: true,
stdin: false,
stdout: true,
stderr: true,
}
}
}
impl DockerClient {
pub async fn restart_container(
&self,
id: &str,
signal: Option<&str>,
t: Option<u32>,
) -> Result<(), DockerError> {
let args = ContainerRestartArgs {
id: id.to_string(),
signal: signal.map(str::to_string),
t: t.map(|v| v.to_string()),
};
container_restart_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(())
}
pub async fn kill_container(
&self,
id: &str,
signal: Option<&str>,
) -> Result<(), DockerError> {
let args = ContainerKillArgs {
id: id.to_string(),
signal: signal.map(str::to_string),
};
container_kill_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(())
}
pub async fn rename_container(&self, id: &str, name: &str) -> Result<(), DockerError> {
let args = ContainerRenameArgs {
id: id.to_string(),
name: Some(name.to_string()),
};
container_rename_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(())
}
pub async fn resize_container(
&self,
id: &str,
h: Option<u32>,
w: Option<u32>,
) -> Result<(), DockerError> {
let args = ContainerResizeArgs {
id: id.to_string(),
h: h.map(|v| v.to_string()),
w: w.map(|v| v.to_string()),
};
container_resize_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(())
}
pub async fn container_top(
&self,
id: &str,
ps_args: Option<&str>,
) -> Result<ContainerTopResponse, DockerError> {
let args = ContainerTopArgs {
id: id.to_string(),
ps_args: ps_args.map(str::to_string),
};
let response = container_top_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(response.body)
}
pub async fn container_changes(
&self,
id: &str,
) -> Result<serde_json::Value, DockerError> {
let args = ContainerChangesArgs {
id: id.to_string(),
};
let response = container_changes_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(response.body)
}
pub async fn container_logs(
&self,
id: &str,
opts: &ContainerLogsOptions,
) -> Result<LogStream, DockerError> {
let args = ContainerLogsArgs {
id: id.to_string(),
follow: Some(opts.follow.to_string()),
stdout: Some(opts.stdout.to_string()),
stderr: Some(opts.stderr.to_string()),
since: opts.since.clone(),
until: opts.until.clone(),
timestamps: Some(opts.timestamps.to_string()),
tail: opts.tail.clone(),
};
let response =
container_logs_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(LogStream::new(response.body, self.limits().max_log_frame))
}
pub async fn container_stats(
&self,
id: &str,
one_shot: bool,
) -> Result<ContainerStatsResponse, DockerError> {
let mut stats = self.send_container_stats(id, false, one_shot).await?;
match stats.next().await {
Some(sample) => sample,
None => Err(DockerError::JsonParse(format!(
"container {id} stats: the daemon answered with an empty body"
))),
}
}
pub async fn container_stats_stream(&self, id: &str) -> Result<StatsStream, DockerError> {
self.send_container_stats(id, true, false).await
}
async fn send_container_stats(
&self,
id: &str,
stream: bool,
one_shot: bool,
) -> Result<StatsStream, DockerError> {
let args = ContainerStatsArgs {
id: id.to_string(),
stream: Some(stream.to_string()),
one_shot: Some(one_shot.to_string()),
};
let response =
container_stats_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(StatsStream::new(response.body))
}
pub async fn container_export(&self, id: &str) -> Result<ByteBody, DockerError> {
let args = ContainerExportArgs { id: id.to_string() };
let response =
container_export_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(ByteBody::new(response.body))
}
pub async fn container_archive(&self, id: &str, path: &str) -> Result<ByteBody, DockerError> {
let args = ContainerArchiveArgs {
id: id.to_string(),
path: Some(path.to_string()),
};
let response =
container_archive_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(ByteBody::new(response.body))
}
pub async fn container_attach(
&self,
id: &str,
opts: &ContainerAttachOptions,
) -> Result<LogStream, DockerError> {
let args = ContainerAttachArgs {
id: id.to_string(),
detach_keys: opts.detach_keys.clone(),
logs: Some(opts.logs.to_string()),
stream: Some(opts.stream.to_string()),
stdin: Some(opts.stdin.to_string()),
stdout: Some(opts.stdout.to_string()),
stderr: Some(opts.stderr.to_string()),
};
let response =
container_attach_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(LogStream::new(response.body, self.limits().max_log_frame))
}
pub async fn container_list(
&self,
all: bool,
limit: Option<u32>,
size: bool,
filters: Option<&str>,
) -> Result<serde_json::Value, DockerError> {
let args = ContainerListArgs {
all: Some(all.to_string()),
limit: limit.map(|v| v.to_string()),
size: Some(size.to_string()),
filters: filters.map(str::to_string),
};
let response = container_list_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(response.body)
}
pub async fn container_prune(
&self,
filters: Option<&str>,
) -> Result<(), DockerError> {
let args = ContainerPruneArgs {
filters: filters.map(str::to_string),
};
container_prune_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(())
}
pub async fn update_container(
&self,
id: &str,
config: serde_json::Value,
) -> Result<ContainerUpdateResponse, DockerError> {
let args = ContainerUpdateArgs {
id: id.to_string(),
body: super::generated_body("container update", config)?,
};
let response =
container_update_request(self.api(), &args, &self.api_url().await?, None::<NoMod>).await?;
Ok(response.body)
}
pub async fn upload_to_container(
&self,
id: &str,
path: &str,
tar: Vec<u8>,
no_overwrite_dir_non_dir: Option<bool>,
copy_uidgid: Option<bool>,
) -> Result<(), DockerError> {
let endpoint_url = format!("{}/containers/{id}/archive", self.api_url().await?);
let mut builder = PreparedRequestBuilder::put(&endpoint_url)
.map_err(|e| ApiError::RequestBuildFailed(e.to_string()))?;
builder = builder.query("path", Some(path));
builder = builder.query(
"noOverwriteDirNonDir",
no_overwrite_dir_non_dir.map(|v| v.to_string()).as_deref(),
);
builder = builder.query("copyUIDGID", copy_uidgid.map(|v| v.to_string()).as_deref());
builder = builder.header(SimpleHeader::custom("Content-Type"), "application/x-tar");
builder = builder.body_bytes(tar);
let response = self.http().send_async(builder.build()).await
.map_err(|e| ApiError::RequestSendFailed(e.to_string()))?;
let status: usize = response.get_status().into();
let headers = response.get_headers_ref().clone();
if status < 200 || status >= 300 {
return Err(ApiError::HttpStatus { code: status as u16, headers, body: None }.into());
}
Ok(())
}
pub async fn get_container_archive_info(
&self,
id: &str,
path: &str,
) -> Result<String, DockerError> {
let endpoint_url = format!("{}/containers/{id}/archive", self.api_url().await?);
let mut builder = PreparedRequestBuilder::head(&endpoint_url)
.map_err(|e| ApiError::RequestBuildFailed(e.to_string()))?;
builder = builder.query("path", Some(path));
let response = self.http().send_async(builder.build()).await
.map_err(|e| ApiError::RequestSendFailed(e.to_string()))?;
let status: usize = response.get_status().into();
let headers = response.get_headers_ref().clone();
if status < 200 || status >= 300 {
return Err(ApiError::HttpStatus { code: status as u16, headers, body: None }.into());
}
let stat = headers
.iter()
.find(|(k, _)| k.to_string().eq_ignore_ascii_case("x-docker-container-path-stat"))
.and_then(|(_, v)| v.first())
.cloned();
stat.ok_or_else(|| {
ApiError::RequestSendFailed("missing X-Docker-Container-Path-Stat header".to_string())
.into()
})
}
}