//! The `new` sub-command. use crate::cloudinit::{CloudInitConfig, CloudInitError}; use crate::image::{ImageError, VirtualMachineImage}; use crate::install::{virt_install, VirtInstallArgs, VirtInstallError}; use crate::spec::Specification; use bytesize::GIB; use log::info; use std::net::TcpStream; const SSH_PORT: i32 = 22; /// Errors returned by this module. #[derive(Debug, thiserror::Error)] pub enum NewError { /// Problem with cloud-init configuration. #[error(transparent)] CloudInitError(#[from] CloudInitError), /// Problem creating VM image. #[error(transparent)] ImageError(#[from] ImageError), /// Problem with libvirt. #[error(transparent)] VirtInstallError(#[from] VirtInstallError), } /// The `new` sub-command. /// /// Create all the new virtual machines specified by the caller. Wait /// until each VM's SSH port listens for connections. pub fn new(specs: &[Specification]) -> Result<(), NewError> { for spec in specs { info!("creating new VM {}", spec.name); info!("creating cloud-init config"); let init = CloudInitConfig::from(&spec)?; info!( "creating VM image {} from {}", spec.image.display(), spec.base.display() ); let image = VirtualMachineImage::new_from_base(&spec.base, &spec.image)?; info!("resizing image to {} GiB", spec.image_size_gib); image.resize(spec.image_size_gib * GIB)?; info!("creating VM"); let mut args = VirtInstallArgs::new(&spec.name, &image, &init); args.set_memory(spec.memory_mib); args.set_vcpus(spec.cpus); virt_install(&args)?; info!("waiting for {} to open its SSH port", spec.name); wait_for_port(&spec.name, SSH_PORT)?; } Ok(()) } fn wait_for_port(name: &str, port: i32) -> Result<(), NewError> { let addr = format!("{}:{}", name, port); loop { if TcpStream::connect(&addr).is_ok() { return Ok(()); } } }