]> git.proxmox.com Git - proxmox-backup.git/commitdiff
close #4763: client: add command to forget backup group
authorGabriel Goller <g.goller@proxmox.com>
Mon, 3 Jun 2024 08:43:11 +0000 (10:43 +0200)
committerWolfgang Bumiller <w.bumiller@proxmox.com>
Wed, 19 Jun 2024 09:32:28 +0000 (11:32 +0200)
Add the command `proxmox-backup-client group forget <group>` so
that we can forget (delete) whole groups with all the containing
snapshots.
To avoid printing full datastore paths (which are in the error messages)
we filter out the most common one (group not found) and rephrase it.

Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
[WB: rebased & sorted import statements in client's main.rs]
[WB: replace extract_repository_from_value with
     remove_repository_from_value since the parameter is rejected on
     the remote side]
Signed-off-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
proxmox-backup-client/src/group.rs [new file with mode: 0644]
proxmox-backup-client/src/main.rs

diff --git a/proxmox-backup-client/src/group.rs b/proxmox-backup-client/src/group.rs
new file mode 100644 (file)
index 0000000..67f26e2
--- /dev/null
@@ -0,0 +1,88 @@
+use anyhow::{bail, Error};
+use serde_json::Value;
+
+use proxmox_router::cli::{CliCommand, CliCommandMap, Confirmation};
+use proxmox_schema::api;
+
+use crate::{
+    complete_backup_group, complete_namespace, complete_repository, merge_group_into,
+    REPO_URL_SCHEMA,
+};
+use pbs_api_types::{BackupGroup, BackupNamespace};
+use pbs_client::tools::{connect, remove_repository_from_value};
+
+pub fn group_mgmt_cli() -> CliCommandMap {
+    CliCommandMap::new().insert(
+        "forget",
+        CliCommand::new(&API_METHOD_FORGET_GROUP)
+            .arg_param(&["group"])
+            .completion_cb("ns", complete_namespace)
+            .completion_cb("repository", complete_repository)
+            .completion_cb("group", complete_backup_group),
+    )
+}
+
+#[api(
+    input: {
+        properties: {
+            group: {
+                type: String,
+                description: "Backup group",
+            },
+            repository: {
+                schema: REPO_URL_SCHEMA,
+                optional: true,
+            },
+            ns: {
+                type: BackupNamespace,
+                optional: true,
+            },
+        }
+    }
+)]
+/// Forget (remove) backup snapshots.
+async fn forget_group(group: String, mut param: Value) -> Result<(), Error> {
+    let backup_group: BackupGroup = group.parse()?;
+    let repo = remove_repository_from_value(&mut param)?;
+    let client = connect(&repo)?;
+
+    let mut api_param = param;
+    merge_group_into(api_param.as_object_mut().unwrap(), backup_group.clone());
+
+    let path = format!("api2/json/admin/datastore/{}/snapshots", repo.store());
+    let result = client.get(&path, Some(api_param.clone())).await?;
+    let snapshots = result["data"].as_array().unwrap().len();
+
+    let confirmation = Confirmation::query_with_default(
+        format!(
+            "Delete group \"{}\" with {} snapshot(s)?",
+            backup_group, snapshots
+        )
+        .as_str(),
+        Confirmation::No,
+    )?;
+    if confirmation.is_yes() {
+        let path = format!("api2/json/admin/datastore/{}/groups", repo.store());
+        if let Err(err) = client.delete(&path, Some(api_param)).await {
+            // "ENOENT: No such file or directory" is part of the error returned when the group
+            // has not been found. The full error contains the full datastore path and we would
+            // like to avoid printing that to the console. Checking if it exists before deleting
+            // the group doesn't work because we currently do not differentiate between an empty
+            // and a nonexistent group. This would make it impossible to remove empty groups.
+            if err
+                .root_cause()
+                .to_string()
+                .contains("ENOENT: No such file or directory")
+            {
+                bail!("Unable to find backup group!");
+            } else {
+                bail!(err);
+            }
+        }
+        println!("Successfully deleted group!");
+    } else {
+        println!("Abort.");
+    }
+
+    Ok(())
+}
index f1c7fbf933ab5a75f686309f1812f27c60887f6a..5f694b11ed7196e7bf4d46a1e8ea4459303c6065 100644 (file)
@@ -63,20 +63,29 @@ use pbs_key_config::{decrypt_key, rsa_encrypt_key_config, KeyConfig};
 use pbs_tools::crypt_config::CryptConfig;
 use pbs_tools::json;
 
+pub mod key;
+pub mod namespace;
+
 mod benchmark;
 pub use benchmark::*;
-mod mount;
-pub use mount::*;
-mod task;
-pub use task::*;
+
 mod catalog;
 pub use catalog::*;
-mod snapshot;
-pub use snapshot::*;
+
+mod group;
+pub use group::*;
+
 mod helper;
 pub(crate) use helper::*;
-pub mod key;
-pub mod namespace;
+
+mod mount;
+pub use mount::*;
+
+mod snapshot;
+pub use snapshot::*;
+
+mod task;
+pub use task::*;
 
 fn record_repository(repo: &BackupRepository) {
     let base = match BaseDirectories::with_prefix("proxmox-backup") {
@@ -2017,6 +2026,7 @@ fn main() {
         .insert("benchmark", benchmark_cmd_def)
         .insert("change-owner", change_owner_cmd_def)
         .insert("namespace", namespace::cli_map())
+        .insert("group", group_mgmt_cli())
         .alias(&["files"], &["snapshot", "files"])
         .alias(&["forget"], &["snapshot", "forget"])
         .alias(&["upload-log"], &["snapshot", "upload-log"])