split BackupVmaToPbsArgs into PbsArgs and VmaBackupArgs

Signed-off-by: Filip Schauer <f.schauer@proxmox.com>
This commit is contained in:
Filip Schauer 2024-10-30 14:55:32 +01:00 committed by Fabian Grünbichler
parent 3c850b39a5
commit f48dd30f86
2 changed files with 124 additions and 88 deletions

View File

@ -6,7 +6,7 @@ use proxmox_time::epoch_i64;
mod vma; mod vma;
mod vma2pbs; mod vma2pbs;
use vma2pbs::{backup_vma_to_pbs, BackupVmaToPbsArgs}; use vma2pbs::{vma2pbs, BackupVmaToPbsArgs, PbsArgs, VmaBackupArgs};
const CMD_HELP: &str = "\ const CMD_HELP: &str = "\
Usage: vma-to-pbs [OPTIONS] --repository <auth_id@host:port:datastore> --vmid <VMID> [vma_file] Usage: vma-to-pbs [OPTIONS] --repository <auth_id@host:port:datastore> --vmid <VMID> [vma_file]
@ -184,12 +184,9 @@ fn parse_args() -> Result<BackupVmaToPbsArgs, Error> {
None None
}; };
let options = BackupVmaToPbsArgs { let pbs_args = PbsArgs {
vma_file_path: vma_file_path.cloned(),
pbs_repository, pbs_repository,
namespace, namespace,
backup_id: vmid,
backup_time,
pbs_password, pbs_password,
keyfile, keyfile,
key_password, key_password,
@ -197,16 +194,24 @@ fn parse_args() -> Result<BackupVmaToPbsArgs, Error> {
fingerprint, fingerprint,
compress, compress,
encrypt, encrypt,
};
let vma_args = VmaBackupArgs {
vma_file_path: vma_file_path.cloned(),
backup_id: vmid,
backup_time,
notes, notes,
log_file_path, log_file_path,
}; };
let options = BackupVmaToPbsArgs { pbs_args, vma_args };
Ok(options) Ok(options)
} }
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
let args = parse_args()?; let args = parse_args()?;
backup_vma_to_pbs(args)?; vma2pbs(args)?;
Ok(()) Ok(())
} }

View File

