refactor: extract authentication function

This commit is contained in:
椰子 2023-11-13 21:19:20 +08:00
parent 770a4180d1
commit 2fa67bbcbc
6 changed files with 118 additions and 94 deletions

View File

@ -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);
}

View File

@ -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;

View File

@ -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>;
}

View File

@ -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,
}

View 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 }
}
}

View 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)..]),
});
}