Files
KazV2/bin/worker.py
T

310 lines
7.5 KiB
Python

#!/usr/bin/env python3
#KAN: 29/09/2026
#KOI: permettre de lancer des actions pour les orga depuis paheko
#KI: fab
#structure:
#
#/kaz/
#├── bin/
#│ ├── worker.py
#│ └── worker_actions/
#│ ├── __init__.py
#│ ├── modifier_services.py
#│ └── definir_quota.py
#└── jobs/
# ├── job-123-ABCD.json
# ├── job-789-HIJS.json.encours
# ├── job-456-EFGH.json.OK
# ├── job-089-LMNO.json.KO
# └── ...
import glob
import importlib
import json
import logging
import os
import subprocess
import sys
import urllib.request
BASE_DIR = "/kaz/bin"
JOBS_DIR = "/kaz/jobs"
if BASE_DIR not in sys.path:
sys.path.insert(0, BASE_DIR)
# ----------------------------------------------------------------------
# LOG
# ----------------------------------------------------------------------
LOG_DIR = "/kaz/log"
LOG_FILE = os.path.join(LOG_DIR, "worker.log")
os.makedirs(LOG_DIR, exist_ok=True)
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[
logging.FileHandler(LOG_FILE, encoding="utf-8"),
logging.StreamHandler(sys.stdout),
],
)
log = logging.getLogger(__name__)
# ----------------------------------------------------------------------
# ENVOI D'UN MESSAGE MATTERMOST
# ----------------------------------------------------------------------
def send_mattermost(message):
url_agora = "agora.kaz.bzh"
result = subprocess.run(
[
"bash",
"-c",
". /kaz/secret/env-mattermostAdmin && "
"printf '%s' \"$token_webhook\""
],
capture_output=True,
text=True,
check=True,
)
token_webhook = result.stdout
url = f"https://{url_agora}/hooks/{token_webhook}"
payload = json.dumps({
"channel": "creation-comptes",
"text": message,
}).encode("utf-8")
request = urllib.request.Request(
url,
data=payload,
headers={
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(request) as response:
if response.status < 200 or response.status >= 300:
raise RuntimeError(
f"Erreur Mattermost : HTTP {response.status}"
)
# ----------------------------------------------------------------------
# ENVOI DU MAIL
# ----------------------------------------------------------------------
def send_mail(mail_file, emails):
if not emails:
log.info("Aucun destinataire de mail.")
return
if not os.path.isfile(mail_file):
log.info(f"Aucun mail à envoyer : {mail_file}")
return
with open(mail_file, "r", encoding="utf-8") as f:
mail = f.read()
if not mail.strip():
log.info(f"Le fichier mail est vide : {mail_file}")
return
#PROD
#recipients = [email.strip() for email in emails if email.strip()]
#TEST
recipients = ["fab@kaz.bzh"]
if not recipients:
log.info("Aucun destinataire de mail valide.")
return
#TEST
annonce = (
"#################################################\n"
"Pendant quelques temps, contact@kaz.bzh va recevoir les mails de modification des services.\n"
"Vérifier le contenu du mail et si OK, le transférer à :\n"
+ "\n".join(emails)
+ "\n"
"En supprimant bien sûr cette annonce.\n"
"Plus tard, le mail partira directement, aussi, il faut bien le valider.\n"
"#################################################\n"
"\n"
)
mail = annonce + mail
#FIN TEST
message = (
"From: contact@kaz.bzh\n"
f"To: {', '.join(recipients)}\n"
"Subject: KAZ - informations\n"
"Content-Type: text/plain; charset=UTF-8\n"
"\n"
+ mail
)
log.info(f"Envoi du mail à : {', '.join(recipients)}")
result = subprocess.run(
["/usr/sbin/sendmail", "-t", "-i"],
input=message,
text=True,
capture_output=True,
)
if result.returncode != 0:
raise RuntimeError(
f"sendmail a échoué avec le code {result.returncode}: "
f"{result.stderr}"
)
log.info("Mail envoyé avec succès.")
# ----------------------------------------------------------------------
# RECHERCHE D'UN JOB
# ----------------------------------------------------------------------
jobs = sorted(
glob.glob(
os.path.join(JOBS_DIR, "job-*.json")
)
)
if not jobs:
log.info("Aucun job à traiter.")
sys.exit(0)
# ----------------------------------------------------------------------
# UN SEUL JOB
# ----------------------------------------------------------------------
job = jobs[0]
encours = job + ".encours"
ok = job + ".OK"
ko = job + ".KO"
mail_file = job + ".mail"
log.info(f"Job trouvé : {job}")
# ----------------------------------------------------------------------
# PRISE EN CHARGE DU JOB
# ----------------------------------------------------------------------
try:
os.rename(job, encours)
except FileNotFoundError:
# Le fichier a disparu entre le glob() et le rename().
log.info(f"Job disparu entre temps : {job}")
sys.exit(0)
except OSError as e:
log.error(f"Impossible de prendre le job en charge : {e}")
sys.exit(1)
log.info(f"Job en cours : {encours}")
# ----------------------------------------------------------------------
# LECTURE DU JSON
# ----------------------------------------------------------------------
try:
with open(encours, "r", encoding="utf-8") as f:
message = json.load(f)
log.info(f"Message : {message}")
action = message.get("action")
if not action:
raise ValueError("Le champ 'action' est absent du JSON")
# ------------------------------------------------------------------
# CHARGEMENT ET EXECUTION DE L'ACTION
# ------------------------------------------------------------------
log.info(f"Action demandée : {action}")
module = importlib.import_module(
f"worker_actions.{action}"
)
module.run(message, encours)
# ------------------------------------------------------------------
# ENVOI DU MAIL
# ------------------------------------------------------------------
send_mail(
mail_file,
message.get("emails", [])
)
# if os.path.isfile(mail_file):
# os.remove(mail_file)
# log.info(f"Fichier mail supprimé : {mail_file}")
# ------------------------------------------------------------------
# ENVOI DU MSG MM
# ------------------------------------------------------------------
message_mattermost = json.dumps(
message,
indent=2,
ensure_ascii=False
)
send_mattermost(message_mattermost)
# ------------------------------------------------------------------
# SUCCES
# ------------------------------------------------------------------
os.rename(encours, ok)
log.info(f"Job terminé avec succès : {ok}")
# ----------------------------------------------------------------------
# ERREUR
# ----------------------------------------------------------------------
except Exception as e:
log.exception(
f"Erreur lors du traitement du job : {e}"
)
try:
os.rename(encours, ko)
log.info(f"Job marqué KO : {ko}")
except OSError as rename_error:
log.error(
f"Impossible de renommer le job en .KO : {rename_error}"
)
sys.exit(1)