From: Christoph Heiss <c.heiss@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH installer 3/5] assistant: replace clap with pico-args for command argument parsing
Date: Fri, 9 May 2025 14:09:15 +0200 [thread overview]
Message-ID: <20250509121007.1430080-4-c.heiss@proxmox.com> (raw)
In-Reply-To: <20250509121007.1430080-1-c.heiss@proxmox.com>
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.heiss@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
next prev parent reply other threads:[~2025-05-09 12:12 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-05-09 12:09 [pve-devel] [PATCH installer 0/5] chroot, assistant: replace clap with pico-args Christoph Heiss
2025-05-09 12:09 ` [pve-devel] [PATCH installer 1/5] common: introduce simple cli subcommand parser based on pico-args Christoph Heiss
2025-05-09 12:09 ` [pve-devel] [PATCH installer 2/5] chroot: replace clap with pico-args for command argument parsing Christoph Heiss
2025-05-09 12:09 ` Christoph Heiss [this message]
2025-05-09 12:09 ` [pve-devel] [PATCH installer 4/5] chroot: replace btrfs parsing regex with lsblk json parsing Christoph Heiss
2025-05-09 12:09 ` [pve-devel] [PATCH installer 5/5] tui: drop unused dependencies from manifest Christoph Heiss
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250509121007.1430080-4-c.heiss@proxmox.com \
--to=c.heiss@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal