|
1 | 1 | use core::str; |
2 | 2 | use std::{ |
3 | | - fs::{create_dir_all, rename}, |
| 3 | + collections::BTreeSet, |
| 4 | + fs::{create_dir_all, read_dir, rename}, |
4 | 5 | net::Ipv4Addr, |
5 | 6 | path::Path, |
| 7 | + time::Duration, |
6 | 8 | }; |
7 | 9 |
|
8 | | -use cmd_lib::{run_fun, spawn_with_output}; |
9 | | -use jane_eyre::eyre; |
10 | | -use settings::TOML; |
| 10 | +use cmd_lib::{run_cmd, run_fun, spawn_with_output}; |
| 11 | +use jane_eyre::eyre::{self, OptionExt, bail}; |
| 12 | +use settings::{TOML, profile::Profile}; |
11 | 13 | use shell::log_output_as_trace; |
12 | | -use tracing::debug; |
| 14 | +use tracing::{debug, info}; |
| 15 | + |
| 16 | +use crate::libvirt::{delete_template_or_rebuild_image_file, template_or_rebuild_images_path}; |
13 | 17 |
|
14 | 18 | pub fn list_template_guests() -> eyre::Result<Vec<String>> { |
15 | 19 | // Output is not filtered by prefix, so we must filter it ourselves. |
@@ -71,6 +75,66 @@ pub fn get_ipv4_address(guest_name: &str) -> Option<Ipv4Addr> { |
71 | 75 | .or_else(|| virsh_domifaddr(guest_name, "agent")) |
72 | 76 | } |
73 | 77 |
|
| 78 | +pub fn start_guest(guest_name: &str) -> eyre::Result<()> { |
| 79 | + info!(?guest_name, "Starting guest"); |
| 80 | + run_cmd!(virsh start -- $guest_name)?; |
| 81 | + |
| 82 | + Ok(()) |
| 83 | +} |
| 84 | + |
| 85 | +pub fn wait_for_guest(guest_name: &str, timeout: Duration) -> eyre::Result<()> { |
| 86 | + let timeout = timeout.as_secs(); |
| 87 | + info!("Waiting for guest to shut down (max {timeout} seconds)"); |
| 88 | + if !run_cmd!(time virsh event --timeout $timeout -- $guest_name lifecycle).is_ok() { |
| 89 | + bail!("`virsh event` failed or timed out!"); |
| 90 | + } |
| 91 | + for _ in 0..100 { |
| 92 | + if run_fun!(virsh domstate -- $guest_name)?.trim_ascii() == "shut off" { |
| 93 | + return Ok(()); |
| 94 | + } |
| 95 | + } |
| 96 | + |
| 97 | + bail!("Guest did not shut down as expected") |
| 98 | +} |
| 99 | + |
| 100 | +pub fn rename_guest(old_guest_name: &str, new_guest_name: &str) -> eyre::Result<()> { |
| 101 | + run_cmd!(virsh domrename -- $old_guest_name $new_guest_name)?; |
| 102 | + Ok(()) |
| 103 | +} |
| 104 | + |
| 105 | +pub fn delete_guest(guest_name: &str) -> eyre::Result<()> { |
| 106 | + if run_cmd!(virsh domstate -- $guest_name).is_ok() { |
| 107 | + // FIXME make this idempotent in a less noisy way? |
| 108 | + let _ = run_cmd!(virsh destroy -- $guest_name); |
| 109 | + run_cmd!(virsh undefine --nvram -- $guest_name)?; |
| 110 | + } |
| 111 | + |
| 112 | + Ok(()) |
| 113 | +} |
| 114 | + |
| 115 | +pub fn prune_base_image_files( |
| 116 | + profile: &Profile, |
| 117 | + keep_snapshots: BTreeSet<String>, |
| 118 | +) -> eyre::Result<()> { |
| 119 | + let base_images_path = template_or_rebuild_images_path(profile); |
| 120 | + info!(?base_images_path, "Pruning base image files"); |
| 121 | + create_dir_all(&base_images_path)?; |
| 122 | + |
| 123 | + for entry in read_dir(&base_images_path)? { |
| 124 | + let filename = entry?.file_name(); |
| 125 | + let filename = filename.to_str().ok_or_eyre("Unsupported path")?; |
| 126 | + if let Some((_base, snapshot_name)) = filename.split_once("@") { |
| 127 | + if !keep_snapshots.contains(snapshot_name) { |
| 128 | + delete_template_or_rebuild_image_file(profile, filename); |
| 129 | + } |
| 130 | + } else { |
| 131 | + delete_template_or_rebuild_image_file(profile, filename); |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + Ok(()) |
| 136 | +} |
| 137 | + |
74 | 138 | fn virsh_domifaddr(guest_name: &str, source: &str) -> Option<Ipv4Addr> { |
75 | 139 | let output = run_fun!(virsh domifaddr --source $source $guest_name 2> /dev/null); |
76 | 140 | match output { |
|
0 commit comments