[pbs-devel] [PATCH vma-to-pbs v3 2/2] add support for notes and logs
Filip Schauer
f.schauer at proxmox.com
Wed Jul 10 16:57:32 CEST 2024
Allow the user to specify a notes file and a log file to associate with
the backup
Signed-off-by: Filip Schauer <f.schauer at proxmox.com>
---
Cargo.toml | 8 ++++
src/main.rs | 16 ++++++++
src/vma2pbs.rs | 104 +++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 128 insertions(+)
diff --git a/Cargo.toml b/Cargo.toml
index 0111362..c62b5e0 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -7,14 +7,22 @@ edition = "2021"
[dependencies]
anyhow = "1.0"
bincode = "1.3"
+hyper = "0.14.5"
pico-args = "0.4"
md5 = "0.7.0"
scopeguard = "1.1.0"
serde = "1.0"
+serde_json = "1.0"
serde-big-array = "0.4.1"
+proxmox-async = "0.4"
proxmox-io = "1.0.1"
proxmox-sys = "0.5.0"
proxmox-time = "2"
+pbs-api-types = { path = "submodules/proxmox-backup-qemu/submodules/proxmox-backup/pbs-api-types" }
+pbs-client = { path = "submodules/proxmox-backup-qemu/submodules/proxmox-backup/pbs-client" }
+pbs-datastore = { path = "submodules/proxmox-backup-qemu/submodules/proxmox-backup/pbs-datastore" }
+pbs-key-config = { path = "submodules/proxmox-backup-qemu/submodules/proxmox-backup/pbs-key-config" }
+pbs-tools = { path = "submodules/proxmox-backup-qemu/submodules/proxmox-backup/pbs-tools" }
proxmox-backup-qemu = { path = "submodules/proxmox-backup-qemu" }
diff --git a/src/main.rs b/src/main.rs
index 2653d3e..de789c1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -37,6 +37,10 @@ Options:
Password file
--key-password-file <KEY_PASSWORD_FILE>
Key password file
+ [--notes-file <NOTES_FILE>]
+ File containing a comment/notes
+ [--log-file <LOG_FILE>]
+ Log file
-h, --help
Print help
-V, --version
@@ -93,6 +97,8 @@ fn parse_args() -> Result<BackupVmaToPbsArgs, Error> {
let encrypt = args.contains(["-e", "--encrypt"]);
let password_file: Option<OsString> = args.opt_value_from_str("--password-file")?;
let key_password_file: Option<OsString> = args.opt_value_from_str("--key-password-file")?;
+ let notes_file: Option<OsString> = args.opt_value_from_str("--notes-file")?;
+ let log_file_path: Option<OsString> = args.opt_value_from_str("--log-file")?;
match (encrypt, keyfile.is_some()) {
(true, false) => bail!("--encrypt requires a --keyfile!"),
@@ -170,6 +176,14 @@ fn parse_args() -> Result<BackupVmaToPbsArgs, Error> {
None
};
+ let notes = if let Some(notes_file) = notes_file {
+ let notes = std::fs::read_to_string(notes_file).context("Could not read notes file")?;
+
+ Some(notes)
+ } else {
+ None
+ };
+
let options = BackupVmaToPbsArgs {
vma_file_path: vma_file_path.cloned(),
pbs_repository,
@@ -183,6 +197,8 @@ fn parse_args() -> Result<BackupVmaToPbsArgs, Error> {
fingerprint,
compress,
encrypt,
+ notes,
+ log_file_path,
};
Ok(options)
diff --git a/src/vma2pbs.rs b/src/vma2pbs.rs
index 35abdcd..d2ce437 100644
--- a/src/vma2pbs.rs
+++ b/src/vma2pbs.rs
@@ -8,6 +8,12 @@ use std::ptr;
use std::time::SystemTime;
use anyhow::{anyhow, bail, Error};
+use pbs_api_types::{BackupDir, BackupNamespace, BackupType};
+use pbs_client::{BackupRepository, HttpClient, HttpClientOptions};
+use pbs_datastore::DataBlob;
+use pbs_key_config::decrypt_key;
+use pbs_tools::crypt_config::CryptConfig;
+use proxmox_async::runtime::block_on;
use proxmox_backup_qemu::{
capi_types::ProxmoxBackupHandle, proxmox_backup_add_config, proxmox_backup_close_image,
proxmox_backup_connect, proxmox_backup_disconnect, proxmox_backup_finish,
@@ -16,6 +22,7 @@ use proxmox_backup_qemu::{
};
use proxmox_time::epoch_to_rfc3339;
use scopeguard::defer;
+use serde_json::Value;
use crate::vma::VmaReader;
@@ -34,6 +41,8 @@ pub struct BackupVmaToPbsArgs {
pub fingerprint: String,
pub compress: bool,
pub encrypt: bool,
+ pub notes: Option<String>,
+ pub log_file_path: Option<OsString>,
}
#[derive(Copy, Clone)]
@@ -352,6 +361,89 @@ where
Ok(())
}
+fn pbs_client_setup(args: &BackupVmaToPbsArgs) -> Result<(HttpClient, String, Value), Error> {
+ let repo: BackupRepository = args.pbs_repository.parse()?;
+ let options = HttpClientOptions::new_interactive(
+ Some(args.pbs_password.clone()),
+ Some(args.fingerprint.clone()),
+ );
+ 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 namespace = match &args.namespace {
+ Some(namespace) => BackupNamespace::new(namespace)?,
+ None => BackupNamespace::root(),
+ };
+
+ let mut request_args = serde_json::to_value(backup_dir)?;
+ if !namespace.is_root() {
+ request_args["ns"] = serde_json::to_value(namespace)?;
+ }
+
+ Ok((client, repo.store().to_owned(), request_args))
+}
+
+fn upload_log(
+ client: &HttpClient,
+ args: &BackupVmaToPbsArgs,
+ store: &str,
+ request_args: Value,
+) -> Result<(), Error> {
+ if let Some(log_file_path) = &args.log_file_path {
+ let path = format!("api2/json/admin/datastore/{}/upload-backup-log", store);
+ let data = std::fs::read(log_file_path)?;
+
+ let blob = if args.encrypt {
+ let crypt_config = match &args.keyfile {
+ None => None,
+ Some(keyfile) => {
+ let key = std::fs::read(keyfile)?;
+ let (key, _created, _) = decrypt_key(&key, &|| -> Result<Vec<u8>, Error> {
+ match &args.key_password {
+ Some(key_password) => Ok(key_password.clone().into_bytes()),
+ None => bail!("no key password provided"),
+ }
+ })?;
+ let crypt_config = CryptConfig::new(key)?;
+ 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());
+
+ block_on(async {
+ client
+ .upload("application/octet-stream", body, &path, Some(request_args))
+ .await
+ .unwrap();
+ });
+ }
+
+ Ok(())
+}
+
+fn set_notes(
+ client: &HttpClient,
+ notes: &str,
+ store: &str,
+ mut request_args: Value,
+) -> Result<(), Error> {
+ request_args["notes"] = Value::from(notes);
+ let path = format!("api2/json/admin/datastore/{}/notes", store);
+ block_on(async {
+ client.put(&path, Some(request_args)).await.unwrap();
+ });
+
+ Ok(())
+}
+
pub fn backup_vma_to_pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> {
let vma_file: Box<dyn BufRead> = match &args.vma_file_path {
Some(vma_file_path) => match File::open(vma_file_path) {
@@ -386,6 +478,18 @@ pub fn backup_vma_to_pbs(args: BackupVmaToPbsArgs) -> Result<(), Error> {
handle_pbs_error(pbs_err, "proxmox_backup_finish")?;
}
+ if args.notes.is_some() || args.log_file_path.is_some() {
+ let (client, store, request_args) = pbs_client_setup(&args)?;
+
+ if args.log_file_path.is_some() {
+ upload_log(&client, &args, &store, request_args.clone())?;
+ }
+
+ if let Some(notes) = args.notes {
+ set_notes(&client, ¬es, &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;
--
2.39.2
More information about the pbs-devel
mailing list