Skip to content
Merged
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
4 changes: 4 additions & 0 deletions awscli/customizations/s3/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@
)


def is_account_regional_namespace_bucket(bucket):
return bucket.endswith('-an')


def human_readable_size(value):
"""Convert a size in bytes into a human readable format.
Expand Down
34 changes: 34 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,40 @@ 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 + f's3://{bucket} --region us-west-2'
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 + f's3://{bucket} --region us-east-1'
expected_params = {
'Bucket': bucket,
'BucketNamespace': 'account-regional',
}
self.assert_params_for_cmd(command, expected_params)

def test_account_regional_namespace_short_bucket_name(self):
bucket = 'xyz-an'
command = self.prefix + f's3://{bucket} --region us-east-1'
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
7 changes: 7 additions & 0 deletions tests/integration/customizations/s3/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,13 @@ def setUp(self):
super(BaseS3IntegrationTest, self).setUp()


class TestMakeBucketAccountRegionalNamespace(BaseS3IntegrationTest):
def test_short_an_suffix_sends_namespace_header(self):
p = aws('s3 mb s3://xyz-an')
assert p.rc != 0
assert 'InvalidBucketNamespace' in p.stderr


class TestMoveCommand(BaseS3IntegrationTest):
def assert_mv_local_to_s3(self, bucket_name):
full_path = self.files.create_file('foo.txt', 'this is foo.txt')
Expand Down
41 changes: 40 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,44 @@ 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_matches_short_bucket_name(self):
self.assertTrue(
is_account_regional_namespace_bucket('xyz-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