Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a new s3 website command. #482

Merged
merged 4 commits into from
Nov 13, 2013
Merged
Show file tree
Hide file tree
Changes from 3 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
82 changes: 67 additions & 15 deletions awscli/customizations/s3/s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,14 @@ def _create_subcommand_table(self):
for cmd in CMD_DICT.keys():
cmd_specification = CMD_DICT[cmd]
cmd_class = cmd_specification.get('command_class', S3SubCommand)

# If a cmd_class is provided, the we'll try to grab the
# description and usage off of that object, otherwise
# we'll look in the command dict.
description, usage = self._get_command_usage(cmd_class)
subcommand_table[cmd] = cmd_class(
cmd, self._session, cmd_specification['options'],
cmd_specification['description'], cmd_specification['usage'])
cmd_specification.get('description', description),
cmd_specification.get('usage', usage))

self._session.emit('building-operation-table.%s' % self._name,
operation_table=subcommand_table,
Expand All @@ -194,6 +198,10 @@ def _create_subcommand_table(self):
arg_table=None)
return subcommand_table

def _get_command_usage(self, cmd_class):
return (getattr(cmd_class, 'DESCRIPTION', None),
getattr(cmd_class, 'USAGE', None))

def create_help_command(self):
"""
This function returns a help command object with a filled command
Expand All @@ -210,6 +218,8 @@ class S3SubCommand(object):
"""
This is the object corresponding to a S3 subcommand.
"""
DESCRIPTION = None
USAGE = None

def __init__(self, name, session, options, documentation="", usage=""):
"""
Expand Down Expand Up @@ -399,6 +409,41 @@ def _make_size_str(self, size):
return size_str.rjust(10, ' ')


class WebsiteCommand(S3SubCommand):
DESCRIPTION = 'Set the website configuration for a bucket.'
USAGE = 's3://bucket [--index-document|--error-document] value'

def _do_command(self, parsed_args, parsed_globals):
service = self._session.get_service('s3')
endpoint = service.get_endpoint(parsed_globals.region)
operation = service.get_operation('PutBucketWebsite')
bucket = self._get_bucket_name(parsed_args.paths[0])
website_configuration = self._build_website_configuration(parsed_args)
operation.call(endpoint, bucket=bucket,
website_configuration=website_configuration)
return 0

def _build_website_configuration(self, parsed_args):
website_config = {}
if parsed_args.index_document is not None:
website_config['IndexDocument'] = {'Suffix': parsed_args.index_document}
elif parsed_args.error_document is not None:
website_config['ErrorDocument'] = {'Key': parsed_args.error_document}
return website_config

def _get_bucket_name(self, path):
# We support either:
# s3://bucketname
# bucketname
#
# We also strip off the trailing slash if a user
# accidently appends a slash.
if path.startswith('s3://'):
path = path[5:]
if path.endswith('/'):
path = path[:-1]
return path


