From: Christian Ebner <c.ebner@proxmox.com>
To: pbs-devel@lists.proxmox.com
Subject: [pbs-devel] [PATCH proxmox-backup 6/7] datastore: add helper to get s3 client from datastore config
Date: Mon, 28 Jul 2025 14:40:05 +0200 [thread overview]
Message-ID: <20250728124006.868999-7-c.ebner@proxmox.com> (raw)
In-Reply-To: <20250728124006.868999-1-c.ebner@proxmox.com>
Refactors the currently duplicate code to get the s3 client based on
a datastore config into a dedicated helper associated function of the
datastore. This can then further be used to get the s3 client for
datastore deletion when all data are requested to be removed.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
pbs-datastore/src/datastore.rs | 34 ++++++++++
src/api2/config/datastore.rs | 119 ++++++++++-----------------------
2 files changed, 71 insertions(+), 82 deletions(-)
diff --git a/pbs-datastore/src/datastore.rs b/pbs-datastore/src/datastore.rs
index a6849ecf4..3bc3aab0d 100644
--- a/pbs-datastore/src/datastore.rs
+++ b/pbs-datastore/src/datastore.rs
@@ -2382,4 +2382,38 @@ impl DataStore {
Ok(())
}
+
+ pub fn s3_client_and_backend_from_datastore_config(
+ datastore_config: &DataStoreConfig,
+ ) -> Result<(DatastoreBackendType, Option<S3Client>), Error> {
+ let backend_config: DatastoreBackendConfig =
+ datastore_config.backend.as_deref().unwrap_or("").parse()?;
+ let backend_type = backend_config.ty.unwrap_or_default();
+
+ if backend_type != DatastoreBackendType::S3 {
+ return Ok((backend_type, None));
+ }
+
+ let s3_client_id = backend_config
+ .client
+ .as_ref()
+ .ok_or_else(|| format_err!("missing required client"))?;
+ let bucket = backend_config
+ .bucket
+ .clone()
+ .ok_or_else(|| format_err!("missing required bucket"))?;
+ let (config, _config_digest) =
+ pbs_config::s3::config().context("failed to get s3 config")?;
+ let client_config: S3ClientConf = config
+ .lookup(S3_CFG_TYPE_ID, s3_client_id)
+ .with_context(|| format!("no '{s3_client_id}' in config"))?;
+ let options = S3ClientOptions::from_config(
+ client_config.config,
+ client_config.secret_key,
+ bucket,
+ datastore_config.name.to_owned(),
+ );
+ let s3_client = S3Client::new(options).context("failed to create s3 client")?;
+ Ok((backend_type, Some(s3_client)))
+ }
}
diff --git a/src/api2/config/datastore.rs b/src/api2/config/datastore.rs
index d24323152..1b56fd276 100644
--- a/src/api2/config/datastore.rs
+++ b/src/api2/config/datastore.rs
@@ -2,25 +2,23 @@ use std::path::{Path, PathBuf};
use std::sync::Arc;
use ::serde::{Deserialize, Serialize};
-use anyhow::{bail, format_err, Context, Error};
+use anyhow::{bail, Context, Error};
use hex::FromHex;
use http_body_util::BodyExt;
use serde_json::Value;
use tracing::{info, warn};
use proxmox_router::{http_bail, Permission, Router, RpcEnvironment, RpcEnvironmentType};
-use proxmox_s3_client::{S3Client, S3ClientConf, S3ClientOptions};
use proxmox_schema::{api, param_bail, ApiType};
use proxmox_section_config::SectionConfigData;
use proxmox_uuid::Uuid;
use pbs_api_types::{
- Authid, DataStoreConfig, DataStoreConfigUpdater, DatastoreBackendConfig, DatastoreBackendType,
- DatastoreNotify, DatastoreTuning, KeepOptions, MaintenanceMode, Operation, PruneJobConfig,
- PruneJobOptions, DATASTORE_SCHEMA, PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT,
- PRIV_DATASTORE_MODIFY, PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA, UPID_SCHEMA,
+ Authid, DataStoreConfig, DataStoreConfigUpdater, DatastoreBackendType, DatastoreNotify,
+ DatastoreTuning, KeepOptions, MaintenanceMode, Operation, PruneJobConfig, PruneJobOptions,
+ DATASTORE_SCHEMA, PRIV_DATASTORE_ALLOCATE, PRIV_DATASTORE_AUDIT, PRIV_DATASTORE_MODIFY,
+ PRIV_SYS_MODIFY, PROXMOX_CONFIG_DIGEST_SCHEMA, UPID_SCHEMA,
};
-use pbs_config::s3::S3_CFG_TYPE_ID;
use pbs_config::BackupLockGuard;
use pbs_datastore::chunk_store::ChunkStore;
@@ -34,7 +32,7 @@ use crate::api2::config::tape_backup_job::{delete_tape_backup_job, list_tape_bac
use crate::api2::config::verify::delete_verification_job;
use pbs_config::CachedUserInfo;
-use pbs_datastore::{get_datastore_mount_status, DatastoreBackend};
+use pbs_datastore::{get_datastore_mount_status, DataStore, DatastoreBackend};
use proxmox_rest_server::WorkerTask;
use proxmox_s3_client::S3ObjectKey;
@@ -131,54 +129,34 @@ pub(crate) fn do_create_datastore(
.parse_property_string(datastore.tuning.as_deref().unwrap_or(""))?,
)?;
- let backend_config: DatastoreBackendConfig =
- datastore.backend.as_deref().unwrap_or("").parse()?;
- let backend_type = backend_config.ty.unwrap_or_default();
- let backend_s3_client = if backend_type == DatastoreBackendType::S3 {
- let s3_client_id = backend_config
- .client
- .as_ref()
- .ok_or_else(|| format_err!("missing required client"))?;
- let bucket = backend_config
- .bucket
- .clone()
- .ok_or_else(|| format_err!("missing required bucket"))?;
- let (config, _config_digest) =
- pbs_config::s3::config().context("failed to get s3 config")?;
- let config: S3ClientConf = config
- .lookup(S3_CFG_TYPE_ID, s3_client_id)
- .with_context(|| format!("no '{s3_client_id}' in config"))?;
- let options = S3ClientOptions::from_config(
- config.config,
- config.secret_key,
- bucket,
- datastore.name.to_owned(),
- );
- let s3_client = S3Client::new(options).context("failed to create s3 client")?;
-
- if !overwrite_in_use {
- let object_key = S3ObjectKey::try_from(S3_DATASTORE_IN_USE_MARKER)
- .context("failed to generate s3 object key")?;
- if let Some(response) =
- proxmox_async::runtime::block_on(s3_client.get_object(object_key.clone()))
- .context("failed to get in-use marker from bucket")?
- {
- let content = proxmox_async::runtime::block_on(response.content.collect())
- .unwrap_or_default();
- let content = String::from_utf8(content.to_bytes().to_vec()).unwrap_or_default();
- let in_use: InUseContent = serde_json::from_str(&content).unwrap_or_default();
- if let Some(hostname) = in_use.hostname {
- bail!("Bucket already contains datastore in use by host {hostname}");
- } else {
- bail!("Bucket already contains datastore in use");
+ let (backend_type, backend_s3_client) =
+ match DataStore::s3_client_and_backend_from_datastore_config(&datastore)? {
+ (backend_type, Some(s3_client)) => {
+ if !overwrite_in_use {
+ let object_key = S3ObjectKey::try_from(S3_DATASTORE_IN_USE_MARKER)
+ .context("failed to generate s3 object key")?;
+ if let Some(response) =
+ proxmox_async::runtime::block_on(s3_client.get_object(object_key.clone()))
+ .context("failed to get in-use marker from bucket")?
+ {
+ let content = proxmox_async::runtime::block_on(response.content.collect())
+ .unwrap_or_default();
+ let content =
+ String::from_utf8(content.to_bytes().to_vec()).unwrap_or_default();
+ let in_use: InUseContent =
+ serde_json::from_str(&content).unwrap_or_default();
+ if let Some(hostname) = in_use.hostname {
+ bail!("Bucket already contains datastore in use by host {hostname}");
+ } else {
+ bail!("Bucket already contains datastore in use");
+ }
+ }
}
- }
- }
- Some(Arc::new(s3_client))
- } else {
- None
- };
+ (backend_type, Some(Arc::new(s3_client)))
+ }
+ (backend_type, None) => (backend_type, None),
+ };
let unmount_guard = if datastore.backing_device.is_some() {
do_mount_device(datastore.clone())?;
@@ -359,34 +337,11 @@ pub fn create_datastore(
let store_name = config.name.to_string();
- let backend_config: DatastoreBackendConfig = config.backend.as_deref().unwrap_or("").parse()?;
- match backend_config.ty.unwrap_or_default() {
- DatastoreBackendType::Filesystem => (),
- DatastoreBackendType::S3 => {
- let s3_client_id = backend_config
- .client
- .as_ref()
- .ok_or_else(|| format_err!("missing required client"))?;
- let bucket = backend_config
- .bucket
- .clone()
- .ok_or_else(|| format_err!("missing required bucket"))?;
- let (config, _config_digest) =
- pbs_config::s3::config().context("failed to get s3 config")?;
- let config: S3ClientConf = config
- .lookup(S3_CFG_TYPE_ID, s3_client_id)
- .with_context(|| format!("no '{s3_client_id}' in config"))?;
- let options = S3ClientOptions::from_config(
- config.config,
- config.secret_key,
- bucket,
- store_name.clone(),
- );
- let s3_client = S3Client::new(options).context("failed to create s3 client")?;
- // Fine to block since this runs in worker task
- proxmox_async::runtime::block_on(s3_client.head_bucket())
- .context("failed to access bucket")?;
- }
+ if let (_backend, Some(s3_client)) =
+ DataStore::s3_client_and_backend_from_datastore_config(&config)?
+ {
+ proxmox_async::runtime::block_on(s3_client.head_bucket())
+ .context("failed to access bucket")?;
}
WorkerTask::new_thread(
--
2.47.2
_______________________________________________
pbs-devel mailing list
pbs-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
next prev parent reply other threads:[~2025-07-28 12:40 UTC|newest]
Thread overview: 11+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-07-28 12:39 [pbs-devel] [PATCH proxmox-backup 0/7] remove objects from s3 backend on datastore destroy Christian Ebner
2025-07-28 12:40 ` [pbs-devel] [PATCH proxmox-backup 1/7] tree wide: fix useless borrow warnings Christian Ebner
2025-07-28 12:40 ` [pbs-devel] [PATCH proxmox-backup 2/7] client: backup writer: elide lifetime which can be auto inferred Christian Ebner
2025-07-28 12:40 ` [pbs-devel] [PATCH proxmox-backup 3/7] api: tape: fix clippy warning on map iteration Christian Ebner
2025-07-28 12:40 ` [pbs-devel] [PATCH proxmox-backup 4/7] datastore: fix clippy warning checking file extension Christian Ebner
2025-07-28 12:40 ` [pbs-devel] [PATCH proxmox-backup 5/7] datastore: fix clippy warning to use ? on option Christian Ebner
2025-07-28 12:40 ` Christian Ebner [this message]
2025-07-28 12:40 ` [pbs-devel] [PATCH proxmox-backup 7/7] datastore: delete all objects on datastore destroy with remove data Christian Ebner
2025-07-29 13:53 ` [pbs-devel] [PATCH proxmox-backup 0/7] remove objects from s3 backend on datastore destroy Lukas Wagner
2025-07-29 16:54 ` Christian Ebner
2025-07-30 7:59 ` [pbs-devel] superseded: " Christian Ebner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250728124006.868999-7-c.ebner@proxmox.com \
--to=c.ebner@proxmox.com \
--cc=pbs-devel@lists.proxmox.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.