remote scan/completion: add namespace support
Signed-off-by: Fabian Grünbichler <f.gruenbichler@proxmox.com> Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
		
				
					committed by
					
						
						Thomas Lamprecht
					
				
			
			
				
	
			
			
			
						parent
						
							40d495de6d
						
					
				
				
					commit
					d4037525a8
				
			@ -1,6 +1,8 @@
 | 
				
			|||||||
use ::serde::{Deserialize, Serialize};
 | 
					use ::serde::{Deserialize, Serialize};
 | 
				
			||||||
use anyhow::{bail, format_err, Error};
 | 
					use anyhow::{bail, format_err, Error};
 | 
				
			||||||
use hex::FromHex;
 | 
					use hex::FromHex;
 | 
				
			||||||
 | 
					use pbs_api_types::BackupNamespace;
 | 
				
			||||||
 | 
					use pbs_api_types::NamespaceListItem;
 | 
				
			||||||
use proxmox_router::list_subdirs_api_method;
 | 
					use proxmox_router::list_subdirs_api_method;
 | 
				
			||||||
use proxmox_router::SubdirMap;
 | 
					use proxmox_router::SubdirMap;
 | 
				
			||||||
use proxmox_sys::sortable;
 | 
					use proxmox_sys::sortable;
 | 
				
			||||||
@ -18,6 +20,7 @@ use pbs_client::{HttpClient, HttpClientOptions};
 | 
				
			|||||||
