refacot, cleanup and fix date inversion issue
This commit is contained in:
parent
264102b92d
commit
e3e2722234
|
@ -40,7 +40,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"num-integer",
|
"num-integer",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"time",
|
"time 0.1.44",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -83,6 +83,18 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"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]]
|
[[package]]
|
||||||
|
@ -168,6 +180,15 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_threads"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "97ba99ba6393e2c3734791401b66902d981cb03bf190af674ca69949b6d5fb15"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "os_str_bytes"
|
name = "os_str_bytes"
|
||||||
version = "6.0.0"
|
version = "6.0.0"
|
||||||
|
@ -256,6 +277,19 @@ dependencies = [
|
||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -299,6 +333,24 @@ dependencies = [
|
||||||
"winapi",
|
"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]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
|
|
@ -13,4 +13,5 @@ serde = { version = "1.0", features = ["derive"] }
|
||||||
clap = { version = "3.0", features = ["derive"] }
|
clap = { version = "3.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
simple_logger = "2.1"
|
114
src/date.rs
114
src/date.rs
|
@ -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<DateTime<FixedOffset>, 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<Utc> {
|
|
||||||
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<String>) -> (DateTime<Utc>, Option<DateTime<Utc>>) {
|
|
||||||
let from: DateTime<Utc>;
|
|
||||||
let mut to: Option<DateTime<Utc>> = 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::<Utc>::from_utc(base_from, Utc);
|
|
||||||
to = Some(DateTime::<Utc>::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::<Utc>::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);
|
|
||||||
}
|
|
110
src/images.rs
110
src/images.rs
|
@ -1,73 +1,88 @@
|
||||||
use chrono::{DateTime, FixedOffset};
|
use chrono::DateTime;
|
||||||
use log::{error, warn};
|
use log::{error, warn};
|
||||||
use serde::Deserialize;
|
use serde::{self, Deserialize, Deserializer};
|
||||||
use std::{
|
use std::process::{exit, Command};
|
||||||
num::ParseFloatError,
|
|
||||||
process::{exit, Command},
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::date;
|
use crate::DateArgs;
|
||||||
use crate::DOCKER_BIN;
|
use crate::DOCKER_BIN;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct Image {
|
struct Image {
|
||||||
#[serde(with = "date", rename = "CreatedAt")]
|
#[serde(deserialize_with = "deserialize_creation_date", rename = "CreatedAt")]
|
||||||
created_at: DateTime<FixedOffset>,
|
created_at: i64,
|
||||||
#[serde(rename = "ID")]
|
#[serde(rename = "ID")]
|
||||||
id: String,
|
id: String,
|
||||||
#[serde(rename = "Tag")]
|
#[serde(rename = "Tag")]
|
||||||
tag: String,
|
tag: String,
|
||||||
#[serde(rename = "Size")]
|
#[serde(deserialize_with = "deserialize_size", rename = "Size")]
|
||||||
size: String,
|
size: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn deserialize_creation_date<'de, D>(deserializer: D) -> Result<i64, D::Error>
|
||||||
|
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<f32, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
let size = String::deserialize(deserializer)?;
|
||||||
|
|
||||||
|
if size.contains("KB") {
|
||||||
|
size.replace("KB", "")
|
||||||
|
.parse::<f32>()
|
||||||
|
.map(|s| s / 1000 as f32)
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
} else if size.contains("MB") {
|
||||||
|
size.replace("MB", "")
|
||||||
|
.parse::<f32>()
|
||||||
|
.map_err(serde::de::Error::custom)
|
||||||
|
} else if size.contains("GB") {
|
||||||
|
size.replace("GB", "")
|
||||||
|
.parse::<f32>()
|
||||||
|
.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(
|
pub fn process_imgs(
|
||||||
repository: Option<String>,
|
repository: Option<String>,
|
||||||
tags: Vec<String>,
|
tags: Vec<String>,
|
||||||
date: Option<String>,
|
timestamps: DateArgs,
|
||||||
) -> (Vec<String>, f32) {
|
) -> (Vec<String>, f32) {
|
||||||
let (date_from, date_to) = date::parse_date(date);
|
|
||||||
let mut ids = vec![];
|
let mut ids = vec![];
|
||||||
let mut saved_size: f32 = 0.0;
|
let mut saved_size = 0.0;
|
||||||
|
|
||||||
for img in parse_imgs(repository) {
|
for img in parse_imgs(repository) {
|
||||||
let image: Image = serde_json::from_str(&img).unwrap();
|
let image: Image = serde_json::from_str(&img).unwrap();
|
||||||
let del = date_to.map_or(
|
let del = timestamps
|
||||||
image.created_at.timestamp() >= date_from.timestamp(),
|
.stop
|
||||||
|max| {
|
.map_or(timestamps.start > image.created_at, |stop| {
|
||||||
date_from.timestamp() >= image.created_at.timestamp()
|
println!(
|
||||||
&& max.timestamp() <= image.created_at.timestamp()
|
"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 del {
|
||||||
if !tags.contains(&image.tag) {
|
if !tags.contains(&image.tag) {
|
||||||
ids.push(image.id);
|
ids.push(image.id);
|
||||||
|
|
||||||
saved_size += if image.size.contains("KB") {
|
saved_size += image.size
|
||||||
image
|
|
||||||
.size
|
|
||||||
.replace("KB", "")
|
|
||||||
.parse::<f32>()
|
|
||||||
.unwrap_or_else(failed_convert_size)
|
|
||||||
/ 1000 as f32
|
|
||||||
} else if image.size.contains("MB") {
|
|
||||||
image
|
|
||||||
.size
|
|
||||||
.replace("MB", "")
|
|
||||||
.parse::<f32>()
|
|
||||||
.unwrap_or_else(failed_convert_size)
|
|
||||||
} else if image.size.contains("GB") {
|
|
||||||
image
|
|
||||||
.size
|
|
||||||
.replace("GB", "")
|
|
||||||
.parse::<f32>()
|
|
||||||
.unwrap_or_else(failed_convert_size)
|
|
||||||
* 1000 as f32
|
|
||||||
} else {
|
|
||||||
error!("Unknown size identification: {}", image.size);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,8 +136,3 @@ fn parse_imgs(repository: Option<String>) -> Vec<String> {
|
||||||
|
|
||||||
return images;
|
return images;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn failed_convert_size(e: ParseFloatError) -> f32 {
|
|
||||||
error!("failed to convert \"String\" to \"f32\": {}", e);
|
|
||||||
exit(1);
|
|
||||||
}
|
|
||||||
|
|
52
src/main.rs
52
src/main.rs
|
@ -1,13 +1,15 @@
|
||||||
mod date;
|
|
||||||
mod images;
|
mod images;
|
||||||
|
|
||||||
|
use chrono::{NaiveDateTime, Utc};
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use std::process::{Command, Stdio};
|
|
||||||
use log::{error, info};
|
use log::{error, info};
|
||||||
|
use simple_logger::SimpleLogger;
|
||||||
|
use std::process::{exit, Command, Stdio};
|
||||||
|
|
||||||
use crate::images::process_imgs;
|
use crate::images::process_imgs;
|
||||||
|
|
||||||
const DOCKER_BIN: &str = "docker";
|
const DOCKER_BIN: &str = "docker";
|
||||||
|
const TWO_DAYS_TIMESTAMP: i64 = 172_800;
|
||||||
|
|
||||||
/// Clear docker images from
|
/// Clear docker images from
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
@ -15,9 +17,9 @@ const DOCKER_BIN: &str = "docker";
|
||||||
struct Args {
|
struct Args {
|
||||||
/// filter by date.
|
/// 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]
|
/// 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)]
|
#[clap(short, long, parse(try_from_str = parse_user_date))]
|
||||||
date: Option<String>,
|
date: DateArgs,
|
||||||
|
|
||||||
/// filter by repository name
|
/// filter by repository name
|
||||||
#[clap(short, long)]
|
#[clap(short, long)]
|
||||||
|
@ -40,7 +42,18 @@ struct Args {
|
||||||
verbose: bool,
|
verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DateArgs {
|
||||||
|
start: i64,
|
||||||
|
stop: Option<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
if SimpleLogger::new().init().is_err() {
|
||||||
|
eprintln!("failed to initialize logger");
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let tags = args.tags.map_or(vec![], |tags| tags);
|
let tags = args.tags.map_or(vec![], |tags| tags);
|
||||||
|
@ -90,3 +103,32 @@ fn main() {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_user_date(date: &str) -> Result<DateArgs, &'static str> {
|
||||||
|
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()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Reference in New Issue