From f993ac44b528374515b41c6ca5a6c7c35b96438c Mon Sep 17 00:00:00 2001 From: James Saryerwinnie Date: Tue, 29 Dec 2015 15:08:06 -0800 Subject: [PATCH] Fail gracefully when no region is configured If we can't create a client for server side completion, we should not propogate an exception. Instead we should return no server side completion values. In the future, it would be nice to have some sort of notification area in the shell where we could let the user know that server side completion won't work because they don't have a region configured. Fixes #84. --- awsshell/resource/index.py | 19 +++++++++++-- tests/unit/test_resources.py | 53 ++++++++++++++++++++++++++++++++---- 2 files changed, 64 insertions(+), 8 deletions(-) diff --git a/awsshell/resource/index.py b/awsshell/resource/index.py index dd0366f..0dbff75 100644 --- a/awsshell/resource/index.py +++ b/awsshell/resource/index.py @@ -20,6 +20,7 @@ import jmespath from botocore import xform_name +from botocore.exceptions import BotoCoreError LOG = logging.getLogger(__name__) @@ -221,11 +222,21 @@ def retrieve_candidate_values(self, service, operation, param): # param='InstanceIds'. if service not in self._describer_creator.services_with_completions(): return [] - client = self._client_creator.create_client(service) + try: + client = self._client_creator.create_client(service) + except BotoCoreError as e: + # create_client() could raise an exception if the session + # isn't fully configured (say it's missing a region). + # However, we don't want to turn off all server side + # completions because it's still possible to create + # clients for some services without a region, e.g. IAM. + LOG.debug("Error when trying to create a client for %s", + service, exc_info=True) + return [] api_operation_name = client.meta.method_to_api_mapping.get( operation.replace('-', '_')) if api_operation_name is None: - return + return [] # Now we need to convert the param name to the # casing used by the API. completer = self._describer_creator.create_completer_query(service) @@ -235,7 +246,9 @@ def retrieve_candidate_values(self, service, operation, param): return try: response = getattr(client, xform_name(result.operation, '_'))() - except Exception: + except Exception as e: + LOG.debug("Error when calling %s.%s: %s", service, + result.operation, e, exc_info=True) return results = jmespath.search(result.path, response) return results diff --git a/tests/unit/test_resources.py b/tests/unit/test_resources.py index d006c37..d02c35d 100644 --- a/tests/unit/test_resources.py +++ b/tests/unit/test_resources.py @@ -1,8 +1,23 @@ """Index and retrive information from the resource JSON.""" import pytest +import mock + +from botocore.exceptions import NoRegionError + from awsshell.resource import index +@pytest.fixture +def describer_creator(): + class FakeDescriberCreator(object): + SERVICES = ['ec2'] + + def services_with_completions(self): + return self.SERVICES + + return FakeDescriberCreator() + + def test_build_from_has_many(): resource = { 'service': { @@ -211,14 +226,42 @@ def services_with_completions(self): assert factory.create_completer_query('ec2') == result -def test_empty_results_returned_when_no_completion_data_exists(): - class FakeDescriberCreator(object): - def services_with_completions(self): - return [] +def test_empty_results_returned_when_no_completion_data_exists(describer_creator): + describer_creator.SERVICES = [] completer = index.ServerSideCompleter( client_creator=None, - describer_creator=FakeDescriberCreator() + describer_creator=describer_creator, ) assert completer.retrieve_candidate_values( 'ec2', 'run-instances', 'ImageId') == [] + + +def test_no_completions_when_cant_create_client(describer_creator): + client_creator = mock.Mock(spec=index.CachedClientCreator) + # This is raised when you don't have a region configured via config file + # env var or manually via a session. + client_creator.create_client.side_effect = NoRegionError() + completer = index.ServerSideCompleter( + client_creator=client_creator, + describer_creator=describer_creator) + + assert completer.retrieve_candidate_values( + 'ec2', 'foo', 'Bar') == [] + + +def test_no_completions_returned_on_unknown_operation(describer_creator): + client = mock.Mock() + client_creator = mock.Mock(spec=index.CachedClientCreator) + client_creator.create_client.return_value = client + + client.meta.method_to_api_mapping = { + 'describe_foo': 'DescribeFoo' + } + + completer = index.ServerSideCompleter( + client_creator=client_creator, + describer_creator=describer_creator) + + assert completer.retrieve_candidate_values( + 'ec2', 'not_describe_foo', 'Bar') == []