diff --git a/Cargo.lock b/Cargo.lock index f3469e1..bc18255 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,7 +40,7 @@ dependencies = [ "libc", "num-integer", "num-traits", - "time", + "time 0.1.44", "winapi", ] @@ -83,6 +83,18 @@ dependencies = [ "log", "serde", "serde_json", + "simple_logger", +] + +[[package]] +name = "colored" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd" +dependencies = [ + "atty", + "lazy_static", + "winapi", ] [[package]] @@ -168,6 +180,15 @@ dependencies = [ "autocfg", ] +[[package]] +name = "num_threads" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15" +dependencies = [ + "libc", +] + [[package]] name = "os_str_bytes" version = "6.0.0" @@ -256,6 +277,19 @@ dependencies = [ "serde", ] +[[package]] +name = "simple_logger" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75a9723083573ace81ad0cdfc50b858aa3c366c48636edb4109d73122a0c0ea" +dependencies = [ + "atty", + "colored", + "log", + "time 0.3.7", + "winapi", +] + [[package]] name = "strsim" version = "0.10.0" @@ -299,6 +333,24 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "004cbc98f30fa233c61a38bc77e96a9106e65c88f2d3bef182ae952027e5753d" +dependencies = [ + "itoa", + "libc", + "num_threads", + "time-macros", +] + +[[package]] +name = "time-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25eb0ca3468fc0acc11828786797f6ef9aa1555e4a211a60d64cc8e4d1be47d6" + [[package]] name = "unicode-xid" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index 0ac6307..b29e252 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,4 +13,5 @@ serde = { version = "1.0", features = ["derive"] } clap = { version = "3.0", features = ["derive"] } serde_json = "1.0" chrono = "0.4" -log = "0.4" \ No newline at end of file +log = "0.4" +simple_logger = "2.1" \ No newline at end of file diff --git a/src/date.rs b/src/date.rs deleted file mode 100644 index ab08e61..0000000 --- a/src/date.rs +++ /dev/null @@ -1,114 +0,0 @@ -use chrono::{Date, DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, TimeZone, Utc}; -use serde::{self, Deserialize, Deserializer}; -use std::{num::TryFromIntError, process::exit}; -use log::error; - -pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let s = String::deserialize(deserializer)?; - - // * format => 2021-01-01 00:00:00 +0100 CET - DateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S %z %Z").map_err(serde::de::Error::custom) -} - -fn get_day_month(year: i32, month: u32) -> u32 { - let date: NaiveDate; - if month == 12 { - date = NaiveDate::from_ymd(year + 1, 1, 1) - } else { - date = NaiveDate::from_ymd(year, month + 1, 1) - } - - u32::try_from( - date.signed_duration_since(NaiveDate::from_ymd(year, month, 1)) - .num_days(), - ) - .unwrap_or_else(|_: TryFromIntError| { - error!("failed to convert i64 to u32"); - exit(1); - }) -} - -pub fn get_past_date(mut year: i32, mut month: u32, mut day: u32, day_remove: u32) -> Date { - if u32::checked_sub(day, day_remove).is_none() || (day - day_remove) < 1 { - if (month - 1) < 1 { - month = 12; - year -= 1; - } else { - month -= 1; - } - - let new_day = get_day_month(year, month); - day = match day { - 2 => new_day, - 1 => new_day - 1, - _ => { - error!("invalid day in condition..."); - exit(1) - } - } - } else { - day -= day_remove; - } - - Utc.from_utc_date(&NaiveDate::from_ymd(year, month, day)) -} - -pub fn parse_date(date: Option) -> (DateTime, Option>) { - let from: DateTime; - let mut to: Option> = None; - - if let Some(d) = date { - if d.contains("|") { - let dates: Vec<&str> = d.split("|").collect(); - let base_from = if dates[0].contains("T") { - dates[0].to_string() - } else { - format!("{}T00:00:00", dates[0]) - }; - let base_to = if dates[1].contains("T") { - dates[1].to_string() - } else { - format!("{}T00:00:00", dates[1]) - }; - - let base_from = NaiveDateTime::parse_from_str(&base_from, "%FT%T").unwrap_or_else(|e| { - error!( - "failed to parse from date. Verify its format (example: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS): {}", - e - ); - exit(1); - }); - let base_to = NaiveDateTime::parse_from_str(&base_to, "%FT%T").unwrap_or_else(|e| { - error!( - "failed to parse to date. Verify its format (example: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS): {}", - e - ); - exit(1); - }); - from = DateTime::::from_utc(base_from, Utc); - to = Some(DateTime::::from_utc(base_to, Utc)); - } else { - let formatted_date = if d.contains("T") { d } else { d + "T00:00:00" }; - - let date = NaiveDateTime::parse_from_str(&formatted_date, "%FT%T") - .unwrap_or_else(|e| { - error!( - "failed to parse date. Verify its format (example: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS): {}", - e - ); - exit(1); - }); - - from = DateTime::::from_utc(date, Utc); - } - } else { - let now = Utc::now(); - - from = get_past_date(now.year(), now.month(), now.day(), 2).and_hms(1, 0, 0); - }; - - return (from, to); -} diff --git a/src/images.rs b/src/images.rs index b369ef6..4e92341 100644 --- a/src/images.rs +++ b/src/images.rs @@ -1,73 +1,88 @@ -use chrono::{DateTime, FixedOffset}; +use chrono::DateTime; use log::{error, warn}; -use serde::Deserialize; -use std::{ - num::ParseFloatError, - process::{exit, Command}, -}; +use serde::{self, Deserialize, Deserializer}; +use std::process::{exit, Command}; -use crate::date; +use crate::DateArgs; use crate::DOCKER_BIN; #[derive(Deserialize, Debug)] struct Image { - #[serde(with = "date", rename = "CreatedAt")] - created_at: DateTime, + #[serde(deserialize_with = "deserialize_creation_date", rename = "CreatedAt")] + created_at: i64, #[serde(rename = "ID")] id: String, #[serde(rename = "Tag")] tag: String, - #[serde(rename = "Size")] - size: String, + #[serde(deserialize_with = "deserialize_size", rename = "Size")] + size: f32, +} + +pub fn deserialize_creation_date<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let date = String::deserialize(deserializer)?; + + // format => 2021-01-01 00:00:00 +0100 CET + DateTime::parse_from_str(&date, "%Y-%m-%d %H:%M:%S %z %Z") + .map(|d| d.timestamp()) + .map_err(serde::de::Error::custom) +} + +pub fn deserialize_size<'de, D>(deserializer: D) -> Result +where + D: Deserializer<'de>, +{ + let size = String::deserialize(deserializer)?; + + if size.contains("KB") { + size.replace("KB", "") + .parse::() + .map(|s| s / 1000 as f32) + .map_err(serde::de::Error::custom) + } else if size.contains("MB") { + size.replace("MB", "") + .parse::() + .map_err(serde::de::Error::custom) + } else if size.contains("GB") { + size.replace("GB", "") + .parse::() + .map(|s| s * 1000 as f32) + .map_err(serde::de::Error::custom) + } else { + Err(serde::de::Error::custom(format!( + "Unknown size identification: {}", + size, + ))) + } } pub fn process_imgs( repository: Option, tags: Vec, - date: Option, + timestamps: DateArgs, ) -> (Vec, f32) { - let (date_from, date_to) = date::parse_date(date); let mut ids = vec![]; - let mut saved_size: f32 = 0.0; + let mut saved_size = 0.0; for img in parse_imgs(repository) { let image: Image = serde_json::from_str(&img).unwrap(); - let del = date_to.map_or( - image.created_at.timestamp() >= date_from.timestamp(), - |max| { - date_from.timestamp() >= image.created_at.timestamp() - && max.timestamp() <= image.created_at.timestamp() - }, - ); + let del = timestamps + .stop + .map_or(timestamps.start > image.created_at, |stop| { + println!( + "stop date set, valid: {}", + timestamps.start > image.created_at && stop < image.created_at + ); + timestamps.start > image.created_at && stop < image.created_at + }); if del { if !tags.contains(&image.tag) { ids.push(image.id); - saved_size += if image.size.contains("KB") { - image - .size - .replace("KB", "") - .parse::() - .unwrap_or_else(failed_convert_size) - / 1000 as f32 - } else if image.size.contains("MB") { - image - .size - .replace("MB", "") - .parse::() - .unwrap_or_else(failed_convert_size) - } else if image.size.contains("GB") { - image - .size - .replace("GB", "") - .parse::() - .unwrap_or_else(failed_convert_size) - * 1000 as f32 - } else { - error!("Unknown size identification: {}", image.size); - exit(1); - } + saved_size += image.size } } } @@ -121,8 +136,3 @@ fn parse_imgs(repository: Option) -> Vec { return images; } - -fn failed_convert_size(e: ParseFloatError) -> f32 { - error!("failed to convert \"String\" to \"f32\": {}", e); - exit(1); -} diff --git a/src/main.rs b/src/main.rs index 534a230..84099b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,15 @@ -mod date; mod images; +use chrono::{NaiveDateTime, Utc}; use clap::Parser; -use std::process::{Command, Stdio}; use log::{error, info}; +use simple_logger::SimpleLogger; +use std::process::{exit, Command, Stdio}; use crate::images::process_imgs; const DOCKER_BIN: &str = "docker"; +const TWO_DAYS_TIMESTAMP: i64 = 172_800; /// Clear docker images from #[derive(Parser, Debug)] @@ -15,9 +17,9 @@ const DOCKER_BIN: &str = "docker"; struct Args { /// filter by date. /// - /// Can filter by a minimum age $DATE or from $FROM|$TO (format example: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS) [default: $NOW - 2 days] - #[clap(short, long)] - date: Option, + /// Can filter by a minimum age $DATE or from $START|$STOP (format example: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS) [default: $NOW - 2 days] + #[clap(short, long, parse(try_from_str = parse_user_date))] + date: DateArgs, /// filter by repository name #[clap(short, long)] @@ -40,7 +42,18 @@ struct Args { verbose: bool, } +#[derive(Debug)] +pub struct DateArgs { + start: i64, + stop: Option, +} + fn main() { + if SimpleLogger::new().init().is_err() { + eprintln!("failed to initialize logger"); + exit(1); + } + let args = Args::parse(); let tags = args.tags.map_or(vec![], |tags| tags); @@ -90,3 +103,32 @@ fn main() { } ); } + +fn parse_user_date(date: &str) -> Result { + if date.contains("|") { + let dates: Vec<&str> = date.split("|").collect(); + + return Ok(DateArgs { + start: format_user_date(dates[0]), + stop: Some(format_user_date(dates[1])), + }); + } + + Ok(DateArgs { + start: format_user_date(date), + stop: None, + }) +} + +fn format_user_date(user_date: &str) -> i64 { + let date = if user_date.contains("T") { + user_date.to_string() + } else { + format!("{}T00:00:00", user_date) + }; + + NaiveDateTime::parse_from_str(&date, "%FT%T") + .map_or(Utc::now().timestamp() - TWO_DAYS_TIMESTAMP, |d| { + d.timestamp() + }) +}