import time
import signal
import os
import tempfile
import random
import shutil
import botocore.session
from awscli.testutils import unittest, aws, BaseS3CLICommand
from awscli.testutils import temporary_file
from awscli.testutils import skip_if_windows
from awscli.clidriver import create_clidriver
class TestBasicCommandFunctionality(unittest.TestCase):
"""
These are a set of tests that assert high level features of
the CLI. They don't test anything exhaustively and they're meant as a smoke
test to verify basic CLI functionality isn't entirely broken.
"""
def put_object(self, bucket, key, content, extra_args=None):
session = botocore.session.get_session()
client = session.create_client('s3', 'us-east-1')
client.create_bucket(Bucket=bucket, ObjectOwnership='ObjectWriter')
time.sleep(5)
client.delete_public_access_block(Bucket=bucket)
self.addCleanup(client.delete_bucket, Bucket=bucket)
call_args = {
'Bucket': bucket,
'Key': key, 'Body': content
}
if extra_args is not None:
call_args.update(extra_args)
client.put_object(**call_args)
self.addCleanup(client.delete_object, Bucket=bucket, Key=key)
def test_ec2_describe_instances(self):
p = aws('ec2 describe-instances')
self.assertEqual(p.rc, 0)
self.assertIn('Reservations', p.json)
def test_help_output(self):
p = aws('help')
self.assertEqual(p.rc, 0)
self.assertIn('AWS', p.stdout)
self.assertRegex(
p.stdout, r'The\s+AWS\s+Command\s+Line\s+Interface')
def test_service_help_output(self):
p = aws('ec2 help')
self.assertEqual(p.rc, 0)
self.assertRegex(p.stdout, r'Amazon\s+EC2')
def test_operation_help_output(self):
p = aws('ec2 describe-instances help')
self.assertEqual(p.rc, 0)
self.assertRegex(p.stdout, r'\s+Describes\s+the\s+specified\s+instances')
def test_topic_list_help_output(self):
p = aws('help topics')
self.assertEqual(p.rc, 0)
self.assertRegex(p.stdout, r'\s+AWS\s+CLI\s+Topic\s+Guide')
self.assertRegex(
p.stdout,
r'\s+This\s+is\s+the\s+AWS\s+CLI\s+Topic\s+Guide'
)
def test_topic_help_output(self):
p = aws('help return-codes')
self.assertEqual(p.rc, 0)
self.assertRegex(p.stdout, r'\s+AWS\s+CLI\s+Return\s+Codes')
self.assertRegex(
p.stdout,
r'These\s+are\s+the\s+following\s+return\s+codes'
)
def test_operation_help_with_required_arg(self):
p = aws('s3api get-object help')
self.assertEqual(p.rc, 0, p.stderr)
self.assertIn('get-object', p.stdout)
def test_service_help_with_required_option(self):
p = aws('cloudsearchdomain help')
self.assertEqual(p.rc, 0, p.stderr)
self.assertIn('cloudsearchdomain', p.stdout)
self.assertEqual(p.stderr, '')
def test_operation_help_with_required_option(self):
p = aws('cloudsearchdomain search help')
self.assertEqual(p.rc, 0, p.stderr)
self.assertIn('search', p.stdout)
self.assertEqual(p.stderr, '')
def test_help_with_warning_blocks(self):
p = aws('elastictranscoder create-pipeline help')
self.assertEqual(p.rc, 0, p.stderr)
self.assertRegex(p.stdout, r'To\s+receive\s+notifications')
def test_param_shorthand(self):
p = aws(
'ec2 describe-instances --filters Name=instance-id,Values=i-123')
self.assertEqual(p.rc, 0)
self.assertIn('Reservations', p.json)
def test_param_json(self):
p = aws(
'ec2 describe-instances --filters '
'\'{"Name": "instance-id", "Values": ["i-123"]}\'')
self.assertEqual(p.rc, 0, p.stdout + p.stderr)
self.assertIn('Reservations', p.json)
def test_param_with_bad_json(self):
p = aws(
'ec2 describe-instances --filters '
'\'{"Name": "bad-filter", "Values": ["i-123"]}\'')
self.assertEqual(p.rc, 255)
self.assertIn("The filter 'bad-filter' is invalid", p.stderr,
"stdout: %s, stderr: %s" % (p.stdout, p.stderr))
def test_param_with_file(self):
d = tempfile.mkdtemp()
self.addCleanup(os.rmdir, d)
param_file = os.path.abspath(os.path.join(d, 'params.json'))
with open(param_file, 'w') as f:
f.write('[{"Name": "instance-id", "Values": ["i-123"]}]')
self.addCleanup(os.remove, param_file)
p = aws('ec2 describe-instances --filters file://%s' % param_file)
self.assertEqual(p.rc, 0)
self.assertIn('Reservations', p.json)
def test_streaming_output_operation(self):
d = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, d)
bucket_name = 'clistream' + str(
int(time.time())) + str(random.randint(1, 100))
self.put_object(bucket=bucket_name, key='foobar',
content='foobar contents')
p = aws('s3api get-object --bucket %s --key foobar %s' % (
bucket_name, os.path.join(d, 'foobar')))
self.assertEqual(p.rc, 0)
with open(os.path.join(d, 'foobar')) as f:
contents = f.read()
self.assertEqual(contents, 'foobar contents')
def test_no_sign_request(self):
d = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, d)
env_vars = os.environ.copy()
env_vars['AWS_ACCESS_KEY_ID'] = 'foo'
env_vars['AWS_SECRET_ACCESS_KEY'] = 'bar'
bucket_name = 'nosign' + str(
int(time.time())) + str(random.randint(1, 100))
self.put_object(bucket_name, 'foo', content='bar',
extra_args={'ACL': 'public-read-write'})
p = aws('s3api get-object --bucket %s --key foo %s' % (
bucket_name, os.path.join(d, 'foo')), env_vars=env_vars)
self.assertEqual(p.rc, 255)
p = aws('s3api get-object --bucket %s --key foo '
'%s --no-sign-request' % (bucket_name, os.path.join(d, 'foo')),
env_vars=env_vars)
self.assertEqual(p.rc, 0)
with open(os.path.join(d, 'foo')) as f:
contents = f.read()
self.assertEqual(contents, 'bar')
def test_no_paginate_arg(self):
d = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, d)
bucket_name = 'nopaginate' + str(
int(time.time())) + str(random.randint(1, 100))
self.put_object(bucket=bucket_name, key='foobar',
content='foobar contents')
p = aws('s3api list-objects --bucket %s --no-paginate' % bucket_name)
self.assertEqual(p.rc, 0, p.stdout + p.stderr)
p = aws('s3api list-objects --bucket %s' % bucket_name)
self.assertEqual(p.rc, 0, p.stdout + p.stderr)
def test_top_level_options_debug(self):
p = aws('ec2 describe-instances --debug')
self.assertEqual(p.rc, 0)
self.assertIn('DEBUG', p.stderr)
def test_make_requests_to_other_region(self):
p = aws('ec2 describe-instances --region us-west-2')
self.assertEqual(p.rc, 0)
self.assertIn('Reservations', p.json)
def test_help_usage_top_level(self):
p = aws('')
self.assertIn('usage: aws [options] <command> '
'<subcommand> [<subcommand> ...] [parameters]', p.stderr)
self.assertIn('aws: error', p.stderr)
def test_help_usage_service_level(self):
p = aws('ec2')
self.assertIn('usage: aws [options] <command> '
'<subcommand> [<subcommand> ...] [parameters]', p.stderr)
self.assertIn('aws: error', p.stderr)
def test_help_usage_operation_level(self):
p = aws('ec2 start-instances')
self.assertIn('usage: aws [options] <command> '
'<subcommand> [<subcommand> ...] [parameters]', p.stderr)
def test_unknown_argument(self):
p = aws('ec2 describe-instances --filterss')
self.assertEqual(p.rc, 255)
self.assertIn('Unknown options: --filterss', p.stderr)
def test_table_output(self):
p = aws('ec2 describe-instances --output table --color off')
self.assertEqual(p.rc, 0, p.stderr)
self.assertIn('-----', p.stdout)
self.assertIn('+-', p.stdout)
self.assertIn('DescribeInstances', p.stdout)
def test_version(self):
p = aws('--version')
self.assertEqual(p.rc, 0)
version_output = p.stderr.startswith('aws-cli') or \
p.stdout.startswith('aws-cli')
self.assertTrue(version_output, p.stderr)
def test_traceback_printed_when_debug_on(self):
p = aws('ec2 describe-instances --filters BADKEY=foo --debug')
self.assertIn('Traceback (most recent call last):', p.stderr, p.stderr)
self.assertIn('DEBUG', p.stderr, p.stderr)
def test_leftover_args_in_operation(self):
p = aws('ec2 describe-instances BADKEY=foo')
self.assertEqual(p.rc, 255)
self.assertIn("Unknown option", p.stderr, p.stderr)
def test_json_param_parsing(self):
p = aws('swf list-domains --registration-status REGISTERED')
self.assertEqual(p.rc, 0)
self.assertIsInstance(p.json, dict)
p = aws('dynamodb list-tables')
self.assertEqual(p.rc, 0)
self.assertIsInstance(p.json, dict)
def test_pagination_with_text_output(self):
p = aws('iam list-users --output text')
self.assertEqual(p.rc, 0)
def test_bad_lc_ctype_env_var_is_handled(self):
base_env_vars = os.environ.copy()
base_env_vars['LC_CTYPE'] = 'UTF-8'
p = aws('iam list-users', env_vars=base_env_vars)
self.assertEqual(p.rc, 0)
def test_error_msg_with_no_region_configured(self):
environ = os.environ.copy()
try:
del environ['AWS_DEFAULT_REGION']
except KeyError:
pass
environ['AWS_CONFIG_FILE'] = 'nowhere-foo'
p = aws('ec2 describe-instances', env_vars=environ)
self.assertIn('must specify a region', p.stderr)
@skip_if_windows('Ctrl-C not supported on windows.')
def test_ctrl_c_does_not_print_traceback(self):
process = aws('ec2 describe-images', wait_for_finish=False)
time.sleep(1)
process.send_signal(signal.SIGINT)
stdout, stderr = process.communicate()
self.assertNotIn(b'Traceback', stdout)
self.assertNotIn(b'Traceback', stderr)
class TestCommandLineage(unittest.TestCase):
def setUp(self):
self.driver = create_clidriver()
self.top_help = self.driver.create_help_command()
def assert_lineage_names(self, ref_lineage_names):
command_table = self.top_help.command_table
for i, cmd_name in enumerate(ref_lineage_names):
command = command_table[cmd_name]
help_command = command.create_help_command()
command_table = help_command.command_table
actual_lineage_names = []
for cmd in command.lineage:
actual_lineage_names.append(cmd.name)
self.assertEqual(actual_lineage_names, ref_lineage_names)
self.assertEqual(command.lineage_names, actual_lineage_names)
def test_service_level_commands(self):
self.assert_lineage_names(['ec2'])
self.assert_lineage_names(['s3api'])
self.assert_lineage_names(['s3'])
self.assert_lineage_names(['configure'])
def test_operation_level_commands(self):
self.assert_lineage_names(['dynamodb', 'create-table'])
self.assert_lineage_names(['s3api', 'list-objects'])
self.assert_lineage_names(['emr', 'create-cluster'])
self.assert_lineage_names(['configure', 'set'])
self.assert_lineage_names(['s3', 'cp'])
def test_wait_commands(self):
self.assert_lineage_names(['ec2', 'wait'])
self.assert_lineage_names(['ec2', 'wait', 'instance-running'])
class TestGlobalArgs(BaseS3CLICommand):
def test_endpoint_url(self):
p = aws('s3api list-objects --bucket dnscompat '
'--endpoint-url http://localhost:51515 '
'--debug')
debug_logs = p.stderr
original_hostname = 'dnscompat.s3.amazonaws.com'
expected = 'localhost'
self.assertNotIn(original_hostname, debug_logs,
'--endpoint-url is being ignored.')
self.assertIn(expected, debug_logs)
def test_no_pagination(self):
bucket_name = self.create_bucket()
self.put_object(bucket_name, 'foo.txt', contents=b'bar')
self.put_object(bucket_name, 'foo2.txt', contents=b'bar')
self.put_object(bucket_name, 'foo3.txt', contents=b'bar')
p = aws('s3api list-objects --bucket %s '
'--no-paginate --output json' % bucket_name)
self.assert_no_errors(p)
response_json = p.json
self.assertIn('IsTruncated', response_json)
self.assertIn('Name', response_json)
def test_no_paginate_and_original_args(self):
bucket_name = self.create_bucket()
self.put_object(bucket_name, 'foo.txt', contents=b'bar')
self.put_object(bucket_name, 'foo2.txt', contents=b'bar')
self.put_object(bucket_name, 'foo3.txt', contents=b'bar')
p = aws('s3api list-objects --bucket %s '
'--max-keys 1 --no-paginate --output json' % bucket_name)
self.assert_no_errors(p)
response_json = p.json
self.assertEqual(len(response_json['Contents']), 1)
def test_max_items(self):
bucket_name = self.create_bucket()
self.put_object(bucket_name, 'foo.txt', contents=b'bar')
self.put_object(bucket_name, 'foo2.txt', contents=b'bar')
self.put_object(bucket_name, 'foo3.txt', contents=b'bar')
p = aws('s3api list-objects --bucket %s '
'--max-items 1 --output json' % bucket_name)
self.assert_no_errors(p)
response_json = p.json
self.assertEqual(len(response_json['Contents']), 1)
def test_query(self):
bucket_name = self.create_bucket()
self.put_object(bucket_name, 'foo.txt', contents=b'bar')
p = aws('s3api list-objects --bucket %s '
'--query Contents[].Key --output json' % bucket_name)
self.assert_no_errors(p)
response_json = p.json
self.assertEqual(response_json, ['foo.txt'])
def test_no_sign_requests(self):
bucket_name = self.create_bucket()
self.put_object(bucket_name, 'public', contents=b'bar',
extra_args={'ACL': 'public-read'})
self.put_object(bucket_name, 'private', contents=b'bar')
env = os.environ.copy()
env['AWS_ACCESS_KEY_ID'] = 'foo'
env['AWS_SECRET_ACCESS_KEY'] = 'bar'
p = aws('s3api head-object --bucket %s --key public --no-sign-request'
% bucket_name, env_vars=env)
self.assert_no_errors(p)
self.assertIn('ETag', p.json)
p = aws('s3api head-object --bucket %s --key private --no-sign-request'
% bucket_name, env_vars=env)
self.assertEqual(p.rc, 255)
def test_profile_arg_has_precedence_over_env_vars(self):
env_vars = os.environ.copy()
with temporary_file('w') as f:
env_vars.pop('AWS_PROFILE', None)
env_vars.pop('AWS_DEFAULT_PROFILE', None)
env_vars['AWS_ACCESS_KEY_ID'] = 'enva'
env_vars['AWS_SECRET_ACCESS_KEY'] = 'envb'
env_vars['AWS_SHARED_CREDENTIALS_FILE'] = f.name
env_vars['AWS_CONFIG_FILE'] = 'does-not-exist-foo'
f.write(
'[from_argument]\n'
'aws_access_key_id=proa\n'
'aws_secret_access_key=prob\n'
)
f.flush()
p = aws('configure list --profile from_argument',
env_vars=env_vars)
self.assertIn('from_argument', p.stdout)
self.assertIn('proa', p.stdout)
self.assertIn('prob', p.stdout)
self.assertIn('shared-credentials-file', p.stdout)
def test_profile_arg_wins_over_profile_env_var(self):
env_vars = os.environ.copy()
with temporary_file('w') as f:
env_vars.pop('AWS_PROFILE', None)
env_vars.pop('AWS_DEFAULT_PROFILE', None)
env_vars['AWS_SHARED_CREDENTIALS_FILE'] = f.name
env_vars['AWS_CONFIG_FILE'] = 'does-not-exist-foo'
f.write(
'[from_env_var]\n'
'aws_access_key_id=enva\n'
'aws_secret_access_key=envb\n'
'\n'
'[from_argument]\n'
'aws_access_key_id=proa\n'
'aws_secret_access_key=prob\n'
)
f.flush()
env_vars['AWS_PROFILE'] = 'from_env_var'
p = aws('configure list --profile from_argument',
env_vars=env_vars)
self.assertIn('from_argument', p.stdout)
self.assertIn('proa', p.stdout)
self.assertIn('prob', p.stdout)
if __name__ == '__main__':
unittest.main()