switch argument handling from clap to pico-args

Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
This commit is contained in:
Filip Schauer 2024-04-09 14:14:22 +02:00 committed by Wolfgang Bumiller
parent 715c658e0e
commit 80fb0a4a79
3 changed files with 134 additions and 100 deletions

View File

@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
anyhow = "1.0"
bincode = "1.3"
clap = { version = "4.0.32", features = ["cargo", "env"] }
pico-args = "0.4"
md5 = "0.7.0"
scopeguard = "1.1.0"
serde = "1.0"

View File

@ -1,91 +1,106 @@
use anyhow::{Context, Error};
use clap::{command, Arg, ArgAction};
use std::ffi::OsString;
use anyhow::{bail, Context, Error};
use proxmox_sys::linux::tty;
mod vma;
mod vma2pbs;
use vma2pbs::{backup_vma_to_pbs, BackupVmaToPbsArgs};
fn main() -> Result<(), Error> {
let matches = command!()
.arg(
Arg::new("repository")
.long("repository")
.value_name("auth_id@host:port:datastore")
.help("Repository URL")
.required(true),
)
.arg(
Arg::new("vmid")
.long("vmid")
.value_name("VMID")
.help("Backup ID")
.required(true),
)
.arg(
Arg::new("fingerprint")
.long("fingerprint")
.value_name("FINGERPRINT")
.help("Proxmox Backup Server Fingerprint")
.env("PBS_FINGERPRINT"),
)
.arg(
Arg::new("keyfile")
.long("keyfile")
.value_name("KEYFILE")
.help("Key file"),
)
.arg(
Arg::new("master_keyfile")
.long("master_keyfile")
.value_name("MASTER_KEYFILE")
.help("Master key file"),
)
.arg(
Arg::new("compress")
.long("compress")
.short('c')
.help("Compress the Backup")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("encrypt")
.long("encrypt")
.short('e')
.help("Encrypt the Backup")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new("password-file")
.long("password-file")
.value_name("PASSWORD_FILE")
.help("Password file"),
)
.arg(
Arg::new("key-password-file")
.long("key-password-file")
.value_name("KEY_PASSWORD_FILE")
.help("Key password file"),
)
.arg(Arg::new("vma_file"))
.get_matches();
const CMD_HELP: &str = "\
Usage: vma-to-pbs [OPTIONS] --repository <auth_id@host:port:datastore> --vmid <VMID> [vma_file]
let pbs_repository = matches.get_one::<String>("repository").unwrap().to_string();
let vmid = matches.get_one::<String>("vmid").unwrap().to_string();
Arguments:
[vma_file]
let fingerprint = matches
.get_one::<String>("fingerprint")
.context("Fingerprint not set. Use $PBS_FINGERPRINT or --fingerprint")?
.to_string();
Options:
--repository <auth_id@host:port:datastore>
Repository URL
--vmid <VMID>
Backup ID
--fingerprint <FINGERPRINT>
Proxmox Backup Server Fingerprint [env: PBS_FINGERPRINT=]
--keyfile <KEYFILE>
Key file
--master_keyfile <MASTER_KEYFILE>
Master key file
-c, --compress
Compress the Backup
-e, --encrypt
Encrypt the Backup
--password_file <PASSWORD_FILE>
Password file
--key_password_file <KEY_PASSWORD_FILE>
Key password file
-h, --help
Print help
-V, --version
Print version
";
let keyfile = matches.get_one::<String>("keyfile");
let master_keyfile = matches.get_one::<String>("master_keyfile");
let compress = matches.get_flag("compress");
let encrypt = matches.get_flag("encrypt");
fn parse_args() -> Result<BackupVmaToPbsArgs, Error> {
let mut args: Vec<_> = std::env::args_os().collect();
args.remove(0); // remove the executable path.
let vma_file_path = matches.get_one::<String>("vma_file");
let mut first_later_args_index = 0;
let options = ["-h", "--help", "-c", "--compress", "-e", "--encrypt"];
let password_file = matches.get_one::<String>("password-file");
for (i, arg) in args.iter().enumerate() {
if let Some(arg) = arg.to_str() {
if arg.starts_with('-') {
if arg == "--" {
args.remove(i);
first_later_args_index = i;
break;
}
first_later_args_index = i + 1;
if !options.contains(&arg) {
first_later_args_index += 1;
}
}
}
}
let forwarded_args = if first_later_args_index > args.len() {
Vec::new()
} else {
args.split_off(first_later_args_index)
};
let mut args = pico_args::Arguments::from_vec(args);
if args.contains(["-h", "--help"]) {
print!("{CMD_HELP}");
std::process::exit(0);
}
let pbs_repository = args.value_from_str("--repository")?;
let vmid = args.value_from_str("--vmid")?;
let fingerprint = args.opt_value_from_str("--fingerprint")?;
let keyfile = args.opt_value_from_str("--keyfile")?;
let master_keyfile = args.opt_value_from_str("--master_keyfile")?;
let compress = args.contains(["-c", "--compress"]);
let encrypt = args.contains(["-e", "--encrypt"]);
let password_file: Option<OsString> = args.opt_value_from_str("--password-file")?;
let key_password_file: Option<OsString> = args.opt_value_from_str("--key-password-file")?;
if !args.finish().is_empty() {
bail!("unexpected extra arguments, use '-h' for usage");
}
let fingerprint = match fingerprint {
Some(v) => v,
None => std::env::var("PBS_FINGERPRINT")
.context("Fingerprint not set. Use $PBS_FINGERPRINT or --fingerprint")?,
};
if forwarded_args.len() > 1 {
bail!("too many arguments");
}
let vma_file_path = forwarded_args.first();
let pbs_password = match password_file {
Some(password_file) => {
@ -101,46 +116,65 @@ fn main() -> Result<(), Error> {
password
}
None => String::from_utf8(tty::read_password("Password: ")?)?,
None => {
if vma_file_path.is_none() {
bail!(
"Please use --password-file to provide the password \
when passing the VMA file to stdin"
);
}
String::from_utf8(tty::read_password("Password: ")?)?
}
};
let key_password = match keyfile {
Some(_) => {
let key_password_file = matches.get_one::<String>("key_password_file");
Some(_) => Some(match key_password_file {
Some(key_password_file) => {
let mut key_password = std::fs::read_to_string(key_password_file)
.context("Could not read key password file")?;
Some(match key_password_file {
Some(key_password_file) => {
let mut key_password = std::fs::read_to_string(key_password_file)
.context("Could not read key password file")?;
if key_password.ends_with('\n') || key_password.ends_with('\r') {
if key_password.ends_with('\n') || key_password.ends_with('\r') {
key_password.pop();
if key_password.ends_with('\r') {
key_password.pop();
if key_password.ends_with('\r') {
key_password.pop();
}
}
key_password
}
None => String::from_utf8(tty::read_password("Key Password: ")?)?,
})
}
key_password
}
None => {
if vma_file_path.is_none() {
bail!(
"Please use --key-password-file to provide the password \
when passing the VMA file to stdin"
);
}
String::from_utf8(tty::read_password("Key Password: ")?)?
}
}),
None => None,
};
let args = BackupVmaToPbsArgs {
let options = BackupVmaToPbsArgs {
vma_file_path: vma_file_path.cloned(),
pbs_repository,
backup_id: vmid,
pbs_password,
keyfile: keyfile.cloned(),
keyfile,
key_password,
master_keyfile: master_keyfile.cloned(),
master_keyfile,
fingerprint,
compress,
encrypt,
};
Ok(options)
}
fn main() -> Result<(), Error> {
let args = parse_args()?;
backup_vma_to_pbs(args)?;
Ok(())

View File

@ -1,7 +1,7 @@
use std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::ffi::{c_char, CStr, CString};
use std::ffi::{c_char, CStr, CString, OsString};
use std::fs::File;
use std::io::{stdin, BufRead, BufReader, Read};
use std::ptr;
@ -22,7 +22,7 @@ use crate::vma::VmaReader;
const VMA_CLUSTER_SIZE: usize = 65536;
pub struct BackupVmaToPbsArgs {
pub vma_file_path: Option<String>,
pub vma_file_path: Option<OsString>,
pub pbs_repository: String,
pub backup_id: String,
pub pbs_password: String,