================================================================================
  █████╗ ██████╗  ██████╗  ██████╗      ██████╗██████╗      ██████╗ ██╗████████╗
 ██╔══██╗██╔══██╗██╔════╝ ██╔═══██╗    ██╔════╝██╔══██╗    ██╔════╝ ██║╚══██╔══╝
 ███████║██████╔╝██║  ███╗██║   ██║    ██║     ██║  ██║    ██║  ███╗██║   ██║
 ██╔══██║██╔══██╗██║   ██║██║   ██║    ██║     ██║  ██║    ██║   ██║██║   ██║
 ██║  ██║██║  ██║╚██████╔╝╚██████╔╝    ╚██████╗██████╔╝    ╚██████╔╝██║   ██║
 ╚═╝  ╚═╝╚═╝  ╚═╝ ╚═════╝  ╚═════╝     ╚═════╝╚═════╝      ╚═════╝ ╚═╝   ╚═╝
================================================================================
  ARGO CD + GIT — DÉPLOIEMENT CONTINU GITOPS EN ÉQUIPE
  Guide ultra-détaillé pour GRAND DÉBUTANT
  Chaque action expliquée: POURQUOI? COMMENT? QUAND? QUI?
  Application Flask complète — Kubernetes — GitOps — CI/CD
================================================================================

[BLACK_RIGHT-POINTING_TRIANGLE] À QUI S'ADRESSE CE GUIDE?
  Ce guide est écrit pour quelqu'un qui:
  - connaît Git et GitHub (a suivi le guide précédent)
  - n'a jamais entendu parler d'Argo CD ou de GitOps
  - ne sait pas ce qu'est Kubernetes (ou très peu)
  - veut comprendre POURQUOI avant de taper des commandes
  - travaille en équipe et veut déployer en production de manière sûre
  - veut explorer TOUTES les fonctionnalités d'Argo CD sans exception

[BLACK_RIGHT-POINTING_TRIANGLE] COMMENT LIRE CE GUIDE?
  Chaque section suit ce format:
  ┌─ POURQUOI? ──────────── Pourquoi cette feature/commande existe-t-elle?
  ├─ QU'EST-CE QUE C'EST? ─ Explication claire du concept
  ├─ QUAND? ──────────────── Dans quelle situation l'utiliser?
  ├─ COMMENT? ────────────── Étapes concrètes, une par une
  └─ RÉSULTAT ATTENDU ────── Ce que vous devez voir à l'écran

[BLACK_RIGHT-POINTING_TRIANGLE] L'APPLICATION
  On reprend le TaskManager API du guide Git & GitHub.
  On y ajoute: Kubernetes + Argo CD + GitOps complet.

  Architecture finale:
  ┌─────────────────────────────────────────────────────────────────────┐
  │  GitHub (code source)   ->   GitHub Actions (CI)   ->   ghcr.io       │
  │                                                     (images Docker) │
  │                                                          v          │
  │  GitHub (manifestes K8s) <- ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ Argo CD              │
  │  (dépôt gitops-config)          (surveille en continu)              │
  │                                                          v          │
  │                                               Kubernetes Cluster    │
  │                                          (dev / staging / prod)     │
  └─────────────────────────────────────────────────────────────────────┘

[BLACK_RIGHT-POINTING_TRIANGLE] L'ÉQUIPE (mêmes personnes, nouvelles responsabilités)
  Alice  — Lead, DevOps, Admin Kubernetes et Argo CD
           Installe le cluster, configure Argo CD, gère les accès
           Déploie les releases en production, gère les rollbacks

  Bob    — Développeur backend
           Développe les features, crée les manifestes K8s pour ses apps
           Déclenche les déploiements via des tags Git

  Claire — Développeuse backend / QA
           Écrit les tests, valide les déploiements en staging
           Fait les reviews des manifestes Kubernetes de Bob

[BLACK_RIGHT-POINTING_TRIANGLE] DÉPÔTS GITHUB UTILISÉS
  equipe/taskmanager          — Code source de l'application Flask
  equipe/taskmanager-gitops   — Manifestes Kubernetes (dépôt séparé !)
  (Pourquoi séparé? Expliqué en Partie 0.4)

================================================================================
PARTIE 0 — COMPRENDRE GITOPS ET ARGO CD AVANT DE TOUCHER UN TERMINAL
================================================================================

Avant de taper la moindre commande, il faut comprendre CE QUE FAIT Argo CD
et pourquoi l'approche GitOps existe.
Beaucoup de développeurs déploient "à la main" via SSH. C'est dangereux.
Cette partie explique pourquoi et comment GitOps résout le problème.

