Generate cloud init disk image
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				continuous-integration/drone/push Build is passing
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			This commit is contained in:
		@@ -109,6 +109,28 @@ pub async fn get_single_src_def(client: LibVirtReq, id: web::Path<SingleVMUUidRe
 | 
			
		||||
        .body(info))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get the generated cloud init configuration disk of a vm
 | 
			
		||||
pub async fn get_cloud_init_disk(client: LibVirtReq, id: web::Path<SingleVMUUidReq>) -> HttpResult {
 | 
			
		||||
    let info = match client.get_single_domain(id.uid).await {
 | 
			
		||||
        Ok(i) => i,
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            log::error!("Failed to get domain information! {e}");
 | 
			
		||||
            return Ok(HttpResponse::InternalServerError().json(e.to_string()));
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let vm = VMInfo::from_domain(info)?;
 | 
			
		||||
    let disk = vm.cloud_init.generate_nocloud_disk()?;
 | 
			
		||||
 | 
			
		||||
    Ok(HttpResponse::Ok()
 | 
			
		||||
        .content_type("application/x-iso9660-image")
 | 
			
		||||
        .insert_header((
 | 
			
		||||
            "Content-Disposition",
 | 
			
		||||
            format!("attachment; filename=\"cloud_init_{}.iso\"", vm.name),
 | 
			
		||||
        ))
 | 
			
		||||
        .body(disk))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Update a VM information
 | 
			
		||||
pub async fn update(
 | 
			
		||||
    client: LibVirtReq,
 | 
			
		||||
 
 | 
			
		||||
@@ -202,6 +202,10 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
                "/api/vm/{uid}/src",
 | 
			
		||||
                web::get().to(vm_controller::get_single_src_def),
 | 
			
		||||
            )
 | 
			
		||||
            .route(
 | 
			
		||||
                "/api/vm/{uid}/cloud_init_disk",
 | 
			
		||||
                web::get().to(vm_controller::get_cloud_init_disk),
 | 
			
		||||
            )
 | 
			
		||||
            .route(
 | 
			
		||||
                "/api/vm/{uid}/autostart",
 | 
			
		||||
                web::get().to(vm_controller::get_autostart),
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,7 @@
 | 
			
		||||
use crate::app_config::AppConfig;
 | 
			
		||||
use crate::constants;
 | 
			
		||||
use std::process::Command;
 | 
			
		||||
 | 
			
		||||
/// VM Cloud Init configuration
 | 
			
		||||
///
 | 
			
		||||
/// RedHat documentation: https://docs.redhat.com/fr/documentation/red_hat_enterprise_linux/9/html/configuring_and_managing_cloud-init_for_rhel_9/configuring-cloud-init_cloud-content
 | 
			
		||||
@@ -17,3 +21,55 @@ pub struct CloudInitConfig {
 | 
			
		||||
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
			
		||||
    network_configuration: Option<String>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl CloudInitConfig {
 | 
			
		||||
    /// Generate disk image for nocloud usage
 | 
			
		||||
    pub fn generate_nocloud_disk(&self) -> anyhow::Result<Vec<u8>> {
 | 
			
		||||
        let temp_path = tempfile::tempdir_in(&AppConfig::get().temp_dir)?;
 | 
			
		||||
 | 
			
		||||
        let mut cmd = Command::new(constants::PROGRAM_CLOUD_LOCALDS);
 | 
			
		||||
 | 
			
		||||
        // ISO destination path
 | 
			
		||||
        let temp_iso = temp_path.path().join("disk.iso");
 | 
			
		||||
        cmd.arg(&temp_iso);
 | 
			
		||||
 | 
			
		||||
        // Process network configuration
 | 
			
		||||
        if let Some(net_conf) = &self.network_configuration {
 | 
			
		||||
            let net_conf_path = temp_path.path().join("network");
 | 
			
		||||
            std::fs::write(&net_conf_path, net_conf)?;
 | 
			
		||||
            cmd.arg("--network-config").arg(&net_conf_path);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Process user data
 | 
			
		||||
        let user_data_path = temp_path.path().join("user-data");
 | 
			
		||||
        std::fs::write(&user_data_path, &self.user_data)?;
 | 
			
		||||
        cmd.arg(user_data_path);
 | 
			
		||||
 | 
			
		||||
        // Process metadata
 | 
			
		||||
        let mut metadatas = vec![];
 | 
			
		||||
        if let Some(inst_id) = &self.instance_id {
 | 
			
		||||
            metadatas.push(format!("instance-id: {}", inst_id));
 | 
			
		||||
        }
 | 
			
		||||
        if let Some(local_hostname) = &self.local_hostname {
 | 
			
		||||
            metadatas.push(format!("local-hostname: {}", local_hostname));
 | 
			
		||||
        }
 | 
			
		||||
        let meta_data_path = temp_path.path().join("meta-data");
 | 
			
		||||
        std::fs::write(&meta_data_path, metadatas.join("\n"))?;
 | 
			
		||||
        cmd.arg(meta_data_path);
 | 
			
		||||
 | 
			
		||||
        // Execute command
 | 
			
		||||
        let output = cmd.output()?;
 | 
			
		||||
        if !output.status.success() {
 | 
			
		||||
            anyhow::bail!(
 | 
			
		||||
                "{} exited with status {}!\nStdout: {}\nStderr: {}",
 | 
			
		||||
                constants::PROGRAM_CLOUD_LOCALDS,
 | 
			
		||||
                output.status,
 | 
			
		||||
                String::from_utf8_lossy(&output.stdout),
 | 
			
		||||
                String::from_utf8_lossy(&output.stderr)
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Read generated ISO file
 | 
			
		||||
        Ok(std::fs::read(temp_iso)?)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -82,6 +82,14 @@ export interface VMNetBridge {
 | 
			
		||||
  bridge: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface VMCloudInit {
 | 
			
		||||
  attach_config: boolean;
 | 
			
		||||
  user_data: string;
 | 
			
		||||
  instance_id?: string;
 | 
			
		||||
  local_hostname?: string;
 | 
			
		||||
  network_configuration?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type VMBootType = "UEFI" | "UEFISecureBoot" | "Legacy";
 | 
			
		||||
 | 
			
		||||
interface VMInfoInterface {
 | 
			
		||||
@@ -101,6 +109,7 @@ interface VMInfoInterface {
 | 
			
		||||
  networks: VMNetInterface[];
 | 
			
		||||
  tpm_module: boolean;
 | 
			
		||||
  oem_strings: string[];
 | 
			
		||||
  cloud_init: VMCloudInit;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class VMInfo implements VMInfoInterface {
 | 
			
		||||
@@ -120,6 +129,7 @@ export class VMInfo implements VMInfoInterface {
 | 
			
		||||
  networks: VMNetInterface[];
 | 
			
		||||
  tpm_module: boolean;
 | 
			
		||||
  oem_strings: string[];
 | 
			
		||||
  cloud_init: VMCloudInit;
 | 
			
		||||
 | 
			
		||||
  constructor(int: VMInfoInterface) {
 | 
			
		||||
    this.name = int.name;
 | 
			
		||||
@@ -138,6 +148,7 @@ export class VMInfo implements VMInfoInterface {
 | 
			
		||||
    this.networks = int.networks;
 | 
			
		||||
    this.tpm_module = int.tpm_module;
 | 
			
		||||
    this.oem_strings = int.oem_strings;
 | 
			
		||||
    this.cloud_init = int.cloud_init;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static NewEmpty(): VMInfo {
 | 
			
		||||
@@ -153,6 +164,7 @@ export class VMInfo implements VMInfoInterface {
 | 
			
		||||
      networks: [],
 | 
			
		||||
      tpm_module: true,
 | 
			
		||||
      oem_strings: [],
 | 
			
		||||
      cloud_init: { attach_config: false, user_data: "" },
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -60,6 +60,7 @@ export function TokenRightsEditor(p: {
 | 
			
		||||
              <TableCell align="center">Get XML definition</TableCell>
 | 
			
		||||
              <TableCell align="center">Get autostart</TableCell>
 | 
			
		||||
              <TableCell align="center">Set autostart</TableCell>
 | 
			
		||||
              <TableCell align="center">Get CloudInit disk</TableCell>
 | 
			
		||||
              <TableCell align="center">Backup disk</TableCell>
 | 
			
		||||
            </TableRow>
 | 
			
		||||
          </TableHead>
 | 
			
		||||
@@ -84,6 +85,13 @@ export function TokenRightsEditor(p: {
 | 
			
		||||
                {...p}
 | 
			
		||||
                right={{ verb: "PUT", path: "/api/vm/*/autostart" }}
 | 
			
		||||
              />
 | 
			
		||||
              <CellRight
 | 
			
		||||
                {...p}
 | 
			
		||||
                right={{
 | 
			
		||||
                  verb: "GET",
 | 
			
		||||
                  path: "/api/vm/*/cloud_init_disk",
 | 
			
		||||
                }}
 | 
			
		||||
              />
 | 
			
		||||
              <CellRight
 | 
			
		||||
                {...p}
 | 
			
		||||
                right={{ verb: "POST", path: "/api/vm/*/disk/*/backup" }}
 | 
			
		||||
@@ -123,7 +131,15 @@ export function TokenRightsEditor(p: {
 | 
			
		||||
                  {...p}
 | 
			
		||||
                  right={{ verb: "PUT", path: `/api/vm/${v.uuid}/autostart` }}
 | 
			
		||||
                  parent={{ verb: "PUT", path: "/api/vm/*/autostart" }}
 | 
			
		||||
                />{" "}
 | 
			
		||||
                />
 | 
			
		||||
                <CellRight
 | 
			
		||||
                  {...p}
 | 
			
		||||
                  right={{
 | 
			
		||||
                    verb: "GET",
 | 
			
		||||
                    path: `/api/vm/${v.uuid}/cloud_init_disk`,
 | 
			
		||||
                  }}
 | 
			
		||||
                  parent={{ verb: "GET", path: "/api/vm/*/cloud_init_disk" }}
 | 
			
		||||
                />
 | 
			
		||||
                <CellRight
 | 
			
		||||
                  {...p}
 | 
			
		||||
                  right={{
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user