//! Create a new VM. //! //! This module runs the `virt-install` tool to create a new VM, using //! an existing image file. It attaches the cloud-init ISO //! configuration image to the VM. use crate::cloudinit::{CloudInitConfig, CloudInitError}; use crate::image::VirtualMachineImage; use std::path::{Path, PathBuf}; use std::process::Command; use std::result::Result; /// Errors from this module #[derive(Debug, thiserror::Error)] pub enum VirtInstallError { /// Failed to create VM. #[error("virt-install failed: {0}")] VirtInstallFailed(String), /// Failed to run virt-install. #[error("couldn't run virt-install")] Run(#[source] std::io::Error), /// No networks defined. #[error("no networks defined for {0}")] NoNetworks(String), /// Error parsing a string as UTF8. #[error(transparent)] StringError(#[from] std::string::FromUtf8Error), /// Error from cloud-init configuration. #[error(transparent)] CloudInitError(#[from] CloudInitError), } /// Arguments to virt-install. /// /// These are the arguments we can adjust, for running virt-install. #[derive(Debug)] pub struct VirtInstallArgs { name: String, memory: u64, vcpus: u64, image: VirtualMachineImage, init: CloudInitConfig, networks: Vec, } impl VirtInstallArgs { /// Create new set of arguments for virt-install. pub fn new(name: &str, image: &VirtualMachineImage, init: &CloudInitConfig) -> Self { Self { name: name.to_string(), memory: 1024, vcpus: 1, image: image.clone(), init: init.clone(), networks: vec![], } } /// Name for new VM. pub fn name(&self) -> &str { &self.name } /// Memory for new VM, in MiB. pub fn memory(&self) -> u64 { self.memory } /// Change memory to give to new VM, in MiB. pub fn set_memory(&mut self, memory: u64) { self.memory = memory } /// Virtual CPUs for new VM. pub fn vcpus(&self) -> u64 { self.vcpus } /// Change virtual CPUs for new VM. pub fn set_vcpus(&mut self, vcpus: u64) { self.vcpus = vcpus } /// Image for new VM. pub fn image(&self) -> &VirtualMachineImage { &self.image } /// cloud-init configuration for new VM. pub fn init(&self) -> &CloudInitConfig { &self.init } /// Add another network to add to the VM. pub fn add_network(&mut self, network: &str) { self.networks.push(network.to_string()); } /// Return list of networks to add to the VM. pub fn networks(&self) -> Vec { self.networks.clone() } } /// Create new VM with virt-install. pub fn virt_install(args: &VirtInstallArgs, iso: &Path) -> Result { let networks: Vec = args .networks .iter() .map(|s| format!("--network={}", s)) .collect(); args.init().create_iso(iso)?; let r = Command::new("virt-install") .arg("--name") .arg(args.name()) .arg("--memory") .arg(format!("{}", args.memory())) .arg("--vcpus") .arg(format!("{}", args.vcpus())) .arg(format!( "--disk=path={},cache=none", args.image().filename().display() )) .arg(format!("--disk=path={},readonly=on", iso.display())) .arg("--connect=qemu::///system") .arg("--connect") .arg("qemu:///system") .arg("--cpu=host-passthrough") .arg("--os-variant=debian9") .arg("--import") .arg("--graphics=spice") .arg("--noautoconsole") .arg("--quiet") .args(&networks) .output() .map_err(VirtInstallError::Run)?; if !r.status.success() { let stderr = String::from_utf8(r.stderr)?; return Err(VirtInstallError::VirtInstallFailed(stderr)); } Ok(iso.to_path_buf()) }