From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from gate001.proxmox.com (gate001.proxmox.com [45.144.208.40]) by lore.proxmox.com (Postfix) with ESMTPS id B0C191FF0AB for ; Wed, 09 Sep 2026 13:12:43 +0200 (CEST) Received: from gate001.proxmox.com (localhost.localdomain [127.0.0.1]) by gate001.proxmox.com (Proxmox) with ESMTP id 0286921619; Wed, 09 Sep 2026 13:12:35 +0200 (CEST) From: Erik Fastermann To: pbs-devel@lists.proxmox.com Subject: [PATCH proxmox-backup 5/5] report: list the accessible s3 buckets Date: Wed, 9 Sep 2026 13:12:28 +0200 Message-ID: <20260909111228.217535-6-e.fastermann@proxmox.com> X-Mailer: git-send-email 2.47.3 In-Reply-To: <20260909111228.217535-1-e.fastermann@proxmox.com> References: <20260909111228.217535-1-e.fastermann@proxmox.com> MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-SPAM-LEVEL: Spam detection results: 2 AWL -1.450 Adjusted score from AWL reputation of From: address DMARC_MISSING 0.1 Missing DMARC policy KAM_DMARC_STATUS 0.01 Test Rule for DKIM or SPF Failure with Strict Alignment (newer systems) KAM_LAZY_DOMAIN_SECURITY 1 Sending domain does not have any anti-forgery methods KAM_MAILER 2 Automated Mailer Tag Left in Email RDNS_NONE 1.274 Delivered to internal network by a host with no rDNS SPF_HELO_NONE 0.001 SPF: HELO does not publish an SPF Record SPF_NONE 0.001 SPF: sender does not publish an SPF Record Message-ID-Hash: NBKTKLHY2BTBRWXXQT6MVGESZHPO3V2K X-Message-ID-Hash: NBKTKLHY2BTBRWXXQT6MVGESZHPO3V2K X-MailFrom: efastermann@ruth.proxmox.com X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; emergency; member-moderation; nonmember-moderation; administrivia; implicit-dest; max-recipients; max-size; news-moderation; no-subject; digests; suspicious-header CC: Erik Fastermann X-Mailman-Version: 3.3.10 Precedence: list List-Id: Proxmox Backup Server development discussion List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: This shows whether the configured credentials still work, whether the endpoint is reachable and which buckets are reported. Implemented as a report function rather than as a command to set a timeout and query the endpoints concurrently. Signed-off-by: Erik Fastermann --- src/server/report.rs | 78 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 67 insertions(+), 11 deletions(-) diff --git a/src/server/report.rs b/src/server/report.rs index b123c1fa9..a454b52bc 100644 --- a/src/server/report.rs +++ b/src/server/report.rs @@ -1,5 +1,14 @@ +use std::fmt::Write; +use std::time::Duration; + +use futures::future::join_all; + use proxmox_system_report::{FileGroup, FunctionMapping, StaticArgsCommandSpec}; +use crate::api2::config::s3::list_buckets; + +const S3_LIST_BUCKETS_TIMEOUT: Duration = Duration::from_secs(5); + fn project_files() -> Vec { vec![ ("Datastores", vec!["/etc/proxmox-backup/datastore.cfg"]), @@ -53,19 +62,66 @@ fn project_commands() -> Vec { ] } -fn project_function_calls() -> Vec { - vec![("Datastores", || { - let config = match pbs_config::datastore::config() { - Ok((config, _digest)) => config, - _ => return String::from("could not read datastore config"), +fn s3_buckets() -> String { + let config = match pbs_config::s3::config() { + Ok((config, _digest)) => config, + Err(err) => return format!("*error*: could not read s3 config: {err:#}"), + }; + if config.order.is_empty() { + return String::from("*note*: no s3 endpoints configured"); + } + + let listings = proxmox_async::runtime::block_on(async { + join_all( + config + .order + .iter() + .map(|id| tokio::time::timeout(S3_LIST_BUCKETS_TIMEOUT, list_buckets(id.clone()))), + ) + .await + }); + + let mut out = String::new(); + + for (id, listing) in config.order.iter().zip(listings) { + writeln!(out, "\n###### {id}").unwrap(); + + match listing { + Ok(Ok(buckets)) if buckets.is_empty() => { + writeln!(out, "*note*: endpoint reported no buckets").unwrap() + } + Ok(Ok(buckets)) => buckets + .into_iter() + .for_each(|bucket| writeln!(out, "- {}", bucket.name).unwrap()), + Ok(Err(err)) => writeln!(out, "*error*: {err:#}").unwrap(), + Err(_timeout) => writeln!( + out, + "*error*: timed out after {} seconds", + S3_LIST_BUCKETS_TIMEOUT.as_secs(), + ) + .unwrap(), }; + } + + out +} - let mut list = Vec::new(); - for store in config.sections.keys() { - list.push(store.as_str()); - } - format!("```\n{}\n```", list.join(", ")) - })] +fn project_function_calls() -> Vec { + vec![ + ("Datastores", || { + let config = match pbs_config::datastore::config() { + Ok((config, _digest)) => config, + _ => return String::from("could not read datastore config"), + }; + + let mut list = Vec::new(); + for store in config.sections.keys() { + list.push(store.as_str()); + } + format!("```\n{}\n```", list.join(", ")) + }), + ("S3 Buckets", s3_buckets), + ] } pub fn generate_report() -> String { -- 2.47.3