Browse Source

feat: download and bcreate image

rust
Giovanni Torres 12 months ago
parent
commit
eddb9b4247
  1. 8
      src/cloudinit.rs
  2. 28
      src/image.rs
  3. 6
      src/lib.rs
  4. 38
      src/main.rs
  5. 1
      src/network.rs
  6. 141
      src/vm.rs

8
src/cloudinit.rs

@ -121,9 +121,7 @@ runcmd:
// Fixed approach - avoid directly chaining methods that create temporary values
let has_genisoimage = {
let result = Command::new("genisoimage")
.arg("--version")
.output();
let result = Command::new("genisoimage").arg("--version").output();
debug!("Checking for genisoimage: {:?}", result.is_ok());
result.is_ok()
};
@ -146,9 +144,7 @@ runcmd:
} else {
// Check if mkisofs is available
let has_mkisofs = {
let result = Command::new("mkisofs")
.arg("--version")
.output();
let result = Command::new("mkisofs").arg("--version").output();
debug!("Checking for mkisofs: {:?}", result.is_ok());
result.is_ok()
};

28
src/image.rs

@ -1,5 +1,5 @@
use anyhow::{Context, Result};
use crate::vm::DistroInfo;
use anyhow::{Context, Result};
use futures_util::StreamExt;
use indicatif::{ProgressBar, ProgressStyle};
use reqwest;
@ -236,14 +236,19 @@ impl ImageManager {
let mut cmd = Command::new("qemu-img");
cmd.args(&[
"create",
"-f", "qcow2",
"-F", "qcow2",
"-b", source_path.to_str().unwrap(),
"-f",
"qcow2",
"-F",
"qcow2",
"-b",
source_path.to_str().unwrap(),
target_path.to_str().unwrap(),
]);
debug!("Executing command: {:?}", cmd);
let status = cmd.status().context("Failed to execute qemu-img create command")?;
let status = cmd
.status()
.context("Failed to execute qemu-img create command")?;
if !status.success() {
return Err(anyhow::anyhow!("Failed to create disk image copy"));
@ -258,7 +263,9 @@ impl ImageManager {
]);
debug!("Executing command: {:?}", resize_cmd);
let resize_status = resize_cmd.status().context("Failed to execute qemu-img resize command")?;
let resize_status = resize_cmd
.status()
.context("Failed to execute qemu-img resize command")?;
if !resize_status.success() {
return Err(anyhow::anyhow!("Failed to resize disk image"));
@ -280,13 +287,12 @@ impl ImageManager {
// Use qemu-img check to verify the image
let mut cmd = Command::new("qemu-img");
cmd.args(&[
"check",
image_path.to_str().unwrap(),
]);
cmd.args(&["check", image_path.to_str().unwrap()]);
debug!("Executing command: {:?}", cmd);
let output = cmd.output().context("Failed to execute qemu-img check command")?;
let output = cmd
.output()
.context("Failed to execute qemu-img check command")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);

6
src/lib.rs

@ -3,14 +3,14 @@ pub mod cloudinit;
pub mod config;
pub mod domain;
pub mod image;
pub mod vm;
pub mod network;
pub mod vm;
pub use cli::Cli;
pub use cli::Commands;
pub use config::Config;
pub use domain::DomainInfo;
pub use domain::DomainState;
pub use vm::VirtualMachine;
pub use vm::DistroInfo;
pub use image::ImageManager;
pub use vm::DistroInfo;
pub use vm::VirtualMachine;

38
src/main.rs

@ -1,7 +1,5 @@
use clap::Parser;
use kvm_install_vm::{
Cli, Commands, Config, VirtualMachine
};
use kvm_install_vm::{Cli, Commands, Config, VirtualMachine};
use std::io::Write;
use std::process;
use tracing::{debug, error, info};
@ -54,8 +52,10 @@ fn main() {
print_progress(&format!("Starting kvm-install-vm for VM: {}", name));
print_progress(&format!("Distribution: {}", distro));
debug!("Configuration: vCPUs={}, Memory={}MB, Disk={}GB, Graphics={}",
vcpus, memory_mb, disk_size_gb, graphics);
debug!(
"Configuration: vCPUs={}, Memory={}MB, Disk={}GB, Graphics={}",
vcpus, memory_mb, disk_size_gb, graphics
);
if *dry_run {
print_progress("Dry run mode - not creating VM");
@ -68,7 +68,7 @@ fn main() {
Ok(config) => {
println!("\x1b[32mOK\x1b[0m");
config
},
}
Err(e) => {
println!("\x1b[31mFAILED\x1b[0m");
eprintln!(" Error: {}", e);
@ -80,7 +80,13 @@ fn main() {
// Initialize VM instance
print_status_start("Creating VM instance");
let vm_name = name.clone();
let mut vm = VirtualMachine::new(name.clone(), *vcpus, *memory_mb, *disk_size_gb, String::new());
let mut vm = VirtualMachine::new(
name.clone(),
*vcpus,
*memory_mb,
*disk_size_gb,
String::new(),
);
println!("\x1b[32mOK\x1b[0m");
// Connect to libvirt
@ -121,7 +127,7 @@ fn main() {
process::exit(1);
}
}
},
}
Err(e) => {
println!("\x1b[31mFAILED\x1b[0m");
eprintln!(" Error: {}", e);
@ -136,13 +142,19 @@ fn main() {
info!("Starting VM destruction process for: {}", name);
print_progress(&format!("Destroying VM: {}", name));
debug!("Destroying parameters - Name: {}, Remove Disk: {}", name, remove_disk);
debug!(
"Destroying parameters - Name: {}, Remove Disk: {}",
name, remove_disk
);
print_status_start("Destroying virtual machine");
match VirtualMachine::destroy(name, None, *remove_disk) {
Ok(()) => {
println!("\x1b[32mOK\x1b[0m");
print_progress(&format!("VM '{}' destroy operation completed successfully", name));
print_progress(&format!(
"VM '{}' destroy operation completed successfully",
name
));
info!("VM '{}' destroyed successfully", name);
}
Err(e) => {
@ -165,8 +177,10 @@ fn main() {
// Determine which types of domains to list
let show_all = *all || (!*running && !*inactive);
debug!("List parameters - All: {}, Running: {}, Inactive: {}, Show all: {}",
all, running, inactive, show_all);
debug!(
"List parameters - All: {}, Running: {}, Inactive: {}, Show all: {}",
all, running, inactive, show_all
);
match VirtualMachine::print_domain_list(None, show_all, *running, *inactive) {
Ok(()) => {

1
src/network.rs

@ -0,0 +1 @@

141
src/vm.rs

@ -13,9 +13,9 @@ use virt::connect::Connect;
use virt::domain::Domain;
use virt::sys;
use crate::cloudinit::CloudInitManager;
use crate::config::Config;
use crate::domain::{DomainInfo, DomainState, extract_disk_paths_from_xml};
use crate::cloudinit::CloudInitManager;
pub struct VirtualMachine {
pub name: String,
@ -282,6 +282,22 @@ impl VirtualMachine {
pub async fn prepare_image(&mut self, distro: &str, config: &Config) -> Result<()> {
info!("Preparing image for VM: {}", self.name);
// First check if domain exists in libvirt
if let Ok(true) = self.domain_exists() {
return Err(anyhow::anyhow!(
"Domain '{}' already exists in libvirt",
self.name
));
}
// Check if VM files exist on disk
if self.vm_files_exist() {
return Err(anyhow::anyhow!(
"VM '{}' files already exist on disk. Use destroy command with --remove-disk flag first",
self.name
));
}
// Get distribution info
let distro_info = config.get_distro(distro)?;
debug!("Using distro: {}", distro);
@ -302,7 +318,8 @@ impl VirtualMachine {
}
// Create disk path for the VM
self.disk_path = vm_dir.join(format!("{}.qcow2", self.name))
self.disk_path = vm_dir
.join(format!("{}.qcow2", self.name))
.to_string_lossy()
.to_string();
debug!("Disk path: {}", self.disk_path);
@ -312,9 +329,12 @@ impl VirtualMachine {
let mut cmd = Command::new("qemu-img");
cmd.args([
"create",
"-f", "qcow2",
"-F", "qcow2",
"-b", cloud_image.to_str().unwrap(),
"-f",
"qcow2",
"-F",
"qcow2",
"-b",
cloud_image.to_str().unwrap(),
&self.disk_path,
]);
@ -358,12 +378,8 @@ impl VirtualMachine {
)?;
// Create cloud-init ISO
let iso_path = CloudInitManager::create_cloud_init_iso(
&vm_dir,
&self.name,
&user_data,
&meta_data,
)?;
let iso_path =
CloudInitManager::create_cloud_init_iso(&vm_dir, &self.name, &user_data, &meta_data)?;
debug!("Cloud-init ISO created at: {}", iso_path.display());
info!("Image preparation completed successfully");
@ -390,6 +406,10 @@ impl VirtualMachine {
}
};
if self.domain_exists()? {
return Err(anyhow::anyhow!("Domain '{}' already exists", self.name));
}
// Check if disk image exists and create if needed
if !Path::new(&self.disk_path).exists() {
debug!("Disk image doesn't exist, creating it");
@ -458,9 +478,7 @@ impl VirtualMachine {
debug!("Executing command: {:?}", cmd);
// Execute the command
let output = cmd
.output()
.context("Failed to execute qemu-img command")?;
let output = cmd.output().context("Failed to execute qemu-img command")?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
@ -542,11 +560,17 @@ impl VirtualMachine {
Ok(_) => {
info!("Domain stopped successfully");
println!("Domain stopped successfully");
},
}
Err(e) => {
warn!("Warning: Failed to stop domain cleanly: {}. Continuing with undefine...", e);
println!("Warning: Failed to stop domain cleanly: {}. Continuing with undefine...", e);
},
warn!(
"Warning: Failed to stop domain cleanly: {}. Continuing with undefine...",
e
);
println!(
"Warning: Failed to stop domain cleanly: {}. Continuing with undefine...",
e
);
}
}
} else {
info!("Domain '{}' is already stopped", name);
@ -576,11 +600,11 @@ impl VirtualMachine {
Ok(_) => {
info!("Successfully removed disk: {}", path);
println!("Successfully removed disk: {}", path);
},
}
Err(e) => {
warn!("Warning: Failed to remove disk {}: {}", path, e);
println!("Warning: Failed to remove disk {}: {}", path, e);
},
}
}
}
} else if !disk_paths.is_empty() {
@ -620,24 +644,18 @@ impl VirtualMachine {
// Get state
let state = match domain.get_state() {
Ok((state, _)) => {
match state {
Ok((state, _)) => match state {
virt::sys::VIR_DOMAIN_RUNNING => DomainState::Running,
virt::sys::VIR_DOMAIN_PAUSED => DomainState::Paused,
virt::sys::VIR_DOMAIN_SHUTDOWN => DomainState::Shutdown,
virt::sys::VIR_DOMAIN_SHUTOFF => DomainState::Shutoff,
virt::sys::VIR_DOMAIN_CRASHED => DomainState::Crashed,
_ => DomainState::Unknown,
}
}
},
Err(_) => DomainState::Unknown,
};
domain_infos.push(DomainInfo {
id,
name,
state,
});
domain_infos.push(DomainInfo { id, name, state });
}
// Process inactive domains
@ -654,7 +672,12 @@ impl VirtualMachine {
Ok(domain_infos)
}
pub fn print_domain_list(uri: Option<&str>, show_all: bool, show_running: bool, show_inactive: bool) -> Result<()> {
pub fn print_domain_list(
uri: Option<&str>,
show_all: bool,
show_running: bool,
show_inactive: bool,
) -> Result<()> {
// Get domain list
let domains = Self::list_domains(uri)?;
@ -665,7 +688,10 @@ impl VirtualMachine {
// Print header
println!("{:<5} {:<20} {:<10}", "ID", "Name", "State");
println!("{:<5} {:<20} {:<10}", "-----", "--------------------", "----------");
println!(
"{:<5} {:<20} {:<10}",
"-----", "--------------------", "----------"
);
// Print domain information
for domain in domains {
@ -677,13 +703,60 @@ impl VirtualMachine {
let is_running = domain.state == DomainState::Running;
let is_inactive = domain.state == DomainState::Shutoff;
if show_all ||
(show_running && is_running) ||
(show_inactive && is_inactive) {
if show_all || (show_running && is_running) || (show_inactive && is_inactive) {
println!("{:<5} {:<20} {:<10}", id, domain.name, domain.state);
}
}
Ok(())
}
pub fn domain_exists(&self) -> Result<bool> {
// Ensure connection is established
let conn = match &self.connection {
Some(c) => c,
None => {
return Err(anyhow::anyhow!(
"Connection not established before domain_exists() call"
));
}
};
// Try to lookup the domain by name
match virt::domain::Domain::lookup_by_name(conn, &self.name) {
Ok(_) => {
// Domain exists
info!("Domain '{}' already exists", self.name);
Ok(true)
}
Err(e) => {
if e.code() == virt::error::ErrorNumber::DomExist {
debug!("Domain '{}' does not exist", self.name);
Ok(false)
} else {
// Some other error occurred
error!("Error checking if domain exists: {}", e);
Err(anyhow::anyhow!("Error checking if domain exists: {}", e))
}
}
}
}
pub fn vm_files_exist(&self) -> bool {
// Check if VM directory exists
if let Some(parent) = Path::new(&self.disk_path).parent() {
if parent.exists() {
debug!("VM directory already exists: {}", parent.display());
return true;
}
}
// Check if disk file exists
if Path::new(&self.disk_path).exists() {
debug!("VM disk already exists: {}", self.disk_path);
return true;
}
false
}
}
Loading…
Cancel
Save