Compare commits

...

11 commits

Author SHA1 Message Date
a9748f409d Alerts: prevent crash on unknown severity level 2024-11-06 09:49:40 +01:00
HgO
313795d050 Merge branch 'more-debug-messages' into 'master'
Ajout de messages de debug

See merge request Neutrinet/matrix-alertbot!16
2024-11-03 13:37:57 +00:00
HgO
ecf6e8b017 linting 2024-11-03 14:34:20 +01:00
HgO
a8c953477c add more debug messages 2024-11-03 14:28:15 +01:00
HgO
6feeadda69 Merge branch 'fix-dm-label-filtering' into 'master'
Fix DM label filtering and find DM rooms with only the bot(s) and user in it

See merge request Neutrinet/matrix-alertbot!15
2024-08-12 11:31:46 +00:00
HgO
6d7980e6d5 linting 2024-08-12 13:28:00 +02:00
HgO
e7d4471c3d remove debug message 2024-08-12 13:27:04 +02:00
HgO
08264b9e75 find dm room with only bot and user in it 2024-08-12 13:24:47 +02:00
HgO
eeb39b8aea fix dm label filtering 2024-08-12 13:24:40 +02:00
HgO
949ace768a Merge branch 'fix-power-levels-dm-room' into 'master'
Grants admin rights when creating dm room

See merge request Neutrinet/matrix-alertbot!14
2024-08-11 16:22:25 +00:00
HgO
26b5fa7fb4 grants admin rights when creating dm room 2024-08-11 18:18:20 +02:00
6 changed files with 194 additions and 59 deletions

View file

@ -37,7 +37,7 @@ class Alert:
self.description = annotations["description"] self.description = annotations["description"]
if self.firing: if self.firing:
self.status = self.labels["severity"] self.status = self.labels.get("severity", "unknown_alert")
else: else:
self.status = "resolved" self.status = "resolved"
@ -55,11 +55,11 @@ class Alert:
@property @property
def emoji(self) -> str: def emoji(self) -> str:
return self.EMOJIS[self.status] return self.EMOJIS.get(self.status, "❓️")
@property @property
def color(self) -> str: def color(self) -> str:
return self.COLORS[self.status] return self.COLORS.get(self.status, "ffff0c")
def match_label(self, label_name: str, pattern: re.Pattern[str]) -> bool: def match_label(self, label_name: str, pattern: re.Pattern[str]) -> bool:
if label_name not in self.labels: if label_name not in self.labels:

View file

