diff --git a/awscli/customizations/preview.py b/awscli/customizations/preview.py index 6eafce776997..ce4432224cee 100644 --- a/awscli/customizations/preview.py +++ b/awscli/customizations/preview.py @@ -33,32 +33,15 @@ import sys import textwrap -from awscli.clidriver import CLICommand - logger = logging.getLogger(__name__) -# Mapping of service name to help text to print -# when a user tries to invoke a service marked as preview. -CLOUDSEARCH_HELP = """ -CloudSearch has a specialized command line tool available at -http://aws.amazon.com/tools#cli. The AWS CLI does not yet -support all of the features of the CloudSearch CLI. Until these features -are added to the AWS CLI, you may have a more complete -experience using the CloudSearch CLI. -""" - - -GENERAL_HELP = """ -This service is only available as a preview service. -""" - -PREVIEW_SERVICES = { - 'cloudfront': GENERAL_HELP, - 'sdb': GENERAL_HELP, - 'efs': GENERAL_HELP, -} +PREVIEW_SERVICES = [ + 'cloudfront', + 'sdb', + 'efs', +] def register_preview_commands(events): @@ -70,15 +53,48 @@ def mark_as_preview(command_table, session, **kwargs): # explicitly enabled in the config file. allowed_services = _get_allowed_services(session) for preview_service in PREVIEW_SERVICES: + is_enabled = False if preview_service in allowed_services: # Then we don't need to swap it as a preview # service, the user has specifically asked to # enable this service. logger.debug("Preview service enabled through config file: %s", preview_service) - continue - command_table[preview_service] = PreviewModeCommand( - preview_service, PREVIEW_SERVICES[preview_service]) + is_enabled = True + original_command = command_table[preview_service] + preview_cls = type( + 'PreviewCommand', + (PreviewModeCommandMixin, original_command.__class__), {}) + command_table[preview_service] = preview_cls( + cli_name=original_command.name, + session=session, + service_name=original_command.service_model.service_name, + is_enabled=is_enabled) + # We also want to register a handler that will update the + # description in the docs to say that this is a preview service. + session.get_component('event_emitter').register_last( + 'doc-description.%s' % preview_service, + update_description_with_preview) + + +def update_description_with_preview(help_command, **kwargs): + style = help_command.doc.style + style.start_note() + style.bold(PreviewModeCommandMixin.HELP_SNIPPET.strip()) + # bcdoc does not currently allow for what I'd like to do + # which is have a code block like: + # + # :: + # [preview] + # service=true + # + # aws configure set preview.service true + # + # So for now we're just going to add the configure command + # to enable this. + style.doc.write("You can enable this service by running: ") + style.code("aws configure set preview.%s true" % help_command.name) + style.end_note() def _get_allowed_services(session): @@ -93,14 +109,9 @@ def _get_allowed_services(session): return allowed -class PreviewModeCommand(CLICommand): - # This is a hidden attribute that tells the doc system - # not to document this command in the provider help. - # This is an internal implementation detail. - _UNDOCUMENTED = True - +class PreviewModeCommandMixin(object): ENABLE_DOCS = textwrap.dedent("""\ - However, if you'd like to use a basic set of {service} commands with the + However, if you'd like to use the "aws {service}" commands with the AWS CLI, you can enable this service by adding the following to your CLI config file: @@ -112,13 +123,24 @@ class PreviewModeCommand(CLICommand): aws configure set preview.{service} true """) + HELP_SNIPPET = "This service is only available as a preview service.\n" - def __init__(self, service_name, service_help): - self._service_name = service_name - self._service_help = service_help + def __init__(self, *args, **kwargs): + self._is_enabled = kwargs.pop('is_enabled') + super(PreviewModeCommandMixin, self).__init__(*args, **kwargs) def __call__(self, args, parsed_globals): - sys.stderr.write(self._service_help) + if self._is_enabled or self._is_help_command(args): + return super(PreviewModeCommandMixin, self).__call__( + args, parsed_globals) + else: + return self._display_opt_in_message() + + def _is_help_command(self, args): + return args and args[-1] == 'help' + + def _display_opt_in_message(self): + sys.stderr.write(self.HELP_SNIPPET) sys.stderr.write("\n") # Then let them know how to enable this service. sys.stderr.write(self.ENABLE_DOCS.format(service=self._service_name)) diff --git a/tests/unit/customizations/test_preview.py b/tests/unit/customizations/test_preview.py index ed82f8c80742..6206b48795a1 100644 --- a/tests/unit/customizations/test_preview.py +++ b/tests/unit/customizations/test_preview.py @@ -40,14 +40,28 @@ def test_invoke_preview_mode_service(self): # ever mark cloudfront as not being a preview service # by default. self.assertIn('cloudfront', preview.PREVIEW_SERVICES) - rc = self.driver.main('cloudfront help'.split()) + rc = self.driver.main('cloudfront list-distributions'.split()) self.assertEqual(rc, 1) - self.assertIn(preview.PREVIEW_SERVICES['cloudfront'], + self.assertIn(preview.PreviewModeCommandMixin.HELP_SNIPPET, self.stderr.getvalue()) - @mock.patch('awscli.help.get_renderer') - def test_preview_service_off(self, get_renderer): + def test_preview_service_not_true(self): + # If it's not "true" then we still make it a preview service. + self.full_config['preview'] = {'cloudfront': 'false'} + rc = self.driver.main('cloudfront list-distributions'.split()) + self.assertEqual(rc, 1) + self.assertIn(preview.PreviewModeCommandMixin.HELP_SNIPPET, + self.stderr.getvalue()) + + def test_preview_service_enabled_makes_call(self): self.full_config['preview'] = {'cloudfront': 'true'} + self.assert_params_for_cmd('cloudfront list-distributions', params={}) + + @mock.patch('awscli.help.get_renderer') + def test_can_still_document_preview_service(self, get_renderer): + # Even if a service is still marked as being in preview, + # you can still pull up its documentation. + self.full_config['preview'] = {'cloudfront': 'false'} renderer = mock.Mock() get_renderer.return_value = renderer self.driver.main('cloudfront help'.split()) @@ -55,18 +69,10 @@ def test_preview_service_off(self, get_renderer): # and we check that we rendered the contents. self.assertTrue(renderer.render.called) - def test_preview_service_not_true(self): - # If it's not "true" then we still make it a preview service. - self.full_config['preview'] = {'cloudfront': 'false'} - rc = self.driver.main('cloudfront help'.split()) - self.assertEqual(rc, 1) - self.assertIn(preview.PREVIEW_SERVICES['cloudfront'], - self.stderr.getvalue()) - @mock.patch('awscli.help.get_renderer') - def test_preview_mode_not_in_provider_help(self, renderer): + def test_preview_mode_is_in_provider_help(self, renderer): self.driver.main(['help']) - contents = renderer.return_value.render.call_args - # The preview services should not be in the help output. + contents = renderer.return_value.render.call_args[0][0] + # The preview services should still be in the help output. for service in preview.PREVIEW_SERVICES: - self.assertNotIn(service, contents) + self.assertIn(service, contents.decode('utf-8')) diff --git a/tests/unit/test_completer.py b/tests/unit/test_completer.py index 9989eb97f75d..0701e307be4e 100644 --- a/tests/unit/test_completer.py +++ b/tests/unit/test_completer.py @@ -28,22 +28,23 @@ '--query', '--no-sign-request'] COMPLETIONS = [ - ('aws ', -1, set(['autoscaling', 'cloudformation', 'cloudhsm', - 'cloudsearch', 'cloudsearchdomain', 'cloudtrail', - 'cloudwatch', 'cognito-identity', 'cognito-sync', - 'configservice', 'configure', 'datapipeline', 'deploy', - 'directconnect', 'ds', 'dynamodb', 'glacier', 'ec2', - 'ecs', 'elasticache', 'elasticbeanstalk', - 'elastictranscoder', 'elb', 'emr', 'iam', - 'importexport', 'kinesis', 'kms', 'lambda', 'logs', - 'machinelearning', 'opsworks', 'rds', 'redshift', - 'route53', 'route53domains', 's3', 's3api', 'ses', - 'sns', 'sqs', 'storagegateway', 'sts', 'ssm', 'support', - 'swf', 'workspaces'])), - ('aws cloud', -1, set(['cloudformation', 'cloudhsm', 'cloudsearch', - 'cloudsearchdomain', 'cloudtrail', 'cloudwatch'])), - ('aws cloudf', -1, set(['cloudformation'])), - ('aws cloudfr', -1, set([])), + ('aws ', -1, set(['autoscaling', 'cloudformation', 'cloudfront', + 'cloudhsm', 'cloudsearch', 'cloudsearchdomain', + 'cloudtrail', 'cloudwatch', 'cognito-identity', + 'cognito-sync', 'configservice', 'configure', + 'datapipeline', 'deploy', 'directconnect', 'ds', + 'dynamodb', 'glacier', 'ec2', 'ecs', 'efs', + 'elasticache', 'elasticbeanstalk', 'elastictranscoder', + 'elb', 'emr', 'iam', 'importexport', 'kinesis', 'kms', + 'lambda', 'logs', 'machinelearning', 'opsworks', 'rds', + 'redshift', 'route53', 'route53domains', 's3', 's3api', + 'sdb', 'ses', 'sns', 'sqs', 'storagegateway', 'sts', + 'ssm', 'support', 'swf', 'workspaces'])), + ('aws cloud', -1, set(['cloudformation', 'cloudfront', 'cloudhsm', + 'cloudsearch', 'cloudsearchdomain', 'cloudtrail', + 'cloudwatch'])), + ('aws cloudf', -1, set(['cloudformation', 'cloudfront',])), + ('aws cloudfr', -1, set(['cloudfront'])), ('aws foobar', -1, set([])), ('aws --', -1, set(GLOBALOPTS)), ('aws --re', -1, set(['--region'])),