146 lines
5.7 KiB
Python
146 lines
5.7 KiB
Python
"""Service Microsoft Teams — envoi de messages via Incoming Webhook.
|
|
|
|
Webhook = simple, pas d'auth Azure AD requise.
|
|
Format : Adaptive Card (titre + corps) ou simple "text" en fallback.
|
|
|
|
À noter : Teams a déprécié les "Office 365 Connectors" (anciens webhooks
|
|
chans Teams). Les nouveaux webhooks utilisent "Workflows" (Power Automate)
|
|
qui acceptent un payload Adaptive Card. Ce service envoie un payload
|
|
compatible avec les deux.
|
|
"""
|
|
import json
|
|
import logging
|
|
from datetime import datetime
|
|
from typing import Dict, Any
|
|
|
|
import requests
|
|
|
|
log = logging.getLogger("patchcenter.teams")
|
|
|
|
HTTP_TIMEOUT = 10
|
|
PROXY_URL = None # à override via Settings si besoin
|
|
|
|
|
|
def _proxies():
|
|
if PROXY_URL:
|
|
return {"http": PROXY_URL, "https": PROXY_URL}
|
|
return None
|
|
|
|
|
|
def _post(webhook_url: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Poste un payload sur le webhook Teams. Retourne {ok, status, detail}."""
|
|
try:
|
|
r = requests.post(webhook_url, json=payload,
|
|
timeout=HTTP_TIMEOUT, proxies=_proxies())
|
|
ok = (200 <= r.status_code < 300)
|
|
return {
|
|
"ok": ok,
|
|
"status": r.status_code,
|
|
"detail": (r.text or "")[:500] if not ok else "Message envoyé",
|
|
}
|
|
except requests.exceptions.RequestException as e:
|
|
return {"ok": False, "status": 0, "detail": f"Réseau: {e}"}
|
|
|
|
|
|
def _adaptive_card(title: str, body_lines: list, color: str = "good") -> Dict[str, Any]:
|
|
"""Construit un payload Adaptive Card minimal (compatible Workflows).
|
|
color : 'good' (vert) | 'warning' (orange) | 'attention' (rouge) | 'default'."""
|
|
facts = []
|
|
body_blocks = [
|
|
{"type": "TextBlock", "text": title, "weight": "Bolder",
|
|
"size": "Medium", "color": color, "wrap": True}
|
|
]
|
|
for ln in body_lines:
|
|
if isinstance(ln, dict) and "title" in ln and "value" in ln:
|
|
facts.append({"title": ln["title"], "value": str(ln["value"])})
|
|
else:
|
|
body_blocks.append({"type": "TextBlock", "text": str(ln), "wrap": True,
|
|
"spacing": "Small"})
|
|
if facts:
|
|
body_blocks.append({"type": "FactSet", "facts": facts})
|
|
body_blocks.append({"type": "TextBlock",
|
|
"text": f"_{datetime.now().strftime('%Y-%m-%d %H:%M')} — PatchCenter_",
|
|
"size": "Small", "isSubtle": True, "spacing": "Medium"})
|
|
return {
|
|
"type": "message",
|
|
"attachments": [{
|
|
"contentType": "application/vnd.microsoft.card.adaptive",
|
|
"contentUrl": None,
|
|
"content": {
|
|
"$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
|
|
"type": "AdaptiveCard",
|
|
"version": "1.4",
|
|
"body": body_blocks,
|
|
}
|
|
}]
|
|
}
|
|
|
|
|
|
def send_test_message(webhook_url: str, channel_name: str, sender: str) -> Dict[str, Any]:
|
|
"""Envoie un message de test pour valider la config du webhook."""
|
|
payload = _adaptive_card(
|
|
title=f"✅ Test PatchCenter — canal '{channel_name}'",
|
|
body_lines=[
|
|
"Ce message confirme que le webhook Teams est correctement configuré.",
|
|
{"title": "Envoyé par", "value": sender},
|
|
{"title": "Source", "value": "PatchCenter > Settings > Teams"},
|
|
],
|
|
color="good",
|
|
)
|
|
return _post(webhook_url, payload)
|
|
|
|
|
|
def send_intervention_start(webhook_url: str, hostname: str, application: str,
|
|
intervenant: str, planned_at: str = None) -> Dict[str, Any]:
|
|
"""Annonce le DÉBUT d'une intervention de patching."""
|
|
body = [
|
|
{"title": "Serveur", "value": hostname},
|
|
{"title": "Application", "value": application or "—"},
|
|
{"title": "Intervenant", "value": intervenant or "—"},
|
|
]
|
|
if planned_at:
|
|
body.append({"title": "Prévu", "value": planned_at})
|
|
payload = _adaptive_card(
|
|
title=f"🟧 Début intervention patching — {hostname}",
|
|
body_lines=body,
|
|
color="warning",
|
|
)
|
|
return _post(webhook_url, payload)
|
|
|
|
|
|
def send_intervention_end(webhook_url: str, hostname: str, application: str,
|
|
intervenant: str, status: str = "ok") -> Dict[str, Any]:
|
|
"""Annonce la FIN d'une intervention. status = 'ok' | 'ko'."""
|
|
color = "good" if status == "ok" else "attention"
|
|
icon = "✅" if status == "ok" else "❌"
|
|
payload = _adaptive_card(
|
|
title=f"{icon} Fin intervention patching — {hostname}",
|
|
body_lines=[
|
|
{"title": "Serveur", "value": hostname},
|
|
{"title": "Application", "value": application or "—"},
|
|
{"title": "Intervenant", "value": intervenant or "—"},
|
|
{"title": "Statut technique", "value": status.upper()},
|
|
"_(En attente de validation par le responsable applicatif)_",
|
|
],
|
|
color=color,
|
|
)
|
|
return _post(webhook_url, payload)
|
|
|
|
|
|
def send_planning_reminder(webhook_url: str, hostname: str, application: str,
|
|
jour: str, heure: str, intervenant: str) -> Dict[str, Any]:
|
|
"""Rappel de planning (envoyable du jeudi/vendredi précédent jusqu'au jour J)."""
|
|
payload = _adaptive_card(
|
|
title=f"🗓 Rappel planning patching — {hostname}",
|
|
body_lines=[
|
|
{"title": "Serveur", "value": hostname},
|
|
{"title": "Application", "value": application or "—"},
|
|
{"title": "Date prévue", "value": jour or "—"},
|
|
{"title": "Heure prévue", "value": heure or "—"},
|
|
{"title": "Intervenant", "value": intervenant or "—"},
|
|
"_Merci de confirmer la disponibilité applicative._",
|
|
],
|
|
color="default",
|
|
)
|
|
return _post(webhook_url, payload)
|