[feat] first ver
This commit is contained in:
parent
b2e98a53b9
commit
80cad2886c
52
Cargo.lock
generated
52
Cargo.lock
generated
@ -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"
|
||||
|
@ -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"] }
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
|
54
src/main.rs
54
src/main.rs
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user