@ -29,11 +29,13 @@ use crate::vma::VmaReader;
const VMA_CLUSTER_SIZE: usize = 65536; const VMA_CLUSTER_SIZE: usize = 65536;
pub struct BackupVmaToPbsArgs { pub struct BackupVmaToPbsArgs {
pub vma_file_path: Option<OsString>, pub pbs_args: PbsArgs,
pub vma_args: VmaBackupArgs,
}
pub struct PbsArgs {
pub pbs_repository: String, pub pbs_repository: String,
pub namespace: Option<String>, pub namespace: Option<String>,
pub backup_id: String,
pub backup_time: i64,
pub pbs_password: String, pub pbs_password: String,
pub keyfile: Option<String>, pub keyfile: Option<String>,
pub key_password: Option<String>, pub key_password: Option<String>,
@ -41,6 +43,12 @@ pub struct BackupVmaToPbsArgs {
pub fingerprint: String, pub fingerprint: String,
pub compress: bool, pub compress: bool,
pub encrypt: bool, pub encrypt: bool,
}
pub struct VmaBackupArgs {
pub vma_file_path: Option<OsString>,
pub backup_id: String,
pub backup_time: i64,
pub notes: Option<String>, pub notes: Option<String>,
pub log_file_path: Option<OsString>, pub log_file_path: Option<OsString>,
} }
@ -61,25 +69,23 @@ fn handle_pbs_error(pbs_err: *mut c_char, function_name: &str) -> Result<(), Err
bail!("{function_name} failed: {pbs_err_str}"); bail!("{function_name} failed: {pbs_err_str}");
} }
fn create_pbs_backup_task(args: &BackupVmaToPbsArgs) -> Result<*mut ProxmoxBackupHandle, Error> { fn create_pbs_backup_task(
println!("PBS repository: {}", args.pbs_repository); pbs_args: &PbsArgs,
if let Some(ns) = &args.namespace { backup_args: &VmaBackupArgs,
println!("PBS namespace: {}", ns); ) -> Result<*mut ProxmoxBackupHandle, Error> {
} println!(
println!("PBS fingerprint: {}", args.fingerprint); "backup time: {}",
println!("compress: {}", args.compress); epoch_to_rfc3339(backup_args.backup_time)?
println!("encrypt: {}", args.encrypt); );
println!("backup time: {}", epoch_to_rfc3339(args.backup_time)?);
let mut pbs_err: *mut c_char = ptr::null_mut(); let mut pbs_err: *mut c_char = ptr::null_mut();
let pbs_repository_cstr = CString::new(args.pbs_repository.as_str())?; let pbs_repository_cstr = CString::new(pbs_args.pbs_repository.as_str())?;
let ns_cstr = CString::new(args.namespace.as_deref().unwrap_or(""))?; let ns_cstr = CString::new(pbs_args.namespace.as_deref().unwrap_or(""))?;
let backup_id_cstr = CString::new(args.backup_id.as_str())?; let backup_id_cstr = CString::new(backup_args.backup_id.as_str())?;
let pbs_password_cstr = CString::new(args.pbs_password.as_str())?; let pbs_password_cstr = CString::new(pbs_args.pbs_password.as_str())?;
let fingerprint_cstr = CString::new(args.fingerprint.as_str())?; let fingerprint_cstr = CString::new(pbs_args.fingerprint.as_str())?;
let keyfile_cstr = args let keyfile_cstr = pbs_args
.keyfile .keyfile
.as_ref() .as_ref()
.map(|v| CString::new(v.as_str()).unwrap()); .map(|v| CString::new(v.as_str()).unwrap());
@ -87,7 +93,7 @@ fn create_pbs_backup_task(args: &BackupVmaToPbsArgs) -> Result<*mut ProxmoxBacku
.as_ref() .as_ref()
.map(|v| v.as_ptr()) .map(|v| v.as_ptr())
.unwrap_or(ptr::null()); .unwrap_or(ptr::null());
let key_password_cstr = args let key_password_cstr = pbs_args
.key_password .key_password
.as_ref() .as_ref()
.map(|v| CString::new(v.as_str()).unwrap()); .map(|v| CString::new(v.as_str()).unwrap());
@ -95,7 +101,7 @@ fn create_pbs_backup_task(args: &BackupVmaToPbsArgs) -> Result<*mut ProxmoxBacku
.as_ref() .as_ref()
.map(|v| v.as_ptr()) .map(|v| v.as_ptr())
.unwrap_or(ptr::null()); .unwrap_or(ptr::null());
let master_keyfile_cstr = args let master_keyfile_cstr = pbs_args
.master_keyfile .master_keyfile
.as_ref() .as_ref()
.map(|v| CString::new(v.as_str()).unwrap()); .map(|v| CString::new(v.as_str()).unwrap());
@ -108,14 +114,14 @@ fn create_pbs_backup_task(args: &BackupVmaToPbsArgs) -> Result<*mut ProxmoxBacku
pbs_repository_cstr.as_ptr(), pbs_repository_cstr.as_ptr(),
ns_cstr.as_ptr(), ns_cstr.as_ptr(),
backup_id_cstr.as_ptr(), backup_id_cstr.as_ptr(),
args.backup_time as u64, backup_args.backup_time as u64,
PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE, PROXMOX_BACKUP_DEFAULT_CHUNK_SIZE,
pbs_password_cstr.as_ptr(), pbs_password_cstr.as_ptr(),
keyfile_ptr, keyfile_ptr,
key_password_ptr, key_password_ptr,
master_keyfile_ptr, master_keyfile_ptr,
args.compress, pbs_args.compress,
args.encrypt, pbs_args.encrypt,
fingerprint_cstr.as_ptr(), fingerprint_cstr.as_ptr(),
&mut pbs_err, &mut pbs_err,
); );
@ -361,17 +367,24 @@ where
Ok(()) Ok(())
} }
fn pbs_client_setup(args: &BackupVmaToPbsArgs) -> Result<(HttpClient, String, Value), Error> { fn pbs_client_setup(
let repo: BackupRepository = args.pbs_repository.parse()?; pbs_args: &PbsArgs,
backup_args: &VmaBackupArgs,
) -> Result<(HttpClient, String, Value), Error> {
let repo: BackupRepository = pbs_args.pbs_repository.parse()?;
let options = HttpClientOptions::new_interactive( let options = HttpClientOptions::new_interactive(
Some(args.pbs_password.clone()), Some(pbs_args.pbs_password.clone()),
Some(args.fingerprint.clone()), Some(pbs_args.fingerprint.clone()),
); );
let client = HttpClient::new(repo.host(), repo.port(), repo.auth_id(), options)?; let client = HttpClient::new(repo.host(), repo.port(), repo.auth_id(), options)?;
let backup_dir = BackupDir::from((BackupType::Vm, args.backup_id.clone(), args.backup_time)); let backup_dir = BackupDir::from((
BackupType::Vm,
backup_args.backup_id.clone(),
backup_args.backup_time,
));
let namespace = match &args.namespace { let namespace = match &pbs_args.namespace {
Some(namespace) => BackupNamespace::new(namespace)?, Some(namespace) => BackupNamespace::new(namespace)?,
None => BackupNamespace::root(), None => BackupNamespace::root(),
}; };
@ -386,45 +399,44 @@ fn pbs_client_setup(args: &BackupVmaToPbsArgs) -> Result<(HttpClient, String, Va
fn upload_log( fn upload_log(
client: &HttpClient, client: &HttpClient,
args: &BackupVmaToPbsArgs, log_file_path: &OsString,
pbs_args: &PbsArgs,
store: &str, store: &str,
request_args: Value, request_args: Value,
) -> Result<(), Error> { ) -> Result<(), Error> {
if let Some(log_file_path) = &args.log_file_path { let path = format!("api2/json/admin/datastore/{}/upload-backup-log", store);
let path = format!("api2/json/admin/datastore/{}/upload-backup-log", store); let data = std::fs::read(log_file_path)?;
let data = std::fs::read(log_file_path)?;
let blob = if args.encrypt { let blob = if pbs_args.encrypt {
let crypt_config = match &args.keyfile { let crypt_config = match &pbs_args.keyfile {
None => None, None => None,
Some(keyfile) => { Some(keyfile) => {
let key = std::fs::read(keyfile)?; let key = std::fs::read(keyfile)?;
let (key, _created, _) = decrypt_key(&key, &|| -> Result<Vec<u8>, Error> { let (key, _created, _) = decrypt_key(&key, &|| -> Result<Vec<u8>, Error> {
match &args.key_password { match &pbs_args.key_password {
Some(key_password) => Ok(key_password.clone().into_bytes()), Some(key_password) => Ok(key_password.clone().into_bytes()),
None => bail!("no key password provided"), None => bail!("no key password provided"),
} }
})?; })?;
let crypt_config = CryptConfig::new(key)?; let crypt_config = CryptConfig::new(key)?;
Some(crypt_config) Some(crypt_config)
} }
};
DataBlob::encode(&data, crypt_config.as_ref(), args.compress)?
} else {
// fixme: howto sign log?
DataBlob::encode(&data, None, args.compress)?
}; };
let body = hyper::Body::from(blob.into_inner()); DataBlob::encode(&data, crypt_config.as_ref(), pbs_args.compress)?
} else {
// fixme: howto sign log?
DataBlob::encode(&data, None, pbs_args.compress)?
};
block_on(async { let body = hyper::Body::from(blob.into_inner());
client
.upload("application/octet-stream", body, &path, Some(request_args)) block_on(async {
.await client
.unwrap(); .upload("application/octet-stream", body, &path, Some(request_args))
}); .await
} .unwrap();
});
Ok(()) Ok(())
} }
@ -444,8 +456,32 @@ fn set_notes(
Ok(()) Ok(())
} }
pub fn backup_vma_to_pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> { pub fn vma2pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> {
let vma_file: Box<dyn BufRead> = match &args.vma_file_path { let pbs_args = &args.pbs_args;
println!("PBS repository: {}", pbs_args.pbs_repository);
if let Some(ns) = &pbs_args.namespace {
println!("PBS namespace: {}", ns);
}
println!("PBS fingerprint: {}", pbs_args.fingerprint);
println!("compress: {}", pbs_args.compress);
println!("encrypt: {}", pbs_args.encrypt);
let start_transfer_time = SystemTime::now();
upload_vma_file(pbs_args, &args.vma_args)?;
let transfer_duration = SystemTime::now().duration_since(start_transfer_time)?;
let total_seconds = transfer_duration.as_secs();
let minutes = total_seconds / 60;
let seconds = total_seconds % 60;
let milliseconds = transfer_duration.as_millis() % 1000;
println!("Backup finished within {minutes} minutes, {seconds} seconds and {milliseconds} ms");
Ok(())
}
fn upload_vma_file(pbs_args: &PbsArgs, backup_args: &VmaBackupArgs) -> Result<(), Error> {
let vma_file: Box<dyn BufRead> = match &backup_args.vma_file_path {
Some(vma_file_path) => match File::open(vma_file_path) { Some(vma_file_path) => match File::open(vma_file_path) {
Err(why) => return Err(anyhow!("Couldn't open file: {}", why)), Err(why) => return Err(anyhow!("Couldn't open file: {}", why)),
Ok(file) => Box::new(BufReader::new(file)), Ok(file) => Box::new(BufReader::new(file)),
@ -454,7 +490,7 @@ pub fn backup_vma_to_pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> {
}; };
let vma_reader = VmaReader::new(vma_file)?; let vma_reader = VmaReader::new(vma_file)?;
let pbs = create_pbs_backup_task(&args)?; let pbs = create_pbs_backup_task(pbs_args, backup_args)?;
defer! { defer! {
proxmox_backup_disconnect(pbs); proxmox_backup_disconnect(pbs);
@ -467,10 +503,6 @@ pub fn backup_vma_to_pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> {
handle_pbs_error(pbs_err, "proxmox_backup_connect")?; handle_pbs_error(pbs_err, "proxmox_backup_connect")?;
} }
println!("Connected to Proxmox Backup Server");
let start_transfer_time = SystemTime::now();
upload_configs(&vma_reader, pbs)?; upload_configs(&vma_reader, pbs)?;
upload_block_devices(vma_reader, pbs)?; upload_block_devices(vma_reader, pbs)?;
@ -478,24 +510,23 @@ pub fn backup_vma_to_pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> {
handle_pbs_error(pbs_err, "proxmox_backup_finish")?; handle_pbs_error(pbs_err, "proxmox_backup_finish")?;
} }
if args.notes.is_some() || args.log_file_path.is_some() { if backup_args.notes.is_some() || backup_args.log_file_path.is_some() {
let (client, store, request_args) = pbs_client_setup(&args)?; let (client, store, request_args) = pbs_client_setup(pbs_args, backup_args)?;
if args.log_file_path.is_some() { if let Some(log_file_path) = &backup_args.log_file_path {
upload_log(&client, &args, &store, request_args.clone())?; upload_log(
&client,
log_file_path,
pbs_args,
&store,
request_args.clone(),
)?;
} }
if let Some(notes) = args.notes { if let Some(notes) = &backup_args.notes {
set_notes(&client, &notes, &store, request_args)?; set_notes(&client, notes, &store, request_args)?;
} }
} }
let transfer_duration = SystemTime::now().duration_since(start_transfer_time)?;
let total_seconds = transfer_duration.as_secs();
let minutes = total_seconds / 60;
let seconds = total_seconds % 60;
let milliseconds = transfer_duration.as_millis() % 1000;
println!("Backup finished within {minutes} minutes, {seconds} seconds and {milliseconds} ms");
Ok(()) Ok(())
} }