────────────────────────────────────────────────────────────────────────────────
0.1 — LE PROBLÈME: COMMENT ON DÉPLOYAIT AVANT (ET POURQUOI C'EST RISQUÉ)
────────────────────────────────────────────────────────────────────────────────

  SCÉNARIO SANS GITOPS (le cauchemar de Bob):
  ─────────────────────────────────────────────
  Lundi 18h: Bob doit déployer une nouvelle version du TaskManager.

  Bob fait:
  1. ssh ubuntu@prod-server-ip
  2. cd /opt/taskmanager
  3. docker pull ghcr.io/equipe/taskmanager-api:v1.2.0
  4. docker-compose up -d
  5. # Croise les doigts [HAND_WITH_INDEX_AND_MIDDLE_FINGERS_CROSSED]

  PROBLÈMES DE CETTE APPROCHE:
  ─────────────────────────────
  [X] Traçabilité zéro: personne ne sait que Bob a déployé v1.2.0 lundi à 18h.
  [X] Si Bob se trompe de commande -> production cassée, pas de trace.
  [X] Alice ne peut pas voir l'état actuel du serveur depuis son ordinateur.
  [X] Pour déployer sur 3 serveurs (dev/staging/prod) -> 3 fois la même procédure.
  [X] Rollback = Bob se reconnecte, cherche quelle version il y avait avant...
  [X] Si Bob est malade -> qui sait comment déployer? Documentation manquante.
  [X] "Configuration drift": le serveur dérive de ce qui est dans Git.
     Le fichier docker-compose.yml sur le serveur n'est peut-être plus le même
     que dans le dépôt (quelqu'un l'a modifié directement sur le serveur).

  SCÉNARIO AVEC GITOPS (ce qu'on va construire):
  ────────────────────────────────────────────────
  Lundi 18h: Bob veut déployer v1.2.0.

  Bob fait:
  1. git tag v1.2.0 && git push origin v1.2.0
     -> GitHub Actions construit l'image Docker et la pousse sur ghcr.io
     -> GitHub Actions met à jour le manifeste K8s dans le dépôt gitops-config
  2. Argo CD détecte le changement dans gitops-config (automatiquement)
  3. Argo CD applique les nouveaux manifestes sur le cluster Kubernetes
  4. 5 minutes plus tard: la nouvelle version tourne en production

  AVANTAGES:
  ──────────
  [OK] Traçabilité complète: le manifeste K8s commit dans Git montre qui/quand.
  [OK] Rollback = git revert du manifeste -> Argo CD revient à l'ancienne version.
  [OK] Alice, Bob et Claire voient en temps réel l'état du cluster depuis l'UI Argo CD.
  [OK] Déploiement identique sur dev/staging/prod (mêmes manifestes, valeurs différentes).
  [OK] Le dépôt gitops-config = source de vérité absolue. Ce qui est dans Git = ce qui tourne.
  [OK] Si quelqu'un modifie le serveur à la main -> Argo CD détecte la dérive et corrige.

────────────────────────────────────────────────────────────────────────────────
0.2 — QU'EST-CE QUE GITOPS?
────────────────────────────────────────────────────────────────────────────────

  DÉFINITION SIMPLE:
  ───────────────────
  GitOps = utiliser Git comme SOURCE DE VÉRITÉ UNIQUE pour tout:
  - Le code de l'application (comme avant)
  - ET la configuration de l'infrastructure (Kubernetes, services, configs)

  "Ce qui est dans Git = ce qui tourne en production."

  Si vous voulez changer quelque chose en production:
  1. Vous modifiez un fichier dans Git (commit + push)
  2. Un outil automatique (Argo CD) détecte le changement
  3. L'outil applique le changement sur l'infrastructure
  Vous ne touchez JAMAIS le serveur directement.

  LES 4 PRINCIPES GITOPS:
  ─────────────────────────

  PRINCIPE 1: Système déclaratif
  -> Vous décrivez L'ÉTAT DÉSIRÉ, pas les étapes pour y arriver.
  -> "Je veux 3 répliques du serveur Flask qui tournent." (pas "lance 3 containers")
  -> Kubernetes lit cette description et fait le nécessaire pour y arriver.

  PRINCIPE 2: Git comme source de vérité
  -> Tout changement passe par Git (commit, PR, review).
  -> Pas de "je vais juste modifier ce fichier sur le serveur".
  -> L'historique Git = l'audit log complet de l'infrastructure.

  PRINCIPE 3: Changements approuvés automatiquement
  -> Le pipeline CI/CD est la SEULE façon de déployer.
  -> Personne n'a de connexion SSH directe au serveur de prod.
  -> (Sauf pour les urgences absolues avec un processus défini)

  PRINCIPE 4: Boucle de contrôle continue
  -> Argo CD compare en permanence: État Désiré (Git) vs État Réel (Cluster).
  -> S'ils divergent -> Argo CD synchronise (ramène à l'état de Git).
  -> "Self-healing": si quelqu'un modifie le serveur à la main,
    Argo CD revient automatiquement à ce que dit Git.

────────────────────────────────────────────────────────────────────────────────
0.3 — QU'EST-CE QU'ARGO CD?
────────────────────────────────────────────────────────────────────────────────

  DÉFINITION:
  ────────────
  Argo CD est un outil GitOps pour Kubernetes.
  Il surveille un dépôt Git et s'assure que le cluster Kubernetes
  reflète toujours ce que dit ce dépôt.

  Argo CD = le pont entre Git (vos manifestes) et Kubernetes (votre cluster).

  COMPOSANTS D'ARGO CD:
  ──────────────────────

  ┌──────────────────────────────────────────────────────────────────────────┐
  │  COMPOSANT: API Server                                                   │
  │  ────────────────────                                                    │
  │  Le cerveau d'Argo CD. Expose l'API REST et gRPC utilisée par           │
  │  l'interface web et le CLI. Gère les Applications, Projets,             │
  │  les politiques d'accès et les notifications.                           │
  └──────────────────────────────────────────────────────────────────────────┘

  ┌──────────────────────────────────────────────────────────────────────────┐
  │  COMPOSANT: Repository Server                                            │
  │  ─────────────────────────────                                           │
  │  Clône et surveille les dépôts Git (votre gitops-config).               │
  │  Génère les manifestes Kubernetes depuis les templates                  │
  │  (Helm, Kustomize, YAML brut...).                                       │
  └──────────────────────────────────────────────────────────────────────────┘

  ┌──────────────────────────────────────────────────────────────────────────┐
  │  COMPOSANT: Application Controller                                       │
  │  ─────────────────────────────────                                       │
  │  Compare en continu l'état désiré (Git) et l'état réel (Kubernetes).   │
  │  Déclenche la synchronisation quand ils divergent.                      │
  │  Le "gardien" qui surveille 24h/24.                                     │
  └──────────────────────────────────────────────────────────────────────────┘

  ┌──────────────────────────────────────────────────────────────────────────┐
  │  COMPOSANT: Interface Web (UI)                                           │
  │  ──────────────────────────────                                          │
  │  Dashboard graphique: voir les Applications, leur état de santé,        │
  │  l'arbre des ressources Kubernetes, les logs, déclencher des syncs.     │
  │  Accessible via navigateur: https://argocd.votre-domaine.com            │
  └──────────────────────────────────────────────────────────────────────────┘

  ┌──────────────────────────────────────────────────────────────────────────┐
  │  COMPOSANT: CLI (argocd)                                                 │
  │  ─────────────────────────                                               │
  │  Ligne de commande pour toutes les opérations Argo CD.                  │
  │  Utilisé dans les scripts CI/CD et pour les opérations en masse.        │
  └──────────────────────────────────────────────────────────────────────────┘

  CONCEPTS CLÉS D'ARGO CD:
  ─────────────────────────

  APPLICATION:
  -> L'objet central d'Argo CD. Une Application dit:
    "Surveille ce dépôt Git, ce chemin, cette branche.
     Déploie dans ce namespace Kubernetes de ce cluster."
  -> Une application par environnement (taskmanager-dev, taskmanager-staging, taskmanager-prod)

  PROJECT:
  -> Regroupe des Applications avec des politiques communes.
  -> "Seules les images de ghcr.io/equipe sont autorisées."
  -> "Ces Applications ne peuvent déployer que dans les namespaces dev/*."

  SYNC (Synchronisation):
  -> L'acte d'appliquer l'état désiré (Git) sur le cluster réel.
  -> Peut être automatique (Argo CD synce dès qu'il voit un changement)
  -> Ou manuelle (un humain clique "Sync" dans l'UI)

  HEALTH (Santé):
  -> Argo CD vérifie si les ressources déployées fonctionnent correctement.
  -> Healthy = tout tourne comme prévu
  -> Degraded = quelque chose ne va pas (Pod en crash, service inaccessible)
  -> Progressing = en cours de déploiement

  SYNC STATUS:
  -> Synced     = ce qui tourne = ce qui est dans Git [OK]
  -> OutOfSync  = Git a changé, le cluster est en retard [ATTENTION]
  -> Unknown    = impossible de comparer (problème de connexion, etc.)

────────────────────────────────────────────────────────────────────────────────
0.4 — POURQUOI UN DÉPÔT SÉPARÉ POUR LES MANIFESTES K8S?
────────────────────────────────────────────────────────────────────────────────

  QUESTION LÉGITIME:
  ───────────────────
  "Pourquoi ne pas mettre les fichiers Kubernetes dans le même dépôt
  que le code source (equipe/taskmanager)?"

  C'est possible et beaucoup de petits projets le font.
  Mais pour une équipe professionnelle, le dépôt séparé a des avantages:

  AVANTAGE 1: Séparation des responsabilités
  -> Les développeurs (Bob, Claire) ont accès à equipe/taskmanager.
  -> Seul Alice (DevOps) peut modifier equipe/taskmanager-gitops.
  -> Les manifestes K8s de prod ne peuvent pas être modifiés par accident.

  AVANTAGE 2: Historique plus lisible
  -> git log dans taskmanager = uniquement les changements de code.
  -> git log dans taskmanager-gitops = uniquement les déploiements.
  -> "Qu'est-ce qui a été déployé le 15 janvier?" -> trivial à retrouver.

  AVANTAGE 3: Vitesse de CI différente
  -> Le code change souvent (toutes les heures en dev).
  -> Les manifestes K8s changent rarement (1 fois par release).
  -> Avoir un dépôt séparé permet des pipelines CI différents et adaptés.

  AVANTAGE 4: Multi-équipe
  -> Dans une grande entreprise: 10 équipes = 10 dépôts code source.
  -> Toutes leurs configs K8s sont dans 1 dépôt gitops centralisé.
  -> L'équipe Ops gère ce dépôt central, pas les développeurs.

  AVANTAGE 5: Sécurité
  -> Les secrets K8s, configurations réseau, politiques de sécurité:
    jamais dans le même dépôt que le code (qui peut être public).
  -> Le dépôt gitops peut être privé même si le code est open source.

  STRUCTURE DES DEUX DÉPÔTS:
  ────────────────────────────

  equipe/taskmanager          equipe/taskmanager-gitops
  ──────────────────          ─────────────────────────
  app/                        apps/
  tests/                        taskmanager/
  config.py                       base/
  run.py                            deployment.yaml
  requirements.txt                  service.yaml
  docker/                           configmap.yaml
  .github/workflows/ci.yml          kustomization.yaml
    (construit l'image,           overlays/
     met à jour gitops-config)       dev/
                                       kustomization.yaml
                                       values.yaml
                                     staging/
                                       kustomization.yaml
                                       values.yaml
                                     prod/
                                       kustomization.yaml
                                       values.yaml
                                argocd-apps/
                                  taskmanager-dev.yaml
                                  taskmanager-staging.yaml
                                  taskmanager-prod.yaml

────────────────────────────────────────────────────────────────────────────────
0.5 — QU'EST-CE QUE KUBERNETES? (Les bases indispensables)
────────────────────────────────────────────────────────────────────────────────

  POURQUOI KUBERNETES?
  ─────────────────────
  Sans Kubernetes: "je lance mon container Docker sur ce serveur."
  Si le serveur tombe -> l'application est down.
  Avec Kubernetes: "je veux 3 copies de mon application, peu importe où."
  Si un serveur tombe -> Kubernetes relance les containers sur un autre serveur.

  Kubernetes = orchestrateur de containers.
  Il décide où et comment faire tourner vos containers.

  LES OBJETS KUBERNETES QU'ON UTILISERA:
  ────────────────────────────────────────

  NAMESPACE:
  -> Espace de travail isolé dans le cluster.
  -> Comme des dossiers: dev, staging, prod sont dans des namespaces séparés.
  -> Un Pod dans "dev" ne peut pas (par défaut) parler à un Pod dans "prod".

  POD:
  -> L'unité la plus petite de Kubernetes.
  -> Contient un ou plusieurs containers Docker.
  -> A une adresse IP dans le cluster.
  -> Éphémère: si un Pod meurt, Kubernetes en crée un nouveau.

  DEPLOYMENT:
  -> Décrit comment créer et maintenir des Pods.
  -> "Je veux 3 replicas du Pod 'api', avec l'image v1.2.0."
  -> Gère les rolling updates: remplace les Pods un par un sans downtime.

  SERVICE:
  -> Point d'entrée stable pour accéder aux Pods (qui changent d'IP).
  -> ClusterIP: accessible uniquement dans le cluster.
  -> LoadBalancer: accessible depuis l'extérieur via une IP publique.
  -> NodePort: accessible via un port du nœud (pour le dev local).

  CONFIGMAP:
  -> Stocke des données de configuration non secrètes.
  -> Injectées dans les containers comme variables d'environnement ou fichiers.
  -> Exemple: FLASK_ENV=production, POSTGRES_HOST=postgres

  SECRET:
  -> Comme ConfigMap mais pour les données sensibles (mots de passe, tokens).
  -> Encodées en base64 (pas chiffrées par défaut, attention!).
  -> En production: utiliser Sealed Secrets ou Vault pour chiffrer vraiment.

  INGRESS:
  -> Règles de routage HTTP/HTTPS vers les Services.
  -> "Toutes les requêtes pour api.taskmanager.com -> Service 'api' port 80"
  -> Géré par un Ingress Controller (Nginx, Traefik...).

  HORIZONTALPODAUTOSCALER (HPA):
  -> Ajuste automatiquement le nombre de réplicas selon la charge CPU/mémoire.
  -> "Si CPU > 70% pendant 2 minutes -> passer de 3 à 6 réplicas."

  PERSISTENTVOLUMECLAIM (PVC):
  -> Demande de stockage persistant pour les bases de données.
  -> Les données survivent aux redémarrages des Pods.

  SCHÉMA D'ARCHITECTURE KUBERNETES:
  ────────────────────────────────────

  ┌─────────────────── Cluster Kubernetes ───────────────────────────────────┐
  │                                                                          │
  │  ┌─── Namespace: taskmanager-prod ────────────────────────────────────┐ │
  │  │                                                                    │ │
  │  │  Ingress (nginx)                                                   │ │
  │  │      v /api/*                                                      │ │
  │  │  Service (api) ──-> Pod api-1 (Flask container)                    │ │
  │  │                ──-> Pod api-2 (Flask container)                    │ │
  │  │                ──-> Pod api-3 (Flask container)                    │ │
  │  │                                                                    │ │
  │  │  Service (postgres) ──-> Pod postgres-0 (StatefulSet)              │ │
  │  │  Service (redis)    ──-> Pod redis-0    (StatefulSet)              │ │
  │  │                                                                    │ │
  │  │  ConfigMap (app-config): FLASK_ENV, POSTGRES_HOST, REDIS_HOST     │ │
  │  │  Secret (app-secrets):   SECRET_KEY, POSTGRES_PASSWORD            │ │
  │  │                                                                    │ │
  │  └────────────────────────────────────────────────────────────────── ┘ │
  │                                                                          │
  │  ┌─── Namespace: argocd ──────────────────────────────────────────────┐ │
  │  │  Argo CD (API Server, Repo Server, App Controller, UI)             │ │
  │  └────────────────────────────────────────────────────────────────── ─┘ │
  │                                                                          │
  └──────────────────────────────────────────────────────────────────────────┘

────────────────────────────────────────────────────────────────────────────────
0.6 — HELM ET KUSTOMIZE: DEUX APPROCHES DE TEMPLATES
────────────────────────────────────────────────────────────────────────────────

  PROBLÈME À RÉSOUDRE:
  ─────────────────────
  On a 3 environnements: dev, staging, prod.
  Ils utilisent les mêmes manifestes K8s mais avec des valeurs différentes:
  -> dev:      1 réplica, image:latest, peu de ressources
  -> staging:  2 réplicas, image:v1.2.0, ressources moyennes
  -> prod:     3 réplicas, image:v1.2.0, beaucoup de ressources

  Option 1: Copier les manifestes 3 fois -> cauchemar de maintenance.
  Option 2: Utiliser un système de templates.

  HELM:
  ──────
  Helm est le "gestionnaire de paquets" pour Kubernetes.
  Vous écrivez des templates avec des variables {{.Values.replicas}}.
  Un fichier values.yaml par environnement définit les valeurs.
  helm install taskmanager ./chart --values values-prod.yaml

  Quand utiliser Helm:
  -> Vous distribuez votre application à d'autres (chart réutilisable)
  -> L'application est complexe avec beaucoup de paramètres configurables
  -> Vous bénéficiez de charts publics existants (PostgreSQL, Redis, Nginx...)

  KUSTOMIZE:
  ───────────
  Kustomize ne fait pas de templates. Il fait des "patches" sur des fichiers de base.
  -> base/ = les manifestes communs à tous les environnements
  -> overlays/dev/ = ce qu'on modifie pour dev (nombre de réplicas, etc.)
  -> overlays/prod/ = ce qu'on modifie pour prod

  Kustomize est intégré nativement dans kubectl et Argo CD.
  Pas besoin de syntaxe spéciale (pas de {{}} dans les fichiers).

  Quand utiliser Kustomize:
  -> Application interne, pas de distribution externe
  -> Vous voulez garder des YAML propres sans syntaxe de templates
  -> Les différences entre environnements sont petites

  CE QU'ON UTILISE DANS CE GUIDE:
  ─────────────────────────────────
  Kustomize pour la base de l'application (simple et natif).
  Helm pour PostgreSQL et Redis (charts publics existants).
  Argo CD supporte les deux, parfois dans la même Application.

================================================================================
PARTIE 1 — ALICE PRÉPARE L'INFRASTRUCTURE (JOUR 1)
================================================================================

Alice arrive le lundi matin avec une mission:
"Avoir Kubernetes + Argo CD opérationnels avant midi pour que
Bob et Claire puissent déployer leurs features l'après-midi."

Plan d'Alice pour la matinée:
  9h00 -> Installer les outils (kubectl, helm, k3d, argocd CLI)
  9h30 -> Créer le cluster Kubernetes local (k3d pour le dev local)
  9h45 -> Installer Argo CD dans le cluster
  10h00 -> Configurer Argo CD (admin, accès, projets)
  10h15 -> Créer le dépôt gitops-config sur GitHub
  10h30 -> Créer la structure des manifestes K8s
  10h45 -> Créer les Applications Argo CD (dev, staging, prod)
  11h00 -> Configurer le pipeline CI/CD GitHub Actions (mise à jour automatique)
  11h30 -> Tester le déploiement end-to-end complet
  12h00 -> Envoyer les accès à Bob et Claire

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.1 — ALICE INSTALLE LES OUTILS
────────────────────────────────────────────────────────────────────────────────

  POURQUOI CES OUTILS?
  ─────────────────────
  kubectl  = la CLI pour interagir avec Kubernetes (comme git pour Git)
  helm     = le gestionnaire de paquets Kubernetes (comme pip pour Python)
  k3d      = crée un cluster Kubernetes DANS Docker (pour le développement local)
             en production on utilisera un vrai cluster (EKS, GKE, AKS...)
  argocd   = la CLI d'Argo CD pour les opérations en ligne de commande

  QUAND LE FAIRE?
  ───────────────
  Une seule fois par machine de développement.

  COMMENT? — SUR MAC (machine d'Alice):
  ──────────────────────────────────────

  # kubectl — CLI Kubernetes:
  brew install kubectl

  # Vérifier:
  kubectl version --client
  # -> Client Version: v1.29.0

  # helm — Gestionnaire de paquets K8s:
  brew install helm

  # Vérifier:
  helm version
  # -> version.BuildInfo{Version:"v3.14.0", ...}

  # k3d — Kubernetes dans Docker (pour dev local):
  # Prérequis: Docker doit être installé et en cours d'exécution.
  brew install k3d

  # Vérifier:
  k3d version
  # -> k3d version v5.6.0

  # argocd CLI:
  brew install argocd

  # Vérifier:
  argocd version --client
  # -> argocd: v2.10.0

  # Bonus: kubectx et kubens — changer facilement de cluster et namespace:
  brew install kubectx
  # kubectx = changer de cluster rapidement
  # kubens  = changer de namespace rapidement

  COMMENT? — SUR UBUNTU (machine de Bob):
  ─────────────────────────────────────────

  # kubectl:
  curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
  chmod +x kubectl && sudo mv kubectl /usr/local/bin/

  # helm:
  curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

  # k3d:
  wget -q -O - https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash

  # argocd CLI:
  curl -sSL -o argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
  sudo install -m 555 argocd-linux-amd64 /usr/local/bin/argocd

  COMMENT? — SUR WINDOWS (machine de Claire, dans PowerShell Admin):
  ───────────────────────────────────────────────────────────────────

  # Via Chocolatey (si installé):
  choco install kubernetes-cli helm k3d argocd-cli

  # Ou via Winget:
  winget install Kubernetes.kubectl
  winget install Helm.Helm

  VÉRIFICATION FINALE (sur les 3 machines):
  ──────────────────────────────────────────
  kubectl version --client && helm version --short && k3d version && argocd version --client
  # -> Toutes les versions doivent s'afficher sans erreur.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.2 — ALICE CRÉE LE CLUSTER KUBERNETES LOCAL
────────────────────────────────────────────────────────────────────────────────

  POURQUOI k3d POUR LE LOCAL?
  ────────────────────────────
  Un vrai cluster Kubernetes (sur AWS, GCP, Azure) coûte de l'argent.
  Pour apprendre et développer: k3d crée un cluster complet DANS Docker.
  Gratuit, rapide à créer (30 secondes), facile à détruire et recréer.

  En production: on utilisera un vrai cluster managé (voir Partie 7).
  Mais pour comprendre Argo CD, k3d est parfait.

  ALICE CRÉE LE CLUSTER:
  ──────────────────────

  # Créer un cluster nommé "taskmanager" avec:
  # --servers 1 = 1 nœud "control plane" (le chef du cluster)
  # --agents 2 = 2 nœuds "workers" (où tourneront les containers)
  # --port = exposer le port 80 et 443 du cluster sur localhost
  # --k3s-arg = configurer le load balancer Traefik intégré

  k3d cluster create taskmanager \
    --servers 1 \
    --agents 2 \
    --port "80:80@loadbalancer" \
    --port "443:443@loadbalancer" \
    --k3s-arg "--disable=traefik@server:0"

  # On désactive Traefik car on installera Nginx Ingress Controller à la place.

  CE QUE VOUS VOYEZ:
  ───────────────────
  INFO[0000] Pulling image 'ghcr.io/k3d-io/k3d-tools:5.6.0'
  INFO[0000] Creating cluster 'taskmanager'
  INFO[0001] Creating network 'k3d-taskmanager'
  INFO[0002] Creating node 'k3d-taskmanager-server-0'
  INFO[0003] Creating node 'k3d-taskmanager-agent-0'
  INFO[0004] Creating node 'k3d-taskmanager-agent-1'
  INFO[0005] Starting cluster 'taskmanager'
  INFO[0020] Cluster 'taskmanager' created successfully!
  INFO[0020] You can now use it with: kubectl cluster-info

  # Vérifier que kubectl pointe vers le nouveau cluster:
  kubectl cluster-info

  CE QUE VOUS VOYEZ:
  ───────────────────
  Kubernetes control plane is running at https://0.0.0.0:43219
  CoreDNS is running at https://0.0.0.0:43219/api/v1/namespaces/...

  # Voir les nœuds du cluster:
  kubectl get nodes

  CE QUE VOUS VOYEZ:
  ───────────────────
  NAME                        STATUS   ROLES                  AGE   VERSION
  k3d-taskmanager-server-0    Ready    control-plane,master   30s   v1.29.0+k3s1
  k3d-taskmanager-agent-0     Ready    <none>                 25s   v1.29.0+k3s1
  k3d-taskmanager-agent-1     Ready    <none>                 25s   v1.29.0+k3s1

  # [OK] 3 nœuds en statut "Ready" = le cluster est fonctionnel!

  # k3d crée automatiquement un contexte kubectl:
  kubectl config get-contexts
  # -> k3d-taskmanager   (avec une * = contexte actif)

  CRÉER LES NAMESPACES:
  ──────────────────────
  # On crée les espaces de travail isolés pour chaque environnement:
  kubectl create namespace argocd
  kubectl create namespace taskmanager-dev
  kubectl create namespace taskmanager-staging
  kubectl create namespace taskmanager-prod

  # Vérifier:
  kubectl get namespaces

  CE QUE VOUS VOYEZ:
  ───────────────────
  NAME                   STATUS   AGE
  argocd                 Active   5s
  default                Active   2m
  kube-system            Active   2m
  taskmanager-dev        Active   3s
  taskmanager-staging    Active   3s
  taskmanager-prod       Active   3s

  INSTALLER NGINX INGRESS CONTROLLER:
  ─────────────────────────────────────
  # L'Ingress Controller gère le routage HTTP vers nos Services.
  # Sans lui, les règles Ingress ne fonctionnent pas.

  kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml

  # Attendre qu'il soit prêt:
  kubectl wait --namespace ingress-nginx \
    --for=condition=ready pod \
    --selector=app.kubernetes.io/component=controller \
    --timeout=120s

  CE QUE VOUS VOYEZ:
  ───────────────────
  pod/ingress-nginx-controller-xxxx condition met
  # [OK] Nginx Ingress est prêt.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.3 — ALICE INSTALLE ARGO CD DANS LE CLUSTER
────────────────────────────────────────────────────────────────────────────────

  POURQUOI INSTALLER ARGO CD DANS KUBERNETES?
  ─────────────────────────────────────────────
  Argo CD tourne LUI-MÊME dans le cluster qu'il gère.
  C'est cohérent avec GitOps: Argo CD est lui-même déclaré dans des manifestes.
  Il peut même se gérer lui-même ("App of Apps" pattern — on verra ça en Partie 5).

  MÉTHODE D'INSTALLATION:
  ────────────────────────
  # Installation officielle dans le namespace "argocd":
  kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/install.yaml

  # Cela crée tous les composants Argo CD:
  # - Deployments: argocd-server, argocd-repo-server, argocd-application-controller
  # - Services, ConfigMaps, Secrets, CRDs (Custom Resource Definitions)

  CE QUE VOUS VOYEZ:
  ───────────────────
  customresourcedefinition.apiextensions.k8s.io/applications.argoproj.io created
  customresourcedefinition.apiextensions.k8s.io/applicationsets.argoproj.io created
  customresourcedefinition.apiextensions.k8s.io/appprojects.argoproj.io created
  serviceaccount/argocd-application-controller created
  serviceaccount/argocd-server created
  ...
  deployment.apps/argocd-server created
  deployment.apps/argocd-repo-server created
  deployment.apps/argocd-application-controller created

  # Attendre que tous les Pods Argo CD soient "Running":
  kubectl wait --for=condition=Ready pods --all -n argocd --timeout=180s

  # Vérifier l'état des Pods Argo CD:
  kubectl get pods -n argocd

  CE QUE VOUS VOYEZ:
  ───────────────────
  NAME                                                READY   STATUS    RESTARTS
  argocd-application-controller-0                     1/1     Running   0
  argocd-dex-server-5d8d4f9454-xxxx                   1/1     Running   0
  argocd-notifications-controller-6d547bbb9f-xxxx     1/1     Running   0
  argocd-redis-657dff6b9c-xxxx                        1/1     Running   0
  argocd-repo-server-69f8f68567-xxxx                  1/1     Running   0
  argocd-server-6b689b9799-xxxx                       1/1     Running   0

  # [OK] Tous les Pods en "Running" = Argo CD est installé!

  ACCÉDER À L'INTERFACE WEB ARGO CD:
  ────────────────────────────────────
  # Par défaut, argocd-server n'est pas exposé à l'extérieur du cluster.
  # Pour y accéder en local, on utilise port-forward:

  # Dans un terminal dédié (ne pas fermer):
  kubectl port-forward svc/argocd-server -n argocd 8080:443 &
  # & = exécuter en arrière-plan

  # Récupérer le mot de passe admin initial:
  argocd admin initial-password -n argocd

  CE QUE VOUS VOYEZ:
  ───────────────────
  vR7mK9pXnQ2sT8wL
  # Ce mot de passe est généré aléatoirement à l'installation.

  # Se connecter avec le CLI:
  argocd login localhost:8080 \
    --username admin \
    --password vR7mK9pXnQ2sT8wL \
    --insecure
  # --insecure car on utilise un certificat auto-signé en local.

  CE QUE VOUS VOYEZ:
  ───────────────────
  'admin:login' logged in successfully
  Context 'localhost:8080' updated

  # CHANGER LE MOT DE PASSE ADMIN (obligatoire!):
  argocd account update-password \
    --current-password vR7mK9pXnQ2sT8wL \
    --new-password "TaskManager-ArgoCD-2024-Admin!"

  # Ouvrir l'UI dans le navigateur:
  open https://localhost:8080
  # -> Accepter l'avertissement certificat non sécurisé -> Continuer
  # -> Login: admin / TaskManager-ArgoCD-2024-Admin!
  # -> Bienvenue dans l'interface Argo CD! (vide pour l'instant)

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.4 — ALICE CONFIGURE ARGO CD
────────────────────────────────────────────────────────────────────────────────

  CRÉER LES COMPTES UTILISATEURS:
  ─────────────────────────────────
  # POURQUOI?
  # Bob et Claire ont besoin d'accès à Argo CD.
  # Bob: peut voir et déclencher des syncs sur son app en dev.
  # Claire: peut voir tous les environnements mais pas modifier prod.
  # Alice: admin complète.

  # Éditer le ConfigMap argocd-cm pour ajouter les utilisateurs:
  kubectl edit configmap argocd-cm -n argocd
  # Cette commande ouvre l'éditeur par défaut (nano ou vim)

  # Ajouter dans la section "data:":
  data:
    # Activer les comptes locaux:
    accounts.bob: apiKey, login
    accounts.claire: apiKey, login
    # apiKey = peut générer des tokens API pour CI/CD
    # login = peut se connecter à l'UI

  # Sauvegarder et quitter l'éditeur.

  # Définir les mots de passe:
  argocd account update-password \
    --account bob \
    --current-password "TaskManager-ArgoCD-2024-Admin!" \
    --new-password "Bob-TaskManager-2024!" \
    --grpc-web

  argocd account update-password \
    --account claire \
    --current-password "TaskManager-ArgoCD-2024-Admin!" \
    --new-password "Claire-TaskManager-2024!" \
    --grpc-web

  CONFIGURER LES RÔLES ET PERMISSIONS (RBAC):
  ─────────────────────────────────────────────
  # POURQUOI le RBAC?
  # Bob ne doit pas pouvoir faire une "Sync hard" sur la prod.
  # Claire peut voir tout mais ne peut pas modifier prod.
  # Argo CD a son propre système de permissions basé sur des politiques Casbin.

  kubectl edit configmap argocd-rbac-cm -n argocd

  # Ajouter dans "data":
  data:
    policy.csv: |
      # Rôle: viewer (lecture seule)
      p, role:viewer, applications, get, */*, allow
      p, role:viewer, applications, list, */*, allow
      p, role:viewer, clusters, get, *, allow
      p, role:viewer, repositories, get, *, allow
      p, role:viewer, logs, get, */*, allow

      # Rôle: developer (peut sync dev et staging, pas prod)
      p, role:developer, applications, get, */*, allow
      p, role:developer, applications, sync, taskmanager/taskmanager-dev, allow
      p, role:developer, applications, sync, taskmanager/taskmanager-staging, allow
      p, role:developer, applications, action/*, taskmanager/taskmanager-dev, allow
      p, role:developer, logs, get, */*, allow

      # Assigner les rôles aux utilisateurs:
      g, bob, role:developer
      g, claire, role:developer

      # Admin par défaut pour alice (déjà configuré, on laisse):
      g, admin, role:admin

    policy.default: role:viewer
    # Les utilisateurs non listés ont le rôle viewer par défaut.

  CONFIGURER LES NOTIFICATIONS (SLACK):
  ───────────────────────────────────────
  # Argo CD peut envoyer des notifications quand une Application:
  # - Commence à se synchroniser
  # - Finit de se synchroniser (succès ou échec)
  # - Devient "Unhealthy" (un Pod plante)
  # - Commence un rollback

  # Créer le Secret avec les credentials de notification:
  kubectl create secret generic argocd-notifications-secret \
    -n argocd \
    --from-literal=slack-token="xoxb-votre-token-slack-ici"

  # Configurer les templates de notification:
  kubectl apply -f - <<'EOF'
  apiVersion: v1
  kind: ConfigMap
  metadata:
    name: argocd-notifications-cm
    namespace: argocd
  data:
    service.slack: |
      token: $slack-token
      username: ArgoCD
      icon: ":argo:"

    template.app-deployed: |
      message: |
        [OK] *{{.app.metadata.name}}* déployé avec succès!
        Révision: `{{.app.status.sync.revision}}`
        Auteur: {{(call .repo.GetCommitMetadata .app.status.sync.revision).Author}}
      slack:
        attachments: |
          [{
            "color": "#18be52",
            "fields": [{
              "title": "Application",
              "value": "{{.app.metadata.name}}",
              "short": true
            }, {
              "title": "Environnement",
              "value": "{{.app.metadata.labels.environment}}",
              "short": true
            }]
          }]

    template.app-health-degraded: |
      message: |
        [X] *{{.app.metadata.name}}* est en état DÉGRADÉ!
        Statut: {{.app.status.health.status}}
        Message: {{.app.status.health.message}}
      slack:
        attachments: |
          [{
            "color": "#E96D76",
            "fields": [{
              "title": "Application",
              "value": "{{.app.metadata.name}}",
              "short": true
            }]
          }]

    trigger.on-deployed: |
      - description: Envoyer quand le déploiement réussit
        send: [app-deployed]
        when: app.status.operationState.phase in ['Succeeded']
              and app.status.health.status == 'Healthy'

    trigger.on-health-degraded: |
      - description: Envoyer quand l'app devient dégradée
        send: [app-health-degraded]
        when: app.status.health.status == 'Degraded'

    defaultTriggers: |
      - on-deployed
      - on-health-degraded
  EOF

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 1.5 — ALICE CRÉE LE DÉPÔT GITOPS-CONFIG
────────────────────────────────────────────────────────────────────────────────

  SUR GITHUB (interface web):
  ────────────────────────────
  1. github.com -> "+" -> "New repository"
  2. Owner: equipe
  3. Name: taskmanager-gitops
  4. Description: "Manifestes Kubernetes GitOps pour TaskManager API"
  5. Visibility: Private
  6. Initialize: [x] Add a README file
  7. -> Create repository

  ALICE CLONE CE DÉPÔT:
  ──────────────────────
  cd ~/
  git clone git@github.com:equipe/taskmanager-gitops.git
  cd taskmanager-gitops/

  CRÉER LA STRUCTURE COMPLÈTE:
  ─────────────────────────────
  mkdir -p apps/taskmanager/base
  mkdir -p apps/taskmanager/overlays/dev
  mkdir -p apps/taskmanager/overlays/staging
  mkdir -p apps/taskmanager/overlays/prod
  mkdir -p apps/taskmanager/charts
  mkdir -p argocd-apps
  mkdir -p argocd-projects
  mkdir -p scripts

  # Vérifier:
  tree .
  # -> .
  #   ├── README.md
  #   ├── apps/
  #   │   └── taskmanager/
  #   │       ├── base/
  #   │       ├── charts/
  #   │       └── overlays/
  #   │           ├── dev/
  #   │           ├── staging/
  #   │           └── prod/
  #   ├── argocd-apps/
  #   ├── argocd-projects/
  #   └── scripts/

================================================================================
PARTIE 2 — ALICE ÉCRIT LES MANIFESTES KUBERNETES COMPLETS
================================================================================

  C'est la partie la plus longue et la plus importante.
  Chaque fichier est expliqué ligne par ligne.
  AUCUN fichier n'est laissé vide ou abrégé.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.1 — LES MANIFESTES DE BASE (apps/taskmanager/base/)
────────────────────────────────────────────────────────────────────────────────

  POURQUOI UNE BASE?
  ───────────────────
  Les manifestes de "base" contiennent tout ce qui est commun
  à TOUS les environnements (dev, staging, prod).
  Les overlays ne contiennent QUE ce qui change par environnement.

  ── apps/taskmanager/base/namespace.yaml ───────────────────────────────────
  # POURQUOI?
  # Définit le namespace de l'application.
  # Kustomize peut créer le namespace automatiquement au déploiement.

  apiVersion: v1
  kind: Namespace
  metadata:
    name: taskmanager-dev   # Sera remplacé par l'overlay (dev/staging/prod)
    labels:
      app.kubernetes.io/managed-by: argocd
      environment: dev   # Sera remplacé par l'overlay

  ── apps/taskmanager/base/configmap.yaml ───────────────────────────────────
  # POURQUOI?
  # Stocke la configuration non-sensible de l'application.
  # Injectée dans les containers Flask comme variables d'environnement.
  # Séparé des Secrets pour: les valeurs non-sensibles sont visibles dans Git.

  apiVersion: v1
  kind: ConfigMap
  metadata:
    name: taskmanager-config
    labels:
      app: taskmanager
      component: api
  data:
    # Flask
    FLASK_APP: "run.py"
    FLASK_ENV: "production"
    FLASK_DEBUG: "0"
    PORT: "5000"

    # PostgreSQL
    POSTGRES_DB: "taskmanager"
    POSTGRES_USER: "taskmanager"
    POSTGRES_HOST: "taskmanager-postgres"   # Nom du Service K8s PostgreSQL
    POSTGRES_PORT: "5432"

    # Redis
    REDIS_HOST: "taskmanager-redis"         # Nom du Service K8s Redis
    REDIS_PORT: "6379"
    REDIS_URL: "redis://taskmanager-redis:6379/0"

    # App
    APP_VERSION: "1.0.0"   # Sera mis à jour automatiquement par le CI/CD

  ── apps/taskmanager/base/secret.yaml ──────────────────────────────────────
  # POURQUOI?
  # Stocke les données SENSIBLES (mots de passe, clés API).
  # ATTENTION: les Secrets K8s de base sont juste encodés en base64, PAS chiffrés!
  # En production: utiliser Sealed Secrets ou External Secrets Operator.
  # Pour ce guide: on montre comment ça marche, avec un avertissement clair.
  #
  # IMPORTANT: Ce fichier ne contient PAS de vraies valeurs.
  # Les vraies valeurs sont injectées par le pipeline CI/CD ou par kubectl.
  # Le fichier dans Git contient des placeholders.

  apiVersion: v1
  kind: Secret
  metadata:
    name: taskmanager-secrets
    labels:
      app: taskmanager
  type: Opaque
  # Les valeurs sont en base64: echo -n "valeur" | base64
  # IMPORTANT: Ce sont des valeurs de DÉMONSTRATION. Changer en production!
  stringData:
    # stringData accepte les valeurs en clair (K8s les encode en base64 automatiquement)
    # Ne jamais committer de vraies valeurs ici!
    SECRET_KEY: "PLACEHOLDER-remplacer-par-vrai-secret"
    POSTGRES_PASSWORD: "PLACEHOLDER-remplacer-par-vrai-mot-de-passe"
    DATABASE_URL: "postgresql://taskmanager:PLACEHOLDER@taskmanager-postgres:5432/taskmanager"

  # Note pour la production: utiliser Sealed Secrets (voir Partie 6)
  # qui permet de committer des secrets CHIFFRÉS dans Git en toute sécurité.

  ── apps/taskmanager/base/deployment.yaml ──────────────────────────────────
  # POURQUOI UN DEPLOYMENT ET PAS UN POD SEUL?
  # Un Pod seul = si il plante, il n'est pas redémarré automatiquement.
  # Un Deployment = K8s maintient toujours le nombre de Pods souhaité.
  # Si un Pod plante -> Deployment en crée un nouveau immédiatement.

  apiVersion: apps/v1
  kind: Deployment
  metadata:
    name: taskmanager-api
    labels:
      app: taskmanager
      component: api
    annotations:
      # Annotation pour activer les notifications Argo CD:
      notifications.argoproj.io/subscribe.on-deployed.slack: "deployments"
      notifications.argoproj.io/subscribe.on-health-degraded.slack: "deployments"
  spec:
    # Nombre de réplicas (copies de l'application):
    # Sera surchargé par les overlays (dev=1, staging=2, prod=3)
    replicas: 1

    # Selector: comment le Deployment trouve SES Pods:
    selector:
      matchLabels:
        app: taskmanager
        component: api

    # Stratégie de mise à jour:
    strategy:
      type: RollingUpdate   # Remplace les Pods un par un -> zéro downtime
      rollingUpdate:
        maxSurge: 1         # Maximum 1 Pod supplémentaire pendant la mise à jour
        maxUnavailable: 0   # Minimum 0 Pod en moins pendant la mise à jour
        # Si on a 3 réplicas et maxSurge=1, maxUnavailable=0:
        # -> K8s crée d'abord le 4ème Pod (nouveau)
        # -> Quand il est Ready, il supprime le 1er (ancien)
        # -> Et ainsi de suite -> toujours 3 Pods en service

    template:
      metadata:
        labels:
          app: taskmanager
          component: api
          # Version de l'image (pour le tracking dans Argo CD):
          app.kubernetes.io/version: "1.0.0"
      spec:
        # Compte de service avec le minimum de permissions:
        serviceAccountName: default

        # Tolérance pour les nœuds avec des taints:
        tolerations:
          - key: "node.kubernetes.io/not-ready"
            operator: "Exists"
            effect: "NoExecute"
            tolerationSeconds: 30

        containers:
          - name: api
            # Image Docker: sera mise à jour par le CI/CD (tag spécifique, pas latest!)
            image: ghcr.io/equipe/taskmanager-api:1.0.0
            imagePullPolicy: Always

            ports:
              - name: http
                containerPort: 5000
                protocol: TCP

            # Variables d'environnement depuis ConfigMap:
            envFrom:
              - configMapRef:
                  name: taskmanager-config
              - secretRef:
                  name: taskmanager-secrets

            # Vérification de démarrage (Startup Probe):
            # K8s attend que ce check réussisse avant de passer aux autres checks.
            # Utile pour les apps qui démarrent lentement (migrations DB...).
            startupProbe:
              httpGet:
                path: /health
                port: 5000
              failureThreshold: 30   # 30 tentatives × 10 secondes = 5 minutes max
              periodSeconds: 10

            # Liveness Probe: "Est-ce que ce container est encore vivant?"
            # Si ça échoue: K8s REDÉMARRE le container.
            # Important: ne pas faire de check trop agressif (DB, Redis...)
            # Un simple /health qui répond sans dépendances externes.
            livenessProbe:
              httpGet:
                path: /health
                port: 5000
              initialDelaySeconds: 10
              periodSeconds: 30
              timeoutSeconds: 5
              failureThreshold: 3   # 3 échecs -> redémarrage

            # Readiness Probe: "Est-ce que ce container peut recevoir du trafic?"
            # Si ça échoue: K8s ENLÈVE le Pod du load balancer SANS le redémarrer.
            # Utile pendant les démarrages ou les périodes de forte charge.
            readinessProbe:
              httpGet:
                path: /health
                port: 5000
              initialDelaySeconds: 5
              periodSeconds: 10
              timeoutSeconds: 3
              successThreshold: 1
              failureThreshold: 3

            # Ressources CPU et mémoire:
            # requests = minimum garanti pour le Pod
            # limits = maximum autorisé (dépassement -> OOMKill pour mémoire)
            resources:
              requests:
                memory: "128Mi"   # 128 Mégaoctets minimum
                cpu: "100m"       # 100 millicores = 0.1 CPU minimum
              limits:
                memory: "512Mi"   # 512 Mo maximum
                cpu: "500m"       # 0.5 CPU maximum

            # Variables d'environnement directes (pas depuis ConfigMap/Secret):
            env:
              - name: POD_NAME
                valueFrom:
                  fieldRef:
                    fieldPath: metadata.name
              - name: POD_NAMESPACE
                valueFrom:
                  fieldRef:
                    fieldPath: metadata.namespace

        # Politique de pull des images privées:
        imagePullSecrets:
          - name: ghcr-credentials   # Secret avec le token GitHub pour ghcr.io

        # Affinité: préférer distribuer les Pods sur différents nœuds:
        affinity:
          podAntiAffinity:
            preferredDuringSchedulingIgnoredDuringExecution:
              - weight: 100
                podAffinityTerm:
                  labelSelector:
                    matchExpressions:
                      - key: component
                        operator: In
                        values:
                          - api
                  topologyKey: kubernetes.io/hostname
                  # -> Préférer placer les réplicas sur des nœuds différents.
                  # Si un nœud tombe, pas tous les réplicas en même temps.

  ── apps/taskmanager/base/service.yaml ─────────────────────────────────────
  # POURQUOI UN SERVICE?
  # Les Pods ont des adresses IP qui changent à chaque redémarrage.
  # Un Service a une IP stable qui redirige vers les Pods en vie.
  # C'est le point d'entrée stable pour accéder à l'application.

  apiVersion: v1
  kind: Service
  metadata:
    name: taskmanager-api
    labels:
      app: taskmanager
      component: api
  spec:
    # ClusterIP = accessible uniquement depuis l'intérieur du cluster.
    # L'accès externe passe par l'Ingress.
    type: ClusterIP
    selector:
      app: taskmanager
      component: api   # Sélectionne les Pods avec ces labels
    ports:
      - name: http
        protocol: TCP
        port: 80          # Port du Service (ce que les autres Pods utilisent)
        targetPort: 5000  # Port du container (où Flask écoute)

  ── apps/taskmanager/base/ingress.yaml ─────────────────────────────────────
  # POURQUOI UN INGRESS?
  # Le Service est accessible dans le cluster mais pas depuis l'extérieur.
  # L'Ingress définit les règles de routage HTTP/HTTPS vers les Services.
  # "Les requêtes pour api-dev.taskmanager.com -> Service taskmanager-api."

  apiVersion: networking.k8s.io/v1
  kind: Ingress
  metadata:
    name: taskmanager-ingress
    labels:
      app: taskmanager
    annotations:
      # Indiquer à Nginx Ingress Controller qu'il gère cet Ingress:
      kubernetes.io/ingress.class: "nginx"

      # Rate limiting: max 100 requêtes par minute par IP:
      nginx.ingress.kubernetes.io/limit-rps: "100"

      # Timeout pour les requêtes longues:
      nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
      nginx.ingress.kubernetes.io/proxy-connect-timeout: "10"

      # En-têtes de sécurité:
      nginx.ingress.kubernetes.io/configuration-snippet: |
        more_set_headers "X-Frame-Options: DENY";
        more_set_headers "X-Content-Type-Options: nosniff";
        more_set_headers "X-XSS-Protection: 1; mode=block";
  spec:
    rules:
      # Sera surchargé par l'overlay (api-dev.*, api-staging.*, api.*)
      - host: api-dev.taskmanager.local
        http:
          paths:
            - path: /
              pathType: Prefix
              backend:
                service:
                  name: taskmanager-api
                  port:
                    number: 80

  ── apps/taskmanager/base/hpa.yaml ─────────────────────────────────────────
  # POURQUOI L'AUTOSCALING?
  # En cas de pic de trafic, l'application doit pouvoir s'adapter.
  # Sans HPA: si CPU monte à 100% -> les requêtes ralentissent.
  # Avec HPA: K8s ajoute automatiquement des réplicas pour absorber la charge.

  apiVersion: autoscaling/v2
  kind: HorizontalPodAutoscaler
  metadata:
    name: taskmanager-api-hpa
    labels:
      app: taskmanager
  spec:
    scaleTargetRef:
      apiVersion: apps/v1
      kind: Deployment
      name: taskmanager-api
    minReplicas: 1    # Sera surchargé par les overlays
    maxReplicas: 10   # Maximum jamais dépasser 10 réplicas
    metrics:
      # Scaler si CPU > 70%:
      - type: Resource
        resource:
          name: cpu
          target:
            type: Utilization
            averageUtilization: 70
      # Scaler si mémoire > 80%:
      - type: Resource
        resource:
          name: memory
          target:
            type: Utilization
            averageUtilization: 80
    behavior:
      # Comportement pour monter en charge (scaleUp):
      scaleUp:
        stabilizationWindowSeconds: 60   # Attendre 1 min avant d'ajouter des réplicas
        policies:
          - type: Pods
            value: 2                     # Ajouter maximum 2 Pods à la fois
            periodSeconds: 60
      # Comportement pour descendre en charge (scaleDown):
      scaleDown:
        stabilizationWindowSeconds: 300  # Attendre 5 min avant de supprimer des réplicas
        policies:
          - type: Percent
            value: 25                    # Supprimer maximum 25% des réplicas à la fois
            periodSeconds: 60

  ── apps/taskmanager/base/poddisruptionbudget.yaml ─────────────────────────
  # POURQUOI UN PDB?
  # Un PodDisruptionBudget garantit qu'il y a TOUJOURS un minimum de Pods
  # disponibles, même pendant des opérations de maintenance du cluster
  # (mises à jour des nœuds, vidage d'un nœud...).
  # Sans PDB: une mise à jour du cluster pourrait supprimer tous les Pods
  # en même temps -> downtime.

  apiVersion: policy/v1
  kind: PodDisruptionBudget
  metadata:
    name: taskmanager-api-pdb
    labels:
      app: taskmanager
  spec:
    # minAvailable: toujours au moins 1 Pod disponible pendant une disruption:
    minAvailable: 1
    selector:
      matchLabels:
        app: taskmanager
        component: api

  ── apps/taskmanager/base/serviceaccount.yaml ──────────────────────────────
  # POURQUOI UN SERVICEACCOUNT?
  # Les Pods s'exécutent sous une identité.
  # Par défaut: "default" qui a beaucoup de permissions.
  # Bonne pratique: créer un compte dédié avec le minimum de permissions.

  apiVersion: v1
  kind: ServiceAccount
  metadata:
    name: taskmanager-api-sa
    labels:
      app: taskmanager
    annotations:
      # Si on utilise AWS EKS avec IRSA (IAM Roles for Service Accounts):
      # eks.amazonaws.com/role-arn: "arn:aws:iam::123456789:role/taskmanager-api"
      kubernetes.io/description: "Service account for TaskManager API pods"
  automountServiceAccountToken: false
  # false = le token K8s n'est pas monté automatiquement dans le Pod.
  # Plus sécurisé: l'application ne peut pas parler à l'API K8s.

  ── apps/taskmanager/base/kustomization.yaml ───────────────────────────────
  # POURQUOI CE FICHIER?
  # C'est le fichier central de Kustomize.
  # Il liste TOUS les fichiers à inclure dans cette "base".
  # Sans ce fichier, Kustomize ne sait pas quoi gérer.

  apiVersion: kustomize.config.k8s.io/v1beta1
  kind: Kustomization

  # Métadonnées communes à toutes les ressources:
  commonLabels:
    app: taskmanager
    managed-by: argocd

  # Liste de toutes les ressources de la base:
  resources:
    - namespace.yaml
    - configmap.yaml
    - secret.yaml
    - serviceaccount.yaml
    - deployment.yaml
    - service.yaml
    - ingress.yaml
    - hpa.yaml
    - poddisruptionbudget.yaml

  # Images: permet à Kustomize de remplacer les tags d'images:
  images:
    - name: ghcr.io/equipe/taskmanager-api
      newTag: "1.0.0"   # Sera mis à jour par le CI/CD

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.2 — L'OVERLAY DÉVELOPPEMENT (overlays/dev/)
────────────────────────────────────────────────────────────────────────────────

  POURQUOI UN OVERLAY DEV DIFFÉRENT?
  ────────────────────────────────────
  En dev: on veut être agile. 1 réplica suffit (économise des ressources).
  Les logs sont en debug. Le Sync Argo CD est automatique (pas besoin de cliquer).
  L'image peut être "latest" pour voir les derniers changements immédiatement.

  ── apps/taskmanager/overlays/dev/kustomization.yaml ───────────────────────

  apiVersion: kustomize.config.k8s.io/v1beta1
  kind: Kustomization

  # Référencer la base:
  resources:
    - ../../base

  # Suffixe pour distinguer les ressources dev (optionnel mais lisible):
  nameSuffix: ""   # Pas de suffixe ici, on utilise le namespace

  # Namespace cible:
  namespace: taskmanager-dev

  # Remplacer des valeurs spécifiques au dev:
  patches:
    # Réduire les réplicas à 1 en dev:
    - patch: |-
        - op: replace
          path: /spec/replicas
          value: 1
      target:
        kind: Deployment
        name: taskmanager-api

    # Remplacer l'hôte de l'Ingress pour dev:
    - patch: |-
        - op: replace
          path: /spec/rules/0/host
          value: api-dev.taskmanager.local
      target:
        kind: Ingress
        name: taskmanager-ingress

    # HPA en dev: min 1, max 2 réplicas (économiser les ressources):
    - patch: |-
        - op: replace
          path: /spec/minReplicas
          value: 1
        - op: replace
          path: /spec/maxReplicas
          value: 2
      target:
        kind: HorizontalPodAutoscaler
        name: taskmanager-api-hpa

    # Ressources réduites en dev:
    - patch: |-
        - op: replace
          path: /spec/template/spec/containers/0/resources
          value:
            requests:
              memory: "64Mi"
              cpu: "50m"
            limits:
              memory: "256Mi"
              cpu: "200m"
      target:
        kind: Deployment
        name: taskmanager-api

  # ConfigMap patch pour dev (FLASK_DEBUG=1):
  configMapGenerator:
    - name: taskmanager-config
      behavior: merge   # Fusionner avec le ConfigMap de la base
      literals:
        - FLASK_ENV=development
        - FLASK_DEBUG=1
        - APP_VERSION=dev-latest

  # Remplacer le tag de l'image en dev (latest pour avoir la dernière version):
  images:
    - name: ghcr.io/equipe/taskmanager-api
      newTag: "latest"

  ── apps/taskmanager/overlays/dev/namespace.yaml ───────────────────────────
  # Patch du namespace pour dev:
  apiVersion: v1
  kind: Namespace
  metadata:
    name: taskmanager-dev
    labels:
      environment: dev
      app.kubernetes.io/managed-by: argocd

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.3 — L'OVERLAY STAGING (overlays/staging/)
────────────────────────────────────────────────────────────────────────────────

  POURQUOI UN STAGING DIFFÉRENT DU DEV?
  ───────────────────────────────────────
  Staging = miroir de la production. Il doit ressembler le plus possible à prod.
  -> 2 réplicas (comme une petite prod)
  -> Image avec un tag de version spécifique (pas latest)
  -> FLASK_DEBUG=0
  -> Ressources proches de la production
  -> Tests de charge et tests d'intégration se font ici avant prod

  ── apps/taskmanager/overlays/staging/kustomization.yaml ───────────────────

  apiVersion: kustomize.config.k8s.io/v1beta1
  kind: Kustomization

  resources:
    - ../../base

  namespace: taskmanager-staging

  patches:
    # 2 réplicas en staging:
    - patch: |-
        - op: replace
          path: /spec/replicas
          value: 2
      target:
        kind: Deployment
        name: taskmanager-api

    # Hôte Ingress pour staging:
    - patch: |-
        - op: replace
          path: /spec/rules/0/host
          value: api-staging.taskmanager.local
      target:
        kind: Ingress
        name: taskmanager-ingress

    # HPA staging: min 2, max 5:
    - patch: |-
        - op: replace
          path: /spec/minReplicas
          value: 2
        - op: replace
          path: /spec/maxReplicas
          value: 5
      target:
        kind: HorizontalPodAutoscaler
        name: taskmanager-api-hpa

    # Ressources intermédiaires en staging:
    - patch: |-
        - op: replace
          path: /spec/template/spec/containers/0/resources
          value:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "512Mi"
              cpu: "500m"
      target:
        kind: Deployment
        name: taskmanager-api

  configMapGenerator:
    - name: taskmanager-config
      behavior: merge
      literals:
        - FLASK_ENV=staging
        - FLASK_DEBUG=0
        - APP_VERSION=staging-1.0.0

  images:
    - name: ghcr.io/equipe/taskmanager-api
      newTag: "1.0.0"   # Version spécifique, mise à jour par CI/CD

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 2.4 — L'OVERLAY PRODUCTION (overlays/prod/)
────────────────────────────────────────────────────────────────────────────────

  SPÉCIFICITÉS DE LA PROD:
  ─────────────────────────
  -> 3 réplicas minimum (haute disponibilité)
  -> Ressources maximales
  -> Anti-affinité stricte (Pods sur différents nœuds)
  -> PodDisruptionBudget avec minAvailable=2
  -> Annotations de monitoring (Prometheus)
  -> Sync Argo CD MANUELLE (validation humaine obligatoire)
  -> Notifications Slack activées

  ── apps/taskmanager/overlays/prod/kustomization.yaml ──────────────────────

  apiVersion: kustomize.config.k8s.io/v1beta1
  kind: Kustomization

  resources:
    - ../../base

  namespace: taskmanager-prod

  patches:
    # 3 réplicas en production:
    - patch: |-
        - op: replace
          path: /spec/replicas
          value: 3
      target:
        kind: Deployment
        name: taskmanager-api

    # Hôte Ingress pour prod (domaine réel):
    - patch: |-
        - op: replace
          path: /spec/rules/0/host
          value: api.taskmanager.equipe.com
      target:
        kind: Ingress
        name: taskmanager-ingress

    # HPA prod: min 3, max 10:
    - patch: |-
        - op: replace
          path: /spec/minReplicas
          value: 3
        - op: replace
          path: /spec/maxReplicas
          value: 10
      target:
        kind: HorizontalPodAutoscaler
        name: taskmanager-api-hpa

    # Ressources complètes en prod:
    - patch: |-
        - op: replace
          path: /spec/template/spec/containers/0/resources
          value:
            requests:
              memory: "256Mi"
              cpu: "250m"
            limits:
              memory: "1Gi"
              cpu: "1000m"
      target:
        kind: Deployment
        name: taskmanager-api

    # PDB prod: toujours au moins 2 Pods disponibles:
    - patch: |-
        - op: replace
          path: /spec/minAvailable
          value: 2
      target:
        kind: PodDisruptionBudget
        name: taskmanager-api-pdb

    # Annotations de monitoring Prometheus pour la prod:
    - patch: |-
        - op: add
          path: /spec/template/metadata/annotations
          value:
            prometheus.io/scrape: "true"
            prometheus.io/port: "5000"
            prometheus.io/path: "/metrics"
      target:
        kind: Deployment
        name: taskmanager-api

  configMapGenerator:
    - name: taskmanager-config
      behavior: merge
      literals:
        - FLASK_ENV=production
        - FLASK_DEBUG=0
        - APP_VERSION=1.0.0

  images:
    - name: ghcr.io/equipe/taskmanager-api
      newTag: "1.0.0"   # Version fixe, mise à jour seulement lors des releases

================================================================================
PARTIE 3 — ALICE CRÉE LES APPLICATIONS ARGO CD
================================================================================

  QU'EST-CE QU'UNE APPLICATION ARGO CD?
  ───────────────────────────────────────
  Une "Application" Argo CD est un objet Kubernetes (une Custom Resource).
  Elle dit à Argo CD:
  1. "Regarde ce dépôt Git, ce chemin, cette branche"
  2. "Déploie le résultat dans ce cluster, ce namespace"
  3. "Avec ces options de synchronisation"

  On peut créer des Applications via:
  -> La CLI: argocd app create ...
  -> L'UI: bouton "+ New App"
  -> Des fichiers YAML (recommandé! GitOps jusqu'au bout)

  On utilisera des fichiers YAML pour que les Applications elles-mêmes
  soient dans Git (pattern "App of Apps" — voir Partie 5).

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 3.1 — CRÉER LE PROJET ARGO CD
────────────────────────────────────────────────────────────────────────────────

  POURQUOI UN PROJET?
  ────────────────────
  Un Projet Argo CD regroupe des Applications avec des politiques communes:
  -> Quels dépôts Git sont autorisés comme sources?
  -> Quels clusters/namespaces sont autorisés comme destinations?
  -> Quelles ressources K8s sont autorisées ou interdites?
  -> Qui peut faire quoi sur ce projet?

  ── argocd-projects/taskmanager-project.yaml ───────────────────────────────

  apiVersion: argoproj.io/v1alpha1
  kind: AppProject
  metadata:
    name: taskmanager
    namespace: argocd
    finalizers:
      # Empêche la suppression si des Applications existent dans ce projet:
      - resources-finalizer.argocd.argoproj.io
  spec:
    description: "Projet TaskManager — API REST Flask + PostgreSQL + Redis"

    # Dépôts Git AUTORISÉS comme sources:
    sourceRepos:
      - 'https://github.com/equipe/taskmanager-gitops'
      - 'https://charts.bitnami.com/bitnami'  # Pour Helm charts PostgreSQL/Redis
      - 'https://github.com/equipe/taskmanager'  # Pour ApplicationSets

    # Destinations AUTORISÉES (cluster + namespace):
    destinations:
      - namespace: taskmanager-dev
        server: https://kubernetes.default.svc
      - namespace: taskmanager-staging
        server: https://kubernetes.default.svc
      - namespace: taskmanager-prod
        server: https://kubernetes.default.svc
      - namespace: argocd
        server: https://kubernetes.default.svc

    # Ressources K8s autorisées dans ce projet:
    # (whitelist: seules ces ressources peuvent être créées)
    clusterResourceWhitelist:
      - group: ''
        kind: Namespace   # Autoriser la création de Namespaces
    namespaceResourceWhitelist:
      - group: 'apps'
        kind: Deployment
      - group: 'apps'
        kind: StatefulSet
      - group: ''
        kind: Service
      - group: ''
        kind: ConfigMap
      - group: ''
        kind: Secret
      - group: ''
        kind: ServiceAccount
      - group: 'networking.k8s.io'
        kind: Ingress
      - group: 'autoscaling'
        kind: HorizontalPodAutoscaler
      - group: 'policy'
        kind: PodDisruptionBudget
      - group: 'bitnami.com'
        kind: SealedSecret

    # Ressources K8s INTERDITES (protection supplémentaire):
    namespaceResourceBlacklist:
      - group: ''
        kind: ResourceQuota   # Ne pas autoriser à modifier les quotas

    # Politiques de RBAC spécifiques au projet:
    roles:
      - name: project-admin
        description: Accès complet au projet TaskManager
        policies:
          - p, proj:taskmanager:project-admin, applications, *, taskmanager/*, allow
        groups:
          - alice-team   # Groupe GitHub (si SSO configuré)

      - name: project-developer
        description: Développeurs — peuvent sync dev et staging
        policies:
          - p, proj:taskmanager:project-developer, applications, get, taskmanager/*, allow
          - p, proj:taskmanager:project-developer, applications, sync, taskmanager/taskmanager-dev, allow
          - p, proj:taskmanager:project-developer, applications, sync, taskmanager/taskmanager-staging, allow
          - p, proj:taskmanager:project-developer, logs, get, taskmanager/*, allow
        groups:
          - developers-team

    # Synchronisation par fenêtre temporelle:
    # (On peut bloquer les déploiements en prod le week-end)
    syncWindows:
      # INTERDIRE les sync sur prod le week-end:
      - kind: deny
        schedule: '0 18 * * 5'   # Vendredi 18h
        duration: 60h              # Pendant 60 heures (jusqu'à lundi 6h)
        applications:
          - taskmanager-prod
        namespaces:
          - taskmanager-prod
        manualSync: false         # Interdire même les syncs manuels!

      # AUTORISER les sync urgents (hotfix) même le week-end:
      # Un admin peut override manuellement si nécessaire.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 3.2 — CRÉER LES APPLICATIONS ARGO CD (argocd-apps/)
────────────────────────────────────────────────────────────────────────────────

  ── argocd-apps/taskmanager-dev.yaml ───────────────────────────────────────

  apiVersion: argoproj.io/v1alpha1
  kind: Application
  metadata:
    name: taskmanager-dev
    namespace: argocd
    labels:
      environment: dev
      app: taskmanager
    annotations:
      # Description visible dans l'UI Argo CD:
      argocd.argoproj.io/description: "TaskManager API — Environnement Développement"
  spec:
    # Projet auquel appartient cette Application:
    project: taskmanager

    # SOURCE: où Argo CD trouve les manifestes:
    source:
      repoURL: https://github.com/equipe/taskmanager-gitops
      targetRevision: HEAD          # Branche à surveiller (HEAD = branche par défaut)
      path: apps/taskmanager/overlays/dev   # Chemin dans le dépôt
      plugin:
        # Pas de plugin nécessaire pour Kustomize natif

    # DESTINATION: où Argo CD déploie:
    destination:
      server: https://kubernetes.default.svc   # Cluster local
      namespace: taskmanager-dev

    # OPTIONS DE SYNCHRONISATION:
    syncPolicy:
      # Synchronisation AUTOMATIQUE pour dev:
      # Dès qu'Argo CD détecte un changement dans Git -> il synce automatiquement.
      automated:
        prune: true       # Supprimer les ressources supprimées de Git
        selfHeal: true    # Corriger les modifications manuelles sur le cluster
        allowEmpty: false # Refuser de déployer si les manifestes sont vides

      # Options appliquées à chaque sync:
      syncOptions:
        - CreateNamespace=true     # Créer le namespace s'il n'existe pas
        - PrunePropagationPolicy=foreground  # Supprimer les ressources enfants avant parents
        - PruneLast=true           # Supprimer après avoir créé les nouvelles ressources
        - ApplyOutOfSyncOnly=true  # N'appliquer que les ressources qui ont changé (plus rapide)
        - RespectIgnoreDifferences=true

      # Retry en cas d'échec:
      retry:
        limit: 5          # Maximum 5 tentatives
        backoff:
          duration: 5s    # Attendre 5s après le 1er échec
          factor: 2       # Doubler à chaque fois: 5s, 10s, 20s, 40s, 80s
          maxDuration: 3m # Maximum 3 minutes d'attente

    # IGNORER CERTAINES DIFFÉRENCES:
    # Certains champs sont modifiés par K8s lui-même (réplicas par HPA,
    # annotations par les controllers). On les ignore pour éviter des
    # faux-positifs de "OutOfSync".
    ignoreDifferences:
      - group: apps
        kind: Deployment
        jsonPointers:
          - /spec/replicas   # Ignoré car le HPA modifie ce champ

  ── argocd-apps/taskmanager-staging.yaml ───────────────────────────────────

  apiVersion: argoproj.io/v1alpha1
  kind: Application
  metadata:
    name: taskmanager-staging
    namespace: argocd
    labels:
      environment: staging
      app: taskmanager
    annotations:
      argocd.argoproj.io/description: "TaskManager API — Environnement Staging"
  spec:
    project: taskmanager

    source:
      repoURL: https://github.com/equipe/taskmanager-gitops
      targetRevision: HEAD
      path: apps/taskmanager/overlays/staging

    destination:
      server: https://kubernetes.default.svc
      namespace: taskmanager-staging

    syncPolicy:
      # SEMI-AUTOMATIQUE pour staging:
      # Auto-sync mais sans selfHeal (les modifications manuelles sont tolérées
      # le temps des tests). La PR doit être approuvée avant que le CI sync staging.
      automated:
        prune: true
        selfHeal: false    # <- Différence avec dev: pas de self-healing
        allowEmpty: false

      syncOptions:
        - CreateNamespace=true
        - PrunePropagationPolicy=foreground
        - PruneLast=true
        - ApplyOutOfSyncOnly=true

      retry:
        limit: 3
        backoff:
          duration: 10s
          factor: 2
          maxDuration: 5m

    ignoreDifferences:
      - group: apps
        kind: Deployment
        jsonPointers:
          - /spec/replicas

  ── argocd-apps/taskmanager-prod.yaml ──────────────────────────────────────

  apiVersion: argoproj.io/v1alpha1
  kind: Application
  metadata:
    name: taskmanager-prod
    namespace: argocd
    labels:
      environment: prod
      app: taskmanager
    annotations:
      argocd.argoproj.io/description: "TaskManager API — PRODUCTION"
      # Notification Slack activée:
      notifications.argoproj.io/subscribe.on-deployed.slack: "prod-deployments"
      notifications.argoproj.io/subscribe.on-health-degraded.slack: "prod-alerts"
  spec:
    project: taskmanager

    source:
      repoURL: https://github.com/equipe/taskmanager-gitops
      targetRevision: main   # <- Toujours sur la branche main (stable!)
      path: apps/taskmanager/overlays/prod

    destination:
      server: https://kubernetes.default.svc
      namespace: taskmanager-prod

    syncPolicy:
      # PAS DE SYNC AUTOMATIQUE EN PROD:
      # automated est absent -> sync uniquement quand quelqu'un clique "Sync".
      # Protège contre les déploiements accidentels en production.

      syncOptions:
        - CreateNamespace=true
        - PrunePropagationPolicy=foreground
        - PruneLast=true
        - ApplyOutOfSyncOnly=true
        # Validate: vérifier les manifestes avant d'appliquer (très important en prod):
        - Validate=true

      retry:
        limit: 2
        backoff:
          duration: 30s
          factor: 2
          maxDuration: 5m

    ignoreDifferences:
      - group: apps
        kind: Deployment
        jsonPointers:
          - /spec/replicas
      # HPA modifie les réplicas -> ignorer:
      - group: autoscaling
        kind: HorizontalPodAutoscaler
        jsonPointers:
          - /status

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 3.3 — ALICE APPLIQUE LE PROJET ET LES APPLICATIONS
────────────────────────────────────────────────────────────────────────────────

  # CONNECTER ARGO CD AU DÉPÔT GITOPS (si dépôt privé):
  # POURQUOI? Argo CD doit s'authentifier pour lire un dépôt privé GitHub.

  # Méthode SSH (recommandée):
  argocd repo add git@github.com:equipe/taskmanager-gitops.git \
    --ssh-private-key-path ~/.ssh/id_ed25519_github \
    --name taskmanager-gitops

  # Ou via HTTPS avec un token:
  argocd repo add https://github.com/equipe/taskmanager-gitops \
    --username git \
    --password ghp_votre_token_github_ici \
    --name taskmanager-gitops

  # Vérifier que le dépôt est accessible:
  argocd repo list
  # -> TYPE  NAME                   REPO
  # -> git   taskmanager-gitops     git@github.com:equipe/taskmanager-gitops.git  [OK]

  # CRÉER LE PROJET:
  kubectl apply -f argocd-projects/taskmanager-project.yaml -n argocd

  # Vérifier:
  argocd proj list
  # -> NAME          DESCRIPTION                      DESTINATIONS   SOURCES
  # -> taskmanager   Projet TaskManager — API Flask   3              2

  # CRÉER LES APPLICATIONS:
  kubectl apply -f argocd-apps/taskmanager-dev.yaml -n argocd
  kubectl apply -f argocd-apps/taskmanager-staging.yaml -n argocd
  kubectl apply -f argocd-apps/taskmanager-prod.yaml -n argocd

  # Vérifier:
  argocd app list

  CE QUE VOUS VOYEZ:
  ───────────────────
  NAME                  CLUSTER     NAMESPACE            PROJECT       STATUS    HEALTH
  taskmanager-dev       in-cluster  taskmanager-dev      taskmanager   Unknown   Missing
  taskmanager-staging   in-cluster  taskmanager-staging  taskmanager   Unknown   Missing
  taskmanager-prod      in-cluster  taskmanager-prod     taskmanager   Unknown   Missing

  # "Unknown/Missing" car aucune synchronisation n'a encore eu lieu.
  # Déclencher la première sync manuellement:

  argocd app sync taskmanager-dev
  argocd app sync taskmanager-staging
  # (pas taskmanager-prod encore, on attendra le CI/CD)

  CE QUE VOUS VOYEZ APRÈS SYNC:
  ──────────────────────────────
  TIMESTAMP                  GROUP        KIND                NAMESPACE            NAME
  2024-01-15T10:30:00+01:00  apps         Deployment          taskmanager-dev      taskmanager-api  Running  Synced
  2024-01-15T10:30:01+01:00               Service             taskmanager-dev      taskmanager-api  Running  Synced
  2024-01-15T10:30:02+01:00               ConfigMap           taskmanager-dev      taskmanager-config  Synced
  ...
  Message:    successfully synced (all tasks run)

  # Vérifier l'état final:
  argocd app list

  CE QUE VOUS VOYEZ:
  ───────────────────
  NAME                  STATUS  HEALTH    SYNC    MESSAGE
  taskmanager-dev       Synced  Healthy   Synced  successfully synced
  taskmanager-staging   Synced  Healthy   Synced  successfully synced
  taskmanager-prod      Synced  Unknown   N/A     Not synced yet

  # Voir les ressources déployées dans dev:
  kubectl get all -n taskmanager-dev

  CE QUE VOUS VOYEZ:
  ───────────────────
  NAME                                    READY   STATUS    RESTARTS   AGE
  pod/taskmanager-api-7d8f9c6b5-xk2mn     1/1     Running   0          45s

  NAME                    TYPE        CLUSTER-IP      PORT(S)   AGE
  service/taskmanager-api ClusterIP   10.96.45.23     80/TCP    45s

  NAME                              READY   UP-TO-DATE   AVAILABLE
  deployment.apps/taskmanager-api   1/1     1            1

================================================================================
PARTIE 4 — CI/CD: LE PIPELINE COMPLET CODE -> DOCKER -> KUBERNETES
================================================================================

  LA CHAÎNE COMPLÈTE:
  ────────────────────
  Bob pousse du code sur GitHub
        v
  GitHub Actions CI (tests + lint)
        v
  GitHub Actions CD (build image Docker + push sur ghcr.io)
        v
  GitHub Actions Update (met à jour le tag dans taskmanager-gitops)
        v
  Argo CD détecte le changement dans taskmanager-gitops
        v
  Argo CD synce le cluster (dev et/ou staging automatiquement)
        v
  Alice clique "Sync" sur prod manuellement (ou via un tag de release)

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.1 — ALICE CONFIGURE LES SECRETS GITHUB POUR LE PIPELINE
────────────────────────────────────────────────────────────────────────────────

  # Dans equipe/taskmanager -> Settings -> Secrets -> New repository secret:
  # (Rappel du guide précédent — on ajoute les secrets spécifiques K8s)

  GHCR_TOKEN         -> Token GitHub avec accès write:packages
                       (pour pousser les images sur ghcr.io)
  GITOPS_DEPLOY_KEY  -> Clé SSH PRIVÉE pour pousser dans taskmanager-gitops
                       (le CI doit pouvoir modifier ce dépôt séparé)
  ARGOCD_TOKEN       -> Token Argo CD pour déclencher des syncs via le CLI

  # Générer la clé de déploiement GitOps:
  ssh-keygen -t ed25519 -C "github-actions-gitops" -f ~/.ssh/gitops_deploy_key -N ""
  # Ajouter la clé PUBLIQUE dans equipe/taskmanager-gitops
  # -> Settings -> Deploy keys -> Add deploy key -> Allow write access [OK]
  # Mettre la clé PRIVÉE dans le secret GITOPS_DEPLOY_KEY de taskmanager (dépôt code)

  # Générer le token Argo CD pour le CI/CD:
  argocd account generate-token --account bob
  # -> eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  # Mettre ce token dans le secret ARGOCD_TOKEN

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.2 — LE WORKFLOW CI (tests) DANS taskmanager
────────────────────────────────────────────────────────────────────────────────

  # Fichier: .github/workflows/ci.yml (dans equipe/taskmanager)
  # Ce fichier existait déjà (guide précédent). On n'y change rien.
  # Il lance les tests à chaque push et pull request.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 4.3 — LE WORKFLOW CD (build + deploy) DANS taskmanager
────────────────────────────────────────────────────────────────────────────────

  ── .github/workflows/cd-gitops.yml ─────────────────────────────────────────
  # Fichier dans equipe/taskmanager (dépôt code source)
  # Ce workflow:
  # 1. Build l'image Docker
  # 2. Pousse sur ghcr.io avec le tag de commit
  # 3. Clone taskmanager-gitops
  # 4. Met à jour le tag de l'image dans les overlays Kustomize
  # 5. Commit et pousse dans taskmanager-gitops
  # -> Argo CD détecte le changement et synce

  name: CD — Build Docker et mise à jour GitOps

  on:
    push:
      branches:
        - develop   # Build à chaque push sur develop -> met à jour dev
      tags:
        - 'v*.*.*'  # Build à chaque tag de release -> met à jour staging et prod

  env:
    REGISTRY: ghcr.io
    IMAGE_NAME: equipe/taskmanager-api

  jobs:

    # ─── JOB 1: BUILD ET PUSH DE L'IMAGE DOCKER ───────────────────────────

    build-and-push:
      name: Build et Push image Docker
      runs-on: ubuntu-latest
      permissions:
        contents: read
        packages: write
      outputs:
        image-tag: ${{ steps.meta.outputs.version }}
        image-digest: ${{ steps.build.outputs.digest }}

      steps:
        - name: Checkout du code source
          uses: actions/checkout@v4

        - name: Connexion à GitHub Container Registry
          uses: docker/login-action@v3
          with:
            registry: ${{ env.REGISTRY }}
            username: ${{ github.actor }}
            password: ${{ secrets.GITHUB_TOKEN }}
            # GITHUB_TOKEN est automatiquement disponible, pas besoin de le créer.

        - name: Extraire les métadonnées Docker (tags, labels)
          id: meta
          uses: docker/metadata-action@v5
          with:
            images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
            tags: |
              # Pour les tags de release (v1.2.0):
              type=semver,pattern={{version}}
              type=semver,pattern={{major}}.{{minor}}
              # Pour les branches (develop -> sha court):
              type=ref,event=branch
              type=sha,prefix=sha-,format=short
              # Tag "latest" uniquement pour la branche principale:
              type=raw,value=latest,enable={{is_default_branch}}
            labels: |
              org.opencontainers.image.title=TaskManager API
              org.opencontainers.image.description=API REST Flask gestion de tâches
              org.opencontainers.image.vendor=Equipe TaskManager

        - name: Configurer Docker Buildx (build multi-plateforme)
          uses: docker/setup-buildx-action@v3

        - name: Build et Push l'image Docker
          id: build
          uses: docker/build-push-action@v5
          with:
            context: .
            file: docker/Dockerfile
            target: production
            platforms: linux/amd64,linux/arm64   # Multi-architecture
            push: true
            tags: ${{ steps.meta.outputs.tags }}
            labels: ${{ steps.meta.outputs.labels }}
            # Cache GitHub Actions pour accélérer les builds:
            cache-from: type=gha
            cache-to: type=gha,mode=max
            build-args: |
              APP_VERSION=${{ github.ref_name }}
              BUILD_DATE=${{ github.event.head_commit.timestamp }}
              GIT_COMMIT=${{ github.sha }}

        - name: Afficher le digest de l'image
          run: echo "Image poussée avec le digest ${{ steps.build.outputs.digest }}"

    # ─── JOB 2: MISE À JOUR DU DÉPÔT GITOPS ──────────────────────────────

    update-gitops:
      name: Mettre à jour le dépôt GitOps
      runs-on: ubuntu-latest
      needs: build-and-push   # Démarre seulement si le build a réussi
      permissions:
        contents: read

      steps:
        - name: Configurer SSH pour accéder à taskmanager-gitops
          uses: webfactory/ssh-agent@v0.9.0
          with:
            ssh-private-key: ${{ secrets.GITOPS_DEPLOY_KEY }}

        - name: Cloner le dépôt gitops-config
          run: |
            git clone git@github.com:equipe/taskmanager-gitops.git
            cd taskmanager-gitops
            git config user.name "GitHub Actions CD"
            git config user.email "actions@github.com"

        - name: Installer kustomize
          run: |
            curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
            sudo mv kustomize /usr/local/bin/

        - name: Mettre à jour le tag de l'image pour dev (sur branche develop)
          if: github.ref_type == 'branch' && github.ref_name == 'develop'
          run: |
            cd taskmanager-gitops/apps/taskmanager/overlays/dev
            IMAGE_TAG="${{ needs.build-and-push.outputs.image-tag }}"
            echo "Mise à jour du tag dev -> ${IMAGE_TAG}"

            # Kustomize met à jour le tag dans kustomization.yaml:
            kustomize edit set image ghcr.io/equipe/taskmanager-api="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${IMAGE_TAG}"

            # Vérifier le changement:
            cat kustomization.yaml | grep -A2 "images:"

        - name: Mettre à jour le tag pour staging et prod (sur tag de release)
          if: github.ref_type == 'tag'
          run: |
            RELEASE_TAG="${{ github.ref_name }}"
            # Supprimer le 'v' du début (v1.2.0 -> 1.2.0):
            IMAGE_TAG="${RELEASE_TAG#v}"
            echo "Mise à jour staging et prod -> ${IMAGE_TAG}"

            cd taskmanager-gitops

            # Mettre à jour staging:
            cd apps/taskmanager/overlays/staging
            kustomize edit set image ghcr.io/equipe/taskmanager-api="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${IMAGE_TAG}"
            cd ../../..

            # Mettre à jour prod:
            cd apps/taskmanager/overlays/prod
            kustomize edit set image ghcr.io/equipe/taskmanager-api="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${IMAGE_TAG}"

            # Mettre à jour aussi le ConfigMap APP_VERSION:
            # (via un patch dans le kustomization.yaml de prod)

        - name: Committer et pousser les changements dans gitops-config
          run: |
            cd taskmanager-gitops

            # Vérifier s'il y a des changements:
            if git diff --quiet; then
              echo "Aucun changement détecté dans gitops-config."
              exit 0
            fi

            git add .
            git status

            # Message de commit structuré avec toutes les infos pour la traçabilité:
            git commit -m "chore(deploy): update image tag to ${{ needs.build-and-push.outputs.image-tag }}

            Triggered by: ${{ github.event_name }}
            Source commit: ${{ github.sha }}
            Source repository: ${{ github.repository }}
            Workflow: ${{ github.workflow }}
            Actor: ${{ github.actor }}
            Image digest: ${{ needs.build-and-push.outputs.image-digest }}"

            git push origin main

            echo "[OK] Dépôt gitops-config mis à jour!"
            echo "Argo CD va détecter ce changement et synchroniser automatiquement."

    # ─── JOB 3: ATTENDRE LA SYNC ARGO CD (OPTIONNEL) ─────────────────────

    wait-for-argocd:
      name: Attendre la synchronisation Argo CD
      runs-on: ubuntu-latest
      needs: update-gitops
      if: github.ref_name == 'develop'   # Seulement pour dev (sync auto)

      steps:
        - name: Installer argocd CLI
          run: |
            curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
            chmod +x argocd && sudo mv argocd /usr/local/bin/

        - name: Se connecter à Argo CD
          run: |
            argocd login ${{ secrets.ARGOCD_SERVER }} \
              --auth-token ${{ secrets.ARGOCD_TOKEN }} \
              --grpc-web \
              --insecure

        - name: Attendre que taskmanager-dev soit synchronisé et healthy
          run: |
            echo "Attente de la synchronisation de taskmanager-dev..."
            argocd app wait taskmanager-dev \
              --sync \
              --health \
              --timeout 300   # Timeout après 5 minutes

        - name: Vérifier le statut final
          run: |
            argocd app get taskmanager-dev
            # Affiche toutes les ressources et leur état.

================================================================================
PARTIE 5 — SCÉNARIOS COMPLETS: ALICE, BOB ET CLAIRE AU QUOTIDIEN
================================================================================

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO 1 — BOB: DÉVELOPPER ET DÉPLOYER UNE FEATURE (E2E)
Issue: #18 — Ajouter des statistiques à GET /tasks/stats
════════════════════════════════════════════════════════════════════════════════

  CONTEXTE:
  ──────────
  Bob doit implémenter un endpoint GET /tasks/stats qui retourne:
  - Nombre total de tâches
  - Tâches par priorité (low/medium/high)
  - Tâches complétées vs en cours
  Cela implique du code Flask ET un nouveau déploiement.

────────────────────────────────────────────────────────────────────────────────
  BOB — PHASE 1: Développement local (Machine Ubuntu)
────────────────────────────────────────────────────────────────────────────────

  ÉTAPE 1 — Créer la branche feature:
  ─────────────────────────────────────
  cd ~/taskmanager
  git checkout develop
  git pull origin develop
  git checkout -b feature/18-stats-endpoint
  git push -u origin feature/18-stats-endpoint

  ÉTAPE 2 — Développer le code:
  ──────────────────────────────
  # Bob ajoute l'endpoint dans app/routes.py:
  # (code complet, pas abrégé)

  @tasks_bp.route('/tasks/stats', methods=['GET'])
  def get_tasks_stats():
      """
      Retourner les statistiques des tâches.

      Returns:
          200: {
              "total": int,
              "by_priority": {"low": int, "medium": int, "high": int},
              "by_status": {"done": int, "pending": int},
              "completion_rate": float (0.0 à 100.0)
          }
      """
      from sqlalchemy import func

      total = Task.query.count()

      if total == 0:
          return jsonify({
              "total": 0,
              "by_priority": {"low": 0, "medium": 0, "high": 0},
              "by_status": {"done": 0, "pending": 0},
              "completion_rate": 0.0
          })

      # Compter par priorité avec une seule requête:
      priority_counts = db.session.query(
          Task.priority,
          func.count(Task.id)
      ).group_by(Task.priority).all()

      by_priority = {"low": 0, "medium": 0, "high": 0}
      for priority, count in priority_counts:
          if priority in by_priority:
              by_priority[priority] = count

      # Compter par statut:
      done_count = Task.query.filter(Task.done == True).count()
      pending_count = total - done_count
      completion_rate = round((done_count / total) * 100, 2)

      return jsonify({
          "total": total,
          "by_priority": by_priority,
          "by_status": {
              "done": done_count,
              "pending": pending_count
          },
          "completion_rate": completion_rate
      })

  # Bob ajoute les tests dans tests/test_routes.py:

  class TestTaskStats:
      def test_stats_empty_db(self, client):
          """Stats sur une base vide doivent retourner 0 partout."""
          r = client.get('/tasks/stats')
          assert r.status_code == 200
          assert r.json['total'] == 0
          assert r.json['completion_rate'] == 0.0

      def test_stats_with_tasks(self, client):
          """Stats avec des tâches créées."""
          # Créer 5 tâches avec différentes priorités et statuts:
          tasks_data = [
              {'title': 'Task 1', 'priority': 'high'},
              {'title': 'Task 2', 'priority': 'high'},
              {'title': 'Task 3', 'priority': 'medium'},
              {'title': 'Task 4', 'priority': 'low'},
              {'title': 'Task 5', 'priority': 'low'},
          ]
          task_ids = []
          for data in tasks_data:
              r = client.post('/tasks',
                  data=json.dumps(data),
                  content_type='application/json')
              task_ids.append(r.json['id'])

          # Marquer les 2 premières comme terminées:
          for task_id in task_ids[:2]:
              client.put(f'/tasks/{task_id}',
                  data=json.dumps({'done': True}),
                  content_type='application/json')

          r = client.get('/tasks/stats')
          assert r.status_code == 200
          assert r.json['total'] == 5
          assert r.json['by_priority']['high'] == 2
          assert r.json['by_priority']['medium'] == 1
          assert r.json['by_priority']['low'] == 2
          assert r.json['by_status']['done'] == 2
          assert r.json['by_status']['pending'] == 3
          assert r.json['completion_rate'] == 40.0

      def test_stats_all_done(self, client):
          """Stats quand toutes les tâches sont terminées."""
          r = client.post('/tasks',
              data=json.dumps({'title': 'Task seule', 'priority': 'medium'}),
              content_type='application/json')
          task_id = r.json['id']
          client.put(f'/tasks/{task_id}',
              data=json.dumps({'done': True}),
              content_type='application/json')

          r = client.get('/tasks/stats')
          assert r.status_code == 200
          assert r.json['completion_rate'] == 100.0

  # Lancer les tests:
  source venv/bin/activate
  pytest tests/ -v --cov=app --cov-report=term-missing
  # -> 27 tests passed, coverage 95%   [OK]

  ÉTAPE 3 — Committer:
  ─────────────────────
  git add app/routes.py tests/test_routes.py
  git commit -m "feat(routes): add GET /tasks/stats endpoint

  Returns aggregated statistics:
  - Total task count
  - Count by priority (low/medium/high)
  - Count by status (done/pending)
  - Completion rate percentage

  Single query for priority counts (SQLAlchemy func.count).
  Returns sensible defaults for empty database.
  Closes #18"

  git push origin feature/18-stats-endpoint

────────────────────────────────────────────────────────────────────────────────
  BOB — PHASE 2: Pull Request -> Review -> Merge -> Déploiement Auto en Dev
────────────────────────────────────────────────────────────────────────────────

  ÉTAPE 4 — Ouvrir la PR:
  ────────────────────────
  gh pr create \
    --base develop \
    --title "feat(routes): add GET /tasks/stats endpoint" \
    --body "Closes #18" \
    --reviewer alice-dev,claire-dev

  ÉTAPE 5 — GitHub Actions CI s'exécute automatiquement:
  ─────────────────────────────────────────────────────────
  # Dans l'onglet "Actions" de la PR:
  # [OK] ci/lint: passed (flake8 + black — 45s)
  # [OK] ci/tests: passed (27 tests, coverage 95% — 1m12s)
  # GitHub affiche: "All checks have passed" -> la PR peut être mergée.

  ÉTAPE 6 — Alice review et merge:
  ──────────────────────────────────
  # Alice review le code, approuve.
  # Claire vérifie les tests, approuve.
  # Alice clique "Squash and merge" -> PR mergée dans develop.

  # GitHub Actions CD se déclenche automatiquement (push sur develop):
  # [OK] build-and-push: image Docker buildée et poussée
  #    -> ghcr.io/equipe/taskmanager-api:sha-a3f8c1d
  # [OK] update-gitops: tag mis à jour dans taskmanager-gitops/overlays/dev
  #    -> commit: "chore(deploy): update image tag to sha-a3f8c1d"
  # [OK] wait-for-argocd: attente de la sync Argo CD

  CE QUE ARGO CD FAIT AUTOMATIQUEMENT (en dev):
  ───────────────────────────────────────────────
  # Argo CD vérifie le dépôt taskmanager-gitops toutes les 3 minutes.
  # (Ou immédiatement via webhook — voir Étape 5.5)
  # Il voit que kustomization.yaml a changé (nouveau tag d'image).
  # Status: OutOfSync <- détecté!
  # Sync automatique déclenché (car automated: true pour dev).

  # DANS L'UI ARGO CD (https://localhost:8080):
  # -> taskmanager-dev: Syncing... ⟳
  # -> After sync: Synced / Healthy [OK]

  # Processus Kubernetes:
  # 1. Argo CD applique le nouveau Deployment avec l'image sha-a3f8c1d
  # 2. K8s crée un nouveau Pod avec la nouvelle image
  # 3. Readiness Probe attend que /health réponde
  # 4. Quand Ready, K8s retire le trafic de l'ancien Pod
  # 5. Ancien Pod supprimé
  # Rolling update = zéro downtime [OK]

  ÉTAPE 7 — Bob vérifie le déploiement:
  ──────────────────────────────────────
  # Via kubectl:
  kubectl get pods -n taskmanager-dev -w
  # Voir le rolling update en temps réel:
  # -> NAME                          READY   STATUS              RESTARTS
  # -> taskmanager-api-7d8f9c6b5-xk2mn  1/1  Running             0  (ancien)
  # -> taskmanager-api-8e9g0d7c6-yn3no  0/1  ContainerCreating   0  (nouveau)
  # -> taskmanager-api-8e9g0d7c6-yn3no  1/1  Running             0  (nouveau prêt)
  # -> taskmanager-api-7d8f9c6b5-xk2mn  0/1  Terminating         0  (ancien supprimé)

  # Tester l'endpoint:
  curl http://api-dev.taskmanager.local/tasks/stats
  # -> {"total": 0, "by_priority": {...}, "completion_rate": 0.0}   [OK]

  # Voir les logs du Pod:
  kubectl logs -n taskmanager-dev deployment/taskmanager-api -f
  # -> [2024-01-15 14:30:00] INFO  in routes: GET /tasks/stats 200 OK

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO 2 — CLAIRE: VÉRIFIER ET PROMOUVOIR VERS STAGING
════════════════════════════════════════════════════════════════════════════════

  CONTEXTE:
  ──────────
  La feature de Bob est déployée en dev. Claire doit:
  1. Vérifier que ça fonctionne en dev
  2. Approuver la promotion vers staging
  3. Valider en staging avant de demander la release prod

────────────────────────────────────────────────────────────────────────────────
  CLAIRE — Vérification en dev via l'UI Argo CD
────────────────────────────────────────────────────────────────────────────────

  # Claire ouvre https://localhost:8080 avec ses credentials.
  # Elle voit le tableau de bord des Applications.

  # Cliquer sur "taskmanager-dev":
  # -> Arbre visuel des ressources Kubernetes (Deployment -> Pods -> Services...)
  # -> Tous les nœuds en vert = Healthy [OK]

  # EXPLORER L'ARBRE DES RESSOURCES:
  # ──────────────────────────────────
  # Deployment/taskmanager-api
  #   └─ ReplicaSet/taskmanager-api-8e9g0d7c6
  #       └─ Pod/taskmanager-api-8e9g0d7c6-yn3no (Running [OK])
  # Service/taskmanager-api (ClusterIP [OK])
  # ConfigMap/taskmanager-config (Synced [OK])
  # Secret/taskmanager-secrets (Synced [OK])
  # Ingress/taskmanager-ingress (Synced [OK])
  # HPA/taskmanager-api-hpa (Synced [OK])

  # VOIR LES LOGS D'UN POD DEPUIS L'UI:
  # ─────────────────────────────────────
  # Cliquer sur le Pod -> "Logs" -> Voir les logs en temps réel.
  # Utile pour débugger sans avoir kubectl configuré.

  # VOIR LES ÉVÉNEMENTS:
  # ─────────────────────
  # Cliquer "Events" pour voir l'historique des événements K8s:
  # -> Normal Scaled deployment/taskmanager-api: Scaled up replica set ...
  # -> Normal Pulled  Pulling image "ghcr.io/equipe/taskmanager-api:sha-a3f8c1d"
  # -> Normal Started Started container api

  # VÉRIFICATION FONCTIONNELLE:
  # ────────────────────────────
  kubectl port-forward svc/taskmanager-api -n taskmanager-dev 5001:80 &
  curl http://localhost:5001/tasks/stats
  # -> {"total": 0, "by_priority": ...}   [OK]

  curl http://localhost:5001/health
  # -> {"status": "healthy", "database": "ok"}   [OK]

  # Créer des tâches de test et vérifier les stats:
  curl -X POST http://localhost:5001/tasks \
    -H 'Content-Type: application/json' \
    -d '{"title": "Test stats", "priority": "high"}'
  curl http://localhost:5001/tasks/stats
  # -> {"total": 1, "by_priority": {"high": 1, ...}, ...}   [OK]

────────────────────────────────────────────────────────────────────────────────
  PROMOTION VERS STAGING — Via Git (pas de clic dans Argo CD)
────────────────────────────────────────────────────────────────────────────────

  POURQUOI PASSER PAR GIT POUR PROMOUVOIR?
  ──────────────────────────────────────────
  Si Claire "synce staging manuellement depuis l'UI Argo CD", ce n'est pas GitOps.
  La promotion doit laisser une trace dans Git.
  Le dépôt gitops-config est la source de vérité.

  Pour promouvoir en staging: mettre à jour le tag dans overlays/staging/kustomization.yaml.

  ALICE CRÉE UN SCRIPT DE PROMOTION:
  ────────────────────────────────────
  # scripts/promote.sh dans taskmanager-gitops:

  cat > scripts/promote.sh << 'SCRIPT'
  #!/usr/bin/env bash
  # USAGE: ./scripts/promote.sh <IMAGE_TAG> <FROM_ENV> <TO_ENV>
  # EXEMPLE: ./scripts/promote.sh sha-a3f8c1d dev staging

  set -euo pipefail

  IMAGE_TAG="$1"
  FROM_ENV="$2"
  TO_ENV="$3"
  REGISTRY="ghcr.io/equipe/taskmanager-api"

  echo "[RAPIDE] Promotion de ${IMAGE_TAG} depuis ${FROM_ENV} vers ${TO_ENV}"

  # Vérifier qu'on est sur une branche propre:
  if [[ -n "$(git status --porcelain)" ]]; then
    echo "[X] Il y a des changements non commités. Annulation."
    exit 1
  fi

  # Mettre à jour le tag dans l'overlay cible:
  cd "apps/taskmanager/overlays/${TO_ENV}"
  kustomize edit set image "${REGISTRY}=${REGISTRY}:${IMAGE_TAG}"
  cd -

  # Vérifier le changement:
  git diff apps/taskmanager/overlays/${TO_ENV}/kustomization.yaml

  # Committer:
  git add apps/taskmanager/overlays/${TO_ENV}/kustomization.yaml
  git commit -m "chore(deploy): promote ${IMAGE_TAG} from ${FROM_ENV} to ${TO_ENV}

  Image: ${REGISTRY}:${IMAGE_TAG}
  From: ${FROM_ENV}
  To: ${TO_ENV}
  Promoted by: $(git config user.name)"

  git push origin main
  echo "[OK] Promotion effectuée! Argo CD va détecter le changement."
  SCRIPT

  chmod +x scripts/promote.sh

  # CLAIRE UTILISE LE SCRIPT:
  cd ~/taskmanager-gitops
  git pull origin main

  # Trouver le tag exact qui tourne en dev:
  argocd app get taskmanager-dev -o json | python3 -c "
  import json, sys
  app = json.load(sys.stdin)
  print(app['status']['sync']['revision'][:7])
  "
  # -> sha-a3f8c1d

  # Promouvoir vers staging:
  ./scripts/promote.sh sha-a3f8c1d dev staging

  CE QUE VOUS VOYEZ:
  ───────────────────
  [RAPIDE] Promotion de sha-a3f8c1d depuis dev vers staging
  diff --git a/apps/taskmanager/overlays/staging/kustomization.yaml b/...
  -      newTag: "sha-old"
  +      newTag: "sha-a3f8c1d"
  [OK] Promotion effectuée! Argo CD va détecter le changement.

  # Argo CD détecte le changement dans gitops-config (staging).
  # taskmanager-staging passe en "OutOfSync".
  # Sync automatique (si configuré) ou manuel:
  argocd app sync taskmanager-staging

  # Attendre que ça soit Healthy:
  argocd app wait taskmanager-staging --health --timeout 120

  # Vérifier:
  argocd app get taskmanager-staging
  # -> Status: Synced, Health: Healthy [OK]

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO 3 — ALICE: DÉPLOYER EN PRODUCTION (RELEASE v1.1.0)
════════════════════════════════════════════════════════════════════════════════

  CONTEXTE:
  ──────────
  La feature #18 est validée en staging. C'est l'heure de la release v1.1.0.
  Alice doit:
  1. Créer le tag Git de release
  2. Attendre que le CI/CD construise l'image et mette à jour gitops-config
  3. Faire la revue de dernière minute
  4. Déclencher manuellement le déploiement en production via Argo CD
  5. Surveiller le déploiement en temps réel

────────────────────────────────────────────────────────────────────────────────
  ALICE — Création du tag de release
────────────────────────────────────────────────────────────────────────────────

  # Dans le dépôt taskmanager (code source):
  cd ~/taskmanager
  git checkout main
  git pull origin main

  # S'assurer que tout est à jour:
  git log --oneline -5
  # -> q8r9s0t chore(release): prepare v1.1.0
  # -> p7q8r9s feat(routes): add GET /tasks/stats (#8) — squash merge
  # -> ...

  # Créer le tag annoté:
  git tag -a v1.1.0 -m "Release v1.1.0 — Statistiques des tâches

  Nouvelles fonctionnalités:
  - GET /tasks/stats: statistiques agrégées (total, par priorité, taux complétion)

  Infrastructure:
  - Kustomize overlays pour 3 environnements
  - Pipeline GitOps complet avec Argo CD
  - Rolling updates sans downtime"

  git push origin v1.1.0

  CE QUI SE PASSE AUTOMATIQUEMENT:
  ─────────────────────────────────
  # GitHub Actions CD se déclenche sur le tag v1.1.0:
  # 1. Build l'image: ghcr.io/equipe/taskmanager-api:1.1.0
  # 2. Met à jour staging/kustomization.yaml -> tag: "1.1.0"
  # 3. Met à jour prod/kustomization.yaml -> tag: "1.1.0"
  # 4. Commit dans taskmanager-gitops

  # Dans l'UI Argo CD:
  # -> taskmanager-staging: OutOfSync (mis à jour par le CI)
  # -> taskmanager-prod: OutOfSync (mis à jour par le CI)
  # -> taskmanager-staging sync automatique -> Synced [OK]
  # -> taskmanager-prod: OutOfSync et ATTEND (pas de sync auto!)

────────────────────────────────────────────────────────────────────────────────
  ALICE — Vérification finale avant déploiement prod
────────────────────────────────────────────────────────────────────────────────

  # DIFF AVANT DE SYNCER (TRÈS IMPORTANT):
  # Argo CD permet de voir exactement ce qui va changer AVANT de le faire.
  # C'est l'équivalent de "git diff" mais pour le cluster K8s.

  argocd app diff taskmanager-prod

  CE QUE VOUS VOYEZ:
  ───────────────────
  ===== apps/Deployment taskmanager-prod/taskmanager-api ======
  34c34
  <   image: ghcr.io/equipe/taskmanager-api:1.0.0
  ---
  >   image: ghcr.io/equipe/taskmanager-api:1.1.0

  # <- Le seul changement est le tag de l'image. Parfait.
  # Pas de changement inattendu de ConfigMap, Service, etc.

  # Vérifier que staging (avec la v1.1.0) est sain depuis au moins 30 minutes:
  argocd app get taskmanager-staging
  # -> Status: Synced
  # -> Health: Healthy
  # -> Last sync: 35 minutes ago

  # Exécuter les smoke tests sur staging:
  STAGING_URL="http://api-staging.taskmanager.local"
  curl "${STAGING_URL}/health" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d['status']=='healthy', f'FAIL: {d}'; print('[OK] /health OK')"
  curl "${STAGING_URL}/tasks/stats" | python3 -c "import sys,json; d=json.load(sys.stdin); assert 'total' in d, f'FAIL: {d}'; print('[OK] /tasks/stats OK')"
  # -> [OK] /health OK
  # -> [OK] /tasks/stats OK

────────────────────────────────────────────────────────────────────────────────
  ALICE — Déploiement en production avec surveillance
────────────────────────────────────────────────────────────────────────────────

  # DÉCLENCHER LE SYNC PROD (MANUELLEMENT):
  # Via CLI:
  argocd app sync taskmanager-prod \
    --prune \        # Autoriser la suppression des ressources obsolètes
    --timeout 300    # Timeout 5 minutes

  # Via l'UI Argo CD:
  # -> Cliquer sur "taskmanager-prod"
  # -> Bouton "SYNC" (en haut)
  # -> Vérifier les options:
  #   [x] PRUNE (supprimer les ressources supprimées de Git)
  #   [x] FORCE (si des ressources sont en conflit)
  #   [x] DRY RUN -> aperçu sans appliquer (optionnel, pour ultime vérification)
  # -> SYNCHRONIZE

  CE QUE VOUS VOYEZ DANS L'UI PENDANT LE DÉPLOIEMENT:
  ──────────────────────────────────────────────────────
  taskmanager-prod -> SYNCING... ⟳
  ├─ Deployment/taskmanager-api -> Progressing ⟳
  │   ├─ Pod/taskmanager-api-8e9g0d7c6-yn3no -> Running (ancienne version)
  │   ├─ Pod/taskmanager-api-9f0h1e8d7-zo4op -> ContainerCreating... (nouvelle)
  │   └─ Pod/taskmanager-api-9f0h1e8d7-zo4op -> Running [OK] (nouvelle, prête)
  │   -> Pod/taskmanager-api-8e9g0d7c6-yn3no -> Terminating (ancienne supprimée)
  │   <- Repeat pour les 2 autres réplicas...
  ├─ Service/taskmanager-api -> Synced [OK]
  └─ ConfigMap/taskmanager-config -> Synced [OK]

  # Dans la CLI, surveiller en temps réel:
  argocd app wait taskmanager-prod --health --timeout 300
  # Affiche les événements au fur et à mesure.

  CE QUE VOUS VOYEZ QUAND C'EST TERMINÉ:
  ────────────────────────────────────────
  TIMESTAMP     GROUP  KIND        NAMESPACE         NAME                 STATUS   HEALTH
  14:45:00      apps   Deployment  taskmanager-prod  taskmanager-api      Synced   Healthy
  HEALTH STATUS IS Healthy

  # Notification Slack automatique:
  # [OK] *taskmanager-prod* déployé avec succès!
  # Révision: 9f0h1e8d7
  # Application: taskmanager-prod | Environnement: prod

  # Vérification finale:
  PROD_URL="https://api.taskmanager.equipe.com"
  curl "${PROD_URL}/health"
  # -> {"status": "healthy", "database": "ok"}   [OK]

  # Créer la GitHub Release:
  gh release create v1.1.0 \
    --title "v1.1.0 — Statistiques des tâches [GRAPHIQUE]" \
    --notes "### Nouveautés
  - `GET /tasks/stats` — Statistiques agrégées des tâches
  ### Infrastructure
  - Pipeline GitOps complet avec Argo CD" \
    --target main

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO 4 — ALICE: ROLLBACK D'URGENCE EN PRODUCTION
════════════════════════════════════════════════════════════════════════════════

  CONTEXTE:
  ──────────
  Alerte Slack 14h52, 7 minutes après le déploiement de v1.1.0:
  "GET /tasks/stats retourne 500 Internal Server Error sur prod!"
  Alice doit faire un rollback immédiat.

  POURQUOI GITOPS REND LE ROLLBACK FACILE?
  ──────────────────────────────────────────
  Le rollback GitOps = revenir à la version précédente dans Git.
  L'historique Git est la source de vérité.
  Deux approches:

  APPROCHE A: Rollback direct dans Argo CD (le plus rapide):
  ──────────────────────────────────────────────────────────
  # Argo CD garde l'historique des syncs précédents.
  # On peut revenir à n'importe quel sync précédent EN UN CLIC.

  # Via CLI:
  argocd app history taskmanager-prod

  CE QUE VOUS VOYEZ:
  ───────────────────
  ID  DATE                    REVISION    INITIATOR
  3   2024-01-15 14:45:00     9f0h1e8d    alice (v1.1.0) <- version actuelle cassée
  2   2024-01-15 09:00:00     a3f8c1d     alice (v1.0.0) <- version stable
  1   2024-01-14 15:30:00     b7d2e4f     alice (initial)

  # Revenir à la version ID=2 (v1.0.0):
  argocd app rollback taskmanager-prod 2

  CE QUE VOUS VOYEZ:
  ───────────────────
  TIMESTAMP     KIND        NAME              STATUS
  14:54:30      Deployment  taskmanager-api   Progressing
  14:54:45      Deployment  taskmanager-api   Healthy
  Application 'taskmanager-prod' rolled back

  # 2 minutes de rollback! La prod est revenue à v1.0.0.

  APPROCHE B: Rollback via Git (approche GitOps pure, laisser une trace):
  ─────────────────────────────────────────────────────────────────────────
  # Plus lent mais laisse une trace claire dans l'historique Git.
  # Recommandé pour les rollbacks planifiés ou les analyses post-mortem.

  cd ~/taskmanager-gitops
  git log --oneline apps/taskmanager/overlays/prod/kustomization.yaml
  # -> 9f0h1e8 chore(deploy): update image tag to 1.1.0  <- cassé
  # -> a3f8c1d chore(deploy): update image tag to 1.0.0  <- stable

  # Annuler le dernier commit sur ce fichier:
  git revert 9f0h1e8 --no-edit
  # Ou plus précisément, juste changer le tag:
  cd apps/taskmanager/overlays/prod
  kustomize edit set image ghcr.io/equipe/taskmanager-api=ghcr.io/equipe/taskmanager-api:1.0.0
  git add kustomization.yaml
  git commit -m "fix(deploy): rollback prod to v1.0.0

  ROLLBACK D'URGENCE
  Raison: GET /tasks/stats retourne 500 sur prod (v1.1.0)
  Revenu à: v1.0.0 (connu stable)
  Incident: #42
  Décision: Alice Martin à 14:53"
  git push origin main

  # Argo CD détecte le changement -> synce prod automatiquement (si configuré)
  # Ou Alice synce manuellement.

  APRÈS LE ROLLBACK — INVESTIGATION:
  ────────────────────────────────────
  # Voir les logs de l'ancienne version pour comprendre le problème:
  kubectl logs -n taskmanager-prod \
    -l app=taskmanager,component=api \
    --previous \           # Logs des Pods qui ont planté
    --tail=100

  # L'analyse révèle: l'endpoint /tasks/stats utilise func.count()
  # qui requiert une version de SQLAlchemy >= 2.0 mais la prod a 1.4.x.
  # Solution: mettre à jour requirements.txt AVANT de re-déployer.

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO 5 — BOB: TRAVAILLER EN PARALLÈLE AVEC PLUSIEURS BRANCHES
════════════════════════════════════════════════════════════════════════════════

  CONTEXTE:
  ──────────
  Bob travaille sur feature/20-export-csv (export des tâches en CSV).
  Alice lui demande en urgence d'analyser le bug du rollback (#42).
  Bob doit voir les logs de la v1.1.0 en staging SANS changer de branche.

────────────────────────────────────────────────────────────────────────────────
  BOB — Analyser les logs d'un déploiement passé via Argo CD
────────────────────────────────────────────────────────────────────────────────

  # Via l'UI Argo CD:
  # -> taskmanager-staging -> Cliquer sur le Pod -> "Logs"
  # -> Filter par "error" ou "500"

  # Via CLI (plus puissant pour filtrer):
  # Voir les logs du déploiement staging avec la v1.1.0:
  argocd app logs taskmanager-staging \
    --container api \
    --tail 200 \
    --grep "500\|ERROR\|Exception"

  # Via kubectl directement:
  kubectl logs -n taskmanager-staging \
    -l app=taskmanager \
    --since=1h \
    | grep -i "error\|500\|exception"

  # L'analyse révèle l'erreur:
  # sqlalchemy.exc.ArgumentError: Only string arguments are accepted for
  # Session.execute() in SQLAlchemy 1.4. Use text() explicitly.
  # CAUSE: func.count() sans text() ne fonctionne qu'avec SQLAlchemy 2.0+

  # BOB CRÉE LE FIX SUR SA BRANCHE FEATURE EN COURS:
  # (Pas besoin de changer de branche — le fix est dans routes.py)
  git stash save "WIP: export CSV en cours"
  git checkout -b fix/42-sqlalchemy-func-count-compat
  # Corriger le bug:
  # Changer: func.count(Task.id) -> db.session.execute(text('SELECT COUNT(*)...'))
  # Ou plus proprement: utiliser l'API SQLAlchemy 1.4 compatible
  git add app/routes.py tests/test_routes.py
  git commit -m "fix(routes): make func.count compatible with SQLAlchemy 1.4

  Replace db.session.query(Task.priority, func.count()).group_by()
  with a properly typed select() statement compatible with both 1.4 and 2.0.
  Regression test added for SQLAlchemy version compatibility.
  Fixes #42"
  git push -u origin fix/42-sqlalchemy-func-count-compat
  # PR -> review rapide -> merge -> déploiement v1.1.1

  # Reprendre la feature export CSV:
  git checkout feature/20-export-csv
  git stash pop

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO 6 — CLAIRE: UTILISER LE "DRY RUN" ET LES PREVIEWS ARGO CD
════════════════════════════════════════════════════════════════════════════════

  CONTEXTE:
  ──────────
  Claire doit déployer une configuration Ingress modifiée sur staging.
  Elle veut VOIR ce qui va changer AVANT d'appliquer pour être sûre.

────────────────────────────────────────────────────────────────────────────────
  CLAIRE — Utiliser argocd app diff et le dry-run
────────────────────────────────────────────────────────────────────────────────

  # VOIR LE DIFF COMPLET ENTRE GIT ET LE CLUSTER:
  argocd app diff taskmanager-staging

  CE QUE VOUS VOYEZ:
  ───────────────────
  ===== /Namespace  taskmanager-staging ======
  (No difference)

  ===== networking.k8s.io/Ingress  taskmanager-staging/taskmanager-ingress ======
  8c8
  <       nginx.ingress.kubernetes.io/limit-rps: "100"
  ---
  >       nginx.ingress.kubernetes.io/limit-rps: "50"

  # <- Claire voit clairement que SEULEMENT la limite de rate limiting change.
  # Elle peut approuver en confiance.

  # DRY-RUN VIA CLI (simule le sync sans l'appliquer):
  argocd app sync taskmanager-staging --dry-run

  CE QUE VOUS VOYEZ:
  ───────────────────
  Name:               taskmanager-staging
  Project:            taskmanager
  ...
  Message:            Dry run complete. No changes applied.
  Phase:              Succeeded (DRY RUN)

  # Toutes les ressources qui SERAIENT modifiées sont listées.
  # Zéro changement appliqué au cluster.

  # Si tout est OK -> sync réel:
  argocd app sync taskmanager-staging

  # CLAIRE UTILISE AUSSI le "HARD REFRESH":
  # Parfois Argo CD a un cache de l'état Git. Hard refresh force une relecture.
  argocd app get taskmanager-staging --hard-refresh

════════════════════════════════════════════════════════════════════════════════
SCÉNARIO 7 — ALICE: CONFIGURER LE PATTERN "APP OF APPS"
════════════════════════════════════════════════════════════════════════════════

  QU'EST-CE QUE L'APP OF APPS?
  ──────────────────────────────
  Problème: on a 3 Applications Argo CD (dev, staging, prod) créées à la main.
  Si on ajoute un 4ème environnement (qa), il faut l'ajouter manuellement dans Argo CD.

  Solution: créer une Application "root" qui gère TOUTES les autres Applications.
  Cette Application "root" lit le dossier argocd-apps/ et crée/gère les Applications.
  -> Si on ajoute taskmanager-qa.yaml dans argocd-apps/, l'Application qa est créée auto!
  -> Argo CD SE GÈRE LUI-MÊME via Git. C'est du GitOps jusqu'au bout.

  ── argocd-apps/root-application.yaml ──────────────────────────────────────

  apiVersion: argoproj.io/v1alpha1
  kind: Application
  metadata:
    name: taskmanager-root
    namespace: argocd
    finalizers:
      - resources-finalizer.argocd.argoproj.io
    annotations:
      argocd.argoproj.io/description: |
        Application racine — Gère toutes les Applications TaskManager.
        Pattern: App of Apps.
        Modifier ce fichier = créer/modifier/supprimer des Applications.
  spec:
    project: taskmanager

    source:
      repoURL: https://github.com/equipe/taskmanager-gitops
      targetRevision: HEAD
      path: argocd-apps   # <- Ce dossier contient les autres Applications!

    destination:
      server: https://kubernetes.default.svc
      namespace: argocd

    syncPolicy:
      automated:
        prune: true      # Si on supprime un fichier de argocd-apps/ -> Application supprimée
        selfHeal: true
      syncOptions:
        - CreateNamespace=true

  # Appliquer l'Application root:
  kubectl apply -f argocd-apps/root-application.yaml -n argocd

  # Argo CD lira argocd-apps/ et créera les Applications dev/staging/prod automatiquement!
  # Plus besoin de kubectl apply pour chaque Application.

  CE QUE VOUS VOYEZ DANS L'UI:
  ──────────────────────────────
  taskmanager-root (Synced / Healthy)
  ├─ Application/taskmanager-dev (Synced / Healthy)
  ├─ Application/taskmanager-staging (Synced / Healthy)
  └─ Application/taskmanager-prod (OutOfSync)
  └─ AppProject/taskmanager (Synced)

================================================================================
PARTIE 6 — FONCTIONNALITÉS AVANCÉES ARGO CD
================================================================================

════════════════════════════════════════════════════════════════════════════════
ÉTAPE 6.1 — APPLICATIONSET: DÉPLOYER SUR PLUSIEURS CLUSTERS D'UN COUP
════════════════════════════════════════════════════════════════════════════════

  QU'EST-CE QU'UN APPLICATIONSET?
  ─────────────────────────────────
  ApplicationSet = générateur d'Applications Argo CD.
  Au lieu de créer 3 fichiers YAML (dev/staging/prod), on en crée UN qui génère les 3.
  C'est DRY (Don't Repeat Yourself) pour les Applications Argo CD.

  UTILITÉ:
  -> Déployer sur 50 clusters différents avec une seule définition.
  -> Créer une Application par branche Git automatiquement (Preview Environments).
  -> Créer une Application par dossier dans un dépôt Git.

  ── argocd-apps/taskmanager-appset.yaml ────────────────────────────────────

  apiVersion: argoproj.io/v1alpha1
  kind: ApplicationSet
  metadata:
    name: taskmanager-environments
    namespace: argocd
  spec:
    # GÉNÉRATEUR: comment générer la liste des Applications:
    generators:
      - list:
          elements:
            - env: dev
              namespace: taskmanager-dev
              revision: HEAD
              autoSync: "true"
              host: api-dev.taskmanager.local
              replicas: "1"

            - env: staging
              namespace: taskmanager-staging
              revision: HEAD
              autoSync: "true"
              host: api-staging.taskmanager.local
              replicas: "2"

            - env: prod
              namespace: taskmanager-prod
              revision: main
              autoSync: "false"   # Pas de sync auto en prod!
              host: api.taskmanager.equipe.com
              replicas: "3"

    # TEMPLATE: le modèle d'Application à créer pour chaque élément:
    template:
      metadata:
        name: 'taskmanager-{{env}}'
        namespace: argocd
        labels:
          environment: '{{env}}'
          app: taskmanager
      spec:
        project: taskmanager
        source:
          repoURL: https://github.com/equipe/taskmanager-gitops
          targetRevision: '{{revision}}'
          path: 'apps/taskmanager/overlays/{{env}}'
        destination:
          server: https://kubernetes.default.svc
          namespace: '{{namespace}}'
        syncPolicy:
          automated:
            prune: true
            selfHeal: '{{autoSync}}'
          syncOptions:
            - CreateNamespace=true

════════════════════════════════════════════════════════════════════════════════
ÉTAPE 6.2 — PREVIEW ENVIRONMENTS: UNE APP PAR PULL REQUEST
════════════════════════════════════════════════════════════════════════════════

  QU'EST-CE QU'UN PREVIEW ENVIRONMENT?
  ──────────────────────────────────────
  IDÉE: Chaque Pull Request crée automatiquement un environnement de test dédié.
  -> Bob ouvre la PR #18 -> Argo CD crée automatiquement:
    - Namespace: pr-18
    - Application: taskmanager-pr-18
    - URL: http://api-pr-18.taskmanager.local
  -> Bob (et les reviewers) peuvent tester directement la PR dans K8s.
  -> Quand la PR est fermée -> l'environnement est automatiquement supprimé.

  COMMENT ÇA MARCHE?
  ────────────────────
  1. GitHub Actions crée une branche pr-18 dans taskmanager-gitops avec
     l'overlay spécifique à cette PR.
  2. L'ApplicationSet utilise le générateur "pullRequest" de GitHub.
  3. Argo CD crée l'Application automatiquement.
  4. GitHub Actions commente la PR avec l'URL de preview.

  ── argocd-apps/taskmanager-preview-appset.yaml ────────────────────────────

  apiVersion: argoproj.io/v1alpha1
  kind: ApplicationSet
  metadata:
    name: taskmanager-previews
    namespace: argocd
  spec:
    generators:
      - pullRequest:
          github:
            owner: equipe
            repo: taskmanager
            # Token pour accéder aux PRs GitHub:
            tokenRef:
              secretName: github-token
              key: token
            # Seulement les PRs avec ce label:
            labels:
              - preview
          requeueAfterSeconds: 60   # Vérifier les nouvelles PRs toutes les 60s

    template:
      metadata:
        name: 'taskmanager-pr-{{number}}'
        namespace: argocd
      spec:
        project: taskmanager
        source:
          repoURL: https://github.com/equipe/taskmanager-gitops
          targetRevision: HEAD
          path: apps/taskmanager/overlays/dev   # Base sur dev
          kustomize:
            images:
              # Utiliser l'image buildée pour cette PR:
              - 'ghcr.io/equipe/taskmanager-api=ghcr.io/equipe/taskmanager-api:sha-{{headSHA}}'
            namePrefix: 'pr-{{number}}-'   # Préfixer les ressources pour éviter les conflits
        destination:
          server: https://kubernetes.default.svc
          namespace: 'taskmanager-pr-{{number}}'
        syncPolicy:
          automated:
            prune: true
            selfHeal: true
          syncOptions:
            - CreateNamespace=true
        info:
          - name: 'PR URL'
            value: '{{htmlURL}}'
          - name: 'Preview URL'
            value: 'http://api-pr-{{number}}.taskmanager.local'

════════════════════════════════════════════════════════════════════════════════
ÉTAPE 6.3 — SEALED SECRETS: CHIFFRER LES SECRETS DANS GIT
════════════════════════════════════════════════════════════════════════════════

  PROBLÈME:
  ──────────
  Les Secrets Kubernetes sont juste en base64 (pas chiffrés).
  On ne peut pas les committer dans Git tel quel (mot de passe visible).
  Solution jusqu'ici: des placeholders. Mais c'est manuel et risqué.

  SEALED SECRETS:
  ────────────────
  Sealed Secrets = outil qui chiffre les Secrets K8s avec une clé publique.
  Seul le cluster peut déchiffrer (avec la clé privée correspondante).
  Vous pouvez committer les "SealedSecrets" dans Git en toute sécurité.
  Même si quelqu'un lit le fichier: il ne voit que du chiffrement RSA.

  ALICE INSTALLE SEALED SECRETS:
  ────────────────────────────────

  # Installer le controller dans le cluster:
  helm repo add sealed-secrets https://bitnami-labs.github.io/sealed-secrets
  helm install sealed-secrets-controller \
    sealed-secrets/sealed-secrets \
    --namespace kube-system \
    --version 2.15.0

  # Installer kubeseal (outil CLI pour chiffrer):
  brew install kubeseal   # Mac
  # Ubuntu: télécharger depuis les releases GitHub

  # Vérifier:
  kubeseal --version
  # -> kubeseal version: 0.26.0

  # CHIFFRER UN SECRET:
  # Créer d'abord le Secret "normal" en YAML (ne pas l'appliquer!):
  cat > /tmp/taskmanager-secret.yaml << 'EOF'
  apiVersion: v1
  kind: Secret
  metadata:
    name: taskmanager-secrets
    namespace: taskmanager-prod
  type: Opaque
  stringData:
    SECRET_KEY: "ma-cle-secrete-tres-longue-pour-flask"
    POSTGRES_PASSWORD: "mon-vrai-mot-de-passe-postgresql"
    DATABASE_URL: "postgresql://taskmanager:mon-vrai-mot-de-passe@taskmanager-postgres:5432/taskmanager"
  EOF

  # Chiffrer avec kubeseal:
  kubeseal \
    --format yaml \
    --namespace taskmanager-prod \
    < /tmp/taskmanager-secret.yaml \
    > apps/taskmanager/overlays/prod/sealed-secret.yaml

  # Le fichier sealed-secret.yaml contient:
  cat apps/taskmanager/overlays/prod/sealed-secret.yaml

  CE QUE VOUS VOYEZ:
  ───────────────────
  apiVersion: bitnami.com/v1alpha1
  kind: SealedSecret
  metadata:
    name: taskmanager-secrets
    namespace: taskmanager-prod
  spec:
    encryptedData:
      SECRET_KEY: AgBy3i4...CHIFFRÉ RSA...6Xm9
      POSTGRES_PASSWORD: AgB2...CHIFFRÉ RSA...8Yz7
      DATABASE_URL: AgBx...CHIFFRÉ RSA...3Pq1
    template:
      metadata:
        name: taskmanager-secrets
        namespace: taskmanager-prod
      type: Opaque

  # [OK] On peut committer ce fichier dans Git! Personne ne peut déchiffrer
  # sans accès au cluster.

  # Ajouter dans kustomization.yaml de prod:
  resources:
    - ../../base
    - sealed-secret.yaml   # <- Ajouter

  # Supprimer le secret "placeholder" de la base:
  # (Ou le garder pour dev/staging avec des valeurs de développement)

  # Committer:
  git add apps/taskmanager/overlays/prod/
  git commit -m "security: use Sealed Secrets for prod credentials

  Replace plaintext secret placeholders with RSA-encrypted SealedSecrets.
  These files are safe to commit — only the cluster can decrypt them.
  Rotated all credentials as part of this change."

  rm /tmp/taskmanager-secret.yaml   # Ne jamais laisser le fichier clair!

════════════════════════════════════════════════════════════════════════════════
ÉTAPE 6.4 — ARGO CD IMAGE UPDATER: MISE À JOUR AUTOMATIQUE DES IMAGES
════════════════════════════════════════════════════════════════════════════════

  QU'EST-CE QUE L'IMAGE UPDATER?
  ────────────────────────────────
  Jusqu'ici: le CI/CD modifie manuellement le tag dans gitops-config.
  Image Updater: surveille ghcr.io et met à jour automatiquement les tags
  dans gitops-config quand de nouvelles images sont disponibles.

  UTILITÉ:
  -> Dev: "utilise toujours la dernière image de develop"
  -> Staging: "utilise la dernière version mineure de 1.x"
  -> Prod: "uniquement les versions stables taguées"

  INSTALLER IMAGE UPDATER:
  ─────────────────────────
  kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj-labs/argocd-image-updater/stable/manifests/install.yaml

  # Configurer les accès à ghcr.io:
  kubectl create secret docker-registry ghcr-credentials \
    --docker-server=ghcr.io \
    --docker-username=alice-dev \
    --docker-password=ghp_votre_token_github \
    -n argocd

  # Configurer l'Application dev pour l'Image Updater:
  # Ajouter des annotations dans argocd-apps/taskmanager-dev.yaml:

  annotations:
    # Activer Image Updater pour cette Application:
    argocd-image-updater.argoproj.io/image-list: api=ghcr.io/equipe/taskmanager-api

    # Stratégie de mise à jour: latest (prend le tag le plus récent):
    argocd-image-updater.argoproj.io/api.update-strategy: latest

    # Méthode de mise à jour: kustomize (met à jour kustomization.yaml):
    argocd-image-updater.argoproj.io/write-back-method: git
    argocd-image-updater.argoproj.io/git-repository: git@github.com:equipe/taskmanager-gitops.git
    argocd-image-updater.argoproj.io/git-branch: main

    # Filtrer par tag (seulement les tags qui commencent par "sha-"):
    argocd-image-updater.argoproj.io/api.allow-tags: regexp:^sha-[0-9a-f]{7}$

════════════════════════════════════════════════════════════════════════════════
ÉTAPE 6.5 — CONFIGURER LES WEBHOOKS GITHUB -> ARGO CD
════════════════════════════════════════════════════════════════════════════════

  POURQUOI LES WEBHOOKS?
  ───────────────────────
  Par défaut, Argo CD vérifie les dépôts Git toutes les 3 minutes.
  Webhook = GitHub envoie une notification À Argo CD dès qu'un push a lieu.
  Résultat: sync déclenché en quelques secondes au lieu de 3 minutes.

  ALICE CONFIGURE LE WEBHOOK:
  ────────────────────────────

  # Sur GitHub — dépôt taskmanager-gitops:
  # Settings -> Webhooks -> Add webhook
  #
  # Payload URL: https://argocd.votre-domaine.com/api/webhook
  # Content type: application/json
  # Secret: un secret partagé (à mettre dans argocd-secret)
  # Events: [x] Just the push event
  # Active: [x]
  # -> Add webhook

  # Configurer Argo CD pour accepter le webhook:
  kubectl edit secret argocd-secret -n argocd
  # Ajouter:
  # data:
  #   webhook.github.secret: BASE64_DU_SECRET_WEBHOOK

  # Tester le webhook:
  # Faire un push dans taskmanager-gitops -> dans les 5 secondes:
  # -> taskmanager-dev passe en "OutOfSync" puis synce immédiatement.

════════════════════════════════════════════════════════════════════════════════
ÉTAPE 6.6 — ARGO CD SYNC HOOKS: ACTIONS PENDANT LE DÉPLOIEMENT
════════════════════════════════════════════════════════════════════════════════

  QU'EST-CE QUE LES SYNC HOOKS?
  ───────────────────────────────
  Des "hooks" = des Jobs Kubernetes qui s'exécutent à des moments précis:
  -> PreSync: AVANT que Argo CD applique les manifestes
  -> Sync: pendant la synchronisation
  -> PostSync: APRÈS que tous les manifestes sont synchronisés
  -> SyncFail: si le sync échoue

  UTILITÉ CONCRÈTE:
  ──────────────────
  PreSync:  Exécuter les migrations de base de données (alembic upgrade head)
  PostSync: Envoyer une notification Slack personnalisée
  PostSync: Lancer des tests de smoke après déploiement
  SyncFail: Alerter en urgence, créer un ticket automatiquement

  ── apps/taskmanager/base/db-migration-hook.yaml ───────────────────────────

  # Hook PreSync: migration de base de données avant le déploiement:
  apiVersion: batch/v1
  kind: Job
  metadata:
    name: taskmanager-db-migration
    annotations:
      # Dire à Argo CD que c'est un hook PreSync:
      argocd.argoproj.io/hook: PreSync
      # Quand supprimer ce Job: quand la sync réussit:
      argocd.argoproj.io/hook-delete-policy: HookSucceeded
  spec:
    backoffLimit: 3   # Maximum 3 tentatives si le Job échoue
    template:
      spec:
        restartPolicy: OnFailure
        initContainers:
          # Attendre que la base de données soit accessible:
          - name: wait-for-postgres
            image: busybox:1.36
            command: ['sh', '-c',
              'until nc -z taskmanager-postgres 5432; do
                echo "Attente de PostgreSQL..."; sleep 2;
              done; echo "PostgreSQL accessible!"']
        containers:
          - name: db-migrate
            image: ghcr.io/equipe/taskmanager-api:1.0.0
            command: ['flask', 'db', 'upgrade']
            envFrom:
              - configMapRef:
                  name: taskmanager-config
              - secretRef:
                  name: taskmanager-secrets
            resources:
              limits:
                memory: "256Mi"
                cpu: "200m"

  ── apps/taskmanager/base/smoke-test-hook.yaml ─────────────────────────────

  # Hook PostSync: tests de smoke après déploiement:
  apiVersion: batch/v1
  kind: Job
  metadata:
    name: taskmanager-smoke-test
    annotations:
      argocd.argoproj.io/hook: PostSync
      argocd.argoproj.io/hook-delete-policy: HookSucceeded
  spec:
    backoffLimit: 2
    template:
      spec:
        restartPolicy: OnFailure
        containers:
          - name: smoke-test
            image: curlimages/curl:8.5.0
            command:
              - sh
              - -c
              - |
                set -e
                echo "=== Tests de smoke post-déploiement ==="
                BASE_URL="http://taskmanager-api"

                echo "Test 1: GET /health"
                HEALTH=$(curl -sf "${BASE_URL}/health" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','FAIL'))")
                if [ "$HEALTH" != "healthy" ]; then
                  echo "[X] FAIL: /health retourne '${HEALTH}'"
                  exit 1
                fi
                echo "[OK] /health = healthy"

                echo "Test 2: GET /tasks (liste vide)"
                TASKS=$(curl -sf "${BASE_URL}/tasks" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('total','ERROR'))")
                echo "[OK] /tasks = ${TASKS} tâches"

                echo "Test 3: GET /tasks/stats"
                STATS=$(curl -sf "${BASE_URL}/tasks/stats" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('total','ERROR'))")
                echo "[OK] /tasks/stats = ${STATS} tâches"

                echo "=== Tous les smoke tests passés! ==="

================================================================================
PARTIE 7 — DÉPLOIEMENT SUR UN VRAI CLUSTER EN PRODUCTION
================================================================================

  JUSQU'ICI: cluster local k3d pour apprendre.
  EN PRODUCTION: un vrai cluster managé dans le cloud.
  Cette partie explique les différences et les étapes supplémentaires.

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 7.1 — CHOIX DU CLUSTER MANAGÉ
────────────────────────────────────────────────────────────────────────────────

  OPTIONS PRINCIPALES:
  ─────────────────────

  AWS EKS (Elastic Kubernetes Service):
  -> Le plus utilisé en entreprise.
  -> Intégration native avec IAM, ALB, EBS, ECR.
  -> Tarif: ~$0.10/heure pour le control plane + nœuds EC2.
  -> CLI: eksctl (simplifie la création/gestion).

  GCP GKE (Google Kubernetes Engine):
  -> Le meilleur en termes d'expérience Kubernetes (Google a créé K8s).
  -> Autopilot mode: gestion automatique des nœuds (serverless).
  -> Tarif: ~$0.10/heure control plane + nœuds GCE.

  Azure AKS (Azure Kubernetes Service):
  -> Préféré si l'équipe est dans l'écosystème Microsoft.
  -> Intégration native Azure DevOps, Active Directory.
  -> Tarif: control plane GRATUIT + nœuds VM Azure.

  Scaleway Kapsule (pour les équipes françaises):
  -> Hébergement en France (conformité RGPD simplifiée).
  -> Tarif compétitif, interface simple.

  CRÉER UN CLUSTER EKS (exemple):
  ─────────────────────────────────
  # Installer eksctl:
  brew install eksctl

  # Créer le cluster (remplacer les valeurs):
  eksctl create cluster \
    --name taskmanager-prod \
    --region eu-west-1 \
    --nodegroup-name workers \
    --node-type t3.medium \
    --nodes 3 \
    --nodes-min 2 \
    --nodes-max 5 \
    --managed \
    --with-oidc \
    --ssh-access

  # Attendre 15-20 minutes -> cluster créé!
  kubectl get nodes
  # -> 3 nœuds Ready

────────────────────────────────────────────────────────────────────────────────
ÉTAPE 7.2 — INSTALLER ARGO CD SUR LE CLUSTER DE PRODUCTION
────────────────────────────────────────────────────────────────────────────────

  # En production: Argo CD est exposé via un domaine avec TLS.
  # Installer cert-manager pour les certificats Let's Encrypt:
  kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.0/cert-manager.yaml

  # Créer un ClusterIssuer pour Let's Encrypt:
  cat > /tmp/letsencrypt-issuer.yaml << 'EOF'
  apiVersion: cert-manager.io/v1
  kind: ClusterIssuer
  metadata:
    name: letsencrypt-prod
  spec:
    acme:
      server: https://acme-v02.api.letsencrypt.org/directory
      email: alice@equipe.com
      privateKeySecretRef:
        name: letsencrypt-prod-key
      solvers:
        - http01:
            ingress:
              class: nginx
  EOF
  kubectl apply -f /tmp/letsencrypt-issuer.yaml

  # Installer Argo CD avec Helm (pour la production):
  helm repo add argo https://argoproj.github.io/argo-helm
  helm install argocd argo/argo-cd \
    --namespace argocd \
    --create-namespace \
    --set server.ingress.enabled=true \
    --set server.ingress.hostname=argocd.taskmanager.equipe.com \
    --set server.ingress.annotations."cert-manager\.io/cluster-issuer"=letsencrypt-prod \
    --set server.ingress.tls=true \
    --set configs.params."server\.insecure"=true \
    --version 6.7.0

  # Argo CD sera accessible sur https://argocd.taskmanager.equipe.com
  # avec un certificat Let's Encrypt valide.

================================================================================
PARTIE 8 — MONITORING ET OBSERVABILITÉ AVEC ARGO CD
================================================================================

════════════════════════════════════════════════════════════════════════════════
ÉTAPE 8.1 — INTÉGRER PROMETHEUS ET GRAFANA
════════════════════════════════════════════════════════════════════════════════

  ARGO CD EXPOSE DES MÉTRIQUES PROMETHEUS:
  ─────────────────────────────────────────
  Argo CD expose nativement des métriques au format Prometheus sur:
  - argocd-server:8083/metrics
  - argocd-application-controller:8082/metrics
  - argocd-repo-server:8084/metrics

  MÉTRIQUES UTILES:
  ──────────────────
  argocd_app_sync_total            -> Nombre de syncs (par statut: success/failed)
  argocd_app_health_status         -> Statut de santé de chaque Application
  argocd_app_sync_status           -> Statut de sync de chaque Application
  argocd_cluster_api_reachable     -> Le cluster est-il accessible?
  argocd_notifications_deliveries_total -> Notifications envoyées

  INSTALLER KUBE-PROMETHEUS-STACK:
  ──────────────────────────────────
  helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
  helm install prometheus prometheus-community/kube-prometheus-stack \
    --namespace monitoring \
    --create-namespace \
    --set grafana.adminPassword=AdminGrafana2024 \
    --version 57.0.0

  # Ajouter le ServiceMonitor pour Argo CD:
  cat > /tmp/argocd-servicemonitor.yaml << 'EOF'
  apiVersion: monitoring.coreos.com/v1
  kind: ServiceMonitor
  metadata:
    name: argocd-metrics
    namespace: monitoring
  spec:
    selector:
      matchLabels:
        app.kubernetes.io/name: argocd-server
    namespaceSelector:
      matchNames:
        - argocd
    endpoints:
      - port: metrics
        interval: 30s
  EOF
  kubectl apply -f /tmp/argocd-servicemonitor.yaml

  # ALERTES IMPORTANTES À CONFIGURER DANS ALERTMANAGER:
  # ────────────────────────────────────────────────────

  # Alerte 1: Application OutOfSync depuis plus de 15 minutes:
  cat > /tmp/argocd-alerts.yaml << 'EOF'
  apiVersion: monitoring.coreos.com/v1
  kind: PrometheusRule
  metadata:
    name: argocd-alerts
    namespace: monitoring
  spec:
    groups:
      - name: argocd
        rules:
          - alert: ArgoCDAppOutOfSync
            expr: |
              argocd_app_sync_status{sync_status="OutOfSync"} == 1
            for: 15m
            labels:
              severity: warning
            annotations:
              summary: "Application {{ $labels.name }} OutOfSync depuis 15min"
              description: "Vérifier le dépôt gitops-config et déclencher un sync."

          - alert: ArgoCDAppNotHealthy
            expr: |
              argocd_app_health_status{health_status!="Healthy",health_status!="Progressing"} == 1
            for: 5m
            labels:
              severity: critical
            annotations:
              summary: "Application {{ $labels.name }} non healthy"
              description: "Statut: {{ $labels.health_status }}. Vérifier les Pods."

          - alert: ArgoCDSyncFailed
            expr: |
              increase(argocd_app_sync_total{phase="Failed"}[1h]) > 2
            labels:
              severity: critical
            annotations:
              summary: "Plus de 2 syncs échoués en 1h pour {{ $labels.name }}"
  EOF
  kubectl apply -f /tmp/argocd-alerts.yaml

================================================================================
PARTIE 9 — CHEATSHEET COMPLET ARGO CD & GITOPS
================================================================================

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
INSTALLATION ET CONNEXION
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  # Installer Argo CD dans le cluster:
  kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/install.yaml

  # Accéder à l'UI en local:
  kubectl port-forward svc/argocd-server -n argocd 8080:443

  # Récupérer le mot de passe initial:
  argocd admin initial-password -n argocd

  # Se connecter:
  argocd login localhost:8080 --username admin --password <MOT_DE_PASSE> --insecure

  # Changer le mot de passe:
  argocd account update-password

  # Voir le contexte actuel:
  argocd context

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GESTION DES DÉPÔTS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  # Ajouter un dépôt (SSH):
  argocd repo add git@github.com:org/repo.git --ssh-private-key-path ~/.ssh/key

  # Ajouter un dépôt (HTTPS avec token):
  argocd repo add https://github.com/org/repo --username git --password <TOKEN>

  # Lister les dépôts:
  argocd repo list

  # Supprimer un dépôt:
  argocd repo rm https://github.com/org/repo

  # Tester la connexion à un dépôt:
  argocd repo get https://github.com/org/repo

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GESTION DES APPLICATIONS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  # Lister toutes les applications:
  argocd app list

  # Voir le détail d'une application:
  argocd app get mon-app

  # Voir le diff (Git vs Cluster):
  argocd app diff mon-app

  # Voir l'historique des syncs:
  argocd app history mon-app

  # Créer une application (via CLI):
  argocd app create mon-app \
    --repo https://github.com/org/gitops-config \
    --path apps/mon-app/overlays/prod \
    --dest-server https://kubernetes.default.svc \
    --dest-namespace mon-app-prod \
    --project mon-projet \
    --sync-policy automated \
    --auto-prune \
    --self-heal

  # Déclencher un sync:
  argocd app sync mon-app

  # Sync avec prune (supprime les ressources obsolètes):
  argocd app sync mon-app --prune

  # Dry-run (simule sans appliquer):
  argocd app sync mon-app --dry-run

  # Attendre qu'une sync soit terminée:
  argocd app wait mon-app --sync --health --timeout 300

  # Forcer un hard refresh (vider le cache):
  argocd app get mon-app --hard-refresh

  # Rollback à une version précédente:
  argocd app rollback mon-app <ID>

  # Supprimer une application (sans supprimer les ressources K8s):
  argocd app delete mon-app --cascade=false

  # Supprimer une application ET ses ressources K8s:
  argocd app delete mon-app --cascade=true

  # Voir les logs d'un Pod via Argo CD:
  argocd app logs mon-app --container mon-container --follow

  # Voir les événements K8s via Argo CD:
  argocd app events mon-app

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GESTION DES PROJETS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  # Lister les projets:
  argocd proj list

  # Voir le détail d'un projet:
  argocd proj get mon-projet

  # Créer un projet:
  argocd proj create mon-projet \
    --description "Mon projet" \
    --src "https://github.com/org/gitops" \
    --dest "https://kubernetes.default.svc,mon-namespace"

  # Ajouter une source autorisée:
  argocd proj add-source mon-projet https://github.com/org/autre-repo

  # Ajouter une destination autorisée:
  argocd proj add-destination mon-projet https://kubernetes.default.svc mon-autre-namespace

  # Voir les politiques d'accès:
  argocd proj role list mon-projet

  # Créer un token pour un rôle (pour le CI/CD):
  argocd proj role create-token mon-projet mon-role --expires-in 30d

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GESTION DES CLUSTERS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  # Lister les clusters enregistrés:
  argocd cluster list

  # Ajouter un cluster externe:
  # (D'abord: kubectl context pointant vers ce cluster)
  argocd cluster add mon-contexte-kubectl

  # Voir les infos d'un cluster:
  argocd cluster get https://cluster-api-endpoint.example.com

  # Supprimer un cluster:
  argocd cluster rm https://cluster-api-endpoint.example.com

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
GESTION DES COMPTES ET ACCÈS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  # Lister les comptes:
  argocd account list

  # Voir les infos d'un compte:
  argocd account get --account bob

  # Changer le mot de passe d'un compte (en tant qu'admin):
  argocd account update-password --account bob --new-password nouveau-mdp

  # Générer un token API pour un compte:
  argocd account generate-token --account bob --expires-in 90d

  # Révoquer tous les tokens d'un compte:
  argocd account delete-token --account bob <TOKEN_ID>

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
COMMANDES KUBERNETES ESSENTIELLES POUR ARGO CD
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  # Voir toutes les ressources d'un namespace:
  kubectl get all -n taskmanager-prod

  # Voir les Applications Argo CD (objets K8s):
  kubectl get applications -n argocd

  # Voir les AppProjects:
  kubectl get appprojects -n argocd

  # Voir les SealedSecrets:
  kubectl get sealedsecrets -n taskmanager-prod

  # Voir les événements d'un namespace (utile pour débugger):
  kubectl get events -n taskmanager-prod --sort-by='.lastTimestamp'

  # Voir les logs d'un Deployment:
  kubectl logs -n taskmanager-prod deployment/taskmanager-api -f

  # Voir les logs de tous les Pods d'un Deployment:
  kubectl logs -n taskmanager-prod -l app=taskmanager --all-containers=true --follow

  # Describe un Pod (voir les erreurs):
  kubectl describe pod <NOM_POD> -n taskmanager-prod

  # Voir l'utilisation CPU/RAM des Pods:
  kubectl top pods -n taskmanager-prod

  # Exécuter une commande dans un Pod:
  kubectl exec -it <NOM_POD> -n taskmanager-prod -- bash

  # Port-forward vers un Service:
  kubectl port-forward svc/taskmanager-api -n taskmanager-prod 5001:80

  # Voir les ConfigMaps:
  kubectl get configmap taskmanager-config -n taskmanager-prod -o yaml

  # Voir un Secret (décodé):
  kubectl get secret taskmanager-secrets -n taskmanager-prod -o jsonpath='{.data.SECRET_KEY}' | base64 -d

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
KUSTOMIZE — COMMANDES ESSENTIELLES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  # Voir les manifestes générés (sans les appliquer):
  kustomize build apps/taskmanager/overlays/prod

  # Appliquer directement avec kubectl:
  kubectl apply -k apps/taskmanager/overlays/prod

  # Mettre à jour un tag d'image:
  cd apps/taskmanager/overlays/prod
  kustomize edit set image ghcr.io/equipe/taskmanager-api=ghcr.io/equipe/taskmanager-api:1.2.0

  # Voir le kustomization.yaml résultant:
  cat kustomization.yaml

  # Valider les manifestes:
  kustomize build apps/taskmanager/overlays/prod | kubectl apply --dry-run=client -f -

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
ERREURS FRÉQUENTES ET SOLUTIONS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ERREUR: Application toujours "OutOfSync" malgré un sync réussi
  ────────────────────────────────────────────────────────────────
  CAUSE:  K8s modifie certains champs automatiquement (réplicas par HPA,
          annotations par controllers). Ces modifications créent un diff.
  FIX:    Ajouter ces champs dans "ignoreDifferences" dans l'Application:
          ignoreDifferences:
            - group: apps
              kind: Deployment
              jsonPointers:
                - /spec/replicas

  ERREUR: "ComparisonError: failed to load live state"
  ──────────────────────────────────────────────────────
  CAUSE:  Argo CD n'arrive pas à lire l'état du cluster.
  FIX:    kubectl get pods -n argocd -> vérifier que argocd-application-controller tourne.
          Vérifier les permissions RBAC du service account Argo CD.

  ERREUR: "rpc error: code = Unavailable" lors du login
  ────────────────────────────────────────────────────────
  CAUSE:  argocd-server n'est pas accessible.
  FIX:    kubectl port-forward svc/argocd-server -n argocd 8080:443 -> vérifier
          que le port-forward est actif.
          En production: vérifier l'Ingress et le certificat TLS.

  ERREUR: Image pull error — "unauthorized: access to the requested resource is not authorized"
  ──────────────────────────────────────────────────────────────────────────────────────────────
  CAUSE:  Le cluster ne peut pas télécharger l'image depuis ghcr.io (dépôt privé).
  FIX:    Créer le Secret d'authentification ghcr.io dans le namespace:
          kubectl create secret docker-registry ghcr-credentials \
            --docker-server=ghcr.io \
            --docker-username=<USERNAME> \
            --docker-password=<TOKEN> \
            -n taskmanager-prod
          Référencer dans le Deployment:
          imagePullSecrets:
            - name: ghcr-credentials

  ERREUR: Pod en "CrashLoopBackOff"
  ────────────────────────────────────
  CAUSE:  Le container plante au démarrage.
  FIX:    kubectl describe pod <NOM_POD> -n <NAMESPACE>
          -> Lire la section "Events" pour la cause
          kubectl logs <NOM_POD> -n <NAMESPACE> --previous
          -> Logs du container avant le crash

  ERREUR: "Sync failed: one or more objects failed to apply"
  ────────────────────────────────────────────────────────────
  CAUSE:  Un manifeste YAML est invalide ou conflit avec une ressource existante.
  FIX:    argocd app sync mon-app --dry-run -> voir quelle ressource échoue
          Vérifier les logs: kubectl logs -n argocd deployment/argocd-application-controller

  ERREUR: Sealed Secret ne se déchiffre pas — "cannot get sealed secret key"
  ────────────────────────────────────────────────────────────────────────────
  CAUSE:  Le controller Sealed Secrets ne tourne pas, ou la clé de déchiffrement
          a été perdue (si le controller a été réinstallé sans sauvegarder la clé).
  FIX:    kubectl get pods -n kube-system | grep sealed
          Pour récupérer une clé perdue: la clé doit être sauvegardée au préalable!
          kubectl get secret -n kube-system -l sealedsecrets.bitnami.com/sealed-secrets-key -o yaml
          -> Sauvegarder ce YAML en lieu sûr avant de réinstaller.

  ERREUR: "auto-sync is prevented by the sync window"
  ────────────────────────────────────────────────────
  CAUSE:  Les SyncWindows bloquent la synchronisation (ex: week-end pour prod).
  FIX:    Vérifier les SyncWindows: argocd proj windows list mon-projet
          Pour un déploiement d'urgence hors fenêtre:
          argocd proj windows disable mon-projet   (temporaire)
          Ou utiliser un sync forcé: argocd app sync mon-app --force

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
FLUX DE TRAVAIL COMPLET (RÉSUMÉ VISUEL)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  DÉVELOPPEMENT D'UNE FEATURE:
  ──────────────────────────────

  BOB (développeur)            GITHUB ACTIONS              ARGO CD
  ─────────────────            ──────────────              ───────
  1. git checkout -b feature/X
  2. [code + tests]
  3. git push feature/X         -> CI: lint + tests [OK]
  4. PR -> review -> merge        -> CD: build image
  into develop                    push to ghcr.io
                                  update gitops-config
                                  (overlays/dev)
                                                           5. Detect change
                                                              Auto-sync dev
                                                              Rolling update
                                                              Smoke tests [OK]
  6. Tester en dev:
  argocd app get taskmanager-dev
  curl api-dev.taskmanager.local/tasks/stats [OK]

  PROMOTION VERS STAGING:
  ─────────────────────────
  CLAIRE/ALICE                 GITOPS-CONFIG               ARGO CD
  ─────────────                ─────────────               ───────
  1. ./scripts/promote.sh      -> Update overlays/staging   -> Detect change
     sha-abc123 dev staging       kustomization.yaml          Sync staging [OK]
  2. argocd app wait staging
     --health --timeout 120
  3. Tests staging [OK]

  RELEASE PRODUCTION:
  ────────────────────
  ALICE                        GITHUB ACTIONS              ARGO CD
  ─────                        ──────────────              ───────
  1. git tag v1.2.0             -> Build image:1.2.0
  2. git push origin v1.2.0     -> Update overlays/staging  -> Auto-sync staging
                                  Update overlays/prod     -> Prod: OutOfSync
  3. argocd app diff prod        (détecte le changement)
  4. Vérifier: rien d'inattendu
  5. argocd app sync prod       -> Deploy v1.2.0 in prod    -> Rolling update
                                                              Smoke tests [OK]
  6. Slack notif: [OK] Déployé!

  ROLLBACK D'URGENCE:
  ────────────────────
  ALICE
  ─────
  1. argocd app history prod     -> Voir les syncs précédents
  2. argocd app rollback prod 3  -> Revenir à l'état ID=3
  3. Attendre 2 minutes          -> Rolling update vers ancienne version
  4. Vérifier: curl prod/health  -> healthy [OK]
  5. Créer fix dans une branche
  6. PR -> merge -> CI/CD -> sync

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
LES 12 RÈGLES D'OR GITOPS + ARGO CD
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  1. Git est la SEULE source de vérité.
     -> Aucune modification directe du cluster sans passer par Git d'abord.

  2. Jamais kubectl apply manuellement sur prod.
     -> Toujours via Argo CD. Exception: urgence absolue, avec ticket d'incident.

  3. Un dépôt séparé pour les manifestes K8s.
     -> equipe/taskmanager-gitops ≠ equipe/taskmanager.
     -> Séparation des responsabilités code vs infrastructure.

  4. Sync automatique pour dev, sync manuel pour prod.
     -> Dev = vélocité. Prod = contrôle.

  5. Toujours vérifier argocd app diff avant de syncer en prod.
     -> Ne jamais déployer sans savoir exactement ce qui change.

  6. Jamais committer de vrais secrets en clair dans Git.
     -> Utiliser Sealed Secrets, External Secrets Operator ou Vault.

  7. Tags de version spécifiques, jamais :latest en staging/prod.
     -> :latest n'est pas reproductible. Impossible de rollback proprement.
     -> Dev peut utiliser :latest pour la rapidité.

  8. Sauvegarder la clé de déchiffrement Sealed Secrets.
     -> Si le controller est réinstallé sans la clé -> tous les secrets sont perdus.
     -> Stocker la clé dans un vault (1Password, HashiCorp Vault, AWS Secrets Manager).

  9. Configurer des SyncWindows pour bloquer les déploiements le week-end.
     -> Personne ne déploie en prod le vendredi soir ou le week-end.
     -> Sauf hotfix avec processus d'override explicite.

  10. Tester les manifestes Kustomize localement avant de pousser.
      -> kustomize build overlays/prod | kubectl apply --dry-run=client -f -
      -> Évite les erreurs de YAML découvertes en prod.

  11. Utiliser ignoreDifferences pour les champs gérés par K8s.
      -> HPA modifie /spec/replicas -> l'ignorer pour éviter les faux OutOfSync.

  12. Documenter chaque rollback avec un ticket d'incident.
      -> "Qui a rollbacké quoi, quand, pourquoi, et le fix final."
      -> L'historique Git du dépôt gitops-config EST cet audit log.

================================================================================
FIN DU GUIDE
================================================================================

Ce guide couvre l'intégralité du workflow GitOps + Argo CD pour une équipe
de 3 développeurs travaillant sur une application Flask déployée sur Kubernetes.

RÉSUMÉ DE CE QUI A ÉTÉ COUVERT:

  CONCEPTS FONDAMENTAUX:
  [OK] GitOps: les 4 principes fondamentaux
  [OK] Argo CD: composants, concepts (Application, Project, Sync, Health)
  [OK] Dépôt séparé gitops-config: pourquoi et comment
  [OK] Kubernetes: Pod, Deployment, Service, ConfigMap, Secret, Ingress, HPA, PDB
  [OK] Helm vs Kustomize: différences et cas d'usage

  INSTALLATION ET CONFIGURATION:
  [OK] Outils (kubectl, helm, k3d, argocd CLI) sur Mac/Ubuntu/Windows
  [OK] Cluster Kubernetes local avec k3d (3 nœuds)
  [OK] Namespaces séparés par environnement (dev/staging/prod)
  [OK] Nginx Ingress Controller
  [OK] Installation Argo CD dans Kubernetes
  [OK] Configuration des comptes utilisateurs (Alice/Bob/Claire)
  [OK] RBAC Argo CD (rôles viewer, developer, admin)
  [OK] Notifications Slack (deployed, health degraded)

  MANIFESTES KUBERNETES (tous complets, tous expliqués ligne par ligne):
  [OK] ConfigMap (configuration non sensible)
  [OK] Secret (données sensibles avec avertissement)
  [OK] Deployment (rolling update, probes, resources, anti-affinity)
  [OK] Service (ClusterIP)
  [OK] Ingress (nginx, rate limiting, headers sécurité)
  [OK] HPA (autoscaling CPU + mémoire avec politique de stabilisation)
  [OK] PodDisruptionBudget (haute disponibilité pendant maintenance)
  [OK] ServiceAccount (moindre privilège)
  [OK] Kustomize base + overlays dev/staging/prod

  APPLICATIONS ARGO CD (YAML complets):
  [OK] AppProject (sources autorisées, destinations, RBAC, SyncWindows)
  [OK] Application dev (sync automatique, selfHeal, retry)
  [OK] Application staging (semi-automatique, sans selfHeal)
  [OK] Application prod (sync manuel, validation, notifications)

  CI/CD PIPELINE COMPLET:
  [OK] GitHub Actions CD: build Docker -> push ghcr.io -> update gitops-config
  [OK] Script de promotion (dev -> staging -> prod)
  [OK] Webhook GitHub -> Argo CD (sync immédiat)
  [OK] argocd app wait (attendre la sync dans le pipeline)

  SCÉNARIOS COMPLETS PAR UTILISATEUR:
  [OK] Scénario 1 — Bob: feature complète E2E (code -> déploiement auto dev)
  [OK] Scénario 2 — Claire: vérification UI, promotion staging, diff/dry-run
  [OK] Scénario 3 — Alice: release prod avec vérifications et surveillance
  [OK] Scénario 4 — Alice: rollback d'urgence (2 approches: CLI et Git)
  [OK] Scénario 5 — Bob: analyse logs, branches parallèles, fix urgent
  [OK] Scénario 6 — Claire: dry-run, hard refresh, diff complet
  [OK] Scénario 7 — Alice: pattern "App of Apps"

  FONCTIONNALITÉS AVANCÉES:
  [OK] ApplicationSet (liste, multi-cluster)
  [OK] Preview Environments (une App par Pull Request)
  [OK] Sealed Secrets (chiffrement des secrets dans Git)
  [OK] Image Updater (mise à jour automatique des tags)
  [OK] Webhooks GitHub -> Argo CD
  [OK] Sync Hooks (PreSync migration DB, PostSync smoke tests)

  PRODUCTION:
  [OK] Cluster managé (EKS, GKE, AKS, Scaleway)
  [OK] Argo CD avec TLS et cert-manager (Let's Encrypt)

  MONITORING ET OBSERVABILITÉ:
  [OK] Métriques Prometheus d'Argo CD
  [OK] Alertes (OutOfSync, unhealthy, sync failed)
  [OK] kube-prometheus-stack + Grafana

  RÉFÉRENCE:
  [OK] Cheatsheet complet argocd CLI (75+ commandes)
  [OK] Commandes kubectl essentielles pour Argo CD
  [OK] Commandes Kustomize essentielles
  [OK] Erreurs fréquentes et solutions détaillées
  [OK] Flux de travail résumé (dev/staging/prod/rollback)
  [OK] 12 règles d'or GitOps + Argo CD

================================================================================