[feat] first ver

This commit is contained in:
Jay 2020-11-11 22:46:10 +08:00
parent b2e98a53b9
commit 80cad2886c
4 changed files with 217 additions and 8 deletions

52
Cargo.lock generated
View File

@ -98,6 +98,19 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
dependencies = [
"libc",
"num-integer",
"num-traits",
"time",
"winapi 0.3.9",
]
[[package]]
name = "core-foundation"
version = "0.7.0"
@ -278,7 +291,7 @@ checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
dependencies = [
"cfg-if 0.1.10",
"libc",
"wasi",
"wasi 0.9.0+wasi-snapshot-preview1",
]
[[package]]
@ -674,6 +687,25 @@ dependencies = [
"winapi 0.3.9",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
@ -887,6 +919,7 @@ name = "registry-cleaner"
version = "0.1.0"
dependencies = [
"args",
"chrono",
"getopts",
"reqwest",
"serde",
@ -1165,6 +1198,17 @@ dependencies = [
"syn",
]
[[package]]
name = "time"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255"
dependencies = [
"libc",
"wasi 0.10.0+wasi-snapshot-preview1",
"winapi 0.3.9",
]
[[package]]
name = "tinyvec"
version = "0.3.4"
@ -1415,6 +1459,12 @@ version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasi"
version = "0.10.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
[[package]]
name = "wasm-bindgen"
version = "0.2.68"

View File

@ -8,6 +8,7 @@ edition = "2018"
[dependencies]
args = "2.2.0"
chrono = "0.4.19"
getopts = "0.2.21"
reqwest = {version = "0.10.8", features =["json", "rustls-tls", "trust-dns", "blocking"]}
serde = {version = "1.0.117", features = ["derive"] }

View File

@ -1,5 +1,6 @@
use reqwest;
use serde::Deserialize;
use std::collections::HashMap;
use url::Url;
pub struct Registry<'a> {
@ -15,10 +16,28 @@ pub struct RepositoryList {
#[allow(dead_code)]
#[derive(Debug, Deserialize)]
pub struct RepositoryTags {
name: String,
tags: Option<Vec<String>>,
pub name: String,
pub tags: Option<Vec<String>>,
}
#[allow(dead_code, non_snake_case)]
#[derive(Debug, Deserialize)]
pub struct RepositoryTagManifest {
pub name: String,
pub tag: String,
pub history: Vec<HashMap<String, String>>,
#[serde(skip)]
pub ts: i64,
#[serde(skip)]
pub digest: String,
}
#[derive(Debug, Deserialize)]
struct History {
created: Option<String>,
}
impl<'a> Registry<'a> {
pub async fn get_repositories(&self) -> Result<RepositoryList, Box<dyn std::error::Error>> {
let registry_url = Url::parse(self.url).unwrap();
@ -43,9 +62,100 @@ impl<'a> Registry<'a> {
let resp = reqwest::get(api_url).await?;
println!("{:?}", resp.headers());
let data = resp.json::<RepositoryTags>().await?;
Ok(data)
}
pub async fn get_repository_tag_manifest(
&self,
repo: &str,
tag: &str,
) -> Result<RepositoryTagManifest, Box<dyn std::error::Error>> {
let registry_url = Url::parse(self.url).unwrap();
let api_url = registry_url
.join(format!("/v2/{}/manifests/{}", repo, tag).as_str())
.unwrap();
let resp = reqwest::get(api_url.clone()).await?;
// let digest = resp
// .headers()
// .get("Docker-Content-Digest")
// .unwrap()
// .to_str()
// .unwrap()
// .to_string();
let digest = reqwest::Client::new()
.get(api_url.clone())
.header(
reqwest::header::ACCEPT,
"application/vnd.docker.distribution.manifest.v2+json",
)
.send()
.await?
.headers()
.get("Docker-Content-Digest")
.unwrap()
.to_str()
.unwrap()
.to_string();
let mut data = resp.json::<RepositoryTagManifest>().await?;
let ts = data.history.iter().fold(0, |acc, d| {
match d.get("v1Compatibility") {
None => acc,
Some(s) => {
// println!("{}", (*s))
let h: History = serde_json::from_str(s).unwrap_or(History { created: None });
match h.created {
None => acc,
Some(time_str) => {
let result = chrono::DateTime::parse_from_rfc3339(time_str.as_str());
match result {
Ok(t) => {
let tt = t.timestamp();
if acc > tt {
acc
} else {
tt
}
}
Err(_) => acc,
}
}
}
}
}
});
data.history.clear();
data.ts = ts;
data.digest = digest;
Ok(data)
}
pub async fn delete_repository_tag(
&self,
repo: &str,
digest: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let registry_url = Url::parse(self.url).unwrap();
let api_url = registry_url
.join(format!("/v2/{}/manifests/{}", repo, digest).as_str())
.unwrap();
// println!("url:: {}", api_url);
let client = reqwest::Client::new();
client.delete(api_url).send().await?;
// println!("Status : {}", resp.status());
// if resp.status().as_u16() >= 400 {
// }
Ok(())
}
}

View File

@ -12,6 +12,7 @@ struct Opts {
registry: String,
image: String,
exclude: Vec<String>,
keep: i32,
}
fn main() {
@ -28,22 +29,55 @@ fn main() {
if res.repositories.len() == 0 {
return;
}
println!("response :::: {:?}", res);
// println!("response :::: {:?}", res);
let mut proc_images: Vec<String> = Vec::new();
if _parsed.image.len() > 0 {
if !res.repositories.contains(&_parsed.image) {
eprintln!("image not in registry");
std::process::exit(1);
}
proc_images.push(_parsed.image);
} else {
proc_images = res.repositories.to_owned();
}
println!("proc images ::: {:?}", proc_images);
// println!("proc images ::: {:?}", proc_images);
for img in proc_images.into_iter() {
let _repo_tags = registry.get_repository_tags(img.as_str()).await.unwrap();
println!("{:?}", _repo_tags);
let mut tags: Vec<api::registry::RepositoryTagManifest> = Vec::new();
match _repo_tags.tags {
Some(s) => {
for x in s.iter() {
if _parsed.exclude.contains(x) {
continue;
}
let manifests = registry
.get_repository_tag_manifest(img.as_str(), x)
.await
.unwrap();
tags.push(manifests);
}
}
_ => (),
}
tags.sort_by(|a, b| b.ts.partial_cmp(&a.ts).unwrap());
// println!("{:?}", tags);
tags.drain(0.._parsed.keep as usize);
println!("delete tag number : {}", tags.len());
for x in tags.iter() {
match registry.delete_repository_tag(img.as_str(), x.digest.as_str()).await {
Ok(_) => println!("delete {} success", x.tag),
Err(_) => println!("delete {} fail", x.tag),
};
}
}
});
}
@ -76,6 +110,14 @@ fn parse(input: &Vec<String>) -> Result<Opts, ArgsError> {
Occur::Optional,
None,
);
arg.option(
"k",
"keep",
"keep tags number",
"KEEP",
Occur::Optional,
None,
);
arg.parse(input).unwrap();
@ -98,10 +140,16 @@ fn parse(input: &Vec<String>) -> Result<Opts, ArgsError> {
let url: String = arg.value_of("registry").unwrap();
let keep: i32 = match arg.value_of("keep") {
Ok(v) => v,
Err(_) => 10,
};
let opts = Opts {
image: image.clone(),
exclude: exclude.clone(),
registry: url,
keep,
};
Ok(opts)