220 lines
10 KiB
Python
220 lines
10 KiB
Python
#!/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)
|