This is an automated email from the ASF dual-hosted git repository.

chibenwa pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/master by this push:
     new 20bf8083e3 [FIX] S3: Fallback bucket should not apply bucket prefix 
(#3128)
20bf8083e3 is described below

commit 20bf8083e39be4a750f606e576a162087bb3f18a
Author: Benoit TELLIER <[email protected]>
AuthorDate: Sat Aug 22 13:44:35 2026 +0700

    [FIX] S3: Fallback bucket should not apply bucket prefix (#3128)
---
 .../servers/partials/configure/blobstore.adoc      |  2 ++
 .../helm-chart/james/configs/blob.properties       |  4 +++-
 .../sample-configuration/blob.properties           |  4 +++-
 .../sample-configuration/blob.properties           |  4 +++-
 .../blob.properties                                |  4 +++-
 .../sample-configuration/blob.properties           |  4 +++-
 .../blob/objectstorage/aws/S3BlobStoreDAO.java     |  6 ++----
 .../blob/objectstorage/aws/S3BlobStoreDAOTest.java | 24 ++++++++++++++++++++++
 8 files changed, 43 insertions(+), 9 deletions(-)

diff --git a/docs/modules/servers/partials/configure/blobstore.adoc 
b/docs/modules/servers/partials/configure/blobstore.adoc
index 8d0535ddc2..44d992c36e 100644
--- a/docs/modules/servers/partials/configure/blobstore.adoc
+++ b/docs/modules/servers/partials/configure/blobstore.adoc
@@ -166,6 +166,8 @@ Unless a special case like storing blobs of deleted 
messages.
 | objectstorage.namespace.read.fallback
 | BlobStore fallback bucket name. Allows to fallback to a previous used bucket 
when blob is missing from the default one.
 It can be useful when migrating blobs to a new bucket for example.
+This is a read only fallback: James never writes to, nor deletes from, that 
bucket.
+This bucket name is used as is: `objectstorage.bucketPrefix` is not applied to 
it.
 |===
 
 ==== SSE-C Configuration
diff --git 
a/server/apps/distributed-app/helm-chart/james/configs/blob.properties 
b/server/apps/distributed-app/helm-chart/james/configs/blob.properties
index 25cc5793e1..7de3823583 100644
--- a/server/apps/distributed-app/helm-chart/james/configs/blob.properties
+++ b/server/apps/distributed-app/helm-chart/james/configs/blob.properties
@@ -42,7 +42,9 @@ cache.sizeThresholdInBytes=16 KiB
 objectstorage.namespace=james-${env:JAMES_BUCKET_SUFFIX}
 
 # Fallback bucket name
-# Optional, read this bucket when default bukcket reads fails if configured
+# Optional, read this bucket when reads on the default bucket fail, if 
configured.
+# This is a read only fallback: blobs are never written to nor deleted from 
this bucket.
+# Note: this name is used as is, `objectstorage.bucketPrefix` is not applied 
to it.
 # objectstorage.namespace.read.fallback=james-fallback
 
 # ========================================= ObjectStorage on S3 
=============================================
diff --git a/server/apps/distributed-app/sample-configuration/blob.properties 
b/server/apps/distributed-app/sample-configuration/blob.properties
index 9c21f6e8ce..f5d781b5a3 100644
--- a/server/apps/distributed-app/sample-configuration/blob.properties
+++ b/server/apps/distributed-app/sample-configuration/blob.properties
@@ -88,7 +88,9 @@ cache.enable=false
 # objectstorage.namespace=james
 
 # Fallback bucket name
-# Optional, read this bucket when default bukcket reads fails if configured
+# Optional, read this bucket when reads on the default bucket fail, if 
configured.
+# This is a read only fallback: blobs are never written to nor deleted from 
this bucket.
+# Note: this name is used as is, `objectstorage.bucketPrefix` is not applied 
to it.
 # objectstorage.namespace.read.fallback=james-fallback
 
 # ========================================= ObjectStorage on S3 
=============================================
diff --git 
a/server/apps/distributed-pop3-app/sample-configuration/blob.properties 
b/server/apps/distributed-pop3-app/sample-configuration/blob.properties
index 311103c4f4..b603700394 100644
--- a/server/apps/distributed-pop3-app/sample-configuration/blob.properties
+++ b/server/apps/distributed-pop3-app/sample-configuration/blob.properties
@@ -58,7 +58,9 @@ cache.enable=false
 # objectstorage.namespace=james
 
 # Fallback bucket name
-# Optional, read this bucket when default bukcket reads fails if configured
+# Optional, read this bucket when reads on the default bucket fail, if 
configured.
+# This is a read only fallback: blobs are never written to nor deleted from 
this bucket.
+# Note: this name is used as is, `objectstorage.bucketPrefix` is not applied 
to it.
 # objectstorage.namespace.read.fallback=james-fallback
 
 # ========================================= ObjectStorage on S3 
=============================================
diff --git 
a/server/apps/postgres-app/sample-configuration-distributed/blob.properties 
b/server/apps/postgres-app/sample-configuration-distributed/blob.properties
index 3f210e0acb..25c32dd582 100644
--- a/server/apps/postgres-app/sample-configuration-distributed/blob.properties
+++ b/server/apps/postgres-app/sample-configuration-distributed/blob.properties
@@ -50,7 +50,9 @@ encryption.aes.enable=false
 # objectstorage.namespace=james
 
 # Fallback bucket name
-# Optional, read this bucket when default bukcket reads fails if configured
+# Optional, read this bucket when reads on the default bucket fail, if 
configured.
+# This is a read only fallback: blobs are never written to nor deleted from 
this bucket.
+# Note: this name is used as is, `objectstorage.bucketPrefix` is not applied 
to it.
 # objectstorage.namespace.read.fallback=james-fallback
 
 # ========================================= ObjectStorage on S3 
=============================================
diff --git 
a/server/apps/scaling-pulsar-smtp/sample-configuration/blob.properties 
b/server/apps/scaling-pulsar-smtp/sample-configuration/blob.properties
index 15549d3285..4a1ed250ac 100644
--- a/server/apps/scaling-pulsar-smtp/sample-configuration/blob.properties
+++ b/server/apps/scaling-pulsar-smtp/sample-configuration/blob.properties
@@ -68,7 +68,9 @@ cache.enable=false
 # objectstorage.namespace=james
 
 # Fallback bucket name
-# Optional, read this bucket when default bukcket reads fails if configured
+# Optional, read this bucket when reads on the default bucket fail, if 
configured.
+# This is a read only fallback: blobs are never written to nor deleted from 
this bucket.
+# Note: this name is used as is, `objectstorage.bucketPrefix` is not applied 
to it.
 # objectstorage.namespace.read.fallback=james-fallback
 
 # ========================================= ObjectStorage on S3 
=============================================
diff --git 
a/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java
 
b/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java
index 5176e47429..f6307d1590 100644
--- 
a/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java
+++ 
b/server/blob/blob-s3/src/main/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAO.java
@@ -173,8 +173,7 @@ public class S3BlobStoreDAO implements BlobStoreDAO {
         return getObjectFromStore(bucketName, blobId)
             .onErrorResume(e -> e instanceof NoSuchKeyException || e 
instanceof NoSuchBucketException, e -> {
                 if (fallbackNamespace.isPresent() && 
bucketNameResolver.isNameSpace(bucketName)) {
-                    BucketName resolvedFallbackBucketName = 
bucketNameResolver.resolve(fallbackNamespace.get());
-                    return getObjectFromStore(resolvedFallbackBucketName, 
blobId);
+                    return getObjectFromStore(fallbackNamespace.get(), blobId);
                 }
                 return Mono.error(e);
             });
@@ -229,8 +228,7 @@ public class S3BlobStoreDAO implements BlobStoreDAO {
         return getObjectBytesFromStore(bucketName, blobId)
                 .onErrorResume(e -> e instanceof NoSuchKeyException || e 
instanceof NoSuchBucketException, e -> {
                     if (fallbackNamespace.isPresent() && 
bucketNameResolver.isNameSpace(bucketName)) {
-                        BucketName resolvedFallbackBucketName = 
bucketNameResolver.resolve(fallbackNamespace.get());
-                        return 
getObjectBytesFromStore(resolvedFallbackBucketName, blobId);
+                        return 
getObjectBytesFromStore(fallbackNamespace.get(), blobId);
                     }
                     return Mono.error(e);
                 });
diff --git 
a/server/blob/blob-s3/src/test/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAOTest.java
 
b/server/blob/blob-s3/src/test/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAOTest.java
index 0c271a9e95..53c22c08a7 100644
--- 
a/server/blob/blob-s3/src/test/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAOTest.java
+++ 
b/server/blob/blob-s3/src/test/java/org/apache/james/blob/objectstorage/aws/S3BlobStoreDAOTest.java
@@ -162,6 +162,30 @@ public class S3BlobStoreDAOTest implements 
BlobStoreDAOContract, MetadataAwareBl
         assertThat(bytes).isEqualTo(ELEVEN_KILOBYTES);
     }
 
+    @Test
+    void 
fallbackBucketNameShouldNotBeAffectedByBucketPrefix(DockerAwsS3Container 
dockerAwsS3) {
+        S3BlobStoreConfiguration prefixedConfiguration = 
S3BlobStoreConfiguration.builder()
+            .authConfiguration(AwsS3AuthConfiguration.builder()
+                .endpoint(dockerAwsS3.getEndpoint())
+                .accessKeyId(DockerAwsS3Container.ACCESS_KEY_ID)
+                .secretKey(DockerAwsS3Container.SECRET_ACCESS_KEY)
+                .build())
+            .region(dockerAwsS3.dockerAwsS3().region())
+            .defaultBucketName(BucketName.DEFAULT)
+            .bucketPrefix("prefix-")
+            .fallbackBucketName(Optional.of(fallbackBucket))
+            .build();
+        S3BlobStoreDAO prefixedStore = new S3BlobStoreDAO(s3ClientFactory, 
prefixedConfiguration, new TestBlobId.Factory(), S3RequestOption.DEFAULT);
+
+        TestBlobId blobId = new TestBlobId("id");
+        // Save in the un-prefixed fallback bucket
+        Mono.from(testee.save(fallbackBucket, blobId, 
ELEVEN_KILOBYTES)).block();
+
+        InputStream read = prefixedStore.read(BucketName.DEFAULT, 
blobId).payload();
+
+        
assertThat(read).hasSameContentAs(ELEVEN_KILOBYTES.asInputStream().payload());
+    }
+
     @Test
     void shouldNotReadOnFallbackBucketWhenNotReadingOnDefaultOne() {
         BlobStoreDAO store = testee();


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to