Drops clap for pico-args, as the former is pretty heavy-weight and completely overhauls its API with ~every major release.
While it results in a bit more boilerplate code, it makes (especially long-term) maintenance simpler. As for the resulting binary, there is a ~13.5% decrease in binary size, from 4M to 3.4MiB (after stripping). text data bss dec hex filename 3793413 299000 616 4093029 3e7465 proxmox-auto-install-assistant-clap 3245990 286136 472 3532598 35e736 proxmox-auto-install-assistant-pico Further, the dependency tree goes from 114 to 103 dependencies, as well as the package compile time on my machine (on hot caches), roughly measured with `cargo clean && time cargo build --package proxmox-chroot --release` from ~31 to ~28s. A big chunk of the added lines are simply the help menus for the different subcommands of the tool, the actual code changes are rather small. No functional changes intended. Signed-off-by: Christoph Heiss <c.he...@proxmox.com> --- debian/control | 1 - proxmox-auto-install-assistant/Cargo.toml | 6 +- proxmox-auto-install-assistant/src/main.rs | 536 +++++++++++++++------ proxmox-auto-installer/Cargo.toml | 1 - proxmox-auto-installer/src/answer.rs | 6 +- proxmox-auto-installer/src/utils.rs | 7 +- 6 files changed, 387 insertions(+), 170 deletions(-) diff --git a/debian/control b/debian/control index 51c45a4..a2005a2 100644 --- a/debian/control +++ b/debian/control @@ -10,7 +10,6 @@ Build-Depends: cargo:native, libpve-common-perl, librsvg2-bin, librust-anyhow-1-dev, - librust-clap-4+derive-dev, librust-cursive-0.21+crossterm-backend-dev, librust-glob-0.3-dev, librust-hex-0.4-dev, diff --git a/proxmox-auto-install-assistant/Cargo.toml b/proxmox-auto-install-assistant/Cargo.toml index 43a968f..9b4a9c4 100644 --- a/proxmox-auto-install-assistant/Cargo.toml +++ b/proxmox-auto-install-assistant/Cargo.toml @@ -12,13 +12,9 @@ homepage = "https://www.proxmox.com" [dependencies] anyhow.workspace = true -log.workspace = true -proxmox-installer-common.workspace = true proxmox-auto-installer.workspace = true -serde = { workspace = true, features = ["derive"] } +proxmox-installer-common = { workspace = true, features = [ "cli" ] } serde_json.workspace = true toml.workspace = true -regex.workspace = true -clap = { version = "4.0", features = ["derive"] } glob = "0.3" diff --git a/proxmox-auto-install-assistant/src/main.rs b/proxmox-auto-install-assistant/src/main.rs index b64623b..6ba6617 100644 --- a/proxmox-auto-install-assistant/src/main.rs +++ b/proxmox-auto-install-assistant/src/main.rs @@ -1,25 +1,31 @@ -use anyhow::{Result, bail, format_err}; -use clap::{Args, Parser, Subcommand, ValueEnum}; +//! This tool can be used to prepare a Proxmox installation ISO for automated installations. +//! Additional uses are to validate the format of an answer file or to test match filters and print +//! information on the properties to match against for the current hardware. + +#![forbid(unsafe_code)] + +use anyhow::{Context, Result, bail, format_err}; use glob::Pattern; -use serde::Serialize; use std::{ collections::BTreeMap, fmt, fs, io::{self, Read}, path::{Path, PathBuf}, - process::{Command, Stdio}, + process::{self, Command, Stdio}, + str::FromStr, }; use proxmox_auto_installer::{ answer::{Answer, FilterMatch}, sysinfo::SysInfo, utils::{ - AutoInstSettings, FetchAnswerFrom, HttpOptions, get_matched_udev_indexes, get_nic_list, - get_single_udev_index, verify_email_and_root_password_settings, verify_first_boot_settings, + AutoInstSettings, FetchAnswerFrom, HttpOptions, default_partition_label, + get_matched_udev_indexes, get_nic_list, get_single_udev_index, + verify_email_and_root_password_settings, verify_first_boot_settings, verify_locale_settings, }, }; -use proxmox_installer_common::{FIRST_BOOT_EXEC_MAX_SIZE, FIRST_BOOT_EXEC_NAME}; +use proxmox_installer_common::{FIRST_BOOT_EXEC_MAX_SIZE, FIRST_BOOT_EXEC_NAME, cli}; static PROXMOX_ISO_FLAG: &str = "/auto-installer-capable"; @@ -27,225 +33,439 @@ static PROXMOX_ISO_FLAG: &str = "/auto-installer-capable"; /// [LocaleInfo](`proxmox_installer_common::setup::LocaleInfo`) struct. const LOCALE_INFO: &str = include_str!("../../locale-info.json"); -/// This tool can be used to prepare a Proxmox installation ISO for automated installations. -/// Additional uses are to validate the format of an answer file or to test match filters and -/// print information on the properties to match against for the current hardware. -#[derive(Parser, Debug)] -#[command(author, version, about, long_about = None)] -struct Cli { - #[command(subcommand)] - command: Commands, +/// Arguments for the `device-info` command. +struct CommandDeviceInfoArgs { + /// Device type for which information should be shown. + device_type: AllDeviceTypes, } -#[derive(Subcommand, Debug)] -enum Commands { - PrepareIso(CommandPrepareISO), - ValidateAnswer(CommandValidateAnswer), - DeviceMatch(CommandDeviceMatch), - DeviceInfo(CommandDeviceInfo), - SystemInfo(CommandSystemInfo), +impl cli::Subcommand for CommandDeviceInfoArgs { + fn parse(args: &mut cli::Arguments) -> Result<Self> { + Ok(Self { + device_type: args + .opt_value_from_str(["-t", "--type"])? + .unwrap_or(AllDeviceTypes::All), + }) + } + + fn print_usage() { + eprintln!( + r#"Show device information that can be used for filters. + +USAGE: + {} device-info [OPTIONS] + +OPTIONS: + -t, --type <type> For which device type information should be shown [default: all] [possible values: all, network, disk] + -h, --help Print this help + -V, --version Print version + "#, + env!("CARGO_PKG_NAME") + ); + } + + fn run(&self) -> Result<()> { + info(self) + } } -/// Show device information that can be used for filters -#[derive(Args, Debug)] -struct CommandDeviceInfo { - /// For which device type information should be shown - #[arg(name="type", short, long, value_enum, default_value_t=AllDeviceTypes::All)] - device: AllDeviceTypes, -} - -/// Test which devices the given filter matches against -/// -/// Filters support the following syntax: -/// - `?` Match a single character -/// - `*` Match any number of characters -/// - `[a]`, `[0-9]` Specific character or range of characters -/// - `[!a]` Negate a specific character of range -/// -/// To avoid globbing characters being interpreted by the shell, use single quotes. -/// Multiple filters can be defined. -/// -/// Examples: -/// Match disks against the serial number and device name, both must match: -/// -/// ```sh -/// proxmox-auto-install-assistant match --filter-match all disk 'ID_SERIAL_SHORT=*2222*' 'DEVNAME=*nvme*' -/// ``` -#[derive(Args, Debug)] -#[command(verbatim_doc_comment)] -struct CommandDeviceMatch { - /// Device type to match the filter against - r#type: Devicetype, +/// Arguments for the `device-match` command. +struct CommandDeviceMatchArgs { + /// Device type to match the filter against. + device_type: DeviceType, /// Filter in the format KEY=VALUE where the key is the UDEV key and VALUE the filter string. /// Multiple filters are possible, separated by a space. filter: Vec<String>, /// Defines if any filter or all filters must match. - #[arg(long, value_enum, default_value_t=FilterMatch::Any)] filter_match: FilterMatch, } -/// Validate if an answer file is formatted correctly. -#[derive(Args, Debug)] -struct CommandValidateAnswer { - /// Path to the answer file +impl cli::Subcommand for CommandDeviceMatchArgs { + fn parse(args: &mut cli::Arguments) -> Result<Self> { + let filter_match = args + .opt_value_from_str("--filter-match")? + .unwrap_or(FilterMatch::Any); + + let device_type = args.free_from_str().context("parsing device type")?; + let mut filter = vec![]; + while let Some(s) = args.opt_free_from_str()? { + filter.push(s); + } + + Ok(Self { + device_type, + filter, + filter_match, + }) + } + + fn print_usage() { + eprintln!( + r#"Test which devices the given filter matches against. + +Filters support the following syntax: +- `?` Match a single character +- `*` Match any number of characters +- `[a]`, `[0-9]` Specific character or range of characters +- `[!a]` Negate a specific character of range + +To avoid globbing characters being interpreted by the shell, use single quotes. +Multiple filters can be defined. + +Examples: +Match disks against the serial number and device name, both must match: + +$ proxmox-auto-install-assistant match --filter-match all disk 'ID_SERIAL_SHORT=*2222*' 'DEVNAME=*nvme*' + +USAGE: + {} device-match [OPTIONS] <TYPE> [FILTER]... + +ARGUMENTS: + <TYPE> + Device type to match the filter against + + [possible values: network, disk] + + [FILTER]... + Filter in the format KEY=VALUE where the key is the UDEV key and VALUE the filter string. Multiple filters are possible, separated by a space. + +OPTIONS: + --filter-match <FILTER_MATCH> + Defines if any filter or all filters must match [default: any] [possible values: any, all] + + -h, --help Print this help + -V, --version Print version + "#, + env!("CARGO_PKG_NAME") + ); + } + + fn run(&self) -> Result<()> { + match_filter(self) + } +} + +/// Arguments for the `validate-answer` command. +struct CommandValidateAnswerArgs { + /// Path to the answer file. path: PathBuf, - #[arg(short, long, default_value_t = false)] + /// Whether to also show the full answer as parsed. debug: bool, } -/// Prepare an ISO for automated installation. -/// -/// The behavior of how to fetch an answer file must be set with the '--fetch-from' parameter. The -/// answer file can be:{n} -/// * integrated into the ISO itself ('iso'){n} -/// * present on a partition / file-system, matched by its label ('partition'){n} -/// * requested via an HTTP Post request ('http'). -/// -/// The URL for the HTTP mode can be defined for the ISO with the '--url' argument. If not present, -/// it will try to get a URL from a DHCP option (250, TXT) or by querying a DNS TXT record for the -/// domain 'proxmox-auto-installer.{search domain}'. -/// -/// The TLS certificate fingerprint can either be defined via the '--cert-fingerprint' argument or -/// alternatively via the custom DHCP option (251, TXT) or in a DNS TXT record located at -/// 'proxmox-auto-installer-cert-fingerprint.{search domain}'. -/// -/// The latter options to provide the TLS fingerprint will only be used if the same method was used -/// to retrieve the URL. For example, the DNS TXT record for the fingerprint will only be used, if -/// no one was configured with the '--cert-fingerprint' parameter and if the URL was retrieved via -/// the DNS TXT record. -/// -/// If the 'partition' mode is used, the '--partition-label' parameter can be used to set the -/// partition label the auto-installer should search for. This defaults to 'proxmox-ais'. -#[derive(Args, Debug)] -struct CommandPrepareISO { - /// Path to the source ISO to prepare +impl cli::Subcommand for CommandValidateAnswerArgs { + fn parse(args: &mut cli::Arguments) -> Result<Self> { + Ok(Self { + debug: args.contains(["-d", "--debug"]), + // Needs to be last + path: args.free_from_str()?, + }) + } + + fn print_usage() { + eprintln!( + r#"Validate if an answer file is formatted correctly. + +USAGE: + {} validate-answer [OPTIONS] <PATH> + +ARGUMENTS: + <PATH> Path to the answer file. + +OPTIONS: + -d, --debug Also show the full answer as parsed. + -h, --help Print this help + -V, --version Print version + "#, + env!("CARGO_PKG_NAME") + ); + } + + fn run(&self) -> Result<()> { + validate_answer(self) + } +} + +/// Arguments for the `prepare-iso` command. +struct CommandPrepareISOArgs { + /// Path to the source ISO to prepare. input: PathBuf, /// Path to store the final ISO to, defaults to an auto-generated file name depending on mode /// and the same directory as the source file is located in. - #[arg(long)] output: Option<PathBuf>, /// Where the automatic installer should fetch the answer file from. - #[arg(long, value_enum)] fetch_from: FetchAnswerFrom, /// Include the specified answer file in the ISO. Requires the '--fetch-from' parameter /// to be set to 'iso'. - #[arg(long)] answer_file: Option<PathBuf>, - /// Specify URL for fetching the answer file via HTTP - #[arg(long)] + /// Specify URL for fetching the answer file via HTTP. url: Option<String>, /// Pin the ISO to the specified SHA256 TLS certificate fingerprint. - #[arg(long)] cert_fingerprint: Option<String>, /// Staging directory to use for preparing the new ISO file. Defaults to the directory of the /// input ISO file. - #[arg(long)] tmp: Option<String>, /// Can be used in combination with `--fetch-from partition` to set the partition label /// the auto-installer will search for. // FAT can only handle 11 characters (per specification at least, drivers might allow more), // so shorten "Automated Installer Source" to "AIS" to be safe. - #[arg(long, default_value_t = { "proxmox-ais".to_owned() } )] partition_label: String, /// Executable file to include, which should be run on the first system boot after the /// installation. Can be used for further bootstrapping the new system. /// /// Must be appropriately enabled in the answer file. - #[arg(long)] on_first_boot: Option<PathBuf>, } -/// Show the system information that can be used to identify a host. -/// -/// The shown information is sent as POST HTTP request when fetching the answer file for the -/// automatic installation through HTTP, You can, for example, use this to return a dynamically -/// assembled answer file. -#[derive(Args, Debug)] -struct CommandSystemInfo {} +impl cli::Subcommand for CommandPrepareISOArgs { + fn parse(args: &mut cli::Arguments) -> Result<Self> { + Ok(Self { + output: args.opt_value_from_str("--output")?, + fetch_from: args.value_from_str("--fetch-from")?, + answer_file: args.opt_value_from_str("--answer-file")?, + url: args.opt_value_from_str("--url")?, + cert_fingerprint: args.opt_value_from_str("--cert-fingerprint")?, + tmp: args.opt_value_from_str("--tmp")?, + partition_label: args + .opt_value_from_str("--partition-label")? + .unwrap_or_else(default_partition_label), + on_first_boot: args.opt_value_from_str("--on-first-boot")?, + // Needs to be last + input: args.free_from_str()?, + }) + } -#[derive(Args, Debug)] -struct GlobalOpts { - /// Output format - #[arg(long, short, value_enum)] - format: OutputFormat, + fn print_usage() { + eprintln!( + r#"Prepare an ISO for automated installation. + +The behavior of how to fetch an answer file must be set with the '--fetch-from' parameter. +The answer file can be: + * integrated into the ISO itself ('iso') + * present on a partition / file-system, matched by its label ('partition') + * requested via an HTTP Post request ('http'). + +The URL for the HTTP mode can be defined for the ISO with the '--url' argument. If not present, it +will try to get a URL from a DHCP option (250, TXT) or by querying a DNS TXT record for the domain +'proxmox-auto-installer.{{search domain}}'. + +The TLS certificate fingerprint can either be defined via the '--cert-fingerprint' argument or +alternatively via the custom DHCP option (251, TXT) or in a DNS TXT record located at +'proxmox-auto-installer-cert-fingerprint.{{search domain}}'. + +The latter options to provide the TLS fingerprint will only be used if the same method was used to +retrieve the URL. For example, the DNS TXT record for the fingerprint will only be used, if no one +was configured with the '--cert-fingerprint' parameter and if the URL was retrieved via the DNS TXT +record. + +If the 'partition' mode is used, the '--partition-label' parameter can be used to set the partition +label the auto-installer should search for. This defaults to 'proxmox-ais'. + +USAGE: + {} prepare-iso [OPTIONS] --fetch-from <FETCH_FROM> <INPUT> + +ARGUMENTS: + <INPUT> + Path to the source ISO to prepare + +OPTIONS: + --output <OUTPUT> + Path to store the final ISO to, defaults to an auto-generated file name depending on mode + and the same directory as the source file is located in. + + --fetch-from <FETCH_FROM> + Where the automatic installer should fetch the answer file from. + + [possible values: iso, http, partition] + + --answer-file <ANSWER_FILE> + Include the specified answer file in the ISO. Requires the '--fetch-from' parameter to + be set to 'iso'. + + --url <URL> + Specify URL for fetching the answer file via HTTP. + + --cert-fingerprint <CERT_FINGERPRINT> + Pin the ISO to the specified SHA256 TLS certificate fingerprint. + + --tmp <TMP> + Staging directory to use for preparing the new ISO file. Defaults to the directory of the + input ISO file. + + --partition-label <PARTITION_LABEL> + Can be used in combination with `--fetch-from partition` to set the partition label the + auto-installer will search for. + + [default: proxmox-ais] + + --on-first-boot <ON_FIRST_BOOT> + Executable file to include, which should be run on the first system boot after the + installation. Can be used for further bootstrapping the new system. + + Must be appropriately enabled in the answer file. + + -h, --help Print this help + -V, --version Print version + "#, + env!("CARGO_PKG_NAME") + ); + } + + fn run(&self) -> Result<()> { + prepare_iso(self) + } } -#[derive(Clone, Debug, ValueEnum, PartialEq)] +/// Arguments for the `system-info` command. +struct CommandSystemInfoArgs; + +impl cli::Subcommand for CommandSystemInfoArgs { + fn parse(_: &mut cli::Arguments) -> Result<Self> { + Ok(Self) + } + + fn print_usage() { + eprintln!( + r#"Show the system information that can be used to identify a host. + +The shown information is sent as POST HTTP request when fetching the answer file for the +automatic installation through HTTP, You can, for example, use this to return a dynamically +assembled answer file. + +USAGE: + {} system-info [OPTIONS] + +OPTIONS: + -h, --help Print this help + -V, --version Print version + "#, + env!("CARGO_PKG_NAME") + ); + } + + fn run(&self) -> Result<()> { + show_system_info(self) + } +} + +#[derive(PartialEq)] enum AllDeviceTypes { All, Network, Disk, } -#[derive(Clone, Debug, ValueEnum)] -enum Devicetype { +impl FromStr for AllDeviceTypes { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result<Self> { + match s.to_lowercase().as_ref() { + "all" => Ok(AllDeviceTypes::All), + "network" => Ok(AllDeviceTypes::Network), + "disk" => Ok(AllDeviceTypes::Disk), + _ => bail!("unknown device type '{s}'"), + } + } +} + +enum DeviceType { Network, Disk, } -#[derive(Clone, Debug, ValueEnum)] -enum OutputFormat { - Pretty, - Json, -} +impl FromStr for DeviceType { + type Err = anyhow::Error; -#[derive(Serialize)] -struct Devs { - disks: Option<BTreeMap<String, BTreeMap<String, String>>>, - nics: Option<BTreeMap<String, BTreeMap<String, String>>>, -} - -fn main() { - let args = Cli::parse(); - let res = match &args.command { - Commands::PrepareIso(args) => prepare_iso(args), - Commands::ValidateAnswer(args) => validate_answer(args), - Commands::DeviceInfo(args) => info(args), - Commands::DeviceMatch(args) => match_filter(args), - Commands::SystemInfo(args) => show_system_info(args), - }; - if let Err(err) = res { - eprintln!("Error: {err:?}"); - std::process::exit(1); + fn from_str(s: &str) -> Result<Self> { + match s.to_lowercase().as_ref() { + "network" => Ok(DeviceType::Network), + "disk" => Ok(DeviceType::Disk), + _ => bail!("unknown device type '{s}'"), + } } } -fn info(args: &CommandDeviceInfo) -> Result<()> { - let mut devs = Devs { - disks: None, - nics: None, - }; +fn main() -> process::ExitCode { + cli::run(cli::AppInfo { + global_help: &format!( + r#"This tool can be used to prepare a Proxmox installation ISO for automated installations. +Additional uses are to validate the format of an answer file or to test match filters and +print information on the properties to match against for the current hardware - if args.device == AllDeviceTypes::Network || args.device == AllDeviceTypes::All { +USAGE: + {} <COMMAND> + +COMMANDS: + prepare-iso Prepare an ISO for automated installation + validate-answer Validate if an answer file is formatted correctly + device-match Test which devices the given filter matches against + device-info Show device information that can be used for filters + system-info Show the system information that can be used to identify a host + +GLOBAL OPTIONS: + -h, --help Print help + -V, --version Print version +"#, + env!("CARGO_PKG_NAME") + ), + on_command: |s, args| match s { + Some("prepare-iso") => cli::handle_command::<CommandPrepareISOArgs>(args), + Some("validate-answer") => cli::handle_command::<CommandValidateAnswerArgs>(args), + Some("device-match") => cli::handle_command::<CommandDeviceMatchArgs>(args), + Some("device-info") => cli::handle_command::<CommandDeviceInfoArgs>(args), + Some("system-info") => cli::handle_command::<CommandSystemInfoArgs>(args), + Some(s) => bail!("unknown subcommand '{s}'"), + None => bail!("subcommand required"), + }, + }) +} + +fn info(args: &CommandDeviceInfoArgs) -> Result<()> { + let nics = if matches!( + args.device_type, + AllDeviceTypes::All | AllDeviceTypes::Network + ) { match get_nics() { - Ok(res) => devs.nics = Some(res), + Ok(res) => Some(res), Err(err) => bail!("Error getting NIC data: {err}"), } - } - if args.device == AllDeviceTypes::Disk || args.device == AllDeviceTypes::All { + } else { + None + }; + + let disks = if matches!(args.device_type, AllDeviceTypes::All | AllDeviceTypes::Disk) { match get_disks() { - Ok(res) => devs.disks = Some(res), + Ok(res) => Some(res), Err(err) => bail!("Error getting disk data: {err}"), } - } - println!("{}", serde_json::to_string_pretty(&devs).unwrap()); + } else { + None + }; + + serde_json::to_writer_pretty( + std::io::stdout(), + &serde_json::json!({ + "disks": disks, + "nics": nics, + }), + )?; Ok(()) } -fn match_filter(args: &CommandDeviceMatch) -> Result<()> { - let devs: BTreeMap<String, BTreeMap<String, String>> = match args.r#type { - Devicetype::Disk => get_disks().unwrap(), - Devicetype::Network => get_nics().unwrap(), +fn match_filter(args: &CommandDeviceMatchArgs) -> Result<()> { + let devs: BTreeMap<String, BTreeMap<String, String>> = match args.device_type { + DeviceType::Disk => get_disks().unwrap(), + DeviceType::Network => get_nics().unwrap(), }; // parse filters @@ -266,21 +486,21 @@ fn match_filter(args: &CommandDeviceMatch) -> Result<()> { } // align return values - let result = match args.r#type { - Devicetype::Disk => { + let result = match args.device_type { + DeviceType::Disk => { get_matched_udev_indexes(&filters, &devs, args.filter_match == FilterMatch::All) } - Devicetype::Network => get_single_udev_index(&filters, &devs).map(|r| vec![r]), + DeviceType::Network => get_single_udev_index(&filters, &devs).map(|r| vec![r]), }; match result { - Ok(result) => println!("{}", serde_json::to_string_pretty(&result).unwrap()), + Ok(result) => serde_json::to_writer_pretty(std::io::stdout(), &result)?, Err(err) => bail!("Error matching filters: {err}"), } Ok(()) } -fn validate_answer(args: &CommandValidateAnswer) -> Result<()> { +fn validate_answer(args: &CommandValidateAnswerArgs) -> Result<()> { let answer = parse_answer(&args.path)?; if args.debug { println!("Parsed data from answer file:\n{:#?}", answer); @@ -288,7 +508,7 @@ fn validate_answer(args: &CommandValidateAnswer) -> Result<()> { Ok(()) } -fn show_system_info(_args: &CommandSystemInfo) -> Result<()> { +fn show_system_info(_args: &CommandSystemInfoArgs) -> Result<()> { match SysInfo::as_json_pretty() { Ok(res) => println!("{res}"), Err(err) => eprintln!("Error fetching system info: {err}"), @@ -296,7 +516,7 @@ fn show_system_info(_args: &CommandSystemInfo) -> Result<()> { Ok(()) } -fn prepare_iso(args: &CommandPrepareISO) -> Result<()> { +fn prepare_iso(args: &CommandPrepareISOArgs) -> Result<()> { check_prepare_requirements(args)?; let uuid = get_iso_uuid(&args.input)?; @@ -393,7 +613,7 @@ fn prepare_iso(args: &CommandPrepareISO) -> Result<()> { Ok(()) } -fn final_iso_location(args: &CommandPrepareISO) -> PathBuf { +fn final_iso_location(args: &CommandPrepareISOArgs) -> PathBuf { if let Some(specified) = args.output.clone() { return specified; } @@ -606,11 +826,11 @@ fn parse_answer(path: impl AsRef<Path> + fmt::Debug) -> Result<Answer> { } } -fn check_prepare_requirements(args: &CommandPrepareISO) -> Result<()> { +fn check_prepare_requirements(args: &CommandPrepareISOArgs) -> Result<()> { match Path::try_exists(&args.input) { Ok(true) => (), - Ok(false) => bail!("Source file does not exist."), - Err(_) => bail!("Source file does not exist."), + Ok(false) => bail!("Source file {:?} does not exist.", args.input), + Err(err) => bail!("Failed to stat source file {:?}: {err:#}", args.input), } match Command::new("xorriso") diff --git a/proxmox-auto-installer/Cargo.toml b/proxmox-auto-installer/Cargo.toml index 221d5d2..8a5283e 100644 --- a/proxmox-auto-installer/Cargo.toml +++ b/proxmox-auto-installer/Cargo.toml @@ -19,7 +19,6 @@ serde_json.workspace = true serde_plain.workspace = true toml.workspace = true -clap = { version = "4.0", features = ["derive"] } glob = "0.3" [dev-dependencies] diff --git a/proxmox-auto-installer/src/answer.rs b/proxmox-auto-installer/src/answer.rs index 34065e8..eb1d829 100644 --- a/proxmox-auto-installer/src/answer.rs +++ b/proxmox-auto-installer/src/answer.rs @@ -1,5 +1,4 @@ use anyhow::{Result, format_err}; -use clap::ValueEnum; use proxmox_installer_common::{ options::{ BtrfsCompressOption, BtrfsRaidLevel, FsType, ZfsChecksumOption, ZfsCompressOption, @@ -364,13 +363,16 @@ pub enum DiskSelection { Selection(Vec<String>), Filter(BTreeMap<String, String>), } -#[derive(Clone, Deserialize, Debug, PartialEq, ValueEnum)] + +#[derive(Clone, Deserialize, Debug, PartialEq)] #[serde(rename_all = "lowercase", deny_unknown_fields)] pub enum FilterMatch { Any, All, } +serde_plain::derive_fromstr_from_deserialize!(FilterMatch); + #[derive(Clone, Deserialize, Serialize, Debug, PartialEq)] #[serde(rename_all = "lowercase", deny_unknown_fields)] pub enum Filesystem { diff --git a/proxmox-auto-installer/src/utils.rs b/proxmox-auto-installer/src/utils.rs index 365e01a..e049748 100644 --- a/proxmox-auto-installer/src/utils.rs +++ b/proxmox-auto-installer/src/utils.rs @@ -1,5 +1,4 @@ use anyhow::{Context, Result, bail}; -use clap::ValueEnum; use glob::Pattern; use log::info; use std::{collections::BTreeMap, process::Command}; @@ -95,7 +94,7 @@ pub fn get_single_udev_index( Ok(dev_index.unwrap()) } -#[derive(Deserialize, Serialize, Debug, Clone, ValueEnum, PartialEq)] +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)] #[serde(rename_all = "lowercase", deny_unknown_fields)] pub enum FetchAnswerFrom { Iso, @@ -103,6 +102,8 @@ pub enum FetchAnswerFrom { Partition, } +serde_plain::derive_fromstr_from_deserialize!(FetchAnswerFrom); + #[derive(Deserialize, Serialize, Clone, Default, PartialEq, Debug)] pub struct HttpOptions { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -121,7 +122,7 @@ pub struct AutoInstSettings { pub http: HttpOptions, } -fn default_partition_label() -> String { +pub fn default_partition_label() -> String { "proxmox-ais".to_owned() } -- 2.49.0 _______________________________________________ pve-devel mailing list pve-devel@lists.proxmox.com https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel