Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions homeassistant/components/aws/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
CONF_REGION,
CONF_SECRET_ACCESS_KEY,
CONF_SERVICE,
CONF_VALIDATE,
DATA_CONFIG,
DATA_HASS_CONFIG,
DATA_SESSIONS,
Expand All @@ -36,10 +37,15 @@
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"]

Expand Down Expand Up @@ -170,7 +176,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
1 change: 1 addition & 0 deletions homeassistant/components/aws/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@
CONF_REGION = "region_name"
CONF_SECRET_ACCESS_KEY = "aws_secret_access_key"
CONF_SERVICE = "service"
CONF_VALIDATE = "validate"
54 changes: 46 additions & 8 deletions tests/components/aws/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
)
Expand All @@ -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):
Expand All @@ -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(
Expand All @@ -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):
Expand All @@ -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(
Expand All @@ -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):
Expand Down Expand Up @@ -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(
Expand All @@ -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):
Expand Down Expand Up @@ -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()