* [PATCH proxmox{,-backup} v2 0/6] fix #7078: Add quirk for providers not supporting deleteObjects
@ 2026-03-04 13:59 Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox v2 1/4] s3-client: parse and return headers for delete object response Christian Ebner
` (5 more replies)
0 siblings, 6 replies; 7+ messages in thread
From: Christian Ebner @ 2026-03-04 13:59 UTC (permalink / raw)
To: pbs-devel
These patches provide a workaround to users which would like to
use the S3 datastore backend with object store API's not implementing
the deleteObjects API method, such as e.g. Google Cloud Storage.
When the quirk is set via the S3 client options, delete objects calls
are performed as individual delete object calls instead.
Patches therefore restructure the s3-client code such that the delete
objects response can be generated and the deletion is performed by
looping over the delete list, performing single object deletions.
On the Proxmox Backup Server side the new, additional quirk is exposed
by reworking the advanced section in the s3 endpoint edit window
to use a fieldset of checkboxes instead of the dropdown selector,
with the intend to improve usability.
Link to the bugtracker issue:
https://bugzilla.proxmox.com/show_bug.cgi?id=7078
Changes since version 1 (thanks @Fabian for review):
- Improve error handling for delete object(s) api calls.
- Retrieved and log delete errors on PBS side.
proxmox:
Christian Ebner (4):
s3-client: parse and return headers for delete object response
s3-client: return dedicated error type for delete object response
s3-client: extend provider quirks by delete objects via delete object
s3-client: return list of errors when deleting by prefix
proxmox-s3-client/src/api_types.rs | 2 +
proxmox-s3-client/src/client.rs | 82 +++++++++++++++++++-----
proxmox-s3-client/src/lib.rs | 2 +
proxmox-s3-client/src/response_reader.rs | 54 ++++++++++++++--
4 files changed, 120 insertions(+), 20 deletions(-)
proxmox-backup:
Christian Ebner (2):
datastore: s3: modify delete objects api and log exposed errors
fix #7078: ui: exponse DeleteObjects via DeleteObject provider quirk
pbs-datastore/src/backup_info.rs | 8 +++--
pbs-datastore/src/datastore.rs | 6 ++--
pbs-datastore/src/s3.rs | 26 ++++++++++++++-
www/window/S3ClientEdit.js | 57 ++++++++++++++++++++++++--------
4 files changed, 77 insertions(+), 20 deletions(-)
Summary over all repositories:
8 files changed, 197 insertions(+), 40 deletions(-)
--
Generated by murpp 0.9.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH proxmox v2 1/4] s3-client: parse and return headers for delete object response
2026-03-04 13:59 [PATCH proxmox{,-backup} v2 0/6] fix #7078: Add quirk for providers not supporting deleteObjects Christian Ebner
@ 2026-03-04 13:59 ` Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox v2 2/4] s3-client: return dedicated error type " Christian Ebner
` (4 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Christian Ebner @ 2026-03-04 13:59 UTC (permalink / raw)
To: pbs-devel
Mimic the response elements from the list objects parsing in the
delete object response.
In preparation for being able to perform delete objects via
individual delete object api calls, if the respective provider quirk
is set in the client options.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
changes since version 1:
- no changes
proxmox-s3-client/src/client.rs | 9 +++++----
proxmox-s3-client/src/response_reader.rs | 21 +++++++++++++++++++--
2 files changed, 24 insertions(+), 6 deletions(-)
diff --git a/proxmox-s3-client/src/client.rs b/proxmox-s3-client/src/client.rs
index 83176b39..3d0af5d6 100644
--- a/proxmox-s3-client/src/client.rs
+++ b/proxmox-s3-client/src/client.rs
@@ -29,8 +29,9 @@ use crate::aws_sign_v4::AWS_SIGN_V4_DATETIME_FORMAT;
use crate::aws_sign_v4::{aws_sign_v4_signature, aws_sign_v4_uri_encode};
use crate::object_key::S3ObjectKey;
use crate::response_reader::{
- CopyObjectResponse, DeleteObjectsResponse, GetObjectResponse, HeadObjectResponse,
- ListBucketsResponse, ListObjectsV2Response, PutObjectResponse, ResponseReader,
+ CopyObjectResponse, DeleteObjectsResponse, DeletedObject, GetObjectResponse,
+ HeadObjectResponse, ListBucketsResponse, ListObjectsV2Response, PutObjectResponse,
+ ResponseReader,
};
/// Default timeout for s3 api requests.
@@ -544,7 +545,7 @@ impl S3Client {
/// Removes an object from a bucket.
/// See reference docs: https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html
- pub async fn delete_object(&self, object_key: S3ObjectKey) -> Result<(), Error> {
+ pub async fn delete_object(&self, object_key: S3ObjectKey) -> Result<DeletedObject, Error> {
let object_key = object_key.to_full_key(&self.options.common_prefix);
let request = Request::builder()
.method(Method::DELETE)
@@ -553,7 +554,7 @@ impl S3Client {
let response = self.send(request, None).await?;
let response_reader = ResponseReader::new(response);
- response_reader.delete_object_response().await
+ response_reader.delete_object_response(object_key).await
}
/// Delete multiple objects from a bucket using a single HTTP request.
diff --git a/proxmox-s3-client/src/response_reader.rs b/proxmox-s3-client/src/response_reader.rs
index 7066c33b..be7c0950 100644
--- a/proxmox-s3-client/src/response_reader.rs
+++ b/proxmox-s3-client/src/response_reader.rs
@@ -361,7 +361,10 @@ impl ResponseReader {
/// Read and parse the delete object response.
///
/// Returns with error if an unexpected status code is encountered.
- pub(crate) async fn delete_object_response(self) -> Result<(), Error> {
+ pub(crate) async fn delete_object_response(
+ self,
+ key: S3ObjectKey,
+ ) -> Result<DeletedObject, Error> {
let (parts, _body) = self.response.into_parts();
match parts.status {
@@ -369,7 +372,21 @@ impl ResponseReader {
status_code => bail!("unexpected status code {status_code}"),
};
- Ok(())
+ let delete_marker = Self::parse_optional_header(
+ HeaderName::from_static("x-amz-delete-marker"),
+ &parts.headers,
+ )?;
+ let delete_marker_version_id = Self::parse_optional_header(
+ HeaderName::from_static("x-amz-version-id"),
+ &parts.headers,
+ )?;
+
+ Ok(DeletedObject {
+ delete_marker,
+ delete_marker_version_id,
+ key: Some(key),
+ version_id: None,
+ })
}
/// Read and parse the delete objects response.
--
2.47.3
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH proxmox v2 2/4] s3-client: return dedicated error type for delete object response
2026-03-04 13:59 [PATCH proxmox{,-backup} v2 0/6] fix #7078: Add quirk for providers not supporting deleteObjects Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox v2 1/4] s3-client: parse and return headers for delete object response Christian Ebner
@ 2026-03-04 13:59 ` Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox v2 3/4] s3-client: extend provider quirks by delete objects via delete object Christian Ebner
` (3 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Christian Ebner @ 2026-03-04 13:59 UTC (permalink / raw)
To: pbs-devel
In order to be able to reuse the delete object API calls as opt-in
replacement for delete objects API calls, but not suppress the
HTTP status code on failure.
Use an enum to distinguish the two cases and implement the Into trait
on it to be albe to easily convert back to an anyhow::Error type.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
changes since version 1:
- not present in previous version
proxmox-s3-client/src/client.rs | 5 ++-
proxmox-s3-client/src/response_reader.rs | 39 +++++++++++++++++++++---
2 files changed, 38 insertions(+), 6 deletions(-)
diff --git a/proxmox-s3-client/src/client.rs b/proxmox-s3-client/src/client.rs
index 3d0af5d6..e536614b 100644
--- a/proxmox-s3-client/src/client.rs
+++ b/proxmox-s3-client/src/client.rs
@@ -554,7 +554,10 @@ impl S3Client {
let response = self.send(request, None).await?;
let response_reader = ResponseReader::new(response);
- response_reader.delete_object_response(object_key).await
+ response_reader
+ .delete_object_response(object_key)
+ .await
+ .map_err(Into::into)
}
/// Delete multiple objects from a bucket using a single HTTP request.
diff --git a/proxmox-s3-client/src/response_reader.rs b/proxmox-s3-client/src/response_reader.rs
index be7c0950..3478ea7c 100644
--- a/proxmox-s3-client/src/response_reader.rs
+++ b/proxmox-s3-client/src/response_reader.rs
@@ -1,6 +1,6 @@
use std::str::FromStr;
-use anyhow::{anyhow, bail, Context, Error};
+use anyhow::{anyhow, bail, format_err, Context, Error};
use http_body_util::BodyExt;
use hyper::body::{Bytes, Incoming};
use hyper::header::HeaderName;
@@ -226,6 +226,26 @@ pub struct Bucket {
pub creation_date: LastModifiedTimestamp,
}
+pub(crate) enum DeleteError {
+ Response(DeleteObjectError),
+ Parsing(Error),
+}
+
+impl Into<Error> for DeleteError {
+ fn into(self) -> Error {
+ match self {
+ Self::Response(delete_error) => {
+ if let Some(code) = delete_error.code {
+ format_err!("unexpected status code: {code}")
+ } else {
+ format_err!("failed to generate error, missing error status code")
+ }
+ }
+ Self::Parsing(error) => error,
+ }
+ }
+}
+
impl ResponseReader {
/// Create a new response reader to parse given response.
pub(crate) fn new(response: Response<Incoming>) -> Self {
@@ -364,22 +384,31 @@ impl ResponseReader {
pub(crate) async fn delete_object_response(
self,
key: S3ObjectKey,
- ) -> Result<DeletedObject, Error> {
+ ) -> Result<DeletedObject, DeleteError> {
let (parts, _body) = self.response.into_parts();
match parts.status {
StatusCode::NO_CONTENT => (),
- status_code => bail!("unexpected status code {status_code}"),
+ status_code => {
+ return Err(DeleteError::Response(DeleteObjectError {
+ code: Some(status_code.to_string()),
+ key: Some(key),
+ message: None,
+ version_id: None,
+ }));
+ }
};
let delete_marker = Self::parse_optional_header(
HeaderName::from_static("x-amz-delete-marker"),
&parts.headers,
- )?;
+ )
+ .map_err(|err| DeleteError::Parsing(err))?;
let delete_marker_version_id = Self::parse_optional_header(
HeaderName::from_static("x-amz-version-id"),
&parts.headers,
- )?;
+ )
+ .map_err(|err| DeleteError::Parsing(err))?;
Ok(DeletedObject {
delete_marker,
--
2.47.3
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH proxmox v2 3/4] s3-client: extend provider quirks by delete objects via delete object
2026-03-04 13:59 [PATCH proxmox{,-backup} v2 0/6] fix #7078: Add quirk for providers not supporting deleteObjects Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox v2 1/4] s3-client: parse and return headers for delete object response Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox v2 2/4] s3-client: return dedicated error type " Christian Ebner
@ 2026-03-04 13:59 ` Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox v2 4/4] s3-client: return list of errors when deleting by prefix Christian Ebner
` (2 subsequent siblings)
5 siblings, 0 replies; 7+ messages in thread
From: Christian Ebner @ 2026-03-04 13:59 UTC (permalink / raw)
To: pbs-devel
Provide a workaround for providers not implementing the delete
objects S3 API method.
If ProviderQuirks::DeleteObjectsViaDeleteObject is set,
S3Client::delete_objects() performs individual
S3Client::delete_object_impl() calls on each provided object key
instead of deleting the keys via the deleteObjects API call.
This can also be used to reduce POST calls in favor of multiple
DELETE calls, which might be charged differently by some S3 object
store providers.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
changes since version 1:
- factor out delete_object_impl and use its return type to better
handle delete object errors
proxmox-s3-client/src/api_types.rs | 2 +
proxmox-s3-client/src/client.rs | 66 +++++++++++++++++++++++++-----
2 files changed, 58 insertions(+), 10 deletions(-)
diff --git a/proxmox-s3-client/src/api_types.rs b/proxmox-s3-client/src/api_types.rs
index 93573fbd..b4fc24e0 100644
--- a/proxmox-s3-client/src/api_types.rs
+++ b/proxmox-s3-client/src/api_types.rs
@@ -87,6 +87,8 @@ pub const S3_BUCKET_NAME_SCHEMA: Schema = StringSchema::new("Bucket name for S3
pub enum ProviderQuirks {
/// Prvider does not support the If-None-Match http header
SkipIfNoneMatchHeader,
+ /// Prvider does not support DeleteObjects API endpoint, use delete object calls instead
+ DeleteObjectsViaDeleteObject,
}
serde_plain::derive_display_from_serialize!(ProviderQuirks);
serde_plain::derive_fromstr_from_deserialize!(ProviderQuirks);
diff --git a/proxmox-s3-client/src/client.rs b/proxmox-s3-client/src/client.rs
index e536614b..5eea4dd3 100644
--- a/proxmox-s3-client/src/client.rs
+++ b/proxmox-s3-client/src/client.rs
@@ -29,9 +29,9 @@ use crate::aws_sign_v4::AWS_SIGN_V4_DATETIME_FORMAT;
use crate::aws_sign_v4::{aws_sign_v4_signature, aws_sign_v4_uri_encode};
use crate::object_key::S3ObjectKey;
use crate::response_reader::{
- CopyObjectResponse, DeleteObjectsResponse, DeletedObject, GetObjectResponse,
- HeadObjectResponse, ListBucketsResponse, ListObjectsV2Response, PutObjectResponse,
- ResponseReader,
+ CopyObjectResponse, DeleteError, DeleteObjectError, DeleteObjectsResponse, DeletedObject,
+ GetObjectResponse, HeadObjectResponse, ListBucketsResponse, ListObjectsV2Response,
+ PutObjectResponse, ResponseReader,
};
/// Default timeout for s3 api requests.
@@ -546,18 +546,31 @@ impl S3Client {
/// Removes an object from a bucket.
/// See reference docs: https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html
pub async fn delete_object(&self, object_key: S3ObjectKey) -> Result<DeletedObject, Error> {
+ self.delete_object_impl(object_key)
+ .await
+ .map_err(Into::into)
+ }
+
+ async fn delete_object_impl(
+ &self,
+ object_key: S3ObjectKey,
+ ) -> Result<DeletedObject, DeleteError> {
let object_key = object_key.to_full_key(&self.options.common_prefix);
let request = Request::builder()
.method(Method::DELETE)
- .uri(self.build_uri(&object_key, &[])?)
- .body(Body::empty())?;
+ .uri(
+ self.build_uri(&object_key, &[])
+ .map_err(|err| DeleteError::Parsing(err))?,
+ )
+ .body(Body::empty())
+ .map_err(|err| DeleteError::Parsing(err.into()))?;
- let response = self.send(request, None).await?;
- let response_reader = ResponseReader::new(response);
- response_reader
- .delete_object_response(object_key)
+ let response = self
+ .send(request, None)
.await
- .map_err(Into::into)
+ .map_err(|err| DeleteError::Parsing(err))?;
+ let response_reader = ResponseReader::new(response);
+ response_reader.delete_object_response(object_key).await
}
/// Delete multiple objects from a bucket using a single HTTP request.
@@ -570,6 +583,39 @@ impl S3Client {
return Ok(DeleteObjectsResponse::default());
}
+ if self
+ .options
+ .provider_quirks
+ .contains(&ProviderQuirks::DeleteObjectsViaDeleteObject)
+ {
+ let mut response = DeleteObjectsResponse::default();
+ response.deleted = Some(Vec::with_capacity(object_keys.len()));
+
+ for object_key in object_keys {
+ match self.delete_object_impl(object_key.clone()).await {
+ Ok(deleted_object) => {
+ let deleted = response.deleted.get_or_insert(Vec::new());
+ deleted.push(deleted_object);
+ }
+ Err(err) => {
+ let errors = response.error.get_or_insert(Vec::new());
+ let err = match err {
+ DeleteError::Response(err) => err,
+ DeleteError::Parsing(err) => DeleteObjectError {
+ code: None,
+ key: Some(object_key.clone()),
+ message: Some(format!("{err}")),
+ version_id: None,
+ },
+ };
+ errors.push(err);
+ }
+ }
+ }
+
+ return Ok(response);
+ }
+
let mut body = String::from(r#"<Delete xmlns="http://s3.amazonaws.com/doc/2006-03-01/">"#);
for object_key in object_keys {
body.push_str("<Object><Key>");
--
2.47.3
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH proxmox v2 4/4] s3-client: return list of errors when deleting by prefix
2026-03-04 13:59 [PATCH proxmox{,-backup} v2 0/6] fix #7078: Add quirk for providers not supporting deleteObjects Christian Ebner
` (2 preceding siblings ...)
2026-03-04 13:59 ` [PATCH proxmox v2 3/4] s3-client: extend provider quirks by delete objects via delete object Christian Ebner
@ 2026-03-04 13:59 ` Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox-backup v2 1/2] datastore: s3: modify delete objects api and log exposed errors Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox-backup v2 2/2] fix #7078: ui: exponse DeleteObjects via DeleteObject provider quirk Christian Ebner
5 siblings, 0 replies; 7+ messages in thread
From: Christian Ebner @ 2026-03-04 13:59 UTC (permalink / raw)
To: pbs-devel
Previously only a boolean flag indicating whether there was an error
or not was returned. Now return a list of errors which occurred
when performing object deletion. This allows to better log and
inspect error causes.
Further, since this is now part of the public facing api, expose the
DeleteObjectError type to allow for error handling on the call side.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
changes since version 1:
- not present in previous version
proxmox-s3-client/src/client.rs | 18 +++++++++---------
proxmox-s3-client/src/lib.rs | 2 ++
2 files changed, 11 insertions(+), 9 deletions(-)
diff --git a/proxmox-s3-client/src/client.rs b/proxmox-s3-client/src/client.rs
index 5eea4dd3..435b3992 100644
--- a/proxmox-s3-client/src/client.rs
+++ b/proxmox-s3-client/src/client.rs
@@ -666,12 +666,12 @@ impl S3Client {
/// Delete objects by given key prefix.
/// Requires at least 2 api calls.
- pub async fn delete_objects_by_prefix(&self, prefix: &S3PathPrefix) -> Result<bool, Error> {
+ pub async fn delete_objects_by_prefix(&self, prefix: &S3PathPrefix) -> Result<Vec<DeleteObjectError>, Error> {
// S3 API does not provide a convenient way to delete objects by key prefix.
// List all objects with given group prefix and delete all objects found, so this
// requires at least 2 API calls.
let mut next_continuation_token: Option<String> = None;
- let mut delete_errors = false;
+ let mut delete_errors = Vec::new();
loop {
let list_objects_result = self
.list_objects_v2(prefix, next_continuation_token.as_deref())
@@ -684,8 +684,8 @@ impl S3Client {
.collect();
let response = self.delete_objects(&objects_to_delete).await?;
- if response.error.is_some() {
- delete_errors = true;
+ if let Some(mut errors) = response.error {
+ delete_errors.append(&mut errors);
}
if list_objects_result.is_truncated {
@@ -713,12 +713,12 @@ impl S3Client {
prefix: &S3PathPrefix,
suffix: &str,
excldue_from_parent: &[&str],
- ) -> Result<bool, Error> {
+ ) -> Result<Vec<DeleteObjectError>, Error> {
// S3 API does not provide a convenient way to delete objects by key prefix.
// List all objects with given group prefix and delete all objects found, so this
// requires at least 2 API calls.
let mut next_continuation_token: Option<String> = None;
- let mut delete_errors = false;
+ let mut delete_errors = Vec::new();
let mut prefix_filters = Vec::new();
let mut list_objects = Vec::new();
loop {
@@ -772,9 +772,9 @@ impl S3Client {
.collect();
for objects in objects_to_delete.chunks(1000) {
- let result = self.delete_objects(objects).await?;
- if result.error.is_some() {
- delete_errors = true;
+ let response = self.delete_objects(objects).await?;
+ if let Some(mut errors) = response.error {
+ delete_errors.append(&mut errors);
}
}
diff --git a/proxmox-s3-client/src/lib.rs b/proxmox-s3-client/src/lib.rs
index d02fd0dc..5c8c4b08 100644
--- a/proxmox-s3-client/src/lib.rs
+++ b/proxmox-s3-client/src/lib.rs
@@ -33,3 +33,5 @@ mod object_key;
pub use object_key::S3ObjectKey;
#[cfg(feature = "impl")]
mod response_reader;
+#[cfg(feature = "impl")]
+pub use response_reader::DeleteObjectError;
--
2.47.3
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH proxmox-backup v2 1/2] datastore: s3: modify delete objects api and log exposed errors
2026-03-04 13:59 [PATCH proxmox{,-backup} v2 0/6] fix #7078: Add quirk for providers not supporting deleteObjects Christian Ebner
` (3 preceding siblings ...)
2026-03-04 13:59 ` [PATCH proxmox v2 4/4] s3-client: return list of errors when deleting by prefix Christian Ebner
@ 2026-03-04 13:59 ` Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox-backup v2 2/2] fix #7078: ui: exponse DeleteObjects via DeleteObject provider quirk Christian Ebner
5 siblings, 0 replies; 7+ messages in thread
From: Christian Ebner @ 2026-03-04 13:59 UTC (permalink / raw)
To: pbs-devel
Delete objects api calls now return a list of errors in case there
were some, not just a boolean flag. Adapt the call sides to the new
api interface and log encountered errors.
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
changes since version 1:
- not present in previous version
pbs-datastore/src/backup_info.rs | 8 +++++---
pbs-datastore/src/datastore.rs | 6 ++++--
pbs-datastore/src/s3.rs | 26 +++++++++++++++++++++++++-
3 files changed, 34 insertions(+), 6 deletions(-)
diff --git a/pbs-datastore/src/backup_info.rs b/pbs-datastore/src/backup_info.rs
index 859039cf4..55ca9a49e 100644
--- a/pbs-datastore/src/backup_info.rs
+++ b/pbs-datastore/src/backup_info.rs
@@ -250,14 +250,15 @@ impl BackupGroup {
.to_str()
.ok_or_else(|| format_err!("invalid group path prefix"))?;
let prefix = format!("{S3_CONTENT_PREFIX}/{group_prefix}");
- let delete_objects_error = proxmox_async::runtime::block_on(
+ let delete_objects_errors = proxmox_async::runtime::block_on(
s3_client.delete_objects_by_prefix_with_suffix_filter(
&S3PathPrefix::Some(prefix),
PROTECTED_MARKER_FILENAME,
&[GROUP_OWNER_FILE_NAME, GROUP_NOTES_FILE_NAME],
),
)?;
- if delete_objects_error {
+ if !delete_objects_errors.is_empty() {
+ crate::s3::log_s3_delete_objects_errors(&delete_objects_errors);
bail!("deleting objects failed");
}
}
@@ -636,7 +637,8 @@ impl BackupDir {
let delete_objects_error = proxmox_async::runtime::block_on(
s3_client.delete_objects_by_prefix(&S3PathPrefix::Some(prefix)),
)?;
- if delete_objects_error {
+ if !delete_objects_error.is_empty() {
+ crate::s3::log_s3_delete_objects_errors(&delete_objects_error);
bail!("deleting objects failed");
}
}
diff --git a/pbs-datastore/src/datastore.rs b/pbs-datastore/src/datastore.rs
index 7ad3d917d..ee3da93f7 100644
--- a/pbs-datastore/src/datastore.rs
+++ b/pbs-datastore/src/datastore.rs
@@ -955,7 +955,8 @@ impl DataStore {
&[GROUP_OWNER_FILE_NAME, GROUP_NOTES_FILE_NAME],
),
)?;
- if delete_objects_error {
+ if !delete_objects_error.is_empty() {
+ crate::s3::log_s3_delete_objects_errors(&delete_objects_error);
bail!("deleting objects failed");
}
}
@@ -2403,7 +2404,8 @@ impl DataStore {
let prefix = S3PathPrefix::Some(String::default());
let delete_objects_error =
proxmox_async::runtime::block_on(s3_client.delete_objects_by_prefix(&prefix))?;
- if delete_objects_error {
+ if !delete_objects_error.is_empty() {
+ crate::s3::log_s3_delete_objects_errors(&delete_objects_error);
bail!("deleting objects failed");
}
}
diff --git a/pbs-datastore/src/s3.rs b/pbs-datastore/src/s3.rs
index 90efe2cb5..985758bb9 100644
--- a/pbs-datastore/src/s3.rs
+++ b/pbs-datastore/src/s3.rs
@@ -2,7 +2,7 @@ use std::path::{Path, PathBuf};
use anyhow::{bail, format_err, Error};
-use proxmox_s3_client::S3ObjectKey;
+use proxmox_s3_client::{DeleteObjectError, S3ObjectKey};
/// Object key prefix to group regular datastore contents (not chunks)
pub const S3_CONTENT_PREFIX: &str = ".cnt";
@@ -48,6 +48,30 @@ pub fn object_key_from_digest_with_suffix(
S3ObjectKey::try_from(object_key_string.as_str())
}
+/// Log errors from delete objects api calls
+pub(crate) fn log_s3_delete_objects_errors(errors: &[DeleteObjectError]) {
+ for error in errors {
+ log::error!(
+ "delete object failed: {} {} {}",
+ error
+ .key
+ .as_ref()
+ .map(|key| key.to_string())
+ .unwrap_or_else(|| "None".into()),
+ error
+ .code
+ .as_ref()
+ .map(|s| s.as_str())
+ .unwrap_or_else(|| "None"),
+ error
+ .message
+ .as_ref()
+ .map(|s| s.as_str())
+ .unwrap_or_else(|| "None"),
+ );
+ }
+}
+
#[test]
fn test_object_key_from_path() {
let path = Path::new("vm/100/2025-07-14T14:20:02Z");
--
2.47.3
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH proxmox-backup v2 2/2] fix #7078: ui: exponse DeleteObjects via DeleteObject provider quirk
2026-03-04 13:59 [PATCH proxmox{,-backup} v2 0/6] fix #7078: Add quirk for providers not supporting deleteObjects Christian Ebner
` (4 preceding siblings ...)
2026-03-04 13:59 ` [PATCH proxmox-backup v2 1/2] datastore: s3: modify delete objects api and log exposed errors Christian Ebner
@ 2026-03-04 13:59 ` Christian Ebner
5 siblings, 0 replies; 7+ messages in thread
From: Christian Ebner @ 2026-03-04 13:59 UTC (permalink / raw)
To: pbs-devel
The S3 compatible storage API for Google Cloud Storage does not
implement deleteObjects api method, even if mentioned in the docs,
although erratic [0] (incorrect anchor and docs switched with delete
object ones at the time of last check).
Provide users a workaround by allowing to set the provider quirk to
perform individual deleteObject calls instead of deleteObjects on the
list of objects to delete.
The same quirk might also be used when delete object calls are
monetarily cheaper as compared to the multi object deletion, at the
cost of additional api calls and all the added delays that implies.
As suggested when the provider quirks first got applied in [1],
instead of exposing the additional quirk via an added entry to the
dropdown selector, move to a fieldset with nested checkboxes for
better accessibility.
[0] https://cloud.google.com/distributed-cloud/hosted/docs/latest/gdch/apis/service/storage/storage-s3-rest-api#DeleteObjects
[1] https://lore.proxmox.com/pbs-devel/175440823229.3188344.11268683178675917633.b4-ty@proxmox.com/
Fixes: https://bugzilla.proxmox.com/show_bug.cgi?id=7078
Signed-off-by: Christian Ebner <c.ebner@proxmox.com>
---
changes since version 1:
- no changes
www/window/S3ClientEdit.js | 57 ++++++++++++++++++++++++++++----------
1 file changed, 43 insertions(+), 14 deletions(-)
diff --git a/www/window/S3ClientEdit.js b/www/window/S3ClientEdit.js
index 8862683b8..c7e8ade1f 100644
--- a/www/window/S3ClientEdit.js
+++ b/www/window/S3ClientEdit.js
@@ -135,20 +135,6 @@ Ext.define('PBS.window.S3ClientEdit', {
emptyText: gettext('Unlimited'),
submitAutoScaledSizeUnit: true,
},
- {
- xtype: 'proxmoxKVComboBox',
- name: 'provider-quirks',
- fieldLabel: gettext('Provider Quirks'),
- value: '__default__',
- defaultValue: '__default__',
- comboItems: [
- ['__default__', gettext('None (default)')],
- ['skip-if-none-match-header', gettext('Skip If-None-Match header')],
- ],
- cbind: {
- deleteEmpty: '{!isCreate}',
- },
- },
],
advancedColumn2: [
{
@@ -166,6 +152,27 @@ Ext.define('PBS.window.S3ClientEdit', {
submitAutoScaledSizeUnit: true,
},
],
+ advancedColumnB: [
+ {
+ xtype: 'fieldset',
+ name: 'provider-quirks',
+ fieldLabel: gettext('Provider Quirks'),
+ items: [
+ {
+ xtype: 'checkbox',
+ name: 'skip-if-none-match-header',
+ fieldLabel: gettext('Skip If-None-Match header'),
+ labelWidth: 200,
+ },
+ {
+ xtype: 'checkbox',
+ name: 'delete-objects-via-delete-object',
+ fieldLabel: gettext('DeleteObjects via deleteObject'),
+ labelWidth: 200,
+ },
+ ],
+ },
+ ],
},
getValues: function () {
@@ -193,6 +200,28 @@ Ext.define('PBS.window.S3ClientEdit', {
delete values['secret-key'];
}
+ let quirks = [];
+ ['skip-if-none-match-header', 'delete-objects-via-delete-object'].forEach((quirk) => {
+ if (values[quirk]) {
+ quirks.push(quirk);
+ delete values[quirk];
+ }
+ });
+
+ if (quirks.length > 0) {
+ values['provider-quirks'] = quirks;
+ } else if (!me.isCreate) {
+ values.delete.push('provider-quirks');
+ }
+
return values;
},
+
+ setValues: function (values) {
+ if (values['provider-quirks']) {
+ values['provider-quirks'].forEach((quirk) => (values[quirk] = true));
+ }
+
+ this.callParent(arguments);
+ },
});
--
2.47.3
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2026-03-04 13:59 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-03-04 13:59 [PATCH proxmox{,-backup} v2 0/6] fix #7078: Add quirk for providers not supporting deleteObjects Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox v2 1/4] s3-client: parse and return headers for delete object response Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox v2 2/4] s3-client: return dedicated error type " Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox v2 3/4] s3-client: extend provider quirks by delete objects via delete object Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox v2 4/4] s3-client: return list of errors when deleting by prefix Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox-backup v2 1/2] datastore: s3: modify delete objects api and log exposed errors Christian Ebner
2026-03-04 13:59 ` [PATCH proxmox-backup v2 2/2] fix #7078: ui: exponse DeleteObjects via DeleteObject provider quirk Christian Ebner
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.