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') == []