[pbs-devel] [PATCH vma-to-pbs 8/9] switch argument handling from clap to pico-args
Filip Schauer
f.schauer at proxmox.com
Wed Apr 3 11:49:12 CEST 2024
Signed-off-by: Filip Schauer <f.schauer at proxmox.com>
---
Cargo.toml | 2 +-
src/main.rs | 238 ++++++++++++++++++++++++++++---------------------
src/vma2pbs.rs | 4 +-
3 files changed, 139 insertions(+), 105 deletions(-)
diff --git a/Cargo.toml b/Cargo.toml
index f56e351..804a179 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/src/main.rs b/src/main.rs
index ff6cc4c..df9f49a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,91 +1,106 @@
-use anyhow::{Context, Result};
-use clap::{command, Arg, ArgAction};
+use std::ffi::OsString;
+
+use anyhow::{bail, Context, Result};
use proxmox_sys::linux::tty;
mod vma;
mod vma2pbs;
use vma2pbs::{backup_vma_to_pbs, BackupVmaToPbsArgs};
-fn main() -> Result<()> {
- let matches = command!()
- .arg(
- Arg::new("repository")
- .long("repository")
- .value_name("auth_id at 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();
-
- let pbs_repository = matches.get_one::<String>("repository").unwrap().to_string();
- let vmid = matches.get_one::<String>("vmid").unwrap().to_string();
-
- let fingerprint = matches
- .get_one::<String>("fingerprint")
- .expect("Fingerprint not set. Use $PBS_FINGERPRINT or --fingerprint")
- .to_string();
-
- 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");
-
- let vma_file_path = matches.get_one::<String>("vma_file");
-
- let password_file = matches.get_one::<String>("password-file");
+const CMD_HELP: &str = "\
+Usage: vma-to-pbs [OPTIONS] --repository <auth_id at host:port:datastore> --vmid <VMID> [vma_file]
+
+Arguments:
+ [vma_file]
+
+Options:
+ --repository <auth_id at 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
+";
+
+fn parse_args() -> Result<BackupVmaToPbsArgs> {
+ let mut args: Vec<_> = std::env::args_os().collect();
+ args.remove(0); // remove the executable path.
+
+ let mut first_later_args_index = 0;
+ let options = ["-h", "--help", "-c", "--compress", "-e", "--encrypt"];
+
+ 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<()> {
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(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') {
+ 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")?;
+
+ 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
+ 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"
+ );
}
- None => String::from_utf8(tty::read_password("Key Password: ")?)?,
- })
- }
+
+ 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<()> {
+ let args = parse_args()?;
backup_vma_to_pbs(args)?;
Ok(())
diff --git a/src/vma2pbs.rs b/src/vma2pbs.rs
index 9483f6e..a530ddb 100644
--- a/src/vma2pbs.rs
+++ b/src/vma2pbs.rs
@@ -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;
@@ -21,7 +21,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,
--
2.39.2
More information about the pbs-devel
mailing list