diff options
author | Matthias Beyer <matthias.beyer@atos.net> | 2021-03-08 12:03:08 +0100 |
---|---|---|
committer | Matthias Beyer <matthias.beyer@atos.net> | 2021-03-08 12:44:27 +0100 |
commit | faafd9df072c9aaf0e276f4736f3f56011033dc7 (patch) | |
tree | ae523daaeefd02412966cca066a317a2ac44baf6 | |
parent | 17d1b74c7674da470fe61676c873062404ab6e64 (diff) |
Add "containers prune" subcommand
Signed-off-by: Matthias Beyer <matthias.beyer@atos.net>
-rw-r--r-- | src/cli.rs | 25 | ||||
-rw-r--r-- | src/commands/endpoint.rs | 52 |
2 files changed, 77 insertions, 0 deletions
@@ -879,6 +879,31 @@ pub fn cli<'a>() -> App<'a> { .subcommand(App::new("containers") .version(crate_version!()) .about("Work with the containers of the endpoint(s)") + .subcommand(App::new("prune") + .version(crate_version!()) + .about("Remove exited containers") + .arg(Arg::new("older_than") + .required(false) + .multiple(false) + .long("older-than") + .takes_value(true) + .value_name("DATE") + .about("List only containers that are older than DATE") + .validator(parse_date_from_string) + .conflicts_with("newer_than") + ) + + .arg(Arg::new("newer_than") + .required(false) + .multiple(false) + .long("newer-than") + .takes_value(true) + .value_name("DATE") + .about("List only containers that are newer than DATE") + .validator(parse_date_from_string) + .conflicts_with("older_than") + ) + ) .subcommand(App::new("list") .version(crate_version!()) .about("List the containers and stats about them") diff --git a/src/commands/endpoint.rs b/src/commands/endpoint.rs index 67e6929..7542d68 100644 --- a/src/commands/endpoint.rs +++ b/src/commands/endpoint.rs @@ -11,6 +11,7 @@ use std::str::FromStr; use std::sync::Arc; +use anyhow::Error; use anyhow::Result; use anyhow::anyhow; use clap::ArgMatches; @@ -160,6 +161,7 @@ async fn containers(endpoint_names: Vec<String>, ) -> Result<()> { match matches.subcommand() { Some(("list", matches)) => containers_list(endpoint_names, matches, config).await, + Some(("prune", matches)) => containers_prune(endpoint_names, matches, config).await, Some((other, _)) => Err(anyhow!("Unknown subcommand: {}", other)), None => Err(anyhow!("No subcommand")), } @@ -223,6 +225,56 @@ async fn containers_list(endpoint_names: Vec<String>, crate::commands::util::display_data(hdr, data, csv) } +async fn containers_prune(endpoint_names: Vec<String>, + matches: &ArgMatches, + config: &Configuration, +) -> Result<()> { + let older_than_filter = matches.value_of("older_than") + .map(humantime::parse_rfc3339_weak) + .transpose()? + .map(chrono::DateTime::<chrono::Local>::from); + let newer_than_filter = matches.value_of("newer_than") + .map(humantime::parse_rfc3339_weak) + .transpose()? + .map(chrono::DateTime::<chrono::Local>::from); + + let stats = connect_to_endpoints(config, &endpoint_names) + .await? + .into_iter() + .map(move |ep| async move { + let stats = ep.container_stats() + .await? + .into_iter() + .filter(|stat| stat.state == "exited") + .filter(|stat| older_than_filter.as_ref().map(|time| time > &stat.created).unwrap_or(true)) + .filter(|stat| newer_than_filter.as_ref().map(|time| time < &stat.created).unwrap_or(true)) + .map(|stat| (ep.clone(), stat)) + .collect::<Vec<(_, _)>>(); + Ok(stats) + }) + .collect::<futures::stream::FuturesUnordered<_>>() + .collect::<Result<Vec<_>>>() + .await?; + + let prompt = format!("Really delete {} Containers?", stats.iter().flatten().count()); + dialoguer::Confirm::new().with_prompt(prompt).interact()?; + + stats.into_iter() + .map(Vec::into_iter) + .flatten() + .map(|(ep, stat)| async move { + ep.get_container_by_id(&stat.id) + .await? + .ok_or_else(|| anyhow!("Failed to find existing container {}", stat.id))? + .delete() + .await + .map_err(Error::from) + }) + .collect::<futures::stream::FuturesUnordered<_>>() + .collect::<Result<()>>() + .await +} + /// Helper function to connect to all endpoints from the configuration, that appear (by name) in /// the `endpoint_names` list pub(super) async fn connect_to_endpoints(config: &Configuration, endpoint_names: &[String]) -> Result<Vec<Arc<Endpoint>>> { |