]> git.proxmox.com Git - proxmox-backup.git/blobdiff - proxmox-backup-client/src/main.rs
api-types: add namespace to BackupGroup
[proxmox-backup.git] / proxmox-backup-client / src / main.rs
index 7c022fadf2c6f180d0419687f00e59e6678d9de4..5b5a791500c9ccff894bc61be036921de2c3fc44 100644 (file)
@@ -7,6 +7,7 @@ use std::task::Context;
 
 use anyhow::{bail, format_err, Error};
 use futures::stream::{StreamExt, TryStreamExt};
+use serde::Deserialize;
 use serde_json::{json, Value};
 use tokio::sync::mpsc;
 use tokio_stream::wrappers::ReceiverStream;
@@ -22,8 +23,9 @@ use proxmox_time::{epoch_i64, strftime_local};
 use pxar::accessor::{MaybeReady, ReadAt, ReadAtOperation};
 
 use pbs_api_types::{
-    Authid, CryptMode, Fingerprint, GroupListItem, HumanByte, PruneListItem, PruneOptions,
-    RateLimitConfig, SnapshotListItem, StorageStatus, BACKUP_ID_SCHEMA, BACKUP_TIME_SCHEMA,
+    Authid, BackupDir, BackupGroup, BackupNamespace, BackupPart, BackupType, CryptMode,
+    Fingerprint, GroupListItem, HumanByte, PruneListItem, PruneOptions, RateLimitConfig,
+    SnapshotListItem, StorageStatus, BACKUP_ID_SCHEMA, BACKUP_NAMESPACE_SCHEMA, BACKUP_TIME_SCHEMA,
     BACKUP_TYPE_SCHEMA, TRAFFIC_CONTROL_BURST_SCHEMA, TRAFFIC_CONTROL_RATE_SCHEMA,
 };
 use pbs_client::catalog_shell::Shell;
@@ -45,7 +47,6 @@ use pbs_client::{
     BACKUP_SOURCE_SCHEMA,
 };
 use pbs_config::key_config::{decrypt_key, rsa_encrypt_key_config, KeyConfig};
-use pbs_datastore::backup_info::{BackupDir, BackupGroup};
 use pbs_datastore::catalog::{BackupCatalogWriter, CatalogReader, CatalogWriter};
 use pbs_datastore::chunk_store::verify_chunk_size;
 use pbs_datastore::dynamic_index::{BufferedDynamicReader, DynamicIndexReader};
