Can export entire server configuration
	
		
			
	
		
	
	
		
	
		
			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:
		@@ -4,6 +4,7 @@ use actix_web::body::BoxBody;
 | 
			
		||||
use actix_web::{HttpResponse, web};
 | 
			
		||||
use std::error::Error;
 | 
			
		||||
use std::fmt::{Display, Formatter};
 | 
			
		||||
use zip::result::ZipError;
 | 
			
		||||
 | 
			
		||||
pub mod api_tokens_controller;
 | 
			
		||||
pub mod auth_controller;
 | 
			
		||||
@@ -102,6 +103,12 @@ impl From<actix_web::Error> for HttpErr {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<ZipError> for HttpErr {
 | 
			
		||||
    fn from(value: ZipError) -> Self {
 | 
			
		||||
        HttpErr::Err(std::io::Error::other(value.to_string()).into())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl From<HttpResponse> for HttpErr {
 | 
			
		||||
    fn from(value: HttpResponse) -> Self {
 | 
			
		||||
        HttpErr::HTTPResponse(value)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,24 @@
 | 
			
		||||
use crate::actors::vnc_tokens_actor::VNC_TOKEN_LIFETIME;
 | 
			
		||||
use crate::app_config::AppConfig;
 | 
			
		||||
use crate::constants;
 | 
			
		||||
use crate::constants::{DISK_NAME_MAX_LEN, DISK_NAME_MIN_LEN, DISK_SIZE_MAX, DISK_SIZE_MIN};
 | 
			
		||||
use crate::controllers::{HttpResult, LibVirtReq};
 | 
			
		||||
use crate::extractors::local_auth_extractor::LocalAuthEnabled;
 | 
			
		||||
use crate::libvirt_rest_structures::hypervisor::HypervisorInfo;
 | 
			
		||||
use crate::libvirt_rest_structures::net::NetworkInfo;
 | 
			
		||||
use crate::libvirt_rest_structures::nw_filter::NetworkFilter;
 | 
			
		||||
use crate::libvirt_rest_structures::vm::VMInfo;
 | 
			
		||||
use crate::nat::nat_hook;
 | 
			
		||||
use crate::utils::net_utils;
 | 
			
		||||
use actix_web::{HttpResponse, Responder};
 | 
			
		||||
use crate::utils::time_utils::{format_date, time};
 | 
			
		||||
use crate::{api_tokens, constants};
 | 
			
		||||
use actix_files::NamedFile;
 | 
			
		||||
use actix_web::{HttpRequest, HttpResponse, Responder};
 | 
			
		||||
use serde::Serialize;
 | 
			
		||||
use std::fs::File;
 | 
			
		||||
use std::io::Write;
 | 
			
		||||
use sysinfo::{Components, Disks, Networks, System};
 | 
			
		||||
use zip::ZipWriter;
 | 
			
		||||
use zip::write::SimpleFileOptions;
 | 
			
		||||
 | 
			
		||||
#[derive(serde::Serialize)]
 | 
			
		||||
struct StaticConfig {
 | 
			
		||||
@@ -199,3 +209,85 @@ pub async fn networks_list() -> HttpResult {
 | 
			
		||||
pub async fn bridges_list() -> HttpResult {
 | 
			
		||||
    Ok(HttpResponse::Ok().json(net_utils::bridges_list()?))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Add JSON file to ZIP
 | 
			
		||||
fn zip_json<E: Serialize, F>(
 | 
			
		||||
    zip: &mut ZipWriter<File>,
 | 
			
		||||
    dir: &str,
 | 
			
		||||
    content: &Vec<E>,
 | 
			
		||||
    file_name: F,
 | 
			
		||||
) -> anyhow::Result<()>
 | 
			
		||||
where
 | 
			
		||||
    F: Fn(&E) -> String,
 | 
			
		||||
{
 | 
			
		||||
    for entry in content {
 | 
			
		||||
        let file_encoded = serde_json::to_string(&entry)?;
 | 
			
		||||
 | 
			
		||||
        let options = SimpleFileOptions::default()
 | 
			
		||||
            .compression_method(zip::CompressionMethod::Deflated)
 | 
			
		||||
            .unix_permissions(0o750);
 | 
			
		||||
 | 
			
		||||
        zip.start_file(format!("{dir}/{}.json", file_name(entry)), options)?;
 | 
			
		||||
        zip.write_all(file_encoded.as_bytes())?;
 | 
			
		||||
    }
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Export all configuration elements at once
 | 
			
		||||
pub async fn export_all_configs(req: HttpRequest, client: LibVirtReq) -> HttpResult {
 | 
			
		||||
    // Perform extractions
 | 
			
		||||
    let vms = client
 | 
			
		||||
        .get_full_domains_list()
 | 
			
		||||
        .await?
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .map(VMInfo::from_domain)
 | 
			
		||||
        .collect::<Result<Vec<_>, _>>()?;
 | 
			
		||||
    let networks = client
 | 
			
		||||
        .get_full_networks_list()
 | 
			
		||||
        .await?
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .map(NetworkInfo::from_xml)
 | 
			
		||||
        .collect::<Result<Vec<_>, _>>()?;
 | 
			
		||||
    let nw_filters = client
 | 
			
		||||
        .get_full_network_filters_list()
 | 
			
		||||
        .await?
 | 
			
		||||
        .into_iter()
 | 
			
		||||
        .map(NetworkFilter::lib2rest)
 | 
			
		||||
        .collect::<Result<Vec<_>, _>>()?;
 | 
			
		||||
    let tokens = api_tokens::full_list().await?;
 | 
			
		||||
 | 
			
		||||
    // Create ZIP file
 | 
			
		||||
    let dest_dir = tempfile::tempdir_in(&AppConfig::get().temp_dir)?;
 | 
			
		||||
    let zip_path = dest_dir.path().join("export.zip");
 | 
			
		||||
 | 
			
		||||
    let file = File::create(&zip_path)?;
 | 
			
		||||
    let mut zip = ZipWriter::new(file);
 | 
			
		||||
 | 
			
		||||
    // Encode entities to JSON
 | 
			
		||||
    zip_json(&mut zip, "vms", &vms, |v| v.name.to_string())?;
 | 
			
		||||
    zip_json(&mut zip, "networks", &networks, |v| v.name.0.to_string())?;
 | 
			
		||||
    zip_json(
 | 
			
		||||
        &mut zip,
 | 
			
		||||
        "nw_filters",
 | 
			
		||||
        &nw_filters,
 | 
			
		||||
        |v| match constants::BUILTIN_NETWORK_FILTER_RULES.contains(&v.name.0.as_str()) {
 | 
			
		||||
            true => format!("builtin/{}", v.name.0),
 | 
			
		||||
            false => v.name.0.to_string(),
 | 
			
		||||
        },
 | 
			
		||||
    )?;
 | 
			
		||||
    zip_json(&mut zip, "tokens", &tokens, |v| v.id.0.to_string())?;
 | 
			
		||||
 | 
			
		||||
    // Finalize ZIP and return response
 | 
			
		||||
    zip.finish()?;
 | 
			
		||||
    let file = File::open(zip_path)?;
 | 
			
		||||
 | 
			
		||||
    let file = NamedFile::from_file(
 | 
			
		||||
        file,
 | 
			
		||||
        format!(
 | 
			
		||||
            "export_{}.zip",
 | 
			
		||||
            format_date(time() as i64).unwrap().replace('/', "-")
 | 
			
		||||
        ),
 | 
			
		||||
    )?;
 | 
			
		||||
 | 
			
		||||
    Ok(file.into_response(&req))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -157,6 +157,10 @@ async fn main() -> std::io::Result<()> {
 | 
			
		||||
                "/api/server/bridges",
 | 
			
		||||
                web::get().to(server_controller::bridges_list),
 | 
			
		||||
            )
 | 
			
		||||
            .route(
 | 
			
		||||
                "/api/server/export_configs",
 | 
			
		||||
                web::get().to(server_controller::export_all_configs),
 | 
			
		||||
            )
 | 
			
		||||
            // Auth controller
 | 
			
		||||
            .route(
 | 
			
		||||
                "/api/auth/local",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
use chrono::Datelike;
 | 
			
		||||
use std::time::{SystemTime, UNIX_EPOCH};
 | 
			
		||||
 | 
			
		||||
/// Get the current time since epoch
 | 
			
		||||
@@ -13,3 +14,15 @@ pub fn time() -> u64 {
 | 
			
		||||
        .unwrap()
 | 
			
		||||
        .as_secs()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Format given UNIX time in a simple format
 | 
			
		||||
pub fn format_date(time: i64) -> anyhow::Result<String> {
 | 
			
		||||
    let date = chrono::DateTime::from_timestamp(time, 0).ok_or(anyhow::anyhow!("invalid date"))?;
 | 
			
		||||
 | 
			
		||||
    Ok(format!(
 | 
			
		||||
        "{:0>2}/{:0>2}/{}",
 | 
			
		||||
        date.day(),
 | 
			
		||||
        date.month(),
 | 
			
		||||
        date.year()
 | 
			
		||||
    ))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user