feat: add slide generation script for CESIZen project, covering deployment and security aspects
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Génère les schémas du dossier Bloc 3 (Déployer et sécuriser) — CESIZen.
|
||||
Sortie : ./assets_bloc3/*.png (réutilisés dans le .docx et le .pptx)
|
||||
"""
|
||||
import os
|
||||
import matplotlib
|
||||
matplotlib.use("Agg")
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
|
||||
import matplotlib.font_manager as fm
|
||||
|
||||
# ─── Charte CESIZen ───────────────────────────────────────────────
|
||||
GREEN = "#06C656"
|
||||
GREEN_D = "#049641"
|
||||
BLUE = "#004682"
|
||||
BLUE_L = "#E6F0FA"
|
||||
GREY_D = "#232323"
|
||||
GREY = "#6E6E6E"
|
||||
WHITE = "#FFFFFF"
|
||||
INK = "#23232D"
|
||||
RED = "#C8102E"
|
||||
ORANGE = "#E4801C"
|
||||
YELLOW_A = "#F0C808"
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
OUT = os.path.join(HERE, "assets_bloc3")
|
||||
os.makedirs(OUT, exist_ok=True)
|
||||
|
||||
plt.rcParams["font.family"] = "DejaVu Sans"
|
||||
|
||||
|
||||
def box(ax, x, y, w, h, text, fc, ec=None, tc="white", fs=10, bold=True, radius=0.02):
|
||||
ec = ec or fc
|
||||
p = FancyBboxPatch((x, y), w, h, boxstyle=f"round,pad=0.005,rounding_size={radius}",
|
||||
fc=fc, ec=ec, lw=1.4, mutation_aspect=1)
|
||||
ax.add_patch(p)
|
||||
ax.text(x + w / 2, y + h / 2, text, ha="center", va="center",
|
||||
color=tc, fontsize=fs, fontweight="bold" if bold else "normal", zorder=5)
|
||||
|
||||
|
||||
def arrow(ax, x1, y1, x2, y2, color=GREY_D, style="-|>", lw=1.8, ls="-"):
|
||||
a = FancyArrowPatch((x1, y1), (x2, y2), arrowstyle=style, mutation_scale=16,
|
||||
color=color, lw=lw, linestyle=ls, zorder=1)
|
||||
ax.add_patch(a)
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# 1. ARCHITECTURE DE DÉPLOIEMENT + CHAÎNE CI/CD
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
def diagram_deploiement():
|
||||
fig, ax = plt.subplots(figsize=(12, 6.6))
|
||||
ax.set_xlim(0, 12); ax.set_ylim(0, 6.6); ax.axis("off")
|
||||
|
||||
ax.text(6, 6.35, "Chaîne de déploiement CESIZen — du poste développeur à la production",
|
||||
ha="center", fontsize=13, fontweight="bold", color=BLUE)
|
||||
|
||||
# --- Bandeau des 3 environnements ---
|
||||
envs = [
|
||||
(0.3, "DÉVELOPPEMENT", "Poste local", GREEN,
|
||||
["Docker Compose (dev)", "PostgreSQL 16 + Redis 7", "Hot-reload back & front", "APP_DEBUG=true"]),
|
||||
(4.15, "TEST / INTÉGRATION", "Gitea Actions (CI)", ORANGE,
|
||||
["Runner conteneurisé", "Base SQLite en mémoire", "29 tests automatisés", "Lint (Pint / ESLint)"]),
|
||||
(8.0, "PRODUCTION", "Coolify (PaaS)", BLUE,
|
||||
["Docker Compose (prod)", "HTTPS / TLS (reverse proxy)", "Secrets générés hors dépôt", "Health checks + restart"]),
|
||||
]
|
||||
for x, titre, sous, col, items in envs:
|
||||
box(ax, x, 3.4, 3.55, 2.2, "", BLUE_L, ec=col, tc=INK)
|
||||
box(ax, x, 5.0, 3.55, 0.6, titre, col, fs=11)
|
||||
ax.text(x + 1.775, 4.75, sous, ha="center", fontsize=9.5, style="italic", color=GREY_D)
|
||||
for i, it in enumerate(items):
|
||||
ax.text(x + 0.15, 4.45 - i * 0.27, "• " + it, ha="left", fontsize=8.5, color=INK)
|
||||
|
||||
arrow(ax, 3.95, 4.5, 4.1, 4.5, color=GREY_D)
|
||||
arrow(ax, 7.8, 4.5, 7.95, 4.5, color=GREY_D)
|
||||
|
||||
# --- Chaîne CI/CD (bas) ---
|
||||
ax.text(6, 2.9, "Flux d'intégration & de livraison continues", ha="center",
|
||||
fontsize=11, fontweight="bold", color=GREEN_D)
|
||||
steps = [
|
||||
(0.3, "git push\n(Gitea)", GREY_D),
|
||||
(2.55, "Gitea Actions\ndéclenché", ORANGE),
|
||||
(4.8, "Build + Tests\n(29 tests)", ORANGE),
|
||||
(7.05, "Webhook\nCoolify", BLUE),
|
||||
(9.3, "Build images\n+ déploiement", BLUE),
|
||||
]
|
||||
for x, t, col in steps:
|
||||
box(ax, x, 1.3, 2.05, 1.05, t, col, fs=9.5)
|
||||
for i in range(len(steps) - 1):
|
||||
arrow(ax, steps[i][0] + 2.05, 1.82, steps[i + 1][0], 1.82, color=GREEN_D, lw=2)
|
||||
|
||||
box(ax, 9.3, 0.15, 2.05, 0.8, "cesizen.cleanows.fr\napi.cesizen.cleanows.fr", GREEN, fs=8)
|
||||
arrow(ax, 10.32, 1.3, 10.32, 0.95, color=GREEN_D, lw=2)
|
||||
ax.text(0.3, 0.55, "Rollback : redéploiement de la version\nGit précédente (tag) en 1 clic Coolify.",
|
||||
ha="left", fontsize=8, color=GREY, style="italic")
|
||||
|
||||
plt.tight_layout()
|
||||
fig.savefig(os.path.join(OUT, "archi_deploiement.png"), dpi=170, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# 2. MATRICE DE RISQUES (criticité = gravité × probabilité)
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
def diagram_matrice_risques():
|
||||
fig, ax = plt.subplots(figsize=(11, 6.8))
|
||||
n = 5
|
||||
for g in range(n): # gravité (y)
|
||||
for p in range(n): # probabilité (x)
|
||||
crit = (g + 1) * (p + 1)
|
||||
if crit <= 4:
|
||||
c = "#2E9E4F"
|
||||
elif crit <= 9:
|
||||
c = "#F0C808"
|
||||
elif crit <= 14:
|
||||
c = "#E4801C"
|
||||
else:
|
||||
c = "#C8102E"
|
||||
ax.add_patch(plt.Rectangle((p, g), 1, 1, fc=c, ec="white", lw=2, alpha=0.9))
|
||||
ax.text(p + 0.5, g + 0.5, str(crit), ha="center", va="center",
|
||||
fontsize=9, color="white", fontweight="bold", alpha=0.6)
|
||||
|
||||
# Positionnement des risques réels du projet (P sur 1..5, G sur 1..5)
|
||||
risques = [
|
||||
("R1", 2, 4), # Fuite données de santé (Art.9)
|
||||
("R2", 3, 3), # Injection / XSS
|
||||
("R3", 2, 3), # Compromission compte admin
|
||||
("R4", 3, 2), # DDoS / indisponibilité
|
||||
("R5", 2, 2), # Perte de données (sauvegarde)
|
||||
("R6", 4, 2), # Dépendances vulnérables
|
||||
("R7", 3, 4), # Absence de chiffrement au repos
|
||||
("R8", 2, 1), # Erreur humaine déploiement
|
||||
]
|
||||
for nom, p, g in risques:
|
||||
ax.plot(p - 0.5, g - 0.5, "o", ms=22, mfc="white", mec=INK, mew=1.8, zorder=6)
|
||||
ax.text(p - 0.5, g - 0.5, nom, ha="center", va="center", fontsize=8.5,
|
||||
fontweight="bold", color=INK, zorder=7)
|
||||
|
||||
ax.set_xlim(0, n); ax.set_ylim(0, n)
|
||||
ax.set_xticks([i + 0.5 for i in range(n)])
|
||||
ax.set_yticks([i + 0.5 for i in range(n)])
|
||||
ax.set_xticklabels(["Très faible", "Faible", "Moyenne", "Forte", "Très forte"], fontsize=9)
|
||||
ax.set_yticklabels(["Mineure", "Modérée", "Grave", "Critique", "Catastroph."], fontsize=9)
|
||||
ax.set_xlabel("Probabilité →", fontsize=11, fontweight="bold", color=BLUE)
|
||||
ax.set_ylabel("Gravité →", fontsize=11, fontweight="bold", color=BLUE)
|
||||
ax.set_title("Matrice de criticité des risques CESIZen (criticité = gravité × probabilité)",
|
||||
fontsize=12.5, fontweight="bold", color=BLUE, pad=12)
|
||||
for s in ax.spines.values():
|
||||
s.set_visible(False)
|
||||
ax.tick_params(length=0)
|
||||
|
||||
legende = ("R1 Fuite de données de santé R2 Injection SQL/XSS R3 Compromission compte admin "
|
||||
"R4 DDoS / indisponibilité\nR5 Perte de données R6 Dépendances vulnérables "
|
||||
"R7 Absence de chiffrement au repos R8 Erreur humaine de déploiement")
|
||||
fig.text(0.5, -0.02, legende, ha="center", fontsize=8.3, color=GREY_D)
|
||||
|
||||
plt.tight_layout()
|
||||
fig.savefig(os.path.join(OUT, "matrice_risques.png"), dpi=170, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
# 3. CYCLE DE VIE D'UN TICKET (maintenance corrective)
|
||||
# ══════════════════════════════════════════════════════════════════
|
||||
def diagram_ticket():
|
||||
fig, ax = plt.subplots(figsize=(12, 5.2))
|
||||
ax.set_xlim(0, 12); ax.set_ylim(0, 5.2); ax.axis("off")
|
||||
ax.text(6, 4.95, "Cycle de vie d'un ticket sur Gitea (prestataire ↔ Ministère)",
|
||||
ha="center", fontsize=13, fontweight="bold", color=BLUE)
|
||||
|
||||
etapes = [
|
||||
("1. Signalement\nIssue créée\n(gabarit incident)", GREY_D),
|
||||
("2. Qualification\nLabels : gravité,\npriorité, module", BLUE),
|
||||
("3. Priorisation\nSLA appliqué\n(bloquant/majeur/mineur)", ORANGE),
|
||||
("4. Correction\nbranche fix/*\n+ tests", GREEN_D),
|
||||
("5. Revue & CI\nPull Request\n29 tests verts", GREEN_D),
|
||||
("6. Livraison\nmerge → Coolify\ndéploie", GREEN),
|
||||
("7. Clôture\nvalidation client\nIssue fermée", GREEN),
|
||||
]
|
||||
x = 0.15
|
||||
w = 1.58
|
||||
gap = 0.11
|
||||
y = 2.5
|
||||
for i, (t, col) in enumerate(etapes):
|
||||
box(ax, x, y, w, 1.3, t, col, fs=8.3)
|
||||
if i < len(etapes) - 1:
|
||||
arrow(ax, x + w, y + 0.65, x + w + gap, y + 0.65, color=GREY_D, lw=1.6)
|
||||
x += w + gap
|
||||
|
||||
# Bandeau SLA
|
||||
ax.text(6, 1.9, "Délais contractuels (jours ouvrés) — repris du cahier des charges",
|
||||
ha="center", fontsize=10, fontweight="bold", color=GREEN_D)
|
||||
sla = [
|
||||
("Bloquant critique", "Prise en compte 1 h", "Correction 3 h", RED),
|
||||
("Bloquant fort", "Prise en compte 2 h", "Correction 6 h", ORANGE),
|
||||
("Majeur", "Prise en compte 7 h", "Correction 16 h", YELLOW_A),
|
||||
("Mineur (par lots)", "Prise en compte 1 j", "Correction 40 h", GREEN),
|
||||
]
|
||||
bx = 0.3
|
||||
bw = 2.85
|
||||
for titre, d1, d2, col in sla:
|
||||
box(ax, bx, 0.35, bw, 1.15, "", BLUE_L, ec=col, tc=INK)
|
||||
box(ax, bx, 1.12, bw, 0.38, titre, col, fs=9, tc="white")
|
||||
ax.text(bx + bw / 2, 0.92, d1, ha="center", fontsize=8.3, color=INK)
|
||||
ax.text(bx + bw / 2, 0.62, d2, ha="center", fontsize=8.3, color=INK)
|
||||
bx += bw + 0.1
|
||||
|
||||
plt.tight_layout()
|
||||
fig.savefig(os.path.join(OUT, "cycle_ticket.png"), dpi=170, bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
diagram_deploiement()
|
||||
diagram_matrice_risques()
|
||||
diagram_ticket()
|
||||
print("Schémas Bloc 3 générés dans :", OUT)
|
||||
for f in sorted(os.listdir(OUT)):
|
||||
print(" -", f)
|
||||
Reference in New Issue
Block a user