diff --git a/src/api2/config/remote.rs b/src/api2/config/remote.rs index edf37b39..d52e3bbe 100644 --- a/src/api2/config/remote.rs +++ b/src/api2/config/remote.rs @@ -1,6 +1,8 @@ use ::serde::{Deserialize, Serialize}; use anyhow::{bail, format_err, Error}; use hex::FromHex; +use pbs_api_types::BackupNamespace; +use pbs_api_types::NamespaceListItem; use proxmox_router::list_subdirs_api_method; use proxmox_router::SubdirMap; use proxmox_sys::sortable; @@ -18,6 +20,7 @@ use pbs_client::{HttpClient, HttpClientOptions}; use pbs_config::sync; use pbs_config::CachedUserInfo; +use serde_json::json; #[api( input: { @@ -393,13 +396,16 @@ pub async fn scan_remote_datastores(name: String) -> Result Result, Error> { +/// List namespaces of a datastore of a remote.cfg entry +pub async fn scan_remote_namespaces( + name: String, + store: String, +) -> Result, Error> { let (remote_config, _digest) = pbs_config::remote::config()?; let remote: Remote = remote_config.lookup("remote", &name)?; @@ -414,7 +420,75 @@ pub async fn scan_remote_groups(name: String, store: String) -> Result serde_json::from_value::>(data.to_owned()), + None => bail!("remote {} did not return any datastore list data", &name), + }; + + match parse_res { + Ok(parsed) => Ok(parsed), + Err(_) => bail!("Failed to parse remote scan api result."), + } +} + +#[api( + input: { + properties: { + name: { + schema: REMOTE_ID_SCHEMA, + }, + store: { + schema: DATASTORE_SCHEMA, + }, + namespace: { + type: BackupNamespace, + optional: true, + }, + }, + }, + access: { + permission: &Permission::Privilege(&["remote", "{name}"], PRIV_REMOTE_AUDIT, false), + }, + returns: { + description: "Lists the accessible backup groups in a remote datastore.", + type: Array, + items: { type: GroupListItem }, + }, +)] +/// List groups of a remote.cfg entry's datastore +pub async fn scan_remote_groups( + name: String, + store: String, + namespace: Option, +) -> 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, None).await.map_err(map_remote_err)?; + + let args = if let Some(ns) = namespace { + Some(json!({ "backup-ns": ns })) + } else { + None + }; + + let api_res = client + .get(&format!("api2/json/admin/datastore/{}/groups", store), args) .await .map_err(map_remote_err)?; let parse_res = match api_res.get("data") { @@ -429,8 +503,13 @@ pub async fn scan_remote_groups(name: String, store: String) -> Result) -> Option<(String, String)> None } +fn get_remote_ns(param: &HashMap) -> Option { + if let Some(ns_str) = param.get("remote-ns") { + BackupNamespace::from_str(ns_str).ok() + } else { + None + } +} + // shell completion helper pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap) -> Vec { let mut list = Vec::new(); @@ -520,13 +529,76 @@ pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap) -> Vec { +pub fn complete_remote_datastore_namespace( + _arg: &str, + param: &HashMap, +) -> Vec { let mut list = Vec::new(); if let Some((remote, remote_store)) = get_remote_store(param) { if let Ok(data) = proxmox_async::runtime::block_on(async move { - crate::api2::config::remote::scan_remote_groups(remote.clone(), remote_store.clone()) - .await + crate::api2::config::remote::scan_remote_namespaces( + remote.clone(), + remote_store.clone(), + ) + .await + }) { + for item in data { + list.push(item.ns.name()); + } + } + } + + list +} + +// shell completion helper +pub fn complete_sync_local_datastore_namespace( + _arg: &str, + param: &HashMap, +) -> Vec { + let mut list = Vec::new(); + let mut rpcenv = CliEnvironment::new(); + rpcenv.set_auth_id(Some(String::from("root@pam"))); + + let mut job: Option = None; + + let store = param.get("store").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.store.clone()); + } + } + None + }); + + if let Some(store) = store { + if let Ok(data) = + crate::api2::admin::namespace::list_namespaces(store, None, None, &mut rpcenv) + { + for item in data { + list.push(item.ns.name()); + } + } + } + + 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) { + let ns = get_remote_ns(param); + if let Ok(data) = proxmox_async::runtime::block_on(async move { + crate::api2::config::remote::scan_remote_groups( + remote.clone(), + remote_store.clone(), + ns, + ) + .await }) { for item in data { list.push(format!("{}/{}", item.backup.ty, item.backup.id));