Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/next-release/enhancement-s3-41977.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "enhancement",
"category": "s3",
"description": "Add support for creating S3 account regional namespace buckets with aws s3 mb. The command now automatically detects bucket names matching the account-regional naming pattern and sets the required x-amz-bucket-namespace header."
}
5 changes: 4 additions & 1 deletion awscli/customizations/s3/subcommands.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
from awscli.customizations.s3.utils import find_bucket_key, AppendFilter, \
find_dest_path_comp_key, human_readable_size, \
RequestParamsMapper, split_s3_bucket_key, block_unsupported_resources, \
S3PathResolver
S3PathResolver, is_account_regional_namespace_bucket
from awscli.customizations.utils import uni_print
from awscli.customizations.s3.syncstrategy.base import MissingFileSync, \
SizeAndLastModifiedSync, NeverSync, AlwaysSync
Expand Down Expand Up @@ -910,6 +910,9 @@ def _run_main(self, parsed_args, parsed_globals):
bucket_config = {}
bucket_tags = self._create_bucket_tags(parsed_args)

if is_account_regional_namespace_bucket(bucket):
params['BucketNamespace'] = 'account-regional'

# Only set LocationConstraint when the region name is not us-east-1.
# Sending LocationConstraint with value us-east-1 results in an error.
if self.client.meta.region_name != 'us-east-1':
Expand Down
8 changes: 8 additions & 0 deletions awscli/customizations/s3/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,20 @@
r'[a-zA-Z0-9\-]{1,63})[/:]?(?P<key>.*)$'
)

_S3_ACCOUNT_REGIONAL_NAMESPACE_REGEX = re.compile(
r'^.+-\d{12}-[a-z]{2}(-[a-z]+-\d+)?-an$'
)

_S3_OBJECT_LAMBDA_TO_BUCKET_KEY_REGEX = re.compile(
r'^(?P<bucket>arn:(aws).*:s3-object-lambda:[a-z\-0-9]+:[0-9]{12}:'
r'accesspoint[/:][a-zA-Z0-9\-]{1,63})[/:]?(?P<key>.*)$'
)


def is_account_regional_namespace_bucket(bucket):
return bool(_S3_ACCOUNT_REGIONAL_NAMESPACE_REGEX.match(bucket))


def human_readable_size(value):
"""Convert a size in bytes into a human readable format.

Expand Down
25 changes: 25 additions & 0 deletions tests/functional/s3/test_mb_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,31 @@ def test_make_bucket_with_multiple_tags(self):
}
self.assert_params_for_cmd(command, expected_params)

def test_account_regional_namespace_bucket(self):
bucket = 'amzn-s3-demo-bucket-111122223333-us-west-2-an'
command = self.prefix + 's3://%s --region us-west-2' % bucket
self.parsed_responses = [{'Location': 'us-west-2'}]
expected_params = {
'Bucket': bucket,
'BucketNamespace': 'account-regional',
'CreateBucketConfiguration': {'LocationConstraint': 'us-west-2'},
}
self.assert_params_for_cmd(command, expected_params)

def test_account_regional_namespace_bucket_us_east_1(self):
bucket = 'my-bucket-111122223333-us-east-1-an'
command = self.prefix + 's3://%s --region us-east-1' % bucket
expected_params = {
'Bucket': bucket,
'BucketNamespace': 'account-regional',
}
self.assert_params_for_cmd(command, expected_params)

def test_regular_bucket_no_namespace(self):
command = self.prefix + 's3://my-regular-bucket --region us-east-1'
expected_params = {'Bucket': 'my-regular-bucket'}
self.assert_params_for_cmd(command, expected_params)

def test_tags_with_three_arguments_fails(self):
command = self.prefix + 's3://bucket --tags Key1 Value1 ExtraArg'
self.assert_params_for_cmd(
Expand Down
43 changes: 42 additions & 1 deletion tests/unit/customizations/s3/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
ProvideLastModifiedTimeSubscriber, DirectoryCreatorSubscriber,
DeleteSourceObjectSubscriber, DeleteSourceFileSubscriber,
DeleteCopySourceObjectSubscriber, NonSeekableStream, CreateDirectoryError,
S3PathResolver, CaseConflictCleanupSubscriber)
S3PathResolver, CaseConflictCleanupSubscriber,
is_account_regional_namespace_bucket)
from awscli.customizations.s3.results import WarningResult
from tests.unit.customizations.s3 import FakeTransferFuture
from tests.unit.customizations.s3 import FakeTransferFutureMeta
Expand Down Expand Up @@ -360,6 +361,46 @@ def test_outpost_bucket_arn_with_slash_raises_exception(self):
)


class TestIsAccountRegionalNamespaceBucket(unittest.TestCase):
def test_matches_standard_pattern(self):
self.assertTrue(
is_account_regional_namespace_bucket(
'amzn-s3-demo-bucket-111122223333-us-west-2-an'
)
)

def test_matches_different_region(self):
self.assertTrue(
is_account_regional_namespace_bucket(
'my-bucket-123456789012-eu-central-1-an'
)
)

def test_no_match_regular_bucket(self):
self.assertFalse(
is_account_regional_namespace_bucket('my-regular-bucket')
)

def test_no_match_missing_an_suffix(self):
self.assertFalse(
is_account_regional_namespace_bucket(
'bucket-111122223333-us-west-2'
)
)

def test_no_match_wrong_account_id_length(self):
self.assertFalse(
is_account_regional_namespace_bucket(
'bucket-12345-us-west-2-an'
)
)

def test_no_match_express_directory_bucket(self):
self.assertFalse(
is_account_regional_namespace_bucket('bucket--usw2-az1--x-s3')
)


class TestCreateWarning(unittest.TestCase):
def test_create_warning(self):
path = '/foo/'
Expand Down
Loading