un worker peut prendre en charge des json présents dans /kaz/jobs (pour les services des orgas)
This commit is contained in:
+309
@@ -0,0 +1,309 @@
|
||||
#!/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)
|
||||
Reference in New Issue
Block a user