refactor: extract authentication function
This commit is contained in:
parent
770a4180d1
commit
2fa67bbcbc
@ -1,8 +1,8 @@
|
||||
use std::io::Write;
|
||||
use clap::{arg, Parser};
|
||||
//use docker_tags::docker::DockerHubTagsFetcher;
|
||||
use docker_tags::{DockerTags, QueryArgs};
|
||||
use docker_tags::registry::RegistryTagsFetcher;
|
||||
use docker_tags::docker_hub::DockerHubTagsFetcher;
|
||||
use docker_tags::{DockerTagsFetcher, QueryArgs};
|
||||
use docker_tags::registry::{docker_config, RegistryTagsFetcher};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "dockertags", author, version, about = "List all tags for a Docker image on a remote registry", long_about = "List all tags for a Docker image on a remote registry")]
|
||||
@ -22,10 +22,17 @@ struct Args {
|
||||
fn main() {
|
||||
let args = Args::parse();
|
||||
let query_args = QueryArgs::new(args.repository.as_str(), args.filter, args.arch);
|
||||
// let docker_tags_fetcher = DockerHubTagsFetcher {};
|
||||
let docker_tags_fetcher = RegistryTagsFetcher {};
|
||||
let tags = docker_tags_fetcher.get_tags(&query_args);
|
||||
|
||||
let config = docker_config::read_config();
|
||||
let tags = match config {
|
||||
None => {
|
||||
let fetcher = DockerHubTagsFetcher::new();
|
||||
fetcher.get_tags(&query_args)
|
||||
}
|
||||
Some(x) => {
|
||||
let fetcher = RegistryTagsFetcher::new(&x);
|
||||
fetcher.get_tags(&query_args)
|
||||
}
|
||||
};
|
||||
for tag in tags {
|
||||
let _ = writeln!(std::io::stdout(), "{}", &tag);
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::{DockerTags, QueryArgs};
|
||||
use crate::{DockerTagsFetcher, QueryArgs};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DockerResult {
|
||||
@ -45,7 +45,13 @@ struct DockerResponse {
|
||||
|
||||
pub struct DockerHubTagsFetcher {}
|
||||
|
||||
impl DockerTags for DockerHubTagsFetcher {
|
||||
impl DockerHubTagsFetcher {
|
||||
pub fn new() -> DockerHubTagsFetcher {
|
||||
DockerHubTagsFetcher {}
|
||||
}
|
||||
}
|
||||
|
||||
impl DockerTagsFetcher for DockerHubTagsFetcher {
|
||||
fn get_tags(&self, args: &QueryArgs) -> Vec<String> {
|
||||
let page = 0;
|
||||
let page_size = 1000;
|
@ -1,5 +1,5 @@
|
||||
pub mod registry;
|
||||
pub mod docker;
|
||||
pub mod docker_hub;
|
||||
|
||||
/// query args
|
||||
pub struct QueryArgs {
|
||||
@ -9,7 +9,7 @@ pub struct QueryArgs {
|
||||
pub arch: Option<String>,
|
||||
}
|
||||
|
||||
pub trait DockerTags {
|
||||
pub trait DockerTagsFetcher {
|
||||
/// query tags
|
||||
fn get_tags(&self, args: &QueryArgs) -> Vec<String>;
|
||||
}
|
||||
|
101
src/registry.rs
101
src/registry.rs
@ -1,8 +1,9 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use directories::UserDirs;
|
||||
pub mod docker_auth;
|
||||
pub mod docker_config;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::{DockerTags, QueryArgs};
|
||||
use crate::{DockerTagsFetcher, QueryArgs};
|
||||
use crate::registry::docker_config::DockerPassword;
|
||||
|
||||
/// registry api response struct
|
||||
#[derive(Serialize, Deserialize)]
|
||||
@ -11,23 +12,25 @@ struct RegistryTagsResponse {
|
||||
tags: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RegistryTokenResponse {
|
||||
token: Option<String>,
|
||||
access_token: Option<String>,
|
||||
expires_in: u32,
|
||||
issued_at: Option<String>,
|
||||
pub struct RegistryTagsFetcher {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
||||
|
||||
impl RegistryTagsFetcher {
|
||||
pub fn new(docker_password: &DockerPassword) -> RegistryTagsFetcher {
|
||||
RegistryTagsFetcher {
|
||||
username: docker_password.username.to_string(),
|
||||
password: docker_password.password.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RegistryTagsFetcher {}
|
||||
|
||||
impl DockerTags for RegistryTagsFetcher {
|
||||
impl DockerTagsFetcher for RegistryTagsFetcher {
|
||||
fn get_tags(&self, args: &QueryArgs) -> Vec<String> {
|
||||
let namespace = args.namespace.as_str();
|
||||
let repository = args.repository.as_str();
|
||||
|
||||
let token = get_token(namespace, repository).unwrap();
|
||||
let token = docker_auth::get_token(namespace, repository, self.username.as_str(), self.password.as_str()).unwrap();
|
||||
let url = format!("https://registry-1.docker.io/v2/{namespace}/{repository}/tags/list");
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let response = client.get(url)
|
||||
@ -41,71 +44,3 @@ impl DockerTags for RegistryTagsFetcher {
|
||||
results
|
||||
}
|
||||
}
|
||||
|
||||
/// get the access token for tags request
|
||||
fn get_token(namespace: &str, repository: &str) -> Option<String> {
|
||||
let config = match read_config() {
|
||||
None => { return None; }
|
||||
Some(x) => { x }
|
||||
};
|
||||
let service = "registry.docker.io";
|
||||
let scope = format!("repository:{namespace}/{repository}:pull");
|
||||
let auth = format!("https://auth.docker.io/token?service={service}&scope={scope}");
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let response = client.get(auth)
|
||||
.basic_auth(config.username, Some(config.password))
|
||||
.send();
|
||||
match response {
|
||||
Ok(r) => {
|
||||
let json: RegistryTokenResponse = serde_json::from_str(r.text().unwrap().as_str()).unwrap();
|
||||
Some(json.token.unwrap())
|
||||
}
|
||||
Err(_) => { None }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DockerConfig {
|
||||
auths: HashMap<String, DockerAuth>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DockerAuth {
|
||||
auth: Option<String>,
|
||||
}
|
||||
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
|
||||
/// read authentication info from local docker config file
|
||||
fn read_config() -> Option<DockerPassword> {
|
||||
let user_dirs = UserDirs::new().unwrap();
|
||||
let home_dir = user_dirs.home_dir();
|
||||
let config_path = home_dir.join(".docker").join("config.json");
|
||||
let config_context = fs::read_to_string(config_path);
|
||||
let docker_config: DockerConfig = serde_json::from_str(config_context.unwrap().as_str()).unwrap();
|
||||
let auths = docker_config.auths;
|
||||
let auth = match auths.get("https://index.docker.io/v1/") {
|
||||
None => { return None; }
|
||||
Some(docker_auth) => { docker_auth.auth.clone().unwrap() }
|
||||
};
|
||||
let mut buffer = Vec::<u8>::new();
|
||||
let _result = general_purpose::URL_SAFE_NO_PAD.decode_vec(auth.as_bytes(), &mut buffer);
|
||||
let decoded = match std::str::from_utf8(&buffer) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
|
||||
};
|
||||
let idx = match decoded.find(':') {
|
||||
None => { panic!("Invalid authentication: {}", decoded) }
|
||||
Some(i) => { i }
|
||||
};
|
||||
return Some(DockerPassword {
|
||||
username: String::from(&decoded[0..idx]),
|
||||
password: String::from(&decoded[(idx + 1)..]),
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DockerPassword {
|
||||
username: String,
|
||||
password: String,
|
||||
}
|
27
src/registry/docker_auth.rs
Normal file
27
src/registry/docker_auth.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct RegistryTokenResponse {
|
||||
token: Option<String>,
|
||||
access_token: Option<String>,
|
||||
expires_in: u32,
|
||||
issued_at: Option<String>,
|
||||
}
|
||||
|
||||
/// get the access token for tags request
|
||||
pub fn get_token(namespace: &str, repository: &str, username: &str, password: &str) -> Option<String> {
|
||||
let service = "registry.docker.io";
|
||||
let scope = format!("repository:{namespace}/{repository}:pull");
|
||||
let auth = format!("https://auth.docker.io/token?service={service}&scope={scope}");
|
||||
let client = reqwest::blocking::Client::new();
|
||||
let response = client.get(auth)
|
||||
.basic_auth(username, Some(password))
|
||||
.send();
|
||||
match response {
|
||||
Ok(r) => {
|
||||
let json: RegistryTokenResponse = serde_json::from_str(r.text().unwrap().as_str()).unwrap();
|
||||
Some(json.token.unwrap())
|
||||
}
|
||||
Err(_) => { None }
|
||||
}
|
||||
}
|
49
src/registry/docker_config.rs
Normal file
49
src/registry/docker_config.rs
Normal file
@ -0,0 +1,49 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use base64::{Engine as _, engine::general_purpose};
|
||||
use directories::UserDirs;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DockerConfig {
|
||||
auths: HashMap<String, DockerAuth>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
struct DockerAuth {
|
||||
auth: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct DockerPassword {
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
/// read authentication info from local docker config file
|
||||
pub fn read_config() -> Option<DockerPassword> {
|
||||
let user_dirs = UserDirs::new().unwrap();
|
||||
let home_dir = user_dirs.home_dir();
|
||||
let config_path = home_dir.join(".docker").join("config.json");
|
||||
let config_context = fs::read_to_string(config_path);
|
||||
let docker_config: DockerConfig = serde_json::from_str(config_context.unwrap().as_str()).unwrap();
|
||||
let auths = docker_config.auths;
|
||||
let auth = match auths.get("https://index.docker.io/v1/") {
|
||||
None => { return None; }
|
||||
Some(docker_auth) => { docker_auth.auth.clone().unwrap() }
|
||||
};
|
||||
let mut buffer = Vec::<u8>::new();
|
||||
let _result = general_purpose::URL_SAFE_NO_PAD.decode_vec(auth.as_bytes(), &mut buffer);
|
||||
let decoded = match std::str::from_utf8(&buffer) {
|
||||
Ok(v) => v,
|
||||
Err(e) => panic!("Invalid UTF-8 sequence: {}", e),
|
||||
};
|
||||
let idx = match decoded.find(':') {
|
||||
None => { panic!("Invalid authentication: {}", decoded) }
|
||||
Some(i) => { i }
|
||||
};
|
||||
return Some(DockerPassword {
|
||||
username: String::from(&decoded[0..idx]),
|
||||
password: String::from(&decoded[(idx + 1)..]),
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue
Block a user