use crate::cli::CloneArgs;
use crate::config::EffectiveConfig;
use crate::errors::ExitCode;
use crate::models::{CommandResponse, OperationResult};
use crate::naming::generate_name;
use crate::output::Reporter;
use crate::state::{self, VmMetadata};
use crate::utm;
use chrono::Utc;
use color_eyre::Result;
use std::collections::HashSet;
pub fn run(args: CloneArgs, cfg: &EffectiveConfig, reporter: &Reporter) -> Result<ExitCode> {
let template = args
.template
.as_deref()
.unwrap_or_else(|| cfg.template_for(args.os_type));
let existing = utm::list_vms(cfg)?
.into_iter()
.map(|vm| vm.name)
.collect::<HashSet<_>>();
let prefix = args.prefix.as_deref().unwrap_or(&cfg.default_prefix);
let mut name = match generate_name(
prefix,
&cfg.naming_template,
cfg.naming_rand_len,
cfg.naming_max_retries,
args.os_type,
args.name.as_deref(),
args.name_exact,
args.name_template.as_deref(),
&existing,
) {
Ok(name) => name,
Err(err) => {
if reporter.is_json() {
reporter.print_json(&CommandResponse::<OperationResult>::failure(
"create",
format!("{}", err),
))?;
} else {
reporter.error(&format!("{}", err));
}
return Ok(ExitCode::Conflict);
}
};
if cfg.dry_run {
let result = OperationResult {
ok: true,
action: "create".to_string(),
target: Some(name.clone()),
message: format!("dry-run: would clone '{}' from '{}'", name, template),
warnings: Vec::new(),
};
if reporter.is_json() {
reporter.print_json(&CommandResponse::success("create", result))?;
} else {
reporter.info(&result.message);
}
return Ok(ExitCode::Success);
}
reporter.info(&format!("cloning '{}' to '{}'", template, name));
let mut clone_err: Option<String> = None;
for attempt in 0..=2 {
if attempt > 0 {
let fresh: HashSet<String> = utm::list_vms(cfg)
.map(|vms| vms.into_iter().map(|vm| vm.name).collect())
.unwrap_or_default();
let retry_prefix = args.prefix.as_deref().unwrap_or(&cfg.default_prefix);
match generate_name(
retry_prefix,
&cfg.naming_template,
cfg.naming_rand_len,
cfg.naming_max_retries,
args.os_type,
None,
false,
None,
&fresh,
) {
Ok(n) => name = n,
Err(_) => {
return Ok(ExitCode::Conflict);
}
}
}
match utm::clone_vm(cfg, template, &name) {
Ok(()) => break,
Err(e) => {
let fresh = utm::list_vms(cfg).unwrap_or_default();
if fresh.iter().any(|vm| vm.name == name) {
continue;
}
clone_err = Some(format!("{}", e));
break;
}
}
}
if let Some(err) = clone_err {
if reporter.is_json() {
reporter.print_json(&CommandResponse::<OperationResult>::failure("create", err))?;
} else {
reporter.error(&err);
}
return Ok(ExitCode::ExternalCommandFailed);
}
state::upsert_vm(
&cfg.state_path,
VmMetadata {
name: name.clone(),
os: Some(args.os_type.as_str().to_string()),
template: Some(template.to_string()),
created_at: Utc::now(),
},
)?;
let result = OperationResult {
ok: true,
action: "create".to_string(),
target: Some(name),
message: "create completed successfully".to_string(),
warnings: Vec::new(),
};
if reporter.is_json() {
reporter.print_json(&CommandResponse::success("create", result))?;
} else {
reporter.info(&result.message);
}
Ok(ExitCode::Success)
}