From b982a81464fae0cf975f574faf1ac5c7a28ea625 Mon Sep 17 00:00:00 2001 From: Marcelo Robert Santos Date: Fri, 7 Mar 2025 14:54:59 -0300 Subject: [PATCH] feat: add discord webhook helpers Closes #1027 --- .../kernelCI_app/helpers/discordWebhook.py | 65 +++++++++++++++++++ backend/kernelCI_app/helpers/logger.py | 19 ++++++ backend/kernelCI_app/views/buildTestsView.py | 10 +++ docker-compose.yml | 1 + 4 files changed, 95 insertions(+) create mode 100644 backend/kernelCI_app/helpers/discordWebhook.py diff --git a/backend/kernelCI_app/helpers/discordWebhook.py b/backend/kernelCI_app/helpers/discordWebhook.py new file mode 100644 index 000000000..abde8270b --- /dev/null +++ b/backend/kernelCI_app/helpers/discordWebhook.py @@ -0,0 +1,65 @@ +from typing import Any, Optional, TypedDict + +import requests +import os + +from kernelCI_app.helpers.logger import log_message + +# For more information on discord webhook structure, visit +# https://discord.com/developers/docs/resources/webhook#execute-webhook + +AVATAR_URL = "https://avatars.githubusercontent.com/u/11725450?s=200&v=4" +WEBHOOK_NAME = "KernelCI Dashboard Notifications" + + +class DiscordImage(TypedDict): + url: str + width: Optional[int] + height: Optional[int] + + +class DiscordEmbed(TypedDict): + title: str + description: Optional[str] + url: Optional[str] + image: Optional[DiscordImage] + + +def send_discord_notification( + *, + content: Optional[str] = None, + embeds: Optional[list[DiscordEmbed]] = None, + avatar_url: Optional[str] = AVATAR_URL, + webhook_name: Optional[str] = WEBHOOK_NAME, +) -> None: + url = os.getenv("DISCORD_WEBHOOK_URL") + if not url: + log_message("DISCORD_WEBHOOK_URL environment variable is not set.") + return + + if not content and not embeds: + log_message( + "Either content or embeds must be set in order to send notifications." + ) + return + + if embeds is not None and len(embeds) > 10: + log_message("The embed list can contain at most 10 elements.") + return + + data: dict[str, Any] = { + "avatar_url": avatar_url, + "username": webhook_name, + } + if content is not None: + data["content"] = content + if embeds is not None: + data["embeds"] = embeds + + try: + result = requests.post(url=url, json=data) + result.raise_for_status() + except requests.HTTPError as e: + log_message(e) + + return diff --git a/backend/kernelCI_app/helpers/logger.py b/backend/kernelCI_app/helpers/logger.py index c2e12692b..6409c2613 100644 --- a/backend/kernelCI_app/helpers/logger.py +++ b/backend/kernelCI_app/helpers/logger.py @@ -1,4 +1,23 @@ +from django.http import HttpRequest +from datetime import datetime + + # For logging that we care about, we create a function so we can easily use # a more sophisticated logging library later. def log_message(message: str) -> None: print(message) + + +def create_endpoint_notification(*, message: str, request: HttpRequest) -> str: + return ( + message + + "\n\nEndpoint:\n" + + request.build_absolute_uri() + + ( + ("\nBody:\n```json\n" + request.body.decode("utf-8") + "```") + if request.body + else "" + ) + + "\nAccessed in: " + + datetime.now().strftime("%Y-%m-%d %H:%M:%S") + ) diff --git a/backend/kernelCI_app/views/buildTestsView.py b/backend/kernelCI_app/views/buildTestsView.py index 339446b70..f4e8981bd 100644 --- a/backend/kernelCI_app/views/buildTestsView.py +++ b/backend/kernelCI_app/views/buildTestsView.py @@ -1,4 +1,6 @@ from http import HTTPStatus +from kernelCI_app.helpers.discordWebhook import send_discord_notification +from kernelCI_app.helpers.logger import create_endpoint_notification from kernelCI_app.typeModels.buildDetails import BuildTestsResponse from kernelCI_app.models import Tests from drf_spectacular.utils import extend_schema @@ -18,6 +20,7 @@ def get(self, request, build_id: str) -> Response: "start_time", "environment_compatible", "environment_misc", + "build__valid", ) if not result: @@ -26,6 +29,13 @@ def get(self, request, build_id: str) -> Response: status=HTTPStatus.OK, ) + if result[0]["build__valid"] is False: + notification = create_endpoint_notification( + message="Found tests for a failed build.", + request=request, + ) + send_discord_notification(content=notification) + try: valid_response = BuildTestsResponse(result) except ValidationError as e: diff --git a/docker-compose.yml b/docker-compose.yml index c085fb19d..03a95b553 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -45,6 +45,7 @@ services: - DB_DEFAULT_USER=${DB_DEFAULT_USER:-kernelci} - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY} - DEBUG=False + - DISCORD_WEBHOOK_URL=${DISCORD_WEBHOOK_URL} dashboard: build: ./dashboard