Add support for EventBridge to aws integration (#77573)

* Added EventBridge support to aws integration

* Added type hints for all aws notification services + Added unit tests for EventBridge AWS integration

* Increase line coverage for unit tests for aws integration.
This commit is contained in:
Parham Ghazanfari 2022-10-25 07:21:25 -04:00 committed by GitHub
parent aea0067e49
commit dbfca8def8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 169 additions and 5 deletions

View file

@ -51,7 +51,7 @@ DEFAULT_CREDENTIAL = [
{CONF_NAME: "default", CONF_PROFILE_NAME: "default", CONF_VALIDATE: False}
]
SUPPORTED_SERVICES = ["lambda", "sns", "sqs"]
SUPPORTED_SERVICES = ["lambda", "sns", "sqs", "events"]
NOTIFY_PLATFORM_SCHEMA = vol.Schema(
{

View file

@ -3,6 +3,7 @@ import asyncio
import base64
import json
import logging
from typing import Any
from aiobotocore.session import AioSession
@ -105,6 +106,9 @@ async def async_get_service(hass, config, discovery_info=None):
if service == "sqs":
return AWSSQS(session, aws_config)
if service == "events":
return AWSEventBridge(session, aws_config)
# should not reach here since service was checked in schema
return None
@ -128,7 +132,7 @@ class AWSLambda(AWSNotify):
super().__init__(session, aws_config)
self.context = context
async def async_send_message(self, message="", **kwargs):
async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send notification to specified LAMBDA ARN."""
if not kwargs.get(ATTR_TARGET):
_LOGGER.error("At least one target is required")
@ -161,7 +165,7 @@ class AWSSNS(AWSNotify):
service = "sns"
async def async_send_message(self, message="", **kwargs):
async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send notification to specified SNS ARN."""
if not kwargs.get(ATTR_TARGET):
_LOGGER.error("At least one target is required")
@ -199,7 +203,7 @@ class AWSSQS(AWSNotify):
service = "sqs"
async def async_send_message(self, message="", **kwargs):
async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send notification to specified SQS ARN."""
if not kwargs.get(ATTR_TARGET):
_LOGGER.error("At least one target is required")
@ -231,3 +235,52 @@ class AWSSQS(AWSNotify):
if tasks:
await asyncio.gather(*tasks)
class AWSEventBridge(AWSNotify):
"""Implement the notification service for the AWS EventBridge service."""
service = "events"
async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send notification to specified EventBus."""
cleaned_kwargs = {k: v for k, v in kwargs.items() if v is not None}
data = cleaned_kwargs.get(ATTR_DATA, {})
detail = (
json.dumps(data["detail"])
if "detail" in data
else json.dumps({"message": message})
)
async with self.session.create_client(
self.service, **self.aws_config
) as client:
tasks = []
entries = []
for target in kwargs.get(ATTR_TARGET, [None]):
entry = {
"Source": data.get("source", "homeassistant"),
"Resources": data.get("resources", []),
"Detail": detail,
"DetailType": data.get("detail_type", ""),
}
if target:
entry["EventBusName"] = target
entries.append(entry)
for i in range(0, len(entries), 10):
tasks.append(
client.put_events(Entries=entries[i : min(i + 10, len(entries))])
)
if tasks:
results = await asyncio.gather(*tasks)
for result in results:
for entry in result["Entries"]:
if len(entry.get("EventId", "")) == 0:
_LOGGER.error(
"Failed to send event: ErrorCode=%s ErrorMessage=%s",
entry["ErrorCode"],
entry["ErrorMessage"],
)

View file

@ -1,5 +1,6 @@
"""Tests for the aws component config and setup."""
from unittest.mock import AsyncMock, MagicMock, patch as async_patch
import json
from unittest.mock import AsyncMock, MagicMock, call, patch as async_patch
from homeassistant.setup import async_setup_component
@ -13,6 +14,7 @@ class MockAioSession:
self.invoke = AsyncMock()
self.publish = AsyncMock()
self.send_message = AsyncMock()
self.put_events = AsyncMock()
def create_client(self, *args, **kwargs):
"""Create a mocked client."""
@ -23,6 +25,7 @@ class MockAioSession:
invoke=self.invoke, # lambda
publish=self.publish, # sns
send_message=self.send_message, # sqs
put_events=self.put_events, # events
)
),
__aexit__=AsyncMock(),
@ -289,3 +292,111 @@ async def test_service_call_extra_data(hass):
"AWS.SNS.SMS.SenderID": {"StringValue": "HA-notify", "DataType": "String"}
},
)
async def test_events_service_call(hass):
"""Test events service (EventBridge) call works as expected."""
mock_session = MockAioSession()
with async_patch(
"homeassistant.components.aws.AioSession", return_value=mock_session
):
await async_setup_component(
hass,
"aws",
{
"aws": {
"notify": [
{
"service": "events",
"name": "Events Test",
"region_name": "us-east-1",
}
]
}
},
)
await hass.async_block_till_done()
assert hass.services.has_service("notify", "events_test") is True
mock_session.put_events.return_value = {
"Entries": [{"EventId": "", "ErrorCode": 0, "ErrorMessage": "test-error"}]
}
await hass.services.async_call(
"notify",
"events_test",
{
"message": "test",
"target": "ARN",
"data": {},
},
blocking=True,
)
mock_session.put_events.assert_called_once_with(
Entries=[
{
"EventBusName": "ARN",
"Detail": json.dumps({"message": "test"}),
"DetailType": "",
"Source": "homeassistant",
"Resources": [],
}
]
)
async def test_events_service_call_10_targets(hass):
"""Test events service (EventBridge) call works with more than 10 targets."""
mock_session = MockAioSession()
with async_patch(
"homeassistant.components.aws.AioSession", return_value=mock_session
):
await async_setup_component(
hass,
"aws",
{
"aws": {
"notify": [
{
"service": "events",
"name": "Events Test",
"region_name": "us-east-1",
}
]
}
},
)
await hass.async_block_till_done()
assert hass.services.has_service("notify", "events_test") is True
await hass.services.async_call(
"notify",
"events_test",
{
"message": "",
"target": [f"eventbus{i}" for i in range(11)],
"data": {
"detail_type": "test_event",
"detail": {"eventkey": "eventvalue"},
"source": "HomeAssistant-test",
"resources": ["resource1", "resource2"],
},
},
blocking=True,
)
entry = {
"Detail": json.dumps({"eventkey": "eventvalue"}),
"DetailType": "test_event",
"Source": "HomeAssistant-test",
"Resources": ["resource1", "resource2"],
}
mock_session.put_events.assert_has_calls(
[
call(Entries=[entry | {"EventBusName": f"eventbus{i}"} for i in range(10)]),
call(Entries=[entry | {"EventBusName": "eventbus10"}]),
]
)