Skip to content

Commit c85d51d

Browse files
committed
libvirt: Clean up connection URI handling
We were incorrectly trying to load the system libvirt state, which we may not have permissions to do (e.g. default GHA on Ubuntu setup). Change all the code to consistently spawn `virsh` via a helper. Signed-off-by: Colin Walters <[email protected]>
1 parent 8db20ff commit c85d51d

File tree

3 files changed

+66
-101
lines changed

3 files changed

+66
-101
lines changed

crates/kit/src/libvirt/base_disks.rs

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub fn find_or_create_base_disk(
1818
image_digest: &str,
1919
install_options: &InstallOptions,
2020
kernel_args: &[String],
21-
connect_uri: Option<&String>,
21+
connect_uri: Option<&str>,
2222
) -> Result<Utf8PathBuf> {
2323
let metadata = DiskImageMetadata::from(install_options, image_digest, kernel_args);
2424
let cache_hash = metadata.compute_cache_hash();
@@ -82,7 +82,7 @@ fn create_base_disk(
8282
image_digest: &str,
8383
install_options: &InstallOptions,
8484
kernel_args: &[String],
85-
connect_uri: Option<&String>,
85+
connect_uri: Option<&str>,
8686
) -> Result<()> {
8787
use crate::run_ephemeral::CommonVmOpts;
8888
use crate::to_disk::{Format, ToDiskAdditionalOpts, ToDiskOpts};
@@ -166,10 +166,7 @@ fn create_base_disk(
166166
}
167167

168168
// Refresh libvirt storage pool so the new disk is visible to virsh
169-
let mut cmd = crate::hostexec::command("virsh", None)?;
170-
if let Some(uri) = connect_uri {
171-
cmd.arg("-c").arg(uri);
172-
}
169+
let mut cmd = super::run::virsh_command(connect_uri)?;
173170
cmd.args(&["pool-refresh", "default"]);
174171

175172
if let Err(e) = cmd
@@ -200,7 +197,7 @@ fn create_base_disk(
200197
pub fn clone_from_base(
201198
base_disk_path: &Utf8Path,
202199
vm_name: &str,
203-
connect_uri: Option<&String>,
200+
connect_uri: Option<&str>,
204201
) -> Result<Utf8PathBuf> {
205202
let pool_path = super::run::get_libvirt_storage_pool_path(connect_uri)?;
206203

@@ -209,20 +206,13 @@ pub fn clone_from_base(
209206
let vm_disk_path = pool_path.join(&vm_disk_name);
210207

211208
// Refresh the storage pool so libvirt knows about all files
212-
let mut refresh_cmd = crate::hostexec::command("virsh", None)?;
213-
if let Some(uri) = connect_uri {
214-
refresh_cmd.arg("-c").arg(uri);
215-
}
209+
let mut refresh_cmd = super::run::virsh_command(connect_uri)?;
216210
refresh_cmd.args(&["pool-refresh", "default"]);
217211
let _ = refresh_cmd.output(); // Ignore errors, pool might not exist yet
218212

219213
// Try to delete the volume if it exists (either as a file or in libvirt's view)
220214
// This handles both cases: file exists but not tracked, or tracked by libvirt
221-
let mut cmd = crate::hostexec::command("virsh", None)?;
222-
if let Some(uri) = connect_uri {
223-
cmd.arg("-c").arg(uri);
224-
}
225-
215+
let mut cmd = super::run::virsh_command(connect_uri)?;
226216
cmd.args(&["vol-delete", "--pool", "default", &vm_disk_name]);
227217

228218
let output = cmd
@@ -270,12 +260,7 @@ pub fn clone_from_base(
270260
color_eyre::eyre::eyre!("Base disk path has no filename: {:?}", base_disk_path)
271261
})?;
272262

273-
let mut cmd = crate::hostexec::command("virsh", None)?;
274-
275-
if let Some(uri) = connect_uri {
276-
cmd.arg("-c").arg(uri);
277-
}
278-
263+
let mut cmd = super::run::virsh_command(connect_uri)?;
279264
cmd.args(&[
280265
"vol-create-as",
281266
"default",
@@ -309,7 +294,7 @@ pub fn clone_from_base(
309294
}
310295

311296
/// List all base disks in the storage pool with reference counts
312-
pub fn list_base_disks(connect_uri: Option<&String>) -> Result<Vec<BaseDiskInfo>> {
297+
pub fn list_base_disks(connect_uri: Option<&str>) -> Result<Vec<BaseDiskInfo>> {
313298
use super::run::list_storage_pool_volumes;
314299

315300
let pool_path = super::run::get_libvirt_storage_pool_path(connect_uri)?;
@@ -372,7 +357,7 @@ pub struct BaseDiskInfo {
372357
}
373358

374359
/// Prune unreferenced base disks
375-
pub fn prune_base_disks(connect_uri: Option<&String>, dry_run: bool) -> Result<Vec<Utf8PathBuf>> {
360+
pub fn prune_base_disks(connect_uri: Option<&str>, dry_run: bool) -> Result<Vec<Utf8PathBuf>> {
376361
use super::run::list_storage_pool_volumes;
377362

378363
let base_disks = list_base_disks(connect_uri)?;
@@ -407,10 +392,7 @@ pub fn prune_base_disks(connect_uri: Option<&String>, dry_run: bool) -> Result<V
407392
color_eyre::eyre::eyre!("Base disk path has no filename: {:?}", base_disk.path)
408393
})?;
409394

410-
let mut cmd = crate::hostexec::command("virsh", None)?;
411-
if let Some(uri) = connect_uri {
412-
cmd.arg("-c").arg(uri);
413-
}
395+
let mut cmd = super::run::virsh_command(connect_uri)?;
414396
cmd.args(&["vol-delete", "--pool", "default", base_disk_name]);
415397

416398
let output = cmd.output().with_context(|| {

crates/kit/src/libvirt/base_disks_cli.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ pub struct PruneOpts {
4545

4646
/// Execute the base-disks command
4747
pub fn run(global_opts: &crate::libvirt::LibvirtOptions, opts: LibvirtBaseDisksOpts) -> Result<()> {
48-
let connect_uri = global_opts.connect.as_ref();
48+
let connect_uri = global_opts.connect.as_deref();
4949

5050
match opts.command {
5151
BaseDisksSubcommand::List(list_opts) => run_list(connect_uri, list_opts),
@@ -54,7 +54,7 @@ pub fn run(global_opts: &crate::libvirt::LibvirtOptions, opts: LibvirtBaseDisksO
5454
}
5555

5656
/// Execute the list subcommand
57-
fn run_list(connect_uri: Option<&String>, opts: ListOpts) -> Result<()> {
57+
fn run_list(connect_uri: Option<&str>, opts: ListOpts) -> Result<()> {
5858
let base_disks = list_base_disks(connect_uri)?;
5959

6060
match opts.format {
@@ -115,7 +115,7 @@ fn run_list(connect_uri: Option<&String>, opts: ListOpts) -> Result<()> {
115115
}
116116

117117
/// Execute the prune subcommand
118-
fn run_prune(connect_uri: Option<&String>, opts: PruneOpts) -> Result<()> {
118+
fn run_prune(connect_uri: Option<&str>, opts: PruneOpts) -> Result<()> {
119119
if opts.dry_run {
120120
println!("Dry run: showing base disks that would be removed");
121121
}

crates/kit/src/libvirt/run.rs

Lines changed: 53 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@ use crate::libvirt::domain::VirtiofsFilesystem;
1717
use crate::utils::parse_memory_to_mb;
1818
use crate::xml_utils;
1919

20+
/// Create a virsh command with optional connection URI
21+
pub(super) fn virsh_command(connect_uri: Option<&str>) -> Result<std::process::Command> {
22+
let mut cmd = crate::hostexec::command("virsh", None)?;
23+
if let Some(uri) = connect_uri {
24+
cmd.arg("-c").arg(uri);
25+
}
26+
Ok(cmd)
27+
}
28+
2029
/// Firmware type for virtual machines
2130
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
2231
#[clap(rename_all = "kebab-case")]
@@ -117,8 +126,8 @@ pub fn run(global_opts: &crate::libvirt::LibvirtOptions, opts: LibvirtRunOpts) -
117126
// Validate labels don't contain commas
118127
opts.validate_labels()?;
119128

120-
let connect_uri = global_opts.connect.as_ref();
121-
let lister = match connect_uri {
129+
let connect_uri = global_opts.connect.as_deref();
130+
let lister = match global_opts.connect.as_ref() {
122131
Some(uri) => DomainLister::with_connection(uri.clone()),
123132
None => DomainLister::new(),
124133
};
@@ -231,24 +240,38 @@ fn get_default_pool_path(connect_uri: &str) -> Utf8PathBuf {
231240
}
232241

233242
/// Ensure the default libvirt storage pool exists, creating it if necessary
234-
fn ensure_default_pool(connect_uri: &str) -> Result<()> {
243+
fn ensure_default_pool(connect_uri: Option<&str>) -> Result<()> {
235244
// Check if default pool already exists
236-
let mut cmd = crate::hostexec::command("virsh", None)?;
237-
cmd.args(&["-c", connect_uri, "pool-info", "default"]);
245+
let mut cmd = virsh_command(connect_uri)?;
246+
cmd.args(&["pool-info", "default"]);
238247
let output = cmd
239248
.output()
240249
.with_context(|| "Failed to check for default pool")?;
241250

242251
if output.status.success() {
243252
// Pool exists, make sure it's active
244-
let mut cmd = crate::hostexec::command("virsh", None)?;
245-
cmd.args(&["-c", connect_uri, "pool-start", "default"]);
253+
let mut cmd = virsh_command(connect_uri)?;
254+
cmd.args(&["pool-start", "default"]);
246255
let _ = cmd.output(); // Ignore errors if already started
247256
return Ok(());
248257
}
249258

250259
// Pool doesn't exist, need to create it
251-
let pool_path = get_default_pool_path(connect_uri);
260+
// Determine the appropriate pool path based on the connection URI
261+
let pool_path = if let Some(uri) = connect_uri {
262+
get_default_pool_path(uri)
263+
} else {
264+
// If no URI specified, virsh will use its default connection
265+
// We need to query what that is to determine the appropriate pool path
266+
let mut cmd = virsh_command(None)?;
267+
cmd.args(&["uri"]);
268+
let output = cmd
269+
.output()
270+
.with_context(|| "Failed to query default libvirt URI")?;
271+
let uri_str = String::from_utf8(output.stdout)
272+
.with_context(|| "Invalid UTF-8 in virsh uri output")?;
273+
get_default_pool_path(uri_str.trim())
274+
};
252275
info!("Creating default storage pool at {:?}", pool_path);
253276

254277
// Create the directory if it doesn't exist
@@ -271,8 +294,8 @@ fn ensure_default_pool(connect_uri: &str) -> Result<()> {
271294
std::fs::write(xml_path, &pool_xml).with_context(|| "Failed to write pool XML")?;
272295

273296
// Define the pool
274-
let mut cmd = crate::hostexec::command("virsh", None)?;
275-
cmd.args(&["-c", connect_uri, "pool-define", xml_path]);
297+
let mut cmd = virsh_command(connect_uri)?;
298+
cmd.args(&["pool-define", xml_path]);
276299
let output = cmd.output().with_context(|| "Failed to define pool")?;
277300

278301
if !output.status.success() {
@@ -285,13 +308,13 @@ fn ensure_default_pool(connect_uri: &str) -> Result<()> {
285308
}
286309

287310
// Build the pool (creates directory structure)
288-
let mut cmd = crate::hostexec::command("virsh", None)?;
289-
cmd.args(&["-c", connect_uri, "pool-build", "default"]);
311+
let mut cmd = virsh_command(connect_uri)?;
312+
cmd.args(&["pool-build", "default"]);
290313
let _ = cmd.output(); // Directory might already exist
291314

292315
// Start the pool
293-
let mut cmd = crate::hostexec::command("virsh", None)?;
294-
cmd.args(&["-c", connect_uri, "pool-start", "default"]);
316+
let mut cmd = virsh_command(connect_uri)?;
317+
cmd.args(&["pool-start", "default"]);
295318
let output = cmd.output().with_context(|| "Failed to start pool")?;
296319

297320
if !output.status.success() {
@@ -304,8 +327,8 @@ fn ensure_default_pool(connect_uri: &str) -> Result<()> {
304327
}
305328

306329
// Autostart the pool
307-
let mut cmd = crate::hostexec::command("virsh", None)?;
308-
cmd.args(&["-c", connect_uri, "pool-autostart", "default"]);
330+
let mut cmd = virsh_command(connect_uri)?;
331+
cmd.args(&["pool-autostart", "default"]);
309332
let _ = cmd.output(); // Not critical if this fails
310333

311334
// Clean up temporary XML file
@@ -316,67 +339,27 @@ fn ensure_default_pool(connect_uri: &str) -> Result<()> {
316339
}
317340

318341
/// Get the path of the default libvirt storage pool
319-
pub fn get_libvirt_storage_pool_path(connect_uri: Option<&String>) -> Result<Utf8PathBuf> {
320-
// If a specific connection URI is provided, use it
321-
if let Some(uri) = connect_uri {
322-
// Ensure pool exists before querying
323-
ensure_default_pool(uri)?;
342+
pub fn get_libvirt_storage_pool_path(connect_uri: Option<&str>) -> Result<Utf8PathBuf> {
343+
// Ensure pool exists before querying
344+
ensure_default_pool(connect_uri)?;
324345

325-
let mut cmd = crate::hostexec::command("virsh", None)?;
326-
cmd.args(&["-c", uri, "pool-dumpxml", "default"]);
327-
let output = cmd
328-
.output()
329-
.with_context(|| "Failed to query libvirt storage pool")?;
330-
331-
if output.status.success() {
332-
let xml = String::from_utf8(output.stdout)
333-
.with_context(|| "Invalid UTF-8 in virsh output")?;
334-
let dom = xml_utils::parse_xml_dom(&xml)
335-
.with_context(|| "Failed to parse storage pool XML")?;
336-
337-
if let Some(path_node) = dom.find("path") {
338-
let path_str = path_node.text_content().trim();
339-
if !path_str.is_empty() {
340-
return Ok(Utf8PathBuf::from(path_str));
341-
}
342-
}
343-
}
344-
}
345-
346-
// Try user session first (qemu:///session)
347-
let session_uri = "qemu:///session";
348-
ensure_default_pool(session_uri)?;
349-
350-
let mut cmd = crate::hostexec::command("virsh", None)?;
351-
cmd.args(&["-c", session_uri, "pool-dumpxml", "default"]);
352-
let output = cmd.output();
353-
354-
let (output, uri_used) = match output {
355-
Ok(o) if o.status.success() => (o, session_uri),
356-
_ => {
357-
// Try system session (qemu:///system)
358-
let system_uri = "qemu:///system";
359-
ensure_default_pool(system_uri)?;
360-
361-
let mut cmd = crate::hostexec::command("virsh", None)?;
362-
cmd.args(&["-c", system_uri, "pool-dumpxml", "default"]);
363-
let out = cmd
364-
.output()
365-
.with_context(|| "Failed to query libvirt storage pool")?;
366-
(out, system_uri)
367-
}
368-
};
346+
let mut cmd = virsh_command(connect_uri)?;
347+
cmd.args(&["pool-dumpxml", "default"]);
348+
let output = cmd
349+
.output()
350+
.with_context(|| "Failed to query libvirt storage pool")?;
369351

370352
if !output.status.success() {
353+
let stderr = String::from_utf8_lossy(&output.stderr);
354+
let uri_desc = connect_uri.unwrap_or("default connection");
371355
return Err(color_eyre::eyre::eyre!(
372-
"Failed to get default storage pool info for {}",
373-
uri_used
356+
"Failed to get default storage pool info for {}: {}",
357+
uri_desc,
358+
stderr
374359
));
375360
}
376361

377362
let xml = String::from_utf8(output.stdout).with_context(|| "Invalid UTF-8 in virsh output")?;
378-
379-
// Parse XML using DOM parser and extract path element
380363
let dom = xml_utils::parse_xml_dom(&xml).with_context(|| "Failed to parse storage pool XML")?;
381364

382365
if let Some(path_node) = dom.find("path") {
@@ -432,7 +415,7 @@ fn generate_unique_vm_name(image: &str, existing_domains: &[String]) -> String {
432415
}
433416

434417
/// List all volumes in the default storage pool
435-
pub fn list_storage_pool_volumes(connect_uri: Option<&String>) -> Result<Vec<Utf8PathBuf>> {
418+
pub fn list_storage_pool_volumes(connect_uri: Option<&str>) -> Result<Vec<Utf8PathBuf>> {
436419
// Get the storage pool path from XML
437420
let pool_path = get_libvirt_storage_pool_path(connect_uri)?;
438421

0 commit comments

Comments
 (0)