diff --git a/src/api2/config/remote.rs b/src/api2/config/remote.rs index e3a22813..0e2413a9 100644 --- a/src/api2/config/remote.rs +++ b/src/api2/config/remote.rs @@ -1,4 +1,7 @@ use anyhow::{bail, format_err, Error}; +use proxmox::sortable; +use proxmox_router::SubdirMap; +use proxmox_router::list_subdirs_api_method; use serde_json::Value; use ::serde::{Deserialize, Serialize}; @@ -8,8 +11,8 @@ use proxmox_schema::api; use pbs_client::{HttpClient, HttpClientOptions}; use pbs_api_types::{ REMOTE_ID_SCHEMA, REMOTE_PASSWORD_SCHEMA, Remote, RemoteConfig, RemoteConfigUpdater, - Authid, PROXMOX_CONFIG_DIGEST_SCHEMA, DataStoreListItem, SyncJobConfig, - PRIV_REMOTE_AUDIT, PRIV_REMOTE_MODIFY, + Authid, PROXMOX_CONFIG_DIGEST_SCHEMA, DATASTORE_SCHEMA, GroupListItem, + DataStoreListItem, SyncJobConfig, PRIV_REMOTE_AUDIT, PRIV_REMOTE_MODIFY, }; use pbs_config::sync; @@ -340,8 +343,72 @@ pub async fn scan_remote_datastores(name: String) -> Result Result, Error> { + let (remote_config, _digest) = pbs_config::remote::config()?; + let remote: Remote = remote_config.lookup("remote", &name)?; + + let map_remote_err = |api_err| { + http_err!(INTERNAL_SERVER_ERROR, + "failed to scan remote '{}' - {}", + &name, + api_err) + }; + + let client = remote_client(&remote) + .await + .map_err(map_remote_err)?; + let api_res = client + .get(&format!("api2/json/admin/datastore/{}/groups", store), None) + .await + .map_err(map_remote_err)?; + let parse_res = match api_res.get("data") { + Some(data) => serde_json::from_value::>(data.to_owned()), + None => bail!("remote {} did not return any group list data", &name), + }; + + match parse_res { + Ok(parsed) => Ok(parsed), + Err(_) => bail!("Failed to parse remote scan api result."), + } +} + +#[sortable] +const DATASTORE_SCAN_SUBDIRS: SubdirMap = &[ + ( + "groups", + &Router::new() + .get(&API_METHOD_SCAN_REMOTE_GROUPS) + ), +]; + +const DATASTORE_SCAN_ROUTER: Router = Router::new() + .get(&list_subdirs_api_method!(DATASTORE_SCAN_SUBDIRS)) + .subdirs(DATASTORE_SCAN_SUBDIRS); + const SCAN_ROUTER: Router = Router::new() - .get(&API_METHOD_SCAN_REMOTE_DATASTORES); + .get(&API_METHOD_SCAN_REMOTE_DATASTORES) + .match_all("store", &DATASTORE_SCAN_ROUTER); const ITEM_ROUTER: Router = Router::new() .get(&API_METHOD_READ_REMOTE) diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs index ff256d39..00e02b3f 100644 --- a/src/bin/proxmox-backup-manager.rs +++ b/src/bin/proxmox-backup-manager.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::io::{self, Write}; -use anyhow::{format_err, Error}; +use anyhow::Error; use serde_json::{json, Value}; use proxmox::tools::fs::CreateOptions; @@ -9,10 +9,11 @@ use proxmox_router::{cli::*, RpcEnvironment}; use proxmox_schema::api; use pbs_client::{display_task_log, view_task_result}; +use pbs_config::sync; use pbs_tools::percent_encoding::percent_encode_component; use pbs_tools::json::required_string_param; use pbs_api_types::{ - GroupFilter, + GroupFilter, SyncJobConfig, DATASTORE_SCHEMA, GROUP_FILTER_LIST_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA, REMOTE_ID_SCHEMA, REMOVE_VANISHED_BACKUPS_SCHEMA, UPID_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA, }; @@ -399,6 +400,7 @@ async fn run() -> Result<(), Error> { .completion_cb("local-store", pbs_config::datastore::complete_datastore_name) .completion_cb("remote", pbs_config::remote::complete_remote_name) .completion_cb("remote-store", complete_remote_datastore_name) + .completion_cb("groups", complete_remote_datastore_group_filter) ) .insert( "verify", @@ -441,24 +443,105 @@ fn main() -> Result<(), Error> { pbs_runtime::main(run()) } +fn get_sync_job(id: &String) -> Result { + let (config, _digest) = sync::config()?; + + config.lookup("sync", id) +} + +fn get_remote(param: &HashMap) -> Option { + param + .get("remote") + .map(|r| r.to_owned()) + .or_else(|| { + if let Some(id) = param.get("id") { + if let Ok(job) = get_sync_job(id) { + return Some(job.remote.clone()); + } + } + None + }) +} + +fn get_remote_store(param: &HashMap) -> Option<(String, String)> { + let mut job: Option = None; + + let remote = param + .get("remote") + .map(|r| r.to_owned()) + .or_else(|| { + if let Some(id) = param.get("id") { + job = get_sync_job(id).ok(); + if let Some(ref job) = job { + return Some(job.remote.clone()); + } + } + None + }); + + if let Some(remote) = remote { + let store = param + .get("remote-store") + .map(|r| r.to_owned()) + .or_else(|| job.map(|job| job.remote_store.clone())); + + if let Some(store) = store { + return Some((remote, store)) + } + } + + None +} + // shell completion helper pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap) -> Vec { let mut list = Vec::new(); - let _ = proxmox_lang::try_block!({ - let remote = param.get("remote").ok_or_else(|| format_err!("no remote"))?; + if let Some(remote) = get_remote(param) { + if let Ok(data) = pbs_runtime::block_on(async move { + crate::api2::config::remote::scan_remote_datastores(remote).await + }) { - let data = pbs_runtime::block_on(async move { - crate::api2::config::remote::scan_remote_datastores(remote.clone()).await - })?; - - for item in data { - list.push(item.store); + for item in data { + list.push(item.store); + } } - - Ok(()) - }).map_err(|_err: Error| { /* ignore */ }); + } + + list +} + +// shell completion helper +pub fn complete_remote_datastore_group(_arg: &str, param: &HashMap) -> Vec { + + let mut list = Vec::new(); + + if let Some((remote, remote_store)) = get_remote_store(param) { + if let Ok(data) = pbs_runtime::block_on(async move { + crate::api2::config::remote::scan_remote_groups(remote.clone(), remote_store.clone()).await + }) { + + for item in data { + list.push(format!("{}/{}", item.backup_type, item.backup_id)); + } + } + } + + list +} + +// shell completion helper +pub fn complete_remote_datastore_group_filter(_arg: &str, param: &HashMap) -> Vec { + + let mut list = Vec::new(); + + list.push("regex:".to_string()); + list.push("type:ct".to_string()); + list.push("type:host".to_string()); + list.push("type:vm".to_string()); + + list.extend(complete_remote_datastore_group(_arg, param).iter().map(|group| format!("group:{}", group))); list } diff --git a/src/bin/proxmox_backup_manager/sync.rs b/src/bin/proxmox_backup_manager/sync.rs index dfd8688d..8bf490ea 100644 --- a/src/bin/proxmox_backup_manager/sync.rs +++ b/src/bin/proxmox_backup_manager/sync.rs @@ -89,6 +89,7 @@ pub fn sync_job_commands() -> CommandLineInterface { .completion_cb("store", pbs_config::datastore::complete_datastore_name) .completion_cb("remote", pbs_config::remote::complete_remote_name) .completion_cb("remote-store", crate::complete_remote_datastore_name) + .completion_cb("groups", crate::complete_remote_datastore_group_filter) ) .insert("update", CliCommand::new(&api2::config::sync::API_METHOD_UPDATE_SYNC_JOB) @@ -97,6 +98,7 @@ pub fn sync_job_commands() -> CommandLineInterface { .completion_cb("schedule", pbs_config::datastore::complete_calendar_event) .completion_cb("store", pbs_config::datastore::complete_datastore_name) .completion_cb("remote-store", crate::complete_remote_datastore_name) + .completion_cb("groups", crate::complete_remote_datastore_group_filter) ) .insert("remove", CliCommand::new(&api2::config::sync::API_METHOD_DELETE_SYNC_JOB)