All checks were successful
		
		
	
	continuous-integration/drone/push Build is passing
				
			
		
			
				
	
	
		
			118 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			118 lines
		
	
	
		
			4.1 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
use crate::app_config::AppConfig;
 | 
						|
use crate::constants;
 | 
						|
use std::process::Command;
 | 
						|
 | 
						|
/// Cloud init DS Mode
 | 
						|
#[derive(Copy, Clone, Debug, serde::Serialize, serde::Deserialize)]
 | 
						|
pub enum CloudInitDSMode {
 | 
						|
    /// Networking is required
 | 
						|
    Net,
 | 
						|
    /// Does not require networking to be up before user-data actions are run
 | 
						|
    Local,
 | 
						|
}
 | 
						|
 | 
						|
/// 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
 | 
						|
/// cloud-localds source code: https://github.com/canonical/cloud-utils/blob/main/bin/cloud-localds
 | 
						|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Default)]
 | 
						|
pub struct CloudInitConfig {
 | 
						|
    pub attach_config: bool,
 | 
						|
    /// Main user data
 | 
						|
    pub user_data: String,
 | 
						|
    /// Instance ID, set in metadata file
 | 
						|
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
						|
    pub instance_id: Option<String>,
 | 
						|
    /// Local hostname, set in metadata file
 | 
						|
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
						|
    pub local_hostname: Option<String>,
 | 
						|
    /// Data source mode
 | 
						|
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
						|
    pub dsmode: Option<CloudInitDSMode>,
 | 
						|
    /// Network configuration
 | 
						|
    #[serde(skip_serializing_if = "Option::is_none")]
 | 
						|
    pub network_configuration: Option<String>,
 | 
						|
}
 | 
						|
 | 
						|
impl CloudInitConfig {
 | 
						|
    /// Check cloud init configuration
 | 
						|
    pub fn check_error(&self) -> Option<String> {
 | 
						|
        if !self.user_data.is_empty() {
 | 
						|
            // Check YAML content
 | 
						|
            if let Err(e) = serde_yml::from_str::<serde_json::Value>(&self.user_data) {
 | 
						|
                return Some(format!(
 | 
						|
                    "user data is an invalid YAML file! Deserialization error: {e}"
 | 
						|
                ));
 | 
						|
            }
 | 
						|
 | 
						|
            // Check first line
 | 
						|
            if !self.user_data.starts_with("#cloud-config\n") {
 | 
						|
                return Some(
 | 
						|
                    "user data file MUST start with '#cloud-config' as first line!".to_string(),
 | 
						|
                );
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        None
 | 
						|
    }
 | 
						|
 | 
						|
    /// 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}"));
 | 
						|
        }
 | 
						|
        if let Some(dsmode) = &self.dsmode {
 | 
						|
            metadatas.push(format!(
 | 
						|
                "dsmode: {}",
 | 
						|
                match dsmode {
 | 
						|
                    CloudInitDSMode::Net => "net",
 | 
						|
                    CloudInitDSMode::Local => "local",
 | 
						|
                }
 | 
						|
            ));
 | 
						|
        }
 | 
						|
        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)?)
 | 
						|
    }
 | 
						|
}
 |