use crate::api::AppState;
use bollard::{
container::PruneContainersOptions, image::PruneImagesOptions, network::PruneNetworksOptions,
volume::PruneVolumesOptions,
};
use shared::Response as RpResponse;
pub struct PruneStat {
pub count: u32,
pub reclaimed_bytes: u64,
}
pub async fn prune_containers(state: AppState) -> RpResponse {
match prune_containers_core(&state).await {
Ok(s) => RpResponse::PruneResult {
count: s.count,
reclaimed_bytes: s.reclaimed_bytes,
},
Err(e) => RpResponse::err("DockerError", e),
}
}
pub(crate) async fn prune_containers_core(state: &AppState) -> Result<PruneStat, String> {
let result = state
.docker
.inner
.prune_containers(None::<PruneContainersOptions<String>>)
.await;
state.docker_cache.df.invalidate().await;
state.docker_cache.networks.invalidate().await;
match result {
Ok(r) => Ok(PruneStat {
count: r.containers_deleted.map(|v| v.len() as u32).unwrap_or(0),
reclaimed_bytes: r.space_reclaimed.unwrap_or(0) as u64,
}),
Err(e) => Err(e.to_string()),
}
}
pub async fn prune_volumes(state: AppState, all: bool) -> RpResponse {
match prune_volumes_core(&state, all).await {
Ok(s) => RpResponse::PruneResult {
count: s.count,
reclaimed_bytes: s.reclaimed_bytes,
},
Err(e) => RpResponse::err("DockerError", e),
}
}
pub(crate) async fn prune_volumes_core(state: &AppState, all: bool) -> Result<PruneStat, String> {
let mut filters = std::collections::HashMap::new();
if all {
filters.insert("all", vec!["true"]);
}
let result = state
.docker
.inner
.prune_volumes(Some(PruneVolumesOptions { filters }))
.await;
state.docker_cache.df.invalidate().await;
match result {
Ok(r) => Ok(PruneStat {
count: r.volumes_deleted.map(|v| v.len() as u32).unwrap_or(0),
reclaimed_bytes: r.space_reclaimed.unwrap_or(0) as u64,
}),
Err(e) => Err(e.to_string()),
}
}
pub async fn prune_images(state: AppState, all: bool) -> RpResponse {
match prune_images_core(&state, all).await {
Ok(s) => RpResponse::PruneResult {
count: s.count,
reclaimed_bytes: s.reclaimed_bytes,
},
Err(e) => RpResponse::err("DockerError", e),
}
}
pub(crate) async fn prune_images_core(state: &AppState, all: bool) -> Result<PruneStat, String> {
let mut filters = std::collections::HashMap::new();
if all {
filters.insert("dangling", vec!["false"]);
}
let result = state
.docker
.inner
.prune_images(Some(PruneImagesOptions { filters }))
.await;
state.docker_cache.df.invalidate().await;
match result {
Ok(r) => Ok(PruneStat {
count: r.images_deleted.map(|v| v.len() as u32).unwrap_or(0),
reclaimed_bytes: r.space_reclaimed.unwrap_or(0) as u64,
}),
Err(e) => Err(e.to_string()),
}
}
pub async fn prune_build_cache(_state: AppState) -> RpResponse {
match prune_build_cache_core().await {
Ok(s) => RpResponse::PruneResult {
count: s.count,
reclaimed_bytes: s.reclaimed_bytes,
},
Err(e) => RpResponse::err("DockerError", e),
}
}
pub(crate) async fn prune_build_cache_core() -> Result<PruneStat, String> {
let output = tokio::process::Command::new("docker")
.args(["builder", "prune", "-f"])
.output()
.await;
match output {
Ok(out) if out.status.success() => {
let text = String::from_utf8_lossy(&out.stdout);
let reclaimed_bytes = parse_reclaimed_space(&text);
Ok(PruneStat {
count: 0,
reclaimed_bytes,
})
}
Ok(out) => Err(String::from_utf8_lossy(&out.stderr).trim().to_string()),
Err(e) => Err(e.to_string()),
}
}
fn parse_reclaimed_space(output: &str) -> u64 {
for line in output.lines() {
let line = line.trim();
if let Some(rest) = line.strip_prefix("Total reclaimed space:") {
let rest = rest.trim();
if let Some((num, unit)) = rest.split_once(char::is_whitespace) {
let n: f64 = num.parse().unwrap_or(0.0);
return match unit.to_uppercase().as_str() {
"GB" | "GIB" => (n * 1_000_000_000.0) as u64,
"MB" | "MIB" => (n * 1_000_000.0) as u64,
"KB" | "KIB" => (n * 1_000.0) as u64,
_ => n as u64,
};
}
}
}
0
}
pub async fn prune_networks(state: AppState) -> RpResponse {
match prune_networks_core(&state).await {
Ok(s) => RpResponse::PruneResult {
count: s.count,
reclaimed_bytes: s.reclaimed_bytes,
},
Err(e) => RpResponse::err("DockerError", e),
}
}
pub(crate) async fn prune_networks_core(state: &AppState) -> Result<PruneStat, String> {
let result = state
.docker
.inner
.prune_networks(None::<PruneNetworksOptions<String>>)
.await;
state.docker_cache.networks.invalidate().await;
match result {
Ok(r) => Ok(PruneStat {
count: r.networks_deleted.map(|v| v.len() as u32).unwrap_or(0),
reclaimed_bytes: 0,
}),
Err(e) => Err(e.to_string()),
}
}