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 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<Vec<DataStoreListIte
 | 
			
		||||
        permission: &Permission::Privilege(&["remote", "{name}"], PRIV_REMOTE_AUDIT, false),
 | 
			
		||||
    },
 | 
			
		||||
    returns: {
 | 
			
		||||
        description: "Lists the accessible backup groups in a remote datastore.",
 | 
			
		||||
        description: "List the accessible namespaces of a remote datastore.",
 | 
			
		||||
        type: Array,
 | 
			
		||||
        items: { type: GroupListItem },
 | 
			
		||||
        items: { type: NamespaceListItem },
 | 
			
		||||
    },
 | 
			
		||||
)]
 | 
			
		||||
/// List groups of a remote.cfg entry's datastore
 | 
			
		||||
pub async fn scan_remote_groups(name: String, store: String) -> Result<Vec<GroupListItem>, Error> {
 | 
			
		||||
/// List namespaces of a datastore of a remote.cfg entry
 | 
			
		||||
pub async fn scan_remote_namespaces(
 | 
			
		||||
    name: String,
 | 
			
		||||
    store: String,
 | 
			
		||||
) -> Result<Vec<NamespaceListItem>, 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<Vec<Group
 | 
			
		||||
 | 
			
		||||
    let client = remote_client(&remote, None).await.map_err(map_remote_err)?;
 | 
			
		||||
    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
 | 
			
		||||
        .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<Vec<Group
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[sortable]
 | 
			
		||||
const DATASTORE_SCAN_SUBDIRS: SubdirMap =
 | 
			
		||||
    &[("groups", &Router::new().get(&API_METHOD_SCAN_REMOTE_GROUPS))];
 | 
			
		||||
const DATASTORE_SCAN_SUBDIRS: SubdirMap = &[
 | 
			
		||||
    ("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()
 | 
			
		||||
    .get(&list_subdirs_api_method!(DATASTORE_SCAN_SUBDIRS))
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::io::{self, Write};
 | 
			
		||||
use std::str::FromStr;
 | 
			
		||||
 | 
			
		||||
use anyhow::Error;
 | 
			
		||||
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::{
 | 
			
		||||
    GroupFilter, RateLimitConfig, SyncJobConfig, DATASTORE_SCHEMA, GROUP_FILTER_LIST_SCHEMA,
 | 
			
		||||
    IGNORE_VERIFIED_BACKUPS_SCHEMA, REMOTE_ID_SCHEMA, REMOVE_VANISHED_BACKUPS_SCHEMA, UPID_SCHEMA,
 | 
			
		||||
    VERIFICATION_OUTDATED_AFTER_SCHEMA,
 | 
			
		||||
    BackupNamespace, GroupFilter, RateLimitConfig, SyncJobConfig, DATASTORE_SCHEMA,
 | 
			
		||||
    GROUP_FILTER_LIST_SCHEMA, IGNORE_VERIFIED_BACKUPS_SCHEMA, REMOTE_ID_SCHEMA,
 | 
			
		||||
    REMOVE_VANISHED_BACKUPS_SCHEMA, UPID_SCHEMA, VERIFICATION_OUTDATED_AFTER_SCHEMA,
 | 
			
		||||
};
 | 
			
		||||
use pbs_client::{display_task_log, view_task_result};
 | 
			
		||||
use pbs_config::sync;
 | 
			
		||||
@ -502,6 +503,14 @@ fn get_remote_store(param: &HashMap<String, String>) -> Option<(String, String)>
 | 
			
		||||
    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
 | 
			
		||||
pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String>) -> Vec<String> {
 | 
			
		||||
    let mut list = Vec::new();
 | 
			
		||||
@ -520,13 +529,76 @@ pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap<String, String
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 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();
 | 
			
		||||
 | 
			
		||||
    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<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
 | 
			
		||||
        }) {
 | 
			
		||||
            for item in data {
 | 
			
		||||
                list.push(format!("{}/{}", item.backup.ty, item.backup.id));
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user