Add --dry-run (-n) flag to create and destroy commands

Shows the exact dladm/zonecfg/zoneadm commands, IPAM allocation, and
registry writes that would happen — without executing anything. Useful
for validating template + pool config before committing to a zone install.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till Wegmueller 2026-03-22 12:16:02 +01:00
parent abdce9c927
commit 5242450dd3
No known key found for this signature in database

View file

@ -32,6 +32,10 @@ struct Cli {
#[arg(long, default_value = "/etc/zmgr")]
registry: String,
/// Show what would be done without executing system commands
#[arg(long, short = 'n')]
dry_run: bool,
#[command(subcommand)]
command: Commands,
}
@ -124,10 +128,14 @@ fn main() -> miette::Result<()> {
let cli = Cli::parse();
let registry = std::path::PathBuf::from(&cli.registry);
let dry_run = cli.dry_run;
match cli.command {
Commands::Init { force } => cmd_init(&registry, force),
Commands::Create { name, template } => cmd_create(&registry, &name, template.as_deref()),
Commands::Destroy { name } => cmd_destroy(&registry, &name),
Commands::Create { name, template } => {
cmd_create(&registry, &name, template.as_deref(), dry_run)
}
Commands::Destroy { name } => cmd_destroy(&registry, &name, dry_run),
Commands::List => cmd_list(&registry),
Commands::Status { name } => cmd_status(&registry, name.as_deref()),
Commands::Import { name } => cmd_import(&registry, name.as_deref()),
@ -172,7 +180,12 @@ fn cmd_init(registry: &Path, force: bool) -> Result<()> {
Ok(())
}
fn cmd_create(registry: &Path, name: &str, template_name: Option<&str>) -> Result<()> {
fn cmd_create(
registry: &Path,
name: &str,
template_name: Option<&str>,
dry_run: bool,
) -> Result<()> {
config::ensure_initialized(registry)?;
if Zone::exists(registry, name) {
@ -194,9 +207,6 @@ fn cmd_create(registry: &Path, name: &str, template_name: Option<&str>) -> Resul
let address = format!("{ip}/{prefix_len}");
let vnic = format!("{name}0");
// Create VNIC
exec::dladm_create_vnic(&vnic, &pool.stub)?;
// Build zonecfg commands
let autoboot = if tmpl.autoboot { "true" } else { "false" };
let zonecfg_cmds = format!(
@ -215,6 +225,40 @@ fn cmd_create(registry: &Path, name: &str, template_name: Option<&str>) -> Resul
cfg.zonepath_prefix, tmpl.brand, tmpl.ip_type, pool.gateway
);
if dry_run {
println!("[dry-run] Would create zone '{name}'");
println!();
println!(" template: {tmpl_name}");
println!(" brand: {}", tmpl.brand);
println!(" address: {address}");
println!(" vnic: {vnic} (on stub {})", pool.stub);
println!(" gateway: {}", pool.gateway);
println!(" zonepath: {}/{name}", cfg.zonepath_prefix);
println!(" autoboot: {}", tmpl.autoboot);
println!();
println!("Commands that would be executed:");
println!();
println!(" dladm create-vnic -l {} {vnic}", pool.stub);
println!();
println!(" zonecfg -z {name} <<EOF");
for line in zonecfg_cmds.lines() {
println!(" {line}");
}
println!(" EOF");
println!();
println!(" zoneadm -z {name} install");
if tmpl.autoboot {
println!(" zoneadm -z {name} boot");
}
println!();
println!("Registry entry that would be written:");
println!(" {}/zones/{name}.kdl", registry.display());
return Ok(());
}
// Create VNIC
exec::dladm_create_vnic(&vnic, &pool.stub)?;
exec::zonecfg_create(name, &zonecfg_cmds)?;
exec::zoneadm_install(name)?;
@ -249,11 +293,30 @@ fn cmd_create(registry: &Path, name: &str, template_name: Option<&str>) -> Resul
Ok(())
}
fn cmd_destroy(registry: &Path, name: &str) -> Result<()> {
fn cmd_destroy(registry: &Path, name: &str, dry_run: bool) -> Result<()> {
config::ensure_initialized(registry)?;
let zone = Zone::load(registry, name)?;
if dry_run {
println!("[dry-run] Would destroy zone '{name}'");
println!();
println!(" template: {}", zone.template);
println!(" address: {}", zone.address);
println!(" vnic: {}", zone.vnic);
println!();
println!("Commands that would be executed:");
println!();
println!(" zoneadm -z {name} halt");
println!(" zoneadm -z {name} uninstall -F");
println!(" zonecfg -z {name} delete -F");
println!(" dladm delete-vnic {}", zone.vnic);
println!();
println!("Registry entry that would be removed:");
println!(" {}/zones/{name}.kdl", registry.display());
return Ok(());
}
// Halt if running (ignore errors — zone may already be halted)
let _ = exec::zoneadm_halt(name);