From d9c1598a5481b31e9ae3c69c7de89f86690d6cc0 Mon Sep 17 00:00:00 2001 From: DataHearth Date: Sun, 20 Feb 2022 23:06:15 +0100 Subject: [PATCH] refacor code --- src/date.rs | 59 ++++++++++++++++- src/images.rs | 134 ++++++++++++++++++++++++++++++++++++++ src/main.rs | 177 ++------------------------------------------------ 3 files changed, 197 insertions(+), 173 deletions(-) create mode 100644 src/images.rs diff --git a/src/date.rs b/src/date.rs index f58824f..8daa814 100644 --- a/src/date.rs +++ b/src/date.rs @@ -1,4 +1,4 @@ -use chrono::{Date, DateTime, FixedOffset, NaiveDate, TimeZone, Utc}; +use chrono::{Date, DateTime, Datelike, FixedOffset, NaiveDate, NaiveDateTime, TimeZone, Utc}; use serde::{self, Deserialize, Deserializer}; use std::{num::TryFromIntError, process::exit}; @@ -54,3 +54,60 @@ pub fn get_past_date(mut year: i32, mut month: u32, mut day: u32, day_remove: u3 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| { + eprintln!( + "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| { + eprintln!( + "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| { + eprintln!( + "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 new file mode 100644 index 0000000..6147efb --- /dev/null +++ b/src/images.rs @@ -0,0 +1,134 @@ +use chrono::{DateTime, FixedOffset}; +use serde::Deserialize; +use std::{ + num::ParseFloatError, + process::{exit, Command}, +}; + +use crate::date; +use crate::DOCKER_BIN; + +#[derive(Deserialize, Debug)] +struct Image { + #[serde(with = "date", rename = "CreatedAt")] + created_at: DateTime, + #[serde(rename = "ID")] + id: String, + #[serde(rename = "Tag")] + tag: String, + #[serde(rename = "Size")] + size: String, +} + +pub fn process_imgs( + repository: Option, + tags: Vec, + date: Option, +) -> (Vec, f32) { + let (date_from, date_to) = date::parse_date(date); + let mut ids = vec![]; + let mut saved_size: f32 = 0.0; + + for img in parse_imgs(repository) { + let image: Image = serde_json::from_str(&img).unwrap(); + let del = if let Some(max) = date_to { + if image.created_at.timestamp() >= date_from.timestamp() + && image.created_at.timestamp() <= max.timestamp() + { + true + } else { + false + } + } else { + if image.created_at.timestamp() >= date_from.timestamp() { + true + } else { + false + } + }; + + 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 { + eprintln!("Unknown size identification: {}", image.size); + exit(1); + } + } + } + } + + return (ids, saved_size); +} + +fn get_images(repo: Option) -> Vec { + let mut cmd = Command::new(DOCKER_BIN); + cmd.arg("images"); + + if let Some(repo) = repo { + cmd.arg(repo); + } + + cmd.args(["--format", "{{json .}}"]); + + match cmd.output() { + Ok(o) => { + if !o.status.success() { + eprintln!("{}", std::str::from_utf8(&o.stderr).unwrap()); + eprintln!("failed to retrieve docker images. Please checkout STDERR"); + exit(1); + } + + o.stdout + } + Err(e) => { + eprintln!("docker command failed: {}", e); + exit(1); + } + } +} + +fn parse_imgs(repository: Option) -> Vec { + let stdout = get_images(repository); + + let output = String::from_utf8(stdout).unwrap_or_else(|e| { + eprintln!("failed to parse docker output: {}", e); + exit(1); + }); + let mut images: Vec = output.lines().map(|s| s.to_string()).collect(); + // * remove last empty line + images.remove(images.len() - 1); + + if images.len() == 0 { + println!("No images found for current timestamp and/or repository"); + exit(1); + } + + return images; +} + +fn failed_convert_size(e: ParseFloatError) -> f32 { + eprintln!("failed to convert \"String\" to \"f32\": {}", e); + exit(1); +} diff --git a/src/main.rs b/src/main.rs index 725c994..b6eb1ea 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,10 @@ mod date; +mod images; -use chrono::{DateTime, Datelike, FixedOffset, NaiveDateTime, Utc}; use clap::Parser; -use date::get_past_date; -use serde::Deserialize; -use serde_json; -use std::{ - num::ParseFloatError, - process::{exit, Command, Stdio}, -}; +use std::process::{Command, Stdio}; + +use crate::images::process_imgs; const DOCKER_BIN: &str = "docker"; @@ -43,142 +39,11 @@ struct Args { verbose: bool, } -#[derive(Deserialize, Debug)] -struct Image { - #[serde(with = "date", rename = "CreatedAt")] - created_at: DateTime, - #[serde(rename = "ID")] - id: String, - #[serde(rename = "Tag")] - tag: String, - #[serde(rename = "Size")] - size: String, -} - fn main() { let args = Args::parse(); - let stdout = get_images(args.repository); - - let s_data = std::str::from_utf8(&stdout).unwrap(); - let mut images: Vec<&str> = s_data.split("\n").collect(); - // * remove last empty line - images.remove(images.len() - 1); - - if images.len() == 0 { - println!("No images found for current timestamp and/or repository"); - return; - } - - let min: DateTime; - let mut max_opt: Option> = None; - let now = Utc::now(); - if let Some(date) = args.date { - if date.contains("|") { - let dates: Vec<&str> = date.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| { - eprintln!( - "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| { - eprintln!( - "failed to parse to date. Verify its format (example: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS): {}", - e - ); - exit(1); - }); - min = DateTime::::from_utc(base_from, Utc); - max_opt = Some(DateTime::::from_utc(base_to, Utc)); - } else { - let formatted_date = if date.contains("T") { - date - } else { - date + "T00:00:00" - }; - - let date = NaiveDateTime::parse_from_str(&formatted_date, "%FT%T") - .unwrap_or_else(|e| { - eprintln!( - "failed to parse date. Verify its format (example: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS): {}", - e - ); - exit(1); - }); - - min = DateTime::::from_utc(date, Utc); - } - } else { - min = get_past_date(now.year(), now.month(), now.day(), 2).and_hms(1, 0, 0); - }; - let tags = if let Some(t) = args.tags { t } else { vec![] }; - let mut ids = vec![]; - let mut saved_size: f32 = 0.0; - - for img in images { - let image: Image = serde_json::from_str(img).unwrap(); - let del; - if let Some(max) = max_opt { - del = if image.created_at.timestamp() <= min.timestamp() - && image.created_at.timestamp() >= max.timestamp() - { - true - } else { - false - } - } else { - del = if image.created_at.timestamp() <= min.timestamp() { - true - } else { - false - } - } - - 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 { - eprintln!("Unknown size identification: {}", image.size); - exit(1); - } - } - } - } + let (ids, saved_size) = process_imgs(args.repository, tags, args.date); if args.dry_run { println!("dry run activated"); @@ -224,35 +89,3 @@ fn main() { } ); } - -fn get_images(repo: Option) -> Vec { - let mut cmd = Command::new(DOCKER_BIN); - cmd.arg("images"); - - if let Some(repo) = repo { - cmd.arg(repo); - } - - cmd.args(["--format", "{{json .}}"]); - - match cmd.output() { - Ok(o) => { - if !o.status.success() { - eprintln!("{}", std::str::from_utf8(&o.stderr).unwrap()); - eprintln!("failed to retrieve docker images. Please checkout STDERR"); - exit(1); - } - - o.stdout - } - Err(e) => { - eprintln!("docker command failed: {}", e); - exit(1); - } - } -} - -fn failed_convert_size(e: ParseFloatError) -> f32 { - eprintln!("failed to convert \"String\" to \"f32\": {}", e); - exit(1); -}