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)?)
|
|
}
|
|
}
|