un worker peut prendre en charge des json présents dans /kaz/jobs (pour les services des orgas)

This commit is contained in:
fab
2026-09-29 23:47:59 +02:00
parent 58fa9024b8
commit 2ffb2fac07
3 changed files with 454 additions and 30 deletions
+309
View File
@@ -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)
+77
View File
@@ -0,0 +1,77 @@
import logging
import os
import subprocess
#ex de json accepté:
#{
# "action": "modifier_services",
# "creer": [
# "cloud"
# ],
# "enlever": [
# "paheko",
# "wp"
# ],
# "nom_court_orga": "monorga11",
# "emails": [
# "admin@kaz.bzh",
# "contact@kaz.bzh"
# ]
# }
log = logging.getLogger(__name__)
def run(message, encours):
nom_court_orga = message["nom_court_orga"]
services_creer = message.get("creer", [])
services_enlever = message.get("enlever", [])
script = "/kaz/config/orgaTmpl/orga-gen.sh"
mail_file = encours.removesuffix(".encours") + ".mail"
command = [script]
command.extend(
f"+{service}"
for service in services_creer
)
command.extend(
f"-{service}"
for service in services_enlever
)
command.append(nom_court_orga)
log.info(
f"Exécution : {' '.join(command)}"
)
env = os.environ.copy()
env["ORGA_GEN_MAIL_FILE"] = mail_file
result = subprocess.run(
command,
capture_output=True,
text=True,
env=env,
)
log.info(
f"Résultat du script :\n{result.stdout}"
)
if result.stderr:
log.warning(
f"Sortie stderr du script :\n{result.stderr}"
)
if result.returncode != 0:
raise RuntimeError(
f"Le script a échoué avec le code "
f"{result.returncode}"
)
+65 -27
View File
@@ -440,7 +440,7 @@ if [[ -n "${STAGE_DEFAULT}${STAGE_CREATE}" ]]; then
ln -sf ../../config/orgaTmpl/orga-gen.sh
ln -sf ../../config/orgaTmpl/orga-rm.sh
ln -sf ../../config/orgaTmpl/init-paheko.sh
#ln -sf ../../config/orgaTmpl/init-paheko.sh
#ln -sf ../../config/orgaTmpl/initdb.d/
#ln -sf ../../config/orgaTmpl/app/
ln -sf ../../config/orgaTmpl/wiki-conf/
@@ -525,7 +525,8 @@ fi
if [[ -n "${STAGE_DEFAULT}${STAGE_INIT}" ]]; then
[ -z "$DBaInitialiser" ] || ./init-db.sh $DBaInitialiser
# ########## init services
[[ "${paheko}" = "on" ]] && ./init-paheko.sh
#[[ "${paheko}" = "on" ]] && ./init-paheko.sh
[[ "${paheko}" = "on" ]]
# initCmd="--install"
# # XXX risque d'écraser user DB
# [[ "${cloud}" = "on" ]] && initCmd="$initCmd -cloud"
@@ -555,10 +556,60 @@ if [[ -n "${STAGE_DEFAULT}${STAGE_CREATE}" ]]; then
MAIL="Bonjour,
et hop ! voici les accès au(x) service(s) demandé(s):
et hop ! comme demandé:
"
if [[ "${wp}" = "on" ]]; then
#quels services ont été arrêtés ou démarés ?
for ARG in "$@"; do
case "${ARG}" in
'-paheko'* )
MAIL="$MAIL
Paheko (compta) a été supprimé.
"
(
cd /kaz/dockers/paheko/ &&
./docker-compose-gen.sh &&
docker-compose down &&
docker-compose up -d
)
;;
'-wp'* )
MAIL="$MAIL
Le site web a été supprimé.
"
;;
'-cloud'* )
MAIL="$MAIL
Le cloud dédié a été supprimé.
"
;;
'+paheko'* )
MAIL_ADMIN_PASS=$(apg -n 1 -m 16 -M NCL)
MAIL="$MAIL
Paheko: il te faut terminer l'installation
adresse: https://${ORGA}-paheko.kaz.bzh/admin/
Dans 'Nom de l'association' et dans 'Nom et prénom', mets: ${ORGA}
Dans 'Adresse E-Mail', mets: ${GREEN}XXXXXX${RESET}
Dans les 2 champs 'Mots de passe', mets: ${MAIL_ADMIN_PASS}
doc: https://wiki.kaz.bzh/paheko/start
"
(
cd /kaz/dockers/paheko/ &&
./docker-compose-gen.sh &&
docker-compose down &&
docker-compose up -d
)
;;
'+wp'* )
MAIL_ADMIN=`grep WORDPRESS_ADMIN_USER /kaz/secret/orgas/${ORGA}/env-wpServ | cut -f2 -d=`
MAIL_ADMIN_PASS=`grep WORDPRESS_ADMIN_PASSWORD /kaz/secret/orgas/${ORGA}/env-wpServ | cut -f2 -d=`
@@ -573,9 +624,9 @@ La première chose à faire est de paramétrer ton site pour qu'il puisse envoye
Si tu as envie d'avoir comme adresse de site , https://${ORGA}.kaz.bzh au lieu de https://${ORGA}-wp.kaz.bzh, fais nous signe !
"
fi
;;
if [[ "${cloud}" = "on" ]]; then
'+cloud'* )
MAIL_ADMIN=`grep NEXTCLOUD_ADMIN_USER /kaz/secret/orgas/${ORGA}/env-nextcloudServ | cut -f2 -d=`
MAIL_ADMIN_PASS=`grep NEXTCLOUD_ADMIN_PASSWORD /kaz/secret/orgas/${ORGA}/env-nextcloudServ | cut -f2 -d=`
@@ -588,20 +639,9 @@ mot de passe: ${MAIL_ADMIN_PASS}
doc: https://wiki.kaz.bzh/nextcloud/start
La première chose à faire est de paramétrer ton cloud pour qu'il puisse envoyer des mails: https://wiki.kaz.bzh/nextcloud/administration/start#pour_parametrer_l_envoi_des_mails_de_notification_depuis_votre_cloud
"
fi
if [[ "${paheko}" = "on" ]]; then
MAIL_ADMIN_PASS=$(apg -n 1 -m 16 -M NCL)
MAIL="$MAIL
Paheko:
adresse: https://${ORGA}-paheko.kaz.bzh/admin/
identifiant: ${GREEN}--mettre ici le mail de l'orga--${RESET}
mot de passe: ${MAIL_ADMIN_PASS}
doc: https://wiki.kaz.bzh/paheko/start
"
fi
;;
esac
done
MAIL="$MAIL
Pour tes prochaines questions, nous t'invitons à les poser sur notre agora, dans le canal \"Un soucis, une question\" sur
@@ -616,6 +656,10 @@ L'équipe Sysadmin, pour la collégiale de Kaz."
fi
if [[ -n "${ORGA_GEN_MAIL_FILE}" ]]; then
echo -e "$MAIL" > "$ORGA_GEN_MAIL_FILE"
fi
echo -e "${GREEN}"
echo "*****************************************************"
echo "Voici le mail à copier/coller et à envoyer à l'orga :"
@@ -623,11 +667,5 @@ echo "*****************************************************"
echo -e "${RESET}"
echo -e "$MAIL"
echo -e "${GREEN}"
echo "MAIS AVANT D'ENVOYER CE MAIL, IL FAUT :
* completer la fiche paheko de l'orga en cochant les services crees"
if [[ "${paheko}" = "on" ]]; then
echo " * lancer /kaz/dockers/paheko/docker-compose-gen.sh, restart le docker puis finir d'installer paheko en allant sur https://${ORGA}-paheko.kaz.bzh/admin/"
fi
echo -e ${RESET}
echo -e "${RESET}"
##### FIN MAIL