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))
|
.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
|
/// Update a VM information
|
||||||
pub async fn update(
|
pub async fn update(
|
||||||
client: LibVirtReq,
|
client: LibVirtReq,
|
||||||
|
@ -202,6 +202,10 @@ async fn main() -> std::io::Result<()> {
|
|||||||
"/api/vm/{uid}/src",
|
"/api/vm/{uid}/src",
|
||||||
web::get().to(vm_controller::get_single_src_def),
|
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(
|
.route(
|
||||||
"/api/vm/{uid}/autostart",
|
"/api/vm/{uid}/autostart",
|
||||||
web::get().to(vm_controller::get_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
|
/// 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
|
/// 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")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
network_configuration: Option<String>,
|
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;
|
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";
|
export type VMBootType = "UEFI" | "UEFISecureBoot" | "Legacy";
|
||||||
|
|
||||||
interface VMInfoInterface {
|
interface VMInfoInterface {
|
||||||
@ -101,6 +109,7 @@ interface VMInfoInterface {
|
|||||||
networks: VMNetInterface[];
|
networks: VMNetInterface[];
|
||||||
tpm_module: boolean;
|
tpm_module: boolean;
|
||||||
oem_strings: string[];
|
oem_strings: string[];
|
||||||
|
cloud_init: VMCloudInit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class VMInfo implements VMInfoInterface {
|
export class VMInfo implements VMInfoInterface {
|
||||||
@ -120,6 +129,7 @@ export class VMInfo implements VMInfoInterface {
|
|||||||
networks: VMNetInterface[];
|
networks: VMNetInterface[];
|
||||||
tpm_module: boolean;
|
tpm_module: boolean;
|
||||||
oem_strings: string[];
|
oem_strings: string[];
|
||||||
|
cloud_init: VMCloudInit;
|
||||||
|
|
||||||
constructor(int: VMInfoInterface) {
|
constructor(int: VMInfoInterface) {
|
||||||
this.name = int.name;
|
this.name = int.name;
|
||||||
@ -138,6 +148,7 @@ export class VMInfo implements VMInfoInterface {
|
|||||||
this.networks = int.networks;
|
this.networks = int.networks;
|
||||||
this.tpm_module = int.tpm_module;
|
this.tpm_module = int.tpm_module;
|
||||||
this.oem_strings = int.oem_strings;
|
this.oem_strings = int.oem_strings;
|
||||||
|
this.cloud_init = int.cloud_init;
|
||||||
}
|
}
|
||||||
|
|
||||||
static NewEmpty(): VMInfo {
|
static NewEmpty(): VMInfo {
|
||||||
@ -153,6 +164,7 @@ export class VMInfo implements VMInfoInterface {
|
|||||||
networks: [],
|
networks: [],
|
||||||
tpm_module: true,
|
tpm_module: true,
|
||||||
oem_strings: [],
|
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 XML definition</TableCell>
|
||||||
<TableCell align="center">Get autostart</TableCell>
|
<TableCell align="center">Get autostart</TableCell>
|
||||||
<TableCell align="center">Set autostart</TableCell>
|
<TableCell align="center">Set autostart</TableCell>
|
||||||
|
<TableCell align="center">Get CloudInit disk</TableCell>
|
||||||
<TableCell align="center">Backup disk</TableCell>
|
<TableCell align="center">Backup disk</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
@ -84,6 +85,13 @@ export function TokenRightsEditor(p: {
|
|||||||
{...p}
|
{...p}
|
||||||
right={{ verb: "PUT", path: "/api/vm/*/autostart" }}
|
right={{ verb: "PUT", path: "/api/vm/*/autostart" }}
|
||||||
/>
|
/>
|
||||||
|
<CellRight
|
||||||
|
{...p}
|
||||||
|
right={{
|
||||||
|
verb: "GET",
|
||||||
|
path: "/api/vm/*/cloud_init_disk",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<CellRight
|
<CellRight
|
||||||
{...p}
|
{...p}
|
||||||
right={{ verb: "POST", path: "/api/vm/*/disk/*/backup" }}
|
right={{ verb: "POST", path: "/api/vm/*/disk/*/backup" }}
|
||||||
@ -123,7 +131,15 @@ export function TokenRightsEditor(p: {
|
|||||||
{...p}
|
{...p}
|
||||||
right={{ verb: "PUT", path: `/api/vm/${v.uuid}/autostart` }}
|
right={{ verb: "PUT", path: `/api/vm/${v.uuid}/autostart` }}
|
||||||
parent={{ verb: "PUT", path: "/api/vm/*/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
|
<CellRight
|
||||||
{...p}
|
{...p}
|
||||||
right={{
|
right={{
|
||||||
|
Reference in New Issue
Block a user