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
3 changes: 0 additions & 3 deletions .coveragerc

This file was deleted.

62 changes: 62 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,68 @@ Exemple minimal `.env` :
```dotenv
GITHUB_TOKEN=ghp_................................
```

### Webhooks (notifications)

GitHub Runner Manager supporte l'envoi de notifications via webhooks pour vous tenir informé des événements importants comme le démarrage/arrêt des runners, la construction d'images, ou les mises à jour disponibles.

Pour configurer les webhooks, ajoutez une section `webhooks` dans votre `runners_config.yaml` :

```yaml
webhooks:
enabled: true
timeout: 10
retry_count: 3
retry_delay: 5

# Configuration pour Slack
slack:
enabled: true
webhook_url: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
username: "GitHub Runner Bot"
events:
- runner_started
- runner_error
- build_completed
- update_available
```

Les événements supportés incluent :
- `runner_started` : Quand un runner est démarré
- `runner_stopped` : Quand un runner est arrêté
- `runner_removed` : Quand un runner est supprimé
- `runner_error` : En cas d'erreur avec un runner
- `runner_skipped` : Quand une action sur un runner est ignorée (ex: arrêt d'un runner qui n'est pas démarré)
- `build_started` : Quand la construction d'une image démarre
- `build_completed` : Quand la construction d'une image est terminée
- `build_failed` : Quand la construction d'une image échoue
- `image_updated` : Quand une image est mise à jour
- `update_available` : Quand une mise à jour est disponible
- `update_applied` : Quand une mise à jour est appliquée
- `update_error` : En cas d'erreur lors d'une mise à jour

Plusieurs providers de webhooks sont supportés :
- Slack
- Discord
- Microsoft Teams
- Webhooks génériques

Pour des exemples de configuration complets, consultez :
```bash
cp runners_config.yaml.webhook-example runners_config.yaml
```

#### Tester les webhooks

Pour tester vos webhooks sans déclencher d'actions réelles :

```bash
# Tester un événement spécifique
python main.py webhook test --event runner_started --provider slack

# Tester tous les événements configurés
python main.py webhook test-all --provider slack
```
Un fichier d'exemple `runners_config.yaml.dist` est fourni à la racine du projet. Copiez-le pour créer votre propre configuration :

