use base64;
use clap::{Parser, Subcommand};
use dirs;
use serde_json;
use std::collections::HashMap;
use std::fs::{self, File};
use std::io::{self, Read, Write};
use std::path::Path;
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Store,
Get,
Erase,
List,
}
struct CredentialStore {
path: String,
}
impl CredentialStore {
fn new() -> Self {
let home_dir = dirs::home_dir().expect("无法获取主目录");
let path = home_dir
.join(".docker")
.join("config.json")
.to_string_lossy()
.to_string();
Self { path }
}
fn read_config(
&self,
) -> Result<HashMap<String, serde_json::Value>, Box<dyn std::error::Error>> {
if !Path::new(&self.path).exists() {
return Ok(HashMap::new());
}
let file = File::open(&self.path)?;
let config: serde_json::Value = serde_json::from_reader(file)?;
let empty_auths = serde_json::Value::Object(serde_json::Map::new());
let auths = config
.get("auths")
.unwrap_or(&empty_auths);
let mut auth_map = HashMap::new();
if let serde_json::Value::Object(auths_obj) = auths {
for (registry, auth_info) in auths_obj {
auth_map.insert(registry.clone(), auth_info.clone());
}
}
Ok(auth_map)
}
fn write_config(
&self,
auths: &HashMap<String, serde_json::Value>,
) -> Result<(), Box<dyn std::error::Error>> {
let config = serde_json::json!({
"auths": auths
});
let config_dir = Path::new(&self.path).parent().unwrap();
if !config_dir.exists() {
fs::create_dir_all(config_dir)?;
}
let file = File::create(&self.path)?;
serde_json::to_writer_pretty(file, &config)?;
Ok(())
}
fn store(
&self,
registry: &str,
username: &str,
password: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let mut auths = self.read_config()?;
let auth = base64::encode(format!("{}:{}", username, password));
auths.insert(
registry.to_string(),
serde_json::json!({
"auth": auth
}),
);
self.write_config(&auths)
}
fn get(&self, registry: &str) -> Result<Option<(String, String)>, Box<dyn std::error::Error>> {
let auths = self.read_config()?;
if let Some(auth_info) = auths.get(registry) {
if let Some(auth) = auth_info.get("auth").and_then(|a| a.as_str()) {
let decoded = base64::decode(auth)?;
let credentials = String::from_utf8(decoded)?;
if let Some((username, password)) = credentials.split_once(":") {
return Ok(Some((username.to_string(), password.to_string())));
}
}
}
Ok(None)
}
fn erase(&self, registry: &str) -> Result<(), Box<dyn std::error::Error>> {
let mut auths = self.read_config()?;
auths.remove(registry);
self.write_config(&auths)
}
fn list(&self) -> Result<HashMap<String, String>, Box<dyn std::error::Error>> {
let auths = self.read_config()?;
let mut result = HashMap::new();
for (registry, _) in auths {
result.insert(registry, "".to_string());
}
Ok(result)
}
}
#[tokio::main]
async fn main() {
let cli = Cli::parse();
let store = CredentialStore::new();
match cli.command {
Commands::Store => {
let mut input = String::new();
io::stdin()
.read_to_string(&mut input)
.expect("无法读取输入");
let credential: serde_json::Value =
serde_json::from_str(&input).expect("无效的 JSON 格式");
let registry = credential
.get("ServerURL")
.and_then(|v| v.as_str())
.expect("缺少 ServerURL");
let username = credential
.get("Username")
.and_then(|v| v.as_str())
.expect("缺少 Username");
let password = credential
.get("Secret")
.and_then(|v| v.as_str())
.expect("缺少 Secret");
if let Err(e) = store.store(registry, username, password) {
eprintln!("存储凭据失败: {:?}", e);
std::process::exit(1);
}
}
Commands::Get => {
let mut input = String::new();
io::stdin()
.read_to_string(&mut input)
.expect("无法读取输入");
let registry = input.trim();
match store.get(registry) {
Ok(Some((username, password))) => {
let result = serde_json::json!({
"Username": username,
"Secret": password
});
println!("{}", serde_json::to_string(&result).unwrap());
}
Ok(None) => {
println!("{{}}");
}
Err(e) => {
eprintln!("获取凭据失败: {:?}", e);
std::process::exit(1);
}
}
}
Commands::Erase => {
let mut input = String::new();
io::stdin()
.read_to_string(&mut input)
.expect("无法读取输入");
let registry = input.trim();
if let Err(e) = store.erase(registry) {
eprintln!("删除凭据失败: {:?}", e);
std::process::exit(1);
}
}
Commands::List => match store.list() {
Ok(registries) => {
let result = serde_json::json!(registries);
println!("{}", serde_json::to_string(&result).unwrap());
}
Err(e) => {
eprintln!("列出凭据失败: {:?}", e);
std::process::exit(1);
}
},
}
}