This commit is contained in:
jay 2022-01-27 16:07:02 +08:00
parent 8c4096882c
commit a8d59e48d1
5 changed files with 1267 additions and 0 deletions

1077
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,3 +6,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
crossterm = "0.22.1"
reqwest = { version = "0.11", features = ["json", "blocking"] }
serde = {version = "1.0.136", features = ["derive"]}
serde_json = "1.0.78"
tokio = { version = "1", features = ["full"] }
tui = "0.17.0"

View File

@ -1,3 +1,31 @@
mod registry;
fn main() {
println!("Hello, world!");
let r = registry::api::Registry {
endpoint: "https://docker.mtfos.xyz".to_string(),
user: "".to_string(),
password: "".to_string(),
};
// let images = r.list_images().unwrap();
// println!("images ::: {:?}", images);
// let tags = r.get_image_tags("mtfos/go-bot").unwrap();
// println!("tags ::: {:?}", tags);
let digest = match r.get_image_tag_manifest("demo/node-hello", "v1") {
Ok(digest) => digest,
Err(e) => panic!("digest err ::: {:?}", e),
};
println!("digest ::: {:?}", digest);
match r.delete_image_tag("demo/node-hello", &digest) {
Ok(_) => println!("deleted"),
Err(e) => println!("error ::: {:?}", e),
};
}

154
src/registry/api.rs Normal file
View File

@ -0,0 +1,154 @@
use reqwest;
use serde::Deserialize;
// use serde_json;
#[warn(dead_code)]
pub struct Registry {
pub endpoint: String,
pub user: String,
pub password: String,
}
#[derive(Debug, Deserialize)]
struct RegistryRepository {
repositories: Vec<String>,
}
#[derive(Debug, Deserialize)]
pub struct ImageTags {
pub name: String,
pub tags: Vec<String>,
}
impl Registry {
fn get_request(&self, method: &str, path: &str) -> reqwest::blocking::RequestBuilder {
let client = reqwest::blocking::Client::new();
let url = format!("{}{}", self.endpoint, path);
let mut m = match method {
"GET" => client.get(&url),
"POST" => client.post(&url),
"PUT" => client.put(&url),
"DELETE" => client.delete(&url),
_ => panic!("Unsupported method"),
};
if (self.user.len() > 0) && (self.password.len() > 0) {
m = m.basic_auth(&self.user, Some(&self.password));
}
return m;
}
pub fn list_images(&self) -> Result<Vec<String>, Box<dyn std::error::Error>> {
let url = "/v2/_catalog";
let builder = self.get_request("GET", &url);
let resp = builder.send()?;
let headers = resp.headers().to_owned();
if !headers
.get("Content-Type")
.unwrap()
.to_str()
.unwrap()
.starts_with("application/json")
{
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"Invalid Content-Type",
)));
}
let data = resp.json::<RegistryRepository>()?;
Ok(data.repositories)
}
pub fn get_image_tags(
&self,
image_name: &str,
) -> Result<ImageTags, Box<dyn std::error::Error>> {
let url = format!("/v2/{}/tags/list", image_name);
let builder = self.get_request("GET", &url);
let resp = builder.send()?;
let headers = resp.headers().to_owned();
println!("\n{:?}\n", headers);
if !headers
.get("Content-Type")
.unwrap()
.to_str()
.unwrap()
.starts_with("application/json")
{
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"Invalid Content-Type",
)));
}
let data = resp.json::<ImageTags>()?;
Ok(data)
}
pub fn get_image_tag_manifest(
&self,
image_name: &str,
tag: &str,
) -> Result<String, Box<dyn std::error::Error>> {
let url = format!("/v2/{}/manifests/{}", image_name, tag);
let mut builder = self.get_request("GET", &url);
builder = builder.header(
reqwest::header::ACCEPT,
"application/vnd.docker.distribution.manifest.v2+json",
);
let resp = builder.send()?;
if !resp.status().is_success() {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"request failed",
)));
}
let headers = resp.headers().to_owned();
let digest = headers
.get("Docker-Content-Digest")
.unwrap()
.to_str()
.unwrap()
.to_string();
Ok(digest)
}
pub fn delete_image_tag(
&self,
image_name: &str,
digest: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let url = format!("/v2/{}/manifests/{}", image_name, digest);
let builder = self.get_request("DELETE", &url);
let resp = builder.send()?;
if !resp.status().is_success() {
return Err(Box::new(std::io::Error::new(
std::io::ErrorKind::Other,
"request failed",
)));
}
Ok(())
}
}

1
src/registry/mod.rs Normal file
View File

@ -0,0 +1 @@
pub mod api;