From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from firstgate.proxmox.com (firstgate.proxmox.com [212.224.123.68]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by lists.proxmox.com (Postfix) with ESMTPS id 656D465E93 for ; Thu, 5 Nov 2020 12:12:41 +0100 (CET) Received: from firstgate.proxmox.com (localhost [127.0.0.1]) by firstgate.proxmox.com (Proxmox) with ESMTP id 104871736A for ; Thu, 5 Nov 2020 12:12:41 +0100 (CET) Received: from proxmox-new.maurer-it.com (proxmox-new.maurer-it.com [212.186.127.180]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits)) (No client certificate requested) by firstgate.proxmox.com (Proxmox) with ESMTPS id DD53917355 for ; Thu, 5 Nov 2020 12:12:36 +0100 (CET) Received: from proxmox-new.maurer-it.com (localhost.localdomain [127.0.0.1]) by proxmox-new.maurer-it.com (Proxmox) with ESMTP id A591D45EB2 for ; Thu, 5 Nov 2020 12:12:36 +0100 (CET) From: =?UTF-8?q?Fabian=20Gr=C3=BCnbichler?= To: pbs-devel@lists.proxmox.com Date: Thu, 5 Nov 2020 12:12:23 +0100 Message-Id: <20201105111226.4105475-3-f.gruenbichler@proxmox.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20201105111226.4105475-1-f.gruenbichler@proxmox.com> References: <20201105111226.4105475-1-f.gruenbichler@proxmox.com> MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 0 AWL 0.024 Adjusted score from AWL reputation of From: address KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment RCVD_IN_DNSWL_MED -2.3 Sender listed at https://www.dnswl.org/, medium trust SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_PASS -0.001 SPF: sender matches SPF record URIBL_BLOCKED 0.001 ADMINISTRATOR NOTICE: The query to URIBL was blocked. See http://wiki.apache.org/spamassassin/DnsBlocklists#dnsbl-block for more information. [pull.rs, proxmox-backup-manager.rs, remote.rs] Subject: [pbs-devel] [PATCH v2 proxmox-backup 2/5] api: refactor remote client and add remote scan X-BeenThere: pbs-devel@lists.proxmox.com X-Mailman-Version: 2.1.29 Precedence: list List-Id: Proxmox Backup Server development discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 05 Nov 2020 11:12:41 -0000 to allow on-demand scanning of remote datastores accessible for the configured remote user. Signed-off-by: Fabian Grünbichler --- Notes: v2: - map remote API errors to 500 to prevent client confusion src/api2/config/remote.rs | 79 ++++++++++++++++++++++++++++++- src/api2/pull.rs | 12 +---- src/bin/proxmox-backup-manager.rs | 26 ++-------- 3 files changed, 84 insertions(+), 33 deletions(-) diff --git a/src/api2/config/remote.rs b/src/api2/config/remote.rs index ffbba1d2..f053ef36 100644 --- a/src/api2/config/remote.rs +++ b/src/api2/config/remote.rs @@ -1,11 +1,13 @@ -use anyhow::{bail, Error}; +use anyhow::{bail, format_err, Error}; use serde_json::Value; use ::serde::{Deserialize, Serialize}; use proxmox::api::{api, ApiMethod, Router, RpcEnvironment, Permission}; +use proxmox::http_err; use proxmox::tools::fs::open_file_locked; use crate::api2::types::*; +use crate::client::{HttpClient, HttpClientOptions}; use crate::config::cached_user_info::CachedUserInfo; use crate::config::remote; use crate::config::acl::{PRIV_REMOTE_AUDIT, PRIV_REMOTE_MODIFY}; @@ -301,10 +303,83 @@ pub fn delete_remote(name: String, digest: Option) -> Result<(), Error> Ok(()) } +/// Helper to get client for remote.cfg entry +pub async fn remote_client(remote: remote::Remote) -> Result { + let options = HttpClientOptions::new() + .password(Some(remote.password.clone())) + .fingerprint(remote.fingerprint.clone()); + + let client = HttpClient::new( + &remote.host, + remote.port.unwrap_or(8007), + &remote.userid, + options)?; + let _auth_info = client.login() // make sure we can auth + .await + .map_err(|err| format_err!("remote connection to '{}' failed - {}", remote.host, err))?; + + Ok(client) +} + + +#[api( + input: { + properties: { + name: { + schema: REMOTE_ID_SCHEMA, + }, + }, + }, + access: { + permission: &Permission::Privilege(&["remote", "{name}"], PRIV_REMOTE_AUDIT, false), + }, + returns: { + description: "List the accessible datastores.", + type: Array, + items: { + description: "Datastore name and description.", + type: DataStoreListItem, + }, + }, +)] +/// List datastores of a remote.cfg entry +pub async fn scan_remote_datastores(name: String) -> Result, Error> { + let (remote_config, _digest) = remote::config()?; + let remote: 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("api2/json/admin/datastore", 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 datastore list data", &name), + }; + + match parse_res { + Ok(parsed) => Ok(parsed), + Err(_) => bail!("Failed to parse remote scan api result."), + } +} + +const SCAN_ROUTER: Router = Router::new() + .get(&API_METHOD_SCAN_REMOTE_DATASTORES); + const ITEM_ROUTER: Router = Router::new() .get(&API_METHOD_READ_REMOTE) .put(&API_METHOD_UPDATE_REMOTE) - .delete(&API_METHOD_DELETE_REMOTE); + .delete(&API_METHOD_DELETE_REMOTE) + .subdirs(&[("scan", &SCAN_ROUTER)]); pub const ROUTER: Router = Router::new() .get(&API_METHOD_LIST_REMOTES) diff --git a/src/api2/pull.rs b/src/api2/pull.rs index d9e9d31d..87015c72 100644 --- a/src/api2/pull.rs +++ b/src/api2/pull.rs @@ -9,7 +9,7 @@ use proxmox::api::{ApiMethod, Router, RpcEnvironment, Permission}; use crate::server::{WorkerTask, jobstate::Job}; use crate::backup::DataStore; -use crate::client::{HttpClient, HttpClientOptions, BackupRepository, pull::pull_store}; +use crate::client::{HttpClient, BackupRepository, pull::pull_store}; use crate::api2::types::*; use crate::config::{ remote, @@ -50,17 +50,9 @@ pub async fn get_pull_parameters( let (remote_config, _digest) = remote::config()?; let remote: remote::Remote = remote_config.lookup("remote", remote)?; - let options = HttpClientOptions::new() - .password(Some(remote.password.clone())) - .fingerprint(remote.fingerprint.clone()); - let src_repo = BackupRepository::new(Some(remote.userid.clone()), Some(remote.host.clone()), remote.port, remote_store.to_string()); - let client = HttpClient::new(&src_repo.host(), src_repo.port(), &src_repo.auth_id(), options)?; - let _auth_info = client.login() // make sure we can auth - .await - .map_err(|err| format_err!("remote connection to '{}' failed - {}", remote.host, err))?; - + let client = crate::api2::config::remote::remote_client(remote).await?; Ok((client, src_repo, tgt_store)) } diff --git a/src/bin/proxmox-backup-manager.rs b/src/bin/proxmox-backup-manager.rs index 7499446b..e52c2f76 100644 --- a/src/bin/proxmox-backup-manager.rs +++ b/src/bin/proxmox-backup-manager.rs @@ -413,29 +413,13 @@ pub fn complete_remote_datastore_name(_arg: &str, param: &HashMap