diff --git a/src/hexkit/protocols/objstorage.py b/src/hexkit/protocols/objstorage.py index 5328740b..fbaa3e91 100644 --- a/src/hexkit/protocols/objstorage.py +++ b/src/hexkit/protocols/objstorage.py @@ -489,7 +489,7 @@ async def _delete_object(self, *, bucket_id: str, object_id: str) -> None: # (is typically only used by the protocol but may also be used in # provider-specific code or overwritten by the provider) - _re_bucket_id = re.compile(r"^[:a-z0-9\-]{3,63}$") + _re_bucket_id = re.compile(r"^[a-z0-9\-]{3,63}$") _re_bucket_id_msg = "must consist of 3-63 lowercase letters, digits or hyphens" @classmethod diff --git a/src/hexkit/providers/s3/provider.py b/src/hexkit/providers/s3/provider.py index e9fe63b1..c7d50bf6 100644 --- a/src/hexkit/providers/s3/provider.py +++ b/src/hexkit/providers/s3/provider.py @@ -42,7 +42,9 @@ __all__ = ["ObjectStorageProtocol", "PresignedPostURL"] # Allow colon character in bucket names to accommodate Ceph multi tenancy S3 -botocore.handlers.VALID_BUCKET = re.compile(r"^[:a-zA-Z0-9.\-_]{1,255}$") +botocore.handlers.VALID_BUCKET = re.compile( + r"^(?:[a-zA-Z0-9_]{1,191}:)?[a-z0-9\-]{3,63}$" +) class S3Config(BaseSettings): @@ -120,6 +122,8 @@ def read_aws_config_ini(aws_config_ini: Path) -> botocore.config.Config: class S3ObjectStorage(ObjectStorageProtocol): """S3-based provider implementing the ObjectStorageProtocol.""" + _re_bucket_id = botocore.handlers.VALID_BUCKET + def __init__( self, *, diff --git a/tests/integration/test_s3.py b/tests/integration/test_s3.py index ea87d9dc..e0a1b262 100644 --- a/tests/integration/test_s3.py +++ b/tests/integration/test_s3.py @@ -105,6 +105,19 @@ async def test_object_existence_checks(s3: S3Fixture, tmp_file: FileObject): # ) +async def test_bucket_name_with_tenant(s3: S3Fixture): + """Test if bucket names containing a tenant work correctly.""" + check_bucket = s3.storage.does_bucket_exist + assert not await check_bucket("non-existing-bucket") + assert not await check_bucket("tenant:non-existing-bucket") + with pytest.raises(ObjectStorageProtocol.BucketIdValidationError): + assert not await check_bucket("tenant:invalid:bucket") + with pytest.raises(ObjectStorageProtocol.BucketIdValidationError): + assert not await check_bucket("tenant-invalid:bucket-valid") + with pytest.raises(ObjectStorageProtocol.BucketIdValidationError): + assert not await check_bucket("tenant_valid:bucket_invalid") + + async def test_get_object_etag(s3: S3Fixture, tmp_file: FileObject): # noqa: F811 """Test ETag retrieval.""" await s3.populate_file_objects([tmp_file])