@@ -135,8 +136,8 @@ async fn api_datastore_list_snapshots(
 
     let mut args = json!({});
     if let Some(group) = group {
-        args["backup-type"] = group.backup_type().into();
-        args["backup-id"] = group.backup_id().into();
+        args["backup-type"] = group.ty.to_string().into();
+        args["backup-id"] = group.id.into();
     }
 
     let mut result = client.get(&path, Some(args)).await?;
@@ -148,26 +149,30 @@ pub async fn api_datastore_latest_snapshot(
     client: &HttpClient,
     store: &str,
     group: BackupGroup,
-) -> Result<(String, String, i64), Error> {
+) -> Result<BackupDir, Error> {
     let list = api_datastore_list_snapshots(client, store, Some(group.clone())).await?;
     let mut list: Vec<SnapshotListItem> = serde_json::from_value(list)?;
 
     if list.is_empty() {
-        bail!(
-            "backup group {:?} does not contain any snapshots.",
-            group.group_path()
-        );
+        bail!("backup group {} does not contain any snapshots.", group);
     }
 
-    list.sort_unstable_by(|a, b| b.backup_time.cmp(&a.backup_time));
+    list.sort_unstable_by(|a, b| b.backup.time.cmp(&a.backup.time));
 
-    let backup_time = list[0].backup_time;
+    Ok((group, list[0].backup.time).into())
+}
 
-    Ok((
-        group.backup_type().to_owned(),
-        group.backup_id().to_owned(),
-        backup_time,
-    ))
+pub async fn dir_or_last_from_group(
+    client: &HttpClient,
+    repo: &BackupRepository,
+    path: &str,
+) -> Result<BackupDir, Error> {
+    match path.parse::<BackupPart>()? {
+        BackupPart::Dir(dir) => Ok(dir),
+        BackupPart::Group(group) => {
+            api_datastore_latest_snapshot(&client, repo.store(), group).await
+        }
+    }
 }
 
 async fn backup_directory<P: AsRef<Path>>(
@@ -260,19 +265,21 @@ async fn list_backup_groups(param: Value) -> Result<Value, Error> {
     record_repository(&repo);
 
     let render_group_path = |_v: &Value, record: &Value| -> Result<String, Error> {
-        let item: GroupListItem = serde_json::from_value(record.to_owned())?;
-        let group = BackupGroup::new(item.backup_type, item.backup_id);
-        Ok(group.group_path().to_str().unwrap().to_owned())
+        let item = GroupListItem::deserialize(record)?;
+        Ok(item.backup.to_string())
     };
 
     let render_last_backup = |_v: &Value, record: &Value| -> Result<String, Error> {
-        let item: GroupListItem = serde_json::from_value(record.to_owned())?;
-        let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.last_backup)?;
-        Ok(snapshot.relative_path().to_str().unwrap().to_owned())
+        let item = GroupListItem::deserialize(record)?;
+        let snapshot = BackupDir {
+            group: item.backup,
+            time: item.last_backup,
+        };
+        Ok(snapshot.to_string())
     };
 
     let render_files = |_v: &Value, record: &Value| -> Result<String, Error> {
-        let item: GroupListItem = serde_json::from_value(record.to_owned())?;
+        let item = GroupListItem::deserialize(record)?;
         Ok(pbs_tools::format::render_backup_file_list(&item.files))
     };
 
@@ -329,8 +336,8 @@ async fn change_backup_owner(group: String, mut param: Value) -> Result<(), Erro
 
     let group: BackupGroup = group.parse()?;
 
-    param["backup-type"] = group.backup_type().into();
-    param["backup-id"] = group.backup_id().into();
+    param["backup-type"] = group.ty.to_string().into();
+    param["backup-id"] = group.id.into();
 
     let path = format!("api2/json/admin/datastore/{}/change-owner", repo.store());
     client.post(&path, Some(param)).await?;
@@ -566,6 +573,10 @@ fn spawn_catalog_upload(
                optional: true,
                default: false,
            },
+           "backup-ns": {
+               schema: BACKUP_NAMESPACE_SCHEMA,
+               optional: true,
+           },
            "backup-type": {
                schema: BACKUP_TYPE_SCHEMA,
                optional: true,
@@ -659,7 +670,15 @@ async fn create_backup(
         .as_str()
         .unwrap_or(proxmox_sys::nodename());
 
-    let backup_type = param["backup-type"].as_str().unwrap_or("host");
+    let backup_namespace: BackupNamespace = match param.get("backup-ns") {
+        Some(ns) => ns
+            .as_str()
+            .ok_or_else(|| format_err!("bad namespace {:?}", ns))?
+            .parse()?,
+        None => BackupNamespace::root(),
+    };
+
+    let backup_type: BackupType = param["backup-type"].as_str().unwrap_or("host").parse()?;
 
     let include_dev = param["include-dev"].as_array();
 
@@ -781,12 +800,13 @@ async fn create_backup(
     let client = connect_rate_limited(&repo, rate_limit)?;
     record_repository(&repo);
 
-    println!(
-        "Starting backup: {}/{}/{}",
+    let snapshot = BackupDir::from((
+        backup_namespace,
         backup_type,
-        backup_id,
-        BackupDir::backup_time_to_string(backup_time)?
-    );
+        backup_id.to_owned(),
+        backup_time,
+    ));
+    println!("Starting backup: {snapshot}");
 
     println!("Client name: {}", proxmox_sys::nodename());
 
@@ -833,9 +853,7 @@ async fn create_backup(
         client,
         crypt_config.clone(),
         repo.store(),
-        backup_type,
-        backup_id,
-        backup_time,
+        &snapshot,
         verbose,
         false,
     )
@@ -879,7 +897,6 @@ async fn create_backup(
         None
     };
 
-    let snapshot = BackupDir::new(backup_type, backup_id, backup_time)?;
     let mut manifest = BackupManifest::new(snapshot);
 
     let mut catalog = None;
@@ -1188,17 +1205,7 @@ async fn restore(param: Value) -> Result<Value, Error> {
 
     let path = json::required_string_param(&param, "snapshot")?;
 
-    let (backup_type, backup_id, backup_time) = if path.matches('/').count() == 1 {
-        let group: BackupGroup = path.parse()?;
-        api_datastore_latest_snapshot(&client, repo.store(), group).await?
-    } else {
-        let snapshot: BackupDir = path.parse()?;
-        (
-            snapshot.group().backup_type().to_owned(),
-            snapshot.group().backup_id().to_owned(),
-            snapshot.backup_time(),
-        )
-    };
+    let backup_dir = dir_or_last_from_group(&client, &repo, &path).await?;
 
     let target = json::required_string_param(&param, "target")?;
     let target = if target == "-" { None } else { Some(target) };
@@ -1221,9 +1228,7 @@ async fn restore(param: Value) -> Result<Value, Error> {
         client,
         crypt_config.clone(),
         repo.store(),
-        &backup_type,
-        &backup_id,
-        backup_time,
+        &backup_dir,
         true,
     )
     .await?;
@@ -1414,8 +1419,8 @@ async fn prune(
     if let Some(dry_run) = dry_run {
         api_param["dry-run"] = dry_run.into();
     }
-    api_param["backup-type"] = group.backup_type().into();
-    api_param["backup-id"] = group.backup_id().into();
+    api_param["backup-type"] = group.ty.to_string().into();
+    api_param["backup-id"] = group.id.into();
 
     let mut result = client.post(&path, Some(api_param)).await?;
 
@@ -1423,8 +1428,7 @@ async fn prune(
 
     let render_snapshot_path = |_v: &Value, record: &Value| -> Result<String, Error> {
         let item: PruneListItem = serde_json::from_value(record.to_owned())?;
-        let snapshot = BackupDir::new(item.backup_type, item.backup_id, item.backup_time)?;
-        Ok(snapshot.relative_path().to_str().unwrap().to_owned())
+        Ok(item.backup.to_string())
     };
 
     let render_prune_action = |v: &Value, _record: &Value| -> Result<String, Error> {