@ -4,6 +4,14 @@ import logging
import re import re
from diskcache import Cache from diskcache import Cache
from nio import (
AccountDataEvent,
EphemeralEvent,
Event,
PresenceEvent,
Response,
ToDeviceEvent,
)
from nio.client import AsyncClient from nio.client import AsyncClient
from nio.events import ( from nio.events import (
InviteMemberEvent, InviteMemberEvent,
@ -525,3 +533,38 @@ class Callbacks:
raise SendRetryError( raise SendRetryError(
f"{response_event.status_code} - {response_event.message}" f"{response_event.status_code} - {response_event.message}"
) )
async def debug_room_event(self, room: MatrixRoom, event: Event):
logger.debug(
f"Bot {self.matrix_client.user_id} | Room ID {room.room_id} | {type(event).__name__} | Event ID {event.event_id} | Received room event: {event.source}"
)
async def debug_presence(self, event: PresenceEvent):
logger.debug(
f"Bot {self.matrix_client.user_id} | User ID {event.user_id} | Received presence event: {event.presence}"
)
async def debug_ephemeral(self, room: MatrixRoom, event: EphemeralEvent):
logger.debug(
f"Bot {self.matrix_client.user_id} | Room ID {room.room_id} | {type(event).__name__} | Received ephemeral event: {event}"
)
async def debug_account_data(self, event: AccountDataEvent):
logger.debug(
f"Bot {self.matrix_client.user_id} | {type(event).__name__} | Received account data event: {event}"
)
async def debug_room_account_data(self, room: MatrixRoom, event: AccountDataEvent):
logger.debug(
f"Bot {self.matrix_client.user_id} | Room ID {room.room_id} | {type(event).__name__} | Received room account data event: {event}"
)
async def debug_to_device(self, event: ToDeviceEvent):
logger.debug(
f"Bot {self.matrix_client.user_id} | {type(event).__name__} | Sender {event.sender} | Received to device event: {event.source}"
)
async def debug_response(self, response: Response):
logger.debug(
f"Bot {self.matrix_client.user_id} | {type(response).__name__} | Received response: {response}"
)

View file

@ -10,7 +10,16 @@ from typing import Dict, List, Optional, Tuple
from aiohttp import ClientConnectionError, ServerDisconnectedError from aiohttp import ClientConnectionError, ServerDisconnectedError
from diskcache import Cache from diskcache import Cache
from nio import RoomPreset, RoomVisibility from nio import (
AccountDataEvent,
EphemeralEvent,
Event,
PresenceEvent,
Response,
RoomPreset,
RoomVisibility,
ToDeviceEvent,
)
from nio.client import AsyncClient, AsyncClientConfig from nio.client import AsyncClient, AsyncClientConfig
from nio.events import ( from nio.events import (
InviteMemberEvent, InviteMemberEvent,
@ -179,6 +188,21 @@ class MatrixClientPool:
matrix_client.add_to_device_callback( matrix_client.add_to_device_callback(
callbacks.key_verification_end, (KeyVerificationMac,) callbacks.key_verification_end, (KeyVerificationMac,)
) )
matrix_client.add_event_callback(callbacks.debug_room_event, (Event,))
matrix_client.add_presence_callback(callbacks.debug_presence, (PresenceEvent,))
matrix_client.add_ephemeral_callback(
callbacks.debug_ephemeral, (EphemeralEvent,)
)
matrix_client.add_global_account_data_callback(
callbacks.debug_account_data, (AccountDataEvent,)
)
matrix_client.add_room_account_data_callback(
callbacks.debug_room_account_data, (AccountDataEvent,)
)
matrix_client.add_to_device_callback(
callbacks.debug_to_device, (ToDeviceEvent,)
)
matrix_client.add_response_callback(callbacks.debug_response, (Response,))
return matrix_client return matrix_client
@ -206,6 +230,9 @@ class MatrixClientPool:
f"Bot {account.id} | Found {len(room_members)} room members in {room_id}" f"Bot {account.id} | Found {len(room_members)} room members in {room_id}"
) )
if len(room_members) > len(self._matrix_clients) + 1:
continue
all_accounts_in_room = True all_accounts_in_room = True
for user_id in unactive_user_ids: for user_id in unactive_user_ids:
if user_id not in room_members: if user_id not in room_members:
@ -236,7 +263,7 @@ class MatrixClientPool:
) -> None: ) -> None:
async with self._lock: async with self._lock:
if matrix_client is self.matrix_client: if matrix_client is self.matrix_client:
unactive_accounts = self.unactive_user_ids() unactive_user_ids = self.unactive_user_ids()
self.dm_rooms = await self.find_existing_dm_rooms( self.dm_rooms = await self.find_existing_dm_rooms(
account=account, matrix_client=matrix_client, config=config account=account, matrix_client=matrix_client, config=config
@ -262,13 +289,16 @@ class MatrixClientPool:
logger.info( logger.info(
f"Bot {account.id} | Creating direct room with user {user_id}" f"Bot {account.id} | Creating direct room with user {user_id}"
) )
invitations = unactive_accounts + [user_id] invitations = unactive_user_ids + [user_id]
room_user_ids = invitations + [account.id]
power_levels = {"users": dict.fromkeys(room_user_ids, 100)}
create_room_response = await matrix_client.room_create( create_room_response = await matrix_client.room_create(
visibility=RoomVisibility.private, visibility=RoomVisibility.private,
name=room_title, name=room_title,
invite=invitations, invite=invitations,
is_direct=True, is_direct=True,
preset=RoomPreset.private_chat, preset=RoomPreset.private_chat,
power_level_override=power_levels,
) )
if isinstance(create_room_response, RoomCreateError): if isinstance(create_room_response, RoomCreateError):
error = create_room_response.message error = create_room_response.message

View file

@ -118,24 +118,27 @@ async def create_alerts(request: web_request.Request) -> web.Response:
body=f"An error occured with Alertmanager when handling alert with fingerprint {alert.fingerprint}.", body=f"An error occured with Alertmanager when handling alert with fingerprint {alert.fingerprint}.",
) )
except (SendRetryError, LocalProtocolError, ClientError) as e: except (SendRetryError, LocalProtocolError, ClientError) as e:
logger.error( logger.exception(
f"Unable to send alert {alert.fingerprint} to Matrix room {room_id}: {e}" f"Unable to send alert {alert.fingerprint} to Matrix room {room_id}",
exc_info=e,
) )
return web.Response( return web.Response(
status=500, status=500,
body=f"An error occured when sending alert with fingerprint '{alert.fingerprint}' to Matrix room.", body=f"An error occured when sending alert with fingerprint '{alert.fingerprint}' to Matrix room.",
) )
except MatrixClientError as e: except MatrixClientError as e:
logger.error( logger.exception(
f"Unable to send alert {alert.fingerprint} to Matrix room {room_id}: {e}" f"Unable to send alert {alert.fingerprint} to Matrix room {room_id}",
exc_info=e,
) )
return web.Response( return web.Response(
status=500, status=500,
body=f"An error occured when sending alert with fingerprint '{alert.fingerprint}' to Matrix room.", body=f"An error occured when sending alert with fingerprint '{alert.fingerprint}' to Matrix room.",
) )
except Exception as e: except Exception as e:
logger.error( logger.exception(
f"Unable to send alert {alert.fingerprint} to Matrix room {room_id}: {e}" f"Unable to send alert {alert.fingerprint} to Matrix room {room_id}",
exc_info=e,
) )
return web.Response( return web.Response(
status=500, status=500,
@ -154,18 +157,26 @@ async def create_alert(
cache: Cache = request.app["cache"] cache: Cache = request.app["cache"]
config: Config = request.app["config"] config: Config = request.app["config"]
if config.dm_select_label and config.dm_select_label in alert.labels:
if alert.match_all_labels(config.dm_filter_labels): if alert.match_all_labels(config.dm_filter_labels):
logger.info("Found all DM filter labels in alert labels")
if config.dm_select_label and config.dm_select_label not in alert.labels:
logger.warning(
f"Dismissing alert: Cannot find select label {config.dm_select_label} in alert labels"
)
return
dm_select_value = alert.labels[config.dm_select_label] dm_select_value = alert.labels[config.dm_select_label]
if dm_select_value not in config.dm_users: if dm_select_value not in config.dm_users:
logger.warning( logger.warning(
f"Cannot find user with label {config.dm_select_label}={dm_select_value}" f"Dismissing alert: Cannot find user with label {config.dm_select_label}={dm_select_value}"
) )
return return
user_id = config.dm_users[dm_select_value] user_id = config.dm_users[dm_select_value]
if user_id not in matrix_client_pool.dm_rooms: if user_id not in matrix_client_pool.dm_rooms:
logger.warning(f"Cannot find a matrix room for user {user_id}") logger.warning(
f"Dismissing alert: Cannot find a matrix room for user {user_id}"
)
return return
room_id = matrix_client_pool.dm_rooms[user_id] room_id = matrix_client_pool.dm_rooms[user_id]
@ -193,6 +204,9 @@ async def create_alert(
event = await send_text_to_room( event = await send_text_to_room(
matrix_client_pool.matrix_client, room_id, plaintext, html, notice=False matrix_client_pool.matrix_client, room_id, plaintext, html, notice=False
) )
logger.info(
f"Sent alert {alert.fingerprint} to room {room_id} with event ID {event.event_id}"
)
else: else:
raise MatrixClientError("No matrix client available") raise MatrixClientError("No matrix client available")

View file

@ -304,8 +304,8 @@ class MatrixClientPoolTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_account_config_1.homeserver_url, matrix_client_1.homeserver self.fake_account_config_1.homeserver_url, matrix_client_1.homeserver
) )
self.assertEqual(self.fake_config.store_dir, matrix_client_1.store_path) self.assertEqual(self.fake_config.store_dir, matrix_client_1.store_path)
self.assertEqual(6, len(matrix_client_1.event_callbacks)) self.assertEqual(7, len(matrix_client_1.event_callbacks))
self.assertEqual(4, len(matrix_client_1.to_device_callbacks)) self.assertEqual(5, len(matrix_client_1.to_device_callbacks))
fake_async_client_config.assert_has_calls( fake_async_client_config.assert_has_calls(
[ [
@ -348,8 +348,8 @@ class MatrixClientPoolTestCase(unittest.IsolatedAsyncioTestCase):
self.fake_account_config_1.homeserver_url, matrix_client_1.homeserver self.fake_account_config_1.homeserver_url, matrix_client_1.homeserver
) )
self.assertEqual(self.fake_config.store_dir, matrix_client_1.store_path) self.assertEqual(self.fake_config.store_dir, matrix_client_1.store_path)
self.assertEqual(6, len(matrix_client_1.event_callbacks)) self.assertEqual(7, len(matrix_client_1.event_callbacks))
self.assertEqual(4, len(matrix_client_1.to_device_callbacks)) self.assertEqual(5, len(matrix_client_1.to_device_callbacks))
self.assertEqual(5, matrix_client_1.config.max_limit_exceeded) self.assertEqual(5, matrix_client_1.config.max_limit_exceeded)
self.assertEqual(3, matrix_client_1.config.max_timeouts) self.assertEqual(3, matrix_client_1.config.max_timeouts)
self.assertTrue(matrix_client_1.config.store_sync_tokens) self.assertTrue(matrix_client_1.config.store_sync_tokens)
@ -476,6 +476,13 @@ class MatrixClientPoolTestCase(unittest.IsolatedAsyncioTestCase):
invite=["@other_user:chat.example.com", "@fake_dm_user:example.com"], invite=["@other_user:chat.example.com", "@fake_dm_user:example.com"],
is_direct=True, is_direct=True,
preset=RoomPreset.private_chat, preset=RoomPreset.private_chat,
power_level_override={
"users": {
"@fake_user:matrix.example.com": 100,
"@other_user:chat.example.com": 100,
"@fake_dm_user:example.com": 100,
}
},
) )
self.assertDictEqual( self.assertDictEqual(
{ {
@ -528,6 +535,13 @@ class MatrixClientPoolTestCase(unittest.IsolatedAsyncioTestCase):
invite=["@other_user:chat.example.com", "@fake_dm_user:example.com"], invite=["@other_user:chat.example.com", "@fake_dm_user:example.com"],
is_direct=True, is_direct=True,
preset=RoomPreset.private_chat, preset=RoomPreset.private_chat,
power_level_override={
"users": {
"@fake_user:matrix.example.com": 100,
"@other_user:chat.example.com": 100,
"@fake_dm_user:example.com": 100,
}
},
) )
self.assertDictEqual( self.assertDictEqual(
{ {
@ -581,6 +595,13 @@ class MatrixClientPoolTestCase(unittest.IsolatedAsyncioTestCase):
invite=["@other_user:chat.example.com", "@fake_dm_user:example.com"], invite=["@other_user:chat.example.com", "@fake_dm_user:example.com"],
is_direct=True, is_direct=True,
preset=RoomPreset.private_chat, preset=RoomPreset.private_chat,
power_level_override={
"users": {
"@fake_user:matrix.example.com": 100,
"@other_user:chat.example.com": 100,
"@fake_dm_user:example.com": 100,
}
},
) )
self.assertDictEqual( self.assertDictEqual(
{ {
@ -631,6 +652,13 @@ class MatrixClientPoolTestCase(unittest.IsolatedAsyncioTestCase):
invite=["@other_user:chat.example.com", "@fake_dm_user:example.com"], invite=["@other_user:chat.example.com", "@fake_dm_user:example.com"],
is_direct=True, is_direct=True,
preset=RoomPreset.private_chat, preset=RoomPreset.private_chat,
power_level_override={
"users": {
"@fake_user:matrix.example.com": 100,
"@other_user:chat.example.com": 100,
"@fake_dm_user:example.com": 100,
}
},
) )
self.assertDictEqual( self.assertDictEqual(
{ {

View file

@ -56,7 +56,7 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
self.fake_config.cache_expire_time = 0 self.fake_config.cache_expire_time = 0
self.fake_config.template_dir = None self.fake_config.template_dir = None
self.fake_config.dm_select_label = "uuid" self.fake_config.dm_select_label = "uuid"
self.fake_config.dm_filter_labels = {"matrix-alertbot": re.compile("dm")} self.fake_config.dm_filter_labels = {"matrix": re.compile("dm")}
self.fake_config.dm_users = BiDict( self.fake_config.dm_users = BiDict(
{"a7b37c33-574c-45ac-bb07-a3b314c2da54": "@fake_dm_user:example.com"} {"a7b37c33-574c-45ac-bb07-a3b314c2da54": "@fake_dm_user:example.com"}
) )
@ -107,7 +107,7 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
"severity": "warning", "severity": "warning",
"job": "job", "job": "job",
"uuid": "a7b37c33-574c-45ac-bb07-a3b314c2da54", "uuid": "a7b37c33-574c-45ac-bb07-a3b314c2da54",
"matrix-alertbot": "dm", "matrix": "dm",
}, },
"annotations": {"description": "some description"}, "annotations": {"description": "some description"},
} }
@ -159,7 +159,8 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
"some description2", "some description2",
notice=False, notice=False,
), ),
] ],
any_order=True,
) )
self.fake_cache.set.assert_called_once_with( self.fake_cache.set.assert_called_once_with(
fake_send_text_to_room.return_value.event_id, fake_send_text_to_room.return_value.event_id,
@ -209,7 +210,8 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
"some description2", "some description2",
notice=False, notice=False,
), ),
] ],
any_order=True,
) )
self.fake_cache.set.assert_called_once_with( self.fake_cache.set.assert_called_once_with(
fake_send_text_to_room.return_value.event_id, fake_send_text_to_room.return_value.event_id,
@ -450,8 +452,9 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
self.fake_cache.set.assert_not_called() self.fake_cache.set.assert_not_called()
self.fake_cache.delete.assert_called_once_with("fingerprint1") self.fake_cache.delete.assert_called_once_with("fingerprint1")
fake_logger.error.assert_called_once_with( fake_logger.exception.assert_called_once_with(
"Unable to send alert fingerprint1 to Matrix room !abcdefg:example.com: Local protocol error" "Unable to send alert fingerprint1 to Matrix room !abcdefg:example.com",
exc_info=fake_send_text_to_room.side_effect,
) )
@patch.object(matrix_alertbot.webhook, "logger", autospec=True) @patch.object(matrix_alertbot.webhook, "logger", autospec=True)
@ -480,8 +483,9 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
) )
fake_create_alert.assert_called_once() fake_create_alert.assert_called_once()
fake_logger.error.assert_called_once_with( fake_logger.exception.assert_called_once_with(
"Unable to send alert fingerprint1 to Matrix room !abcdefg:example.com: Matrix client error" "Unable to send alert fingerprint1 to Matrix room !abcdefg:example.com",
exc_info=fake_create_alert.side_effect,
) )
@patch.object(matrix_alertbot.webhook, "logger", autospec=True) @patch.object(matrix_alertbot.webhook, "logger", autospec=True)
@ -512,15 +516,19 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
self.fake_cache.set.assert_not_called() self.fake_cache.set.assert_not_called()
self.fake_cache.delete.assert_called_once_with("fingerprint1") self.fake_cache.delete.assert_called_once_with("fingerprint1")
fake_logger.error.assert_called_once_with( fake_logger.exception.assert_called_once_with(
"Unable to send alert fingerprint1 to Matrix room !abcdefg:example.com: Exception" "Unable to send alert fingerprint1 to Matrix room !abcdefg:example.com",
exc_info=fake_send_text_to_room.side_effect,
) )
async def test_create_alert_update_silence(self) -> None: async def test_create_alert_update_silence(self) -> None:
fake_alert = Mock(spec=Alert) fake_alert = Alert(
fake_alert.firing = True fingerprint="fingerprint",
fake_alert.fingerprint = "fingerprint" url="https://example.com",
fake_alert.labels = [] firing=True,
labels={"severity": "critical"},
annotations={"description": "dummy description"},
)
await create_alert(fake_alert, self.fake_room_id, self.fake_request) await create_alert(fake_alert, self.fake_room_id, self.fake_request)
@ -533,10 +541,13 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
async def test_create_alert_with_silence_not_found_error( async def test_create_alert_with_silence_not_found_error(
self, fake_send_text_to_room: Mock self, fake_send_text_to_room: Mock
) -> None: ) -> None:
fake_alert = Mock(spec=Alert) fake_alert = Alert(
fake_alert.firing = True fingerprint="fingerprint",
fake_alert.fingerprint = "fingerprint" url="https://example.com",
fake_alert.labels = [] firing=True,
labels={"severity": "critical"},
annotations={"description": "dummy description"},
)
self.fake_alertmanager_client.update_silence.side_effect = SilenceNotFoundError self.fake_alertmanager_client.update_silence.side_effect = SilenceNotFoundError
@ -562,10 +573,13 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
async def test_create_alert_with_silence_extend_error( async def test_create_alert_with_silence_extend_error(
self, fake_send_text_to_room: Mock self, fake_send_text_to_room: Mock
) -> None: ) -> None:
fake_alert = Mock(spec=Alert) fake_alert = Alert(
fake_alert.firing = True fingerprint="fingerprint",
fake_alert.fingerprint = "fingerprint" url="https://example.com",
fake_alert.labels = [] firing=True,
labels={"severity": "critical"},
annotations={"description": "dummy description"},
)
self.fake_alertmanager_client.update_silence.side_effect = SilenceExtendError self.fake_alertmanager_client.update_silence.side_effect = SilenceExtendError
@ -589,10 +603,13 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
@patch.object(matrix_alertbot.webhook, "send_text_to_room", autospec=True) @patch.object(matrix_alertbot.webhook, "send_text_to_room", autospec=True)
async def test_create_alert_not_firing(self, fake_send_text_to_room: Mock) -> None: async def test_create_alert_not_firing(self, fake_send_text_to_room: Mock) -> None:
fake_alert = Mock(spec=Alert) fake_alert = Alert(
fake_alert.firing = False fingerprint="fingerprint",
fake_alert.fingerprint = "fingerprint" url="https://example.com",
fake_alert.labels = [] firing=False,
labels={},
annotations={"description": "dummy description"},
)
await create_alert(fake_alert, self.fake_room_id, self.fake_request) await create_alert(fake_alert, self.fake_room_id, self.fake_request)
@ -610,10 +627,13 @@ class WebhookApplicationTestCase(aiohttp.test_utils.AioHTTPTestCase):
async def test_create_alert_not_firing_raise_matrix_client_error( async def test_create_alert_not_firing_raise_matrix_client_error(
self, fake_send_text_to_room: Mock self, fake_send_text_to_room: Mock
) -> None: ) -> None:
fake_alert = Mock(spec=Alert) fake_alert = Alert(
fake_alert.firing = False fingerprint="fingerprint",
fake_alert.fingerprint = "fingerprint" url="https://example.com",
fake_alert.labels = [] firing=False,
labels={},
annotations={"description": "dummy description"},
)
self.fake_matrix_client_pool.matrix_client = None self.fake_matrix_client_pool.matrix_client = None