class S3Parameter(BaseCLIArgument):
"""
Expand Down Expand Up @@ -612,9 +657,7 @@ def _verify_bucket_exists(self, bucket_name):
endpoint = service.get_endpoint(self.parameters['region'])
operation = service.get_operation('ListObjects')
# This will raise an exception if the bucket does not exist.
html_response, response_data = operation.call(endpoint,
bucket=bucket_name,
max_keys=0)
operation.call(endpoint, bucket=bucket_name, max_keys=0)

def check_path_type(self, paths):
"""
Expand Down Expand Up @@ -668,10 +711,8 @@ def check_src_path(self, paths):
src_path += '/' # all prefixes must end with a /
bucket, key = find_bucket_key(src_path)
operation = service.get_operation('ListObjects')
html_response, response_data = operation.call(endpoint,
bucket=bucket,
prefix=key,
delimiter='/')
response_data = operation.call(endpoint, bucket=bucket, prefix=key,
delimiter='/')[1]
check_error(response_data)
contents = response_data['Contents']
common_prefixes = response_data['CommonPrefixes']
Expand Down Expand Up @@ -706,8 +747,8 @@ def check_force(self, parsed_globals):
"""
if 'force' in self.parameters:
if self.parameters['force']:
bucket, key = find_bucket_key(self.parameters['src'][5:])
path = 's3://'+bucket
bucket = find_bucket_key(self.parameters['src'][5:])[0]
path = 's3://' + bucket
try:
del_objects = S3SubCommand('rm', self.session, {'nargs': 1})
del_objects([path, '--recursive'], parsed_globals)
Expand Down Expand Up @@ -777,7 +818,10 @@ def check_region(self, parsed_globals):
'params': [], 'default': 's3://',
'command_class': ListCommand},
'mb': {'options': {'nargs': 1}, 'params': []},
'rb': {'options': {'nargs': 1}, 'params': ['force']}
'rb': {'options': {'nargs': 1}, 'params': ['force']},
'website': {'options': {'nargs': 1},
'params': ['index-document', 'error-document'],
'command_class': WebsiteCommand},
}

add_command_descriptions(CMD_DICT)
Expand Down Expand Up @@ -816,8 +860,16 @@ def check_region(self, parsed_globals):
'content-encoding': {'options': {'nargs': 1}},
'content-language': {'options': {'nargs': 1}},
'expires': {'options': {'nargs': 1}},
}
'index-document': {'options': {}, 'documents':
('A suffix that is appended to a request that is for a '
'directory on the website endpoint (e.g. if the suffix '
'is index.html and you make a request to '
'samplebucket/images/ the data that is returned will '
'be for the object with the key name images/index.html) '
'The suffix must not be empty and must not include a '
'slash character.')},
'error-document': {'options': {}, 'documents':
'The object key name to use when a 4XX class error occurs.'}

}
add_param_descriptions(PARAMS_DICT)


14 changes: 14 additions & 0 deletions tests/integration/customizations/s3/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,5 +545,19 @@ def test_transfer_single_large_file(self):
self.assert_max_memory_used(p, self.max_mem_allowed, download_full_command)


class TestWebsiteConfiguration(BaseS3CLICommand):
def test_create_website_configuration(self):
bucket_name = self.create_bucket()
full_command = 's3 website %s --index-document index.html' % (bucket_name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably a good idea to test the error document, either here or in another test.

NVM, I see the tests below.

p = aws(full_command)
self.assertEqual(p.rc, 0)
self.assert_no_errors(p)
# Verify we have a bucket website configured.
operation = self.service.get_operation('GetBucketWebsite')
parsed = operation.call(
self.endpoint, bucket=bucket_name)[1]
self.assertEqual(parsed['IndexDocument']['Suffix'], 'index.html')


if __name__ == "__main__":
unittest.main()
7 changes: 7 additions & 0 deletions tests/unit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,22 @@ def assert_params_for_cmd(self, cmd, params=None, expected_rc=0,
self.assertDictEqual(params, last_params)
return stdout, stderr, rc

def before_parameter_build(self, params, **kwargs):
self.last_kwargs = params

def run_cmd(self, cmd, expected_rc=0):
logging.debug("Calling cmd: %s", cmd)
self.patch_make_request()
driver = create_clidriver()
driver.session.register('before-call', self.before_call)
driver.session.register('before-parameter-build',
self.before_parameter_build)
if not isinstance(cmd, list):
cmdlist = cmd.split()
else:
cmdlist = cmd
rc = driver.main(cmdlist)

captured_stderr = six.StringIO()
captured_stdout = six.StringIO()
with mock.patch('sys.stderr', captured_stderr):
Expand Down
4 changes: 3 additions & 1 deletion tests/unit/customizations/s3/test_s3.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ def test_initialize(self):
reference.append("building-command-table.main")
reference.append("building-operation-table.s3")
reference.append("doc-examples.S3.*")
cmds = ['mv', 'rm', 'ls', 'rb', 'mb', 'cp', 'sync']
# TODO: Change this test, it has to be updated everytime we
# add a new command.
cmds = ['mv', 'rm', 'ls', 'rb', 'mb', 'cp', 'sync', 'website']
for cmd in cmds:
reference.append("building-parameter-table.s3." + cmd)
for arg in self.cli.register.call_args_list:
Expand Down
44 changes: 44 additions & 0 deletions tests/unit/customizations/s3/test_website_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python
# Copyright 2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
from tests.unit import BaseAWSCommandParamsTest
import re


class TestWebsiteCommand(BaseAWSCommandParamsTest):

prefix = 's3 website '

def test_index_document(self):
cmdline = self.prefix + 's3://mybucket --index-document index.html'
result = {'uri_params': {'Bucket': 'mybucket'},
'headers': {},}
self.assert_params_for_cmd(cmdline, result, ignore_params=['payload'])
self.assertEqual(
self.last_kwargs,
{'website_configuration': {'IndexDocument': {'Suffix': 'index.html'}},
'bucket': u'mybucket'})

def test_error_document(self):
cmdline = self.prefix + 's3://mybucket --error-document mykey'
result = {'uri_params': {'Bucket': 'mybucket'},
'headers': {},}
self.assert_params_for_cmd(cmdline, result, ignore_params=['payload'])
self.assertEqual(
self.last_kwargs,
{'website_configuration': {'ErrorDocument': {'Key': 'mykey'}},
'bucket': u'mybucket'})


if __name__ == "__main__":
unittest.main()
2 changes: 1 addition & 1 deletion tests/unit/test_completer.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
set(['--filters', '--dry-run', '--no-dry-run', '--endpoint-url',
'--no-verify-ssl', '--no-paginate', '--output', '--profile',
'--region', '--version', '--color', '--query'])),
('aws s3', -1, set(['cp', 'mv', 'rm', 'mb', 'rb', 'ls', 'sync'])),
('aws s3', -1, set(['cp', 'mv', 'rm', 'mb', 'rb', 'ls', 'sync', 'website'])),
('aws s3 m', -1, set(['mv', 'mb'])),
('aws s3 cp -', -1, set(['--no-guess-mime-type', '--dryrun',
'--recursive', '--website-redirect',
Expand Down