diff --git a/homeassistant/components/aws/__init__.py b/homeassistant/components/aws/__init__.py index 9533d2c776d..fe57aa73db4 100644 --- a/homeassistant/components/aws/__init__.py +++ b/homeassistant/components/aws/__init__.py @@ -20,6 +20,7 @@ from .const import ( CONF_REGION, CONF_SECRET_ACCESS_KEY, CONF_SERVICE, + CONF_VALIDATE, DATA_CONFIG, DATA_HASS_CONFIG, DATA_SESSIONS, @@ -34,10 +35,15 @@ AWS_CREDENTIAL_SCHEMA = vol.Schema( vol.Inclusive(CONF_ACCESS_KEY_ID, ATTR_CREDENTIALS): cv.string, vol.Inclusive(CONF_SECRET_ACCESS_KEY, ATTR_CREDENTIALS): cv.string, vol.Exclusive(CONF_PROFILE_NAME, ATTR_CREDENTIALS): cv.string, + vol.Optional(CONF_VALIDATE, default=True): cv.boolean, } ) -DEFAULT_CREDENTIAL = [{CONF_NAME: "default", CONF_PROFILE_NAME: "default"}] +DEFAULT_CREDENTIAL = [{ + CONF_NAME: "default", + CONF_PROFILE_NAME: "default", + CONF_VALIDATE: False, +}] SUPPORTED_SERVICES = ["lambda", "sns", "sqs"] @@ -168,7 +174,8 @@ async def _validate_aws_credentials(hass, credential): else: session = aiobotocore.AioSession(loop=hass.loop) - async with session.create_client("iam", **aws_config) as client: - await client.get_user() + if credential[CONF_VALIDATE]: + async with session.create_client("iam", **aws_config) as client: + await client.get_user() return session diff --git a/homeassistant/components/aws/const.py b/homeassistant/components/aws/const.py index 4fa88566934..4738547bdec 100644 --- a/homeassistant/components/aws/const.py +++ b/homeassistant/components/aws/const.py @@ -14,3 +14,4 @@ CONF_PROFILE_NAME = "profile_name" CONF_REGION = "region_name" CONF_SECRET_ACCESS_KEY = "aws_secret_access_key" CONF_SERVICE = "service" +CONF_VALIDATE = "validate" diff --git a/tests/components/aws/test_init.py b/tests/components/aws/test_init.py index 89dd9deaa0a..9a0bf2ccee2 100644 --- a/tests/components/aws/test_init.py +++ b/tests/components/aws/test_init.py @@ -10,15 +10,19 @@ class MockAioSession: def __init__(self, *args, **kwargs): """Init a mock session.""" + self.get_user = CoroutineMock() + self.invoke = CoroutineMock() + self.publish = CoroutineMock() + self.send_message = CoroutineMock() def create_client(self, *args, **kwargs): # pylint: disable=no-self-use """Create a mocked client.""" return MagicMock( __aenter__=CoroutineMock(return_value=CoroutineMock( - get_user=CoroutineMock(), # iam - invoke=CoroutineMock(), # lambda - publish=CoroutineMock(), # sns - send_message=CoroutineMock(), # sqs + get_user=self.get_user, # iam + invoke=self.invoke, # lambda + publish=self.publish, # sns + send_message=self.send_message, # sqs )), __aexit__=CoroutineMock() ) @@ -35,7 +39,10 @@ async def test_empty_config(hass): sessions = hass.data[aws.DATA_SESSIONS] assert sessions is not None assert len(sessions) == 1 - assert isinstance(sessions.get('default'), MockAioSession) + session = sessions.get('default') + assert isinstance(session, MockAioSession) + # we don't validate auto-created default profile + session.get_user.assert_not_awaited() async def test_empty_credential(hass): @@ -55,7 +62,8 @@ async def test_empty_credential(hass): sessions = hass.data[aws.DATA_SESSIONS] assert sessions is not None assert len(sessions) == 1 - assert isinstance(sessions.get('default'), MockAioSession) + session = sessions.get('default') + assert isinstance(session, MockAioSession) assert hass.services.has_service('notify', 'new_lambda_test') is True await hass.services.async_call( @@ -64,6 +72,7 @@ async def test_empty_credential(hass): {'message': 'test', 'target': 'ARN'}, blocking=True ) + session.invoke.assert_awaited_once() async def test_profile_credential(hass): @@ -88,7 +97,8 @@ async def test_profile_credential(hass): sessions = hass.data[aws.DATA_SESSIONS] assert sessions is not None assert len(sessions) == 1 - assert isinstance(sessions.get('test'), MockAioSession) + session = sessions.get('test') + assert isinstance(session, MockAioSession) assert hass.services.has_service('notify', 'sns_test') is True await hass.services.async_call( @@ -97,6 +107,7 @@ async def test_profile_credential(hass): {'title': 'test', 'message': 'test', 'target': 'ARN'}, blocking=True ) + session.publish.assert_awaited_once() async def test_access_key_credential(hass): @@ -128,7 +139,8 @@ async def test_access_key_credential(hass): sessions = hass.data[aws.DATA_SESSIONS] assert sessions is not None assert len(sessions) == 2 - assert isinstance(sessions.get('key'), MockAioSession) + session = sessions.get('key') + assert isinstance(session, MockAioSession) assert hass.services.has_service('notify', 'sns_test') is True await hass.services.async_call( @@ -137,6 +149,7 @@ async def test_access_key_credential(hass): {'title': 'test', 'message': 'test', 'target': 'ARN'}, blocking=True ) + session.publish.assert_awaited_once() async def test_notify_credential(hass): @@ -197,3 +210,28 @@ async def test_notify_credential_profile(hass): {'message': 'test', 'target': 'ARN'}, blocking=True ) + + +async def test_credential_skip_validate(hass): + """Test credential can skip validate.""" + with async_patch('aiobotocore.AioSession', new=MockAioSession): + await async_setup_component(hass, 'aws', { + 'aws': { + 'credentials': [ + { + 'name': 'key', + 'aws_access_key_id': 'not-valid', + 'aws_secret_access_key': 'dont-care', + 'validate': False + }, + ], + } + }) + await hass.async_block_till_done() + + sessions = hass.data[aws.DATA_SESSIONS] + assert sessions is not None + assert len(sessions) == 1 + session = sessions.get('key') + assert isinstance(session, MockAioSession) + session.get_user.assert_not_awaited()