Automatically generate cloud disk image when updating domains configuration
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-06-07 11:15:55 +02:00
parent f1339f0711
commit 1fe7c60f36
6 changed files with 55 additions and 8 deletions

View File

@ -182,6 +182,13 @@ impl Handler<DeleteDomainReq> for LibVirtActor {
false => sys::VIR_DOMAIN_UNDEFINE_NVRAM, false => sys::VIR_DOMAIN_UNDEFINE_NVRAM,
})?; })?;
// Delete associated cloud init disk
let cloud_init_disk = AppConfig::get().cloud_init_disk_path_for_vm(&domain_name);
if cloud_init_disk.exists() {
std::fs::remove_file(cloud_init_disk)?;
}
// If requested, delete block storage associated with the VM
if !msg.keep_files { if !msg.keep_files {
log::info!("Delete storage associated with the domain"); log::info!("Delete storage associated with the domain");
let path = AppConfig::get().vm_storage_path(msg.id); let path = AppConfig::get().vm_storage_path(msg.id);

View File

@ -250,6 +250,19 @@ impl AppConfig {
self.storage_path().join("iso") self.storage_path().join("iso")
} }
/// Get the path where generated cloud init disk image are stored
pub fn cloud_init_disk_storage_path(&self) -> PathBuf {
self.storage_path().join("cloud_init_disks")
}
/// Get the path where the disk image of a VM is stored
pub fn cloud_init_disk_path_for_vm(&self, name: &str) -> PathBuf {
self.cloud_init_disk_storage_path().join(format!(
"{}-{name}.iso",
constants::CLOUD_INIT_IMAGE_PREFIX_NAME
))
}
/// Get disk images storage directory /// Get disk images storage directory
pub fn disk_images_storage_path(&self) -> PathBuf { pub fn disk_images_storage_path(&self) -> PathBuf {
self.storage_path().join("disk_images") self.storage_path().join("disk_images")

View File

@ -57,6 +57,9 @@ pub const DISK_SIZE_MIN: FileSize = FileSize::from_mb(50);
/// Disk size max (B) /// Disk size max (B)
pub const DISK_SIZE_MAX: FileSize = FileSize::from_gb(20000); pub const DISK_SIZE_MAX: FileSize = FileSize::from_gb(20000);
/// Cloud init generated disk image prefix
pub const CLOUD_INIT_IMAGE_PREFIX_NAME: &str = "virtweb-cloudinit-autogen-image";
/// Net nat entry comment max size /// Net nat entry comment max size
pub const NET_NAT_COMMENT_MAX_SIZE: usize = 250; pub const NET_NAT_COMMENT_MAX_SIZE: usize = 250;

View File

@ -142,9 +142,22 @@ impl VMInfo {
return Err(StructureExtraction("Invalid number of vCPU specified!").into()); return Err(StructureExtraction("Invalid number of vCPU specified!").into());
} }
let mut disks = vec![]; let mut iso_absolute_files = vec![];
// Add ISO files // Process cloud init image
if self.cloud_init.attach_config {
let cloud_init_disk_path = AppConfig::get().cloud_init_disk_path_for_vm(&self.name);
// Apply latest cloud init configuration
std::fs::write(
&cloud_init_disk_path,
self.cloud_init.generate_nocloud_disk()?,
)?;
iso_absolute_files.push(cloud_init_disk_path);
}
// Process uploaded ISO files
for iso_file in &self.iso_files { for iso_file in &self.iso_files {
if !files_utils::check_file_name(iso_file) { if !files_utils::check_file_name(iso_file) {
return Err(StructureExtraction("ISO filename is invalid!").into()); return Err(StructureExtraction("ISO filename is invalid!").into());
@ -156,6 +169,13 @@ impl VMInfo {
return Err(StructureExtraction("Specified ISO file does not exists!").into()); return Err(StructureExtraction("Specified ISO file does not exists!").into());
} }
iso_absolute_files.push(path);
}
let mut disks = vec![];
// Add ISO disk files
for iso_path in iso_absolute_files {
disks.push(DiskXML { disks.push(DiskXML {
r#type: "file".to_string(), r#type: "file".to_string(),
device: "cdrom".to_string(), device: "cdrom".to_string(),
@ -165,7 +185,7 @@ impl VMInfo {
cache: "none".to_string(), cache: "none".to_string(),
}, },
source: DiskSourceXML { source: DiskSourceXML {
file: path.to_string_lossy().to_string(), file: iso_path.to_string_lossy().to_string(),
}, },
target: DiskTargetXML { target: DiskTargetXML {
dev: format!( dev: format!(
@ -182,6 +202,7 @@ impl VMInfo {
}) })
} }
// Configure VNC access, if requested
let (vnc_graphics, vnc_video) = match self.vnc_access { let (vnc_graphics, vnc_video) = match self.vnc_access {
true => ( true => (
Some(GraphicsXML { Some(GraphicsXML {
@ -495,6 +516,7 @@ impl VMInfo {
.iter() .iter()
.filter(|d| d.device == "cdrom") .filter(|d| d.device == "cdrom")
.map(|d| d.source.file.rsplit_once('/').unwrap().1.to_string()) .map(|d| d.source.file.rsplit_once('/').unwrap().1.to_string())
.filter(|d| !d.starts_with(constants::CLOUD_INIT_IMAGE_PREFIX_NAME))
.collect(), .collect(),
file_disks: domain file_disks: domain

View File

@ -61,6 +61,8 @@ async fn main() -> std::io::Result<()> {
log::debug!("Create required directory, if missing"); log::debug!("Create required directory, if missing");
files_utils::create_directory_if_missing(AppConfig::get().iso_storage_path()).unwrap(); files_utils::create_directory_if_missing(AppConfig::get().iso_storage_path()).unwrap();
files_utils::create_directory_if_missing(AppConfig::get().cloud_init_disk_storage_path())
.unwrap();
files_utils::create_directory_if_missing(AppConfig::get().disk_images_storage_path()).unwrap(); files_utils::create_directory_if_missing(AppConfig::get().disk_images_storage_path()).unwrap();
files_utils::create_directory_if_missing(AppConfig::get().vnc_sockets_path()).unwrap(); files_utils::create_directory_if_missing(AppConfig::get().vnc_sockets_path()).unwrap();
files_utils::set_file_permission(AppConfig::get().vnc_sockets_path(), 0o777).unwrap(); files_utils::set_file_permission(AppConfig::get().vnc_sockets_path(), 0o777).unwrap();

View File

@ -8,18 +8,18 @@ use std::process::Command;
/// cloud-localds source code: https://github.com/canonical/cloud-utils/blob/main/bin/cloud-localds /// cloud-localds source code: https://github.com/canonical/cloud-utils/blob/main/bin/cloud-localds
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Default)] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Default)]
pub struct CloudInitConfig { pub struct CloudInitConfig {
attach_config: bool, pub attach_config: bool,
/// Main user data /// Main user data
user_data: String, pub user_data: String,
/// Instance ID, set in metadata file /// Instance ID, set in metadata file
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
instance_id: Option<String>, pub instance_id: Option<String>,
/// Local hostname, set in metadata file /// Local hostname, set in metadata file
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
local_hostname: Option<String>, pub local_hostname: Option<String>,
/// Network configuration /// Network configuration
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
network_configuration: Option<String>, pub network_configuration: Option<String>,
} }
impl CloudInitConfig { impl CloudInitConfig {