use pbs_config::sync;
 | 
					use pbs_config::sync;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use pbs_config::CachedUserInfo;
 | 
					use pbs_config::CachedUserInfo;
 | 
				
			||||||
 | 
					use serde_json::json;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[api(
 | 
					#[api(
 | 
				
			||||||
    input: {
 | 
					    input: {
 | 
				
			||||||
@ -393,13 +396,16 @@ pub async fn scan_remote_datastores(name: String) -> Result<Vec<DataStoreListIte
 | 
				
			|||||||
        permission: &Permission::Privilege(&["remote", "{name}"], PRIV_REMOTE_AUDIT, false),
 | 
					        permission: &Permission::Privilege(&["remote", "{name}"], PRIV_REMOTE_AUDIT, false),
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    returns: {
 | 
					    returns: {
 | 
				
			||||||
        description: "Lists the accessible backup groups in a remote datastore.",
 | 
					        description: "List the accessible namespaces of a remote datastore.",
 | 
				
			||||||
        type: Array,
 | 
					        type: Array,
 | 
				
			||||||
        items: { type: GroupListItem },
 | 
					        items: { type: NamespaceListItem },
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
)]
 | 
					)]
 | 
				
			||||||
/// List groups of a remote.cfg entry's datastore
 | 
					/// List namespaces of a datastore of a remote.cfg entry
 | 
				
			||||||
pub async fn scan_remote_groups(name: String, store: String) -> Result<Vec<GroupListItem>, Error> {
 | 
					pub async fn scan_remote_namespaces(
 | 
				
			||||||
 | 
					    name: String,
 | 
				
			||||||
 | 
					    store: String,
 | 
				
			||||||
 | 
					) -> Result<Vec<NamespaceListItem>, Error> {
 | 
				
			||||||
    let (remote_config, _digest) = pbs_config::remote::config()?;
 | 
					    let (remote_config, _digest) = pbs_config::remote::config()?;
 | 
				
			||||||
    let remote: Remote = remote_config.lookup("remote", &name)?;
 | 
					    let remote: Remote = remote_config.lookup("remote", &name)?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -414,7 +420,75 @@ pub async fn scan_remote_groups(name: String, store: String) -> Result<Vec<Group
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let client = remote_client(&remote, None).await.map_err(map_remote_err)?;
 | 
					    let client = remote_client(&remote, None).await.map_err(map_remote_err)?;
 | 
				
			||||||
    let api_res = client
 | 
					    let api_res = client
 | 
				
			||||||
        .get(&format!("api2/json/admin/datastore/{}/groups", store), None)
 | 
					        .get(
 | 
				
			||||||
 | 
					            &format!("api2/json/admin/datastore/{}/namespace", store),
 | 
				
			||||||
 | 
					            None,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .map_err(map_remote_err)?;
 | 
				
			||||||
 | 
					    let parse_res = match api_res.get("data") {
 | 
				
			||||||
 | 
					        Some(data) => serde_json::from_value::<Vec<NamespaceListItem>>(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<BackupNamespace>,
 | 
				
			||||||
 | 
					) -> Result<Vec<GroupListItem>, 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
 | 
					        .await
 | 
				
			||||||
        .map_err(map_remote_err)?;
 | 
					        .map_err(map_remote_err)?;
 | 
				
			||||||
    let parse_res = match api_res.get("data") {
 | 
					    let parse_res = match api_res.get("data") {
 | 
				
			||||||
@ -429,8 +503,13 @@ pub async fn scan_remote_groups(name: String, store: String) -> Result<Vec<Group
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[sortable]
 | 
					#[sortable]
 | 
				
			||||||
const DATASTORE_SCAN_SUBDIRS: SubdirMap =
 | 
					const DATASTORE_SCAN_SUBDIRS: SubdirMap = &[
 | 
				
			||||||
    &[("groups", &Router::new().get(&API_METHOD_SCAN_REMOTE_GROUPS))];
 | 
					    ("groups", &Router::new().get(&API_METHOD_SCAN_REMOTE_GROUPS)),
 | 
				
			||||||
 | 
					    (
 | 
				
			||||||
 | 
					        "namespaces",
 | 
				
			||||||
 | 
					        &Router::new().get(&API_METHOD_SCAN_REMOTE_NAMESPACES),
 | 
				
			||||||
 | 
					    ),
 | 
				
			||||||
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const DATASTORE_SCAN_ROUTER: Router = Router::new()
 | 
					const DATASTORE_SCAN_ROUTER: Router = Router::new()
 | 
				
			||||||
    .get(&list_subdirs_api_method!(DATASTORE_SCAN_SUBDIRS))
 | 
					    .get(&list_subdirs_api_method!(DATASTORE_SCAN_SUBDIRS))
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
use std::collections::HashMap;
 | 
					use std::collections::HashMap;
 | 
				
			||||||
use std::io::{self, Write};
 | 
					use std::io::{self, Write};
 | 
				
			||||||
 | 
					use std::str::FromStr;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use anyhow::Error;
 | 
					use anyhow::Error;
 | 
				
			||||||
use serde_json::{json, Value};
 | 
					use serde_json::{json, Value};
 | 
				
			||||||
@ -10,9 +11,9 @@ use proxmox_sys::fs::CreateOptions;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use pbs_api_types::percent_encoding::percent_encode_component;
 | 
					use pbs_api_types::percent_encoding::percent_encode_component;
 | 
				
			||||||
use pbs_api_types::{
 | 
					use pbs_api_types::{
 | 
				
			||||||
    GroupFilter, RateLimitConfig, SyncJobConfig, DATASTORE_SCHEMA, GROUP_FILTER_LIST_SCHEMA,
 | 
					    BackupNamespace, GroupFilter, RateLimitConfig, SyncJobConfig, DATASTORE_SCHEMA,
 | 
				
			||||||
    IGNORE_VERIFIED_BACKUPS_SCHEMA, REMOTE_ID_SCHEMA, REMOVE_VANISHED_BACKUPS_SCHEMA, UPID_SCHEMA,
 | 
					    GROUP_FILTER_LIST_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA, REMOTE_ID_SCHEMA,
 | 
				
			||||||
    VERIFICATION_OUTDATED_AFTER_SCHEMA,
 | 
					    REMOVE_VANISHED_BACKUPS_SCHEMA, UPID_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
use pbs_client::{display_task_log, view_task_result};
 | 
					use pbs_client::{display_task_log, view_task_result};
 | 
				
			||||||
use pbs_config::sync;
 | 
					use pbs_config::sync;
 | 
				
			||||||
@ -502,6 +503,14 @@ fn get_remote_store(param: &HashMap<String, String>) -> Option<(String, String)>
 | 
				
			|||||||
    None
 | 
					    None
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn get_remote_ns(param: &HashMap<String, String>) -> Option<BackupNamespace> {
 | 
				
			||||||
 | 
					    if let Some(ns_str) = param.get("remote-ns") {
 | 
				
			||||||
 | 
					        BackupNamespace::from_str(ns_str).ok()
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        None
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// shell completion helper
 | 
					// shell completion helper
 | 
				
			||||||
pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
 | 
					pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
 | 
				
			||||||
    let mut list = Vec::new();
 | 
					    let mut list = Vec::new();
 | 
				
			||||||
@ -520,12 +529,75 @@ pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// shell completion helper
 | 
					// shell completion helper
 | 
				
			||||||
pub fn complete_remote_datastore_group(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
 | 
					pub fn complete_remote_datastore_namespace(
 | 
				
			||||||
 | 
					    _arg: &str,
 | 
				
			||||||
 | 
					    param: &HashMap<String, String>,
 | 
				
			||||||
 | 
					) -> Vec<String> {
 | 
				
			||||||
    let mut list = Vec::new();
 | 
					    let mut list = Vec::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Some((remote, remote_store)) = get_remote_store(param) {
 | 
					    if let Some((remote, remote_store)) = get_remote_store(param) {
 | 
				
			||||||
        if let Ok(data) = proxmox_async::runtime::block_on(async move {
 | 
					        if let Ok(data) = proxmox_async::runtime::block_on(async move {
 | 
				
			||||||
            crate::api2::config::remote::scan_remote_groups(remote.clone(), remote_store.clone())
 | 
					            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<String, String>,
 | 
				
			||||||
 | 
					) -> Vec<String> {
 | 
				
			||||||
 | 
					    let mut list = Vec::new();
 | 
				
			||||||
 | 
					    let mut rpcenv = CliEnvironment::new();
 | 
				
			||||||
 | 
					    rpcenv.set_auth_id(Some(String::from("root@pam")));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut job: Option<SyncJobConfig> = 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<String, String>) -> Vec<String> {
 | 
				
			||||||
 | 
					    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
 | 
					            .await
 | 
				
			||||||
        }) {
 | 
					        }) {
 | 
				
			||||||
            for item in data {
 | 
					            for item in data {
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user