```bash
Expand Down
150 changes: 148 additions & 2 deletions runners_config.yaml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,151 @@ scheduler:
actions:
- check
- build
- deploy
max_retries: 3
- deploy
max_retries: 3


webhooks:
# Configuration générale des webhooks
enabled: true
timeout: 10 # Délai d'attente en secondes global
retry_count: 3
retry_delay: 5 # Secondes

# Configuration spécifique pour Slack
slack:
enabled: true
webhook_url: "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"
username: "GitHub Runner Bot"
timeout: 10 # Délai d'attente spécifique à Slack

# Liste des événements à notifier
events:
- runner_started
- runner_stopped
- runner_error
- build_started
- build_completed
- build_failed
- update_available
- update_applied

# Templates de messages pour différents événements
templates:
# Template par défaut (utilisé si pas de template spécifique)
default:
title: "Notification GitHub Runner Manager"
text: "Un événement s'est produit"
color: "#36a64f" # Vert
use_attachment: true
fields: []

# Runner démarré
runner_started:
title: "Runner démarré"
text: "Le runner *{runner_name}* a été démarré avec succès"
color: "#36a64f" # Vert
fields:
- name: "Runner ID"
value: "{runner_id}"
short: true
- name: "Labels"
value: "{labels}"
short: true
- name: "Technologie"
value: "{techno} {techno_version}"
short: true

# Runner arrêté
runner_stopped:
title: "Runner arrêté"
text: "Le runner *{runner_name}* a été arrêté"
color: "#ff9000" # Orange
fields:
- name: "Runner ID"
value: "{runner_id}"
short: true
- name: "Temps de fonctionnement"
value: "{uptime}"
short: true

# Erreur runner
runner_error:
title: "⚠️ Erreur Runner"
text: "Une erreur s'est produite avec le runner *{runner_name}*"
color: "#ff0000" # Rouge
fields:
- name: "Runner ID"
value: "{runner_id}"
short: true
- name: "Erreur"
value: "```{error_message}```"
short: false

# Build commencé
build_started:
title: "Build démarré"
text: "Construction de l'image *{image_name}* démarrée"
color: "#3AA3E3" # Bleu
fields:
- name: "Base image"
value: "{base_image}"
short: true
- name: "Technologie"
value: "{techno} {techno_version}"
short: true

# Build terminé
build_completed:
title: "Build terminé"
text: "Construction de l'image *{image_name}* terminée avec succès en {duration}s"
color: "#36a64f" # Vert
fields:
- name: "Image"
value: "{image_name}"
short: true
- name: "Taille"
value: "{image_size}"
short: true

# Build échoué
build_failed:
title: "⚠️ Build échoué"
text: "La construction de l'image *{image_name}* a échoué"
color: "#ff0000" # Rouge
fields:
- name: "Erreur"
value: "```{error_message}```"
short: false

# Mise à jour disponible
update_available:
title: "Mise à jour disponible"
text: "Une mise à jour est disponible pour l'image *{image_name}*"
color: "#3AA3E3" # Bleu
fields:
- name: "Version actuelle"
value: "{current_version}"
short: true
- name: "Nouvelle version"
value: "{new_version}"
short: true
- name: "Auto-update"
value: "{auto_update}"
short: true

# Mise à jour appliquée
update_applied:
title: "Mise à jour appliquée"
text: "L'image *{image_name}* a été mise à jour avec succès"
color: "#36a64f" # Vert
fields:
- name: "Ancienne version"
value: "{old_version}"
short: true
- name: "Nouvelle version"
value: "{new_version}"
short: true
- name: "Runners impactés"
value: "{affected_runners}"
short: false
33 changes: 33 additions & 0 deletions src/notifications/channels/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Canaux de notifications (interface + registry)."""

from __future__ import annotations

from typing import List, Protocol

from ..events import NotificationEvent


class NotificationChannel(Protocol):
name: str

def supports(
self, event: NotificationEvent
) -> bool: # pragma: no cover (interface)
...

def send(self, event: NotificationEvent) -> None: # pragma: no cover (interface)
...


_registry: List[NotificationChannel] = []


def register(channel: NotificationChannel) -> None:
_registry.append(channel)


def channels() -> List[NotificationChannel]:
return list(_registry)


__all__ = ["NotificationChannel", "register", "channels"]
39 changes: 39 additions & 0 deletions src/notifications/channels/webhook.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""Canal de notification via le service Webhook existant."""

from __future__ import annotations

from src.services.webhook_service import WebhookService

from ..events import NotificationEvent
from .base import NotificationChannel, register


class WebhookChannel:
name = "webhook"

def __init__(self, webhook_service: WebhookService):
self._svc = webhook_service

# Tous les événements sont supportés, filtrage déjà assuré côté WebhookService via config
def supports(self, event: NotificationEvent) -> bool: # pragma: no cover - trivial
return True

def send(self, event: NotificationEvent) -> None:
payload = event.to_payload()
event_type = payload.pop("event_type")
# Compat: ne pas inclure timestamp ni valeurs None pour ne pas casser anciens tests
payload.pop("timestamp", None)
compact = {k: v for k, v in payload.items() if v is not None}
# Compat héritage: retirer restarted si False
if compact.get("restarted") is False:
compact.pop("restarted")
self._svc.notify(event_type, compact)


def build_and_register(webhook_service: WebhookService) -> NotificationChannel:
channel = WebhookChannel(webhook_service)
register(channel)
return channel


__all__ = ["WebhookChannel", "build_and_register"]
22 changes: 22 additions & 0 deletions src/notifications/dispatcher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""Dispatcher qui route les événements vers les canaux enregistrés."""

from __future__ import annotations

from typing import Iterable

from .channels.base import channels
from .events import NotificationEvent


class NotificationDispatcher:
def dispatch(self, event: NotificationEvent) -> None:
for ch in channels():
if ch.supports(event):
ch.send(event)

def dispatch_many(self, events: Iterable[NotificationEvent]) -> None:
for e in events:
self.dispatch(e)


__all__ = ["NotificationDispatcher"]
Loading