All checks were successful
continuous-integration/drone/push Build is passing
256 lines
7.9 KiB
Rust
256 lines
7.9 KiB
Rust
use crate::app_config::AppConfig;
|
|
use crate::constants;
|
|
use crate::controllers::{HttpResult, LibVirtReq};
|
|
use crate::libvirt_lib_structures::XMLUuid;
|
|
use crate::libvirt_rest_structures::vm::VMInfo;
|
|
use crate::utils::file_disks_utils::{DiskFileFormat, DiskFileInfo};
|
|
use crate::utils::files_utils;
|
|
use actix_files::NamedFile;
|
|
use actix_multipart::form::MultipartForm;
|
|
use actix_multipart::form::tempfile::TempFile;
|
|
use actix_web::{HttpRequest, HttpResponse, web};
|
|
|
|
#[derive(Debug, MultipartForm)]
|
|
pub struct UploadDiskImageForm {
|
|
#[multipart(rename = "file")]
|
|
files: Vec<TempFile>,
|
|
}
|
|
|
|
/// Upload disk image file
|
|
pub async fn upload(MultipartForm(mut form): MultipartForm<UploadDiskImageForm>) -> HttpResult {
|
|
if form.files.is_empty() {
|
|
log::error!("Missing uploaded disk file!");
|
|
return Ok(HttpResponse::BadRequest().json("Missing file!"));
|
|
}
|
|
|
|
let file = form.files.remove(0);
|
|
|
|
// Check uploaded file size
|
|
if file.size > constants::DISK_IMAGE_MAX_SIZE.as_bytes() {
|
|
return Ok(HttpResponse::BadRequest().json("Disk image max size exceeded!"));
|
|
}
|
|
|
|
// Check file mime type
|
|
if let Some(mime_type) = file.content_type {
|
|
if !constants::ALLOWED_DISK_IMAGES_MIME_TYPES.contains(&mime_type.as_ref()) {
|
|
return Ok(HttpResponse::BadRequest().json(format!(
|
|
"Unsupported file type for disk upload: {}",
|
|
mime_type
|
|
)));
|
|
}
|
|
}
|
|
|
|
// Extract and check file name
|
|
let Some(file_name) = file.file_name else {
|
|
return Ok(HttpResponse::BadRequest().json("Missing file name of uploaded file!"));
|
|
};
|
|
if !files_utils::check_file_name(&file_name) {
|
|
return Ok(HttpResponse::BadRequest().json("Invalid uploaded file name!"));
|
|
}
|
|
|
|
// Check if a file with the same name already exists
|
|
let dest_path = AppConfig::get().disk_images_file_path(&file_name);
|
|
if dest_path.is_file() {
|
|
return Ok(HttpResponse::Conflict().json("A file with the same name already exists!"));
|
|
}
|
|
|
|
// Copy the file to the destination
|
|
file.file.persist(&dest_path)?;
|
|
|
|
// Check if file information can be loaded
|
|
if let Err(e) = DiskFileInfo::load_file(&dest_path) {
|
|
log::error!("Failed to get information about uploaded disk file! {e}");
|
|
std::fs::remove_file(&dest_path)?;
|
|
return Ok(HttpResponse::InternalServerError()
|
|
.json(format!("Unable to process uploaded file! {e}")));
|
|
}
|
|
|
|
Ok(HttpResponse::Ok().json("Successfully uploaded disk image!"))
|
|
}
|
|
|
|
/// Get disk images list
|
|
pub async fn get_list() -> HttpResult {
|
|
let mut list = vec![];
|
|
for entry in AppConfig::get().disk_images_storage_path().read_dir()? {
|
|
let entry = entry?;
|
|
list.push(DiskFileInfo::load_file(&entry.path())?);
|
|
}
|
|
|
|
Ok(HttpResponse::Ok().json(list))
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct DiskFilePath {
|
|
filename: String,
|
|
}
|
|
|
|
/// Download disk image
|
|
pub async fn download(p: web::Path<DiskFilePath>, req: HttpRequest) -> HttpResult {
|
|
if !files_utils::check_file_name(&p.filename) {
|
|
return Ok(HttpResponse::BadRequest().json("Invalid file name!"));
|
|
}
|
|
|
|
let file_path = AppConfig::get().disk_images_file_path(&p.filename);
|
|
|
|
if !file_path.exists() {
|
|
return Ok(HttpResponse::NotFound().json("Disk image does not exists!"));
|
|
}
|
|
|
|
Ok(NamedFile::open(file_path)?.into_response(&req))
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct ConvertDiskImageRequest {
|
|
dest_file_name: String,
|
|
#[serde(flatten)]
|
|
format: DiskFileFormat,
|
|
}
|
|
|
|
/// Convert disk image into a new format
|
|
pub async fn convert(
|
|
p: web::Path<DiskFilePath>,
|
|
req: web::Json<ConvertDiskImageRequest>,
|
|
) -> HttpResult {
|
|
if !files_utils::check_file_name(&p.filename) {
|
|
return Ok(HttpResponse::BadRequest().json("Invalid src file name!"));
|
|
}
|
|
|
|
let src_file_path = AppConfig::get().disk_images_file_path(&p.filename);
|
|
|
|
let src = DiskFileInfo::load_file(&src_file_path)?;
|
|
|
|
handle_convert_request(src, &req).await
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct BackupVMDiskPath {
|
|
uid: XMLUuid,
|
|
diskid: String,
|
|
}
|
|
|
|
/// Perform disk backup
|
|
pub async fn backup_disk(
|
|
client: LibVirtReq,
|
|
path: web::Path<BackupVMDiskPath>,
|
|
req: web::Json<ConvertDiskImageRequest>,
|
|
) -> HttpResult {
|
|
// Get the VM information
|
|
let info = match client.get_single_domain(path.uid).await {
|
|
Ok(i) => i,
|
|
Err(e) => {
|
|
log::error!("Failed to get domain info! {e}");
|
|
return Ok(HttpResponse::InternalServerError().json(e.to_string()));
|
|
}
|
|
};
|
|
|
|
let vm = VMInfo::from_domain(info)?;
|
|
|
|
// Load disk information
|
|
let Some(disk) = vm
|
|
.file_disks
|
|
.into_iter()
|
|
.find(|disk| disk.name == path.diskid)
|
|
else {
|
|
return Ok(HttpResponse::NotFound()
|
|
.json(format!("Disk {} not found for vm {}", path.diskid, vm.name)));
|
|
};
|
|
|
|
let src_path = disk.disk_path(vm.uuid.expect("Missing VM uuid!"));
|
|
let src_disk = DiskFileInfo::load_file(&src_path)?;
|
|
|
|
// Perform conversion
|
|
handle_convert_request(src_disk, &req).await
|
|
}
|
|
|
|
/// Generic controller code that performs image conversion to create a disk image file
|
|
pub async fn handle_convert_request(
|
|
src: DiskFileInfo,
|
|
req: &ConvertDiskImageRequest,
|
|
) -> HttpResult {
|
|
// Check destination file
|
|
if !files_utils::check_file_name(&req.dest_file_name) {
|
|
return Ok(HttpResponse::BadRequest().json("Invalid destination file name!"));
|
|
}
|
|
if !req
|
|
.format
|
|
.ext()
|
|
.iter()
|
|
.any(|e| req.dest_file_name.ends_with(e))
|
|
{
|
|
return Ok(HttpResponse::BadRequest().json("Invalid destination file extension!"));
|
|
}
|
|
|
|
let dst_file_path = AppConfig::get().disk_images_file_path(&req.dest_file_name);
|
|
|
|
if dst_file_path.exists() {
|
|
return Ok(HttpResponse::Conflict().json("Specified destination file already exists!"));
|
|
}
|
|
|
|
// Perform conversion
|
|
if let Err(e) = src.convert(&dst_file_path, req.format) {
|
|
log::error!("Disk file conversion error: {e}");
|
|
return Ok(
|
|
HttpResponse::InternalServerError().json(format!("Disk file conversion error: {e}"))
|
|
);
|
|
}
|
|
|
|
Ok(HttpResponse::Accepted().json("Successfully converted disk file"))
|
|
}
|
|
|
|
#[derive(serde::Deserialize)]
|
|
pub struct RenameDiskImageRequest {
|
|
name: String,
|
|
}
|
|
|
|
/// Rename disk image
|
|
pub async fn rename(
|
|
p: web::Path<DiskFilePath>,
|
|
req: web::Json<RenameDiskImageRequest>,
|
|
) -> HttpResult {
|
|
// Check source
|
|
if !files_utils::check_file_name(&p.filename) {
|
|
return Ok(HttpResponse::BadRequest().json("Invalid src file name!"));
|
|
}
|
|
let src_path = AppConfig::get().disk_images_file_path(&p.filename);
|
|
if !src_path.exists() {
|
|
return Ok(HttpResponse::NotFound().json("Disk image does not exists!"));
|
|
}
|
|
|
|
// Check destination
|
|
if !files_utils::check_file_name(&req.name) {
|
|
return Ok(HttpResponse::BadRequest().json("Invalid dst file name!"));
|
|
}
|
|
let dst_path = AppConfig::get().disk_images_file_path(&req.name);
|
|
if dst_path.exists() {
|
|
return Ok(HttpResponse::Conflict().json("Destination name already exists!"));
|
|
}
|
|
|
|
// Check extension
|
|
let disk = DiskFileInfo::load_file(&src_path)?;
|
|
if !disk.format.ext().iter().any(|e| req.name.ends_with(e)) {
|
|
return Ok(HttpResponse::BadRequest().json("Invalid destination file extension!"));
|
|
}
|
|
|
|
// Perform rename
|
|
std::fs::rename(&src_path, &dst_path)?;
|
|
|
|
Ok(HttpResponse::Accepted().finish())
|
|
}
|
|
|
|
/// Delete a disk image
|
|
pub async fn delete(p: web::Path<DiskFilePath>) -> HttpResult {
|
|
if !files_utils::check_file_name(&p.filename) {
|
|
return Ok(HttpResponse::BadRequest().json("Invalid file name!"));
|
|
}
|
|
|
|
let file_path = AppConfig::get().disk_images_file_path(&p.filename);
|
|
|
|
if !file_path.exists() {
|
|
return Ok(HttpResponse::NotFound().json("Disk image does not exists!"));
|
|
}
|
|
|
|
std::fs::remove_file(file_path)?;
|
|
|
|
Ok(HttpResponse::Accepted().finish())
|
|
}
|