"""Service Safe Patching — Quick Win : patching sans interruption de service""" from datetime import datetime from sqlalchemy import text # Packages qui TOUJOURS nécessitent un reboot REBOOT_PACKAGES = [ "kernel", "kernel-core", "kernel-modules", "kernel-tools", "glibc", "glibc-common", "glibc-devel", "systemd", "systemd-libs", "systemd-udev", "dbus", "dbus-libs", "dbus-daemon", "linux-firmware", "microcode_ctl", "polkit", "polkit-libs", "tuned", ] # Standard excludes (middleware/apps — jamais en safe) STD_EXCLUDES = [ "mongodb*", "mysql*", "postgres*", "mariadb*", "oracle*", "pgdg*", "php*", "java*", "redis*", "elasticsearch*", "nginx*", "mod_ssl*", "haproxy*", "certbot*", "python-certbot*", "docker*", "podman*", "centreon*", "qwserver*", "ansible*", "node*", "tina*", "memcached*", "nextcloud*", "pgbouncer*", "pgpool*", "pgbadger*", "psycopg2*", "barman*", "kibana*", "splunk*", ] def build_safe_excludes(): """Construit la liste d'exclusions pour le safe patching""" excludes = list(REBOOT_PACKAGES) + [e.replace("*", "") for e in STD_EXCLUDES] return excludes def build_yum_command(extra_excludes=None): """Génère la commande yum update safe""" all_excludes = REBOOT_PACKAGES + STD_EXCLUDES if extra_excludes: all_excludes += extra_excludes exclude_str = " ".join([f"--exclude={e}*" if not e.endswith("*") else f"--exclude={e}" for e in all_excludes]) return f"yum update {exclude_str} -y" def create_quickwin_campaign(db, year, week_number, label, user_id, assistant_id=None): """Crée une campagne Quick Win avec les deux branches (hprod + prod)""" from .campaign_service import _week_dates wc = f"S{week_number:02d}" lun, mar, mer, jeu = _week_dates(year, week_number) row = db.execute(text(""" INSERT INTO campaigns (week_code, year, label, status, date_start, date_end, created_by, campaign_type) VALUES (:wc, :y, :label, 'draft', :ds, :de, :uid, 'quickwin') RETURNING id """), {"wc": wc, "y": year, "label": label, "ds": lun, "de": jeu, "uid": user_id}).fetchone() cid = row.id # Tous les serveurs Linux en prod secops servers = db.execute(text(""" SELECT s.id, s.hostname, e.name as env_name FROM servers s LEFT JOIN domain_environments de ON s.domain_env_id = de.id LEFT JOIN environments e ON de.environment_id = e.id WHERE s.etat = 'en_production' AND s.patch_os_owner = 'secops' AND s.licence_support IN ('active', 'els') AND s.os_family = 'linux' ORDER BY e.name, s.hostname """)).fetchall() for s in servers: is_prod = (s.env_name == 'Production') date_prevue = mer if is_prod else lun # hprod lundi, prod mercredi db.execute(text(""" INSERT INTO patch_sessions (campaign_id, server_id, status, date_prevue, intervenant_id, forced_assignment, assigned_at) VALUES (:cid, :sid, 'pending', :dp, :uid, true, now()) ON CONFLICT (campaign_id, server_id) DO NOTHING """), {"cid": cid, "sid": s.id, "dp": date_prevue, "uid": user_id}) # Assigner l'assistant si défini if assistant_id: db.execute(text(""" INSERT INTO campaign_operator_limits (campaign_id, user_id, max_servers, note) VALUES (:cid, :aid, 0, 'Assistant Quick Win') """), {"cid": cid, "aid": assistant_id}) count = db.execute(text( "SELECT COUNT(*) FROM patch_sessions WHERE campaign_id = :cid" ), {"cid": cid}).scalar() db.execute(text("UPDATE campaigns SET total_servers = :c WHERE id = :cid"), {"c": count, "cid": cid}) db.commit() return cid def get_quickwin_stats(db, campaign_id): """Stats Quick Win par branche""" return db.execute(text(""" SELECT COUNT(*) FILTER (WHERE e.name != 'Production') as hprod_total, COUNT(*) FILTER (WHERE e.name != 'Production' AND ps.status = 'patched') as hprod_patched, COUNT(*) FILTER (WHERE e.name != 'Production' AND ps.status = 'failed') as hprod_failed, COUNT(*) FILTER (WHERE e.name = 'Production') as prod_total, COUNT(*) FILTER (WHERE e.name = 'Production' AND ps.status = 'patched') as prod_patched, COUNT(*) FILTER (WHERE e.name = 'Production' AND ps.status = 'failed') as prod_failed, COUNT(*) FILTER (WHERE ps.status = 'excluded') as excluded FROM patch_sessions ps JOIN servers s ON ps.server_id = s.id LEFT JOIN domain_environments de ON s.domain_env_id = de.id LEFT JOIN environments e ON de.environment_id = e.id WHERE ps.campaign_id = :cid """), {"cid": campaign_id}).fetchone()