Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 54 additions & 41 deletions crates/kit/src/libvirt/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,6 @@ pub struct LibvirtCreateOpts {
#[clap(long)]
pub vnc: bool,

/// Hypervisor connection URI (e.g., qemu:///system, qemu+ssh://host/system)
#[clap(short = 'c', long = "connect")]
pub connect: Option<String>,

/// VNC port (default: auto-assign)
#[clap(long)]
pub vnc_port: Option<u16>,
Expand Down Expand Up @@ -123,12 +119,8 @@ pub struct SshConfig {

impl LibvirtCreateOpts {
/// Build a virsh command with optional connection URI
fn virsh_command(&self) -> Command {
let mut cmd = Command::new("virsh");
if let Some(ref connect) = self.connect {
cmd.arg("-c").arg(connect);
}
cmd
fn virsh_command(&self, global_opts: &crate::libvirt::LibvirtOptions) -> Command {
global_opts.virsh_command()
}
Comment thread
cgwalters marked this conversation as resolved.

/// Check if the input appears to be a container image (vs volume name)
Expand Down Expand Up @@ -161,7 +153,11 @@ impl LibvirtCreateOpts {
}

/// Find existing volume by container image digest using name-based lookup
fn find_cached_volume(&self, image_digest: &str) -> Result<Option<String>> {
fn find_cached_volume(
&self,
global_opts: &crate::libvirt::LibvirtOptions,
image_digest: &str,
) -> Result<Option<String>> {
debug!("Looking for cached volume with digest: {}", image_digest);

// Create a temporary upload opts to get the expected cached volume name
Expand All @@ -176,14 +172,13 @@ impl LibvirtCreateOpts {
},
vcpus: Some(default_vcpus()),
karg: vec![],
connect: self.connect.clone(),
};

let expected_volume_name = temp_upload_opts.get_cached_volume_name(image_digest);
let expected_volume_path = format!("{}.raw", expected_volume_name);

// Check if this specific volume exists
let output = self
let output = global_opts
.virsh_command()
.args(&["vol-info", &expected_volume_path, "--pool", &self.pool])
.output()?;
Expand All @@ -198,7 +193,7 @@ impl LibvirtCreateOpts {
}

/// Automatically upload container image if no cached volume exists
fn ensure_volume_exists(&self) -> Result<String> {
fn ensure_volume_exists(&self, global_opts: &crate::libvirt::LibvirtOptions) -> Result<String> {
if !self.is_container_image() {
// If it's already a volume name, just return it
return Ok(self.volume_name_or_image.clone());
Expand All @@ -207,7 +202,7 @@ impl LibvirtCreateOpts {
// It's a container image, check for cached volume
let image_digest = images::get_image_digest(&self.volume_name_or_image)?;

if let Some(cached_volume) = self.find_cached_volume(&image_digest)? {
if let Some(cached_volume) = self.find_cached_volume(global_opts, &image_digest)? {
debug!("Using cached volume: {}", cached_volume);
return Ok(cached_volume);
}
Expand All @@ -227,11 +222,10 @@ impl LibvirtCreateOpts {
memory: self.install_memory.clone(),
vcpus: self.install_vcpus,
karg: self.karg.clone(),
connect: self.connect.clone(),
};

// Run the upload
crate::libvirt::upload::run(upload_opts.clone())?;
crate::libvirt::upload::run(global_opts, upload_opts.clone())?;

// Return the generated volume name (with digest)
Ok(upload_opts.get_cached_volume_name(&image_digest))
Expand All @@ -243,15 +237,19 @@ impl LibvirtCreateOpts {
}

/// Check if volume exists in the specified pool
fn check_volume_exists(&self, volume_name: &str) -> Result<String> {
fn check_volume_exists(
&self,
global_opts: &crate::libvirt::LibvirtOptions,
volume_name: &str,
) -> Result<String> {
let volume_path = if volume_name.ends_with(".raw") {
volume_name.to_string()
} else {
format!("{}.raw", volume_name)
};

let output = self
.virsh_command()
.virsh_command(global_opts)
.args(&["vol-info", &volume_path, "--pool", &self.pool])
.output()?;

Expand All @@ -267,7 +265,7 @@ impl LibvirtCreateOpts {

// Get the full volume path
let vol_path_output = self
.virsh_command()
.virsh_command(global_opts)
.args(&["vol-path", &volume_path, "--pool", &self.pool])
.output()?;

Expand All @@ -280,15 +278,19 @@ impl LibvirtCreateOpts {
}

/// Extract metadata from bootc volume
fn get_volume_metadata(&self, volume_name: &str) -> Result<BootcVolumeMetadata> {
fn get_volume_metadata(
&self,
global_opts: &crate::libvirt::LibvirtOptions,
volume_name: &str,
) -> Result<BootcVolumeMetadata> {
let volume_path = if volume_name.ends_with(".raw") {
volume_name.to_string()
} else {
format!("{}.raw", volume_name)
};

let output = self
.virsh_command()
.virsh_command(global_opts)
.args(&["vol-dumpxml", &volume_path, "--pool", &self.pool])
.output()?;
if !output.status.success() {
Expand All @@ -311,9 +313,13 @@ impl LibvirtCreateOpts {
}

/// Check if domain already exists
fn check_domain_exists(&self, domain_name: &str) -> bool {
fn check_domain_exists(
&self,
global_opts: &crate::libvirt::LibvirtOptions,
domain_name: &str,
) -> bool {
let output = self
.virsh_command()
.virsh_command(global_opts)
.args(&["dominfo", domain_name])
.output();

Expand All @@ -324,22 +330,27 @@ impl LibvirtCreateOpts {
}

/// Create a domain-specific copy of the volume
fn create_domain_volume(&self, source_volume_name: &str, domain_name: &str) -> Result<String> {
fn create_domain_volume(
&self,
global_opts: &crate::libvirt::LibvirtOptions,
source_volume_name: &str,
domain_name: &str,
) -> Result<String> {
let domain_volume_name = format!("{}-{}", source_volume_name, domain_name);
let domain_volume_path = format!("{}.raw", domain_volume_name);
let source_volume_path = format!("{}.raw", source_volume_name);

// Check if domain volume already exists
let check_output = self
.virsh_command()
.virsh_command(global_opts)
.args(&["vol-info", &domain_volume_path, "--pool", &self.pool])
.output()?;

if check_output.status.success() {
if self.force {
debug!("Removing existing domain volume: {}", domain_volume_name);
let _ = self
.virsh_command()
.virsh_command(global_opts)
.args(&["vol-delete", &domain_volume_path, "--pool", &self.pool])
.output();
} else {
Expand All @@ -357,7 +368,7 @@ impl LibvirtCreateOpts {

// Clone the source volume to create a domain-specific copy
let output = self
.virsh_command()
.virsh_command(global_opts)
.args(&[
"vol-clone",
&source_volume_path,
Expand All @@ -374,7 +385,7 @@ impl LibvirtCreateOpts {

// Get the path to the new domain volume
let vol_path_output = self
.virsh_command()
.virsh_command(global_opts)
.args(&["vol-path", &domain_volume_path, "--pool", &self.pool])
.output()?;

Expand All @@ -389,6 +400,7 @@ impl LibvirtCreateOpts {
/// Create the libvirt domain
fn create_domain(
&self,
global_opts: &crate::libvirt::LibvirtOptions,
_volume_path: &str,
metadata: &BootcVolumeMetadata,
volume_name: &str,
Expand All @@ -397,7 +409,8 @@ impl LibvirtCreateOpts {
let memory_mb = self.parse_memory()?;

// Create a domain-specific volume copy to avoid file locking issues
let domain_volume_path = self.create_domain_volume(volume_name, &domain_name)?;
let domain_volume_path =
self.create_domain_volume(global_opts, volume_name, &domain_name)?;
debug!("Using domain-specific volume: {}", domain_volume_path);

// Setup SSH configuration
Expand All @@ -408,22 +421,22 @@ impl LibvirtCreateOpts {
domain_name, volume_name, self.pool
);

if self.check_domain_exists(&domain_name) && !self.force {
if self.check_domain_exists(global_opts, &domain_name) && !self.force {
return Err(eyre!(
"Domain '{}' already exists. Use --force to recreate.",
domain_name
));
}

// If domain exists and force is specified, undefine it first
if self.check_domain_exists(&domain_name) && self.force {
if self.check_domain_exists(global_opts, &domain_name) && self.force {
debug!("Domain exists, removing it first (--force specified)");
let _ = self
.virsh_command()
.virsh_command(global_opts)
.args(&["destroy", &domain_name])
.output();
let _ = self
.virsh_command()
.virsh_command(global_opts)
.args(&["undefine", &domain_name])
.output();
}
Expand Down Expand Up @@ -514,7 +527,7 @@ impl LibvirtCreateOpts {

// Define the domain
let output = self
.virsh_command()
.virsh_command(global_opts)
.args(&["define", "/dev/stdin"])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
Expand All @@ -539,7 +552,7 @@ impl LibvirtCreateOpts {
if self.start {
debug!("Starting domain '{}'", domain_name);
let output = self
.virsh_command()
.virsh_command(global_opts)
.args(&["start", &domain_name])
.output()?;

Expand Down Expand Up @@ -676,27 +689,27 @@ impl LibvirtCreateOpts {
}

/// Execute the libvirt domain creation process
pub fn run(opts: LibvirtCreateOpts) -> Result<()> {
pub fn run(global_opts: &crate::libvirt::LibvirtOptions, opts: LibvirtCreateOpts) -> Result<()> {
debug!(
"Creating libvirt domain from: {}",
opts.volume_name_or_image
);

// Phase 1: Ensure volume exists (auto-upload if needed)
let volume_name = opts.ensure_volume_exists()?;
let volume_name = opts.ensure_volume_exists(global_opts)?;

// Phase 2: Validate volume exists and get path
let volume_path = opts.check_volume_exists(&volume_name)?;
let volume_path = opts.check_volume_exists(global_opts, &volume_name)?;
debug!("Found volume at: {}", volume_path);

// Phase 3: Extract volume metadata
let metadata = opts.get_volume_metadata(&volume_name)?;
let metadata = opts.get_volume_metadata(global_opts, &volume_name)?;
if let Some(ref source_image) = metadata.source_image {
debug!("Volume contains bootc image: {}", source_image);
}

// Phase 4: Create and optionally start domain
opts.create_domain(&volume_path, &metadata, &volume_name)?;
opts.create_domain(global_opts, &volume_path, &metadata, &volume_name)?;

Ok(())
}
13 changes: 6 additions & 7 deletions crates/kit/src/libvirt/inspect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,15 @@ pub struct LibvirtInspectOpts {
}

/// Execute the libvirt inspect command
pub fn run(opts: LibvirtInspectOpts) -> Result<()> {
inspect_vm_impl(opts)
}

/// Show detailed information about a VM (implementation)
pub fn inspect_vm_impl(opts: LibvirtInspectOpts) -> Result<()> {
pub fn run(global_opts: &crate::libvirt::LibvirtOptions, opts: LibvirtInspectOpts) -> Result<()> {
use crate::domain_list::DomainLister;
use color_eyre::eyre::Context;

let lister = DomainLister::new();
let connect_uri = global_opts.connect.as_ref();
let lister = match connect_uri {
Some(uri) => DomainLister::with_connection(uri.clone()),
None => DomainLister::new(),
};

// Get domain info
let vm = lister
Expand Down
13 changes: 6 additions & 7 deletions crates/kit/src/libvirt/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,16 @@ pub struct LibvirtListOpts {
}

/// Execute the libvirt list command
pub fn run(opts: LibvirtListOpts) -> Result<()> {
list_vms_impl(opts)
}

/// List all VMs (implementation)
pub fn list_vms_impl(opts: LibvirtListOpts) -> Result<()> {
pub fn run(global_opts: &crate::libvirt::LibvirtOptions, opts: LibvirtListOpts) -> Result<()> {
use crate::domain_list::DomainLister;
use color_eyre::eyre::Context;

// Use libvirt as the source of truth for domain listing
let lister = DomainLister::new();
let connect_uri = global_opts.connect.as_ref();
let lister = match connect_uri {
Some(uri) => DomainLister::with_connection(uri.clone()),
None => DomainLister::new(),
};

let domains = if opts.all {
lister
Expand Down
Loading