diff options
-rw-r--r-- | src/cli.rs | 67 | ||||
-rw-r--r-- | src/commands/db.rs | 41 | ||||
-rw-r--r-- | src/commands/endpoint.rs | 32 | ||||
-rw-r--r-- | src/commands/util.rs | 33 |
4 files changed, 129 insertions, 44 deletions
@@ -362,6 +362,28 @@ pub fn cli<'a>() -> App<'a> { .takes_value(false) .about("Format output as CSV") ) + + .arg(arg_older_than_date("List only releases older than DATE")) + .arg(arg_newer_than_date("List only releases newer than DATE")) + + .arg(Arg::new("package_name_regex") + .required(false) + .multiple(false) + .long("pkg") + .short('p') + .takes_value(true) + .value_name("REGEX") + .about("Limit search with package name matching REGEX") + ) + .arg(Arg::new("package_version_constraint") + .required(false) + .multiple(false) + .long("version") + .short('v') + .takes_value(true) + .value_name("VERSION_CONSTRAINT") + .about("Limit search for package in version VERSION") + ) ) ) @@ -1018,14 +1040,14 @@ pub fn cli<'a>() -> App<'a> { .subcommand(App::new("prune") .version(crate_version!()) .about("Remove exited containers") - .arg(arg_older_than_date()) - .arg(arg_newer_than_date()) + .arg(arg_older_than_date("Prune only containers older than DATE")) + .arg(arg_newer_than_date("Prune only containers newer than DATE")) ) .subcommand(App::new("stop") .version(crate_version!()) .about("Stop running containers") - .arg(arg_older_than_date()) - .arg(arg_newer_than_date()) + .arg(arg_older_than_date("Stop only containers older than DATE")) + .arg(arg_newer_than_date("Stop only containers newer than DATE")) .arg(Arg::new("timeout") .required(false) .multiple(false) @@ -1065,8 +1087,8 @@ pub fn cli<'a>() -> App<'a> { .about("List only containers of IMAGE") ) - .arg(arg_older_than_date()) - .arg(arg_newer_than_date()) + .arg(arg_older_than_date("List only containers older than DATE")) + .arg(arg_newer_than_date("List only containers newer than DATE")) ) .subcommand(App::new("top") .version(crate_version!()) @@ -1246,18 +1268,18 @@ fn dir_exists_validator(s: &str) -> Result<(), String> { } } -fn arg_older_than_date<'a>() -> Arg<'a> { +fn arg_older_than_date(about: &str) -> 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") + .about(about) .long_about(r#" - List only containers that are older than DATE - DATE can be a freeform date, for example '2h' + It can also be a exact date: '2020-01-01 00:12:45' + If the hour-minute-second part is omitted, " 00:00:00" is appended automatically. Supported suffixes: @@ -1274,21 +1296,20 @@ fn arg_older_than_date<'a>() -> Arg<'a> { "#) .validator(parse_date_from_string) - .conflicts_with("newer_than") } -fn arg_newer_than_date<'a>() -> Arg<'a> { +fn arg_newer_than_date(about: &str) -> 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") + .about(about) .long_about(r#" - List only containers that are newer than DATE - DATE can be a freeform date, for example '2h' + It can also be a exact date: '2020-01-01 00:12:45' + If the hour-minute-second part is omitted, " 00:00:00" is appended automatically. Supported suffixes: @@ -1305,11 +1326,23 @@ fn arg_newer_than_date<'a>() -> Arg<'a> { "#) .validator(parse_date_from_string) - .conflicts_with("older_than") } fn parse_date_from_string(s: &str) -> std::result::Result<(), String> { - humantime::parse_duration(s).map_err(|e| e.to_string()).map(|_| ()) + humantime::parse_duration(s) + .map_err(|e| e.to_string()) + .map(|_| ()) + .or_else(|_| { + humantime::parse_rfc3339_weak(s) + .map_err(|e| e.to_string()) + .map(|_| ()) + }) + .or_else(|_| { + let s = format!("{} 00:00:00", s); + humantime::parse_rfc3339_weak(&s) + .map_err(|e| e.to_string()) + .map(|_| ()) + }) } fn parse_usize(s: &str) -> std::result::Result<(), String> { diff --git a/src/commands/db.rs b/src/commands/db.rs index 5b9a85a..2e86954 100644 --- a/src/commands/db.rs +++ b/src/commands/db.rs @@ -14,6 +14,7 @@ use std::io::Write; use std::path::PathBuf; use std::process::Command; use std::str::FromStr; +use std::convert::TryFrom; use anyhow::Context; use anyhow::Error; @@ -32,9 +33,11 @@ use log::info; use log::trace; use crate::config::Configuration; -use crate::db::models; use crate::db::DbConnectionConfig; +use crate::db::models; use crate::log::JobResult; +use crate::package::PackageVersion; +use crate::package::PackageVersionConstraint; use crate::package::Script; use crate::schema; @@ -691,7 +694,7 @@ fn releases(conn_cfg: DbConnectionConfig<'_>, config: &Configuration, matches: & let csv = matches.is_present("csv"); let conn = conn_cfg.establish_connection()?; let header = crate::commands::util::mk_header(["Package", "Version", "Date", "Path"].to_vec()); - let data = schema::jobs::table + let mut query = schema::jobs::table .inner_join(schema::packages::table) .inner_join(schema::artifacts::table) .inner_join(schema::releases::table @@ -701,6 +704,28 @@ fn releases(conn_cfg: DbConnectionConfig<'_>, config: &Configuration, matches: & .order_by(schema::packages::dsl::name.asc()) .then_order_by(schema::packages::dsl::version.asc()) .then_order_by(schema::releases::release_date.asc()) + .into_boxed(); + + if let Some(date) = crate::commands::util::get_date_filter("older_than", matches)? { + query = query.filter(schema::releases::release_date.lt(date)); + } + + if let Some(date) = crate::commands::util::get_date_filter("newer_than", matches)? { + query = query.filter(schema::releases::release_date.gt(date)); + } + + let package_name_regex_filter = matches.value_of("package_name_regex") + .map(crate::commands::util::mk_package_name_regex) + .transpose() + .context("Constructing package name regex")?; + + let package_version_filter = matches.value_of("package_version_constraint") + .map(PackageVersionConstraint::try_from) + .transpose() + .context("Parsing package version constraint") + .context("A valid package version constraint looks like this: '=1.0.0'")?; + + let data = query .select({ let art = schema::artifacts::all_columns; let pac = schema::packages::all_columns; @@ -710,8 +735,18 @@ fn releases(conn_cfg: DbConnectionConfig<'_>, config: &Configuration, matches: & }) .load::<(models::Artifact, models::Package, models::Release, models::ReleaseStore)>(&conn)? .into_iter() + .filter(|(_, package, _, _)| { + package_name_regex_filter.as_ref() + .map(|regex| regex.captures(&package.name).is_some()) + .unwrap_or(true) + }) + .filter(|(_, package, _, _)| { + package_version_filter.as_ref() + .map(|verf| verf.matches(&PackageVersion::from(package.version.clone()))) + .unwrap_or(true) + }) .filter_map(|(art, pack, rel, rstore)| { - let p = config.releases_directory().join(rstore.store_name).join(&art.path); + let p = config.releases_directory().join(rstore.store_name).join(&art.path); if p.is_file() { Some(vec![ diff --git a/src/commands/endpoint.rs b/src/commands/endpoint.rs index 4de51d2..6fd7d69 100644 --- a/src/commands/endpoint.rs +++ b/src/commands/endpoint.rs @@ -182,8 +182,8 @@ async fn containers_list(endpoint_names: Vec<EndpointName>, ) -> Result<()> { let list_stopped = matches.is_present("list_stopped"); let filter_image = matches.value_of("filter_image"); - let older_than_filter = get_date_filter("older_than", matches)?; - let newer_than_filter = get_date_filter("newer_than", matches)?; + let older_than_filter = crate::commands::util::get_date_filter("older_than", matches)?; + let newer_than_filter = crate::commands::util::get_date_filter("newer_than", matches)?; let csv = matches.is_present("csv"); let hdr = crate::commands::util::mk_header([ "Endpoint", @@ -232,8 +232,8 @@ async fn containers_prune(endpoint_names: Vec<EndpointName>, matches: &ArgMatches, config: &Configuration, ) -> Result<()> { - let older_than_filter = get_date_filter("older_than", matches)?; - let newer_than_filter = get_date_filter("newer_than", matches)?; + let older_than_filter = crate::commands::util::get_date_filter("older_than", matches)?; + let newer_than_filter = crate::commands::util::get_date_filter("newer_than", matches)?; let stats = connect_to_endpoints(config, &endpoint_names) .await? @@ -279,8 +279,8 @@ async fn containers_top(endpoint_names: Vec<EndpointName>, config: &Configuration, ) -> Result<()> { let limit = matches.value_of("limit").map(usize::from_str).transpose()?; - let older_than_filter = get_date_filter("older_than", matches)?; - let newer_than_filter = get_date_filter("newer_than", matches)?; + let older_than_filter = crate::commands::util::get_date_filter("older_than", matches)?; + let newer_than_filter = crate::commands::util::get_date_filter("newer_than", matches)?; let csv = matches.is_present("csv"); let data = connect_to_endpoints(config, &endpoint_names) @@ -374,8 +374,8 @@ async fn containers_stop(endpoint_names: Vec<EndpointName>, matches: &ArgMatches, config: &Configuration, ) -> Result<()> { - let older_than_filter = get_date_filter("older_than", matches)?; - let newer_than_filter = get_date_filter("newer_than", matches)?; + let older_than_filter = crate::commands::util::get_date_filter("older_than", matches)?; + let newer_than_filter = crate::commands::util::get_date_filter("newer_than", matches)?; let stop_timeout = matches.value_of("timeout") .map(u64::from_str) @@ -422,22 +422,6 @@ async fn containers_stop(endpoint_names: Vec<EndpointName>, } -fn get_date_filter(name: &str, matches: &ArgMatches) -> Result<Option<chrono::DateTime::<chrono::Local>>> { - matches.value_of(name) - .map(humantime::parse_duration) - .transpose()? - .map(chrono::Duration::from_std) - .transpose()? - .map(|dur| { - chrono::offset::Local::now() - .checked_sub_signed(dur) - .ok_or_else(|| anyhow!("Time calculation would overflow")) - .with_context(|| anyhow!("Cannot subtract {} from 'now'", dur)) - .map_err(Error::from) - }) - .transpose() -} - /// 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: &[EndpointName]) -> Result<Vec<Arc<Endpoint>>> { diff --git a/src/commands/util.rs b/src/commands/util.rs index e892875..acfbdd2 100644 --- a/src/commands/util.rs +++ b/src/commands/util.rs @@ -225,3 +225,36 @@ pub fn display_data<D: Display>( } } +pub fn get_date_filter(name: &str, matches: &ArgMatches) -> Result<Option<chrono::DateTime::<chrono::Local>>> { + matches.value_of(name) + .map(|s| { + trace!("Parsing duration: '{}'", s); + humantime::parse_duration(s) + .map_err(Error::from) + .or_else(|_| { + trace!("Parsing time: '{}'", s); + humantime::parse_rfc3339_weak(s) + .map_err(Error::from) + .and_then(|d| d.elapsed().map_err(Error::from)) + }) + .or_else(|_| { + let s = format!("{} 00:00:00", s); + trace!("Parsing time: '{}'", s); + humantime::parse_rfc3339_weak(&s) + .map_err(Error::from) + .and_then(|d| d.elapsed().map_err(Error::from)) + }) + }) + .transpose()? + .map(chrono::Duration::from_std) + .transpose()? + .map(|dur| { + chrono::offset::Local::now() + .checked_sub_signed(dur) + .ok_or_else(|| anyhow!("Time calculation would overflow")) + .with_context(|| anyhow!("Cannot subtract {} from 'now'", dur)) + .map_err(Error::from) + }) + .transpose() +} + |