use crate::compose::types::ComposeFile;
use crate::error::Result;
use crate::libpod::{urlencoded, API_PREFIX};
use super::Engine;
const DEFAULT_STOP_GRACE_SECS: i32 = 10;
impl Engine {
pub async fn down_by_label(&self, remove_volumes: bool) -> Result<()> {
let grace = self.stop_timeout.unwrap_or(DEFAULT_STOP_GRACE_SECS);
for container_name in self.list_project_container_names(None).await? {
let stop_path = format!(
"{API_PREFIX}/containers/{}/stop?t={}",
urlencoded(&container_name),
super::targets::stop_timeout_param(grace),
);
if let Err(e) = self
.client
.post_empty_ok_within(&stop_path, super::targets::stop_deadline(grace))
.await
{
if !e.is_status(404) {
tracing::warn!("could not stop {container_name}: {e}");
}
}
let rm_path = super::container_rm_path(&container_name, remove_volumes);
match self.client.delete_ok(&rm_path).await {
Ok(()) => crate::ui::progress_line("Container", &container_name, "Removed"),
Err(e) if e.is_status(404) => {}
Err(e) => tracing::warn!("could not remove {container_name}: {e}"),
}
}
self.remove_project_networks_by_label().await;
if remove_volumes {
self.remove_project_volumes_by_label().await;
}
self.remove_internal_secrets(&ComposeFile::default())
.await?;
Ok(())
}
pub(super) async fn remove_project_networks_by_label(&self) {
let net_filters =
serde_json::json!({ "label": [format!("podup.project={}", self.project)] });
let list_path = format!(
"{API_PREFIX}/networks/json?filters={}",
urlencoded(&net_filters.to_string()),
);
let Ok(nets) = self
.client
.get_json::<Vec<serde_json::Value>>(&list_path)
.await
else {
return;
};
for net in nets {
let Some(net_name) = net.get("name").and_then(|n| n.as_str()) else {
continue;
};
let del = format!("{API_PREFIX}/networks/{}", urlencoded(net_name));
match self.client.delete_ok(&del).await {
Ok(_) => crate::ui::progress_line("Network", net_name, "Removed"),
Err(e) if e.is_status(404) => {}
Err(e) => tracing::warn!("could not remove network {net_name}: {e}"),
}
}
}
pub(super) async fn remove_project_volumes_by_label(&self) {
let path = format!("{API_PREFIX}/volumes/json");
let Ok(vols) = self.client.get_json::<Vec<serde_json::Value>>(&path).await else {
return;
};
for vol in vols {
if !volume_owned_by(&vol, &self.project) {
continue;
}
let Some(name) = vol.get("Name").and_then(|n| n.as_str()) else {
continue;
};
let del = format!("{API_PREFIX}/volumes/{}", urlencoded(name));
match self.client.delete_ok(&del).await {
Ok(_) => crate::ui::progress_line("Volume", name, "Removed"),
Err(e) if e.is_status(404) => {}
Err(e) => tracing::warn!("could not remove volume {name}: {e}"),
}
}
}
}
fn volume_owned_by(vol: &serde_json::Value, project: &str) -> bool {
vol.get("Labels")
.and_then(|l| l.get("podup.project"))
.and_then(|v| v.as_str())
== Some(project)
}
#[cfg(test)]
mod tests {
use super::volume_owned_by;
#[test]
fn volume_owned_by_matches_project_label() {
let vol = serde_json::json!({
"Name": "proj_data",
"Labels": { "podup.project": "proj", "extra": "1" },
});
assert!(volume_owned_by(&vol, "proj"));
assert!(!volume_owned_by(&vol, "other"));
let unlabelled = serde_json::json!({ "Name": "loose", "Labels": {} });
assert!(!volume_owned_by(&unlabelled, "proj"));
let no_labels = serde_json::json!({ "Name": "loose" });
assert!(!volume_owned_by(&no_labels, "proj"));
}
}