#!/usr/bin/env bash set -euo pipefail ROOT="${1:-.}" need_file() { local f="$1" if [[ ! -f "$ROOT/$f" ]]; then echo "Fichier introuvable: $f" >&2 exit 1 fi } echo "==> Vérification du dépôt" need_file "domains.yaml" need_file "scripts/generate_bpm.py" need_file "Makefile" need_file "ansible/roles/life_noc/defaults/main.yml" need_file "ansible/roles/life_noc/tasks/main.yml" echo "==> Sauvegarde minimale" mkdir -p "$ROOT/.patch-backup" cp -a "$ROOT/scripts/generate_bpm.py" "$ROOT/.patch-backup/generate_bpm.py.bak" cp -a "$ROOT/Makefile" "$ROOT/.patch-backup/Makefile.bak" cp -a "$ROOT/ansible/roles/life_noc/defaults/main.yml" "$ROOT/.patch-backup/life_noc_defaults_main.yml.bak" cp -a "$ROOT/ansible/roles/life_noc/tasks/main.yml" "$ROOT/.patch-backup/life_noc_tasks_main.yml.bak" [[ -f "$ROOT/preuve-de-reproductibilite.md" ]] && cp -a "$ROOT/preuve-de-reproductibilite.md" "$ROOT/.patch-backup/preuve-de-reproductibilite.md.bak" [[ -f "$ROOT/decisions-techniques.md" ]] && cp -a "$ROOT/decisions-techniques.md" "$ROOT/.patch-backup/decisions-techniques.md.bak" echo "==> Réécriture de scripts/generate_bpm.py" cat > "$ROOT/scripts/generate_bpm.py" <<'PYEOF' #!/usr/bin/env python3 from __future__ import annotations import re from pathlib import Path import sys import yaml ROOT = Path(__file__).resolve().parents[1] DOMAINS_YAML = ROOT / "domains.yaml" OUTPUT_DIR = ROOT / "bpm" OUTPUT_FILE = OUTPUT_DIR / "Life-NOC.conf" HOST_NAME = "life-noc" TITLE = "Life-NOC" OWNER = "icingadmin" def slugify(value: str) -> str: value = value.strip().lower() replacements = { "à": "a", "â": "a", "ä": "a", "ç": "c", "é": "e", "è": "e", "ê": "e", "ë": "e", "î": "i", "ï": "i", "ô": "o", "ö": "o", "ù": "u", "û": "u", "ü": "u", "ÿ": "y", "œ": "oe", "æ": "ae", "'": "", "’": "", } for old, new in replacements.items(): value = value.replace(old, new) value = re.sub(r"[^a-z0-9\-]+", "-", value) value = re.sub(r"-{2,}", "-", value) return value.strip("-") def aliasify(value: str) -> str: value = value.strip().upper() replacements = { "À": "A", "Â": "A", "Ä": "A", "Ç": "C", "É": "E", "È": "E", "Ê": "E", "Ë": "E", "Î": "I", "Ï": "I", "Ô": "O", "Ö": "O", "Ù": "U", "Û": "U", "Ü": "U", "Ÿ": "Y", "Œ": "OE", "Æ": "AE", "'": "", "’": "", } for old, new in replacements.items(): value = value.replace(old, new) value = re.sub(r"[^A-Z0-9\-]+", "-", value) value = re.sub(r"-{2,}", "-", value) return value.strip("-") def read_domains() -> dict: data = yaml.safe_load(DOMAINS_YAML.read_text(encoding="utf-8")) if not isinstance(data, dict) or "domains" not in data: raise ValueError("domains.yaml doit contenir une clé racine 'domains'") domains = data["domains"] if not isinstance(domains, dict): raise ValueError("domains.yaml: 'domains' doit être un mapping") return domains def collect_services(domain_data: dict, domain_name: str) -> list[str]: items = domain_data.get("items", []) if not isinstance(items, list): raise ValueError(f"Domaine '{domain_name}': 'items' doit être une liste") services: list[str] = [] for item in items: if not isinstance(item, dict): raise ValueError(f"Domaine '{domain_name}': item invalide (dict attendu)") item_name = item.get("name") if not item_name: raise ValueError(f"Domaine '{domain_name}': item sans champ 'name'") service_name = f"{slugify(domain_name)}-{slugify(str(item_name))}" services.append(f"{HOST_NAME};{service_name}") if not services: raise ValueError(f"Domaine '{domain_name}' ne contient aucun item") return services def header() -> str: return f"""### Business Process Config File ### # # Title : {TITLE} # Description : # Owner : {OWNER} # AddToMenu : yes # Backend : # Statetype : soft # ################################### """ def main() -> int: domains = read_domains() OUTPUT_DIR.mkdir(parents=True, exist_ok=True) lines: list[str] = [header()] aliases: list[tuple[str, str]] = [] for domain_name, domain_data in domains.items(): if not isinstance(domain_data, dict): raise ValueError(f"Domaine '{domain_name}': structure invalide") alias = aliasify(domain_name) label = domain_data.get("label", domain_name) services = collect_services(domain_data, domain_name) expr = " & ".join(services) lines.append(f"{alias} = {expr}\n") aliases.append((alias, str(label))) lines.append("\n") root_expr = " & ".join(alias for alias, _label in aliases) lines.append(f"LIFE-NOC = {root_expr}\n") lines.append("display 1;LIFE-NOC;LIFE-NOC\n") for alias, label in aliases: lines.append(f"display 1;{alias};{label}\n") OUTPUT_FILE.write_text("".join(lines), encoding="utf-8") print(f"BPM généré: {OUTPUT_FILE.relative_to(ROOT)}") print("Processus générés :") print(" - LIFE-NOC") for alias, _label in aliases: print(f" - {alias}") return 0 if __name__ == "__main__": try: raise SystemExit(main()) except Exception as exc: print(f"ERREUR: {exc}", file=sys.stderr) raise PYEOF chmod +x "$ROOT/scripts/generate_bpm.py" echo "==> Ajustements texte ciblés" python3 - "$ROOT" <<'PYEOF' from pathlib import Path import re import sys root = Path(sys.argv[1]) def replace_text(path_str, replacements): path = root / path_str if not path.exists(): return text = path.read_text(encoding="utf-8") original = text for old, new in replacements: text = text.replace(old, new) if text != original: path.write_text(text, encoding="utf-8") print(f"Modifié: {path_str}") def regex_replace(path_str, pattern, repl, must_match=False): path = root / path_str if not path.exists(): return text = path.read_text(encoding="utf-8") new_text, count = re.subn(pattern, repl, text, flags=re.MULTILINE) if must_match and count == 0: raise SystemExit(f"Aucun match dans {path_str} pour: {pattern}") if new_text != text: path.write_text(new_text, encoding="utf-8") print(f"Modifié: {path_str}") # Makefile replace_text("Makefile", [ ("bpm/life-noc.json", "bpm/Life-NOC.conf"), ]) # defaults main.yml replace_text("ansible/roles/life_noc/defaults/main.yml", [ ("/etc/icingaweb2/modules/businessprocess/processes/life-noc.json", "/etc/icingaweb2/modules/businessprocess/processes/Life-NOC.conf"), ]) # tasks main.yml replace_text("ansible/roles/life_noc/tasks/main.yml", [ ("bpm/life-noc.json", "bpm/Life-NOC.conf"), ("life-noc.json", "Life-NOC.conf"), ]) # docs for doc in ["preuve-de-reproductibilite.md", "decisions-techniques.md"]: replace_text(doc, [ ("bpm/life-noc.json", "bpm/Life-NOC.conf"), ("life-noc.json", "Life-NOC.conf"), ("export JSON", "export natif Business Process"), ("artefact JSON", "artefact natif Business Process"), ]) PYEOF echo "==> Nettoyage de l'ancien artefact si présent" rm -f "$ROOT/bpm/life-noc.json" echo "==> Génération du nouveau BPM" ( cd "$ROOT" python3 scripts/generate_bpm.py ) echo "==> Vérification rapide" if [[ -f "$ROOT/bpm/Life-NOC.conf" ]]; then echo "OK: bpm/Life-NOC.conf généré" else echo "ERREUR: bpm/Life-NOC.conf absent" >&2 exit 1 fi echo echo "Patch terminé." echo "Fichiers sauvegardés dans: $ROOT/.patch-backup" echo "Prochaine étape recommandée:" echo " make check" echo "Puis redéploiement avec BPM activé."