"""Healthcheck endpoint - GMAO Collège""" from flask import Blueprint, jsonify from app_new.extensions import db from app_new.core.models import User from app_new.version import get_version import subprocess import os import time from datetime import datetime health_bp = Blueprint('health', __name__, url_prefix='/health') # Constantes WATCHDOG_STARTUP_GRACE_SECONDS = 60 APP_START_TIME = time.time() def _check_watchdog(name, start_time_threshold=None): """Verifie si un processus watchdog tourne via /proc.""" try: pids = [] for pid in os.listdir('/proc'): if not pid.isdigit(): continue try: cmdline_path = f'/proc/{pid}/cmdline' with open(cmdline_path, 'rb') as f: cmdline = f.read().decode('utf-8', errors='replace').replace('\x00', ' ') if name in cmdline and 'grep' not in cmdline: pids.append(int(pid)) except (FileNotFoundError, PermissionError): continue if pids: return {'running': True, 'pid': pids[0], 'instances': len(pids)} # Grace period au demarrage if start_time_threshold and time.time() - APP_START_TIME < start_time_threshold: return {'running': False, 'starting': True} return {'running': False} except Exception as e: return {'running': False, 'error': str(e)} def _check_database(): """Verifie la connexion a la base de donnees.""" try: db.session.execute(db.text('SELECT 1')) return {'connected': True, 'dialect': str(db.engine.url).split('://')[0]} except Exception as e: return {'connected': False, 'error': str(e)} @health_bp.route('/') def health(): """Endpoint public de healthcheck.""" db_status = _check_database() # Le watchdog pronote met parfois plus de temps a demarrer (connexion ENT) watchdogs = { 'flask': _check_watchdog('gunicorn'), 'gmao_watchdog': _check_watchdog('gmao_watchdog.py'), 'ent_watchdog': _check_watchdog('ent_watchdog.py'), 'pronote_watchdog': _check_watchdog('pronote_watchdog.py', start_time_threshold=WATCHDOG_STARTUP_GRACE_SECONDS), 'watchdog_dnd': _check_watchdog('watchdog_dnd.py'), } all_watchdogs_ok = all( w.get('running') or w.get('starting') for w in watchdogs.values() ) healthy = db_status.get('connected') and all_watchdogs_ok and not any(w.get('starting') for w in watchdogs.values()) git_commit = 'unknown' try: result = subprocess.run( ['git', '-C', '/app', 'rev-parse', '--short', 'HEAD'], capture_output=True, text=True, timeout=5 ) if result.returncode == 0: git_commit = result.stdout.strip() except Exception: pass # Fallback si git n'est pas disponible dans l'image if git_commit == 'unknown': version_file = '/app/.git-version' if os.path.exists(version_file): try: with open(version_file) as f: git_commit = f.read().strip() except Exception: pass response = { 'status': 'healthy' if healthy else 'degraded', 'version': get_version(), 'timestamp': datetime.utcnow().isoformat() + 'Z', 'database': db_status, 'watchdogs': watchdogs, 'git_commit': git_commit, 'environment': os.environ.get('FLASK_ENV', 'production'), 'uptime_seconds': int(time.time() - APP_START_TIME) } return jsonify(response), 200 if healthy else 503