# Fichier: python_cheats/cheatsheets/aws.txt
# Cheatsheet AWS (Amazon Web Services) - Guide Complet pour Débutants


═══════════════════════════════════════════════════════════════════════════════
[OK] INTRODUCTION À AWS
═══════════════════════════════════════════════════════════════════════════════

AWS (Amazon Web Services) est la plateforme cloud la plus utilisée au monde.
Elle offre plus de 200 services pour héberger applications, stocker données,
gérer bases de données, machine learning, IoT, et bien plus.

# Concepts fondamentaux:
- Region: Zone géographique (ex: us-east-1, eu-west-1)
- Availability Zone (AZ): Data center dans une région (ex: us-east-1a)
- VPC: Réseau virtuel privé isolé
- IAM: Gestion des identités et accès
- Pay-as-you-go: Payer uniquement ce qu'on utilise

# Principaux services (ordre d'importance):
1. EC2 - Serveurs virtuels
2. S3 - Stockage d'objets
3. RDS - Bases de données relationnelles
4. Lambda - Fonctions serverless
5. IAM - Gestion des accès
6. VPC - Réseau virtuel
7. CloudWatch - Monitoring
8. Route 53 - DNS
9. ELB - Load balancing
10. CloudFront - CDN


═══════════════════════════════════════════════════════════════════════════════
[OK] CONFIGURATION INITIALE - AWS CLI
═══════════════════════════════════════════════════════════════════════════════

# === INSTALLATION AWS CLI ===

# Linux (x86_64)
curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# Linux (ARM)
curl "https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip" -o "awscliv2.zip"
unzip awscliv2.zip
sudo ./aws/install

# macOS
curl "https://awscli.amazonaws.com/AWSCLIV2.pkg" -o "AWSCLIV2.pkg"
sudo installer -pkg AWSCLIV2.pkg -target /

# macOS (Homebrew)
brew install awscli

# Windows (MSI installer)
# Télécharger: https://awscli.amazonaws.com/AWSCLIV2.msi
msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2.msi

# Windows (avec Chocolatey)
choco install awscli

# Vérifier installation
aws --version
# Output: aws-cli/2.x.x Python/3.x.x ...

La commande "aws configure" sert à initialiser et configurer l’AWS CLI pour que ton ordinateur puisse communiquer avec AWS.
Sans cette commande, aucune commande AWS ne fonctionne, car la CLI ne sait pas :
qui tu es
à quel compte AWS tu appartiens
dans quelle région tu veux travailler
où trouver tes identifiants
C’est comme configurer un compte avant d'utiliser une application.

[OBJECTIF] Pourquoi aws configure existe ?

Parce que la CLI a besoin de 4 informations essentielles pour fonctionner :
Access Key ID
Secret Access Key
Région par défaut
Format de sortie

Voici pourquoi chacune est indispensable v

[CLE] 1) Access Key ID

Identifiant public de ton utilisateur AWS.

Elle dit :
"Qui essaie d’accéder à AWS ?"

[VERROUILLE] 2) Secret Access Key

Mot de passe cryptographique secret permettant de signer chaque requête envoyée à AWS.

Elle répond :
"Prouve que tu es bien l’utilisateur associé à l’Access Key."

Sans elle -> impossible de s’authentifier -> toutes les commandes échouent.

[MONDE] 3) Région par défaut

AWS est divisé en régions (eu-west-1, us-east-1, etc.).

La CLI doit savoir où envoyer tes commandes.

Exemples :

créer une instance EC2
créer un bucket S3
déployer une Lambda
Si tu ne définis pas la région -> erreur :

You must specify a region.

[FICHIER] 4) Format de sortie

Comment les réponses AWS doivent être affichées :

json (par défaut)
yaml
text
table

Ce n’est pas obligatoire pour fonctionner, mais plus pratique.

[NOTE] Exemple concret

Si tu fais :
aws s3 ls

La CLI va :

lire Access Key / Secret Key dans ~/.aws/credentials
lire la région dans ~/.aws/config
signer la requête avec ta clé secrète
appeler l’API AWS
afficher la liste de tes buckets

# === CONFIGURATION INITIALE (détaillé) ===
1) aws configure — qu’est-ce que c’est ?

La commande aws configure (AWS CLI) lance une configuration interactive pour enregistrer les informations nécessaires à l’authentification et au fonctionnement de l’outil en ligne de commande :

$ aws configure
AWS Access Key ID [None]: AKIA...
AWS Secret Access Key [None]: wJalrXU...
Default region name [None]: eu-west-1
Default output format [None]: json


Elle écrit ces valeurs dans des fichiers de configuration locaux (voir section suivante).

2) Que signifie chaque champ ?

AWS Access Key ID : identifiant public lié à une paire de clés IAM. C’est l’équivalent d’un nom d’utilisateur pour l’accès programmatique (API/CLI).

AWS Secret Access Key : la partie secrète de la paire (comme un mot de passe). Ne la partage jamais.

Default region name : région AWS par défaut (ex : eu-west-1 pour Irlande). Les services sont souvent régionaux ; certains coûts/ressources dépendent de la région.

Default output format : format de sortie par défaut (json, yaml, text, table). json est le plus courant pour les scripts.

3) Fichiers créés et format

Après aws configure, deux fichiers sont créés/édités :

Chemins

Linux / macOS:

~/.aws/credentials

~/.aws/config

Windows:

%USERPROFILE%\.aws\credentials

%USERPROFILE%\.aws\config

Exemple ~/.aws/credentials
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

Exemple ~/.aws/config
[default]
region = eu-west-1
output = json

Remarque : credentials contient les clés, config contient la région, le format et d’autres paramètres (profiles, rôle, etc.).

4) Profils nommés (très utile)

Tu peux stocker plusieurs comptes/profils (ex: dev, prod) :

aws configure --profile dev


Fichiers :

[dev]   # dans credentials
aws_access_key_id = ...
aws_secret_access_key = ...

[profile dev]   # dans config (note le mot-clé 'profile' devant le nom)
region = eu-west-1
output = json


Pour utiliser un profil :

AWS_PROFILE=dev aws s3 ls
# ou
aws s3 ls --profile dev

5) Variables d’environnement (alternatives temporaires)

Au lieu des fichiers, tu peux définir des variables d’environnement (pratique pour des sessions CI ou tests) :

Linux / macOS (bash/zsh) :

export AWS_ACCESS_KEY_ID=AKIA...
export AWS_SECRET_ACCESS_KEY=wJalr...
export AWS_DEFAULT_REGION=eu-west-1
export AWS_PROFILE=dev   # optionnel


PowerShell :

$Env:AWS_ACCESS_KEY_ID = "AKIA..."
$Env:AWS_SECRET_ACCESS_KEY = "wJalr..."

6) Vérifier que tout fonctionne

Lister le profil/config utilisé :

aws configure list

Cette commande affiche les valeurs réellement utilisées par le profil actif
(profil par défaut ou profil défini via --profile ou AWS_PROFILE).

Exemple sans profil (profil default) :
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                default              manual    --      
access_key     ****************ABCD        shared-credentials-file    
secret_key     ****************1234        shared-credentials-file    
    region                us-east-1              env    AWS_DEFAULT_REGION


aws configure list-profiles

Cette commande liste tous les profils configurés sur la machine.

Exemple :
default
dev
prod
personal
sandbox


Vérifier l’identité (commande simple et utile) :

aws sts get-caller-identity
# retourne l'Account, ARN, et UserId pour le profil courant

7) Sécurité — règles importantes (à suivre impérativement)

Ne jamais commit ~/.aws/credentials ou des clés dans Git.
Ne jamais partager les Access Key/Secret Key (même dans des captures d’écran).
Utiliser IAM avec principe du least privilege (droits mininum nécessaires).
Préférer les rôles et les identifiants temporaires (STS) plutôt que des clés à long terme quand c’est possible.
Activer et exiger MFA pour la console et opérations sensibles.
Faire tourner / remplacer (rotate) régulièrement les access keys.
Limiter l’accès réseau/conditions IAM (par IP, par condition aws:SourceIp, par heure, etc.).
Protéger les fichiers locaux : sur Linux/macOS, permissions strictes :
chmod 600 ~/.aws/credentials
chmod 600 ~/.aws/config

Utiliser des outils sécurisés pour stocker les clés (ex : aws-vault, awsume, gestionnaire de secrets, ou AWS SSO pour login centralisé).

8) Alternatives plus sûres

AWS SSO / AWS IAM Identity Center : gestion centralisée des comptes sans distribuer long-lived keys.
Rôles IAM (EC2 / ECS / Lambda) : attacher un rôle au service pour éviter toute clé sur le disque.
Credential_process : dans config, tu peux appeler un processus qui fournit dynamiquement des credentials (utile pour intégration avec gestionnaires de secrets).
Outils de coffre-fort : aws-vault ou gestionnaire secrets pour chiffrer et injector les clés uniquement en mémoire.

9) Rotation et suppression d’une clé (procédure rapide)

Créer une nouvelle clé dans la console IAM (ou via CLI) pour l’utilisateur.
Mettre à jour ~/.aws/credentials / variables d’environnement pour utiliser la nouvelle clé.
Tester (aws sts get-caller-identity).
Supprimer l’ancienne clé quand la nouvelle est validée.
Commande pour lister les clés IAM (si tu as les droits) :

aws iam list-access-keys --user-name ton-nom-utilisateur

Pour supprimer une clé :

aws iam delete-access-key --user-name ton-nom --access-key-id <OLDKEYID>

10) Paramètres avancés utiles dans ~/.aws/config

Assumer un rôle :

[profile prod-readonly]
role_arn = arn:aws:iam::123456789012:role/ReadOnlyRole
source_profile = admin
region = eu-west-1


Ici source_profile (ex: admin) contient les credentials utilisés pour assumer le rôle.

credential_process (exécution d’un helper pour récupérer des credentials temporaires) :

[profile my-special]
credential_process = /usr/local/bin/get-temporary-creds --arg foo

11) Petits conseils pratiques

Utilise aws configure set pour script :

aws configure set region eu-west-1 --profile dev
aws configure set output json --profile dev


Pour la sortie lisible humainement : aws s3 ls --profile dev --output table (mais json est préférable pour les scripts).

Si tu perds une clé : révoque-la immédiatement depuis la console IAM.

12) Récapitulatif rapide

aws configure enregistre clé + région + format dans ~/.aws/credentials et ~/.aws/config.

Utilise profils nommés pour séparer environnements.

Sécurité d’abord : pas de commit, rotation, MFA, préférer rôles/SSO/identifiants temporaires.

Vérifie avec aws sts get-caller-identity et aws configure list.

# === PROFILS MULTIPLES ===

# Configurer profil nommé
aws configure --profile dev
aws configure --profile prod
aws configure --profile staging

# Structure ~/.aws/credentials
[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

[dev]
aws_access_key_id = AKIAI44QH8DHBEXAMPLE
aws_secret_access_key = je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY

[prod]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

# Structure ~/.aws/config
[default]
region = eu-west-1
output = json

[profile dev]
region = us-east-1
output = yaml

[profile prod]
region = eu-west-3
output = table

# Utiliser profil spécifique
aws s3 ls --profile dev
aws ec2 describe-instances --profile prod

# Définir profil par défaut (temporaire)
export AWS_PROFILE=dev              # Linux/Mac
set AWS_PROFILE=dev                 # Windows CMD
$env:AWS_PROFILE="dev"              # PowerShell

# === CONFIGURATION AVANCÉE (DÉTAILLÉ) ===

Les commandes ci-dessous permettent de consulter, récupérer, modifier et personnaliser les paramètres de l’AWS CLI sans entrer dans les fichiers manuellement.

[OK] 1) Lister les configurations existantes
[IMPORTANT] Commande :
aws configure list

[RECHERCHE] Ce que ça affiche :

Cette commande montre les valeurs actuellement utilisées par la CLI, provenant de plusieurs sources :

Source	Priorité	Exemple
Variables d’environnement	Très haute	AWS_ACCESS_KEY_ID=...
Fichiers de config (config)	Moyenne	region = eu-west-1
Fichiers credentials	Moyenne	Access Key / Secret Key
Valeurs par défaut	Basse	None

Exemple de sortie :

      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************ABCD      config-file   ~/.aws/credentials
secret_key     ****************1234      config-file   ~/.aws/credentials
    region                eu-west-1             config-file   ~/.aws/config

[IMPORTANT] Lister la configuration d’un profil spécifique
aws configure list --profile dev


Cela montre les valeurs utilisées uniquement pour le profil dev.

Très utile quand tu as plusieurs environnements :

default
dev
stage
prod

[OK] 2) Obtenir une valeur spécifique
[IMPORTANT] Récupérer la région courante
aws configure get region


Affiche la région par défaut (profil default).

[IDEE] Utilité :

Scripts

Debug (pourquoi AWS utilise-t-il la mauvaise région ?)

Automatisation CI

[IMPORTANT] Récupérer une valeur pour un profil donné
aws configure get aws_access_key_id --profile dev


Affiche uniquement la clé ID du profil dev.

Très utile pour savoir si ton profil est bien configuré.

Exemple :

AKIAIOSFODNN7EXAMPLE

[OK] 3) Définir une valeur spécifique

Ces commandes modifient directement les fichiers dans ~/.aws/.

[IMPORTANT] Changer la région par défaut
aws configure set region us-west-2


Modifie ~/.aws/config dans la section [default].

Résultat dans config :
[default]
region = us-west-2

[IMPORTANT] Définir un format de sortie pour un profil
aws configure set output yaml --profile dev

Résultat dans config :
[profile dev]
output = yaml

Les formats possibles :

json (le plus précis)
table (lisible pour humains)
text
yaml

[IMPORTANT] Désactiver la pagination automatique (very useful)

Par défaut, certaines commandes AWS utilisent un pager (comme less) pour afficher les résultats.
Cela peut gêner dans les scripts.

Désactivation :

aws configure set cli_pager ""

Effet :

Pas de pagination
Sortie directe dans la console
Plus simple pour les scripts CI/CD

Résultat dans config :
[default]
cli_pager =

Si tu mets " " (une chaîne vide), AWS CLI comprend que tu ne veux plus de pager.

[COURS] Résumé clair
Action	Commande
Lister la config active:	aws configure list
Lister un profil:	aws configure list --profile dev
Lire une valeur:	aws configure get region
Lire valeur d’un profil:	aws configure get aws_access_key_id --profile dev
Modifier valeur:	aws configure set region us-west-2
Modifier pour un profil:	aws configure set output yaml --profile dev
Désactiver pagination:	aws configure set cli_pager ""
[LOGIQUE] Conseils avancés & bonnes pratiques
- 1. Utilise aws configure set dans les scripts DevOps / CI

Exemple d’un script Bash d'initialisation :

aws configure set region eu-west-1 --profile dev
aws configure set output json --profile dev
aws configure set cli_pager ""

- 2. Tu peux éditer les fichiers directement, mais…

Utilise toujours aws configure set pour éviter les erreurs de syntaxe.

Par exemple :

espaces inutiles

mauvais en-tête [profile dev] vs [dev]

clés dans le mauvais fichier (config vs credentials)

- 3. Préférez les profils nommés plutôt que default

Pour éviter de casser un déploiement :

[X] Mauvaise pratique :

aws configure set region us-east-1


[OK] Bonne pratique :

aws configure set region us-east-1 --profile prod

- 4. Pour vérifier ce que la CLI utilise réellement :
aws sts get-caller-identity --profile dev


Permet de voir :

compte AWS

utilisateur/rôle

ID

Très pratique pour vérifier que tu n’utilises pas le mauvais profil.

# === VARIABLES D'ENVIRONNEMENT ===

# Définir credentials via variables (TEMPORAIRE)
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
export AWS_DEFAULT_REGION=eu-west-1

# Windows CMD
set AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
set AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
set AWS_DEFAULT_REGION=eu-west-1

# PowerShell
$env:AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
$env:AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
$env:AWS_DEFAULT_REGION="eu-west-1"

# === OBTENIR CLÉS D'ACCÈS (EXPLIQUÉ EN DÉTAIL) ===

Les clés d’accès (Access Key + Secret Access Key) permettent à un programme, un script, une CLI ou un service externe de se connecter à AWS sans passer par l’interface web.

Elles sont l’équivalent technique d’un nom d’utilisateur + mot de passe, mais pour les API.

[OK] 1) Connexion à la console AWS

Accéder à la console web :

https://console.aws.amazon.com


Identifie-toi avec :

ton email / ton compte root ou

ton utilisateur IAM (fortement recommandé)

[!] IMPORTANT :
Utilise la console root uniquement pour l’administration extrême (facturation, sécurité majeure).
Pour tout le reste, utilise un utilisateur IAM normal.

[OK] 2) Accéder à ton utilisateur IAM

En haut à droite tu verras :

ton nom d’utilisateur IAM ou
ton nom de compte root
Clique dessus -> My security credentials

Cela ouvre la page complète de configuration de sécurité.

[OK] 3) Aller dans "Security credentials"

Sur la page de ton utilisateur IAM, tu trouveras plusieurs sections :

Password console

MFA
Access keys (programmatic access)
SSH keys for CodeCommit
Signing certificates

Pour les clés CLI/API, c’est la section Access keys.

[OK] 4) Créer une nouvelle clé d’accès

Va dans :

Security credentials -> Access keys -> Create access key

[IDEE] Important :

AWS limite le nombre de clés par utilisateur :
-> Maximum 2 clés actives.

Créer une clé génère :

Access Key ID (publique)
Secret Access Key (secrète)

Tu verras un écran du genre :

Access Key ID: AKIAIOSFODNN7XXXXXX
Secret Access Key: *************************** (visible une seule fois)

[OK] 5) Télécharger le fichier CSV

AWS te propose ensuite de télécharger un fichier CSV contenant :

User name,Access key ID,Secret access key
mohamed,AKIAIOS...,wJalrXUtnFEMI...


[ATTENTION] C’est la SEULE et UNIQUE fois où tu verras le Secret Access Key.

Si tu perds ce secret :
-> Tu dois supprimer la clé et en créer une nouvelle.
AWS n'affiche jamais un secret une seconde fois.

[ATTENTION] SÉCURITÉ : RÈGLES ABSOLUMENT INDISPENSABLES

Les clés AWS sont aussi puissantes qu'un mot de passe administrateur.
Tu dois donc respecter des règles très strictes.

[VERROUILLE] 1) Ne jamais partager

Jamais dans :

messages
captures d’écran
mails
photos
forums
code

Même floutées, c'est risqué.

[VERROUILLE] 2) Ne jamais committer dans Git

Dans aucun cas, ne mets jamais de clés dans :

GitHub
GitLab
Bitbucket
Projets publics ou privés
.env sans secret manager
Dockerfile

Même un repo privé peut être compromis.

Si tu commites une clé :
GitHub la détecte automatiquement
Elle est immédiatement compromise
Tu DOIS la supprimer dans AWS

[VERROUILLE] 3) Utiliser IAM roles (MEILLEURE PRATIQUE)

Pour EC2, ECS, Lambda, Terraform Cloud, etc. :

-> NE JAMAIS mettre de clés dans les serveurs.
-> Utilise un IAM Role attaché à la ressource.

Les rôles :
ne stockent pas de secret
génèrent des credentials temporaires automatiquement
sont impossibles à voler

Exemples :

EC2 -> Instance Profile
Lambda -> Execution Role
ECS -> Task Role

C'est la meilleure solution en production.

[VERROUILLE] 4) Activer MFA (authentification à 2 facteurs)

Très important, surtout si tu utilises la console AWS.

Deux types :
MFA virtuelle (Google Authenticator, Authy, etc.)
MFA physique (Yubikey)

Sans MFA :
n’importe qui qui obtient ton mot de passe peut tout supprimer.

Avec MFA :
impossible de se connecter sans ton smartphone ou ta clé physique.

[VERROUILLE] 5) Rotation régulière des clés

AWS recommande :

rotation tous les 60 à 90 jours
rotation immédiate si fuite suspectée

Processus recommandé :
Créer une nouvelle clé
Tester qu’elle fonctionne (CLI, scripts…)
Désactiver l’ancienne clé

La supprimer dès que tout marche

[PACKAGE] BONUS : Exemple de stratégie IAM correcte

Pour un développeur :
AdministratorAccess


[X] À éviter.

Mieux :

AmazonS3ReadOnlyAccess
AmazonEC2ReadOnlyAccess
IAMReadOnlyAccess


Toujours appliquer la règle du principe du moindre privilège (Least Privilege).

[STOP] Erreurs courantes (à éviter absolument)
Erreur	Conséquence
Mettre les clés dans un fichier Git	Fuite publique, compromission totale
Mettre les clés dans un Dockerfile	Clés volées immédiatement
Envoyer clés par email / WhatsApp	Risque énorme
Utiliser la même clé 1 an ou plus	Très dangereux
Utiliser le compte root pour créer des clés	ERREUR CATASTROPHIQUE

-> Le compte root ne doit JAMAIS avoir de Access Keys.

[COURS] Récapitulatif clair

Créer une clé AWS :
Console AWS
Security credentials
Access keys
Create access key
Télécharger CSV (visible une seule fois)

Sécurité :
jamais partager
jamais mettre dans Git
utiliser IAM Roles
activer MFA
rotation régulière


Choisir une **région AWS** est indispensable parce que **tous les services AWS sont répartis dans des régions physiques différentes**, et AWS ne sait pas automatiquement où tu veux créer ou gérer tes ressources.

Voici l’explication claire, simple et complète v

---

# [OBJECTIF] Pourquoi doit-on choisir une région AWS ?

Parce que **chaque ressource AWS existe dans une région spécifique**.

Exemples :

* Une instance **EC2** créée en *eu-west-1* (Irlande) n’existe pas en *us-east-1*.
* Un bucket **S3** créé en *us-east-1* n’est pas visible dans *eu-west-3*.
* Une **Lambda**, un **RDS**, un **EKS**, un **VPC**, tout est régional.

-> AWS doit donc savoir *où* exécuter ta commande.

---

# [IMPORTANT] 1) Tu dois dire à AWS où créer tes ressources

Si tu tapes :

```bash
aws ec2 run-instances ...
```

AWS te demandera implicitement :

> “Dans quel centre de données dois-je créer ton instance ?”

Parce que chaque région correspond à un vrai endroit physique :

| Région         | Localisation |
| -------------- | ------------ |
| eu-west-1      | Irlande      |
| eu-west-3      | Paris        |
| us-east-1      | Virginie     |
| us-west-2      | Oregon       |
| ap-south-1     | Inde         |
| ap-northeast-1 | Tokyo        |

---

# [IMPORTANT] 2) Les prix changent d’une région à l’autre

Une même instance EC2 peut :

* coûter **moins cher** en Virginie (us-east-1)
* être **plus chère** à Paris (eu-west-3)

Les coûts varient selon la région.

---

# [IMPORTANT] 3) Les services disponibles varient selon les régions

Tous les services AWS ne sont pas disponibles partout.

Exemples :

* **Bedrock** n’est pas disponible dans toutes les régions.
* Certains types d’EC2, de RDS ou de GPU ne sont disponibles que dans des régions spécifiques.

Donc AWS doit savoir où chercher.

---

# [IMPORTANT] 4) Pour respecter la loi (RGPD, conformité, localisation des données)

Certaines entreprises doivent stocker les données :

* dans l’UE
* dans un pays spécifique
* dans une région géographique donnée

Donc on choisit souvent :

* `eu-west-3` -> Paris
* `eu-central-1` -> Francfort

Parce que les données ne doivent **pas quitter l’Europe**.

---

# [IMPORTANT] 5) Pour la latence : vitesse d’accès

Si ton application web sert des utilisateurs en Europe, tu veux :

-> une région européenne
-> sinon les requêtes traversent l’Atlantique -> latence plus haute

Exemples :

* App française -> `eu-west-3` (Paris)
* App africaine -> `eu-west-1` (Irlande) ou `me-central-1` (EAU)
* App US -> `us-east-1`

---

# [IMPORTANT] 6) AWS CLI doit savoir quelle région utiliser par défaut

Quand tu fais :

```bash
aws s3 ls
```

AWS doit savoir si tu listes les buckets de :

* *eu-west-1* ?
* *eu-west-3* ?
* *us-east-1* ?
* *ap-south-1* ?

Sinon, erreur :

```
You must specify a region.
```

---

# [COURS] Résumé simple

**Choisir la région = choisir le centre de données dans lequel tes ressources AWS vivent.**

C’est important pour :

* le prix
* les performances
* la conformité
* les services disponibles
* la gestion de tes ressources

-> Sans région -> AWS ne sait pas où travailler.


Voici l’explication claire, simple et **sans ambiguïté**, spécialement pour comprendre la différence entre **Access Key** et **IAM** :

---

# [OK] **Différence entre Access Key et IAM**

## **1. IAM (Identity and Access Management)**

**IAM = Le système complet qui gère les utilisateurs et les permissions dans AWS.**

IAM permet :

* de créer des **utilisateurs** (ex: dev, admin, stagiaire…)
* de créer des **rôles** (ex: rôle pour EC2, rôle pour Lambda…)
* d’attribuer des **permissions** (ex: accès S3 seulement, accès RDS, accès total…)
* de créer des **groupes**
* de gérer les **politiques de sécurité**

-> **IAM = la gestion des identités et des accès.**

C’est comme *Active Directory*, mais pour AWS.

---

## **2. Access Key**

Une **Access Key** est simplement **une paire d’identifiants de connexion pour un utilisateur IAM**.

Elle contient :

* **Access Key ID**
* **Secret Access Key**

Ces clés servent uniquement :

* à se connecter à AWS depuis la **CLI** (`aws cli`)
* ou depuis du **code** (Python, Node, Java…)
* ou depuis un **outil externe** (Terraform, Ansible…)

-> **Access Key = mot de passe + login pour un utilisateur IAM (mais pour machines / code).**

---

# [OBJECTIF] Résumé clair

| -------------- | ----------------------------------------------------- | ------------------------------------ |
| Concept        | Description                                           | Utilité                              |
| -------------- | ----------------------------------------------------- | ------------------------------------ |
| **IAM**        | Le service qui gère les comptes, permissions et rôles | Définir qui a le droit de faire quoi |
| **IAM User**   | Un compte dans AWS                                    | Identité humaine ou machine          |
| **Access Key** | Identifiants d’un IAM User                            | Permet connexion via AWS CLI / SDK   |
| -------------- | ----------------------------------------------------- | ------------------------------------ |

---

# [COURS] Exemple pour bien comprendre

### - Étape 1 : Tu crées un **utilisateur IAM**

Ex : `admin-dev`.

### - Étape 2 : Tu lui donnes des permissions IAM

Ex : accès à S3 et EC2.

### - Étape 3 : Tu génères une **Access Key** pour cet utilisateur

* AWS_ACCESS_KEY_ID
* AWS_SECRET_ACCESS_KEY

### - Étape 4 : Tu utilises ces clés dans :

```
aws configure
```

Et la CLI devient authentifiée.

---

# [LOGIQUE] Retient ceci :

**IAM = système**
**IAM User = identité**
**Access Key = clé d’accès pour cette identité**


Voici l’explication **la plus claire possible**, car c’est une confusion très fréquente :

---

# [OK] **IAM Role vs Access Key : la différence essentielle**

## **1. IAM Role (Rôle IAM)**

Un **IAM Role** est une *identité temporaire* que **les services AWS** ou **des applications** peuvent **assumer** (“assume role”).
Il n’a **pas de mot de passe**
Il n’a **pas d’access key permanente**
Il fournit **des permissions temporaires** (via des tokens STS).

-> **Le rôle donne des permissions à une machine, un service, ou un utilisateur pour un temps limité.**

### Exemple :

* Un **EC2** peut avoir un rôle qui lui donne accès à un bucket S3.
* Une **Lambda** peut avoir un rôle qui lui permet d’accéder à DynamoDB.
* Un **développeur** peut *assumer* un rôle admin pour 1 heure, sans utiliser d’Access Key.

---

## **2. Access Key**

Une **Access Key** est une paire d’identifiants **permanents** pour un **utilisateur IAM**, utilisée pour se connecter depuis :

* la CLI
* un script Python
* Terraform
* un outil externe

Elle agit comme un **mot de passe machine**.

-> **L’Access Key permet à du code ou une personne de se connecter à AWS de manière permanente (jusqu’à révocation).**

---

# [LOGIQUE] Résumé Ultra Simple

| Caractéristique        | IAM Role                                             | Access Key                                       |
| ---------------------- | ---------------------------------------------------- | ------------------------------------------------ |
| **Type**               | Identité temporaire                                  | Identité permanente                              |
| **Destiné à**          | AWS services / applications / assumée temporairement | humains ou scripts                               |
| **Authentification**   | via **STS tokens** temporaires                       | via **Access Key ID + Secret**                   |
| **Expiration**         | Oui (minutes à heures)                               | Non (reste valide tant qu’on ne la supprime pas) |
| **Sécurité**           | Très élevée                                          | Risque élevé si fuite                            |
| **Utilisation idéale** | EC2, Lambda, EKS, S3 Replication, cross-account      | CLI, scripts locaux, outils externes             |

---

# [OBJECTIF] Exemple concret pour bien comprendre

### - Sans IAM Role

Tu veux que ton EC2 accède à S3.
Tu es obligé de mettre une **Access Key dans ton code**, ce qui est dangereux.
[X] Si la clé fuite -> piratage total.

---

### - Avec IAM Role

Tu attaches un rôle à l’EC2 :

* pas de mot de passe
* pas de clés
* permissions gérées par AWS
* rotation automatique

L’EC2 reçoit **des clés temporaires** sécurisées.
[OK] Aucun risque de fuite d’Access Key.

---

# [COURS] La règle d’or AWS

### **Utilise IAM Role pour les serveurs et services AWS**

### **Utilise Access Key uniquement pour un humain ou outil externe**

---

# [SECURISE] Pourquoi AWS pousse à utiliser les Rôles ?

Parce que :

* ils expirent automatiquement
* ils réduisent le risque de vol de clés
* ils sont parfaitement intégrés à EC2, Lambda, ECS, EKS…

AWS considère même que les Access Keys sont un **dernier recours**, uniquement pour la CLI locale.


Voici l’explication la plus claire, simple et **précise** du fonctionnement des rôles IAM et de STS.

---

# [OK] **Comment un IAM Role génère des clés temporaires via STS ?**

AWS utilise un service interne appelé **STS (Security Token Service)**.
C’est lui qui crée les **credentials temporaires** lorsqu’un rôle est assumé.

---

# [HOT] 1. Le principe général

Quand une machine (EC2, Lambda, EKS) ou un humain depuis la CLI “**assume**” un rôle :

-> **AWS STS génère automatiquement une paire de clés temporaires :**

* AccessKeyId
* SecretAccessKey
* SessionToken

Ces clés :

* durent entre **15 minutes et 12 heures**
* sont **renouvelées automatiquement** pour les services AWS
* expirent ensuite -> inutilisables

---

# [OUTIL] 2. Workflow exact (en 5 étapes)

### **Étape 1 — Une entité demande à assumer un rôle**

Exemples :

* un EC2 demande : “Je veux assumer le rôle `EC2ReadS3Role`”
* un humain tape :

  ```bash
  aws sts assume-role --role-arn arn:aws:iam::123:role/AdminRole
  ```

---

### **Étape 2 — AWS vérifie que l’entité a le droit d’assumer ce rôle**

Un rôle contient une **Trust Policy** (policy de confiance).

Exemple de trust policy minimaliste pour EC2 :

```json
{
  "Effect": "Allow",
  "Principal": { "Service": "ec2.amazonaws.com" },
  "Action": "sts:AssumeRole"
}
```

C'est cette policy qui dit :
-> “EC2 a le droit de devenir ce rôle”.

---

### **Étape 3 — STS génère automatiquement des credentials temporaires**

STS renvoie un bloc complet comme :

```json
{
  "AccessKeyId": "ASIA....",
  "SecretAccessKey": "abc123...",
  "SessionToken": "IQoJb3JpZ2luX2V..."
}
```

Ce sont les **credentials temporaires**.

---

### **Étape 4 — L'entité (EC2, utilisateur CLI, Lambda…) utilise ces clés**

Pendant la durée de validité :

* les requêtes AWS se font avec ces **clés temporaires**
* STS valide le **session token**
* les permissions sont celles du rôle

---

### **Étape 5 — Expiration**

Une fois expirées :

* impossible de les réutiliser
* une nouvelle demande STS est nécessaire

AWS régénère automatiquement les clés pour EC2/Lambda.
Pour un humain, il faut rappeler `assume-role`.

---

# [LOGIQUE] Pourquoi ajouter un *SessionToken* ?

C’est ce qui différencie une **clé permanente** (danger)
d’une **clé temporaire STS** (sécurité).

Le SessionToken :

* est unique
* expire automatiquement
* empêche la réutilisation illégale
* protège contre le vol

---

# [PACKAGE] Exemple concret : un EC2 assume un rôle automatiquement

[OBJECTIF] Quand tu attaches un rôle à une instance EC2 :

AWS installe un **metadata service** à cette adresse :

```
http://169.254.169.254/latest/meta-data/iam/security-credentials/
```

L’EC2 demande :

```
GET /latest/meta-data/iam/security-credentials/MonRole
```

AWS renvoie des clés temporaires STS :

* renouvelées automatiquement
* jamais stockées dans du code

C’est transparent pour toi.

---

# [OBJECTIF] Résumé ultra simple

| Type                   | Permanent ?   | Qui les génère ? | Sécurité              |
| ---------------------- | ------------- | ---------------- | --------------------- |
| **Access Key IAM**     | Oui           | Créée par toi    | [ATTENTION] Dangereux si fuite |
| **STS Temporary Keys** | Non (max 12h) | Générées par STS | [VERROUILLE] Très sécurisé      |

-> **Les IAM Roles utilisent STS pour ne jamais avoir de clés permanentes.**



═══════════════════════════════════════════════════════════════════════════════
[OK] IAM (IDENTITY AND ACCESS MANAGEMENT) - GESTION DES ACCÈS
═══════════════════════════════════════════════════════════════════════════════

# [REFLEXION] QU'EST-CE QUE IAM?
IAM = système qui contrôle QUI peut faire QUOI sur vos ressources AWS.
C'est le système de sécurité et de permissions de AWS.

[IDEE] Analogie: IAM = système de badges et portes sécurisées dans une entreprise
- Certaines personnes ont badge pour accéder au parking
- D'autres ont badge pour accéder aux bureaux
- Le PDG a accès partout
- Le stagiaire a accès limité

# [OBJECTIF] CONCEPTS FONDAMENTAUX (BIEN COMPRENDRE)

## 1. Users (Utilisateurs)
= Une personne ou une application qui a besoin d'accéder à AWS
Exemple: Jean, développeur dans votre équipe

## 2. Groups (Groupes)
= Collection d'utilisateurs avec permissions similaires
Exemple: Groupe "Developers" avec tous les développeurs
Avantage: Gérer permissions par groupe plutôt que utilisateur par utilisateur

## 3. Roles (Rôles)
= Ensemble de permissions qu'un SERVICE AWS peut utiliser
Exemple: Permettre à EC2 d'accéder à S3
[ATTENTION] IMPORTANT: Roles ≠ Users
- Users = pour personnes
- Roles = pour services AWS (EC2, Lambda, etc.)

## 4. Policies (Politiques)
= Document JSON qui définit les permissions
Exemple: "Peut lire S3 mais pas supprimer"

# [GRAPHIQUE] HIÉRARCHIE IAM (Comment tout s'organise)

Compte AWS Root ([ROUGE] DANGEREUX - NE PAS UTILISER!)
    │
    ├─── Utilisateur IAM (Jean)
    │    └─── Policies attachées directement
    │
    ├─── Groupe IAM (Developers)
    │    ├─── Jean (membre)
    │    ├─── Marie (membre)
    │    └─── Policies du groupe (tous les membres héritent)
    │
    └─── Role IAM (EC2-S3-Access)
         ├─── Trust Policy (qui peut assumer ce role?)
         └─── Permissions Policy (que peut faire ce role?)

# === USERS (UTILISATEURS) ===

# [NOTE] EXPLICATION: Un utilisateur = une identité permanente dans AWS
# Utilisé pour: des personnes qui ont besoin d'accéder à AWS

# Créer utilisateur
aws iam create-user --user-name john

# [IDEE] Ce que ça fait:
# - Crée un compte "john" dans votre compte AWS
# - Par défaut, john n'a AUCUNE permission (principe du moindre privilège)
# - john ne peut PAS encore se connecter (pas de mot de passe ni clés)

# Output:
# {
#     "User": {
#         "UserName": "john",
#         "UserId": "AIDAJEXAMPLE",
#         "Arn": "arn:aws:iam::123456789012:user/john",
#         "CreateDate": "2024-01-15T10:30:00Z"
#     }
# }

# Créer avec tags (pour organisation)
aws iam create-user --user-name john \
  --tags Key=Department,Value=Engineering Key=Team,Value=Backend

# [IDEE] Tags = étiquettes pour organiser ressources
# Utiles pour: facturation, organisation, recherche
# Exemple: "Montrer tous les utilisateurs du département Engineering"

# Lister tous les utilisateurs
aws iam list-users

# Output:
# {
#     "Users": [
#         {
#             "UserName": "john",
#             "UserId": "AIDAJEXAMPLE",
#             "Arn": "arn:aws:iam::123456789012:user/john",
#             "CreateDate": "2024-01-15T10:30:00Z"
#         },
#         {
#             "UserName": "marie",
#             "UserId": "AIDAIEXAMPLE2",
#             "Arn": "arn:aws:iam::123456789012:user/marie",
#             "CreateDate": "2024-01-14T09:20:00Z"
#         }
#     ]
# }

# Lister avec format table (plus lisible)
aws iam list-users --output table

# Obtenir info utilisateur spécifique
aws iam get-user --user-name john

# Obtenir info sur MOI (utilisateur actuel)
aws iam get-user
# [IDEE] Utile pour vérifier quel utilisateur vous utilisez

# Supprimer utilisateur
aws iam delete-user --user-name john

# [ATTENTION] ATTENTION: Vous devez d'abord:
# 1. Supprimer toutes les clés d'accès
# 2. Détacher toutes les policies
# 3. Retirer des groupes
# Sinon vous aurez une erreur!

# === ACCESS KEYS (CLÉS D'ACCÈS) ===

# [NOTE] EXPLICATION: Access Keys = identifiants pour AWS CLI/SDK/API
# Composées de 2 parties:
# - Access Key ID = identifiant public (comme nom d'utilisateur)
# - Secret Access Key = mot de passe secret (à garder secret!)

# Créer clé d'accès pour un utilisateur
aws iam create-access-key --user-name john

# Output ([ATTENTION] TRÈS IMPORTANT - Sauvegarder immédiatement!):
# {
#     "AccessKey": {
#         "UserName": "john",
#         "AccessKeyId": "AKIAI44QH8DHBEXAMPLE",
#         "Status": "Active",
#         "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
#         "CreateDate": "2024-01-15T10:30:00Z"
#     }
# }

# [ATTENTION] CRITIQUE: C'est la SEULE fois où vous verrez le SecretAccessKey!
# - Copiez-le immédiatement dans un endroit sûr
# - Si vous le perdez, vous devrez créer une nouvelle clé
# - Ne JAMAIS partager ces clés
# - Ne JAMAIS les commiter dans Git

# [IDEE] Donner ces clés à john pour qu'il configure AWS CLI:
# john $ aws configure
# AWS Access Key ID: AKIAI44QH8DHBEXAMPLE
# AWS Secret Access Key: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
# ...

# Lister toutes les clés d'un utilisateur
aws iam list-access-keys --user-name john

# Output:
# {
#     "AccessKeyMetadata": [
#         {
#             "UserName": "john",
#             "AccessKeyId": "AKIAI44QH8DHBEXAMPLE",
#             "Status": "Active",
#             "CreateDate": "2024-01-15T10:30:00Z"
#         }
#     ]
# }

# [IDEE] Note: Vous ne voyez PAS le SecretAccessKey (c'est normal, c'est secret!)

# Désactiver clé (sans la supprimer)
aws iam update-access-key \
  --user-name john \
  --access-key-id AKIAI44QH8DHBEXAMPLE \
  --status Inactive

# [IDEE] Quand l'utiliser?
# - Vous suspectez que la clé a été compromise
# - Vous voulez temporairement empêcher l'accès
# - Test de rotation de clés
# Avantage: Vous pouvez la réactiver plus tard

# Réactiver clé
aws iam update-access-key \
  --user-name john \
  --access-key-id AKIAI44QH8DHBEXAMPLE \
  --status Active

# Supprimer clé (DÉFINITIF)
aws iam delete-access-key \
  --user-name john \
  --access-key-id AKIAI44QH8DHBEXAMPLE

# [IDEE] Après suppression, john ne pourra plus utiliser cette clé
# Il devra en créer une nouvelle

# [VERROUILLE] BONNES PRATIQUES POUR LES CLÉS:
# [OK] Rotation régulière (tous les 90 jours)
# [OK] Une clé par utilisateur (pas de partage!)
# [OK] Désactiver/supprimer clés inutilisées
# [OK] Utiliser IAM Roles au lieu de clés quand possible (EC2, Lambda)
# [X] Ne JAMAIS hardcoder dans le code
# [X] Ne JAMAIS commiter dans Git
# [X] Ne JAMAIS partager par email/Slack

# === GROUPS (GROUPES) ===

# [NOTE] EXPLICATION: Groupe = collection d'utilisateurs
# Au lieu de donner permissions à chaque utilisateur individuellement,
# vous créez un groupe avec permissions et ajoutez utilisateurs dedans.

# [IDEE] Exemple d'organisation type:
# - Groupe "Admins" -> Permissions complètes
# - Groupe "Developers" -> Accès EC2, S3, RDS
# - Groupe "ReadOnly" -> Lecture seule partout
# - Groupe "Billing" -> Voir les coûts

# Créer groupe
aws iam create-group --group-name Developers

# Output:
# {
#     "Group": {
#         "GroupName": "Developers",
#         "GroupId": "AGPAJEXAMPLE",
#         "Arn": "arn:aws:iam::123456789012:group/Developers",
#         "CreateDate": "2024-01-15T10:30:00Z"
#     }
# }

# Créer plusieurs groupes
aws iam create-group --group-name Admins
aws iam create-group --group-name ReadOnly
aws iam create-group --group-name Billing

# Lister tous les groupes
aws iam list-groups

# Output:
# {
#     "Groups": [
#         {
#             "GroupName": "Developers",
#             "GroupId": "AGPAJEXAMPLE",
#             "Arn": "arn:aws:iam::123456789012:group/Developers",
#             "CreateDate": "2024-01-15T10:30:00Z"
#         },
#         {
#             "GroupName": "Admins",
#             ...
#         }
#     ]
# }

# Ajouter utilisateur à un groupe
aws iam add-user-to-group \
  --user-name john \
  --group-name Developers

# [IDEE] Ce que ça fait:
# - john hérite maintenant de TOUTES les permissions du groupe Developers
# - Si vous ajoutez une permission au groupe, john l'obtient automatiquement
# - john peut être dans plusieurs groupes (exemple: Developers + Billing)

# Ajouter john à plusieurs groupes
aws iam add-user-to-group --user-name john --group-name Developers
aws iam add-user-to-group --user-name john --group-name Billing

# Retirer utilisateur d'un groupe
aws iam remove-user-from-group \
  --user-name john \
  --group-name Developers

# [IDEE] john perd les permissions du groupe Developers

# Lister tous les utilisateurs d'un groupe
aws iam get-group --group-name Developers

# Output:
# {
#     "Group": {
#         "GroupName": "Developers",
#         ...
#     },
#     "Users": [
#         {
#             "UserName": "john",
#             ...
#         },
#         {
#             "UserName": "marie",
#             ...
#         }
#     ]
# }

# Supprimer groupe
aws iam delete-group --group-name Developers

# [ATTENTION] ATTENTION: Le groupe doit être vide
# 1. Retirer tous les utilisateurs
# 2. Détacher toutes les policies
# 3. Puis supprimer

# === POLICIES (POLITIQUES) ===

# [NOTE] EXPLICATION: Policy = document JSON qui définit les permissions
# Format: "Effect": "Allow" ou "Deny"
#         "Action": Quelles actions? (s3:GetObject, ec2:StartInstances, etc.)
#         "Resource": Sur quelles ressources? (bucket spécifique, toutes les EC2, etc.)

# [OBJECTIF] 2 TYPES DE POLICIES:

# 1⃣ AWS Managed Policies
#    = Policies créées et maintenues par AWS
#    Exemples: AmazonS3ReadOnlyAccess, AmazonEC2FullAccess
#    [OK] Avantages: Prêtes à l'emploi, mises à jour par AWS
#    [OK] Recommandées pour débuter

# 2⃣ Customer Managed Policies
#    = Policies que VOUS créez
#    Pour: besoins spécifiques, contrôle fin

# Lister toutes les policies AWS managées
aws iam list-policies --scope AWS

# [IDEE] Il y en a beaucoup! (plusieurs centaines)
# Filtre utile:
aws iam list-policies --scope AWS --max-items 10

# Lister VOS policies custom
aws iam list-policies --scope Local

# Obtenir détails d'une policy
aws iam get-policy --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# Output:
# {
#     "Policy": {
#         "PolicyName": "AmazonS3ReadOnlyAccess",
#         "Arn": "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess",
#         "Description": "Provides read only access to all buckets via the AWS Management Console",
#         "DefaultVersionId": "v1",
#         ...
#     }
# }

# Obtenir le JSON de la policy (voir les permissions exactes)
aws iam get-policy-version \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess \
  --version-id v1

# [IDEE] Voir le contenu JSON pour comprendre exactement ce qui est autorisé

# === EXEMPLES DE POLICIES JSON (COMPRENDRE LA STRUCTURE) ===

# [NOTE] STRUCTURE D'UNE POLICY:

# policy.json - Accès S3 lecture seule sur UN bucket spécifique
{
    "Version": "2012-10-17",              # Version du langage (toujours cette valeur)
    "Statement": [                         # Liste de "règles"
        {
            "Effect": "Allow",             # "Allow" = autoriser, "Deny" = refuser
            "Action": [                    # Quelles actions?
                "s3:GetObject",            # Télécharger fichiers
                "s3:ListBucket"            # Lister fichiers du bucket
            ],
            "Resource": [                  # Sur quelles ressources?
                "arn:aws:s3:::my-bucket",          # Le bucket lui-même
                "arn:aws:s3:::my-bucket/*"         # Tous les objets dans le bucket
            ]
        }
    ]
}

# [IDEE] Explication ligne par ligne:
# - Cette policy AUTORISE ("Allow")
# - 2 actions: télécharger fichiers (GetObject) et lister fichiers (ListBucket)
# - Uniquement sur le bucket "my-bucket" et son contenu
# - Résultat: L'utilisateur peut LIRE le bucket mais pas écrire, supprimer, etc.

# policy-admin-s3.json - Accès S3 COMPLET sur TOUS les buckets
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:*",              # "*" = toutes les actions S3
            "Resource": "*"                # "*" = toutes les ressources
        }
    ]
}

# [IDEE] Explication:
# - "s3:*" = Toutes les actions S3 (lire, écrire, supprimer, etc.)
# - "Resource": "*" = Sur tous les buckets et objets
# - C'est très permissif! Utiliser avec prudence

# policy-ec2-start-stop.json - Démarrer/arrêter instances EC2
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:StartInstances",      # Démarrer instances
                "ec2:StopInstances",       # Arrêter instances
                "ec2:DescribeInstances"    # Voir liste des instances
            ],
            "Resource": "*"                # Sur toutes les instances
        }
    ]
}

# [IDEE] Cas d'usage:
# Pour un utilisateur qui doit pouvoir démarrer/arrêter serveurs
# mais PAS les créer/supprimer

# policy-specific-instances.json - Accès SEULEMENT à certaines instances
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "ec2:*",
            "Resource": [
                "arn:aws:ec2:us-east-1:123456789012:instance/i-1234567890abcdef0",
                "arn:aws:ec2:us-east-1:123456789012:instance/i-0987654321fedcba0"
            ]
        },
        {
            "Effect": "Allow",
            "Action": "ec2:Describe*",     # Voir la liste (nécessaire)
            "Resource": "*"
        }
    ]
}

# [IDEE] Explication:
# - Contrôle total sur 2 instances spécifiques seulement
# - Peut voir la liste de toutes les instances (mais pas les modifier)

# policy-deny-example.json - REFUSER certaines actions
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "s3:*",
            "Resource": "*"
        },
        {
            "Effect": "Deny",              # [ATTENTION] Deny a priorité sur Allow!
            "Action": "s3:DeleteBucket",   # Interdire suppression buckets
            "Resource": "*"
        }
    ]
}

# [IDEE] Explication:
# - Accès complet S3 SAUF la suppression de buckets
# - "Deny" est TOUJOURS prioritaire sur "Allow" (sécurité)
# - Même si autre policy dit "Allow", le "Deny" gagne

# === ATTACHER POLICIES (DONNER PERMISSIONS) ===

# [NOTE] EXPLICATION: Créer policy/groupe/user ne donne PAS automatiquement permissions
# Vous devez "attacher" (lier) la policy à l'utilisateur ou groupe

# [OBJECTIF] 3 FAÇONS D'ATTACHER POLICIES:
# 1. À un utilisateur directement
# 2. À un groupe (tous les membres héritent)
# 3. À un role (pour services AWS)

# Attacher policy managée AWS à un utilisateur
aws iam attach-user-policy \
  --user-name john \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# [IDEE] Ce que ça fait:
# - john peut maintenant LIRE tous les buckets S3
# - john ne peut PAS écrire/supprimer (ReadOnly)
# - La policy reste attachée jusqu'à ce que vous la détachiez

# Attacher policy managée à un groupe
aws iam attach-group-policy \
  --group-name Developers \
  --policy-arn arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess

# [IDEE] Ce que ça fait:
# - TOUS les membres du groupe Developers peuvent voir les instances EC2
# - Utile pour donner permissions à toute une équipe

# Créer votre propre policy custom
aws iam create-policy \
  --policy-name MyS3ReadPolicy \
  --policy-document file://policy.json

# [IDEE] policy.json doit être dans le dossier actuel
# Output inclut le PolicyArn (vous en aurez besoin après)

# Output:
# {
#     "Policy": {
#         "PolicyName": "MyS3ReadPolicy",
#         "PolicyId": "ANPAJEXAMPLE",
#         "Arn": "arn:aws:iam::123456789012:policy/MyS3ReadPolicy",
#         ...
#     }
# }

# Attacher votre policy custom
aws iam attach-user-policy \
  --user-name john \
  --policy-arn arn:aws:iam::123456789012:policy/MyS3ReadPolicy

# Détacher policy d'un utilisateur
aws iam detach-user-policy \
  --user-name john \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# [IDEE] john perd immédiatement cette permission

# Lister toutes les policies attachées à un utilisateur
aws iam list-attached-user-policies --user-name john

# Output:
# {
#     "AttachedPolicies": [
#         {
#             "PolicyName": "AmazonS3ReadOnlyAccess",
#             "PolicyArn": "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
#         },
#         {
#             "PolicyName": "MyS3ReadPolicy",
#             "PolicyArn": "arn:aws:iam::123456789012:policy/MyS3ReadPolicy"
#         }
#     ]
# }

# [IDEE] Voir toutes les permissions de john

# Lister policies attachées à un groupe
aws iam list-attached-group-policies --group-name Developers

# === INLINE POLICIES (MÉTHODE ALTERNATIVE - MOINS RECOMMANDÉE) ===

# [NOTE] EXPLICATION: 2 types de policies:
# 1. Managed Policies (ci-dessus) - Réutilisables, recommandées
# 2. Inline Policies - Attachées directement, uniques à cet utilisateur/groupe

# [IDEE] Différence:
# - Managed: Créer UNE policy, l'attacher à plusieurs users/groups
# - Inline: Policy existe UNIQUEMENT pour cet user/group spécifique

# Quand utiliser Inline?
# - Relation 1:1 stricte (policy pour UN seul user)
# - Permissions très spécifiques qui ne seront jamais réutilisées

# Attacher inline policy directement à utilisateur
aws iam put-user-policy \
  --user-name john \
  --policy-name S3Access \
  --policy-document file://policy.json

# [IDEE] Ce que ça fait:
# - Crée ET attache la policy en une seule commande
# - La policy existe seulement pour john
# - Si vous supprimez john, la policy disparaît aussi

# Lister inline policies d'un utilisateur
aws iam list-user-policies --user-name john

# Output:
# {
#     "PolicyNames": [
#         "S3Access"
#     ]
# }

# Obtenir contenu d'une inline policy
aws iam get-user-policy \
  --user-name john \
  --policy-name S3Access

# Supprimer inline policy
aws iam delete-user-policy \
  --user-name john \
  --policy-name S3Access

# [OBJECTIF] MANAGED vs INLINE - Quelle méthode choisir?

# [OK] Utiliser MANAGED Policies (recommandé) quand:
# - Policy sera utilisée par plusieurs users/groups
# - Vous voulez réutiliser
# - Plus facile à gérer à grande échelle

# [OK] Utiliser INLINE Policies quand:
# - Relation 1:1 stricte
# - Permission très spécifique à UN utilisateur
# - Vous voulez que la policy disparaisse avec l'utilisateur

# === ROLES (POUR SERVICES AWS) ===

# [NOTE] EXPLICATION: Role ≠ User!
# 
# User = Pour une PERSONNE
# - A des credentials (clés d'accès)
# - Connexion longue durée
# 
# Role = Pour un SERVICE AWS
# - PAS de credentials permanents
# - Le service "assume" le role temporairement
# - Credentials temporaires (expiration automatique)

# [IDEE] Cas d'usage typiques:
# - Instance EC2 doit accéder à S3
# - Fonction Lambda doit écrire dans DynamoDB
# - Service ECS doit lire secrets dans Secrets Manager

# [OBJECTIF] POURQUOI UTILISER ROLES AU LIEU DE CLÉS?

# [X] Mauvaise méthode (DANGEREUX):
# 1. Créer clés d'accès AWS
# 2. Les mettre dans le code de l'app sur EC2
# Problèmes:
# - Clés hardcodées dans code
# - Risque de fuite si code est publié
# - Rotation difficile
# - Moins sécurisé

# [OK] Bonne méthode (SÉCURISÉ):
# 1. Créer IAM Role avec permissions S3
# 2. Attacher role à EC2
# 3. Code sur EC2 utilise automatiquement le role
# Avantages:
# - Pas de clés dans le code
# - Credentials temporaires auto-renouvelés
# - Rotation automatique
# - Beaucoup plus sécurisé

# === CRÉER UN ROLE (ÉTAPE PAR ÉTAPE) ===

# Étape 1: Créer Trust Policy
# = Document qui dit "QUI peut utiliser ce role"

# trust-policy.json - EC2 peut utiliser ce role
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "ec2.amazonaws.com"    # Le service EC2
            },
            "Action": "sts:AssumeRole"            # Action "assumer le role"
        }
    ]
}

# [IDEE] Explication:
# - "Principal": {"Service": "ec2.amazonaws.com"} = Le service EC2
# - "sts:AssumeRole" = Action d'assumer (prendre) le role
# - Résultat: Les instances EC2 peuvent utiliser ce role

# Étape 2: Créer le role
aws iam create-role \
  --role-name EC2-S3-Access-Role \
  --assume-role-policy-document file://trust-policy.json

# Output:
# {
#     "Role": {
#         "RoleName": "EC2-S3-Access-Role",
#         "RoleId": "AROAJEXAMPLE",
#         "Arn": "arn:aws:iam::123456789012:role/EC2-S3-Access-Role",
#         "AssumeRolePolicyDocument": {...},
#         "CreateDate": "2024-01-15T10:30:00Z"
#     }
# }

# [IDEE] À ce stade, le role existe mais n'a AUCUNE permission!

# Étape 3: Attacher permissions au role
aws iam attach-role-policy \
  --role-name EC2-S3-Access-Role \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# [IDEE] Maintenant le role peut lire S3
# Toute instance EC2 avec ce role pourra lire S3

# Lister tous les roles
aws iam list-roles

# Obtenir info sur un role spécifique
aws iam get-role --role-name EC2-S3-Access-Role

# Lister policies attachées à un role
aws iam list-attached-role-policies --role-name EC2-S3-Access-Role

# Output:
# {
#     "AttachedPolicies": [
#         {
#             "PolicyName": "AmazonS3ReadOnlyAccess",
#             "PolicyArn": "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess"
#         }
#     ]
# }

# Supprimer role (détacher policies d'abord!)
# Étape 1: Détacher toutes les policies
aws iam detach-role-policy \
  --role-name EC2-S3-Access-Role \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess

# Étape 2: Supprimer le role
aws iam delete-role --role-name EC2-S3-Access-Role

# === INSTANCE PROFILES (POUR ATTACHER ROLE À EC2) ===

# [NOTE] EXPLICATION: Pour utiliser un role avec EC2, il faut un "Instance Profile"
# Instance Profile = conteneur qui contient le role
# C'est une étape technique nécessaire pour EC2

# [IDEE] Analogie: Role = badge, Instance Profile = porte-badge

# Étape 1: Créer instance profile
aws iam create-instance-profile \
  --instance-profile-name EC2-S3-Access-Profile

# Output:
# {
#     "InstanceProfile": {
#         "InstanceProfileName": "EC2-S3-Access-Profile",
#         "InstanceProfileId": "AIPAJEXAMPLE",
#         "Arn": "arn:aws:iam::123456789012:instance-profile/EC2-S3-Access-Profile",
#         "CreateDate": "2024-01-15T10:30:00Z",
#         "Roles": []                              # Vide pour l'instant
#     }
# }

# Étape 2: Ajouter role à instance profile
aws iam add-role-to-instance-profile \
  --instance-profile-name EC2-S3-Access-Profile \
  --role-name EC2-S3-Access-Role

# [IDEE] Maintenant l'instance profile contient le role

# Lister instance profiles
aws iam list-instance-profiles

# Étape 3: Attacher instance profile à instance EC2
aws ec2 associate-iam-instance-profile \
  --instance-id i-1234567890abcdef0 \
  --iam-instance-profile Name=EC2-S3-Access-Profile

# [IDEE] Ce que ça fait:
# - L'instance EC2 peut maintenant utiliser le role
# - Le code sur l'instance peut accéder à S3 sans clés hardcodées
# - AWS SDK détecte automatiquement le role

# Exemple de code Python sur l'instance EC2:
# import boto3
# s3 = boto3.client('s3')  # Pas besoin de credentials!
# s3.list_buckets()        # Fonctionne grâce au role

# Retirer instance profile d'une instance
aws ec2 disassociate-iam-instance-profile \
  --association-id <ASSOCIATION_ID>

# === TRUST POLICIES AVANCÉES ===

# trust-policy-lambda.json - Pour Lambda
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "lambda.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

# trust-policy-multiple-services.json - Plusieurs services
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "ec2.amazonaws.com",
                    "lambda.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

# trust-policy-cross-account.json - Autre compte AWS peut utiliser
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::999999999999:root"  # Autre compte
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

# [IDEE] Cas d'usage: Donner accès à partenaire/client à certaines ressources

# === PASSWORD POLICY (POLITIQUE DE MOTS DE PASSE) ===

# [NOTE] EXPLICATION: Définir règles de sécurité pour mots de passe des utilisateurs IAM
# Important pour sécurité si utilisateurs se connectent via Console Web

# Définir politique stricte de mots de passe
aws iam update-account-password-policy \
  --minimum-password-length 12 \
  --require-symbols \
  --require-numbers \
  --require-uppercase-characters \
  --require-lowercase-characters \
  --allow-users-to-change-password \
  --max-password-age 90 \
  --password-reuse-prevention 5

# [IDEE] Explication de chaque paramètre:
# --minimum-password-length 12
#   -> Minimum 12 caractères (plus sécurisé)
#
# --require-symbols
#   -> Doit contenir caractères spéciaux (!@#$%^&*)
#
# --require-numbers
#   -> Doit contenir au moins un chiffre (0-9)
#
# --require-uppercase-characters
#   -> Doit contenir au moins une majuscule (A-Z)
#
# --require-lowercase-characters
#   -> Doit contenir au moins une minuscule (a-z)
#
# --allow-users-to-change-password
#   -> Utilisateurs peuvent changer leur propre mot de passe
#
# --max-password-age 90
#   -> Mot de passe expire après 90 jours (rotation forcée)
#
# --password-reuse-prevention 5
#   -> Ne peut pas réutiliser les 5 derniers mots de passe

# Obtenir politique actuelle
aws iam get-account-password-policy

# Output:
# {
#     "PasswordPolicy": {
#         "MinimumPasswordLength": 12,
#         "RequireSymbols": true,
#         "RequireNumbers": true,
#         "RequireUppercaseCharacters": true,
#         "RequireLowercaseCharacters": true,
#         "AllowUsersToChangePassword": true,
#         "MaxPasswordAge": 90,
#         "PasswordReusePrevention": 5
#     }
# }

# Supprimer politique (revenir aux défauts)
aws iam delete-account-password-policy

# === MFA (MULTI-FACTOR AUTHENTICATION) ===

# [NOTE] EXPLICATION: MFA = Authentification à deux facteurs
# Même si quelqu'un vole votre mot de passe, il ne peut pas se connecter sans le 2ème facteur

# [OBJECTIF] TYPES DE MFA:
# 1. Virtual MFA (app smartphone) - RECOMMANDÉ pour débuter
#    Apps: Google Authenticator, Authy, Microsoft Authenticator
#
# 2. Hardware MFA (clé physique USB)
#    Exemple: YubiKey
#
# 3. SMS (moins sécurisé, pas recommandé)

# [IDEE] Comment ça marche:
# 1. Vous activez MFA avec une app (Google Authenticator par exemple)
# 2. L'app génère un code qui change toutes les 30 secondes
# 3. À la connexion: mot de passe + code de l'app

# Activer MFA pour un utilisateur (nécessite configuration préalable)
aws iam enable-mfa-device \
  --user-name john \
  --serial-number arn:aws:iam::123456789012:mfa/john \
  --authentication-code-1 123456 \
  --authentication-code-2 789012

# [IDEE] Explication:
# --serial-number: Identifiant du device MFA
# --authentication-code-1: Premier code de l'app
# --authentication-code-2: Code suivant (30 sec après)
# Pourquoi 2 codes? Pour prouver que l'app fonctionne correctement

# [ATTENTION] IMPORTANT: Cette commande CLI est complexe pour débutants
# Il est plus facile d'activer MFA via la Console Web:
# 1. AWS Console -> IAM -> Users -> john
# 2. Security credentials tab
# 3. Assign MFA device
# 4. Scanner QR code avec votre app
# 5. Entrer 2 codes consécutifs

# Lister MFA devices d'un utilisateur
aws iam list-mfa-devices --user-name john

# Output:
# {
#     "MFADevices": [
#         {
#             "UserName": "john",
#             "SerialNumber": "arn:aws:iam::123456789012:mfa/john",
#             "EnableDate": "2024-01-15T10:30:00Z"
#         }
#     ]
# }

# Désactiver MFA
aws iam deactivate-mfa-device \
  --user-name john \
  --serial-number arn:aws:iam::123456789012:mfa/john

# [IDEE] Utiliser si: device perdu, changement de téléphone

# [VERROUILLE] POURQUOI MFA EST CRITIQUE:
# [OK] Même si mot de passe est volé, compte reste protégé
# [OK] Protection contre phishing
# [OK] Requis pour accès root account (obligatoire!)
# [OK] Gratuit et facile à mettre en place

# === ACCOUNT ALIAS (ALIAS DE COMPTE) ===

# [NOTE] EXPLICATION: Par défaut, URL de connexion AWS = numéro de compte
# https://123456789012.signin.aws.amazon.com/console
# C'est difficile à retenir!

# Avec un alias, vous pouvez avoir:
# https://mon-entreprise.signin.aws.amazon.com/console

# Créer alias (nom unique dans tout AWS)
aws iam create-account-alias --account-alias mon-entreprise

# [IDEE] Choisir un nom:
# - Unique dans tout AWS (comme nom de domaine)
# - Minuscules, chiffres, tirets seulement
# - Entre 3 et 63 caractères

# Lister alias actuel
aws iam list-account-aliases

# Output:
# {
#     "AccountAliases": [
#         "mon-entreprise"
#     ]
# }

# Supprimer alias
aws iam delete-account-alias --account-alias mon-entreprise

# [IDEE] Revient à l'URL avec numéro de compte

# === RÉSUMÉ: WORKFLOW TYPIQUE POUR NOUVEAU UTILISATEUR ===

# [OBJECTIF] SCÉNARIO: Ajouter Marie, nouvelle développeuse

# Étape 1: Créer utilisateur
aws iam create-user --user-name marie

# Étape 2: Créer clés d'accès
aws iam create-access-key --user-name marie
# [ATTENTION] Sauvegarder les clés et les donner à Marie

# Étape 3: Ajouter à groupe Developers (qui a déjà des permissions)
aws iam add-user-to-group --user-name marie --group-name Developers

# Étape 4: (Optionnel) Permissions additionnelles spécifiques
aws iam attach-user-policy \
  --user-name marie \
  --policy-arn arn:aws:iam::aws:policy/IAMReadOnlyAccess

# Étape 5: (Optionnel) Créer mot de passe pour Console Web
aws iam create-login-profile \
  --user-name marie \
  --password TempPassword123! \
  --password-reset-required

# [IDEE] --password-reset-required force Marie à changer le mot de passe
# à sa première connexion (bonne pratique)

# Étape 6: Informer Marie
# - Clés d'accès (pour AWS CLI)
# - URL de connexion: https://mon-entreprise.signin.aws.amazon.com/console
# - Nom d'utilisateur: marie
# - Mot de passe temporaire: TempPassword123!
# - Lui dire d'activer MFA!

# === RÉSUMÉ: WORKFLOW POUR INSTANCE EC2 + S3 ===

# [OBJECTIF] SCÉNARIO: Instance EC2 doit lire et écrire dans S3

# Étape 1: Créer trust policy
cat > trust-policy.json << EOF
{
    "Version": "2012-10-17",
    "Statement": [{
        "Effect": "Allow",
        "Principal": {"Service": "ec2.amazonaws.com"},
        "Action": "sts:AssumeRole"
    }]
}
EOF

# Étape 2: Créer role
aws iam create-role \
  --role-name EC2-S3-FullAccess-Role \
  --assume-role-policy-document file://trust-policy.json

# Étape 3: Attacher permissions S3
aws iam attach-role-policy \
  --role-name EC2-S3-FullAccess-Role \
  --policy-arn arn:aws:iam::aws:policy/AmazonS3FullAccess

# Étape 4: Créer instance profile
aws iam create-instance-profile \
  --instance-profile-name EC2-S3-FullAccess-Profile

# Étape 5: Ajouter role à instance profile
aws iam add-role-to-instance-profile \
  --instance-profile-name EC2-S3-FullAccess-Profile \
  --role-name EC2-S3-FullAccess-Role

# Étape 6: Lancer EC2 avec le profile (ou attacher à existante)
# Voir section EC2 pour commande complète

# Étape 7: Dans votre code sur EC2, utiliser boto3 (Python)
# import boto3
# s3 = boto3.client('s3')  # Credentials automatiques!
# s3.list_buckets()

# === BONNES PRATIQUES IAM (RÈGLES D'OR) ===

# [VERROUILLE] SÉCURITÉ:

# 1⃣ [X] NE JAMAIS utiliser Root Account pour opérations quotidiennes
#    [OK] Créer utilisateur IAM admin et utiliser celui-là
#
# 2⃣ [OK] TOUJOURS activer MFA sur Root Account (OBLIGATOIRE!)
#    [OK] Activer MFA sur tous les utilisateurs avec accès important
#
# 3⃣ [OK] TOUJOURS appliquer principe du moindre privilège
#    Donner UNIQUEMENT les permissions nécessaires, pas plus
#    Exemple: Si besoin lecture S3 -> AmazonS3ReadOnlyAccess
#             PAS AmazonS3FullAccess
#
# 4⃣ [OK] Utiliser Groups pour gérer permissions (pas directement sur users)
#    Plus facile à maintenir quand équipe grandit
#
# 5⃣ [OK] Utiliser Roles pour EC2/Lambda (PAS de clés hardcodées)
#    Plus sécurisé, rotation automatique
#
# 6⃣ [OK] Rotation régulière des clés d'accès (tous les 90 jours)
#    Supprimer clés inutilisées
#
# 7⃣ [OK] Activer CloudTrail pour audit (voir qui fait quoi)
#
# 8⃣ [X] NE JAMAIS partager clés d'accès entre utilisateurs
#    Une personne = un utilisateur IAM
#
# 9⃣ [X] NE JAMAIS commiter credentials dans Git
#    Utiliser .gitignore pour ~/.aws/credentials
#
# [10] [OK] Utiliser AWS Organizations pour multi-comptes
#    Séparer: dev, staging, prod

# [GRAPHIQUE] ORGANISATION:

# [OK] Convention de nommage claire:
#    Users: prenom.nom (marie.dupont)
#    Groups: Par fonction (Developers, Admins, ReadOnly)
#    Roles: Par service-usage (EC2-S3-Access, Lambda-DynamoDB-Write)
#
# [OK] Tags sur toutes les ressources:
#    Environment: production/staging/dev
#    Team: backend/frontend/data
#    Owner: email@example.com
#
# [OK] Documentation des policies custom:
#    Description claire de ce que fait la policy
#    Pourquoi elle existe
#    Qui l'utilise

# [OBJECTIF] STRUCTURE D'ÉQUIPE TYPIQUE:

# Groupe "Admins"
# - Permissions: AdministratorAccess (tout)
# - Membres: CTO, Lead DevOps
# - MFA: OBLIGATOIRE

# Groupe "Developers"
# - Permissions: EC2, S3, RDS, Lambda (lecture + écriture)
# - Permissions: Billing (lecture seule)
# - Membres: Tous les développeurs
# - MFA: Recommandé

# Groupe "ReadOnly"
# - Permissions: Tout en lecture seule
# - Membres: Stagiaires, nouveaux arrivants
# - MFA: Recommandé

# Groupe "Billing"
# - Permissions: Voir coûts et factures
# - Membres: Finance, managers
# - MFA: Recommandé

# === VÉRIFIER PERMISSIONS (TROUBLESHOOTING) ===

# [NOTE] EXPLICATION: Comment savoir si un utilisateur a une permission spécifique?

# Méthode 1: Lister toutes les policies de l'utilisateur
aws iam list-attached-user-policies --user-name marie
aws iam list-user-policies --user-name marie  # Inline policies

# Méthode 2: Lister groupes de l'utilisateur
aws iam list-groups-for-user --user-name marie

# Méthode 3: Voir policies des groupes
aws iam list-attached-group-policies --group-name Developers

# Méthode 4: IAM Policy Simulator (via Console Web)
# https://policysim.aws.amazon.com/
# Permet de tester: "Est-ce que marie peut faire s3:PutObject?"

# [RECHERCHE] COMPRENDRE LES ERREURS "ACCESS DENIED":

# Erreur: "User: arn:aws:iam::123456789012:user/marie is not authorized
#          to perform: s3:PutObject on resource: arn:aws:s3:::my-bucket/file.txt"

# [IDEE] Explication:
# - marie essaie de faire: s3:PutObject (upload fichier)
# - Sur: my-bucket/file.txt
# - Résultat: Refusé (pas la permission)

# Solutions possibles:
# 1. Attacher AmazonS3FullAccess à marie ou son groupe
# 2. Créer policy custom pour ce bucket spécifique
# 3. Vérifier si Deny bloque (Deny > Allow)
# 4. Vérifier bucket policy (permissions côté bucket)

# === COMMANDES UTILES POUR DÉBOGAGE ===

# Qui suis-je? (Quel utilisateur/role j'utilise)
aws sts get-caller-identity

# Output:
# {
#     "UserId": "AIDAJEXAMPLE",
#     "Account": "123456789012",
#     "Arn": "arn:aws:iam::123456789012:user/marie"
# }

# [IDEE] Si vous voyez "root" dans l'Arn, vous utilisez le compte root!
# C'EST DANGEREUX! Créez un utilisateur IAM.

# Lister TOUTES les permissions d'un utilisateur (complexe mais complet)
# Nécessite d'aller chercher:
# 1. Policies attachées directement
# 2. Policies inline
# 3. Policies des groupes
# 4. Policies des groupes inline

# Script pour voir toutes les permissions de marie:
echo "=== Policies attachées directement ==="
aws iam list-attached-user-policies --user-name marie

echo "=== Inline policies ==="
aws iam list-user-policies --user-name marie

echo "=== Groupes ==="
aws iam list-groups-for-user --user-name marie

echo "=== Policies des groupes ==="
for group in $(aws iam list-groups-for-user --user-name marie --query 'Groups[*].GroupName' --output text); do
  echo "Groupe: $group"
  aws iam list-attached-group-policies --group-name $group
done"Elastic Beanstalk = PaaS pour déployer applications sans gérer infrastructure
Supporte: Node.js, Python, Ruby, Java, PHP, .NET, Go, Docker


# Fichier: python_cheats/cheatsheets/EC2.txt
# Cheatsheet AWS EC2 - Guide Complet pour Débutants


═══════════════════════════════════════════════════════════════════════════════
[OK] EC2 (ELASTIC COMPUTE CLOUD) - C'EST QUOI?
═══════════════════════════════════════════════════════════════════════════════

EC2 = "Ordinateurs virtuels dans le cloud"

Imaginez que vous pouvez louer des ordinateurs sur internet:
- Au lieu d'acheter un serveur physique (cher, lourd, à maintenir)
- Vous payez pour utiliser un ordinateur virtuel chez AWS
- Vous pouvez le démarrer, l'arrêter, le modifier quand vous voulez
- Parfait pour héberger des sites, des applications, des APIs, etc.

ANALOGIE: C'est comme louer une chambre d'hôtel
- Vous n'êtes pas propriétaire du bâtiment
- Vous payez par nuit d'utilisation
- Vous pouvez partir quand vous voulez
- L'hôtel gère la maintenance

TERMINOLOGIE DE BASE:
- Instance = 1 ordinateur virtuel (c'est ce que vous louerez)
- AMI = Image/Système d'exploitation (Ubuntu, Amazon Linux, Windows, etc.)
- Instance type = Puissance de l'ordi (t2.micro = petit, m5.large = plus gros)
- Region = Localisation géographique (us-east-1, eu-west-1, etc.)
- Security Group = Pare-feu (contrôle qui peut se connecter)
- Key Pair = Clé SSH (pour se connecter en SSH sans mot de passe)
- Elastic IP = Adresse IP fixe (IP ne change pas si vous redémarrez)


═══════════════════════════════════════════════════════════════════════════════
[OK] INSTALLATION & CONFIGURATION PRÉALABLE
═══════════════════════════════════════════════════════════════════════════════

# 1. Créer compte AWS gratuit
# https://aws.amazon.com/fr/free/
# - 12 mois gratuits (certains services)
# - t2.micro gratuit pendant 12 mois
# - Besoin d'une carte bancaire (sera facturée si vous dépassez)

# 2. Installer AWS CLI (outil en ligne de commande)
# Mac (Homebrew)
brew install awscli

# Linux
sudo apt-get install awscli

# Windows
# Télécharger: https://aws.amazon.com/fr/cli/
# Ou avec pip
pip install awscli

# 3. Configurer AWS CLI
aws configure
# Vous devez fournir:
# AWS Access Key ID: Votre clé (voir dans AWS Console > IAM)
# AWS Secret Access Key: Votre secret (voir dans AWS Console > IAM)
# Default region name: us-east-1 (ou votre région préférée)
# Default output format: json (ou table)

# 4. Vérifier que tout fonctionne
aws sts get-caller-identity
# Affiche vos informations AWS si configuré correctement

# 5. Générer Access Keys si vous n'en avez pas
# Aller dans: AWS Console > IAM > Users > [votre user] > Security credentials
# Créer une nouvelle "Access Key"


═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 1 - CRÉER UNE CLÉ SSH (KEY PAIR)
═══════════════════════════════════════════════════════════════════════════════

POURQUOI? Pour pouvoir se connecter à votre instance EC2 sans mot de passe.
C'est comme avoir une clé pour accéder à votre serveur.

# Créer une paire de clés (clé privée + clé publique)
aws ec2 create-key-pair \
  --key-name my-key-pair \
  --query 'KeyMaterial' \
  --output text > my-key-pair.pem

# EXPLICATION DÉTAILLÉE:
# "aws" = Programme AWS CLI
# "ec2" = Service EC2 (Elastic Compute Cloud)
# "create-key-pair" = Créer une paire de clés
# "--key-name my-key-pair" = Donner un nom à la clé (vous pouvez changer "my-key-pair")
# "--query 'KeyMaterial'" = Extraire seulement la clé (pas le reste des infos)
# "--output text" = Afficher en texte simple (pas en JSON)
# "> my-key-pair.pem" = Écrire dans un fichier (> = redirection)
#
# Résumé: "Créer une clé appelée 'my-key-pair', extraire le texte, l'écrire dans un fichier"

# Résultat: Un fichier "my-key-pair.pem" est créé dans votre dossier courant
# Ce fichier contient votre clé privée (À GARDER SECRET!)
# Ne le mettez JAMAIS sur GitHub ou en ligne!
# C'est comme la clé de votre maison - tout le monde pourrait entrer si vous la perdez

# IMPORTANT: Fixer les permissions sur Linux/Mac
chmod 400 my-key-pair.pem

# EXPLICATION DE CHMOD:
# "chmod" = Change Mode (modification des permissions)
# "400" = Signifie:
#   - Propriétaire (vous): 4 = lire (r) seulement
#   - Groupe: 0 = aucune permission
#   - Autres: 0 = aucune permission
#
# En clair: Seul vous pouvez lire ce fichier, personne d'autre
# C'est obligatoire! SSH refusera la clé si les permissions sont mauvaises

# Pour vérifier les permissions:
ls -la my-key-pair.pem
# Résultat: "-r--------" signifie que c'est correct

# Sur Windows: Clic droit > Propriétés > Sécurité
# Enlever tous les droits sauf pour votre utilisateur
# Ou en PowerShell:
# icacls my-key-pair.pem /inheritance:r /grant:r "$($env:USERNAME):(F)"

# Lister vos clés
aws ec2 describe-key-pairs

# Exemple de résultat:
# {
#   "KeyPairs": [
#     {"KeyName": "my-key-pair", "KeyFingerprint": "ab:cd:ef:..."}
#   ]
# }

# Supprimer une clé (attention: irréversible!)
aws ec2 delete-key-pair --key-name my-key-pair

# CONSEIL: Gardez votre fichier .pem en sécurité!
# Créez une sauvegarde à plusieurs endroits


═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 2 - CRÉER UN SECURITY GROUP (PARE-FEU)
═══════════════════════════════════════════════════════════════════════════════

POURQUOI? Pour contrôler qui peut se connecter à votre instance.
C'est le pare-feu qui décide: SSH? HTTP? HTTPS? Qui?

CONCEPT: Vous définissez des RÈGLES d'entrée (Ingress)
- SSH (port 22) = se connecter en ligne de commande
- HTTP (port 80) = voir votre site web
- HTTPS (port 443) = voir votre site web en sécurisé
- Etc.

# Créer un security group
aws ec2 create-security-group \
  --group-name my-sg \
  --description "Mon premier security group" \
  --vpc-id vpc-1a2b3c4d

# Résultat: {"GroupId": "sg-0123456789abcdef0"}
# Gardez cet ID, vous en aurez besoin!

# === AJOUTER UNE RÈGLE: SSH depuis votre ordinateur ===

# D'abord: connaître votre IP publique
# Allez sur: https://www.whatismyipaddress.com/
# Ou: curl https://ifconfig.me

# Autoriser SSH SEULEMENT depuis votre IP (SÉCURISÉ!)
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 22 \
  --cidr 203.0.113.25/32
# Remplacez 203.0.113.25 par votre IP publique

# === AJOUTER UNE RÈGLE: HTTP depuis partout ===

# Si vous voulez un site web public
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 80 \
  --cidr 0.0.0.0/0
# 0.0.0.0/0 = "Depuis n'importe où sur Internet"

# === AJOUTER UNE RÈGLE: HTTPS depuis partout ===

# Pour un site web sécurisé
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0

# Lister vos security groups
aws ec2 describe-security-groups

# Lister les règles d'un security group spécifique
aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0

# Exemple de résultat:
# "IpPermissions": [
#   {"IpProtocol": "tcp", "FromPort": 22, "ToPort": 22, "IpRanges": [{"CidrIp": "203.0.113.25/32"}]},
#   {"IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}
# ]

# Supprimer une règle
aws ec2 revoke-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 22 \
  --cidr 0.0.0.0/0

# Supprimer le security group entier
aws ec2 delete-security-group --group-id sg-0123456789abcdef0


═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 3 - CHOISIR UNE IMAGE (AMI)
═══════════════════════════════════════════════════════════════════════════════

POURQUOI? L'image = le système d'exploitation de votre instance.
Ubuntu? Amazon Linux? Windows? Vous choisissez!

AMI = "Amazon Machine Image" = une image pré-configurée prête à démarrer

# === IMAGES COURANTES ===
# Ubuntu 22.04 = Populaire, simple, gratuit
# Amazon Linux 2023 = Optimisé pour AWS, gratuit
# Windows Server = Pour Windows (payant)
# CentOS = Gratuit, serveurs robustes

# Trouver l'ID de l'image Ubuntu 22.04
aws ec2 describe-images \
  --owners 099720109477 \
  --filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" \
  --query 'Images[*].[ImageId,Name,CreationDate]' \
  --output table

# Résultat: Une liste des images Ubuntu disponibles
# Prenez le dernier ID (la plus récente)
# Exemple: ami-0c55b159cbfafe1f0

# Trouver l'ID de l'image Amazon Linux 2023
aws ec2 describe-images \
  --owners amazon \
  --filters "Name=name,Values=al2023-ami-2023*-x86_64" \
  --query 'Images | sort_by(@, &CreationDate) | [-1].[ImageId,Name]'

# CONSEIL POUR DÉBUTANTS: Utilisez Ubuntu 22.04
# C'est le plus facile pour apprendre


═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 4 - CHOISIR UN TYPE D'INSTANCE
═══════════════════════════════════════════════════════════════════════════════

TYPE D'INSTANCE = Puissance de votre ordinateur virtuel

C'est comme choisir:
- t2.micro = Petit bureau (gratuit 12 mois, 1 vCPU, 1 GB RAM)
- t2.small = Bureau normal (1 vCPU, 2 GB RAM)
- t2.medium = Bon ordinateur (2 vCPU, 4 GB RAM)
- m5.large = Serveur puissant (2 vCPU, 8 GB RAM)
- c5.large = Pour calculs lourds (2 vCPU, 4 GB RAM)

POUR DÉBUTANTS: t2.micro (gratuit pendant 12 mois!)

# Voir les types disponibles
aws ec2 describe-instance-types \
  --filters "Name=instance-type,Values=t2.*" \
  --query 'InstanceTypes[*].[InstanceType,VCpuInfo.DefaultVCpus,MemoryInfo.SizeInMiB]' \
  --output table

# Résultat: Liste des types t2 avec vCPU et RAM


═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 5 - LANCER VOTRE PREMIÈRE INSTANCE
═══════════════════════════════════════════════════════════════════════════════

Vous avez maintenant tout ce qu'il faut:
1. Une clé SSH (my-key-pair.pem)
2. Un security group (sg-0123456789abcdef0)
3. Une image (ami-0c55b159cbfafe1f0)
4. Un type d'instance (t2.micro)

LANÇONS!

# Commande pour lancer une instance
aws ec2 run-instances \
  --image-id ami-0c55b159cbfafe1f0 \
  --instance-type t2.micro \
  --key-name my-key-pair \
  --security-group-ids sg-0123456789abcdef0 \
  --count 1 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=MyFirstServer}]'

# Explication:
# --image-id = L'OS que vous avez choisi
# --instance-type = t2.micro (gratuit)
# --key-name = La clé SSH que vous avez créée
# --security-group-ids = Le pare-feu que vous avez configuré
# --count = 1 instance
# --tag-specifications = Nommer votre instance "MyFirstServer"

# Résultat: Vous voyez les détails de votre instance
# Important: Notez l'InstanceId = i-1234567890abcdef0
# Vous en aurez besoin pour gérer votre instance!

# Résultat: Vous voyez aussi PublicIpAddress = 203.0.113.45
# C'est l'IP pour accéder à votre instance par SSH


═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 6 - SE CONNECTER À VOTRE INSTANCE
═══════════════════════════════════════════════════════════════════════════════

OBJECTIF: Vous connecter à votre serveur comme si vous êtiez dedans

# Sur Linux/Mac, ouvrir le terminal et:

ssh -i my-key-pair.pem ubuntu@203.0.113.45

# Explication:
# ssh = "Secure Shell" (connexion sécurisée)
# -i my-key-pair.pem = Utiliser votre clé privée
# ubuntu = Nom d'utilisateur (Ubuntu par défaut)
# 203.0.113.45 = IP publique de votre instance

# Premier message:
# "Are you sure you want to continue connecting? (yes/no)"
# Répondez: yes

# Vous êtes connecté! Vous êtes maintenant DANS votre serveur
# Vous pouvez taper des commandes comme sur votre ordinateur

# Exemples:
pwd                 # Voir le dossier actuel
ls                  # Lister fichiers
uname -a            # Info du système
df -h               # Espace disque


# === SUR WINDOWS ===

# Méthode 1: Avec PuTTY (GUI)
# 1. Télécharger PuTTY et PuTTYgen
# 2. Ouvrir PuTTYgen
# 3. Charger my-key-pair.pem
# 4. Exporter en .ppk
# 5. Ouvrir PuTTY
# 6. Hostname: ubuntu@203.0.113.45
# 7. SSH > Auth > .ppk
# 8. Connect

# Méthode 2: Avec Windows Terminal/PowerShell
# Si vous avez SSH d'installé (Windows 10+)
ssh -i my-key-pair.pem ubuntu@203.0.113.45

# Méthode 3: Avec Git Bash
# Git Bash inclut SSH
ssh -i my-key-pair.pem ubuntu@203.0.113.45

# Pour quitter votre session SSH:
exit


═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 7 - GÉRER VOTRE INSTANCE
═══════════════════════════════════════════════════════════════════════════════

Maintenant que votre instance est lancée, vous pouvez:

# Lister TOUTES vos instances
aws ec2 describe-instances

# Résultat: Beaucoup d'informations (JSON)

# Lister en format lisible
aws ec2 describe-instances \
  --query 'Reservations[*].Instances[*].[InstanceId,State.Name,InstanceType,PublicIpAddress,Tags[?Key==`Name`].Value|[0]]' \
  --output table

# Résultat:
# | InstanceId | State | InstanceType | PublicIpAddress | Name |
# | i-123456 | running | t2.micro | 203.0.113.45 | MyFirstServer |

# === ARRÊTER L'INSTANCE ===
# (Vous continuez à payer, mais elle est arrêtée)
aws ec2 stop-instances --instance-ids i-1234567890abcdef0

# === DÉMARRER L'INSTANCE ===
aws ec2 start-instances --instance-ids i-1234567890abcdef0

# === REDÉMARRER L'INSTANCE ===
aws ec2 reboot-instances --instance-ids i-1234567890abcdef0

# === TERMINER L'INSTANCE ===
# (SUPPRESSION DÉFINITIVE! Vous arrêtez de payer)
# Attention: C'est irréversible!
aws ec2 terminate-instances --instance-ids i-1234567890abcdef0

# === AJOUTER DES TAGS (ÉTIQUETTES) ===
# Pour mieux identifier vos instances
aws ec2 create-tags \
  --resources i-1234567890abcdef0 \
  --tags Key=Environment,Value=Development Key=Team,Value=Backend

# === OBTENIR L'IP PUBLIQUE ===
aws ec2 describe-instances \
  --instance-ids i-1234567890abcdef0 \
  --query 'Reservations[0].Instances[0].PublicIpAddress' \
  --output text

# Résultat: 203.0.113.45


═══════════════════════════════════════════════════════════════════════════════
[OK] BONUS - ELASTIC IP (IP FIXE)
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME: Si vous arrêtez puis redémarrez votre instance,
son adresse IP publique change!

SOLUTION: Utiliser une Elastic IP (IP fixe)

# Allouer une Elastic IP
aws ec2 allocate-address --domain vpc

# Résultat: 
# {
#   "AllocationId": "eipalloc-12345678",
#   "PublicIp": "203.0.113.100"
# }

# Associer l'Elastic IP à votre instance
aws ec2 associate-address \
  --instance-id i-1234567890abcdef0 \
  --allocation-id eipalloc-12345678

# Maintenant, votre instance a une IP fixe!
# Même si vous l'arrêtez et la redémarrez,
# l'IP reste la même

# Lister vos Elastic IPs
aws ec2 describe-addresses

# Dissocier
aws ec2 disassociate-address --association-id eipassoc-12345678

# Libérer (vous ne payez plus pour cette IP)
aws ec2 release-address --allocation-id eipalloc-12345678


═══════════════════════════════════════════════════════════════════════════════
[OK] BONUS - USER DATA (SCRIPT AU DÉMARRAGE)
═══════════════════════════════════════════════════════════════════════════════

USER DATA = Un script qui s'exécute automatiquement au démarrage

UTILITÉ: Installer Apache, un serveur web, mettre à jour le système, etc.

# Créer un fichier userdata.sh
# userdata.sh
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Hello from EC2!</h1>" > /var/www/html/index.html

# Explication:
# yum update -y = Mettre à jour le système
# yum install -y httpd = Installer Apache (serveur web)
# systemctl start httpd = Démarrer Apache
# systemctl enable httpd = Apache redémarre automatiquement
# echo = Créer une page HTML

# Lancer instance AVEC user data
aws ec2 run-instances \
  --image-id ami-0c55b159cbfafe1f0 \
  --instance-type t2.micro \
  --key-name my-key-pair \
  --security-group-ids sg-0123456789abcdef0 \
  --user-data file://userdata.sh

# Attendez quelques secondes, puis accédez à votre site:
# http://203.0.113.45/

# Vous devez voir: "Hello from EC2!"

# CONSEIL: Le user data s'exécute une seule fois au PREMIER démarrage


═══════════════════════════════════════════════════════════════════════════════
[OK] BONUS - VOLUMES EBS (STOCKAGE SUPPLÉMENTAIRE)
═══════════════════════════════════════════════════════════════════════════════

EBS = Disque dur virtuel pour votre instance

PAR DÉFAUT: Votre instance a un disque de 8 GB

# Créer un volume EBS supplémentaire
aws ec2 create-volume \
  --availability-zone us-east-1a \
  --size 20 \
  --volume-type gp3 \
  --tag-specifications 'ResourceType=volume,Tags=[{Key=Name,Value=MyExtraStorage}]'

# Résultat: {"VolumeId": "vol-1234567890abcdef0"}

# Attacher le volume à votre instance
aws ec2 attach-volume \
  --volume-id vol-1234567890abcdef0 \
  --instance-id i-1234567890abcdef0 \
  --device /dev/sdf

# Lister vos volumes
aws ec2 describe-volumes

# Créer un snapshot (sauvegarde) du volume
aws ec2 create-snapshot \
  --volume-id vol-1234567890abcdef0 \
  --description "Sauvegarde de mon stockage"

# Lister snapshots
aws ec2 describe-snapshots --owner-ids self

# EXPLICATION:
# "describe-snapshots" = Lister les snapshots
# "--owner-ids self" = Seulement vos snapshots (pas ceux d'autres gens)
#
# "self" = AWS Magic word pour "mes ressources"
#
# Résultat:
# {
#   "Snapshots": [
#     {
#       "SnapshotId": "snap-1234567890abcdef0",
#       "State": "completed",
#       "VolumeSize": 20,
#       "Description": "My snapshot"
#     }
#   ]
# }

# Détacher le volume
aws ec2 detach-volume --volume-id vol-1234567890abcdef0

# Supprimer le volume
aws ec2 delete-volume --volume-id vol-1234567890abcdef0


═══════════════════════════════════════════════════════════════════════════════
[OK] BONNES PRATIQUES POUR DÉBUTER
═══════════════════════════════════════════════════════════════════════════════

# 1. TOUJOURS utiliser des Security Groups restrictifs
# Ne jamais ouvrir SSH (port 22) au monde entier (0.0.0.0/0)
# Utilisez votre IP personnelle

# 2. Garder votre clé SSH en sécurité
# Sauvegardez my-key-pair.pem à plusieurs endroits
# Ne la mettez JAMAIS sur GitHub
# chmod 400 my-key-pair.pem (Linux/Mac)

# 3. Utiliser Elastic IP si vous avez besoin d'une IP fixe
# Sinon, l'IP change à chaque redémarrage

# 4. Terminer les instances quand vous ne les utilisez pas
# Vous payez par heure! Arrêter != Terminer
# Terminer = Suppression définitive (pas de facturation)

# 5. Utiliser des Tags pour identifier vos instances
# Key=Environment,Value=Development
# Key=Project,Value=MyApp
# Key=Owner,Value=YourName

# 6. Documenter vos configurations
# Noteque vous avez utilisé quel AMI, quel type d'instance, etc.

# 7. Créer des snapshots régulièrement
# Pour les volumes EBS importants
# C'est votre assurance contre la perte de données

# 8. Vérifier votre facturation régulièrement
# AWS Console > Billing
# Assurez-vous que vous ne dépassez pas le free tier

# 9. Utiliser CloudWatch pour surveiller
# CPU, réseau, disque, etc.

# 10. Automatiser avec des scripts
# User data pour les configurations
# AWS CLI dans des scripts bash


═══════════════════════════════════════════════════════════════════════════════
[OK] DÉPANNAGE COURANT
═══════════════════════════════════════════════════════════════════════════════

# === PROBLÈME: Impossible de se connecter en SSH ===

# 1. Vérifier que votre clé a les bons droits
chmod 400 my-key-pair.pem

# 2. Vérifier que votre Security Group autorise SSH (port 22)
aws ec2 describe-security-groups --group-ids sg-0123456789abcdef0

# 3. Attendre que l'instance soit complètement lancée
# Cela peut prendre quelques minutes

# 4. Vérifier l'IP publique
aws ec2 describe-instances --instance-ids i-1234567890abcdef0

# 5. Essayer avec -v pour voir les détails
ssh -v -i my-key-pair.pem ubuntu@203.0.113.45

# === PROBLÈME: "Permission denied" avec la clé SSH ===

# Sur Linux/Mac:
chmod 400 my-key-pair.pem

# Sur Windows (PuTTY):
# PuTTYgen > Load > my-key-pair.pem > Save private key as .ppk

# === PROBLÈME: Mon instance s'arrête toute seule ===

# 1. Vérifier les logs
# SSH dans l'instance et regarder les logs du système

# 2. Vérifier les alarmes CloudWatch
# Peut-être que l'instance n'a pas assez de mémoire

# 3. Vérifier que vous n'avez pas terminé l'instance
aws ec2 describe-instances --instance-ids i-1234567890abcdef0

# === PROBLÈME: Je suis facturé mais mon instance ne tourne pas ===

# Vérifier vos instances ARRÊTÉES
aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=stopped"

# Terminer les instances que vous ne voulez pas garder
aws ec2 terminate-instances --instance-ids i-1234567890abcdef0


═══════════════════════════════════════════════════════════════════════════════
[OK] CAS D'USAGE COURANTS
═══════════════════════════════════════════════════════════════════════════════

# === CAS 1: Héberger un site web simple ===

# 1. Lancer instance t2.micro
aws ec2 run-instances \
  --image-id ami-0c55b159cbfafe1f0 \
  --instance-type t2.micro \
  --key-name my-key-pair \
  --security-group-ids sg-0123456789abcdef0 \
  --user-data file://install-apache.sh

# install-apache.sh:
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd

# 2. Associer une Elastic IP
aws ec2 allocate-address --domain vpc
aws ec2 associate-address --instance-id i-xxx --allocation-id eipalloc-xxx

# 3. Accéder au site: http://203.0.113.45/

# === CAS 2: Serveur de développement ===

# 1. t2.micro ou t2.small
# 2. SSH depuis votre IP seulement
# 3. Installer votre stack (Python, Node.js, PostgreSQL, etc.)
# 4. Lancer votre application

# === CAS 3: Base de données ===

# 1. t2.small ou plus (base de données = gourmand en RAM)
# 2. Ajouter un volume EBS supplémentaire pour les données
# 3. Security Group: seulement depuis vos applications
# 4. Créer des snapshots réguliers

# === CAS 4: Load balancer ===

# 1. Lancer plusieurs instances
# 2. Utiliser AWS ELB pour distribuer le trafic
# 3. Chaque instance a sa propre Elastic IP
# 4. L'ELB balance le trafic entre elles


═══════════════════════════════════════════════════════════════════════════════
[OK] TYPES D'INSTANCES POPULAIRES
═══════════════════════════════════════════════════════════════════════════════

GRATUIT 12 MOIS:
- t2.micro: 1 vCPU, 1 GB RAM (parfait pour débuter)

GÉNÉRALISTES:
- t2.small: 1 vCPU, 2 GB RAM
- t2.medium: 2 vCPU, 4 GB RAM
- t3.small: 2 vCPU, 2 GB RAM (plus récent, ~20% plus rapide)
- t3.medium: 2 vCPU, 4 GB RAM

PERFORMANCE GÉNÉRALE:
- m5.large: 2 vCPU, 8 GB RAM
- m5.xlarge: 4 vCPU, 16 GB RAM

OPTIMISÉ CALCUL (CPU lourd):
- c5.large: 2 vCPU, 4 GB RAM
- c5.xlarge: 4 vCPU, 8 GB RAM

OPTIMISÉ MÉMOIRE (Bases de données):
- r5.large: 2 vCPU, 16 GB RAM
- r5.xlarge: 4 vCPU, 32 GB RAM

POUR DÉBUTER: t2.micro (gratuit) ou t2.small (très abordable)


═══════════════════════════════════════════════════════════════════════════════
[OK] FACTURE AWS: CE QUE VOUS ALLEZ PAYER
═══════════════════════════════════════════════════════════════════════════════

GRATUIT 12 MOIS (Si vous respectez le free tier):
- t2.micro: 730 heures/mois = 1 instance gratuite
- 30 GB EBS gp2
- 100 GB sortie données gratuite

APRÈS 12 MOIS OU SI VOUS DÉPASSEZ:
- t2.micro: ~$0.01/heure (~$7/mois si 24h/24)
- t2.small: ~$0.02/heure (~$15/mois si 24h/24)
- t2.medium: ~$0.04/heure (~$30/mois si 24h/24)
- EBS gp3: ~$0.10/GB/mois
- Élastic IP (si inutilisée): $0.005/heure

CONSEIL: Vérifiez votre facturation mensuelle!
AWS Console > Billing Dashboard


═══════════════════════════════════════════════════════════════════════════════
[OK] WORKFLOW COMPLET POUR DÉBUTANT
═══════════════════════════════════════════════════════════════════════════════

ÉTAPE 1: Préparer votre machine locale
# Installer AWS CLI
pip install awscli

# Configurer
aws configure

# Vérifier
aws sts get-caller-identity


ÉTAPE 2: Créer une clé SSH
aws ec2 create-key-pair \
  --key-name my-first-key \
  --query 'KeyMaterial' \
  --output text > my-first-key.pem

chmod 400 my-first-key.pem


ÉTAPE 3: Créer un Security Group
aws ec2 create-security-group \
  --group-name my-first-sg \
  --description "Mon premier security group"

# Résultat: sg-0123456789abcdef0


ÉTAPE 4: Autoriser SSH depuis votre IP
# Trouvez votre IP: https://www.whatismyipaddress.com/
# Supposons: 192.168.1.100

aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 22 \
  --cidr 192.168.1.100/32

# EXPLICATION:
# Vous venez de dire à AWS:
# "Autoriser les connexions SSH (port 22) SEULEMENT depuis ma machine"
# Les hackers ne peuvent pas se connecter depuis ailleurs


ÉTAPE 5: Trouver une image Ubuntu
aws ec2 describe-images \
  --owners 099720109477 \
  --filters "Name=name,Values=ubuntu/images/hvm-ssd/ubuntu-jammy-22.04-amd64-server-*" \
  --query 'Images | sort_by(@, &CreationDate) | [-1].ImageId' \
  --output text

# Résultat: ami-0c55b159cbfafe1f0


ÉTAPE 6: Lancer votre instance
aws ec2 run-instances \
  --image-id ami-0c55b159cbfafe1f0 \
  --instance-type t2.micro \
  --key-name my-first-key \
  --security-group-ids sg-0123456789abcdef0 \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=MyFirstInstance}]'

# Attendez 30-60 secondes


ÉTAPE 7: Obtenir l'IP publique
aws ec2 describe-instances \
  --filters "Name=tag:Name,Values=MyFirstInstance" \
  --query 'Reservations[0].Instances[0].PublicIpAddress' \
  --output text

# Résultat: 203.0.113.45


ÉTAPE 8: Se connecter en SSH
ssh -i my-first-key.pem ubuntu@203.0.113.45

# Bienvenue sur votre serveur!


ÉTAPE 9: Faire quelque chose
# Une fois connecté:
sudo apt update
sudo apt upgrade -y
sudo apt install -y git python3-pip


ÉTAPE 10: Terminer quand vous avez fini
# Quitter SSH
exit

# Terminer l'instance
aws ec2 terminate-instances \
  --instance-ids i-1234567890abcdef0


═══════════════════════════════════════════════════════════════════════════════
[OK] RESSOURCES & DOCUMENTATION
═══════════════════════════════════════════════════════════════════════════════

DOCUMENTATION OFFICIELLE:
- AWS EC2 Documentation: https://docs.aws.amazon.com/ec2/
- AWS CLI Reference: https://docs.aws.amazon.com/cli/latest/
- Free Tier Details: https://aws.amazon.com/fr/free/

TUTORIELS UTILES:
- AWS Getting Started: https://aws.amazon.com/fr/getting-started/
- EC2 User Guide: https://docs.aws.amazon.com/fr_fr/AWSEC2/latest/UserGuide/

OUTILS:
- AWS Console (Web): https://console.aws.amazon.com/
- AWS CLI: https://aws.amazon.com/fr/cli/
- PuTTY (Windows): https://www.putty.org/

SÉCURITÉ:
- Never commit your .pem file to Git!
- Use .gitignore: echo "*.pem" >> .gitignore
- Store backups securely (not on GitHub!)
- Use IAM users for CLI access (not root)


═══════════════════════════════════════════════════════════════════════════════
[OK] COMPARAISON AWS EC2 vs AUTRES
═══════════════════════════════════════════════════════════════════════════════

EC2 vs Heroku:
- EC2: Plus cher, mais plus de contrôle, plus flexible
- Heroku: Plus cher pour petit usage, mais plus simple

EC2 vs DigitalOcean:
- EC2: Cher pour débutants, mais plus de services
- DigitalOcean: Plus simple, moins cher, moins de services

EC2 vs Linode:
- EC2: Complexe mais très scalable
- Linode: Intermédiaire, bon rapport qualité/prix

Pour débuter: DigitalOcean ou Linode
Pour production d'entreprise: AWS EC2


═══════════════════════════════════════════════════════════════════════════════
[OK] ERREURS COURANTES À ÉVITER
═══════════════════════════════════════════════════════════════════════════════

ERREUR 1: Ouvrir SSH au monde entier (0.0.0.0/0)
[X] MAUVAIS: --cidr 0.0.0.0/0
[OK] BON: --cidr 192.168.1.100/32 (votre IP)
Risque: Hackers essaient de se connecter

ERREUR 2: Perdre son fichier .pem
[X] Impossible de se connecter à l'instance!
[OK] Sauvegardez-le à plusieurs endroits

ERREUR 3: Oublier de terminer une instance
[X] Vous payez même si vous n'l'utilisez pas
[OK] Vérifiez votre facturation chaque mois

ERREUR 4: Ne pas faire de snapshots
[X] Si le disque échoue, vous perdez vos données
[OK] Créez des snapshots réguliers

ERREUR 5: Utiliser un mot de passe faible
[X] Avec root ou sudo sans SSH key
[OK] Utilisez TOUJOURS SSH key

ERREUR 6: Ne pas configurer de Security Group
[X] Ports inutiles ouverts = sécurité faible
[OK] Ouvrez seulement les ports nécessaires

ERREUR 7: Utiliser une région trop loin
[X] Latence élevée pour vos utilisateurs
[OK] Choisissez une région proche de vos utilisateurs

ERREUR 8: Ne pas faire de mise à jour de sécurité
[X] Vulnerabilités exploitables
[OK] sudo apt update && sudo apt upgrade -y régulièrement


# Fichier: python_cheats/cheatsheets/S3.txt
# Cheatsheet AWS S3 - Guide Complet pour Débutants

═══════════════════════════════════════════════════════════════════════════════
[OK] S3 (SIMPLE STORAGE SERVICE) - C'EST QUOI?
═══════════════════════════════════════════════════════════════════════════════

S3 = "Disque dur infini dans le cloud"

Imaginez un immense entrepôt gratuit:
- Vous pouvez y ranger des fichiers (photos, vidéos, documents, etc.)
- L'espace est illimité
- Vous payez seulement pour ce que vous utilisez
- Très fiable (99.999999999% de durabilité - 11 nines!)
- Accessible de n'importe où sur Internet

ANALOGIE: C'est comme une boîte de stockage virtuelle
- Au lieu de stocker des disques durs chez vous
- Vous stocker dans un immense cloud
- Vous payez par GB utilisé
- Vos fichiers sont protégés (AWS gère les backups)

TERMINOLOGIE DE BASE:
- Bucket = Dossier racine (conteneur pour vos fichiers)
- Object = Fichier (n'importe quel type: txt, jpg, mp4, zip, etc.)
- Key = Chemin/nom du fichier (ex: "folder/document.pdf")
- Prefix = Dossier virtuel (S3 n'a pas de vrais dossiers)
- ACL = Permissions (qui peut accéder)
- Bucket Policy = Règles d'accès avancées (JSON)
- Storage Class = Type de stockage (Standard, Archive, etc.)

CAS D'USAGE COURANTS:
- Héberger un site web statique
- Sauvegarder des fichiers
- Stocker des photos d'applications
- Archive de données à long terme
- Logs de serveurs
- Distribution de fichiers publics


═══════════════════════════════════════════════════════════════════════════════
[OK] TARIFICATION S3 (CE QUE VOUS ALLEZ PAYER)
═══════════════════════════════════════════════════════════════════════════════

GRATUIT (Free Tier):
- 5 GB de stockage S3 Standard par mois
- 20 000 requêtes GET par mois
- 2 000 requêtes PUT/COPY/POST/DELETE par mois
- 100 GB de sortie données par mois

APRÈS LE FREE TIER:
- Stockage: ~$0.023 par GB/mois (très bon marché!)
- Requêtes GET: ~$0.0004 par 10 000 requêtes
- Requêtes PUT: ~$0.005 par 1 000 requêtes
- Transfert données OUT: ~$0.09 par GB

EXEMPLE:
- 1 GB de photos: $0.023/mois
- 100 GB de backup: $2.30/mois
- 1 TB d'archive: $23/mois (super pas cher!)

CONSEIL: S3 est TRÈS bon marché pour du stockage


═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 1 - CRÉER UN BUCKET
═══════════════════════════════════════════════════════════════════════════════

BUCKET = Conteneur principal (comme un disque dur)

# Créer bucket
aws s3 mb s3://my-unique-bucket-name-12345

# EXPLICATION DÉTAILLÉE:
# "aws s3 mb" = "make bucket" (créer un bucket)
# "s3://" = Protocole S3 (c'est le standard AWS)
# "my-unique-bucket-name-12345" = Nom du bucket
#
# RÈGLES DE NOMMAGE:
# [OK] Doit être UNIQUE mondialement (personne d'autre ne peut l'avoir)
# [OK] 3-63 caractères
# [OK] Minuscules (a-z)
# [OK] Chiffres (0-9)
# [OK] Hyphens (-)
# [OK] Commencer et finir par lettre ou chiffre
#
# [X] NON AUTORISÉ:
# [X] Majuscules (BUCKET, Bucket = erreur!)
# [X] Underscore (_) = mon_bucket ERREUR
# [X] Points consécutifs (..)
# [X] IP format (192.168.1.1 = erreur)
# [X] Noms de domaine (même pas besoin!)
#
# BON EXEMPLE: my-bucket-2024
# MAUVAIS EXEMPLE: my_bucket, MyBucket, 192.168.1.1
#
# RÉSULTAT: Si ça marche, rien n'est affiché (c'est normal!)
# Si le nom existe déjà: "BucketAlreadyOwnedByYou" ou "BucketAlreadyExists"

# Créer bucket dans région spécifique
aws s3 mb s3://my-bucket --region eu-west-1

# EXPLICATION:
# "--region eu-west-1" = Où stocker le bucket (Europe)
# Si vous ne spécifiez pas, AWS utilise votre région par défaut
#
# RÉGIONS COURANTES:
# - us-east-1 = États-Unis (Virginie)
# - us-west-1 = États-Unis (Californie)
# - eu-west-1 = Europe (Irlande)
# - eu-central-1 = Europe (Francfort)
# - ap-northeast-1 = Japon (Tokyo)
# - ap-southeast-1 = Asie du Sud-Est (Singapour)
#
# CONSEIL: Choisissez une région PROCHE de vos utilisateurs!

# Lister tous vos buckets
aws s3 ls

# EXPLICATION:
# "ls" = "list" (lister)
# Affiche TOUS vos buckets S3
#
# Résultat:
# 2024-01-15 14:32:45 my-first-bucket
# 2024-01-15 15:10:22 my-second-bucket
# 2024-01-15 15:45:01 backup-bucket
#
# Colonnes: Date de création, Heure, Nom du bucket

# Lister contenu d'un bucket (fichiers dans le bucket)
aws s3 ls s3://my-bucket

# EXPLICATION:
# "aws s3 ls s3://my-bucket" = Lister les fichiers du bucket "my-bucket"
# Affiche SEULEMENT les fichiers/dossiers au premier niveau (pas récursif)
#
# Résultat:
# 2024-01-15 10:00:00    1234 document.pdf
# 2024-01-15 11:30:00 DIR                 photos/
# 2024-01-15 12:45:00    5678 video.mp4
#
# "DIR" = C'est un dossier (prefix)
# Les nombres = Taille en bytes

# Lister contenu d'un sous-dossier
aws s3 ls s3://my-bucket/photos/

# EXPLICATION:
# "s3://my-bucket/photos/" = Lister le contenu du dossier "photos"
# "/photos/" = Dossier virtuel (S3 simule les dossiers)
#
# Résultat: Fichiers dans photos/
# 2024-01-15 09:00:00 2345678 vacation-1.jpg
# 2024-01-15 09:15:00 3456789 vacation-2.jpg

# Lister TOUT le contenu (récursivement, tous les niveaux)
aws s3 ls s3://my-bucket --recursive

# EXPLICATION:
# "--recursive" = Descendre dans tous les dossiers
# Affiche TOUS les fichiers (peut être très long!)
#
# Résultat: Toute la structure
# 2024-01-15 10:00:00    1234 document.pdf
# 2024-01-15 09:00:00 2345678 photos/vacation-1.jpg
# 2024-01-15 09:15:00 3456789 photos/vacation-2.jpg
# 2024-01-15 12:45:00    5678 videos/clip.mp4

# Lister avec formatage lisible
aws s3 ls s3://my-bucket --recursive --human-readable --summarize

# EXPLICATION:
# "--human-readable" = Afficher les tailles en KB, MB, GB (pas bytes)
# "--summarize" = Afficher un résumé total
#
# Résultat:
# 2024-01-15 10:00:00    1.2 KiB document.pdf
# 2024-01-15 09:00:00    2.3 MiB photos/vacation-1.jpg
# Total Size: 15.4 MiB
# Total Objects: 42

# Supprimer bucket (doit être VIDE)
aws s3 rb s3://my-bucket

# EXPLICATION:
# "rb" = "remove bucket" (supprimer bucket)
# ATTENTION: Le bucket DOIT être complètement vide!
# Si des fichiers dedans, vous aurez une erreur
#
# Erreur possible:
# "An error occurred (BucketNotEmpty) when calling the RemoveBucket"
# = Le bucket n'est pas vide!

# Supprimer bucket ET TOUT SON CONTENU ([ATTENTION] TRÈS DANGEREUX!)
aws s3 rb s3://my-bucket --force

# EXPLICATION:
# "--force" = Supprimer même s'il y a des fichiers
# [ATTENTION] ATTENTION: C'EST IRRÉVERSIBLE!
# TOUS les fichiers seront supprimés définitivement
# À moins d'avoir des snapshots/backups ailleurs, c'est fini
#
# CONSEIL: N'utilisez jamais --force sauf si vous ÊTES SÛR!


═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 2 - UPLOADER DES FICHIERS
═══════════════════════════════════════════════════════════════════════════════

UPLOAD = Envoyer des fichiers de votre ordinateur vers S3

# Upload un seul fichier
aws s3 cp file.txt s3://my-bucket/

# EXPLICATION DÉTAILLÉE:
# "aws s3 cp" = "copy" (copier un fichier)
# "file.txt" = Fichier LOCAL (sur votre ordinateur)
# "s3://my-bucket/" = Destination S3
#
# RÉSULTAT:
# - Le fichier est envoyé vers S3
# - Il porte le même nom: "file.txt"
# - Vous le retrouverez à: s3://my-bucket/file.txt
#
# Résultat console:
# upload: ./file.txt to s3://my-bucket/file.txt
# Completed 1 of 1
#
# EXEMPLE RÉEL:
# Fichier local: /home/user/documents/rapport.pdf
# Commande: aws s3 cp /home/user/documents/rapport.pdf s3://my-bucket/
# Résultat: Le fichier est maintenant à s3://my-bucket/rapport.pdf

# Upload un fichier avec nouveau nom
aws s3 cp file.txt s3://my-bucket/my-new-name.txt

# EXPLICATION:
# "my-new-name.txt" = Nouveau nom du fichier
# Le fichier sera stocké sous ce nom (pas "file.txt")
# Vous le retrouverez à: s3://my-bucket/my-new-name.txt

# Upload dans un sous-dossier (prefix)
aws s3 cp file.txt s3://my-bucket/folder/subfolder/file.txt

# EXPLICATION:
# "folder/subfolder/" = Chemin complet (s3 simule les dossiers)
# Le fichier sera organisé comme: bucket > folder > subfolder > file.txt
# Les dossiers sont créés automatiquement
#
# À l'accès: s3://my-bucket/folder/subfolder/file.txt

# Upload un dossier entier
aws s3 cp mydir/ s3://my-bucket/mydir/ --recursive

# EXPLICATION:
# "mydir/" = Dossier LOCAL (avec slash final)
# "s3://my-bucket/mydir/" = Destination S3
# "--recursive" = Copier TOUS les fichiers dedans (y compris sous-dossiers)
#
# RÉSULTAT:
# Si mydir/ contient:
#   - file1.txt
#   - file2.txt
#   - subfolder/
#     - file3.txt
#
# Ils seront uploadés comme:
#   - s3://my-bucket/mydir/file1.txt
#   - s3://my-bucket/mydir/file2.txt
#   - s3://my-bucket/mydir/subfolder/file3.txt
#
# Structure préservée!

# Upload avec métadonnées (informations supplémentaires)
aws s3 cp file.txt s3://my-bucket/ \
  --metadata key1=value1,key2=value2

# EXPLICATION:
# "--metadata" = Ajouter des données sur le fichier
# "key1=value1,key2=value2" = Paires clé-valeur
#
# Utilité: Informations pour l'application
# Exemple: author=john, department=engineering
#
# Plus tard, vous pourrez récupérer ces métadonnées
# aws s3api head-object --bucket my-bucket --key file.txt

# Upload avec type de contenu (Content-Type)
aws s3 cp index.html s3://my-bucket/ --content-type text/html

# EXPLICATION:
# "--content-type text/html" = Type MIME du fichier
# C'est important pour le navigateur (comment afficher le fichier)
#
# Types MIME courants:
# - text/html = Page HTML
# - text/plain = Fichier texte
# - image/jpeg = Image JPG
# - image/png = Image PNG
# - application/json = Fichier JSON
# - application/pdf = Fichier PDF
# - video/mp4 = Vidéo MP4
#
# Sans Content-Type correct, le navigateur peut:
# - Télécharger au lieu d'afficher
# - Afficher mal le contenu
#
# Utilité: Si vous hébergez un site web statique!

# Upload avec cache control
aws s3 cp image.jpg s3://my-bucket/ --cache-control max-age=86400

# EXPLICATION:
# "--cache-control" = Combien de temps garder en cache
# "max-age=86400" = 86400 secondes = 1 jour
#
# Utilité:
# - Moins de requêtes à S3 = Moins cher
# - Plus rapide (fichier en cache localement)
#
# Valeurs courantes:
# - max-age=3600 = 1 heure
# - max-age=86400 = 1 jour
# - max-age=604800 = 1 semaine
# - max-age=31536000 = 1 an
#
# Pour fichiers qui changent peu: cache long
# Pour fichiers qui changent souvent: cache court (ou 0)

═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 3 - TÉLÉCHARGER DES FICHIERS
═══════════════════════════════════════════════════════════════════════════════

DOWNLOAD = Récupérer des fichiers de S3 vers votre ordinateur

# Télécharger un fichier
aws s3 cp s3://my-bucket/file.txt ./

# EXPLICATION:
# "s3://my-bucket/file.txt" = Fichier S3 (source)
# "./" = Dossier LOCAL courant (destination)
#
# RÉSULTAT:
# - Le fichier "file.txt" est téléchargé
# - Il est mis dans le dossier où vous êtes
#
# Résultat console:
# download: s3://my-bucket/file.txt to ./file.txt
#
# EXEMPLE:
# Vous êtes dans /home/user/downloads/
# Commande: aws s3 cp s3://my-bucket/photo.jpg ./
# Résultat: Le fichier est maintenant en /home/user/downloads/photo.jpg

# Télécharger avec nouveau nom
aws s3 cp s3://my-bucket/file.txt ./downloaded-file.txt

# EXPLICATION:
# "./downloaded-file.txt" = Nouveau nom LOCAL
# Le fichier sera renommé en téléchargeant

# Télécharger un dossier entier
aws s3 cp s3://my-bucket/mydir/ ./mydir/ --recursive

# EXPLICATION:
# "s3://my-bucket/mydir/" = Dossier S3 (source)
# "./mydir/" = Dossier LOCAL (destination)
# "--recursive" = Télécharger tous les fichiers dedans
#
# Structure préservée!
#
# Si S3 contient:
#   - s3://my-bucket/mydir/file1.txt
#   - s3://my-bucket/mydir/subfolder/file2.txt
#
# Votre disque aura:
#   - ./mydir/file1.txt
#   - ./mydir/subfolder/file2.txt


═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 4 - SYNCHRONISER DES DOSSIERS (SYNC)
═══════════════════════════════════════════════════════════════════════════════

SYNC = Copier SEULEMENT ce qui a changé (très intelligent!)

# Synchroniser dossier local -> S3
aws s3 sync ./local-folder s3://my-bucket/remote-folder

# EXPLICATION:
# "aws s3 sync" = Synchroniser intelligemment
# "./local-folder" = Dossier LOCAL (source)
# "s3://my-bucket/remote-folder" = Dossier S3 (destination)
#
# COMMENT ÇA MARCHE:
# - AWS compare les fichiers locaux et S3
# - Envoie SEULEMENT les fichiers nouveaux/modifiés
# - Très efficient!
#
# EXEMPLE:
# Première fois: sync 10 fichiers (envoie les 10)
# Deuxième fois (1 fichier modifié, 1 nouveau):
#   - sync envoie SEULEMENT 2 fichiers
#   - Les 9 autres ne sont pas touchés
#
# Résultat console:
# upload: local-folder/file1.txt to s3://my-bucket/remote-folder/file1.txt
# upload: local-folder/file2.txt to s3://my-bucket/remote-folder/file2.txt
# upload: local-folder/subfolder/file3.txt to s3://my-bucket/remote-folder/subfolder/file3.txt

# Synchroniser S3 -> dossier local
aws s3 sync s3://my-bucket/remote-folder ./local-folder

# EXPLICATION:
# Fait l'inverse: télécharge depuis S3 vers votre ordinateur
# Pratique pour récupérer les modifications

# Synchroniser AVEC suppression
aws s3 sync ./local-folder s3://my-bucket/remote-folder --delete

# EXPLICATION:
# "--delete" = Supprimer sur S3 ce qui a été supprimé localement
#
# DANGER! Exemple:
# Vous avez 5 fichiers sur S3
# Vous supprimez 2 fichiers localement
# Vous lancez sync --delete
# -> Les 2 fichiers seront AUSSI supprimés de S3!
#
# Utilité: Synchronisation complète (S3 = miroir exact du local)

# Synchroniser AVEC exclusions (ignorer certains fichiers)
aws s3 sync ./local-folder s3://my-bucket/ --exclude "*.tmp" --exclude ".git/*"

# EXPLICATION:
# "--exclude" = Ne pas copier ces fichiers
# "*.tmp" = Tous les fichiers .tmp
# ".git/*" = Tout ce qui est dans .git/
#
# Utilité: Ne pas envoyer les fichiers inutiles
# - Fichiers temporaires
# - Dossiers .git (trop lourds)
# - Fichiers de compilation
# - Fichiers secrets
#
# Vous pouvez ajouter plusieurs --exclude

# Synchroniser SEULEMENT certains fichiers
aws s3 sync ./local-folder s3://my-bucket/ --include "*.jpg" --exclude "*"

# EXPLICATION:
# "--include" = SEULEMENT ces fichiers
# "--exclude '*'" = Exclure tout le reste
#
# Ici: Envoyer SEULEMENT les fichiers .jpg
# Les .png, .txt, etc. ne seront pas envoyés

═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 5 - DÉPLACER ET SUPPRIMER
═══════════════════════════════════════════════════════════════════════════════

# Déplacer/renommer un fichier
aws s3 mv s3://my-bucket/old.txt s3://my-bucket/new.txt

# EXPLICATION:
# "aws s3 mv" = "move" (déplacer)
# "old.txt" = Nom actuel
# "new.txt" = Nouveau nom
#
# RÉSULTAT:
# - Le fichier est renommé
# - L'ancien nom disparaît
# - Le nouveau nom apparaît
#
# C'est équivalent à: delete old.txt + create new.txt

# Déplacer entre buckets
aws s3 mv s3://source-bucket/file.txt s3://dest-bucket/file.txt

# EXPLICATION:
# Déplace un fichier d'un bucket à l'autre
# Le fichier disparaît du source-bucket
# Le fichier apparaît dans dest-bucket

# Supprimer un fichier
aws s3 rm s3://my-bucket/file.txt

# EXPLICATION:
# "aws s3 rm" = "remove" (supprimer)
# "file.txt" = Fichier à supprimer
#
# [ATTENTION] ATTENTION: C'EST PERMANENT!
# Le fichier est supprimé définitivement
# Sauf si vous avez la versioning activée (voir plus loin)

# Supprimer un dossier entier
aws s3 rm s3://my-bucket/folder/ --recursive

# EXPLICATION:
# "--recursive" = Supprimer TOUT dedans
# Le dossier et tous les sous-dossiers/fichiers seront supprimés
#
# [ATTENTION] ATTENTION: IRRÉVERSIBLE!
# Vérifiez que c'est vraiment ce que vous voulez supprimer!

# Supprimer plusieurs fichiers (wildcards)
aws s3 rm s3://my-bucket/ --recursive --exclude "*" --include "*.tmp"

# EXPLICATION:
# "--include '*.tmp'" = Seulement fichiers .tmp
# "--exclude '*'" = Exclure tout le reste
# Résultat: Supprime SEULEMENT les fichiers .tmp


═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 6 - PARTAGER DES FICHIERS (PERMISSIONS)
═══════════════════════════════════════════════════════════════════════════════

PERMISSIONS = Contrôler qui peut voir/télécharger vos fichiers

# Rendre un fichier PUBLIC (tout le monde peut le télécharger)
aws s3 cp file.txt s3://my-bucket/ --acl public-read

# EXPLICATION:
# "--acl public-read" = ACL (Access Control List)
# "public-read" = N'importe qui peut LIRE (télécharger)
#
# QU'EST-CE QUE ACL:
# ACL = Liste de qui peut faire quoi
# Les valeurs possibles:
# - private = Propriétaire SEULEMENT (défaut)
# - public-read = Tout le monde peut LIRE
# - public-read-write = Tout le monde peut LIRE + ÉCRIRE (très dangereux!)
# - authenticated-read = Utilisateurs AWS authentifiés seulement
#
# EXEMPLE:
# Vous uploadez une image publique:
# aws s3 cp image.jpg s3://my-bucket/ --acl public-read
#
# Maintenant, vous pouvez partager le lien:
# https://my-bucket.s3.amazonaws.com/image.jpg
# N'importe qui peut voir l'image!

# [ATTENTION] ATTENTION: Ne jamais utiliser public-read-write!
# aws s3 cp file.txt s3://my-bucket/ --acl public-read-write
# Cela signifie: N'importe qui peut MODIFIER/SUPPRIMER vos fichiers!
# Dangereux!

# Uploader avec ACL par défaut (private = sécurisé)
aws s3 cp file.txt s3://my-bucket/
# Sans --acl, c'est "private" (seulement vous)

# Rendre un fichier privé après l'avoir uploadé
aws s3api put-object-acl \
  --bucket my-bucket \
  --key file.txt \
  --acl private

# EXPLICATION:
# "aws s3api" = API plus bas niveau (plus d'options)
# "put-object-acl" = Changer les ACL d'un objet
# "--bucket my-bucket" = Quel bucket
# "--key file.txt" = Quel fichier
# "--acl private" = Nouveau ACL (privé)

═══════════════════════════════════════════════════════════════════════════════
[OK] ÉTAPE 7 - URLS TEMPORAIRES (PRESIGNED URLS)
═══════════════════════════════════════════════════════════════════════════════

PRESIGNED URL = Lien temporaire pour accéder à un fichier privé

# Générer une URL temporaire (expire après 1 heure)
aws s3 presign s3://my-bucket/private-file.pdf --expires-in 3600

# EXPLICATION DÉTAILLÉE:
# "aws s3 presign" = Créer une URL temporaire
# "s3://my-bucket/private-file.pdf" = Fichier privé
# "--expires-in 3600" = Valide pendant 3600 secondes (1 heure)
#
# RÉSULTAT:
# https://my-bucket.s3.amazonaws.com/private-file.pdf?
# X-Amz-Algorithm=AWS4-HMAC-SHA256&
# X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F...
# ... (beaucoup de caractères)
#
# C'est une longue URL avec un token (clé) dedans
#
# UTILITÉ:
# - Vous avez un fichier PRIVÉ dans S3
# - Vous voulez donner l'accès À QUELQU'UN D'AUTRE
# - Vous générez une URL temporaire
# - Vous lui envoyez ce lien
# - Il peut télécharger pendant 1 heure
# - Après 1 heure, le lien ne fonctionne plus
#
# EXEMPLE RÉEL:
# Cas d'usage: Partage de factures PDF avec clients
# 1. Facture est PRIVÉE (clients ne voient pas autres factures)
# 2. Vous générez presigned URL pour la facture de Jean
# 3. Vous envoyez le lien par email à Jean
# 4. Jean peut la télécharger
# 5. Après 24h, le lien expire (plus d'accès)

# URL valide pendant 7 jours
aws s3 presign s3://my-bucket/file.txt --expires-in 604800

# EXPLICATION:
# "604800" = 7 jours en secondes (60*60*24*7)
#
# Vous pouvez adapter la durée:
# - 1 minute: 60
# - 1 heure: 3600
# - 1 jour: 86400
# - 7 jours: 604800
# - 30 jours: 2592000
#
# CONSEIL:
# - Temps court (1h) = Sécurité maximale
# - Temps long (7j) = Flexibilité maximale
# - Choisissez selon votre besoin

# UTILITÉ DANS UNE APP:
# Cas: App web avec téléchargement de fichiers
# 1. L'utilisateur clique sur "télécharger"
# 2. Votre backend génère presigned URL
# 3. Votre frontend redirige vers cette URL
# 4. Le navigateur télécharge le fichier
# 5. Vous n'avez pas besoin de serveur pour ça!
# (Économie de bande passante!)


═══════════════════════════════════════════════════════════════════════════════
[OK] STORAGE CLASSES (TYPES DE STOCKAGE)
═══════════════════════════════════════════════════════════════════════════════

STORAGE CLASS = Où et comment stocker les données (affecte le prix!)

CLASSES DISPONIBLES:

S3 Standard (par défaut)
- Utilité: Fichiers accédés SOUVENT
- Latence: Très basse (immédiat)
- Prix: Plus cher
- Durabilité: 99.999999999%
- Cas d'usage: Sites web, applications, photos récentes

S3 Intelligent-Tiering
- Utilité: Usage imprévisible
- Latence: Automatique selon accès
- Prix: AWS choisit automatiquement
- Cas d'usage: Données mixtes, pas sûr du pattern

S3 Standard-IA (Infrequent Access)
- Utilité: Fichiers accédés RAREMENT
- Latence: Basse mais pas immédiate
- Prix: Moins cher, mais coûts accès
- Cas d'usage: Backups, archives de travail, 30j+

S3 One Zone-IA
- Utilité: Fichiers accédés rarement + UNE zone seulement
- Latence: Basse
- Prix: Très bon marché
- Cas d'usage: Backups locaux, données reproductibles

S3 Glacier Instant
- Utilité: Archive, accès en millisecondes
- Latence: Millisecondes
- Prix: Moins cher que IA
- Cas d'usage: Compliance (loi 7 ans), données anciennes

S3 Glacier Flexible
- Utilité: Archive, accès en minutes/heures
- Latence: 1 minute à 12 heures
- Prix: Très bon marché
- Cas d'usage: Très bonnes archives, données vieilles

S3 Glacier Deep Archive
- Utilité: Archive très longue durée
- Latence: 12 heures garanties
- Prix: TRÈS bon marché
- Cas d'usage: Compliance 30 ans, données jamais accédées

COMPARAISON DE PRIX (par GB/mois):
- S3 Standard: $0.023
- S3 Standard-IA: $0.0125
- S3 Glacier Instant: $0.004
- S3 Glacier Flexible: $0.0036
- S3 Glacier Deep: $0.00099

EXEMPLE: 100 GB de backups à garder 1 an
- Avec Standard: $0.023 × 100 × 12 = $27.60
- Avec Glacier Deep: $0.00099 × 100 × 12 = $1.19
- Économie: $26.41!

# Upload avec storage class spécifique
aws s3 cp file.txt s3://my-bucket/ --storage-class GLACIER

# EXPLICATION:
# "--storage-class GLACIER" = Type de stockage
# Le fichier sera archivé (excellent pour longue durée)
#
# Valeurs possibles:
# - STANDARD (défaut)
# - INTELLIGENT_TIERING
# - STANDARD_IA
# - ONE_ZONE_IA
# - GLACIER_IR (Instant Retrieval)
# - GLACIER (Flexible, défaut pour Glacier)
# - DEEP_ARCHIVE

# CONSEIL:
# - Photos/docs courants: Standard
# - Backups 30 jours: Standard-IA
# - Backups 1 an: Glacier
# - Compliance 7+ ans: Deep Archive# Pour fichiers qui changent peu: cache long
# Pour fichiers qui changent souvent: cache court (ou 0)


# Fichier: python_cheats/cheatsheets/RDS.txt
# Cheatsheet AWS RDS - Guide Complet et Pratique


═══════════════════════════════════════════════════════════════════════════════
[OK] RDS (RELATIONAL DATABASE SERVICE) - C'EST QUOI?
═══════════════════════════════════════════════════════════════════════════════

RDS = "Base de données managée dans le cloud"

Imaginez:
- AU LIEU DE: Installer/gérer vous-même un serveur BD
- VOUS AVEZ: AWS qui gère tout (backups, updates, sécurité, HA)
- VOUS PAYEZ: Seulement pour ce que vous utilisez

ANALOGIE: Louer un administrateur de base de données professionnel
- AWS configure, monitore, patche, sauvegarde
- Vous accédez juste à la DB
- Vous payez mensuellement

MOTEURS SUPPORTÉS:
- MySQL        = Gratuit, populaire, open-source
- PostgreSQL   = Puissant, libre, ultra-fiable
- MariaDB      = Alternative MySQL, compatible
- Oracle DB    = Entreprise, très cher
- SQL Server   = Microsoft, payant
- Aurora       = Version AWS optimisée (5x plus rapide!)

CAS D'USAGE:
- Applications web (stocker utilisateurs, articles, etc.)
- E-commerce (produits, commandes, paiements)
- Analyses et reporting
- Systèmes CRM/ERP
- Data warehouse


═══════════════════════════════════════════════════════════════════════════════
[OK] TARIFICATION RDS (CE QUE VOUS ALLEZ PAYER)
═══════════════════════════════════════════════════════════════════════════════

GRATUIT (Free Tier - 12 mois):
- db.t3.micro: 750 heures/mois (24/7 pendant ~31 jours!)
- 20 GB de stockage (gp2/gp3)
- 20 GB de backups
- Données entrantes: Gratuit
- Données sortantes: 1 GB/mois gratuit

APRÈS FREE TIER (tarif horaire):
- db.t3.micro:    ~$0.02/h  (~$15/mois 24/24)
- db.t3.small:    ~$0.04/h  (~$30/mois 24/24)
- db.t3.medium:   ~$0.08/h  (~$60/mois 24/24)
- db.m5.large:    ~$0.30/h  (~$220/mois 24/24)
- db.r5.large:    ~$0.60/h  (~$450/mois 24/24, optimisé mémoire)

COÛTS ADDITIONNELS:
- Stockage:  ~$0.11 par GB/mois (gp3)
- Backups:   ~$0.023 par GB/mois
- Multi-AZ:  +100% du coût instance
- Data out:  ~$0.02 par GB

EXEMPLE RÉALISTE:
- 1x db.t3.micro + 20 GB gp3: ~$15 + $2.20 = ~$17/mois
- 1x db.t3.small + 50 GB gp3: ~$30 + $5.50 = ~$35/mois
- Production MySQL + Multi-AZ: ~$60 + $60 + $6.50 = ~$126/mois

[IDEE] CONSEIL: RDS Free Tier = EXCELLENT pour apprendre!


═══════════════════════════════════════════════════════════════════════════════
[OK] TERMINOLOGIE CLÉS RDS
═══════════════════════════════════════════════════════════════════════════════

DB Instance:
  = Une base de données individuelle
  = Exemple: mydb, prod-mysql, analytics-db
  = Identifié par son "DB Instance Identifier"

Engine:
  = Type de moteur (mysql, postgres, mariadb, oracle, sqlserver)
  = Une instance = 1 seul moteur
  = Ne peut pas être changé après création

Instance Class:
  = Puissance (CPU, RAM, réseau)
  = db.t3.micro (1 GB RAM) vs db.m5.large (8 GB RAM)
  = Plus grand = Plus puissant et cher

Endpoint:
  = URL + port pour se connecter
  = Exemple: mydb.c9akciq32.us-east-1.rds.amazonaws.com:3306
  = Change si vous migrez ou changez region

Port:
  = 3306 = MySQL
  = 5432 = PostgreSQL
  = 3306 = MariaDB
  = 1521 = Oracle
  = 1433 = SQL Server

Security Group:
  = Pare-feu pour la base
  = Contrôle: qui peut accéder, sur quel port
  = OBLIGATOIRE pour sécurité

Parameter Group:
  = Configuration de la BD (max_connections, timeout, charset, etc.)
  = Appliquées à la création ou via modify
  = Certains paramètres = redémarrage nécessaire

Backup:
  = Sauvegarde complète automatique (quotidienne)
  = Retention: 1-35 jours configurable
  = Utilisé pour recovery et PITR

Snapshot:
  = Photo manuelle d'une BD
  = Gardé indéfiniment (vous décidez quand supprimer)
  = Idéal: avant modification, avant suppression

Read Replica:
  = Copie en lecture seule (asynchrone)
  = Réduit charge du master
  = Peut être en autre région (pour DR)

Multi-AZ:
  = 2 zones de disponibilité différentes
  = Failover automatique en cas de problème
  = 2x plus cher mais haute disponibilité garantie

Aurora:
  = Version AWS ultra-optimisée (MySQL/PostgreSQL)
  = 5x plus rapide, réplication 3 zones automatique
  = Très cher mais performance premium


═══════════════════════════════════════════════════════════════════════════════
[RECHERCHE] EXPLICATIONS DÉTAILLÉES DES COMMANDES AWS CLI
═══════════════════════════════════════════════════════════════════════════════

# Comprendre la structure des commandes AWS RDS:

# SYNTAXE GÉNÉRALE:
# aws rds <action> --paramètre-1 valeur-1 --paramètre-2 valeur-2

# EXEMPLE:
aws rds create-db-instance \
  --db-instance-identifier mydb

# DÉCOMPOSITION:
# "aws"                           = CLI AWS
# "rds"                           = Service (RDS)
# "create-db-instance"            = Action (créer une instance)
# "--db-instance-identifier mydb" = Paramètre (nom de la DB)

# FORMATS DE RÉPONSE:
# Par défaut: JSON (très verbeux)
# --output table  = Format tableau lisible
# --output text   = Texte simple (pour scripts)
# --output json   = JSON explicite

# FILTER & QUERY:
# --query 'DBInstances[0].Endpoint.Address' = Extraire info spécifique
# --filters Name=key,Values=value = Filtrer résultats

# EXEMPLE COMPLET:
aws rds describe-db-instances \
  --db-instance-identifier mydb \
  --query 'DBInstances[0].[DBInstanceIdentifier,DBInstanceStatus,Engine]' \
  --output table

# EXPLICATION:
# describe-db-instances  = Afficher infos
# --db-instance-identifier = Filtrer par ID
# --query                = Extraire colonnes spécifiques
# --output table         = Afficher en tableau


═══════════════════════════════════════════════════════════════════════════════
[OK] PRÉPARER LE RÉSEAU (AVANT DE CRÉER)
═══════════════════════════════════════════════════════════════════════════════

Avant de créer une BD, vous avez besoin:
1. Un Security Group (pare-feu)
2. Optionnel: DB Subnet Group (pour VPC)

# Créer Security Group pour RDS
aws ec2 create-security-group \
  --group-name rds-mysql-sg \
  --description "Security group for RDS MySQL"

# EXPLICATIONS:
# "aws ec2"               = Service EC2 (gère security groups)
# "create-security-group" = Créer nouveau group
# "--group-name"          = Nom du group (lisible)
# "--description"         = Description (optionnel mais recommandé)
#
# RÉSULTAT: sg-0123456789abcdef0
# Cet ID est utilisé partout après!
# Sauvegardez-le!

# Résultat JSON:
# {
#     "GroupId": "sg-0123456789abcdef0",
#     "Tags": []
# }

# Extraire SEULEMENT l'ID:
aws ec2 create-security-group \
  --group-name rds-mysql-sg \
  --description "Security group for RDS MySQL" \
  --query 'GroupId' \
  --output text

# RÉSULTAT: sg-0123456789abcdef0 (plus propre!)

# OPTION 1: Autoriser depuis votre IP (développement)
# Remplacez 203.0.113.25 par VOTRE IP (curl https://icanhazip.com)

aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 3306 \
  --cidr 203.0.113.25/32

# EXPLICATION:
# "--port 3306" = Port MySQL standard
# "203.0.113.25/32" = Votre IP exacte
# /32 = Une IP unique (si 0.0.0.0/0 = ouvert à tous, DANGEREUX!)

# OPTION 2: Autoriser depuis instance EC2 (meilleur)
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 3306 \
  --source-group sg-app-0123456789abcdef0

# EXPLICATION:
# Les instances du security group "sg-app-..." peuvent accéder
# Plus sécurisé (pas d'exposition Internet)

# OPTION 3: Autoriser PostgreSQL depuis un range
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 5432 \
  --cidr 192.168.0.0/16

# Vérifier les règles
aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0

# Créer DB Subnet Group (optionnel, pour VPC)
aws rds create-db-subnet-group \
  --db-subnet-group-name my-db-subnets \
  --db-subnet-group-description "My private DB subnets" \
  --subnet-ids subnet-12345678 subnet-87654321

# EXPLICATION:
# "subnet-12345678" et "subnet-87654321" = 2 sous-réseaux différents
# RDS va placer la DB (et replica) dans ces subnets
# OBLIGATOIRE si vous utilisez une VPC privée


═══════════════════════════════════════════════════════════════════════════════
[OK] CRÉER UNE BASE DE DONNÉES
═══════════════════════════════════════════════════════════════════════════════

# CRÉER MYSQL (SIMPLE)
aws rds create-db-instance \
  --db-instance-identifier mydb \
  --db-instance-class db.t3.micro \
  --engine mysql \
  --engine-version 8.0.35 \
  --master-username admin \
  --master-user-password MySecurePassword123! \
  --allocated-storage 20 \
  --storage-type gp3 \
  --vpc-security-group-ids sg-0123456789abcdef0 \
  --backup-retention-period 7 \
  --publicly-accessible

# CRÉER MYSQL (PRODUCTION)
aws rds create-db-instance \
  --db-instance-identifier prod-mysql \
  --db-instance-class db.m5.large \
  --engine mysql \
  --engine-version 8.0.35 \
  --master-username admin \
  --master-user-password MySecurePassword123! \
  --allocated-storage 100 \
  --storage-type gp3 \
  --iops 3000 \
  --vpc-security-group-ids sg-0123456789abcdef0 \
  --db-subnet-group-name my-db-subnets \
  --backup-retention-period 30 \
  --preferred-backup-window "03:00-04:00" \
  --preferred-maintenance-window "mon:04:00-mon:05:00" \
  --multi-az \
  --no-publicly-accessible \
  --enable-cloudwatch-logs-exports error,general,slowquery \
  --deletion-protection

# CRÉER POSTGRESQL
aws rds create-db-instance \
  --db-instance-identifier mypgdb \
  --db-instance-class db.t3.micro \
  --engine postgres \
  --engine-version 15.4 \
  --master-username postgres \
  --master-user-password MySecurePassword123! \
  --allocated-storage 20 \
  --storage-type gp3 \
  --vpc-security-group-ids sg-0123456789abcdef0 \
  --backup-retention-period 7

# EXPLICATIONS DÉTAILLÉES:

# "--db-instance-identifier mydb"
#   = Nom unique de la DB (obligatoire)
#   = Minuscules, chiffres, hyphens (pas d'underscore)
#   = Conseils: prod-mysql, dev-pg, analytics-db, etc.

# "--db-instance-class db.t3.micro"
#   = Puissance de l'instance
#   = COMMON OPTIONS:
#     - db.t3.micro   (2 vCPU, 1 GB RAM)  -> Gratuit 12 mois
#     - db.t3.small   (2 vCPU, 2 GB RAM)  -> ~$30/mois
#     - db.t3.medium  (2 vCPU, 4 GB RAM)  -> ~$60/mois
#     - db.m5.large   (2 vCPU, 8 GB RAM)  -> ~$220/mois
#     - db.r5.large   (2 vCPU, 16 GB RAM) -> ~$450/mois (mémoire opt)
#   = CONSEIL: t3.micro pour apprendre, m5.large pour production

# "--engine mysql" ou "--engine postgres"
#   = Moteur BD: mysql, postgres, mariadb, oracle, sqlserver
#   = [ATTENTION] NE PEUT PAS ÊTRE CHANGÉ APRÈS CRÉATION!

# "--engine-version 8.0.35"
#   = Version exacte du moteur
#   = Omis = AWS choisit la plus récente par défaut
#   = Chercher version stable et récente

# "--master-username admin"
#   = Utilisateur root pour accéder à la DB
#   = Vous l'utiliserez pour connexion initiale
#   = [ATTENTION] NE PEUT PAS ÊTRE CHANGÉ APRÈS CRÉATION!
#   = Conventions: admin, root, postgres (selon moteur)

# "--master-user-password MySecurePassword123!"
#   = Mot de passe root (SÉCURISÉ!)
#   = Minimum 8 caractères, majuscules, chiffres, symboles
#   = [ATTENTION] SAUVEGARDEZ EN SÉCURITÉ! (gestionnaire de mots de passe)
#   = [ATTENTION] NE METTEZ PAS EN DUR DANS LE CODE!

# "--allocated-storage 20"
#   = Taille initiale du disque en GB
#   = MySQL free tier: max 20 GB
#   = Production: 100+ GB courant
#   = [ATTENTION] ON PEUT AUGMENTER, PAS DIMINUER!

# "--storage-type gp3"
#   = Type de stockage:
#     - gp3 = General Purpose (MEILLEUR CHOIX)
#     - gp2 = Plus ancien, compatible
#     - io1 = Haute IOPS (très cher)
#   = CONSEIL: Toujours gp3

# "--iops 3000"
#   = Opérations par seconde (gp3 seulement)
#   = Par défaut: 3000 IOPS
#   = Pour performance: 5000-16000 IOPS
#   = Plus d'IOPS = Plus cher

# "--vpc-security-group-ids sg-0123456789abcdef0"
#   = Pare-feu pour accès à la DB
#   = OBLIGATOIRE!
#   = Doit être créé avant

# "--db-subnet-group-name my-db-subnets"
#   = Subnets VPC où placer la DB
#   = Optionnel si VPC par défaut
#   = Recommandé pour production

# "--backup-retention-period 7"
#   = Combien de jours garder backups auto
#   = 0 = pas de backup auto
#   = 7 = semaine (bon par défaut)
#   = 30-35 = production critique

# "--preferred-backup-window 03:00-04:00"
#   = Quand faire le backup quotidien
#   = Format: HH:MM-HH:MM (UTC)
#   = CONSEIL: Hors heures de pointe

# "--preferred-maintenance-window mon:04:00-mon:05:00"
#   = Quand AWS peut faire maintenance (patches, etc.)
#   = Format: ddd:HH:MM-ddd:HH:MM
#   = CONSEIL: Lundi 04:00 = peu d'utilisateurs

# "--multi-az"
#   = Haute disponibilité (2 zones)
#   = Failover automatique en cas de panne
#   = COÛTE 2x PLUS CHER!
#   = CONSEIL: Production oui, dev non

# "--publicly-accessible"
#   = Accessible depuis Internet (IP publique)
#   = SANS: Seulement depuis EC2/VPC
#   = [ATTENTION] SÉCURITÉ RÉDUITE SI PUBLIC!
#   = CONSEIL: Dev oui, Prod non

# "--enable-cloudwatch-logs-exports error,general,slowquery"
#   = Envoyer logs vers CloudWatch
#   = error = erreurs BD
#   = general = toutes requêtes (lourd!)
#   = slowquery = requêtes lentes (utile!)

# "--deletion-protection"
#   = Protège contre suppression accidentelle
#   = CONSEIL: Pour production


═══════════════════════════════════════════════════════════════════════════════
[OK] LISTER ET VÉRIFIER LES BASES DE DONNÉES
═══════════════════════════════════════════════════════════════════════════════

# Lister toutes les DB instances
aws rds describe-db-instances

# Affichage lisible (tableau)
aws rds describe-db-instances \
  --query 'DBInstances[*].[DBInstanceIdentifier,DBInstanceStatus,Engine,DBInstanceClass,Endpoint.Address]' \
  --output table

# Résultat:
# | DBInstanceIdentifier | DBInstanceStatus | Engine | DBInstanceClass | Endpoint.Address |
# |----------------------|------------------|--------|-----------------|------------------|
# | mydb                 | available        | mysql  | db.t3.micro     | mydb.c9akciq32... |

# Attendre que Status = "available" (5-10 minutes)

# Décrire une DB spécifique
aws rds describe-db-instances --db-instance-identifier mydb

# Récupérer SEULEMENT l'endpoint (URL de connexion)
aws rds describe-db-instances \
  --db-instance-identifier mydb \
  --query 'DBInstances[0].Endpoint.Address' \
  --output text

# Résultat: mydb.c9akciq32.us-east-1.rds.amazonaws.com

# Vérifier en format JSON complet
aws rds describe-db-instances \
  --db-instance-identifier mydb \
  --output json

# Vérifier le port de connexion
aws rds describe-db-instances \
  --db-instance-identifier mydb \
  --query 'DBInstances[0].Endpoint.Port' \
  --output text


═══════════════════════════════════════════════════════════════════════════════
[OK] MODIFIER UNE BASE DE DONNÉES
═══════════════════════════════════════════════════════════════════════════════

# Upgrade classe d'instance
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --db-instance-class db.t3.small \
  --apply-immediately

# EXPLICATION:
# "db.t3.small" = nouvelle classe (plus puissante)
# "--apply-immediately" = tout de suite (sinon: maintenance window)
# [ATTENTION] ATTENTION: ~1-2 minutes d'indisponibilité!

# Downgrade classe (économiser)
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --db-instance-class db.t3.micro

# Augmenter stockage (flexible, pas de downtime)
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --allocated-storage 100 \
  --apply-immediately

# EXPLICATION:
# Augmente tout de suite
# [ATTENTION] ON NE PEUT QUE AUGMENTER!

# Changer mot de passe
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --master-user-password NewSecurePassword456! \
  --apply-immediately

# EXPLICATION:
# Le nouveau mot de passe s'applique immédiatement
# L'ancien ne fonctionne plus

# Activer Multi-AZ (haute disponibilité)
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --multi-az \
  --apply-immediately

# EXPLICATION:
# Crée une replica en autre zone
# Failover automatique en cas de panne
# ~3-5 minutes de downtime lors de l'activation

# Désactiver Multi-AZ (économiser)
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --no-multi-az

# Changer security group
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --vpc-security-group-ids sg-newgroup0123456789

# Augmenter periode de backup
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --backup-retention-period 30

# Activer logs CloudWatch
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --enable-cloudwatch-logs-exports error,slowquery

# Activer Enhanced Monitoring (métriques détaillées)
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --monitoring-interval 60 \
  --monitoring-role-arn arn:aws:iam::123456789012:role/rds-monitoring

# Activer Performance Insights
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --enable-performance-insights \
  --performance-insights-retention-period 7


═══════════════════════════════════════════════════════════════════════════════
[OK] ARRÊTER, DÉMARRER, REDÉMARRER, SUPPRIMER
═══════════════════════════════════════════════════════════════════════════════

# ARRÊTER (économiser pendant 7 jours max)
aws rds stop-db-instance --db-instance-identifier mydb

# EXPLICATION:
# La DB s'arrête, données restent intactes
# Vous payez que pour le stockage (~$0.11/GB/mois)
# Plus de charge instance (~$0.02-0.30/h économisé)
# Max 7 jours, puis auto-redémarrage

# QUAND UTILISER:
# - Dev/test (arrêter le soir)
# - Staging (arrêter entre tests)
# - Économiser provisoirement

# DÉMARRER (après arrêt)
aws rds start-db-instance --db-instance-identifier mydb

# EXPLICATION:
# Redémarre une DB arrêtée
# ~30-60 secondes pour être prête

# REDÉMARRER (sans arrêt complet)
aws rds reboot-db-instance --db-instance-identifier mydb

# EXPLICATION:
# Redémarrage complet (comme Ctrl+Alt+Del)
# Connexions fermées, données intactes
# ~1-2 minutes d'indisponibilité

# QUAND UTILISER:
# - Après modification de parameter group
# - Problème connexion
# - Nettoyage mémoire

# SUPPRIMER (avec snapshot final - RECOMMANDÉ)
aws rds delete-db-instance \
  --db-instance-identifier mydb \
  --final-db-snapshot-identifier mydb-final-snapshot

# EXPLICATION:
# La DB est supprimée
# Snapshot créé avant suppression
# Vous pouvez restaurer plus tard si besoin
# [OK] TOUJOURS FAIRE ÇA!

# SUPPRIMER (sans snapshot - [ATTENTION] PERTE DE DONNÉES)
aws rds delete-db-instance \
  --db-instance-identifier mydb \
  --skip-final-snapshot

# [ATTENTION] ATTENTION: IRRÉVERSIBLE! PERTE DÉFINITIVE!
# NE FAITES ÇA QUE SI VOUS ÊTES 100% SÛR!


═══════════════════════════════════════════════════════════════════════════════
[OK] SAUVEGARDES ET SNAPSHOTS
═══════════════════════════════════════════════════════════════════════════════

BACKUPS AUTOMATIQUES vs SNAPSHOTS MANUELS:

Backups Auto:        Snapshots Manuels:
- Quotidien          - À la demande
- 1-35 jours         - Indéfinis
- Gratuit (inclus)   - Coûtent ($)
- PITR possible      - Pour archivage

# Créer snapshot MANUEL
aws rds create-db-snapshot \
  --db-instance-identifier mydb \
  --db-snapshot-identifier mydb-snapshot-$(date +%Y%m%d)

# Résultat: mydb-snapshot-20240115

# QUAND CRÉER:
# - Avant grosse modification
# - Avant suppression
# - Archive long-terme
# - Clone de prod pour test

# Lister TOUS les snapshots
aws rds describe-db-snapshots

# Lister snapshots spécifiques (manuels seulement)
aws rds describe-db-snapshots \
  --filters Name=db-instance-id,Values=mydb

# Format lisible
aws rds describe-db-snapshots \
  --query 'DBSnapshots[*].[DBSnapshotIdentifier,DBInstanceIdentifier,SnapshotCreateTime,AllocatedStorage]' \
  --output table

# Copier snapshot vers autre région (Disaster Recovery)
aws rds copy-db-snapshot \
  --source-db-snapshot-identifier arn:aws:rds:us-east-1:123456789012:snapshot:mydb-snapshot \
  --target-db-snapshot-identifier mydb-snapshot-eu \
  --region eu-west-1

# Restaurer DB à partir de snapshot
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier mydb-restored \
  --db-snapshot-identifier mydb-snapshot-20240115

# EXPLICATION:
# Crée une NOUVELLE DB à partir du snapshot
# Identique aux données du moment du snapshot
# Les deux BD coexistent (pas d'overwrite)

# UTILISATIONS:
# - Cloner prod pour test
# - Récupérer après erreur (avant snapshot)
# - Migrer entre régions

# Restaurer à point dans le temps (PITR)
aws rds restore-db-instance-to-point-in-time \
  --source-db-instance-identifier mydb \
  --target-db-instance-identifier mydb-pitr \
  --restore-time 2024-01-15T10:30:00Z

# EXPLICATION:
# Restaure à un moment PRÉCIS
# Pas besoin de snapshot pour ça
# Les backups auto doivent couvrir cette période

# EXEMPLE:
# À 14h30 vous avez supprimé 1000 clients par erreur
# Vous faites: PITR à 14h25 (avant l'erreur)
# Vous récupérez les 1000 clients!

# Supprimer un snapshot (économiser)
aws rds delete-db-snapshot \
  --db-snapshot-identifier mydb-snapshot-20240115

# [ATTENTION] ATTENTION: IRRÉVERSIBLE! Pas de snapshot = pas de récupération!


═══════════════════════════════════════════════════════════════════════════════
[OK] READ REPLICAS (SCALING EN LECTURE)
═══════════════════════════════════════════════════════════════════════════════

READ REPLICA = Copie en lecture seule (asynchrone)

UTILITÉ:
- Réduire charge sur DB principale
- Rapports lourds sans impact prod
- Géographique failover
- Haute disponibilité

# Créer read replica (même région)
aws rds create-db-instance-read-replica \
  --db-instance-identifier mydb-replica \
  --source-db-instance-identifier mydb \
  --db-instance-class db.t3.micro

# Créer read replica (autre région)
aws rds create-db-instance-read-replica \
  --db-instance-identifier mydb-replica-eu \
  --source-db-instance-identifier mydb \
  --source-region us-east-1 \
  --region eu-west-1 \
  --db-instance-class db.t3.micro

# EXPLICATION:
# La replica est créée dans eu-west-1
# Réplication asynchrone depuis us-east-1
# Décalage: quelques secondes à ms

# Promouvoir replica en DB indépendante
aws rds promote-read-replica \
  --db-instance-identifier mydb-replica

# EXPLICATION:
# La replica devient indépendante
# Plus liée à la source
# Vous pouvez l'écrire maintenant

# UTILITÉ:
# - Avant migration
# - Test sur copie de prod
# - Créer une seconde DB


═══════════════════════════════════════════════════════════════════════════════
[OK] PARAMETER GROUPS (CONFIGURATION AVANCÉE)
═══════════════════════════════════════════════════════════════════════════════

PARAMETER GROUPS = Configuration BD (max_connections, charset, etc.)

# Créer custom parameter group
aws rds create-db-parameter-group \
  --db-parameter-group-name mydb-params \
  --db-parameter-group-family mysql8.0 \
  --description "Custom MySQL 8.0 parameters"

# EXPLICATION:
# "mysql8.0" = Famille (MySQL 8.0 seulement compatible)
# Doit correspondre à votre version moteur

# Modifier paramètres
aws rds modify-db-parameter-group \
  --db-parameter-group-name mydb-params \
  --parameters "ParameterName=max_connections,ParameterValue=500,ApplyMethod=immediate"

# Paramètres courants MySQL:
# max_connections = Max connexions (défaut: 152)
# character_set_server = Charset (utf8mb4)
# slow_query_log = Log requêtes lentes (0/1)
# long_query_time = Seuil requête lente (2 secondes)

# Appliquer parameter group à DB
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --db-parameter-group-name mydb-params

# Lister parameter groups
aws rds describe-db-parameter-groups

# Voir paramètres d'un group
aws rds describe-db-parameters \
  --db-parameter-group-name mydb-params

# Voir seulement paramètres MODIFIÉS
aws rds describe-db-parameters \
  --db-parameter-group-name mydb-params \
  --filters Name=isModified,Values=true


═══════════════════════════════════════════════════════════════════════════════
[OK] SE CONNECTER À LA BASE DE DONNÉES
═══════════════════════════════════════════════════════════════════════════════

Avant: Obtenir l'endpoint!
aws rds describe-db-instances \
  --db-instance-identifier mydb \
  --query 'DBInstances[0].Endpoint.Address' \
  --output text

# Résultat: mydb.c9akciq32.us-east-1.rds.amazonaws.com

# === MYSQL EN LIGNE DE COMMANDE ===

# Se connecter avec MySQL CLI
mysql -h mydb.c9akciq32.us-east-1.rds.amazonaws.com \
  -P 3306 \
  -u admin \
  -p

# Invite pour mot de passe (plus sécurisé)
# Taper: MySecurePassword123!
# Vous êtes connecté!

# Commandes MySQL basiques:
# SHOW DATABASES;           = Lister bases
# USE mydb;                 = Utiliser base
# CREATE TABLE users (...); = Créer table
# SHOW TABLES;              = Lister tables
# SELECT * FROM users;      = Voir données
# INSERT INTO users ...;    = Ajouter données
# exit;                     = Quitter

# === POSTGRESQL EN LIGNE DE COMMANDE ===

# Se connecter avec psql
psql -h mypgdb.c9akciq32.us-east-1.rds.amazonaws.com \
  -p 5432 \
  -U postgres \
  -d postgres

# Invite pour mot de passe
# Taper: MySecurePassword123!
# Vous êtes connecté!

# Commandes PostgreSQL basiques:
# \l                 = Lister bases
# \c mydb            = Changer base
# \dt                = Lister tables
# \d table_name      = Structure table
# SELECT * FROM ...  = Requête
# \q                 = Quitter

# === DEPUIS PYTHON (MySQL) ===

import pymysql
import os

# Sécurisé: variables d'environnement
host = os.environ.get('DB_HOST')
user = os.environ.get('DB_USER')
password = os.environ.get('DB_PASSWORD')
database = os.environ.get('DB_NAME')

try:
    connection = pymysql.connect(
        host=host,
        user=user,
        password=password,
        database=database,
        port=3306,
        cursorclass=pymysql.cursors.DictCursor
    )
    
    with connection:
        with connection.cursor() as cursor:
            cursor.execute("SELECT * FROM users;")
            result = cursor.fetchall()
            print(result)
            
except pymysql.Error as e:
    print(f"Erreur: {e}")

# Installation:
# pip install pymysql

# === DEPUIS PYTHON (PostgreSQL) ===

import psycopg2
import os

host = os.environ.get('DB_HOST')
user = os.environ.get('DB_USER')
password = os.environ.get('DB_PASSWORD')
database = os.environ.get('DB_NAME')

try:
    connection = psycopg2.connect(
        host=host,
        user=user,
        password=password,
        database=database,
        port=5432
    )
    
    cursor = connection.cursor()
    cursor.execute("SELECT * FROM users;")
    result = cursor.fetchall()
    print(result)
    cursor.close()
    connection.close()
    
except psycopg2.Error as e:
    print(f"Erreur: {e}")

# Installation:
# pip install psycopg2-binary

# === DEPUIS PYTHON (SQLAlchemy - RECOMMANDÉ) ===

from sqlalchemy import create_engine
import os

# MySQL
engine = create_engine(
    f"mysql+pymysql://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}"
    f"@{os.getenv('DB_HOST')}/{os.getenv('DB_NAME')}"
)

# PostgreSQL
engine = create_engine(
    f"postgresql+psycopg2://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}"
    f"@{os.getenv('DB_HOST')}/{os.getenv('DB_NAME')}"
)

# Tester connexion
with engine.connect() as conn:
    result = conn.execute("SELECT 1")
    print(result.fetchone())

# Installation:
# pip install sqlalchemy pymysql psycopg2-binary

# [ATTENTION] SÉCURITÉ: Variables d'environnement
# NE METTEZ JAMAIS le mot de passe en dur!
# Créer .env (git ignore) ou utiliser AWS Secrets Manager


═══════════════════════════════════════════════════════════════════════════════
[OK] INTÉGRATION FLASK + RDS - GUIDE COMPLET
═══════════════════════════════════════════════════════════════════════════════

OBJECTIF: Créer une application Flask qui communique avec RDS

ÉTAPES:
1. Préparer RDS (créer DB, endpoint, credentials)
2. Installer dépendances Python
3. Configurer connexion (variables d'env, secrets)
4. Créer modèles de données
5. Écrire routes Flask
6. Tester l'intégration
7. Déployer

ÉTAPE 1: PRÉPARER RDS
════════════════════════════════════════════════════════════════════════════════

# Créer security group (depuis machine locale ou EC2)
SG_ID=$(aws ec2 create-security-group \
  --group-name flask-rds-sg \
  --description "Security group for Flask + RDS" \
  --query 'GroupId' \
  --output text)

echo "Security Group créé: $SG_ID"

# Autoriser depuis votre IP (DEV SEULEMENT!)
YOUR_IP="203.0.113.25"  # Remplacer par VOTRE IP
aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --protocol tcp \
  --port 3306 \
  --cidr $YOUR_IP/32

# Créer DB MySQL
aws rds create-db-instance \
  --db-instance-identifier flask-app-db \
  --db-instance-class db.t3.micro \
  --engine mysql \
  --engine-version 8.0.35 \
  --master-username admin \
  --master-user-password SecureFlaskPassword123! \
  --allocated-storage 20 \
  --storage-type gp3 \
  --vpc-security-group-ids $SG_ID \
  --backup-retention-period 7 \
  --publicly-accessible

# Attendre que DB soit ready
echo "Attendre ~10 minutes..."
aws rds wait db-instance-available \
  --db-instance-identifier flask-app-db

# Récupérer l'endpoint
ENDPOINT=$(aws rds describe-db-instances \
  --db-instance-identifier flask-app-db \
  --query 'DBInstances[0].Endpoint.Address' \
  --output text)

echo "Endpoint: $ENDPOINT"
echo "Host: $ENDPOINT"
echo "Port: 3306"
echo "User: admin"
echo "Password: SecureFlaskPassword123!"


ÉTAPE 2: INSTALLER DÉPENDANCES PYTHON
════════════════════════════════════════════════════════════════════════════════

# Créer environnement virtuel
python3 -m venv venv
source venv/bin/activate  # Linux/Mac
# venv\Scripts\activate  # Windows

# Créer requirements.txt
cat > requirements.txt << 'EOF'
Flask==2.3.0
Flask-MySQL==1.5.2
python-dotenv==1.0.0
pymysql==1.1.0
SQLAlchemy==2.0.0
Flask-SQLAlchemy==3.0.0
EOF

# Installer dépendances
pip install -r requirements.txt

# EXPLICATIONS:
# Flask              = Framework web
# Flask-MySQL        = Intégration MySQL simple
# python-dotenv      = Charger variables .env
# pymysql            = Driver MySQL
# SQLAlchemy         = ORM (Object Relational Mapping)
# Flask-SQLAlchemy   = Extension Flask pour SQLAlchemy


ÉTAPE 3: CONFIGURER CONNEXION (Variables d'environnement)
════════════════════════════════════════════════════════════════════════════════

# Créer fichier .env (NE PAS COMMIT DANS GIT!)
cat > .env << 'EOF'
# RDS Configuration
DB_HOST=flask-app-db.c9akciq32.us-east-1.rds.amazonaws.com
DB_PORT=3306
DB_USER=admin
DB_PASSWORD=SecureFlaskPassword123!
DB_NAME=flask_app

# Flask
FLASK_ENV=development
FLASK_DEBUG=1
SECRET_KEY=your-super-secret-key-change-this

# AWS (optionnel)
AWS_REGION=us-east-1
EOF

# Ajouter .env à .gitignore
echo ".env" >> .gitignore

# EXPLICATIONS:
# Ces variables seront chargées par python-dotenv
# Jamais exposées en dur dans le code
# Facile à changer par environnement (dev/prod)


ÉTAPE 4: CRÉER FICHIER app.py (PRINCIPALE)
════════════════════════════════════════════════════════════════════════════════

# app.py

import os
from flask import Flask, render_template, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv
from datetime import datetime

# Charger variables d'environnement
load_dotenv()

# Initialiser Flask
app = Flask(__name__)

# Configurer SQLAlchemy
app.config['SQLALCHEMY_DATABASE_URI'] = (
    f"mysql+pymysql://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}"
    f"@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

# EXPLICATIONS:
# SQLALCHEMY_DATABASE_URI = URL de connexion complète
#   Format: mysql+pymysql://user:password@host:port/database
# SQLALCHEMY_TRACK_MODIFICATIONS = Désactiver warning inutile

# Initialiser SQLAlchemy (ORM)
db = SQLAlchemy(app)

# EXPLICATIONS:
# SQLAlchemy = "Langage Python pour bases de données"
# Permet de définir tables comme classes Python
# Pas besoin d'écrire SQL directement!


ÉTAPE 5: DÉFINIR MODÈLES DE DONNÉES (Models)
════════════════════════════════════════════════════════════════════════════════

# Ajouter à app.py (après initialiser db)

# Modèle Utilisateur
class User(db.Model):
    __tablename__ = 'users'  # Nom de la table en BD
    
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(255), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    # Relation avec Posts
    posts = db.relationship('Post', backref='author', lazy=True, cascade='all, delete-orphan')
    
    def __repr__(self):
        return f'<User {self.username}>'
    
    def to_dict(self):
        return {
            'id': self.id,
            'username': self.username,
            'email': self.email,
            'created_at': self.created_at.isoformat()
        }

# EXPLICATIONS:
# __tablename__         = Nom table en BD (users)
# db.Column             = Colonne de table
# db.Integer            = Type: entier
# db.String(80)         = Type: texte max 80 caractères
# primary_key=True      = Clé primaire
# unique=True           = Valeur unique (pas doublons)
# nullable=False        = Obligatoire (pas NULL)
# default=datetime.utcnow = Valeur par défaut
# onupdate=datetime.utcnow = Auto-mise à jour
# db.relationship       = Lien vers autre table
# cascade='all,delete-orphan' = Supprimer posts si user supprimé
# to_dict()             = Convertir en JSON


# Modèle Article (Post)
class Post(db.Model):
    __tablename__ = 'posts'
    
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    def __repr__(self):
        return f'<Post {self.title}>'
    
    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'content': self.content,
            'author': self.author.username,
            'created_at': self.created_at.isoformat()
        }

# EXPLICATIONS:
# db.ForeignKey('users.id') = Lien vers table users (contrainte BD)
# Garantit intégrité: user_id doit exister


ÉTAPE 6: ROUTES FLASK (API)
════════════════════════════════════════════════════════════════════════════════

# Ajouter à app.py (après modèles)

# === ROUTE: Créer les tables ===
@app.route('/init-db', methods=['POST'])
def init_db():
    """Créer les tables si elles n'existent pas"""
    try:
        db.create_all()
        return jsonify({
            'status': 'success',
            'message': 'Tables créées avec succès'
        }), 201
    except Exception as e:
        return jsonify({
            'status': 'error',
            'message': str(e)
        }), 500

# EXPLICATION:
# POST /init-db = Crée les tables en BD
# db.create_all() = Crée toutes les tables définies
# À faire UNE SEULE FOIS!

# === ROUTE: Créer utilisateur ===
@app.route('/api/users', methods=['POST'])
def create_user():
    """Créer un nouvel utilisateur"""
    data = request.get_json()
    
    # Validation
    if not data or not all(k in data for k in ['username', 'email', 'password']):
        return jsonify({'error': 'Données manquantes'}), 400
    
    # Vérifier si user existe déjà
    if User.query.filter_by(username=data['username']).first():
        return jsonify({'error': 'Username existant'}), 400
    
    if User.query.filter_by(email=data['email']).first():
        return jsonify({'error': 'Email existant'}), 400
    
    # Créer nouvel utilisateur
    try:
        user = User(
            username=data['username'],
            email=data['email'],
            password=data['password']  # À HASHER en production!
        )
        db.session.add(user)
        db.session.commit()
        
        return jsonify({
            'status': 'success',
            'user': user.to_dict()
        }), 201
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

# EXPLICATION:
# POST /api/users = Crée utilisateur via JSON
# request.get_json() = Récupère données POST
# db.session.add() = Préparer insertion
# db.session.commit() = Confirmer insertion
# db.session.rollback() = Annuler si erreur

# === ROUTE: Lister tous les utilisateurs ===
@app.route('/api/users', methods=['GET'])
def get_users():
    """Récupérer tous les utilisateurs"""
    try:
        users = User.query.all()
        return jsonify({
            'status': 'success',
            'users': [user.to_dict() for user in users]
        }), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# EXPLICATION:
# GET /api/users = Liste tous les users
# User.query.all() = SELECT * FROM users;

# === ROUTE: Récupérer un utilisateur ===
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    """Récupérer un utilisateur par ID"""
    try:
        user = User.query.get(user_id)
        if not user:
            return jsonify({'error': 'Utilisateur non trouvé'}), 404
        
        return jsonify({
            'status': 'success',
            'user': user.to_dict(),
            'posts': [post.to_dict() for post in user.posts]
        }), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# EXPLICATION:
# GET /api/users/1 = Récupère user ID 1
# User.query.get(user_id) = SELECT * FROM users WHERE id = ?

# === ROUTE: Modifier un utilisateur ===
@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    """Modifier un utilisateur"""
    data = request.get_json()
    
    try:
        user = User.query.get(user_id)
        if not user:
            return jsonify({'error': 'Utilisateur non trouvé'}), 404
        
        if 'email' in data:
            user.email = data['email']
        if 'username' in data:
            user.username = data['username']
        
        db.session.commit()
        
        return jsonify({
            'status': 'success',
            'user': user.to_dict()
        }), 200
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

# EXPLICATION:
# PUT /api/users/1 = Modifie user ID 1
# Modifie colonnes spécifiées

# === ROUTE: Supprimer un utilisateur ===
@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    """Supprimer un utilisateur"""
    try:
        user = User.query.get(user_id)
        if not user:
            return jsonify({'error': 'Utilisateur non trouvé'}), 404
        
        db.session.delete(user)
        db.session.commit()
        
        return jsonify({
            'status': 'success',
            'message': f'Utilisateur {user_id} supprimé'
        }), 200
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

# EXPLICATION:
# DELETE /api/users/1 = Supprime user ID 1
# db.session.delete() = Marquer pour suppression
# CASCADE = Les posts sont aussi supprimés

# === ROUTE: Créer un article ===
@app.route('/api/posts', methods=['POST'])
def create_post():
    """Créer un nouvel article"""
    data = request.get_json()
    
    if not data or not all(k in data for k in ['title', 'content', 'user_id']):
        return jsonify({'error': 'Données manquantes'}), 400
    
    try:
        # Vérifier que l'utilisateur existe
        user = User.query.get(data['user_id'])
        if not user:
            return jsonify({'error': 'Utilisateur non trouvé'}), 404
        
        post = Post(
            title=data['title'],
            content=data['content'],
            user_id=data['user_id']
        )
        db.session.add(post)
        db.session.commit()
        
        return jsonify({
            'status': 'success',
            'post': post.to_dict()
        }), 201
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

# === ROUTE: Récupérer tous les articles ===
@app.route('/api/posts', methods=['GET'])
def get_posts():
    """Récupérer tous les articles"""
    try:
        posts = Post.query.all()
        return jsonify({
            'status': 'success',
            'posts': [post.to_dict() for post in posts]
        }), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# === ROUTE: Health Check ===
@app.route('/health', methods=['GET'])
def health():
    """Vérifier que l'app et BD sont OK"""
    try:
        # Tester connexion BD
        db.session.execute('SELECT 1')
        return jsonify({
            'status': 'healthy',
            'database': 'connected'
        }), 200
    except Exception as e:
        return jsonify({
            'status': 'unhealthy',
            'error': str(e)
        }), 500

# EXPLICATION:
# GET /health = Vérifier l'app
# SELECT 1 = Requête simple pour tester connexion

# === ROUTE: Accueil ===
@app.route('/', methods=['GET'])
def index():
    """Page d'accueil avec docs API"""
    return jsonify({
        'message': 'API Flask + RDS',
        'version': '1.0',
        'endpoints': {
            'POST /init-db': 'Créer les tables',
            'POST /api/users': 'Créer utilisateur',
            'GET /api/users': 'Lister utilisateurs',
            'GET /api/users/<id>': 'Récupérer utilisateur',
            'PUT /api/users/<id>': 'Modifier utilisateur',
            'DELETE /api/users/<id>': 'Supprimer utilisateur',
            'POST /api/posts': 'Créer article',
            'GET /api/posts': 'Lister articles',
            'GET /health': 'Vérifier santé app'
        }
    }), 200


ÉTAPE 7: LANCER L'APPLICATION
════════════════════════════════════════════════════════════════════════════════

# Ajouter à la fin de app.py

if __name__ == '__main__':
    with app.app_context():
        print("Connecté à la base de données...")
        try:
            # Test connexion
            db.session.execute('SELECT 1')
            print("[OK] Connexion OK!")
        except Exception as e:
            print(f"[X] Erreur connexion: {e}")
    
    # Lancer le serveur
    app.run(
        host='0.0.0.0',
        port=5000,
        debug=True
    )

# EXPLICATION:
# host='0.0.0.0' = Accessible depuis n'importe où (DEV SEULEMENT!)
# port=5000 = Port HTTP
# debug=True = Rechargement auto, meilleur error display


ÉTAPE 8: TESTER L'APPLICATION
════════════════════════════════════════════════════════════════════════════════

# Terminal 1: Lancer Flask
python app.py

# Résultat:
# * Running on http://127.0.0.1:5000

# Terminal 2: Tester l'API

# 1. Initialiser la base
curl -X POST http://localhost:5000/init-db

# 2. Vérifier santé
curl http://localhost:5000/health

# 3. Créer un utilisateur
curl -X POST http://localhost:5000/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "email": "alice@example.com",
    "password": "password123"
  }'

# 4. Lister utilisateurs
curl http://localhost:5000/api/users

# 5. Récupérer un utilisateur
curl http://localhost:5000/api/users/1

# 6. Créer un article
curl -X POST http://localhost:5000/api/posts \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Mon premier post",
    "content": "Contenu du post",
    "user_id": 1
  }'

# 7. Récupérer articles
curl http://localhost:5000/api/posts


ÉTAPE 9: FICHIER COMPLET app.py
════════════════════════════════════════════════════════════════════════════════

# app.py (VERSION COMPLÈTE)

import os
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from dotenv import load_dotenv
from datetime import datetime

load_dotenv()

app = Flask(__name__)

# Configuration base de données
app.config['SQLALCHEMY_DATABASE_URI'] = (
    f"mysql+pymysql://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}"
    f"@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')

db = SQLAlchemy(app)

# ===== MODÈLES =====

class User(db.Model):
    __tablename__ = 'users'
    
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    password = db.Column(db.String(255), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    posts = db.relationship('Post', backref='author', cascade='all, delete-orphan')
    
    def to_dict(self):
        return {
            'id': self.id,
            'username': self.username,
            'email': self.email,
            'created_at': self.created_at.isoformat()
        }

class Post(db.Model):
    __tablename__ = 'posts'
    
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    
    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'content': self.content,
            'author': self.author.username,
            'created_at': self.created_at.isoformat()
        }

# ===== ROUTES =====

@app.route('/', methods=['GET'])
def index():
    return jsonify({'message': 'API Flask + RDS'}), 200

@app.route('/init-db', methods=['POST'])
def init_db():
    try:
        db.create_all()
        return jsonify({'status': 'success', 'message': 'Tables créées'}), 201
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/health', methods=['GET'])
def health():
    try:
        db.session.execute('SELECT 1')
        return jsonify({'status': 'healthy', 'database': 'connected'}), 200
    except Exception as e:
        return jsonify({'status': 'unhealthy', 'error': str(e)}), 500

@app.route('/api/users', methods=['POST'])
def create_user():
    data = request.get_json()
    if not data or not all(k in data for k in ['username', 'email', 'password']):
        return jsonify({'error': 'Données manquantes'}), 400
    
    try:
        user = User(username=data['username'], email=data['email'], password=data['password'])
        db.session.add(user)
        db.session.commit()
        return jsonify({'status': 'success', 'user': user.to_dict()}), 201
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

@app.route('/api/users', methods=['GET'])
def get_users():
    try:
        users = User.query.all()
        return jsonify({'users': [u.to_dict() for u in users]}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    try:
        user = User.query.get(user_id)
        if not user:
            return jsonify({'error': 'Non trouvé'}), 404
        return jsonify({'user': user.to_dict()}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500

@app.route('/api/users/<int:user_id>', methods=['PUT'])
def update_user(user_id):
    data = request.get_json()
    try:
        user = User.query.get(user_id)
        if not user:
            return jsonify({'error': 'Non trouvé'}), 404
        if 'email' in data:
            user.email = data['email']
        db.session.commit()
        return jsonify({'user': user.to_dict()}), 200
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

@app.route('/api/users/<int:user_id>', methods=['DELETE'])
def delete_user(user_id):
    try:
        user = User.query.get(user_id)
        if not user:
            return jsonify({'error': 'Non trouvé'}), 404
        db.session.delete(user)
        db.session.commit()
        return jsonify({'status': 'supprimé'}), 200
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

@app.route('/api/posts', methods=['POST'])
def create_post():
    data = request.get_json()
    if not all(k in data for k in ['title', 'content', 'user_id']):
        return jsonify({'error': 'Données manquantes'}), 400
    try:
        post = Post(title=data['title'], content=data['content'], user_id=data['user_id'])
        db.session.add(post)
        db.session.commit()
        return jsonify({'post': post.to_dict()}), 201
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

@app.route('/api/posts', methods=['GET'])
def get_posts():
    try:
        posts = Post.query.all()
        return jsonify({'posts': [p.to_dict() for p in posts]}), 200
    except Exception as e:
        return jsonify({'error': str(e)}), 500

if __name__ == '__main__':
    with app.app_context():
        try:
            db.session.execute('SELECT 1')
            print("[OK] Connexion BD OK!")
        except Exception as e:
            print(f"[X] Erreur: {e}")
    app.run(host='0.0.0.0', port=5000, debug=True)



# ============================================================================
# EXEMPLE COMPLET: Application Flask + RDS
# ============================================================================
# Cette application Blog permet:
# - Créer/lire/modifier/supprimer utilisateurs
# - Créer/lire/modifier/supprimer articles
# - Authentification JWT
# - Validation et gestion d'erreurs
# ============================================================================

# === 1. FICHIER: requirements.txt ===
"""
Flask==2.3.0
Flask-SQLAlchemy==3.0.0
Flask-JWT-Extended==4.4.4
Flask-CORS==4.0.0
python-dotenv==1.0.0
pymysql==1.1.0
werkzeug==2.3.0
"""

# === 2. FICHIER: .env ===
"""
# RDS Configuration
DB_HOST=your-db.c9akciq32.us-east-1.rds.amazonaws.com
DB_PORT=3306
DB_USER=admin
DB_PASSWORD=YourSecurePassword123!
DB_NAME=flask_blog

# Flask
FLASK_ENV=development
FLASK_DEBUG=1
SECRET_KEY=change-this-to-something-very-secret-in-production
JWT_SECRET_KEY=jwt-secret-key-change-this-too

# API
API_TITLE=Blog API
API_VERSION=1.0.0
"""

# === 3. FICHIER: app.py (APPLICATION PRINCIPALE) ===

import os
from datetime import datetime
from functools import wraps
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_jwt_extended import JWTManager, create_access_token, jwt_required, get_jwt_identity
from flask_cors import CORS
from werkzeug.security import generate_password_hash, check_password_hash
from dotenv import load_dotenv

# Charger variables d'environnement
load_dotenv()

# ========== INITIALISATION FLASK ==========

app = Flask(__name__)

# Configuration
app.config['SQLALCHEMY_DATABASE_URI'] = (
    f"mysql+pymysql://{os.getenv('DB_USER')}:{os.getenv('DB_PASSWORD')}"
    f"@{os.getenv('DB_HOST')}:{os.getenv('DB_PORT')}/{os.getenv('DB_NAME')}"
)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = os.getenv('SECRET_KEY')
app.config['JWT_SECRET_KEY'] = os.getenv('JWT_SECRET_KEY')

# Initialiser extensions
db = SQLAlchemy(app)
jwt = JWTManager(app)
CORS(app)

# ========== MODÈLES BD ==========

class User(db.Model):
    """Modèle utilisateur"""
    __tablename__ = 'users'
    
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False, index=True)
    email = db.Column(db.String(120), unique=True, nullable=False, index=True)
    password_hash = db.Column(db.String(255), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    
    # Relations
    articles = db.relationship('Article', backref='author', lazy=True, cascade='all, delete-orphan')
    
    def set_password(self, password):
        """Hash et stocke le mot de passe"""
        self.password_hash = generate_password_hash(password, method='pbkdf2:sha256')
    
    def check_password(self, password):
        """Vérifie si le mot de passe correspond"""
        return check_password_hash(self.password_hash, password)
    
    def to_dict(self, include_email=False):
        """Convertir en dictionnaire JSON"""
        data = {
            'id': self.id,
            'username': self.username,
            'created_at': self.created_at.isoformat(),
            'articles_count': len(self.articles)
        }
        if include_email:
            data['email'] = self.email
        return data

class Article(db.Model):
    """Modèle article/blog post"""
    __tablename__ = 'articles'
    
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(200), nullable=False, index=True)
    content = db.Column(db.Text, nullable=False)
    user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    created_at = db.Column(db.DateTime, default=datetime.utcnow, index=True)
    updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
    views_count = db.Column(db.Integer, default=0)
    
    def to_dict(self, include_content=False):
        """Convertir en dictionnaire JSON"""
        data = {
            'id': self.id,
            'title': self.title,
            'author': self.author.username,
            'author_id': self.user_id,
            'views_count': self.views_count,
            'created_at': self.created_at.isoformat(),
            'updated_at': self.updated_at.isoformat()
        }
        if include_content:
            data['content'] = self.content
        return data

# ========== ROUTES: UTILISATEURS ==========

@app.route('/api/auth/register', methods=['POST'])
def register():
    """Créer un nouvel utilisateur"""
    data = request.get_json()
    
    # Validation
    if not data or not all(k in data for k in ['username', 'email', 'password']):
        return jsonify({'error': 'username, email et password obligatoires'}), 400
    
    if len(data['username']) < 3 or len(data['username']) > 80:
        return jsonify({'error': 'username: 3-80 caractères'}), 400
    
    if len(data['password']) < 8:
        return jsonify({'error': 'Mot de passe: minimum 8 caractères'}), 400
    
    # Vérifier doublons
    if User.query.filter_by(username=data['username']).first():
        return jsonify({'error': 'Username existant'}), 409
    
    if User.query.filter_by(email=data['email']).first():
        return jsonify({'error': 'Email existant'}), 409
    
    try:
        user = User(username=data['username'], email=data['email'])
        user.set_password(data['password'])
        
        db.session.add(user)
        db.session.commit()
        
        return jsonify({
            'message': 'Utilisateur créé avec succès',
            'user': user.to_dict(include_email=True)
        }), 201
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': f'Erreur création: {str(e)}'}), 500

@app.route('/api/auth/login', methods=['POST'])
def login():
    """Authentification et génération JWT"""
    data = request.get_json()
    
    if not data or not all(k in data for k in ['username', 'password']):
        return jsonify({'error': 'username et password obligatoires'}), 400
    
    user = User.query.filter_by(username=data['username']).first()
    
    if not user or not user.check_password(data['password']):
        return jsonify({'error': 'Identifiants invalides'}), 401
    
    access_token = create_access_token(identity=user.id)
    
    return jsonify({
        'message': 'Connexion réussie',
        'access_token': access_token,
        'user': user.to_dict()
    }), 200

@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user(user_id):
    """Récupérer profil utilisateur"""
    user = User.query.get(user_id)
    
    if not user:
        return jsonify({'error': 'Utilisateur non trouvé'}), 404
    
    return jsonify({
        'user': user.to_dict(include_email=True),
        'articles': [a.to_dict() for a in user.articles]
    }), 200

@app.route('/api/users', methods=['GET'])
def list_users():
    """Lister tous les utilisateurs (avec pagination)"""
    page = request.args.get('page', 1, type=int)
    per_page = request.args.get('per_page', 10, type=int)
    
    paginated = User.query.paginate(page=page, per_page=per_page)
    
    return jsonify({
        'users': [u.to_dict() for u in paginated.items],
        'pagination': {
            'total': paginated.total,
            'pages': paginated.pages,
            'current_page': page,
            'per_page': per_page
        }
    }), 200

@app.route('/api/users/<int:user_id>', methods=['PUT'])
@jwt_required()
def update_user(user_id):
    """Modifier profil utilisateur (authentifié)"""
    current_user_id = get_jwt_identity()
    
    if current_user_id != user_id:
        return jsonify({'error': 'Non autorisé'}), 403
    
    user = User.query.get(user_id)
    if not user:
        return jsonify({'error': 'Utilisateur non trouvé'}), 404
    
    data = request.get_json()
    
    try:
        if 'email' in data:
            # Vérifier que email n'existe pas
            if User.query.filter_by(email=data['email']).filter(User.id != user_id).first():
                return jsonify({'error': 'Email déjà utilisé'}), 409
            user.email = data['email']
        
        if 'password' in data and len(data['password']) >= 8:
            user.set_password(data['password'])
        
        db.session.commit()
        
        return jsonify({'user': user.to_dict(include_email=True)}), 200
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

@app.route('/api/users/<int:user_id>', methods=['DELETE'])
@jwt_required()
def delete_user(user_id):
    """Supprimer compte utilisateur"""
    current_user_id = get_jwt_identity()
    
    if current_user_id != user_id:
        return jsonify({'error': 'Non autorisé'}), 403
    
    user = User.query.get(user_id)
    if not user:
        return jsonify({'error': 'Utilisateur non trouvé'}), 404
    
    try:
        db.session.delete(user)
        db.session.commit()
        return jsonify({'message': 'Compte supprimé'}), 200
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

# ========== ROUTES: ARTICLES ==========

@app.route('/api/articles', methods=['POST'])
@jwt_required()
def create_article():
    """Créer un nouvel article"""
    user_id = get_jwt_identity()
    data = request.get_json()
    
    if not data or not all(k in data for k in ['title', 'content']):
        return jsonify({'error': 'title et content obligatoires'}), 400
    
    if len(data['title']) < 5 or len(data['title']) > 200:
        return jsonify({'error': 'title: 5-200 caractères'}), 400
    
    try:
        article = Article(
            title=data['title'],
            content=data['content'],
            user_id=user_id
        )
        
        db.session.add(article)
        db.session.commit()
        
        return jsonify({
            'message': 'Article créé',
            'article': article.to_dict(include_content=True)
        }), 201
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

@app.route('/api/articles', methods=['GET'])
def list_articles():
    """Lister les articles (avec pagination et filtrage)"""
    page = request.args.get('page', 1, type=int)
    per_page = request.args.get('per_page', 10, type=int)
    user_id = request.args.get('user_id', type=int)
    
    query = Article.query
    
    if user_id:
        query = query.filter_by(user_id=user_id)
    
    query = query.order_by(Article.created_at.desc())
    paginated = query.paginate(page=page, per_page=per_page)
    
    return jsonify({
        'articles': [a.to_dict() for a in paginated.items],
        'pagination': {
            'total': paginated.total,
            'pages': paginated.pages,
            'current_page': page
        }
    }), 200

@app.route('/api/articles/<int:article_id>', methods=['GET'])
def get_article(article_id):
    """Récupérer un article (augmente compteur de vues)"""
    article = Article.query.get(article_id)
    
    if not article:
        return jsonify({'error': 'Article non trouvé'}), 404
    
    # Incrémenter compteur de vues
    article.views_count += 1
    db.session.commit()
    
    return jsonify({'article': article.to_dict(include_content=True)}), 200

@app.route('/api/articles/<int:article_id>', methods=['PUT'])
@jwt_required()
def update_article(article_id):
    """Modifier un article (seulement l'auteur)"""
    user_id = get_jwt_identity()
    article = Article.query.get(article_id)
    
    if not article:
        return jsonify({'error': 'Article non trouvé'}), 404
    
    if article.user_id != user_id:
        return jsonify({'error': 'Non autorisé'}), 403
    
    data = request.get_json()
    
    try:
        if 'title' in data:
            article.title = data['title']
        if 'content' in data:
            article.content = data['content']
        
        db.session.commit()
        return jsonify({'article': article.to_dict(include_content=True)}), 200
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

@app.route('/api/articles/<int:article_id>', methods=['DELETE'])
@jwt_required()
def delete_article(article_id):
    """Supprimer un article (seulement l'auteur)"""
    user_id = get_jwt_identity()
    article = Article.query.get(article_id)
    
    if not article:
        return jsonify({'error': 'Article non trouvé'}), 404
    
    if article.user_id != user_id:
        return jsonify({'error': 'Non autorisé'}), 403
    
    try:
        db.session.delete(article)
        db.session.commit()
        return jsonify({'message': 'Article supprimé'}), 200
    except Exception as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 500

# ========== ROUTES: UTILITAIRES ==========

@app.route('/', methods=['GET'])
def index():
    """Documentation API"""
    return jsonify({
        'title': 'Blog API Flask + RDS',
        'version': '1.0',
        'documentation': 'https://your-api.com/docs',
        'endpoints': {
            'auth': {
                'POST /api/auth/register': 'Créer compte',
                'POST /api/auth/login': 'Se connecter (JWT)'
            },
            'users': {
                'GET /api/users': 'Lister utilisateurs',
                'GET /api/users/<id>': 'Profil utilisateur',
                'PUT /api/users/<id>': 'Modifier profil (auth)',
                'DELETE /api/users/<id>': 'Supprimer compte (auth)'
            },
            'articles': {
                'GET /api/articles': 'Lister articles',
                'GET /api/articles/<id>': 'Lire article',
                'POST /api/articles': 'Créer article (auth)',
                'PUT /api/articles/<id>': 'Modifier article (auth)',
                'DELETE /api/articles/<id>': 'Supprimer article (auth)'
            }
        }
    }), 200

@app.route('/health', methods=['GET'])
def health():
    """Vérifier santé app et BD"""
    try:
        db.session.execute('SELECT 1')
        return jsonify({
            'status': 'healthy',
            'database': 'connected',
            'timestamp': datetime.utcnow().isoformat()
        }), 200
    except Exception as e:
        return jsonify({
            'status': 'unhealthy',
            'error': str(e)
        }), 503

@app.route('/api/init', methods=['POST'])
def init_db():
    """Initialiser les tables (RUN ONCE!)"""
    try:
        db.create_all()
        return jsonify({'message': 'Tables créées'}), 201
    except Exception as e:
        return jsonify({'error': str(e)}), 500

# ========== GESTION D'ERREURS ==========

@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Ressource non trouvée'}), 404

@app.errorhandler(500)
def internal_error(error):
    db.session.rollback()
    return jsonify({'error': 'Erreur serveur'}), 500

# ========== POINT D'ENTRÉE ==========

if __name__ == '__main__':
    with app.app_context():
        try:
            db.session.execute('SELECT 1')
            print("[OK] Connexion RDS OK!")
        except Exception as e:
            print(f"[X] Erreur BD: {e}")
            print("Assurez-vous que:")
            print("  1. RDS instance est running")
            print("  2. Variables d'env sont correctes (.env)")
            print("  3. Security group autorise votre IP")
    
    app.run(
        host='0.0.0.0',
        port=5000,
        debug=os.getenv('FLASK_DEBUG', False)
    )


ÉTAPE 10: TESTER L'APPLICATION FLASK
════════════════════════════════════════════════════════════════════════════════

# 1. Initialiser DB (UNE SEULE FOIS!)
curl -X POST http://localhost:5000/api/init

# 2. Vérifier santé
curl http://localhost:5000/health

# Réponse:
# {
#   "status": "healthy",
#   "database": "connected",
#   "timestamp": "2024-01-15T10:30:00.123456"
# }

# 3. Créer un compte utilisateur
curl -X POST http://localhost:5000/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "email": "alice@example.com",
    "password": "SecurePassword123!"
  }'

# Réponse:
# {
#   "message": "Utilisateur créé avec succès",
#   "user": {
#     "id": 1,
#     "username": "alice",
#     "email": "alice@example.com",
#     "created_at": "2024-01-15T10:30:00"
#   }
# }

# 4. Se connecter (obtenir JWT token)
curl -X POST http://localhost:5000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "password": "SecurePassword123!"
  }'

# Réponse:
# {
#   "message": "Connexion réussie",
#   "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
#   "user": {...}
# }

# Sauvegarder le token!
TOKEN="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

# 5. Lister utilisateurs
curl http://localhost:5000/api/users

# 6. Récupérer profil utilisateur
curl http://localhost:5000/api/users/1

# 7. Créer un article (nécessite auth)
curl -X POST http://localhost:5000/api/articles \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "title": "Mon premier article",
    "content": "Contenu du premier article..."
  }'

# 8. Lister articles
curl http://localhost:5000/api/articles

# 9. Lire un article
curl http://localhost:5000/api/articles/1

# Note: Le compteur de vues augmente!

# 10. Modifier article (avec token)
curl -X PUT http://localhost:5000/api/articles/1 \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "title": "Titre modifié",
    "content": "Contenu modifié..."
  }'

# 11. Supprimer article
curl -X DELETE http://localhost:5000/api/articles/1 \
  -H "Authorization: Bearer $TOKEN"

# 12. Lister articles d'un utilisateur spécifique
curl "http://localhost:5000/api/articles?user_id=1&page=1&per_page=5"

# 13. Lister avec pagination
curl "http://localhost:5000/api/users?page=2&per_page=10"


ÉTAPE 11: SCRIPT DE TEST AUTOMATISÉ (test.sh)
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash

# test.sh - Script de test complet
BASE_URL="http://localhost:5000"
TOKEN=""

echo "[TEST] TESTS API FLASK + RDS"
echo "======================="

# 1. Init DB
echo ""
echo "1⃣ Initialiser base de données..."
curl -s -X POST $BASE_URL/api/init | python -m json.tool

# 2. Health check
echo ""
echo "2⃣ Vérifier santé app..."
curl -s $BASE_URL/health | python -m json.tool

# 3. Register user
echo ""
echo "3⃣ Créer utilisateur alice..."
REGISTER=$(curl -s -X POST $BASE_URL/api/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "email": "alice@example.com",
    "password": "AlicePass123!"
  }')
echo $REGISTER | python -m json.tool

# 4. Login
echo ""
echo "4⃣ Se connecter..."
LOGIN=$(curl -s -X POST $BASE_URL/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "password": "AlicePass123!"
  }')
echo $LOGIN | python -m json.tool

# Extract token
TOKEN=$(echo $LOGIN | python -c "import sys, json; print(json.load(sys.stdin)['access_token'])")
echo "[OK] Token: $TOKEN"

# 5. Create article
echo ""
echo "5⃣ Créer article..."
curl -s -X POST $BASE_URL/api/articles \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "title": "Mon Premier Article",
    "content": "Ceci est un article de test..."
  }' | python -m json.tool

# 6. List articles
echo ""
echo "6⃣ Lister articles..."
curl -s $BASE_URL/api/articles | python -m json.tool

# 7. List users
echo ""
echo "7⃣ Lister utilisateurs..."
curl -s $BASE_URL/api/users | python -m json.tool

echo ""
echo "[OK] Tests terminés!"

# Utilisation:
# chmod +x test.sh
# ./test.sh


ÉTAPE 12: BONNES PRATIQUES FLASK + RDS
════════════════════════════════════════════════════════════════════════════════

# 1. HASHER LES MOTS DE PASSE (sécurité!)
from werkzeug.security import generate_password_hash, check_password_hash

# Lors de la création:
user = User(
    username=data['username'],
    email=data['email'],
    password=generate_password_hash(data['password'])  # HASH!
)

# Lors de la vérification:
if check_password_hash(user.password, provided_password):
    print("Mot de passe correct!")

# 2. PAGINATION (pour éviter charger 1 million de rows)
@app.route('/api/posts', methods=['GET'])
def get_posts():
    page = request.args.get('page', 1, type=int)
    per_page = 10
    
    posts = Post.query.paginate(page=page, per_page=per_page)
    
    return jsonify({
        'posts': [p.to_dict() for p in posts.items],
        'total': posts.total,
        'pages': posts.pages,
        'current_page': page
    }), 200

# EXPLICATION:
# paginate(page=1, per_page=10) = 10 posts par page
# posts.items = Articles actuels
# posts.total = Nombre total
# posts.pages = Nombre de pages

# 3. VALIDATIONS (ne pas faire confiance à l'utilisateur)
from marshmallow import Schema, fields, validate, ValidationError

class UserSchema(Schema):
    username = fields.String(required=True, validate=validate.Length(min=3, max=80))
    email = fields.Email(required=True)
    password = fields.String(required=True, validate=validate.Length(min=8))

schema = UserSchema()

@app.route('/api/users', methods=['POST'])
def create_user():
    try:
        data = schema.load(request.get_json())
    except ValidationError as err:
        return jsonify({'errors': err.messages}), 400
    # ... reste du code

# 4. GESTION ERREURS GLOBALE
@app.errorhandler(404)
def not_found(error):
    return jsonify({'error': 'Ressource non trouvée'}), 404

@app.errorhandler(500)
def internal_error(error):
    db.session.rollback()
    return jsonify({'error': 'Erreur serveur'}), 500

# 5. LOGGING (pour déboguer)
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

@app.route('/api/users', methods=['POST'])
def create_user():
    logger.info(f"Création utilisateur: {request.get_json()}")
    # ...

# 6. LIMITER REQUÊTES (rate limiting)
from flask_limiter import Limiter

limiter = Limiter(app, key_func=lambda: request.remote_addr)

@app.route('/api/users', methods=['POST'])
@limiter.limit("10 per minute")
def create_user():
    # Max 10 requêtes par minute
    pass

# 7. CORS (si frontend séparé)
from flask_cors import CORS

CORS(app, origins="http://localhost:3000")

# 8. AUTHENTIFICATION (JWT)
from flask_jwt_extended import JWTManager, create_access_token, jwt_required

app.config['JWT_SECRET_KEY'] = os.getenv('SECRET_KEY')
jwt = JWTManager(app)

@app.route('/api/login', methods=['POST'])
def login():
    data = request.get_json()
    user = User.query.filter_by(username=data['username']).first()
    
    if not user or not check_password_hash(user.password, data['password']):
        return jsonify({'error': 'Identifiants invalides'}), 401
    
    access_token = create_access_token(identity=user.id)
    return jsonify({'access_token': access_token}), 200

@app.route('/api/protected', methods=['GET'])
@jwt_required()
def protected():
    # Seulement accessible avec JWT valide
    return jsonify({'message': 'Accès autorisé'}), 200


ÉTAPE 11: DÉPLOYER SUR AWS (Elastic Beanstalk)
════════════════════════════════════════════════════════════════════════════════

# Créer .ebextensions/python.config
option_settings:
  aws:autoscaling:launchconfiguration:
    IamInstanceProfile: aws-elasticbeanstalk-ec2-role
  aws:elasticbeanstalk:application:environment:
    PYTHONPATH: /var/app/current:$PYTHONPATH
    DB_HOST: flask-app-db.c9akciq32.us-east-1.rds.amazonaws.com
    DB_USER: admin
    DB_PORT: 3306
    DB_NAME: flask_app

# Créer .ebextensions/iam-policy.json (accès RDS)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "rds:DescribeDBInstances",
        "rds:DescribeDBClusters"
      ],
      "Resource": "*"
    }
  ]
}

# Initialiser Elastic Beanstalk
eb init -p python-3.11 flask-app --region us-east-1

# Créer environnement
eb create flask-app-env

# Déployer
git add .
git commit -m "Ready for deployment"
eb deploy

# Vérifier status
eb status

# Voir logs
eb logs

# SSH sur instance
eb ssh


ÉTAPE 12: CONTAINERISER AVEC DOCKER
════════════════════════════════════════════════════════════════════════════════

# Créer Dockerfile
FROM python:3.11-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

ENV FLASK_APP=app.py

CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]

# EXPLICATION:
# FROM python:3.11 = Image Python de base
# WORKDIR /app = Dossier de travail
# COPY requirements.txt = Copier dépendances
# RUN pip install = Installer dépendances
# CMD gunicorn = Serveur production (pas Flask dev!)

# Créer docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "5000:5000"
    environment:
      DB_HOST: db
      DB_USER: admin
      DB_PASSWORD: password
      DB_NAME: flask_app
    depends_on:
      - db
    volumes:
      - .:/app

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: flask_app
      MYSQL_USER: admin
      MYSQL_PASSWORD: password
    ports:
      - "3306:3306"
    volumes:
      - db_data:/var/lib/mysql

volumes:
  db_data:

# Lancer Docker Compose
docker-compose up

# Accès:
# Flask: http://localhost:5000
# MySQL: localhost:3306


═══════════════════════════════════════════════════════════════════════════════
[OK] MONITORING & ÉVÉNEMENTS
═══════════════════════════════════════════════════════════════════════════════

# Lister événements récents (24h)
aws rds describe-events \
  --duration 1440 \
  --source-type db-instance

# EXPLICATION:
# "--duration 1440" = 1440 minutes = 24 heures
# Affiche: créations, modifications, erreurs, patches

# Événements DB spécifique
aws rds describe-events \
  --source-identifier mydb \
  --source-type db-instance

# Voir en format lisible
aws rds describe-events \
  --source-identifier mydb \
  --query 'Events[*].[EventCategories,Message,SourceArn,SourceType]' \
  --output table

# Voir logs (MySQL)
aws rds describe-db-log-files \
  --db-instance-identifier mydb

# Télécharger log
aws rds download-db-log-file-portion \
  --db-instance-identifier mydb \
  --db-log-file-name error/mysql-error.log \
  --starting-token 0 \
  > error.log

# Logs disponibles (MySQL):
# error        = Erreurs
# general      = Toutes requêtes (lourd!)
# slowquery    = Requêtes lentes
# audit        = Actions (si activé)

# CloudWatch Metrics (via console ou CLI)
aws cloudwatch get-metric-statistics \
  --namespace AWS/RDS \
  --metric-name CPUUtilization \
  --dimensions Name=DBInstanceIdentifier,Value=mydb \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-16T00:00:00Z \
  --period 300 \
  --statistics Average

# Métriques courantes:
# CPUUtilization      = CPU %
# DatabaseConnections = Connexions actives
# ReadLatency         = Temps lecture (ms)
# WriteLatency        = Temps écriture (ms)
# DiskQueueDepth      = Files d'attente disque
# FreeableMemory      = Mémoire libre
# NetworkReceiveThroughput = Réseau reçu


═══════════════════════════════════════════════════════════════════════════════
[OK] AURORA (VERSION OPTIMISÉE AWS)
═══════════════════════════════════════════════════════════════════════════════

AURORA = MySQL/PostgreSQL optimisé par AWS
- 5x plus rapide que MySQL
- 3x plus rapide que PostgreSQL
- Réplication 3 zones automatique
- Auto-scaling lectures
- Plus cher mais performance premium

# Créer cluster Aurora MySQL
aws rds create-db-cluster \
  --db-cluster-identifier myaurora \
  --engine aurora-mysql \
  --engine-version 8.0.mysql_aurora.3.04.0 \
  --master-username admin \
  --master-user-password MySecurePassword123! \
  --vpc-security-group-ids sg-0123456789abcdef0 \
  --backup-retention-period 30

# Créer instance Aurora dans cluster
aws rds create-db-instance \
  --db-instance-identifier myaurora-instance-1 \
  --db-instance-class db.r5.large \
  --engine aurora-mysql \
  --db-cluster-identifier myaurora

# Créer Aurora Serverless (auto-scaling)
aws rds create-db-cluster \
  --db-cluster-identifier myaurora-serverless \
  --engine aurora-mysql \
  --engine-mode serverless \
  --master-username admin \
  --master-user-password MySecurePassword123! \
  --scaling-configuration MinCapacity=2,MaxCapacity=16,AutoPause=true

# EXPLICATION:
# "engine-mode serverless" = Sans serveur
# MinCapacity=2 = 2 ACUs minimum
# MaxCapacity=16 = 16 ACUs maximum
# AutoPause=true = Pause si inactif (économie!)

# Lister clusters Aurora
aws rds describe-db-clusters

# Lister instances Aurora
aws rds describe-db-instances \
  --filters Name=engine,Values=aurora-mysql


═══════════════════════════════════════════════════════════════════════════════
[OK] SECURITY GROUPS & RÉSEAU
═══════════════════════════════════════════════════════════════════════════════

# Voir règles actuelles
aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0

# Ajouter règle ingress (entrant)
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 3306 \
  --cidr 203.0.113.25/32

# Ajouter règle depuis autre security group
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 3306 \
  --source-group sg-app-instances

# Révoquer (supprimer) règle
aws ec2 revoke-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 3306 \
  --cidr 203.0.113.25/32

# Règles de sortie (optionnel)
aws ec2 authorize-security-group-egress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0


═══════════════════════════════════════════════════════════════════════════════
[OK] OPTION GROUPS (FONCTIONNALITÉS ADDITIONNELLES)
═══════════════════════════════════════════════════════════════════════════════

# Créer option group
aws rds create-option-group \
  --option-group-name mydb-options \
  --engine-name mysql \
  --major-engine-version 8.0 \
  --option-group-description "MySQL custom options"

# Ajouter option (exemple: MariaDB Audit Plugin)
aws rds add-option-to-option-group \
  --option-group-name mydb-options \
  --options "OptionName=MARIADB_AUDIT_PLUGIN"

# Appliquer à DB
aws rds modify-db-instance \
  --db-instance-identifier mydb \
  --option-group-name mydb-options


═══════════════════════════════════════════════════════════════════════════════
[OK] BACKUP STRATEGY (STRATÉGIE SAUVEGARDE)
═══════════════════════════════════════════════════════════════════════════════

STRATÉGIE RECOMMANDÉE:

DEV/TEST:
- Backup retention: 7 jours
- Snapshots: 1-2 manuels avant modif
- Multi-AZ: NON (économie)

PRODUCTION:
- Backup retention: 30 jours
- Snapshots: quotidiens (automatisé)
- Multi-AZ: OUI (haute dispo)
- Read Replicas: 1-2 (DR + performance)
- Copy to another region: hebdo

# Automatiser snapshots quotidiens
# Créer fonction Lambda + EventBridge (hors scope ici)

# Script shell (manuel):
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_ID="mydb-backup-$DATE"
aws rds create-db-snapshot \
  --db-instance-identifier mydb \
  --db-snapshot-identifier $BACKUP_ID
echo "Snapshot créé: $BACKUP_ID"

# Ajouter à crontab (Linux):
# 0 2 * * * /home/ubuntu/backup.sh
# Exécute chaque jour à 02:00


═══════════════════════════════════════════════════════════════════════════════
[OK] DÉPANNAGE & SOLUTIONS
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME: "Can't connect to database"
SOLUTIONS:
1. Vérifier endpoint:
   aws rds describe-db-instances --db-instance-identifier mydb \
   --query 'DBInstances[0].Endpoint.Address'

2. Vérifier DB est "available":
   aws rds describe-db-instances --db-instance-identifier mydb \
   --query 'DBInstances[0].DBInstanceStatus'

3. Vérifier sécurité groupe (votre IP autorisée?):
   aws ec2 describe-security-groups --group-ids sg-xxx

4. Tester ping (DNS ok?):
   ping mydb.c9akciq32.us-east-1.rds.amazonaws.com

5. Tester port:
   telnet mydb.c9akciq32.us-east-1.rds.amazonaws.com 3306

6. Vérifier credentials:
   Utilisateur correct? Mot de passe correct?

---

PROBLÈME: "DB is down / unavailable"
SOLUTIONS:
1. Vérifier état:
   aws rds describe-db-instances --db-instance-identifier mydb \
   --query 'DBInstances[0].DBInstanceStatus'

2. Lire événements:
   aws rds describe-events --source-identifier mydb

3. Redémarrer si nécessaire:
   aws rds reboot-db-instance --db-instance-identifier mydb

4. Vérifier Multi-AZ failover:
   Si Multi-AZ = vérifier logs pour basculement auto

---

PROBLÈME: "Slow queries / performance problem"
SOLUTIONS:
1. Activer slow query log:
   aws rds modify-db-instance \
   --db-instance-identifier mydb \
   --enable-cloudwatch-logs-exports slowquery

2. Voir logs:
   aws rds download-db-log-file-portion \
   --db-instance-identifier mydb \
   --db-log-file-name slowquery/mysql-slowquery.log

3. Utiliser Performance Insights:
   aws rds modify-db-instance \
   --db-instance-identifier mydb \
   --enable-performance-insights

4. Vérifier métriques CloudWatch:
   CPU saturé? RAM saturé? Disque plein?

5. Upgrade instance:
   aws rds modify-db-instance \
   --db-instance-identifier mydb \
   --db-instance-class db.m5.large \
   --apply-immediately

6. Créer indexes:
   Sur colonnes WHERE/JOIN

---

PROBLÈME: "Oups, j'ai supprimé des données!"
SOLUTIONS:
1. Point-In-Time Restore (PITR):
   aws rds restore-db-instance-to-point-in-time \
   --source-db-instance-identifier mydb \
   --target-db-instance-identifier mydb-recovered \
   --restore-time 2024-01-15T14:25:00Z

2. Restaurer depuis snapshot:
   aws rds restore-db-instance-from-db-snapshot \
   --db-instance-identifier mydb-recovered \
   --db-snapshot-identifier mydb-snapshot-before-delete

3. Vérifier données récupérées
4. Copier les données vers DB principale

---

PROBLÈME: "Storage full"
SOLUTIONS:
1. Vérifier espace:
   aws rds describe-db-instances --db-instance-identifier mydb \
   --query 'DBInstances[0].AllocatedStorage'

2. Augmenter stockage:
   aws rds modify-db-instance \
   --db-instance-identifier mydb \
   --allocated-storage 200 \
   --apply-immediately

3. Nettoyer anciennes données:
   DELETE FROM table WHERE date < DATE_SUB(NOW(), INTERVAL 1 YEAR);

4. Voir espace disque:
   Via Performance Insights -> "Free Storage Space"

---

PROBLÈME: "Too many connections"
SOLUTIONS:
1. Voir limite actuelle:
   SHOW VARIABLES LIKE 'max_connections';

2. Augmenter limit:
   Créer parameter group, modifier max_connections
   aws rds modify-db-parameter-group \
   --db-parameter-group-name mydb-params \
   --parameters "ParameterName=max_connections,ParameterValue=1000"

3. Utiliser connection pooling dans app:
   PgBouncer (PostgreSQL)
   ProxySQL (MySQL)

4. Fermer connexions inutiles:
   SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST;
   KILL CONNECTION id;


═══════════════════════════════════════════════════════════════════════════════
[OK] CHECKLIST: PREMIÈRE DB EN PRODUCTION
═══════════════════════════════════════════════════════════════════════════════

AVANT:
[ ] Choisir région
[ ] Choisir moteur (MySQL/PostgreSQL)
[ ] Choisir classe instance
[ ] Créer security group
[ ] Créer subnet group (si VPC privée)

CRÉATION:
[ ] Créer DB instance
[ ] Attendre "available" (~10 min)
[ ] Récupérer endpoint
[ ] Tester connexion

SÉCURITÉ:
[ ] Sauvegarder mot de passe (gestionnaire)
[ ] Activer Multi-AZ
[ ] Activer deletion-protection
[ ] Security group restrictif
[ ] Pas --publicly-accessible si possible

BACKUPS:
[ ] Backup retention: 30 jours
[ ] Preferred backup window: 03:00-04:00
[ ] Créer snapshot initial
[ ] Test PITR (restaurer à moment X)
[ ] Copier snapshot vers autre région

MONITORING:
[ ] Activer CloudWatch logs
[ ] Activer Enhanced Monitoring
[ ] Activer Performance Insights
[ ] Configurer CloudWatch alarms
[ ] CPU > 80%, RAM > 90%, Storage > 90%

PERFORMANCE:
[ ] Créer parameter group custom
[ ] Configurer max_connections
[ ] Configurer slow query log
[ ] Créer indexes sur tables principales
[ ] Read replicas pour scaling lecture

DOCUMENTATION:
[ ] Noter: host, port, user, database
[ ] Documenter: schema structure
[ ] Documenter: backup strategy
[ ] Documenter: runbook pour incidents


═══════════════════════════════════════════════════════════════════════════════
[OK] COÛTS & OPTIMISATION
═══════════════════════════════════════════════════════════════════════════════

RÉDUIRE LES COÛTS:

1. UTILISER FREE TIER (~$0/mois 12 mois)
   - db.t3.micro
   - 20 GB stockage
   - 750 heures/mois

2. ARRÊTER INACTIF
   - Dev/test: arrêter le soir (~50% économie)
   - Staging: arrêter entre releases

3. CHOISIR BON MOTEUR
   - MySQL: bon tout-usage, économique
   - PostgreSQL: puissant, même prix
   - Aurora: premium, 2-3x plus cher

4. CHOISIR BON STORAGE
   - gp3: meilleur rapport qualité/prix
   - gp2: compatible, moins cher
   - io1: seulement si besoin IOPS élevés

5. MONITORAGE USAGE
   - CloudWatch metrics
   - Downsize instance si CPU < 10%
   - Augmenter storage progressivement

6. RESERVED INSTANCES
   - Acheter 1/3 ans: 30-50% réduction
   - Si DB stable long-terme

7. NETTOYER SNAPSHOTS
   - Vieux snapshots = argent gaspillé
   - Garder: snapshots importants seulement

8. MULTI-AZ SAGEMENT
   - 2x plus cher
   - Production critique seulement

EXEMPLE: Dev vs Production
DEV (micro + low storage):
  - db.t3.micro: $0
  - 20 GB gp3: $2.20
  - Backup 7j: $0.50
  - Total: ~$2.70/mois (gratuit year 1)

PROD (m5.large + Multi-AZ):
  - 2x db.m5.large: $440
  - 100 GB gp3: $11
  - Backup 30j: $3
  - Total: ~$454/mois


═══════════════════════════════════════════════════════════════════════════════
[OK] COMMANDES ESSENTIELLES (RÉFÉRENCE RAPIDE)
═══════════════════════════════════════════════════════════════════════════════

# CRÉER
aws rds create-db-instance --db-instance-identifier mydb --engine mysql --master-username admin --master-user-password Pwd123!

# LISTER
aws rds describe-db-instances

# ENDPOINT
aws rds describe-db-instances --db-instance-identifier mydb --query 'DBInstances[0].Endpoint.Address' --output text

# STATUS
aws rds describe-db-instances --db-instance-identifier mydb --query 'DBInstances[0].DBInstanceStatus' --output text

# MODIFIER (Upgrade)
aws rds modify-db-instance --db-instance-identifier mydb --db-instance-class db.t3.small --apply-immediately

# SNAPSHOT
aws rds create-db-snapshot --db-instance-identifier mydb --db-snapshot-identifier mydb-backup-$(date +%Y%m%d)

# RESTAURER SNAPSHOT
aws rds restore-db-instance-from-db-snapshot --db-instance-identifier mydb-restored --db-snapshot-identifier mydb-backup

# PITR
aws rds restore-db-instance-to-point-in-time --source-db-instance-identifier mydb --target-db-instance-identifier mydb-pitr --restore-time 2024-01-15T10:30:00Z

# READ REPLICA
aws rds create-db-instance-read-replica --db-instance-identifier mydb-replica --source-db-instance-identifier mydb

# ARRÊTER
aws rds stop-db-instance --db-instance-identifier mydb

# DÉMARRER
aws rds start-db-instance --db-instance-identifier mydb

# REDÉMARRER
aws rds reboot-db-instance --db-instance-identifier mydb

# SUPPRIMER
aws rds delete-db-instance --db-instance-identifier mydb --final-db-snapshot-identifier mydb-final


═══════════════════════════════════════════════════════════════════════════════
[OK] RESSOURCES & LIENS
═══════════════════════════════════════════════════════════════════════════════

AWS Documentation:
https://docs.aws.amazon.com/rds/

RDS User Guide:
https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/

AWS CLI RDS:
https://docs.aws.amazon.com/cli/latest/reference/rds/

Tools Recommandés:
- MySQL Workbench (GUI MySQL/MariaDB)
- pgAdmin (GUI PostgreSQL)
- DBeaver (Universal DB client)
- AWS Database Migration Service (Migrations)

Bonnes Pratiques:
- Backups: Min 7j, Prod 30j
- Multi-AZ: Production critique seulement
- Security Groups: Aussi restrictif que possible
- Parameter Groups: Custom si besoin config
- Monitoring: CloudWatch + Performance Insights
- PITR: Tester régulièrement
- Documentation: Schema, users, runbooks

Performance Tips:
- Indexes sur colonnes WHERE/JOIN/ORDER BY
- Normalisation: Éviter duplication
- Connection pooling: Pour haute concurrence
- Read replicas: Pour rapports/analytics
- Slow query log: Identifier bottlenecks

# ============================================================================
# GUIDE DE DÉPANNAGE: Flask + RDS
# ============================================================================
# Solutions aux problèmes les plus courants


═══════════════════════════════════════════════════════════════════════════════
[X] ERREUR 1: "No module named 'flask'"
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME:
ModuleNotFoundError: No module named 'flask'

CAUSE:
- Dépendances pas installées
- Mauvais environnement virtuel activé

SOLUTION:
1. Créer venv:
   python3 -m venv venv

2. Activer venv:
   # Linux/Mac:
   source venv/bin/activate
   
   # Windows:
   venv\Scripts\activate

3. Installer dépendances:
   pip install -r requirements.txt

4. Vérifier:
   python -c "import flask; print(flask.__version__)"

EXPLICATION:
- Environnement virtuel = "univers Python isolé"
- Chaque projet a ses dépendances
- Activation nécessaire à chaque fois qu'on ouvre le terminal


═══════════════════════════════════════════════════════════════════════════════
[X] ERREUR 2: "(pymysql.err.OperationalError) (2003, 'Can't connect to MySQL'"
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME:
pymysql.err.OperationalError: (2003, "Can't connect to MySQL server on '...'")

CAUSE:
- RDS instance pas running
- Mauvais endpoint
- Security group n'autorise pas votre IP
- Firewall local bloque port 3306

DIAGNOSTIC:

1. Vérifier que RDS existe et est en "available":
   aws rds describe-db-instances --db-instance-identifier flask-app-db \
   --query 'DBInstances[0].DBInstanceStatus'
   
   Résultat devrait être: "available"

2. Vérifier endpoint:
   aws rds describe-db-instances --db-instance-identifier flask-app-db \
   --query 'DBInstances[0].Endpoint.Address'

3. Vérifier dans .env:
   cat .env
   # DB_HOST doit correspondre à l'endpoint

4. Tester connexion directement:
   mysql -h flask-app-db.c9akciq32.us-east-1.rds.amazonaws.com \
   -u admin \
   -p
   # Taper le mot de passe

5. Vérifier security group:
   aws ec2 describe-security-groups --group-ids sg-xxx
   
   Chercher une règle comme:
   - Protocol: tcp
   - Port: 3306
   - CIDR: votre IP/32 OU sg-app-instances

6. Vérifier votre IP:
   curl https://icanhazip.com
   
   Puis ajouter à security group:
   aws ec2 authorize-security-group-ingress \
   --group-id sg-xxx \
   --protocol tcp \
   --port 3306 \
   --cidr YOUR_IP/32

SOLUTION COMPLÈTE (pas à pas):
1. Démarrer RDS si arrêtée:
   aws rds start-db-instance --db-instance-identifier flask-app-db

2. Attendre 2-3 minutes
3. Tester depuis Python:
   python
   >>> import pymysql
   >>> pymysql.connect(host='...', user='admin', password='...', port=3306)
   
   Si connexion OK -> problème dans .env
   Si erreur -> problème connexion réseau


═══════════════════════════════════════════════════════════════════════════════
[X] ERREUR 3: "OperationalError: (1045, 'Access denied for user 'admin'')"
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME:
SQLAlchemy OperationalError: (1045, "Access denied for user 'admin'@'...'")

CAUSE:
- Mot de passe incorrect
- Utilisateur n'existe pas

DIAGNOSTIC:

1. Vérifier mot de passe en .env:
   grep DB_PASSWORD .env

2. Tester connexion manuelle:
   mysql -h endpoint.rds.amazonaws.com -u admin -p
   # Taper le mot de passe de .env

SOLUTION:
1. Si mot de passe correct mais rejeté:
   a. RDS peut nécessiter quelques minutes après création
   b. Attendre 5 minutes puis réessayer

2. Si mot de passe oublié:
   a. Modifier mot de passe RDS:
      aws rds modify-db-instance \
      --db-instance-identifier flask-app-db \
      --master-user-password NewPassword123! \
      --apply-immediately
   
   b. Attendre 1-2 minutes
   c. Mettre à jour .env
   d. Redémarrer app

3. Vérifier qu'il y a pas d'espaces:
   # INCORRECT:
   DB_PASSWORD = MyPassword123!  # Espaces!
   
   # CORRECT:
   DB_PASSWORD=MyPassword123!


═══════════════════════════════════════════════════════════════════════════════
[X] ERREUR 4: "OperationalError: (1049, 'Unknown database'"
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME:
SQLAlchemy OperationalError: (1049, "Unknown database 'flask_app'")

CAUSE:
- Base de données n'existe pas
- Mauvais nom de BD en .env

DIAGNOSTIC:

1. Vérifier que BD existe:
   # Se connecter à RDS d'abord:
   mysql -h endpoint -u admin -p
   
   # Puis dans MySQL:
   SHOW DATABASES;
   # Chercher "flask_app"

2. Vérifier nom en .env:
   grep DB_NAME .env

SOLUTION:

Option 1: Créer la base via MySQL:
   mysql -h endpoint -u admin -p
   > CREATE DATABASE flask_app;
   > exit

Option 2: Créer via AWS CLI (plus simple):
   # D'abord, se connecter à RDS
   mysql -h flask-app-db.c9akciq32.us-east-1.rds.amazonaws.com \
   -u admin -p
   
   # Dans MySQL prompt:
   > CREATE DATABASE flask_app CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
   > exit

Option 3: Modifier .env pour utiliser "mysql" (BD par défaut):
   DB_NAME=mysql
   
   Puis l'app créera les tables dans "mysql" (pas idéal)


═══════════════════════════════════════════════════════════════════════════════
[X] ERREUR 5: "ProgrammingError: (1146, 'Table 'flask_app.users' doesn't exist')"
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME:
ProgrammingError: (1146, "Table 'flask_app.users' doesn't exist")

CAUSE:
- Tables pas créées
- Endpoint /api/init pas appelé

SOLUTION:

1. Appeler endpoint d'initialisation:
   curl -X POST http://localhost:5000/api/init
   
   Réponse devrait être:
   {"message": "Tables créées"}

2. Vérifier que les tables existent:
   mysql -h endpoint -u admin -p -e "USE flask_app; SHOW TABLES;"
   
   Résultat:
   | Tables_in_flask_app |
   | articles            |
   | users               |

3. Si erreur persiste:
   a. Vérifier que modèles sont définis dans app.py
   b. Vérifier que db.create_all() est appelé
   c. Regarder les logs de la migration

EXPLICATION:
- SQLAlchemy crée pas les tables automatiquement
- db.create_all() crée toutes les tables des modèles
- Doit être fait UNE SEULE FOIS


═══════════════════════════════════════════════════════════════════════════════
[X] ERREUR 6: "IntegrityError: (1062, 'Duplicate entry'"
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME:
IntegrityError: (1062, "Duplicate entry 'alice' for key 'username'")

CAUSE:
- Essai de créer utilisateur avec username existant
- Pas de vérification des doublons

DIAGNOSTIC:

1. Vérifier utilisateurs existants:
   mysql -h endpoint -u admin -p -e "USE flask_app; SELECT * FROM users;"

2. Tester login plutôt que register:
   curl -X POST http://localhost:5000/api/auth/login \
   -d '{"username": "alice", "password": "..."}'

SOLUTION:

Code déjà inclus dans app.py:
```python
if User.query.filter_by(username=data['username']).first():
    return jsonify({'error': 'Username existant'}), 409
```

Si vous n'avez pas cette vérification:
1. Ajouter le code ci-dessus
2. Tester avec autre username

Alternative: Supprimer l'utilisateur:
   mysql -h endpoint -u admin -p
   > USE flask_app;
   > DELETE FROM users WHERE username='alice';
   > exit


═══════════════════════════════════════════════════════════════════════════════
[X] ERREUR 7: "JWTError: Missing authorization header"
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME:
Quand d'appel une route @jwt_required() sans token

CAUSE:
- Token pas envoyé
- Token mal formaté
- Token expiré

DIAGNOSTIC:

1. Vérifier que token est envoyé:
   # INCORRECT (pas de token):
   curl -X POST http://localhost:5000/api/articles \
   -d '{"title": "...", "content": "..."}'
   
   # CORRECT (avec token):
   curl -X POST http://localhost:5000/api/articles \
   -H "Authorization: Bearer $TOKEN" \
   -d '{"title": "...", "content": "..."}'

2. Obtenir un nouveau token:
   curl -X POST http://localhost:5000/api/auth/login \
   -d '{"username": "alice", "password": "..."}'
   
   Copier le "access_token"

SOLUTION:

1. Toujours envoyer le token:
   TOKEN=$(curl -s ... | grep access_token | ...)
   curl -H "Authorization: Bearer $TOKEN" ...

2. Format correct: "Bearer XXXXXXXX"
   Pas "Bearer:" ni "Bearer  " (double espace)

3. Python/requests:
   headers = {"Authorization": f"Bearer {token}"}
   response = requests.post(url, headers=headers)


═══════════════════════════════════════════════════════════════════════════════
[X] ERREUR 8: "Address already in use"
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME:
Address already in use port 5000

CAUSE:
- Application déjà en cours d'exécution sur port 5000
- Autre application utilise le port

DIAGNOSTIC:

1. Voir quelle app utilise port 5000:
   lsof -i :5000  # Linux/Mac
   netstat -ano | findstr :5000  # Windows

2. Voir tous les services:
   netstat -tlnp | grep LISTEN

SOLUTION:

Option 1: Tuer l'app existante:
   pkill -f "python app.py"  # Linux/Mac
   
   Ou ctrl+C dans le terminal Flask

Option 2: Utiliser autre port:
   app.run(port=5001)
   # Puis accéder à http://localhost:5001

Option 3: Nettoyer le port (Linux):
   sudo fuser -k 5000/tcp


═══════════════════════════════════════════════════════════════════════════════
[X] ERREUR 9: "CORS policy: No 'Access-Control-Allow-Origin' header"
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME:
CORS policy: No 'Access-Control-Allow-Origin' header is present

CAUSE:
- Frontend et backend sur domaines/ports différents
- CORS pas activé

DIAGNOSTIC:

1. Où s'exécute le frontend?
   http://localhost:3000 ? http://example.com ?

2. Où s'exécute le backend?
   http://localhost:5000

SOLUTION:

Dans app.py, ajouter:
```python
from flask_cors import CORS

CORS(app, origins="http://localhost:3000")
```

Ou permettre tous (DEV seulement!):
```python
CORS(app)  # Dangereux en production!
```

En production:
```python
CORS(app, origins=[
    "https://yourdomain.com",
    "https://www.yourdomain.com"
])
```

Vérifier que CORS est activé:
Dans navigateur, voir headers réponse:
   Access-Control-Allow-Origin: http://localhost:3000


═══════════════════════════════════════════════════════════════════════════════
[X] ERREUR 10: "Health check fails" / BD disconnect
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME:
GET /health retourne status=unhealthy

CAUSE:
- RDS instance s'est arrêtée
- Connexion BD perdue
- Timeout réseau

DIAGNOSTIC:

1. Vérifier état RDS:
   aws rds describe-db-instances \
   --db-instance-identifier flask-app-db \
   --query 'DBInstances[0].DBInstanceStatus'

2. Consulter événements RDS:
   aws rds describe-events \
   --source-identifier flask-app-db

3. Tester connexion manuelle:
   mysql -h endpoint -u admin -p -e "SELECT 1;"

SOLUTION:

1. Si RDS arrêtée:
   aws rds start-db-instance --db-instance-identifier flask-app-db
   Attendre 1-2 minutes

2. Si "maintenance in progress":
   Attendre que maintenance se termine (peut être longue)

3. Redémarrer Flask app:
   Ctrl+C
   python app.py

4. En production, utiliser connection pooling:
   from sqlalchemy import create_engine
   
   engine = create_engine(
       '...',
       pool_size=10,
       pool_recycle=3600,  # Recycler connexions après 1h
       pool_pre_ping=True   # Tester connexion avant utilisation
   )


═══════════════════════════════════════════════════════════════════════════════
[OK] CHECKLIST DE DÉPANNAGE
═══════════════════════════════════════════════════════════════════════════════

Avant de crier "c'est cassé!":

[WHITE_SQUARE] 1. Python venv activé?
[WHITE_SQUARE] 2. Dépendances installées? (pip install -r requirements.txt)
[WHITE_SQUARE] 3. .env existe et variables correctes?
[WHITE_SQUARE] 4. RDS instance running? (aws rds describe-db-instances)
[WHITE_SQUARE] 5. Base de données créée? (SHOW DATABASES;)
[WHITE_SQUARE] 6. Tables créées? (GET /api/init appelé?)
[WHITE_SQUARE] 7. Security group OK? (IP autorisée?)
[WHITE_SQUARE] 8. Port 5000 disponible?
[WHITE_SQUARE] 9. Vérifier logs Flask (regarde l'erreur exacte!)
[WHITE_SQUARE] 10. Tester connexion BD directement (mysql CLI)


═══════════════════════════════════════════════════════════════════════════════
[NOTE] LOGS UTILES
═══════════════════════════════════════════════════════════════════════════════

# Voir logs Flask (terminal):
# Chercher: ERROR, WARNING, Traceback

# Voir logs RDS:
aws rds download-db-log-file-portion \
  --db-instance-identifier flask-app-db \
  --db-log-file-name error/mysql-error.log

# Voir logs erreur applicatif (Python):
python app.py 2>&1 | tee app.log
# Puis consulter app.log

# Activer DEBUG mode Flask:
FLASK_ENV=development
FLASK_DEBUG=1
# Recharge auto + meilleur error display


═══════════════════════════════════════════════════════════════════════════════
🆘 HELP! Encore pas résolu?
═══════════════════════════════════════════════════════════════════════════════

1. Copier l'erreur exacte (Traceback complet)
2. Essayer chaque diagnostic/solution ci-dessus
3. Vérifier AWS CloudWatch logs
4. Consulter Flask docs: https://flask.palletsprojects.com
5. Consulter SQLAlchemy docs: https://docs.sqlalchemy.org


# Fichier: python_cheats/cheatsheets/lambda.txt
# Cheatsheet AWS Lambda - Fonctions Serverless Expliquées


═══════════════════════════════════════════════════════════════════════════════
[OK] AWS LAMBDA - C'EST QUOI?
═══════════════════════════════════════════════════════════════════════════════

Lambda = "Exécuter du code sans gérer de serveurs"

ANALOGIE:
- AVANT Lambda: Vous louez un serveur 24/7 (même si inutilisé)
- AVEC Lambda: Vous payez SEULEMENT quand le code s'exécute (millisecondes)

EXEMPLE CONCRET:
- Serveur traditionnel: $100/mois (toujours actif)
- Lambda: $0.0000002 par exécution (presque gratuit!)

QUAND UTILISER LAMBDA?
[OK] Traiter uploads S3 (fichiers images, vidéos)
[OK] Répondre à événements (DynamoDB streams)
[OK] API légères (API Gateway -> Lambda)
[OK] Tâches planifiées (CloudWatch Events/EventBridge)
[OK] Traiter messages SQS/SNS
[OK] Backend pour applications mobiles
[OK] Automatisation AWS
[OK] Data processing

QUAND NE PAS UTILISER?
[X] Applications web stateful (sessions)
[X] Connexions longues (>15 minutes)
[X] GPU intensif
[X] Haute concurrence (>1000 requêtes/sec simultanées)

TARIFICATION LAMBDA:
- Requêtes: $0.20 par 1 million d'appels
- Durée: $0.0000166667 par GB-seconde
- Exemple: 1M appels × 100ms × 128MB = ~$0.21/mois

LANGAGES SUPPORTÉS:
- Python (3.12, 3.11, 3.10, 3.9)
- Node.js (20, 18, 16)
- Java (21, 17, 11)
- Go (1.x)
- Ruby (3.3, 3.2)
- .NET (8, 6)
- Custom runtime (même binary!)

LIMITS LAMBDA:
- Timeout max: 15 minutes (900 secondes)
- Mémoire: 128 MB à 10,240 MB
- Stockage temp (/tmp): 10 GB
- Payload: 6 MB synchrone, 256 KB asynchrone
- Concurrence: 1000 par défaut (configurable)


═══════════════════════════════════════════════════════════════════════════════
[OK] CONCEPTS CLÉS LAMBDA
═══════════════════════════════════════════════════════════════════════════════

HANDLER (Fonction d'entrée):
= Point d'entrée de votre code
= Appelée à chaque invocation
= Reçoit 2 paramètres: event, context
= Doit retourner un résultat

EVENT:
= Données envoyées à la fonction
= Format dépend de la source:
  * S3: Infos fichier uploadé
  * API Gateway: Requête HTTP
  * DynamoDB Stream: Enregistrement modifié
  * CloudWatch Events: Informations tâche planifiée
  * SQS: Message
  * Manuel: Payload custom

CONTEXT:
= Informations sur l'exécution
= Exemple: function_name, request_id, memory_limit_in_mb

EXECUTION ROLE (Rôle IAM):
= Permissions que Lambda peut faire
= Exemple: lire S3, écrire CloudWatch Logs
= Obligatoire lors de création

VERSION:
= Snapshot immuable de fonction
= $LATEST = code modifiable actuellement
= Versions numérotées (1, 2, 3...) = snapshots immutables

ALIAS:
= Pointeur vers une version
= Permet canary deployments
= Exemple: prod -> v1, staging -> v2

LAYER:
= Fichiers partagés entre fonctions
= Parfait pour: librairies, configurations
= Économise espace et facilite maintenance

COLD START:
= Premier appel d'une fonction (lent)
= Initialisation: quelques secondes
= Appels suivants: plus rapides
= Provisioned Concurrency = éviter cold starts

CONCURRENT EXECUTIONS:
= Nombre d'appels simultanés
= Par défaut: 1000 pour tout le compte
= Dépassement = throttling (rejet)
= Peut être augmenté (demander AWS)


═══════════════════════════════════════════════════════════════════════════════
[OK] PRÉPARER L'IAM ROLE
═══════════════════════════════════════════════════════════════════════════════

Lambda DOIT avoir un IAM role pour s'exécuter!

CRÉER UN ROLE (option simple):
════════════════════════════════════════════════════════════════════════════════

# Créer document de confiance (policy JSON)
cat > trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

# EXPLICATION:
# Principal.Service: "lambda.amazonaws.com" = Lambda service peut utiliser ce role
# Action: sts:AssumeRole = Lambda peut "revêtir" ce rôle

# Créer le rôle
aws iam create-role \
  --role-name lambda-execution-role \
  --assume-role-policy-document file://trust-policy.json

# RÉSULTAT: arn:aws:iam::123456789012:role/lambda-execution-role
# SAUVEGARDEZ CET ARN!

# Donner permission au rôle (logs CloudWatch - obligatoire)
aws iam attach-role-policy \
  --role-name lambda-execution-role \
  --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

# EXPLICATION:
# AWSLambdaBasicExecutionRole = permissions pour écrire logs CloudWatch
# TOUTE fonction Lambda devrait avoir ça

# Donner permission d'accéder à S3 (si besoin)
cat > s3-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetObject",
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}
EOF

aws iam put-role-policy \
  --role-name lambda-execution-role \
  --policy-name s3-access \
  --policy-document file://s3-policy.json

# EXPLICATION:
# GetObject = lire fichiers
# PutObject = écrire fichiers
# Resource = arn:aws:s3:::my-bucket/* = tous les fichiers du bucket

# Donner permission d'accéder à RDS (si besoin)
cat > rds-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "rds:DescribeDBInstances",
        "rds:DescribeDBClusters"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:CreateNetworkInterface",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DeleteNetworkInterface"
      ],
      "Resource": "*"
    }
  ]
}
EOF

aws iam put-role-policy \
  --role-name lambda-execution-role \
  --policy-name rds-access \
  --policy-document file://rds-policy.json

# EXPLICATION:
# Pour Lambda dans VPC (besoin d'accéder RDS privée)
# ec2 permissions = gérer network interfaces

# Donner permission au secret manager (pour credentials sécurisés)
cat > secrets-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "secretsmanager:GetSecretValue",
      "Resource": "arn:aws:secretsmanager:us-east-1:123456789012:secret:*"
    }
  ]
}
EOF

aws iam put-role-policy \
  --role-name lambda-execution-role \
  --policy-name secrets-access \
  --policy-document file://secrets-policy.json


═══════════════════════════════════════════════════════════════════════════════
[OK] CRÉER UNE FONCTION LAMBDA - EXPLICATIONS DÉTAILLÉES
═══════════════════════════════════════════════════════════════════════════════

ÉTAPE 1: ÉCRIRE LE CODE
════════════════════════════════════════════════════════════════════════════════

# lambda_function.py - Fonction simple Hello World

def lambda_handler(event, context):
    """
    EXPLICATIONS:
    - event: dict avec les données d'entrée
    - context: objet avec infos sur l'exécution
    
    PARAMETERS:
    - event['key'] = accéder aux données
    - context.function_name = nom de la fonction
    - context.invoked_function_arn = ARN complet
    - context.memory_limit_in_mb = mémoire disponible
    - context.request_id = ID unique de l'appel
    - context.get_remaining_time_in_millis() = temps restant
    """
    
    print(f"Nom fonction: {context.function_name}")
    print(f"Requête ID: {context.request_id}")
    print(f"Données reçues: {event}")
    
    # Retourner une réponse
    return {
        'statusCode': 200,
        'body': 'Hello from Lambda!'
    }

# EXPLICATION:
# def lambda_handler = fonction d'entrée (nom conventionnel)
# (event, context) = paramètres obligatoires
# return {...} = résultat JSON

# lambda_function.py - Fonction avec API Gateway

def lambda_handler(event, context):
    """
    Répondre à requêtes HTTP via API Gateway
    
    event contient:
    {
      "httpMethod": "GET",
      "path": "/users/123",
      "queryStringParameters": {"filter": "active"},
      "body": "{...json...}",
      "headers": {...}
    }
    """
    
    method = event['httpMethod']
    path = event['path']
    
    if method == 'GET' and path == '/users':
        users = [
            {'id': 1, 'name': 'Alice'},
            {'id': 2, 'name': 'Bob'}
        ]
        return {
            'statusCode': 200,
            'headers': {'Content-Type': 'application/json'},
            'body': str(users)  # Doit être string!
        }
    
    return {
        'statusCode': 404,
        'body': 'Not found'
    }

# EXPLICATION:
# httpMethod = GET/POST/PUT/DELETE
# path = URL route
# body = contenu POST (string, pas dict!)
# headers = doivent être dict

# lambda_function.py - Fonction avec S3 trigger

import json
import boto3

s3 = boto3.client('s3')

def lambda_handler(event, context):
    """
    Déclenché quand fichier uploadé sur S3
    
    event['Records'][0] contient:
    {
      's3': {
        'bucket': {'name': 'my-bucket'},
        'object': {'key': 'uploads/photo.jpg', 'size': 12345}
      }
    }
    """
    
    # Extraire infos du fichier
    for record in event['Records']:
        bucket = record['s3']['bucket']['name']
        key = record['s3']['object']['key']
        size = record['s3']['object']['size']
        
        print(f"Fichier uploadé: s3://{bucket}/{key} ({size} bytes)")
        
        # Traiter le fichier
        response = s3.get_object(Bucket=bucket, Key=key)
        content = response['Body'].read()
        
        # Faire quelque chose...
        # Exemple: redimensionner image, extraire texte, etc.
        
    return {
        'statusCode': 200,
        'body': 'Fichier traité'
    }

# EXPLICATION:
# event['Records'][0] = premier événement
# Records peut contenir plusieurs fichiers
# s3.get_object = récupérer le fichier
# response['Body'].read() = contenu du fichier


ÉTAPE 2: PRÉPARER LE DÉPLOIEMENT
════════════════════════════════════════════════════════════════════════════════

# Créer dossier
mkdir lambda-project
cd lambda-project

# Copier le code
cp lambda_function.py .

# Si besoin de dépendances (ex: requests)
pip install requests -t .

# Zipper le code ET les dépendances
zip -r function.zip . -x "*.git*"

# EXPLICATION:
# -r = récursif (incluez subdossiers)
# -x "*.git*" = exclure fichiers git

# Vérifier le contenu du ZIP
unzip -l function.zip

# Résultat devrait montrer:
# lambda_function.py
# requests/  (si dépendan)


ÉTAPE 3: CRÉER LA FONCTION LAMBDA
════════════════════════════════════════════════════════════════════════════════

# Créer fonction simple (sans VPC)
aws lambda create-function \
  --function-name hello-world \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-execution-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip

# EXPLICATIONS DÉTAILLÉES:

# "--function-name hello-world"
#   = Nom unique de votre fonction
#   = Minuscules, tirets OK
#   = Pas de symboles spéciaux

# "--runtime python3.12"
#   = Langage et version
#   = Options: python3.12, python3.11, nodejs20.x, java21, etc.
#   = AWS met à jour les runtimes régulièrement

# "--role arn:aws:iam::123456789012:role/lambda-execution-role"
#   = ARN du rôle IAM créé précédemment
#   = Remplacer 123456789012 par votre Account ID
#   = OBLIGATOIRE!

# "--handler lambda_function.lambda_handler"
#   = Où trouver la fonction d'entrée
#   = Format: filename.function_name
#   = "lambda_function" = nom du fichier Python (sans .py)
#   = "lambda_handler" = nom de la fonction

# "--zip-file fileb://function.zip"
#   = Chemin vers le ZIP
#   = fileb:// = fichier binaire local
#   = Limite: max 50 MB (pour fichiers > 50MB, utiliser S3)

# RÉSULTAT:
# AWS retourne les détails de la fonction créée
# Incluant: FunctionArn, CodeSha256, etc.


# Créer fonction avec variables d'environnement
aws lambda create-function \
  --function-name process-images \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-execution-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --environment Variables={OUTPUT_BUCKET=my-output-bucket,MAX_SIZE=1000000}

# EXPLICATION:
# Variables={KEY1=value1,KEY2=value2}
# Accessibles dans code via: os.environ.get('OUTPUT_BUCKET')

# Créer fonction avec configuration personnalisée
aws lambda create-function \
  --function-name data-processor \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-execution-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --timeout 60 \
  --memory-size 512 \
  --ephemeral-storage Size=512 \
  --description "Traite données des queues SQS"

# EXPLICATIONS:

# "--timeout 60"
#   = Temps maximum d'exécution (secondes)
#   = Min: 1 seconde
#   = Max: 900 secondes (15 minutes)
#   = Par défaut: 3 secondes (TRÈS court!)
#   = [ATTENTION] Si timeout -> fonction tue + erreur
#   = CONSEIL: Toujours spécifier timeout réaliste!

# "--memory-size 512"
#   = Mémoire RAM allouée (MB)
#   = Min: 128 MB
#   = Max: 10,240 MB (10 GB)
#   = Par défaut: 128 MB
#   = Plus de mémoire = CPU plus rapide = plus cher
#   = CONSEIL: Commencer avec 256-512 MB, augmenter si besoin

# "--ephemeral-storage Size=512"
#   = Stockage temporaire (/tmp)
#   = Min: 512 MB
#   = Max: 10,240 MB
#   = Utile pour fichiers temporaires
#   = [ATTENTION] Perdu après exécution

# "--description"
#   = Documentation (optionnel mais recommandé)


ÉTAPE 4: TESTER LA FONCTION
════════════════════════════════════════════════════════════════════════════════

# Invoquer la fonction synchrone (attendre réponse)
aws lambda invoke \
  --function-name hello-world \
  response.json

# EXPLICATION:
# Lambda exécute le code et retourne le résultat
# response.json = fichier où sauver la réponse

# Voir la réponse
cat response.json

# Résultat exemple:
# {"statusCode": 200, "body": "Hello from Lambda!"}

# Invoquer avec payload (données d'entrée)
aws lambda invoke \
  --function-name hello-world \
  --payload '{"name":"Alice","age":30}' \
  response.json

# EXPLICATION:
# --payload = les données deviennent event dans lambda_handler
# Le JSON doit être entre guillemets simples

# Ou à partir d'un fichier JSON
echo '{"name":"Bob","items":["apple","banana"]}' > input.json

aws lambda invoke \
  --function-name hello-world \
  --payload file://input.json \
  response.json

# Invoquer asynchrone (ne pas attendre)
aws lambda invoke \
  --function-name hello-world \
  --invocation-type Event \
  response.json

# EXPLICATION:
# --invocation-type Event = asynchrone
# Lambda ne retourne pas le résultat
# Seulement un code HTTP 202 (accepté)
# [ATTENTION] Si erreur -> événement pas traité!

# Invoquer avec Log Type (voir logs console)
aws lambda invoke \
  --function-name hello-world \
  --log-type Tail \
  response.json

# Les logs s'affichent dans la réponse

# Voir détails complets
aws lambda invoke \
  --function-name hello-world \
  --payload '{"test":true}' \
  response.json
cat response.json | python -m json.tool


═══════════════════════════════════════════════════════════════════════════════
[OK] LISTER ET GÉRER FONCTIONS
═══════════════════════════════════════════════════════════════════════════════

# Lister toutes les fonctions
aws lambda list-functions

# Format tableau lisible
aws lambda list-functions \
  --query 'Functions[*].[FunctionName,Runtime,LastModified,CodeSize]' \
  --output table

# Résultat:
# | FunctionName    | Runtime     | LastModified            | CodeSize |
# |-----------------|-------------|-------------------------|----------|
# | hello-world     | python3.12  | 2024-01-15T10:30:00     | 2048     |
# | process-images  | python3.12  | 2024-01-14T15:22:00     | 5120     |

# Récupérer info une fonction spécifique
aws lambda get-function \
  --function-name hello-world \
  --query 'Configuration.[FunctionName,Runtime,Handler,MemorySize,Timeout]' \
  --output table

# Récupérer SEULEMENT la configuration (pas le code)
aws lambda get-function-configuration \
  --function-name hello-world

# Récupérer les variables d'environnement
aws lambda get-function-configuration \
  --function-name hello-world \
  --query 'Environment.Variables' \
  --output table

# Récupérer les logs récents (CloudWatch)
aws logs tail /aws/lambda/hello-world --follow

# EXPLICATION:
# /aws/lambda/FUNCTION_NAME = log group automatique
# --follow = afficher logs en temps réel

# Voir logs avec filter
aws logs tail /aws/lambda/hello-world --grep ERROR

# Compter invocations
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Invocations \
  --dimensions Name=FunctionName,Value=hello-world \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-16T00:00:00Z \
  --period 3600 \
  --statistics Sum


═══════════════════════════════════════════════════════════════════════════════
[OK] METTRE À JOUR UNE FONCTION - EXPLICATIONS
═══════════════════════════════════════════════════════════════════════════════

# Mettre à jour le code (ZIP)
zip function.zip lambda_function.py

aws lambda update-function-code \
  --function-name hello-world \
  --zip-file fileb://function.zip

# EXPLICATION:
# Remplace le code avec le ZIP fourni
# Version $LATEST est modifiée immédiatement
# Autres versions restent inchangées

# Mettre à jour code à partir de S3
aws lambda update-function-code \
  --function-name hello-world \
  --s3-bucket my-code-bucket \
  --s3-key lambda/function.zip

# EXPLICATION:
# Utile si ZIP > 50 MB
# Doit être dans le même compte et région

# Mettre à jour configuration
aws lambda update-function-configuration \
  --function-name hello-world \
  --timeout 120 \
  --memory-size 512

# Mettre à jour runtime
aws lambda update-function-configuration \
  --function-name hello-world \
  --runtime python3.12

# Mettre à jour handler
aws lambda update-function-configuration \
  --function-name hello-world \
  --handler new_module.new_handler

# Mettre à jour variables d'environnement
aws lambda update-function-configuration \
  --function-name hello-world \
  --environment Variables={API_KEY=abc123,DEBUG=true}

# Ajouter une variable (vs remplacer)
# [ATTENTION] ATTENTION: variables={...} REMPLACE toutes les variables!
# Donc récupérez d'abord les anciennes:

aws lambda get-function-configuration \
  --function-name hello-world \
  --query 'Environment.Variables' > old-env.json

# Puis les ajouter à votre commande update

# Mettre à jour description
aws lambda update-function-configuration \
  --function-name hello-world \
  --description "Nouvelle description"


═══════════════════════════════════════════════════════════════════════════════
[OK] VERSIONS ET ALIAS - DEPLOYMENTS SAINS
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME SANS VERSIONS:
- Quand mettre à jour code -> affects immédiatement TOUS les appels
- Si bug -> tout casse
- Pas de rollback facile

SOLUTION: VERSIONS & ALIAS

# Publier une version (snapshot du code actuel)
aws lambda publish-version \
  --function-name hello-world \
  --description "Release v1.0 - Fixes bug XYZ"

# EXPLICATION:
# Crée immuable snapshot du code actuel
# La version a un numéro (1, 2, 3...)
# $LATEST = code modifiable actuellement
# Vous pouvez toujours invoquer les anciennes versions!

# Résultat:
# {
#   "FunctionName": "hello-world",
#   "Version": "1",
#   "Description": "Release v1.0 - Fixes bug XYZ",
#   "LastModified": "2024-01-15T10:30:00.000+0000"
# }

# Lister toutes les versions
aws lambda list-versions-by-function \
  --function-name hello-world

# Invoquer version spécifique (pas $LATEST)
aws lambda invoke \
  --function-name hello-world:1 \
  response.json

# EXPLICATION:
# hello-world:1 = invoquer version 1 (pas la dernière!)
# Utile pour rollback rapide

# Créer un alias (pointeur vers version)
aws lambda create-alias \
  --function-name hello-world \
  --name prod \
  --function-version 1 \
  --description "Production: code stable"

# EXPLICATION:
# Alias = nom lisible vers version
# prod = alias pointe vers version 1
# Vous pouvez changer quelle version "prod" pointe sans changer le code!

# Invoquer via alias
aws lambda invoke \
  --function-name hello-world:prod \
  response.json

# EXPLICATION:
# hello-world:prod = invoquer la version que "prod" pointe
# Comparé à: hello-world:1 (version numérotée)

# Créer alias "staging" (test)
aws lambda create-alias \
  --function-name hello-world \
  --name staging \
  --function-version 1

# Mettre à jour alias (changez quelle version pointe)
aws lambda update-alias \
  --function-name hello-world \
  --name prod \
  --function-version 2

# EXPLICATION:
# prod alias maintenant pointe vers version 2
# Ancien code? Version 1 toujours là si besoin rollback!

# Canary deployment: progressivement basculer trafic
# 10% -> version 2, 90% -> version 1
aws lambda update-alias \
  --function-name hello-world \
  --name prod \
  --function-version 2 \
  --routing-config AdditionalVersionWeights={'1'=0.9}

# EXPLICATION:
# AdditionalVersionWeights = poids (% trafic)
# 0.9 = 90% vers version 1
# Reste 10% = version 2 (nouvelle)
# Permet tester nouvelle version sans tout casser!

# Lister aliases
aws lambda list-aliases --function-name hello-world

# Supprimer alias
aws lambda delete-alias \
  --function-name hello-world \
  --name staging


═══════════════════════════════════════════════════════════════════════════════
[OK] LAYERS - DÉPENDANCES PARTAGÉES
═══════════════════════════════════════════════════════════════════════════════

PROBLÈME SANS LAYERS:
- Chaque fonction = ZIP avec toutes les dépendances
- Dépendances dupliquées (requests, boto3, etc.)
- Espace gaspillé

SOLUTION: LAYERS
- Dépendances une seule fois
- Utilisables par plusieurs fonctions
- Mise à jour centralisée

CRÉER UN LAYER

# Structure requise:
# python/lib/python3.12/site-packages/requests/
mkdir -p python/lib/python3.12/site-packages

# Installer dépendances dans la structure
pip install requests -t python/lib/python3.12/site-packages

# Resultat:
# python/
#   lib/
#     python3.12/
#       site-packages/
#         requests/
#         ...

# Zipper le layer
zip -r requests-layer.zip python/

# Publier le layer
aws lambda publish-layer-version \
  --layer-name requests-library \
  --zip-file fileb://requests-layer.zip \
  --compatible-runtimes python3.12

# EXPLICATION:
# --layer-name = nom unique du layer
# --zip-file = le ZIP avec structure python/lib/...
# --compatible-runtimes = quels runtimes peuvent l'utiliser
# RÉSULTAT: LayerVersionArn (sauvegardez!)

# Lister les layers
aws lambda list-layers

# Lister versions d'un layer
aws lambda list-layer-versions --layer-name requests-library

# ATTACHER LE LAYER À UNE FONCTION

# À la création:
aws lambda create-function \
  --function-name process-json \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-execution-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --layers arn:aws:lambda:us-east-1:123456789012:layer:requests-library:1

# À la modification:
aws lambda update-function-configuration \
  --function-name process-json \
  --layers \
    arn:aws:lambda:us-east-1:123456789012:layer:requests-library:1 \
    arn:aws:lambda:us-east-1:123456789012:layer:other-layer:2

# EXPLICATION:
# --layers = liste de layers
# Séparé par espace
# ARN complet avec version

# Utiliser dans le code:
import requests  # From layer!

def lambda_handler(event, context):
    response = requests.get('https://api.example.com')
    return response.json()


═══════════════════════════════════════════════════════════════════════════════
[OK] TRIGGERS - DÉCLENCHER LAMBDA
═══════════════════════════════════════════════════════════════════════════════

# === TRIGGERS - DÉCLENCHER LAMBDA (SUITE) ===

# TRIGGER API GATEWAY
# Permet d'exposer Lambda comme API REST HTTP

# 1. Créer API Gateway REST API
aws apigateway create-rest-api \
  --name my-lambda-api \
  --description "API pour fonction Lambda"

# EXPLICATION:
# Crée un nouveau API Gateway
# Notez l'id retourné (ex: abc123def4)

# 2. Obtenir root resource ID
aws apigateway get-resources \
  --rest-api-id abc123def4

# EXPLICATION:
# Retourne les resources de l'API
# Notez le root resource id (ex: xyz789)

# 3. Créer une resource (route)
aws apigateway create-resource \
  --rest-api-id abc123def4 \
  --parent-id xyz789 \
  --path-part users

# EXPLICATION:
# Crée route /users
# Notez le nouveau resource id

# 4. Créer méthode GET
aws apigateway put-method \
  --rest-api-id abc123def4 \
  --resource-id resource-id \
  --http-method GET \
  --authorization-type NONE

# EXPLICATION:
# GET /users accepte requêtes sans auth
# Options auth: NONE, AWS_IAM, COGNITO_USER_POOLS

# 5. Intégrer avec Lambda
aws apigateway put-integration \
  --rest-api-id abc123def4 \
  --resource-id resource-id \
  --http-method GET \
  --type AWS_PROXY \
  --integration-http-method POST \
  --uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:123456789012:function:my-function/invocations

# EXPLICATION:
# type AWS_PROXY = API Gateway passe requête complète à Lambda
# integration-http-method POST = toujours POST pour Lambda
# uri = ARN Lambda avec path spécial

# 6. Donner permission API Gateway
aws lambda add-permission \
  --function-name my-function \
  --statement-id apigateway-invoke \
  --action lambda:InvokeFunction \
  --principal apigateway.amazonaws.com \
  --source-arn "arn:aws:execute-api:us-east-1:123456789012:abc123def4/*/GET/users"

# EXPLICATION:
# Permet API Gateway d'invoquer Lambda
# source-arn = API ID + stage + méthode + path
# */ = tous les stages

# 7. Déployer API
aws apigateway create-deployment \
  --rest-api-id abc123def4 \
  --stage-name prod

# EXPLICATION:
# Crée déploiement sur stage "prod"
# URL: https://abc123def4.execute-api.us-east-1.amazonaws.com/prod/users

# Tester l'API
curl https://abc123def4.execute-api.us-east-1.amazonaws.com/prod/users


# TRIGGER DYNAMODB STREAMS
# Lambda invoquée quand items modifiés dans table DynamoDB

# 1. Activer DynamoDB Stream sur table
aws dynamodb update-table \
  --table-name my-table \
  --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES

# EXPLICATION:
# StreamViewType options:
#   KEYS_ONLY = seulement keys modifiées
#   NEW_IMAGE = nouvelle valeur item
#   OLD_IMAGE = ancienne valeur item
#   NEW_AND_OLD_IMAGES = les deux

# 2. Obtenir Stream ARN
aws dynamodb describe-table \
  --table-name my-table \
  --query 'Table.LatestStreamArn' \
  --output text

# 3. Créer event source mapping
aws lambda create-event-source-mapping \
  --function-name my-function \
  --event-source-arn arn:aws:dynamodb:us-east-1:123456789012:table/my-table/stream/2024-01-15T10:00:00.000 \
  --batch-size 100 \
  --starting-position LATEST

# EXPLICATION:
# batch-size = nombre max records par invocation
#   Min: 1, Max: 10,000
#   Défaut: 100
# starting-position options:
#   TRIM_HORIZON = depuis début du stream
#   LATEST = seulement nouveaux records
#   AT_TIMESTAMP = depuis timestamp spécifique

# Exemple code Lambda pour DynamoDB Stream
# lambda_function.py
import json

def lambda_handler(event, context):
    for record in event['Records']:
        # Type modification: INSERT, MODIFY, REMOVE
        event_name = record['eventName']
        
        if event_name == 'INSERT':
            new_item = record['dynamodb']['NewImage']
            print(f"Nouveau item: {new_item}")
        
        elif event_name == 'MODIFY':
            old_item = record['dynamodb']['OldImage']
            new_item = record['dynamodb']['NewImage']
            print(f"Modifié: {old_item} -> {new_item}")
        
        elif event_name == 'REMOVE':
            old_item = record['dynamodb']['OldImage']
            print(f"Supprimé: {old_item}")
    
    return {'statusCode': 200}


# TRIGGER SQS
# Lambda invoquée pour traiter messages SQS

# 1. Créer queue SQS
aws sqs create-queue --queue-name my-lambda-queue

# 2. Obtenir queue ARN
aws sqs get-queue-attributes \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-lambda-queue \
  --attribute-names QueueArn

# 3. Créer event source mapping
aws lambda create-event-source-mapping \
  --function-name my-function \
  --event-source-arn arn:aws:sqs:us-east-1:123456789012:my-lambda-queue \
  --batch-size 10 \
  --maximum-batching-window-in-seconds 5

# EXPLICATION:
# batch-size = messages max par invocation (1-10,000)
# maximum-batching-window = attendre X secondes pour remplir batch
#   Si batch pas plein après X secondes -> invoquer quand même
#   Min: 0, Max: 300 secondes

# Exemple code Lambda pour SQS
# lambda_function.py
import json

def lambda_handler(event, context):
    for record in event['Records']:
        # Corps du message
        body = json.loads(record['body'])
        message_id = record['messageId']
        
        print(f"Message {message_id}: {body}")
        
        # Traiter le message
        # ...
    
    # Si succès -> messages supprimés automatiquement de queue
    # Si erreur -> messages retournés à queue (selon DLQ config)
    return {'statusCode': 200}

# Configuration DLQ (Dead Letter Queue)
# Messages qui échouent -> envoyés vers DLQ
aws lambda update-function-configuration \
  --function-name my-function \
  --dead-letter-config TargetArn=arn:aws:sqs:us-east-1:123456789012:my-dlq

# EXPLICATION:
# Si Lambda échoue (exception, timeout) -> message vers DLQ
# Permet de traiter/analyser messages problématiques


# TRIGGER SNS
# Lambda invoquée quand message publié sur topic SNS

# 1. Créer topic SNS
aws sns create-topic --name my-lambda-topic

# 2. Obtenir topic ARN
aws sns list-topics \
  --query 'Topics[?contains(TopicArn, `my-lambda-topic`)].TopicArn' \
  --output text

# 3. Souscrire Lambda au topic
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-lambda-topic \
  --protocol lambda \
  --notification-endpoint arn:aws:lambda:us-east-1:123456789012:function:my-function

# 4. Donner permission SNS
aws lambda add-permission \
  --function-name my-function \
  --statement-id sns-invoke \
  --action lambda:InvokeFunction \
  --principal sns.amazonaws.com \
  --source-arn arn:aws:sns:us-east-1:123456789012:my-lambda-topic

# Exemple code Lambda pour SNS
# lambda_function.py
import json

def lambda_handler(event, context):
    for record in event['Records']:
        # Message SNS
        message = record['Sns']['Message']
        subject = record['Sns']['Subject']
        
        print(f"Reçu: {subject}")
        print(f"Message: {message}")
        
        # Traiter le message
        # ...
    
    return {'statusCode': 200}


# TRIGGER EVENTBRIDGE (CloudWatch Events)
# Lambda invoquée selon schedule ou pattern événement

# 1. Créer rule EventBridge (schedule)
aws events put-rule \
  --name my-lambda-schedule \
  --schedule-expression "rate(5 minutes)" \
  --state ENABLED \
  --description "Invoquer Lambda toutes les 5 minutes"

# EXPLICATION:
# schedule-expression formats:
#   rate(5 minutes) = toutes les 5 minutes
#   rate(1 hour) = toutes les heures
#   rate(1 day) = tous les jours
#   cron(0 12 * * ? *) = tous les jours à 12h UTC
#   cron(0 9 ? * MON-FRI *) = lundi-vendredi à 9h

# 2. Ajouter Lambda comme target
aws events put-targets \
  --rule my-lambda-schedule \
  --targets "Id"="1","Arn"="arn:aws:lambda:us-east-1:123456789012:function:my-function"

# 3. Donner permission EventBridge
aws lambda add-permission \
  --function-name my-function \
  --statement-id eventbridge-invoke \
  --action lambda:InvokeFunction \
  --principal events.amazonaws.com \
  --source-arn arn:aws:events:us-east-1:123456789012:rule/my-lambda-schedule

# Créer rule basée sur pattern (événements AWS)
aws events put-rule \
  --name ec2-state-change \
  --event-pattern '{"source":["aws.ec2"],"detail-type":["EC2 Instance State-change Notification"]}' \
  --state ENABLED

# EXPLICATION:
# event-pattern = JSON qui filtre événements
# Invoquer Lambda quand instance EC2 change d'état


# TRIGGER KINESIS DATA STREAMS
# Lambda invoquée pour traiter records Kinesis

# 1. Créer stream Kinesis
aws kinesis create-stream \
  --stream-name my-stream \
  --shard-count 1

# 2. Créer event source mapping
aws lambda create-event-source-mapping \
  --function-name my-function \
  --event-source-arn arn:aws:kinesis:us-east-1:123456789012:stream/my-stream \
  --batch-size 100 \
  --starting-position LATEST \
  --maximum-batching-window-in-seconds 10

# EXPLICATION:
# batch-size = records max par invocation
# starting-position:
#   TRIM_HORIZON = depuis début
#   LATEST = nouveaux seulement
#   AT_TIMESTAMP = depuis timestamp


═══════════════════════════════════════════════════════════════════════════════
[OK] CONCURRENCY & SCALING
═══════════════════════════════════════════════════════════════════════════════

# CONCURRENCY = nombre d'exécutions simultanées

# Voir concurrency actuelle
aws lambda get-function-concurrency \
  --function-name my-function

# RÉSERVER CONCURRENCY (garantir capacité)
aws lambda put-function-concurrency \
  --function-name my-function \
  --reserved-concurrent-executions 100

# EXPLICATION:
# Réserve 100 exécutions pour cette fonction
# Autres fonctions ne peuvent pas utiliser ces 100
# Utile pour fonctions critiques

# RETIRER RÉSERVATION
aws lambda delete-function-concurrency \
  --function-name my-function

# PROVISIONED CONCURRENCY (éviter cold starts)
# Lambda garde instances chaudes (toujours prêtes)

# Configurer provisioned concurrency
aws lambda put-provisioned-concurrency-config \
  --function-name my-function \
  --provisioned-concurrent-executions 10 \
  --qualifier prod

# EXPLICATION:
# 10 instances toujours chaudes sur alias "prod"
# ZÉRO cold start pour ces 10 instances
# Plus cher mais réponse instantanée

# Voir provisioned concurrency
aws lambda get-provisioned-concurrency-config \
  --function-name my-function \
  --qualifier prod

# Supprimer provisioned concurrency
aws lambda delete-provisioned-concurrency-config \
  --function-name my-function \
  --qualifier prod


═══════════════════════════════════════════════════════════════════════════════
[OK] MONITORING & DEBUGGING
═══════════════════════════════════════════════════════════════════════════════

# CLOUDWATCH LOGS
# Lambda logs automatiquement vers /aws/lambda/FUNCTION_NAME

# Voir logs en temps réel
aws logs tail /aws/lambda/my-function --follow

# Filtrer logs (seulement erreurs)
aws logs tail /aws/lambda/my-function --grep ERROR

# Voir logs d'une période spécifique
aws logs filter-log-events \
  --log-group-name /aws/lambda/my-function \
  --start-time 1705334400000 \
  --end-time 1705420800000

# EXPLICATION:
# start-time/end-time = timestamp Unix en millisecondes

# CLOUDWATCH METRICS
# Lambda publie métriques automatiquement

# Voir invocations (nombre d'appels)
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Invocations \
  --dimensions Name=FunctionName,Value=my-function \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-16T00:00:00Z \
  --period 3600 \
  --statistics Sum

# Voir erreurs
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Errors \
  --dimensions Name=FunctionName,Value=my-function \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-16T00:00:00Z \
  --period 3600 \
  --statistics Sum

# Voir durée d'exécution
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Duration \
  --dimensions Name=FunctionName,Value=my-function \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-16T00:00:00Z \
  --period 3600 \
  --statistics Average,Maximum

# Voir throttles (appels rejetés)
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Throttles \
  --dimensions Name=FunctionName,Value=my-function \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-16T00:00:00Z \
  --period 3600 \
  --statistics Sum

# X-RAY (TRACING distribué)
# Permet de visualiser requêtes à travers services

# Activer X-Ray
aws lambda update-function-configuration \
  --function-name my-function \
  --tracing-config Mode=Active

# EXPLICATION:
# Mode=Active = activer tracing
# Mode=PassThrough = propager trace ID seulement

# Utiliser X-Ray dans code
# lambda_function.py
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.core import patch_all

# Patcher bibliothèques (boto3, requests, etc.)
patch_all()

def lambda_handler(event, context):
    # Créer subsegment custom
    with xray_recorder.capture('mon_traitement'):
        # Votre code
        result = faire_quelque_chose()
    
    return result


═══════════════════════════════════════════════════════════════════════════════
[OK] SÉCURITÉ BEST PRACTICES
═══════════════════════════════════════════════════════════════════════════════

# 1. PRINCIPE DU MOINDRE PRIVILÈGE
# Donnez SEULEMENT les permissions nécessaires

# Mauvais exemple (trop de permissions)
{
  "Effect": "Allow",
  "Action": "s3:*",
  "Resource": "*"
}

# Bon exemple (permissions minimales)
{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "s3:PutObject"],
  "Resource": "arn:aws:s3:::my-bucket/uploads/*"
}

# 2. SECRETS MANAGER (pas de credentials en clair!)
# Stocker credentials dans Secrets Manager

# Créer secret
aws secretsmanager create-secret \
  --name my-db-password \
  --secret-string '{"username":"admin","password":"SuperSecret123!"}'

# Récupérer secret dans Lambda
# lambda_function.py
import boto3
import json

secretsmanager = boto3.client('secretsmanager')

def lambda_handler(event, context):
    # Récupérer secret
    response = secretsmanager.get_secret_value(SecretId='my-db-password')
    secret = json.loads(response['SecretString'])
    
    username = secret['username']
    password = secret['password']
    
    # Utiliser credentials...

# 3. CHIFFREMENT VARIABLES D'ENVIRONNEMENT
# Variables sensibles -> chiffrer avec KMS

# Créer KMS key
aws kms create-key --description "Lambda encryption key"

# Obtenir key ID
aws kms list-keys

# Utiliser key pour chiffrer variables
aws lambda update-function-configuration \
  --function-name my-function \
  --environment Variables={DB_PASSWORD=encrypted_value} \
  --kms-key-arn arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012

# 4. RESOURCE-BASED POLICY (contrôler qui peut invoquer)
# Lister permissions actuelles
aws lambda get-policy --function-name my-function

# Retirer permission
aws lambda remove-permission \
  --function-name my-function \
  --statement-id some-statement-id


═══════════════════════════════════════════════════════════════════════════════
[OK] PERFORMANCE OPTIMIZATION
═══════════════════════════════════════════════════════════════════════════════

# 1. RÉUTILISER CONNEXIONS
# NE PAS créer connexions dans handler!

# [X] MAUVAIS (connexion à chaque invocation)
def lambda_handler(event, context):
    client = boto3.client('s3')  # Nouvelle connexion
    # ...

# [OK] BON (connexion réutilisée)
import boto3
client = boto3.client('s3')  # En dehors du handler

def lambda_handler(event, context):
    # Utiliser client existant
    # ...

# 2. OPTIMISER MÉMOIRE
# Plus de mémoire = CPU plus rapide = exécution plus rapide

# Tester différentes tailles mémoire
for mem in 128 256 512 1024 2048; do
  aws lambda update-function-configuration \
    --function-name my-function \
    --memory-size $mem
  
  # Invoquer et mesurer temps
  time aws lambda invoke --function-name my-function out.json
done

# 3. UTILISER LAYERS POUR DÉPENDANCES LOURDES
# Éviter d'inclure grosses bibliothèques dans chaque fonction

# 4. WARM-UP FUNCTIONS
# Invoquer fonction périodiquement pour éviter cold starts

# EventBridge rule (toutes les 5 min)
aws events put-rule \
  --name lambda-warmer \
  --schedule-expression "rate(5 minutes)"

aws events put-targets \
  --rule lambda-warmer \
  --targets "Id"="1","Arn"="arn:aws:lambda:us-east-1:123456789012:function:my-function"


═══════════════════════════════════════════════════════════════════════════════
[OK] EXEMPLES COMPLETS PAR CAS D'USAGE
═══════════════════════════════════════════════════════════════════════════════

# === EXEMPLE 1: IMAGE PROCESSING (S3 -> Lambda -> S3) ===

# lambda_function.py
import boto3
from PIL import Image
import io

s3 = boto3.client('s3')

def lambda_handler(event, context):
    # Récupérer info fichier uploadé
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']
    
    # Télécharger image
    response = s3.get_object(Bucket=bucket, Key=key)
    image_data = response['Body'].read()
    
    # Ouvrir image
    image = Image.open(io.BytesIO(image_data))
    
    # Redimensionner
    image.thumbnail((800, 800))
    
    # Sauver dans buffer
    buffer = io.BytesIO()
    image.save(buffer, 'JPEG')
    buffer.seek(0)
    
    # Upload vers S3
    output_key = f"thumbnails/{key}"
    s3.put_object(
        Bucket=bucket,
        Key=output_key,
        Body=buffer,
        ContentType='image/jpeg'
    )
    
    return {'statusCode': 200, 'body': f'Thumbnail créé: {output_key}'}

# requirements.txt
# Pillow==10.0.0

# Créer layer Pillow
mkdir -p python/lib/python3.12/site-packages
pip install Pillow -t python/lib/python3.12/site-packages
zip -r pillow-layer.zip python/

# Publier layer
aws lambda publish-layer-version \
  --layer-name pillow \
  --zip-file fileb://pillow-layer.zip \
  --compatible-runtimes python3.12

# Créer fonction
zip function.zip lambda_function.py

aws lambda create-function \
  --function-name image-processor \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-s3-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --timeout 60 \
  --memory-size 512 \
  --layers arn:aws:lambda:us-east-1:123456789012:layer:pillow:1

# Configurer S3 trigger
aws lambda add-permission \
  --function-name image-processor \
  --statement-id s3-trigger \
  --action lambda:InvokeFunction \
  --principal s3.amazonaws.com \
  --source-arn arn:aws:s3:::my-bucket

aws s3api put-bucket-notification-configuration \
  --bucket my-bucket \
  --notification-configuration '{
    "LambdaFunctionConfigurations": [{
      "LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:image-processor",
      "Events": ["s3:ObjectCreated:*"],
      "Filter": {"Key": {"FilterRules": [{"Name": "prefix", "Value": "uploads/"}]}}
    }]
  }'


# === EXEMPLE 2: API REST (CRUD DynamoDB) ===

# lambda_function.py
import json
import boto3
import os
from decimal import Decimal

dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])

class DecimalEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, Decimal):
            return float(obj)
        return super(DecimalEncoder, self).default(obj)

def lambda_handler(event, context):
    http_method = event['httpMethod']
    path = event['path']
    
    try:
        if http_method == 'GET' and path == '/users':
            # Lister tous les users
            response = table.scan()
            return {
                'statusCode': 200,
                'body': json.dumps(response['Items'], cls=DecimalEncoder),
                'headers': {'Content-Type': 'application/json'}
            }
        
        elif http_method == 'GET' and path.startswith('/users/'):
            # Obtenir user spécifique
            user_id = path.split('/')[-1]
            response = table.get_item(Key={'userId': user_id})
            
            if 'Item' in response:
                return {
                    'statusCode': 200,
                    'body': json.dumps(response['Item'], cls=DecimalEncoder)
                }
            else:
                return {'statusCode': 404, 'body': 'User not found'}
        
        elif http_method == 'POST' and path == '/users':
            # Créer nouveau user
            body = json.loads(event['body'])
            table.put_item(Item=body)
            
            return {
                'statusCode': 201,
                'body': json.dumps({'message': 'User created', 'user': body})
            }
        
        elif http_method == 'DELETE' and path.startswith('/users/'):
            # Supprimer user
            user_id = path.split('/')[-1]
            table.delete_item(Key={'userId': user_id})
            
            return {
                'statusCode': 200,
                'body': json.dumps({'message': 'User deleted'})
            }
        
        else:
            return {'statusCode': 404, 'body': 'Route not found'}
    
    except Exception as e:
        print(f"Error: {str(e)}")
        return {
            'statusCode': 500,
            'body': json.dumps({'error': str(e)})
        }

# Créer fonction
zip function.zip lambda_function.py

aws lambda create-function \
  --function-name users-api \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-dynamodb-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --environment Variables={TABLE_NAME=users-table} \
  --timeout 30


# === EXEMPLE 3: CRON JOB (NETTOYAGE S3) ===

# lambda_function.py
import boto3
from datetime import datetime, timedelta

s3 = boto3.client('s3')
BUCKET_NAME = 'my-logs-bucket'
DAYS_TO_KEEP = 30

def lambda_handler(event, context):
    # Date limite
    cutoff_date = datetime.now() - timedelta(days=DAYS_TO_KEEP)
    
    # Lister objets
    paginator = s3.get_paginator('list_objects_v2')
    pages = paginator.paginate(Bucket=BUCKET_NAME, Prefix='logs/')
    
    deleted_count = 0
    
    for page in pages:
        if 'Contents' not in page:
            continue
        
        for obj in page['Contents']:
            # Vérifier date
            if obj['LastModified'].replace(tzinfo=None) < cutoff_date:
                print(f"Suppression: {obj['Key']} (date: {obj['LastModified']})")
                
                s3.delete_object(Bucket=BUCKET_NAME, Key=obj['Key'])
                deleted_count += 1
    
    return {
        'statusCode': 200,
        'body': f'{deleted_count} fichiers supprimés (plus de {DAYS_TO_KEEP} jours)'
    }

# Créer fonction
zip function.zip lambda_function.py

aws lambda create-function \
  --function-name s3-cleanup \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-s3-cleanup-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --timeout 300 \
  --memory-size 256

# Configurer schedule (tous les jours à 2h UTC)
aws events put-rule \
  --name daily-s3-cleanup \
  --schedule-expression "cron(0 2 * * ? *)" \
  --state ENABLED

aws events put-targets \
  --rule daily-s3-cleanup \
  --targets "Id"="1","Arn"="arn:aws:lambda:us-east-1:123456789012:function:s3-cleanup"

aws lambda add-permission \
  --function-name s3-cleanup \
  --statement-id eventbridge-invoke \
  --action lambda:InvokeFunction \
  --principal events.amazonaws.com \
  --source-arn arn:aws:events:us-east-1:123456789012:rule/daily-s3-cleanup


# === EXEMPLE 4: ETL PIPELINE (SQS -> Lambda -> RDS) ===

# lambda_function.py
import json
import boto3
import pymysql
import os

# Connexion DB en dehors du handler (réutilisée)
def get_db_connection():
    return pymysql.connect(
        host=os.environ['DB_HOST'],
        user=os.environ['DB_USER'],
        password=os.environ['DB_PASSWORD'],
        database=os.environ['DB_NAME'],
        cursorclass=pymysql.cursors.DictCursor
    )

connection = None

def lambda_handler(event, context):
    global connection
    
    # Récupérer ou créer connexion
    if connection is None or not connection.open:
        connection = get_db_connection()
    
    # Traiter chaque message SQS
    for record in event['Records']:
        try:
            # Parser message
            body = json.loads(record['body'])
            
            # Extraire données
            user_id = body['userId']
            action = body['action']
            timestamp = body['timestamp']
            
            # Insérer dans RDS
            with connection.cursor() as cursor:
                sql = """
                    INSERT INTO user_actions (user_id, action, timestamp)
                    VALUES (%s, %s, %s)
                """
                cursor.execute(sql, (user_id, action, timestamp))
            
            connection.commit()
            print(f"Traité: {user_id} - {action}")
            
        except Exception as e:
            print(f"Erreur traitement message: {str(e)}")
            # Message retourne à queue (selon DLQ config)
            raise
    
    return {'statusCode': 200, 'processedMessages': len(event['Records'])}

# requirements.txt
# PyMySQL==1.1.0

# Créer layer PyMySQL
mkdir -p python/lib/python3.12/site-packages
pip install PyMySQL -t python/lib/python3.12/site-packages
zip -r pymysql-layer.zip python/

aws lambda publish-layer-version \
  --layer-name pymysql \
  --zip-file fileb://pymysql-layer.zip \
  --compatible-runtimes python3.12

# Créer fonction (dans VPC pour accéder RDS)
zip function.zip lambda_function.py

aws lambda create-function \
  --function-name etl-processor \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-vpc-rds-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --layers arn:aws:lambda:us-east-1:123456789012:layer:pymysql:1 \
  --timeout 60 \
  --memory-size 512 \
  --vpc-config SubnetIds=subnet-12345,subnet-67890,SecurityGroupIds=sg-abc123 \
  --environment Variables={
    DB_HOST=mydb.abc123.us-east-1.rds.amazonaws.com,
    DB_USER=admin,
    DB_PASSWORD=SuperSecret123,
    DB_NAME=analytics
  }

# Configurer SQS trigger
aws lambda create-event-source-mapping \
  --function-name etl-processor \
  --event-source-arn arn:aws:sqs:us-east-1:123456789012:my-queue \
  --batch-size 10 \
  --maximum-batching-window-in-seconds 5


# === EXEMPLE 5: NOTIFICATION SYSTEM (DynamoDB Stream -> Lambda -> SNS) ===

# lambda_function.py
import json
import boto3
import os

sns = boto3.client('sns')
TOPIC_ARN = os.environ['SNS_TOPIC_ARN']

def lambda_handler(event, context):
    for record in event['Records']:
        # Vérifier type événement
        if record['eventName'] == 'INSERT':
            # Nouvel item créé
            new_item = record['dynamodb']['NewImage']
            
            # Extraire données (format DynamoDB)
            order_id = new_item['orderId']['S']
            customer_email = new_item['customerEmail']['S']
            total_amount = new_item['totalAmount']['N']
            
            # Créer message
            message = f"""
            Nouvelle commande reçue!
            
            Order ID: {order_id}
            Client: {customer_email}
            Montant: ${total_amount}
            
            Merci pour votre commande!
            """
            
            # Envoyer notification SNS
            sns.publish(
                TopicArn=TOPIC_ARN,
                Subject=f'Nouvelle commande #{order_id}',
                Message=message
            )
            
            print(f"Notification envoyée pour commande {order_id}")
        
        elif record['eventName'] == 'MODIFY':
            # Item modifié (ex: statut commande changé)
            old_item = record['dynamodb']['OldImage']
            new_item = record['dynamodb']['NewImage']
            
            order_id = new_item['orderId']['S']
            old_status = old_item.get('status', {}).get('S', 'unknown')
            new_status = new_item.get('status', {}).get('S', 'unknown')
            
            if old_status != new_status:
                message = f"Commande #{order_id}: {old_status} -> {new_status}"
                
                sns.publish(
                    TopicArn=TOPIC_ARN,
                    Subject=f'Mise à jour commande #{order_id}',
                    Message=message
                )
    
    return {'statusCode': 200}

# Créer SNS topic
aws sns create-topic --name order-notifications

# Souscrire email au topic
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:order-notifications \
  --protocol email \
  --notification-endpoint customer@example.com

# Créer fonction
zip function.zip lambda_function.py

aws lambda create-function \
  --function-name order-notifier \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-dynamodb-sns-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --environment Variables={SNS_TOPIC_ARN=arn:aws:sns:us-east-1:123456789012:order-notifications}

# Configurer DynamoDB Stream trigger
aws lambda create-event-source-mapping \
  --function-name order-notifier \
  --event-source-arn arn:aws:dynamodb:us-east-1:123456789012:table/orders/stream/2024-01-15T10:00:00.000 \
  --batch-size 10 \
  --starting-position LATEST


# === EXEMPLE 6: FILE PROCESSING (CSV -> Lambda -> DynamoDB) ===

# lambda_function.py
import json
import boto3
import csv
import io
import os

s3 = boto3.client('s3')
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['TABLE_NAME'])

def lambda_handler(event, context):
    # Récupérer info fichier S3
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']
    
    print(f"Traitement fichier: s3://{bucket}/{key}")
    
    # Télécharger fichier
    response = s3.get_object(Bucket=bucket, Key=key)
    content = response['Body'].read().decode('utf-8')
    
    # Parser CSV
    csv_reader = csv.DictReader(io.StringIO(content))
    
    processed_count = 0
    batch_items = []
    
    for row in csv_reader:
        # Préparer item DynamoDB
        item = {
            'id': row['id'],
            'name': row['name'],
            'email': row['email'],
            'age': int(row['age']),
            'timestamp': row.get('timestamp', '')
        }
        
        batch_items.append(item)
        
        # Batch write (max 25 items)
        if len(batch_items) == 25:
            write_batch(batch_items)
            processed_count += len(batch_items)
            batch_items = []
    
    # Écrire items restants
    if batch_items:
        write_batch(batch_items)
        processed_count += len(batch_items)
    
    print(f"Total traité: {processed_count} items")
    
    return {
        'statusCode': 200,
        'body': f'Traité {processed_count} items de {key}'
    }

def write_batch(items):
    """Écrire batch dans DynamoDB"""
    with table.batch_writer() as batch:
        for item in items:
            batch.put_item(Item=item)

# Créer fonction
zip function.zip lambda_function.py

aws lambda create-function \
  --function-name csv-processor \
  --runtime python3.12 \
  --role arn:aws:iam::123456789012:role/lambda-s3-dynamodb-role \
  --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip \
  --timeout 300 \
  --memory-size 512 \
  --environment Variables={TABLE_NAME=users}

# Configurer S3 trigger
aws lambda add-permission \
  --function-name csv-processor \
  --statement-id s3-csv-trigger \
  --action lambda:InvokeFunction \
  --principal s3.amazonaws.com \
  --source-arn arn:aws:s3:::my-csv-bucket

aws s3api put-bucket-notification-configuration \
  --bucket my-csv-bucket \
  --notification-configuration '{
    "LambdaFunctionConfigurations": [{
      "LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:csv-processor",
      "Events": ["s3:ObjectCreated:*"],
      "Filter": {"Key": {"FilterRules": [{"Name": "suffix", "Value": ".csv"}]}}
    }]
  }'


═══════════════════════════════════════════════════════════════════════════════
[OK] LAMBDA AVEC CONTAINERS (ALTERNATIVE AU ZIP)
═══════════════════════════════════════════════════════════════════════════════

# Pour code > 50 MB ou dépendances complexes
# Utiliser images Docker au lieu de ZIP

# Créer Dockerfile
cat > Dockerfile << 'EOF'
FROM public.ecr.aws/lambda/python:3.12

# Copier requirements
COPY requirements.txt ${LAMBDA_TASK_ROOT}

# Installer dépendances
RUN pip install -r requirements.txt

# Copier code
COPY lambda_function.py ${LAMBDA_TASK_ROOT}

# Handler
CMD ["lambda_function.lambda_handler"]
EOF

# requirements.txt
cat > requirements.txt << 'EOF'
numpy==1.24.0
pandas==2.0.0
scikit-learn==1.3.0
EOF

# Créer code Lambda
cat > lambda_function.py << 'EOF'
import numpy as np
import pandas as pd

def lambda_handler(event, context):
    # Utiliser numpy/pandas
    data = np.array([1, 2, 3, 4, 5])
    df = pd.DataFrame({'numbers': data})
    
    return {
        'statusCode': 200,
        'body': df.to_json()
    }
EOF

# Créer repo ECR
aws ecr create-repository \
  --repository-name lambda-ml-function \
  --image-scanning-configuration scanOnPush=true

# Login Docker vers ECR
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

# Build image
docker build -t lambda-ml-function .

# Tag image
docker tag lambda-ml-function:latest \
  123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-ml-function:latest

# Push vers ECR
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-ml-function:latest

# Créer fonction Lambda avec container
aws lambda create-function \
  --function-name ml-processor \
  --package-type Image \
  --code ImageUri=123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-ml-function:latest \
  --role arn:aws:iam::123456789012:role/lambda-execution-role \
  --timeout 60 \
  --memory-size 2048

# Mettre à jour image
docker build -t lambda-ml-function .
docker tag lambda-ml-function:latest \
  123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-ml-function:latest
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-ml-function:latest

aws lambda update-function-code \
  --function-name ml-processor \
  --image-uri 123456789012.dkr.ecr.us-east-1.amazonaws.com/lambda-ml-function:latest


═══════════════════════════════════════════════════════════════════════════════
[OK] LAMBDA EXTENSIONS (MONITORING CUSTOM, CACHING, ETC.)
═══════════════════════════════════════════════════════════════════════════════

# Extensions = processus qui s'exécutent avec Lambda
# Cas d'usage: monitoring, sécurité, caching, logging custom

# EXEMPLE: Extension simple (logs custom)

# extension.sh
#!/bin/bash

# Enregistrer extension
LAMBDA_EXTENSION_NAME="my-extension"
curl -X POST "http://${AWS_LAMBDA_RUNTIME_API}/2020-01-01/extension/register" \
  -H "Lambda-Extension-Name: ${LAMBDA_EXTENSION_NAME}" \
  -d '{"events":["INVOKE","SHUTDOWN"]}'

# Boucle événements
while true; do
  # Attendre prochain événement
  EVENT=$(curl -s "http://${AWS_LAMBDA_RUNTIME_API}/2020-01-01/extension/event/next")
  
  echo "Extension reçu événement: $EVENT"
  
  # Traiter événement
  # ...
done

# Structure fichier extension
# extensions/
#   my-extension (executable)

chmod +x extension.sh
mkdir extensions
mv extension.sh extensions/my-extension

# Zipper avec code Lambda
zip -r function.zip lambda_function.py extensions/

# Déployer
aws lambda update-function-code \
  --function-name my-function \
  --zip-file fileb://function.zip


═══════════════════════════════════════════════════════════════════════════════
[OK] COST OPTIMIZATION - RÉDUIRE COÛTS LAMBDA
═══════════════════════════════════════════════════════════════════════════════

# 1. OPTIMISER TIMEOUT
# Timeout trop long = payer pour rien

# Mesurer durée réelle
aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Duration \
  --dimensions Name=FunctionName,Value=my-function \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-16T00:00:00Z \
  --period 3600 \
  --statistics Maximum

# Ajuster timeout (ajouter marge 20%)
# Si durée max = 5 sec -> timeout = 6 sec
aws lambda update-function-configuration \
  --function-name my-function \
  --timeout 6

# 2. OPTIMISER MÉMOIRE (TROUVER SWEET SPOT)
# Plus de mémoire = plus cher MAIS exécution plus rapide
# Trouver équilibre optimal

# Tester différentes configurations
# lambda-power-tuning (outil open source)
# https://github.com/alexcasalboni/aws-lambda-power-tuning

# 3. ÉVITER INVOCATIONS INUTILES
# Filtrer événements S3/DynamoDB avant Lambda

# S3 filter (seulement .jpg)
aws s3api put-bucket-notification-configuration \
  --bucket my-bucket \
  --notification-configuration '{
    "LambdaFunctionConfigurations": [{
      "LambdaFunctionArn": "arn:aws:lambda:us-east-1:123456789012:function:image-processor",
      "Events": ["s3:ObjectCreated:*"],
      "Filter": {
        "Key": {
          "FilterRules": [
            {"Name": "prefix", "Value": "uploads/"},
            {"Name": "suffix", "Value": ".jpg"}
          ]
        }
      }
    }]
  }'

# 4. UTILISER RESERVED CONCURRENCY POUR CONTRÔLER COÛTS
# Limite max invocations simultanées = limite coûts max

aws lambda put-function-concurrency \
  --function-name my-function \
  --reserved-concurrent-executions 10

# 5. ARCHITECTURE GRAVITON2 (ARM64)
# 20% moins cher et 19% plus performant

aws lambda update-function-configuration \
  --function-name my-function \
  --architectures arm64

# 6. UTILISER LAYERS POUR PARTAGER CODE
# Éviter dupliquer dépendances dans chaque fonction

# 7. CLOUDWATCH LOGS RETENTION
# Par défaut, logs gardés indéfiniment = $$$
# Définir rétention courte

aws logs put-retention-policy \
  --log-group-name /aws/lambda/my-function \
  --retention-in-days 7

# EXPLICATION:
# Options: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653
# Plus court = moins cher

# 8. DÉSACTIVER FONCTIONS INUTILISÉES
# Identifier fonctions non utilisées

aws cloudwatch get-metric-statistics \
  --namespace AWS/Lambda \
  --metric-name Invocations \
  --dimensions Name=FunctionName,Value=my-function \
  --start-time 2024-01-01T00:00:00Z \
  --end-time 2024-01-31T00:00:00Z \
  --period 2592000 \
  --statistics Sum

# Si Sum = 0 -> fonction jamais appelée -> supprimer


═══════════════════════════════════════════════════════════════════════════════
[OK] TROUBLESHOOTING COMMUN
═══════════════════════════════════════════════════════════════════════════════

# PROBLÈME 1: "Unable to import module 'lambda_function'"
# CAUSE: Structure ZIP incorrecte ou dépendances manquantes

# SOLUTION: Vérifier structure ZIP
unzip -l function.zip

# Doit montrer:
# lambda_function.py (à la racine)
# requests/ (si dépendances)

# PAS:
# myproject/lambda_function.py (sous-dossier)


# PROBLÈME 2: Timeout après 3 secondes
# CAUSE: Timeout par défaut trop court

# SOLUTION: Augmenter timeout
aws lambda update-function-configuration \
  --function-name my-function \
  --timeout 30


# PROBLÈME 3: "Task timed out after 15.00 seconds"
# CAUSE: Timeout max atteint (15 min limit)

# SOLUTION: Décomposer tâche ou utiliser Step Functions
# Pour tâches longues (> 15 min), ne PAS utiliser Lambda


# PROBLÈME 4: Lambda dans VPC ne peut pas accéder Internet
# CAUSE: Subnet privé sans NAT Gateway

# SOLUTION:
# 1. Ajouter NAT Gateway dans subnet public
# 2. Configurer route table subnet privé -> NAT Gateway
# 3. Ou utiliser VPC Endpoints pour services AWS


# PROBLÈME 5: "AccessDenied" erreurs
# CAUSE: IAM role manque permissions

# SOLUTION: Vérifier logs CloudWatch pour voir quelle permission manque
aws logs tail /aws/lambda/my-function --grep AccessDenied

# Ajouter permission manquante au rôle


# PROBLÈME 6: Cold starts trop lents
# CAUSE: Initialisation lourde

# SOLUTIONS:
# 1. Provisioned Concurrency
aws lambda put-provisioned-concurrency-config \
  --function-name my-function \
  --provisioned-concurrent-executions 5 \
  --qualifier prod

# 2. Déplacer initialisation hors du handler
# 3. Utiliser layers pour dépendances
# 4. Architecture ARM64 (plus rapide)


# PROBLÈME 7: Throttling (TooManyRequestsException)
# CAUSE: Concurrency limit dépassé

# SOLUTION: Augmenter reserved concurrency
aws lambda put-function-concurrency \
  --function-name my-function \
  --reserved-concurrent-executions 200

# Ou demander augmentation limite compte AWS


# PROBLÈME 8: "Unzipped size must be smaller than..."
# CAUSE: Code décompressé > 250 MB

# SOLUTION:
# 1. Utiliser layers pour dépendances
# 2. Utiliser container image (limite 10 GB)
# 3. Télécharger dépendances depuis S3 à runtime


# PROBLÈME 9: Lambda échoue silencieusement
# CAUSE: Pas de logs, erreurs avalées

# SOLUTION: Toujours logger erreurs
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    try:
        # Code
        pass
    except Exception as e:
        logger.error(f"Erreur: {str(e)}", exc_info=True)
        raise


# PROBLÈME 10: Memory exhausted
# CAUSE: Mémoire insuffisante

# SOLUTION: Augmenter mémoire
aws lambda update-function-configuration \
  --function-name my-function \
  --memory-size 1024


═══════════════════════════════════════════════════════════════════════════════
[OK] BEST PRACTICES RÉSUMÉ
═══════════════════════════════════════════════════════════════════════════════

# [OK] TOUJOURS initialiser connexions HORS du handler
# [OK] Utiliser variables d'environnement pour configuration
# [OK] Implémenter proper error handling et logging
# [OK] Définir timeouts réalistes (pas default 3 sec)
# [OK] Utiliser layers pour dépendances partagées
# [OK] Appliquer principe moindre privilège (IAM)
# [OK] Chiffrer secrets avec Secrets Manager/Parameter Store
# [OK] Configurer retention CloudWatch Logs
# [OK] Monitorer métriques (Duration, Errors, Throttles)
# [OK] Tester localement avant déployer
# [OK] Utiliser versions et alias pour déploiements sécurisés
# [OK] Implémenter idempotence (si invocation multiple)
# [OK] Optimiser taille package (retirer fichiers inutiles)
# [OK] Utiliser ARM64 pour meilleures performances/coûts
# [OK] Configurer DLQ pour traiter échecs
# [OK] Documenter fonctions (description, tags)


═══════════════════════════════════════════════════════════════════════════════
[OK] COMMANDES RAPIDES - QUICK REFERENCE
═══════════════════════════════════════════════════════════════════════════════

# Créer fonction
aws lambda create-function --function-name NAME --runtime python3.12 \
  --role ROLE_ARN --handler lambda_function.lambda_handler \
  --zip-file fileb://function.zip

# Invoquer
aws lambda invoke --function-name NAME output.json

# Mettre à jour code
aws lambda update-function-code --function-name NAME \
  --zip-file fileb://function.zip

# Mettre à jour config
aws lambda update-function-configuration --function-name NAME \
  --timeout 60 --memory-size 512

# Voir logs
aws logs tail /aws/lambda/NAME --follow

# Lister fonctions
aws lambda list-functions

# Supprimer fonction
aws lambda delete-function --function-name NAME

# Publier version
aws lambda publish-version --function-name NAME

# Créer alias
aws lambda create-alias --function-name NAME --name ALIAS \
  --function-version VERSION

# Event source mapping (SQS/Kinesis/DynamoDB)
aws lambda create-event-source-mapping --function-name NAME \
  --event-source-arn ARN --batch-size 10

# Voir métriques
aws cloudwatch get-metric-statistics --namespace AWS/Lambda \
  --metric-name Invocations --dimensions Name=FunctionName,Value=NAME \
  --start-time START --end-time END --period 3600 --statistics Sum


═══════════════════════════════════════════════════════════════════════════════
[OK] RESSOURCES UTILES
═══════════════════════════════════════════════════════════════════════════════

# Documentation officielle
https://docs.aws.amazon.com/lambda/

# Limites Lambda
https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html

# Pricing calculator
https://calculator.aws/#/

# Exemples code
https://github.com/aws-samples/aws-lambda-examples

# Serverless Framework (alternative AWS CLI)
https://www.serverless.com/

# SAM (Serverless Application Model)
https://aws.amazon.com/serverless/sam/

# Lambda Power Tuning (optimiser coûts)
https://github.com/alexcasalboni/aws-lambda-power-tuning


# Fichier: python_cheats/cheatsheets/VPC.txt
# Cheatsheet AWS VPC - Virtual Private Cloud Expliqué en Détail


═══════════════════════════════════════════════════════════════════════════════
[OK] AWS VPC - C'EST QUOI?
═══════════════════════════════════════════════════════════════════════════════

VPC = Virtual Private Cloud = "Votre propre réseau privé dans AWS"

ANALOGIE SIMPLE:
- VPC = Votre maison avec plusieurs pièces
- Subnets = Les pièces de la maison
- Internet Gateway = La porte d'entrée principale
- NAT Gateway = Une porte de service (sortie seulement)
- Security Groups = Gardes de sécurité à chaque porte
- Route Tables = Panneaux d'indication pour le trafic

POURQUOI UTILISER VPC?
[OK] Isoler vos ressources AWS (sécurité)
[OK] Contrôler le trafic réseau (qui peut communiquer avec qui)
[OK] Connecter votre datacenter on-premise à AWS
[OK] Organiser resources par environnement (prod, dev, staging)
[OK] Respecter conformité (données sensibles isolées)

COMPOSANTS PRINCIPAUX:
1. VPC -> Le réseau global
2. Subnets -> Sous-réseaux (public ou privé)
3. Internet Gateway -> Accès Internet
4. NAT Gateway -> Internet sortant pour subnets privés
5. Route Tables -> Règles de routage
6. Security Groups -> Firewall instances
7. Network ACLs -> Firewall subnets

CIDR (Classless Inter-Domain Routing):
= Format pour définir plages d'adresses IP
= Exemple: 10.0.0.0/16
  * 10.0.0.0 = adresse de base
  * /16 = taille du réseau (16 bits fixes)
  * Résultat: 10.0.0.0 à 10.0.255.255 (65,536 adresses)

PLAGES CIDR COMMUNES:
- /16 = 65,536 adresses (ex: 10.0.0.0/16)
- /24 = 256 adresses (ex: 10.0.1.0/24)
- /28 = 16 adresses (ex: 10.0.1.0/28)
- /32 = 1 adresse (ex: 10.0.1.5/32)

PLAGES IP PRIVÉES (RFC 1918):
- 10.0.0.0/8 (10.0.0.0 -> 10.255.255.255)
- 172.16.0.0/12 (172.16.0.0 -> 172.31.255.255)
- 192.168.0.0/16 (192.168.0.0 -> 192.168.255.255)

DIFFÉRENCE PUBLIC vs PRIVÉ:
- PUBLIC = Accessible depuis Internet (avec IP publique)
- PRIVÉ = Accessible seulement dans VPC (pas d'IP publique)


═══════════════════════════════════════════════════════════════════════════════
[OK] CRÉER VPC - EXPLICATIONS DÉTAILLÉES
═══════════════════════════════════════════════════════════════════════════════

# ÉTAPE 1: CRÉER LE VPC
════════════════════════════════════════════════════════════════════════════════

# Créer VPC basique
aws ec2 create-vpc \
  --cidr-block 10.0.0.0/16

# EXPLICATION:
# --cidr-block 10.0.0.0/16
#   = Créer réseau avec plage 10.0.0.0 à 10.0.255.255
#   = 65,536 adresses IP disponibles
#   = /16 signifie "16 premiers bits fixes"
#   = Calcul: 2^(32-16) = 2^16 = 65,536 adresses

# RÉSULTAT:
# {
#   "Vpc": {
#     "VpcId": "vpc-0123456789abcdef0",
#     "State": "available",
#     "CidrBlock": "10.0.0.0/16",
#     "DhcpOptionsId": "dopt-abc123",
#     "InstanceTenancy": "default"
#   }
# }

# [ATTENTION] IMPORTANT: Notez le VpcId (vpc-0123456789abcdef0)
# Vous en aurez besoin pour toutes les commandes suivantes!

# Créer VPC avec nom (recommandé)
aws ec2 create-vpc \
  --cidr-block 10.0.0.0/16 \
  --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=MyProductionVPC}]'

# EXPLICATION:
# --tag-specifications = Ajouter tags (métadonnées)
# ResourceType=vpc = Type de ressource à tagger
# Tags=[{Key=Name,Value=MyProductionVPC}] = Nom du VPC
# Les tags facilitent l'organisation et facturation

# Créer VPC avec tenancy dédiée (plus cher)
aws ec2 create-vpc \
  --cidr-block 10.0.0.0/16 \
  --instance-tenancy dedicated

# EXPLICATION:
# --instance-tenancy dedicated
#   = Instances lancées dans ce VPC seront sur hardware dédié
#   = Plus cher mais requis pour certaines conformités
#   = Options: default, dedicated, host

# Activer DNS resolution et DNS hostnames
aws ec2 modify-vpc-attribute \
  --vpc-id vpc-0123456789abcdef0 \
  --enable-dns-support

aws ec2 modify-vpc-attribute \
  --vpc-id vpc-0123456789abcdef0 \
  --enable-dns-hostnames

# EXPLICATION:
# enable-dns-support = Résolution DNS dans VPC (obligatoire)
# enable-dns-hostnames = Instances ont noms DNS publics
# Exemple hostname: ec2-54-123-45-67.compute-1.amazonaws.com

# LISTER VPCs
════════════════════════════════════════════════════════════════════════════════

# Lister tous les VPCs
aws ec2 describe-vpcs

# Lister avec format simplifié
aws ec2 describe-vpcs \
  --query 'Vpcs[*].[VpcId,CidrBlock,State,Tags[?Key==`Name`].Value|[0]]' \
  --output table

# RÉSULTAT EXEMPLE:
# -------------------------------------------------------------------------
# |                           DescribeVpcs                                |
# +-------------------------+---------------+------------+----------------+
# | vpc-0123456789abcdef0   | 10.0.0.0/16   | available  | MyProductionVPC|
# | vpc-abc123def456        | 172.31.0.0/16 | available  | DefaultVPC     |
# +-------------------------+---------------+------------+----------------+

# Obtenir VPC par défaut (créé automatiquement par AWS)
aws ec2 describe-vpcs \
  --filters "Name=isDefault,Values=true"

# EXPLICATION:
# Chaque région AWS a un VPC par défaut
# CIDR: 172.31.0.0/16
# Subnets créés automatiquement dans chaque AZ

# Obtenir VPC spécifique par ID
aws ec2 describe-vpcs \
  --vpc-ids vpc-0123456789abcdef0

# Obtenir VPC par nom
aws ec2 describe-vpcs \
  --filters "Name=tag:Name,Values=MyProductionVPC"


═══════════════════════════════════════════════════════════════════════════════
[OK] SUBNETS - DÉCOUPER LE VPC
═══════════════════════════════════════════════════════════════════════════════

SUBNET = Sous-réseau = "Pièce dans la maison (VPC)"

TYPES DE SUBNETS:
1. PUBLIC -> A accès Internet direct (via Internet Gateway)
2. PRIVÉ -> Pas d'accès Internet direct (utilise NAT Gateway)

BEST PRACTICE:
- Créer AU MOINS 2 subnets dans 2 Availability Zones différentes
- Pour haute disponibilité (si une AZ tombe, l'autre continue)

EXEMPLE ARCHITECTURE:
VPC: 10.0.0.0/16 (65,536 IPs)
├─ Public Subnet 1: 10.0.1.0/24 (256 IPs) - us-east-1a
├─ Public Subnet 2: 10.0.2.0/24 (256 IPs) - us-east-1b
├─ Private Subnet 1: 10.0.10.0/24 (256 IPs) - us-east-1a
├─ Private Subnet 2: 10.0.11.0/24 (256 IPs) - us-east-1b
├─ Database Subnet 1: 10.0.20.0/24 (256 IPs) - us-east-1a
└─ Database Subnet 2: 10.0.21.0/24 (256 IPs) - us-east-1b

# CRÉER SUBNETS
════════════════════════════════════════════════════════════════════════════════

# Créer subnet PUBLIC dans us-east-1a
aws ec2 create-subnet \
  --vpc-id vpc-0123456789abcdef0 \
  --cidr-block 10.0.1.0/24 \
  --availability-zone us-east-1a \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Public-Subnet-1A}]'

# EXPLICATION:
# --vpc-id = VPC parent (doit exister)
# --cidr-block 10.0.1.0/24 = 256 adresses (10.0.1.0 à 10.0.1.255)
# --availability-zone us-east-1a = Zone de disponibilité
# [ATTENTION] CIDR subnet doit être DANS le CIDR VPC!

# RÉSULTAT:
# {
#   "Subnet": {
#     "SubnetId": "subnet-0123456789abcdef0",
#     "VpcId": "vpc-0123456789abcdef0",
#     "CidrBlock": "10.0.1.0/24",
#     "AvailabilityZone": "us-east-1a",
#     "AvailableIpAddressCount": 251
#   }
# }

# [ATTENTION] POURQUOI 251 IPs au lieu de 256?
# AWS réserve 5 IPs par subnet:
# - 10.0.1.0 = adresse réseau
# - 10.0.1.1 = VPC router
# - 10.0.1.2 = DNS server
# - 10.0.1.3 = réservé (usage futur AWS)
# - 10.0.1.255 = broadcast
# Résultat: 256 - 5 = 251 IPs utilisables

# Créer subnet PUBLIC dans us-east-1b (haute disponibilité)
aws ec2 create-subnet \
  --vpc-id vpc-0123456789abcdef0 \
  --cidr-block 10.0.2.0/24 \
  --availability-zone us-east-1b \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Public-Subnet-1B}]'

# Créer subnet PRIVÉ dans us-east-1a
aws ec2 create-subnet \
  --vpc-id vpc-0123456789abcdef0 \
  --cidr-block 10.0.10.0/24 \
  --availability-zone us-east-1a \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Private-Subnet-1A},{Key=Type,Value=Private}]'

# Créer subnet PRIVÉ dans us-east-1b
aws ec2 create-subnet \
  --vpc-id vpc-0123456789abcdef0 \
  --cidr-block 10.0.11.0/24 \
  --availability-zone us-east-1b \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Private-Subnet-1B},{Key=Type,Value=Private}]'

# EXPLICATION TAGS MULTIPLES:
# Tags=[{Key=Name,Value=Private-Subnet-1B},{Key=Type,Value=Private}]
# Tag 1: Name = Private-Subnet-1B (nom lisible)
# Tag 2: Type = Private (facilite filtrage)

# ACTIVER AUTO-ASSIGN PUBLIC IP (pour subnets publics)
════════════════════════════════════════════════════════════════════════════════

# Activer attribution automatique d'IP publiques
aws ec2 modify-subnet-attribute \
  --subnet-id subnet-0123456789abcdef0 \
  --map-public-ip-on-launch

# EXPLICATION:
# map-public-ip-on-launch = Auto-assigner IP publique
# Instances lancées dans ce subnet auront IP publique automatiquement
# [ATTENTION] À faire SEULEMENT pour subnets publics!

# Désactiver auto-assign public IP
aws ec2 modify-subnet-attribute \
  --subnet-id subnet-0123456789abcdef0 \
  --no-map-public-ip-on-launch

# LISTER SUBNETS
════════════════════════════════════════════════════════════════════════════════

# Lister tous les subnets
aws ec2 describe-subnets

# Lister subnets d'un VPC spécifique
aws ec2 describe-subnets \
  --filters "Name=vpc-id,Values=vpc-0123456789abcdef0"

# Format tableau lisible
aws ec2 describe-subnets \
  --filters "Name=vpc-id,Values=vpc-0123456789abcdef0" \
  --query 'Subnets[*].[SubnetId,CidrBlock,AvailabilityZone,AvailableIpAddressCount,Tags[?Key==`Name`].Value|[0]]' \
  --output table

# RÉSULTAT EXEMPLE:
# -------------------------------------------------------------------------------------
# | subnet-abc123    | 10.0.1.0/24  | us-east-1a | 251 | Public-Subnet-1A  |
# | subnet-def456    | 10.0.2.0/24  | us-east-1b | 251 | Public-Subnet-1B  |
# | subnet-ghi789    | 10.0.10.0/24 | us-east-1a | 251 | Private-Subnet-1A |
# -------------------------------------------------------------------------------------

# Filtrer subnets par tag
aws ec2 describe-subnets \
  --filters "Name=tag:Type,Values=Private"

# Obtenir subnet par ID
aws ec2 describe-subnets \
  --subnet-ids subnet-0123456789abcdef0


═══════════════════════════════════════════════════════════════════════════════
[OK] INTERNET GATEWAY - ACCÈS INTERNET
═══════════════════════════════════════════════════════════════════════════════

INTERNET GATEWAY (IGW) = "Porte d'entrée/sortie vers Internet"

RÔLE:
- Permet communication entre VPC et Internet
- Traduit IPs privées en IPs publiques (NAT)
- OBLIGATOIRE pour subnets publics

RÈGLES:
- 1 VPC = 1 Internet Gateway maximum
- IGW doit être attaché au VPC
- Route table doit pointer vers IGW

# CRÉER INTERNET GATEWAY
════════════════════════════════════════════════════════════════════════════════

# Créer Internet Gateway
aws ec2 create-internet-gateway \
  --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=MyIGW}]'

# RÉSULTAT:
# {
#   "InternetGateway": {
#     "InternetGatewayId": "igw-0123456789abcdef0",
#     "Attachments": [],
#     "Tags": [{"Key": "Name", "Value": "MyIGW"}]
#   }
# }

# [ATTENTION] Notez InternetGatewayId: igw-0123456789abcdef0

# ATTACHER INTERNET GATEWAY AU VPC
════════════════════════════════════════════════════════════════════════════════

aws ec2 attach-internet-gateway \
  --internet-gateway-id igw-0123456789abcdef0 \
  --vpc-id vpc-0123456789abcdef0

# EXPLICATION:
# Après attachement, IGW devient actif
# Instances dans subnets publics peuvent communiquer avec Internet
# [ATTENTION] MAIS: Il faut aussi configurer route table!

# Vérifier attachement
aws ec2 describe-internet-gateways \
  --internet-gateway-ids igw-0123456789abcdef0

# RÉSULTAT montre:
# "Attachments": [
#   {
#     "State": "available",
#     "VpcId": "vpc-0123456789abcdef0"
#   }
# ]

# LISTER INTERNET GATEWAYS
════════════════════════════════════════════════════════════════════════════════

# Lister tous les IGWs
aws ec2 describe-internet-gateways

# Lister IGWs d'un VPC
aws ec2 describe-internet-gateways \
  --filters "Name=attachment.vpc-id,Values=vpc-0123456789abcdef0"

# Format lisible
aws ec2 describe-internet-gateways \
  --query 'InternetGateways[*].[InternetGatewayId,Attachments[0].VpcId,Attachments[0].State,Tags[?Key==`Name`].Value|[0]]' \
  --output table

# DÉTACHER INTERNET GATEWAY
════════════════════════════════════════════════════════════════════════════════

# Détacher IGW du VPC (avant suppression)
aws ec2 detach-internet-gateway \
  --internet-gateway-id igw-0123456789abcdef0 \
  --vpc-id vpc-0123456789abcdef0

# EXPLICATION:
# Détacher = retirer connexion VPC <-> IGW
# Nécessaire avant de supprimer IGW

# Supprimer Internet Gateway
aws ec2 delete-internet-gateway \
  --internet-gateway-id igw-0123456789abcdef0

# [ATTENTION] ERREUR COMMUNE:
# "Network igw-xxx has some mapped public address(es)"
# SOLUTION: Arrêter instances avec IPs publiques d'abord


═══════════════════════════════════════════════════════════════════════════════
[OK] ROUTE TABLES - DIRIGER LE TRAFIC
═══════════════════════════════════════════════════════════════════════════════

ROUTE TABLE = "Panneau d'indication pour le trafic réseau"

RÔLE:
- Définir où envoyer le trafic selon destination
- Chaque subnet doit avoir une route table
- Route table contient des routes (règles)

TYPES DE ROUTES:
1. Local -> Trafic dans VPC (automatique)
2. Internet -> Trafic vers Internet (via IGW)
3. NAT -> Trafic sortant (via NAT Gateway)
4. Peering -> Trafic vers autre VPC

ROUTE TABLE PAR DÉFAUT:
- Créée automatiquement avec VPC
- Contient seulement route "local"
- Tous les subnets l'utilisent par défaut

# CRÉER ROUTE TABLE
════════════════════════════════════════════════════════════════════════════════

# Créer route table pour subnets publics
aws ec2 create-route-table \
  --vpc-id vpc-0123456789abcdef0 \
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=Public-Route-Table}]'

# RÉSULTAT:
# {
#   "RouteTable": {
#     "RouteTableId": "rtb-0123456789abcdef0",
#     "VpcId": "vpc-0123456789abcdef0",
#     "Routes": [
#       {
#         "DestinationCidrBlock": "10.0.0.0/16",
#         "GatewayId": "local",
#         "State": "active"
#       }
#     ]
#   }
# }

# EXPLICATION ROUTE LOCALE:
# DestinationCidrBlock: 10.0.0.0/16
# = Trafic vers n'importe quelle IP dans VPC
# GatewayId: local
# = Reste dans VPC (pas de gateway)
# Cette route est AUTOMATIQUE et NON SUPPRIMABLE

# [ATTENTION] Notez RouteTableId: rtb-0123456789abcdef0

# Créer route table pour subnets privés
aws ec2 create-route-table \
  --vpc-id vpc-0123456789abcdef0 \
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=Private-Route-Table}]'

# AJOUTER ROUTES
════════════════════════════════════════════════════════════════════════════════

# Ajouter route vers Internet Gateway (pour subnet public)
aws ec2 create-route \
  --route-table-id rtb-0123456789abcdef0 \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id igw-0123456789abcdef0

# EXPLICATION:
# --destination-cidr-block 0.0.0.0/0
#   = "Tout le trafic" (toutes destinations)
#   = 0.0.0.0/0 signifie "n'importe quelle adresse IP"
# --gateway-id igw-0123456789abcdef0
#   = Envoyer vers Internet Gateway
# Résultat: Trafic vers Internet -> IGW

# EXEMPLE CONCRET:
# Instance dans subnet public veut accéder google.com (8.8.8.8)
# 1. Cherche route pour 8.8.8.8
# 2. Trouve route 0.0.0.0/0 -> IGW
# 3. Envoie trafic via IGW
# 4. IGW traduit IP privée en IP publique
# 5. Trafic atteint google.com

# Ajouter route spécifique
aws ec2 create-route \
  --route-table-id rtb-0123456789abcdef0 \
  --destination-cidr-block 192.168.1.0/24 \
  --vpc-peering-connection-id pcx-0123456789abcdef0

# EXPLICATION:
# Trafic vers 192.168.1.0/24 -> peering connection
# Plus spécifique que 0.0.0.0/0 donc prioritaire

# ASSOCIER ROUTE TABLE À SUBNET
════════════════════════════════════════════════════════════════════════════════

# Associer route table public à subnet public
aws ec2 associate-route-table \
  --route-table-id rtb-0123456789abcdef0 \
  --subnet-id subnet-0123456789abcdef0

# RÉSULTAT:
# {
#   "AssociationId": "rtbassoc-0123456789abcdef0"
# }

# EXPLICATION:
# Maintenant subnet utilise cette route table
# Instances dans subnet suivent ces routes
# [ATTENTION] Notez AssociationId pour désassocier plus tard

# Associer route table à deuxième subnet public
aws ec2 associate-route-table \
  --route-table-id rtb-0123456789abcdef0 \
  --subnet-id subnet-abcdef0123456789

# EXPLICATION:
# Même route table peut être utilisée par plusieurs subnets
# Utile pour subnets avec même comportement réseau

# LISTER ROUTE TABLES
════════════════════════════════════════════════════════════════════════════════

# Lister toutes les route tables
aws ec2 describe-route-tables

# Lister route tables d'un VPC
aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=vpc-0123456789abcdef0"

# Voir routes d'une route table spécifique
aws ec2 describe-route-tables \
  --route-table-ids rtb-0123456789abcdef0 \
  --query 'RouteTables[0].Routes' \
  --output table

# RÉSULTAT EXEMPLE:
# -------------------------------------------------------------------
# |                          Routes                                 |
# +---------------------+-----------------+---------+---------------+
# | DestinationCidrBlock| GatewayId       | State   | Origin        |
# +---------------------+-----------------+---------+---------------+
# | 10.0.0.0/16         | local           | active  | CreateRouteTable|
# | 0.0.0.0/0           | igw-abc123      | active  | CreateRoute   |
# +---------------------+-----------------+---------+---------------+

# Voir associations subnet
aws ec2 describe-route-tables \
  --route-table-ids rtb-0123456789abcdef0 \
  --query 'RouteTables[0].Associations' \
  --output table

# MODIFIER/SUPPRIMER ROUTES
════════════════════════════════════════════════════════════════════════════════

# Remplacer route existante
aws ec2 replace-route \
  --route-table-id rtb-0123456789abcdef0 \
  --destination-cidr-block 0.0.0.0/0 \
  --nat-gateway-id nat-0123456789abcdef0

# EXPLICATION:
# Remplace route 0.0.0.0/0 pour utiliser NAT Gateway au lieu d'IGW

# Supprimer route
aws ec2 delete-route \
  --route-table-id rtb-0123456789abcdef0 \
  --destination-cidr-block 0.0.0.0/0

# [ATTENTION] Ne peut pas supprimer route "local"!

# Désassocier route table d'un subnet
aws ec2 disassociate-route-table \
  --association-id rtbassoc-0123456789abcdef0

# EXPLICATION:
# Subnet retourne à la route table par défaut du VPC


═══════════════════════════════════════════════════════════════════════════════
[OK] NAT GATEWAY - INTERNET SORTANT POUR SUBNETS PRIVÉS
═══════════════════════════════════════════════════════════════════════════════

NAT GATEWAY = "Porte de service pour sortir (pas entrer)"

PROBLÈME:
- Instances dans subnet privé = pas d'IP publique
- Ne peuvent pas accéder Internet directement
- Mais ont besoin d'Internet pour updates, APIs, etc.

SOLUTION: NAT GATEWAY
- Permet instances privées d'accéder Internet
- Internet NE PEUT PAS initier connexions vers instances
- One-way traffic: sortant seulement

DIFFÉRENCE NAT Gateway vs Internet Gateway:
┌──────────────────────┬─────────────────────┬────────────────────┐
│                      │  Internet Gateway   │   NAT Gateway      │
├──────────────────────┼─────────────────────┼────────────────────┤
│ Trafic entrant       │  [OK] Oui             │  [X] Non            │
│ Trafic sortant       │  [OK] Oui             │  [OK] Oui            │
│ Placement            │  VPC level          │  Subnet public     │
│ Coût                 │  Gratuit            │  ~$0.045/heure     │
│ Usage                │  Subnets publics    │  Subnets privés    │
└──────────────────────┴─────────────────────┴────────────────────┘

ARCHITECTURE TYPIQUE:
Internet
   ^v
Internet Gateway (IGW)
   ^v
Subnet Public (10.0.1.0/24)
   └─ NAT Gateway (avec Elastic IP)
        v (one-way)
   Subnet Privé (10.0.10.0/24)
     └─ Instances privées

# CRÉER NAT GATEWAY - PROCÉDURE COMPLÈTE
════════════════════════════════════════════════════════════════════════════════

# ÉTAPE 1: Allouer Elastic IP (IP publique statique)
aws ec2 allocate-address --domain vpc

# RÉSULTAT:
# {
#   "AllocationId": "eipalloc-0123456789abcdef0",
#   "PublicIp": "54.123.45.67",
#   "Domain": "vpc"
# }

# EXPLICATION:
# AllocationId = ID de l'Elastic IP
# PublicIp = Adresse IP publique
# Domain = vpc (pour VPC, pas EC2-Classic)
# [ATTENTION] Notez AllocationId: eipalloc-0123456789abcdef0

# Nommer l'Elastic IP (optionnel mais recommandé)
aws ec2 create-tags \
  --resources eipalloc-0123456789abcdef0 \
  --tags Key=Name,Value=NAT-Gateway-EIP

# ÉTAPE 2: Créer NAT Gateway dans subnet PUBLIC
aws ec2 create-nat-gateway \
  --subnet-id subnet-0123456789abcdef0 \
  --allocation-id eipalloc-0123456789abcdef0 \
  --tag-specifications 'ResourceType=nat-gateway,Tags=[{Key=Name,Value=MyNAT}]'

# RÉSULTAT:
# {
#   "NatGateway": {
#     "NatGatewayId": "nat-0123456789abcdef0",
#     "SubnetId": "subnet-0123456789abcdef0",
#     "State": "pending",
#     "NatGatewayAddresses": [
#       {
#         "AllocationId": "eipalloc-0123456789abcdef0",
#         "PublicIp": "54.123.45.67"
#       }
#     ]
#   }
# }

# EXPLICATION:
# --subnet-id = Subnet PUBLIC (avec IGW access)
# --allocation-id = Elastic IP allouée précédemment
# State = pending (devient "available" après ~2 min)
# [ATTENTION] Notez NatGatewayId: nat-0123456789abcdef0

# [ATTENTION] POURQUOI SUBNET PUBLIC?
# NAT Gateway a besoin d'accès Internet via IGW
# Il reçoit trafic de subnets privés et le forward vers Internet
# Donc DOIT être dans subnet avec route vers IGW

# ÉTAPE 3: Attendre que NAT Gateway soit disponible
aws ec2 wait nat-gateway-available \
  --nat-gateway-ids nat-0123456789abcdef0

# EXPLICATION:
# Commande attend que State = "available"
# Prend environ 2-5 minutes
# IMPORTANT: Ne pas continuer avant que NAT soit prêt!

# Vérifier état NAT Gateway
aws ec2 describe-nat-gateways \
  --nat-gateway-ids nat-0123456789abcdef0 \
  --query 'NatGateways[0].State'

# RÉSULTAT: "available" (quand prêt)

# ÉTAPE 4: Ajouter route dans route table PRIVÉE
aws ec2 create-route \
  --route-table-id rtb-private123456 \
  --destination-cidr-block 0.0.0.0/0 \
  --nat-gateway-id nat-0123456789abcdef0

# EXPLICATION:
# rtb-private123456 = Route table des subnets PRIVÉS
# 0.0.0.0/0 = Tout trafic vers Internet
# nat-gateway-id = Envoyer via NAT Gateway

# COMMENT ÇA MARCHE:
# 1. Instance privée (10.0.10.5) veut accéder api.example.com
# 2. Route table privée: 0.0.0.0/0 -> NAT Gateway
# 3. NAT Gateway (dans subnet public) reçoit requête
# 4. NAT traduit IP source: 10.0.10.5 -> 54.123.45.67 (Elastic IP)
# 5. NAT envoie via Internet Gateway
# 6. Réponse revient à 54.123.45.67
# 7. NAT traduit destination: 54.123.45.67 -> 10.0.10.5
# 8. Instance privée reçoit réponse

# LISTER NAT GATEWAYS
════════════════════════════════════════════════════════════════════════════════

# Lister tous les NAT Gateways
aws ec2 describe-nat-gateways

# Lister NAT Gateways d'un VPC
aws ec2 describe-nat-gateways \
  --filter "Name=vpc-id,Values=vpc-0123456789abcdef0"

# Format lisible
aws ec2 describe-nat-gateways \
  --query 'NatGateways[*].[NatGatewayId,State,SubnetId,NatGatewayAddresses[0].PublicIp,Tags[?Key==`Name`].Value|[0]]' \
  --output table

# RÉSULTAT EXEMPLE:
# --------------------------------------------------------------------------------
# | nat-abc123    | available | subnet-pub1  | 54.123.45.67 | MyNAT           |
# | nat-def456    | available | subnet-pub2  | 54.234.56.78 | MyNAT-AZ2       |
# --------------------------------------------------------------------------------

# Obtenir NAT Gateway par ID
aws ec2 describe-nat-gateways \
  --nat-gateway-ids nat-0123456789abcdef0

# Voir details complets (JSON)
aws ec2 describe-nat-gateways \
  --nat-gateway-ids nat-0123456789abcdef0 \
  --output json

# HAUTE DISPONIBILITÉ - NAT GATEWAY PAR AZ
════════════════════════════════════════════════════════════════════════════════

# PROBLÈME:
# 1 NAT Gateway = Single Point of Failure
# Si AZ tombe, instances privées perdent Internet

# SOLUTION: 1 NAT Gateway par Availability Zone

# Architecture recommandée:
# AZ 1 (us-east-1a):
#   - Public Subnet 1 -> NAT Gateway 1
#   - Private Subnet 1 -> Route vers NAT Gateway 1
#
# AZ 2 (us-east-1b):
#   - Public Subnet 2 -> NAT Gateway 2
#   - Private Subnet 2 -> Route vers NAT Gateway 2

# Créer NAT Gateway dans AZ 2
# 1. Allouer nouvelle Elastic IP
aws ec2 allocate-address --domain vpc

# Notez AllocationId: eipalloc-abcdef0123456789

# 2. Créer NAT Gateway dans subnet public AZ 2
aws ec2 create-nat-gateway \
  --subnet-id subnet-public-az2 \
  --allocation-id eipalloc-abcdef0123456789 \
  --tag-specifications 'ResourceType=nat-gateway,Tags=[{Key=Name,Value=MyNAT-AZ2}]'

# Notez NatGatewayId: nat-abcdef0123456789

# 3. Attendre disponibilité
aws ec2 wait nat-gateway-available \
  --nat-gateway-ids nat-abcdef0123456789

# 4. Créer route dans route table privée AZ 2
aws ec2 create-route \
  --route-table-id rtb-private-az2 \
  --destination-cidr-block 0.0.0.0/0 \
  --nat-gateway-id nat-abcdef0123456789

# EXPLICATION:
# Maintenant: Chaque AZ a son propre NAT Gateway
# Si AZ 1 tombe -> instances AZ 2 continuent (via NAT Gateway 2)
# Coût: 2× NAT Gateway ($0.045/h × 2 = $0.09/h)

# SUPPRIMER NAT GATEWAY
════════════════════════════════════════════════════════════════════════════════

# Supprimer NAT Gateway
aws ec2 delete-nat-gateway \
  --nat-gateway-id nat-0123456789abcdef0

# EXPLICATION:
# State devient "deleting" puis "deleted"
# Elastic IP reste allouée (continue à coûter!)

# Attendre suppression complète
aws ec2 wait nat-gateway-deleted \
  --nat-gateway-ids nat-0123456789abcdef0

# Libérer Elastic IP (important pour éviter coûts!)
aws ec2 release-address \
  --allocation-id eipalloc-0123456789abcdef0

# [ATTENTION] IMPORTANT:
# Toujours libérer Elastic IP après supprimer NAT Gateway
# Sinon: Vous payez $0.005/heure pour IP inutilisée!

# COÛTS NAT GATEWAY
════════════════════════════════════════════════════════════════════════════════

# NAT Gateway pricing (us-east-1):
# - $0.045 par heure (toujours en cours)
# - $0.045 par GB de données traitées

# EXEMPLE CALCUL:
# - 730 heures/mois × $0.045 = $32.85/mois
# - 100 GB données × $0.045 = $4.50
# - Total = $37.35/mois

# ALTERNATIVE MOINS CHÈRE: NAT Instance
# EC2 instance configurée comme NAT (au lieu de NAT Gateway)
# Plus complexe à gérer mais moins cher
# [ATTENTION] Non recommandé pour production (NAT Gateway plus fiable)


═══════════════════════════════════════════════════════════════════════════════
[OK] SECURITY GROUPS - FIREWALL INSTANCES
═══════════════════════════════════════════════════════════════════════════════

SECURITY GROUP = "Garde de sécurité devant chaque instance"

CARACTÉRISTIQUES:
- Firewall au niveau instance (pas subnet)
- STATEFUL = Si trafic entrant autorisé, réponse sortante automatique
- Règles ALLOW seulement (pas de DENY)
- Par défaut: TOUT refusé sauf ce qui est explicitement autorisé

DIFFÉRENCE Security Group vs Network ACL:
┌──────────────────────┬─────────────────────┬────────────────────┐
│                      │  Security Group     │   Network ACL      │
├──────────────────────┼─────────────────────┼────────────────────┤
│ Niveau               │  Instance           │  Subnet            │
│ Stateful             │  [OK] Oui             │  [X] Non (stateless)│
│ Règles               │  ALLOW seulement    │  ALLOW et DENY     │
│ Ordre règles         │  Toutes évaluées    │  Ordre numérique   │
│ Défaut               │  Tout refusé        │  Tout refusé       │
└──────────────────────┴─────────────────────┴────────────────────┘

EXEMPLE STATEFUL:
- Règle: Autoriser HTTP entrant (port 80)
- Résultat automatique: Réponse HTTP sortante autorisée
- Pas besoin de règle sortante!

# CRÉER SECURITY GROUP
════════════════════════════════════════════════════════════════════════════════

# Créer security group pour web servers
aws ec2 create-security-group \
  --group-name web-servers-sg \
  --description "Security group for web servers" \
  --vpc-id vpc-0123456789abcdef0 \
  --tag-specifications 'ResourceType=security-group,Tags=[{Key=Name,Value=WebServers-SG}]'

# RÉSULTAT:
# {
#   "GroupId": "sg-0123456789abcdef0"
# }

# EXPLICATION:
# --group-name = Nom unique du security group
# --description = Description (obligatoire)
# --vpc-id = VPC parent
# [ATTENTION] Notez GroupId: sg-0123456789abcdef0

# RÈGLES PAR DÉFAUT CRÉÉES:
# Entrantes: RIEN (tout refusé)
# Sortantes: 0.0.0.0/0 (tout autorisé)

# AJOUTER RÈGLES ENTRANTES (INGRESS)
════════════════════════════════════════════════════════════════════════════════

# Autoriser HTTP (port 80) depuis n'importe où
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 80 \
  --cidr 0.0.0.0/0

# EXPLICATION:
# --protocol tcp = Protocole TCP (options: tcp, udp, icmp, -1=all)
# --port 80 = Port HTTP
# --cidr 0.0.0.0/0 = Depuis n'importe quelle IP Internet

# Autoriser HTTPS (port 443) depuis n'importe où
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0

# Autoriser SSH (port 22) depuis IP spécifique
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 22 \
  --cidr 203.0.113.25/32

# EXPLICATION:
# --cidr 203.0.113.25/32 = Seulement cette IP
# /32 = 1 seule adresse IP
# [ATTENTION] SÉCURITÉ: Jamais autoriser SSH depuis 0.0.0.0/0 en production!

# Autoriser plage de ports (ex: 8000-8100)
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 8000-8100 \
  --cidr 10.0.0.0/16

# EXPLICATION:
# --port 8000-8100 = Plage de ports
# --cidr 10.0.0.0/16 = Seulement depuis VPC

# Autoriser depuis autre security group
aws ec2 authorize-security-group-ingress \
  --group-id sg-database123 \
  --protocol tcp \
  --port 3306 \
  --source-group sg-0123456789abcdef0

# EXPLICATION:
# --source-group = Autoriser traffic depuis autre SG
# Exemple: Database SG autorise MySQL depuis Web Servers SG
# Avantage: Si IP web server change, règle reste valide

# Autoriser ICMP (ping)
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol icmp \
  --port -1 \
  --cidr 10.0.0.0/16

# EXPLICATION:
# --protocol icmp = ICMP (ping, traceroute)
# --port -1 = Tous les types ICMP

# Autoriser tous les protocoles
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol -1 \
  --cidr 10.0.0.0/16

# EXPLICATION:
# --protocol -1 = Tous les protocoles
# [ATTENTION] À utiliser avec précaution!

# AJOUTER RÈGLE AVEC ip-permissions (format avancé)
════════════════════════════════════════════════════════════════════════════════

# Format JSON pour règles complexes
aws ec2 authorize-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --ip-permissions '[
    {
      "IpProtocol": "tcp",
      "FromPort": 80,
      "ToPort": 80,
      "IpRanges": [
        {"CidrIp": "0.0.0.0/0", "Description": "Allow HTTP from Internet"}
      ]
    },
    {
      "IpProtocol": "tcp",
      "FromPort": 443,
      "ToPort": 443,
      "IpRanges": [
        {"CidrIp": "0.0.0.0/0", "Description": "Allow HTTPS from Internet"}
      ]
    }
  ]'

# EXPLICATION:
# Ajouter plusieurs règles en une commande
# Description = documentation de la règle
# Plus lisible et maintenable

# AJOUTER RÈGLES SORTANTES (EGRESS)
════════════════════════════════════════════════════════════════════════════════

# Par défaut: TOUT sortant autorisé (0.0.0.0/0)
# Mais on peut restreindre pour plus de sécurité

# Retirer règle sortante par défaut
aws ec2 revoke-security-group-egress \
  --group-id sg-0123456789abcdef0 \
  --protocol -1 \
  --cidr 0.0.0.0/0

# Autoriser sortant vers port 443 (HTTPS)
aws ec2 authorize-security-group-egress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0

# Autoriser sortant vers base de données
aws ec2 authorize-security-group-egress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 3306 \
  --destination-group sg-database123

# LISTER SECURITY GROUPS
════════════════════════════════════════════════════════════════════════════════

# Lister tous les security groups
aws ec2 describe-security-groups

# Lister security groups d'un VPC
aws ec2 describe-security-groups \
  --filters "Name=vpc-id,Values=vpc-0123456789abcdef0"

# Obtenir security group par ID
aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0

# Format lisible (voir règles)
aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0 \
  --query 'SecurityGroups[0].IpPermissions' \
  --output table

# RÉSULTAT EXEMPLE:
# --------------------------------------------------------------------------
# |                           IpPermissions                                |
# +-------------+----------+-----------+-----------------------------------+
# | FromPort    | IpProtocol| ToPort  | IpRanges                           |
# +-------------+----------+-----------+-----------------------------------+
# | 80          | tcp       | 80       | [{'CidrIp': '0.0.0.0/0'}]         |
# | 443         | tcp       | 443      | [{'CidrIp': '0.0.0.0/0'}]         |
# | 22          | tcp       | 22       | [{'CidrIp': '203.0.113.25/32'}]   |
# +-------------+----------+-----------+-----------------------------------+

# Voir règles sortantes
aws ec2 describe-security-groups \
  --group-ids sg-0123456789abcdef0 \
  --query 'SecurityGroups[0].IpPermissionsEgress' \
  --output table

# RETIRER RÈGLES
════════════════════════════════════════════════════════════════════════════════

# Retirer règle entrante
aws ec2 revoke-security-group-ingress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 80 \
  --cidr 0.0.0.0/0

# EXPLICATION:
# Spécifier exactement même paramètres que lors de création
# Retirer = inverser authorize-security-group-ingress

# Retirer règle sortante
aws ec2 revoke-security-group-egress \
  --group-id sg-0123456789abcdef0 \
  --protocol tcp \
  --port 443 \
  --cidr 0.0.0.0/0

# SUPPRIMER SECURITY GROUP
════════════════════════════════════════════════════════════════════════════════

aws ec2 delete-security-group \
  --group-id sg-0123456789abcdef0

# [ATTENTION] ERREUR POSSIBLE:
# "resource sg-xxx has a dependent object"
# CAUSE: Security group attaché à instances ou autre SG
# SOLUTION: Détacher d'abord, puis supprimer

# EXEMPLES CONFIGURATIONS TYPIQUES
════════════════════════════════════════════════════════════════════════════════

# Configuration 1: Web Server (public)
# - Entrantes: HTTP (80), HTTPS (443) depuis 0.0.0.0/0
# - Entrantes: SSH (22) depuis IP admin seulement
# - Sortantes: Tout (pour updates, APIs)

aws ec2 create-security-group \
  --group-name web-sg \
  --description "Web servers" \
  --vpc-id vpc-0123456789abcdef0

aws ec2 authorize-security-group-ingress \
  --group-id sg-web123 \
  --ip-permissions '[
    {"IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "IpRanges": [{"CidrIp": "0.0.0.0/0"}]},
    {"IpProtocol": "tcp", "FromPort": 443, "ToPort": 443, "IpRanges": [{"CidrIp": "0.0.0.0/0"}]},
    {"IpProtocol": "tcp", "FromPort": 22, "ToPort": 22, "IpRanges": [{"CidrIp": "203.0.113.25/32"}]}
  ]'

# Configuration 2: App Server (private)
# - Entrantes: Port 8080 depuis Web Server SG
# - Entrantes: SSH depuis Bastion SG
# - Sortantes: MySQL vers Database SG

aws ec2 create-security-group \
  --group-name app-sg \
  --description "Application servers" \
  --vpc-id vpc-0123456789abcdef0

aws ec2 authorize-security-group-ingress \
  --group-id sg-app456 \
  --ip-permissions '[
    {"IpProtocol": "tcp", "FromPort": 8080, "ToPort": 8080, "UserIdGroupPairs": [{"GroupId": "sg-web123"}]},
    {"IpProtocol": "tcp", "FromPort": 22, "ToPort": 22, "UserIdGroupPairs": [{"GroupId": "sg-bastion789"}]}
  ]'

# Retirer règle sortante par défaut
aws ec2 revoke-security-group-egress \
  --group-id sg-app456 \
  --protocol -1 \
  --cidr 0.0.0.0/0

# Autoriser sortant vers database
aws ec2 authorize-security-group-egress \
  --group-id sg-app456 \
  --protocol tcp \
  --port 3306 \
  --destination-group sg-db789

# Configuration 3: Database (private)
# - Entrantes: MySQL (3306) depuis App Server SG
# - Sortantes: Rien (isolé)

aws ec2 create-security-group \
  --group-name db-sg \
  --description "Database servers" \
  --vpc-id vpc-0123456789abcdef0

aws ec2 authorize-security-group-ingress \
  --group-id sg-db789 \
  --protocol tcp \
  --port 3306 \
  --source-group sg-app456

# Retirer règle sortante (database ne sort jamais)
aws ec2 revoke-security-group-egress \
  --group-id sg-db789 \
  --protocol -1 \
  --cidr 0.0.0.0/0


═══════════════════════════════════════════════════════════════════════════════
[OK] NETWORK ACL (NACL) - FIREWALL SUBNET
═══════════════════════════════════════════════════════════════════════════════

NETWORK ACL = "Firewall au niveau subnet (pas instance)"

DIFFÉRENCES CLÉS avec Security Groups:
- STATELESS = Trafic entrant et sortant évalués séparément
- Règles ALLOW et DENY
- Évaluation par ordre numérique (première règle match gagne)
- S'applique à TOUT le subnet

QUAND UTILISER NACL?
- Protection supplémentaire (défense en profondeur)
- Bloquer IPs malveillantes (DENY)
- Règles globales subnet

NACL PAR DÉFAUT:
- Créée automatiquement avec VPC
- TOUT autorisé (entrée et sortie)
- Associée à tous les subnets par défaut

# CRÉER NETWORK ACL
════════════════════════════════════════════════════════════════════════════════

# Créer NACL custom
aws ec2 create-network-acl \
  --vpc-id vpc-0123456789abcdef0 \
  --tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=Public-NACL}]'

# RÉSULTAT:
# {
#   "NetworkAcl": {
#     "NetworkAclId": "acl-0123456789abcdef0",
#     "VpcId": "vpc-0123456789abcdef0",
#     "IsDefault": false,
#     "Entries": [
#       {
#         "RuleNumber": 32767,
#         "Protocol": "-1",
#         "RuleAction": "deny",
#         "Egress": true,
#         "CidrBlock": "0.0.0.0/0"
#       },
#       {
#         "RuleNumber": 32767,
#         "Protocol": "-1",
#         "RuleAction": "deny",
#         "Egress": false,
#         "CidrBlock": "0.0.0.0/0"
#       }
#     ]
#   }
# }

# EXPLICATION RÈGLES PAR DÉFAUT:
# RuleNumber 32767 = Règle implicite finale (deny all)
# Egress: true = Sortante
# Egress: false = Entrante
# NACL custom commence avec TOUT refusé!

# [ATTENTION] Notez NetworkAclId: acl-0123456789abcdef0

# AJOUTER RÈGLES ENTRANTES
════════════════════════════════════════════════════════════════════════════════

# Autoriser HTTP (port 80) - Règle 100
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 100 \
  --protocol 6 \
  --port-range From=80,To=80 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --ingress

# EXPLICATION:
# --rule-number 100 = Numéro de priorité (1-32766)
#   * Plus petit = plus prioritaire
#   * Recommandé: Incrémenter par 100 (100, 200, 300...)
#   * Laisse espace pour insérer règles entre
# --protocol 6 = TCP (6=TCP, 17=UDP, 1=ICMP, -1=all)
# --port-range From=80,To=80 = Port 80 seulement
# --rule-action allow = Autoriser (options: allow, deny)
# --ingress = Règle entrante (vs --egress)

# Autoriser HTTPS (port 443) - Règle 110
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 110 \
  --protocol 6 \
  --port-range From=443,To=443 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --ingress

# Autoriser SSH depuis IP admin - Règle 120
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 120 \
  --protocol 6 \
  --port-range From=22,To=22 \
  --cidr-block 203.0.113.25/32 \
  --rule-action allow \
  --ingress

# [ATTENTION] IMPORTANT: PORTS ÉPHÉMÈRES
# Connexions TCP utilisent ports éphémères pour réponses (1024-65535)
# NACL est STATELESS -> doit autoriser réponses explicitement!

# Autoriser ports éphémères (réponses) - Règle 130
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 130 \
  --protocol 6 \
  --port-range From=1024,To=65535 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --ingress

# EXPLICATION:
# Quand instance initie connexion sortante (ex: apt-get update)
# Réponse revient sur port éphémère (ex: 54321)
# Sans cette règle -> réponses bloquées!

# BLOQUER IP malveillante - Règle 50 (prioritaire)
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 50 \
  --protocol -1 \
  --cidr-block 198.51.100.50/32 \
  --rule-action deny \
  --ingress

# EXPLICATION:
# Règle 50 évaluée AVANT règle 100
# Bloque TOUS les protocoles depuis cette IP
# Utile contre attaques DDoS

# AJOUTER RÈGLES SORTANTES
════════════════════════════════════════════════════════════════════════════════

# Autoriser HTTP sortant - Règle 100
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 100 \
  --protocol 6 \
  --port-range From=80,To=80 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --egress

# Autoriser HTTPS sortant - Règle 110
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 110 \
  --protocol 6 \
  --port-range From=443,To=443 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --egress

# Autoriser ports éphémères sortants (réponses HTTP/HTTPS) - Règle 120
aws ec2 create-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 120 \
  --protocol 6 \
  --port-range From=1024,To=65535 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --egress

# EXPLICATION:
# Quand client externe envoie requête HTTP (port 80)
# Instance répond depuis port éphémère (ex: 54321)
# Cette règle autorise la réponse sortante

# ASSOCIER NACL À SUBNET
════════════════════════════════════════════════════════════════════════════════

# D'abord, obtenir AssociationId actuelle
aws ec2 describe-network-acls \
  --filters "Name=association.subnet-id,Values=subnet-0123456789abcdef0" \
  --query 'NetworkAcls[0].Associations[0].NetworkAclAssociationId'

# RÉSULTAT: "aclassoc-0123456789abcdef0"

# Remplacer association (NACL custom remplace NACL par défaut)
aws ec2 replace-network-acl-association \
  --association-id aclassoc-0123456789abcdef0 \
  --network-acl-id acl-0123456789abcdef0

# EXPLICATION:
# Subnet utilise maintenant NACL custom
# Ancienne NACL (par défaut) n'est plus utilisée par ce subnet

# LISTER NETWORK ACLs (suite)
════════════════════════════════════════════════════════════════════════════════

# Lister toutes les NACLs
aws ec2 describe-network-acls

# Lister NACLs d'un VPC
aws ec2 describe-network-acls \
  --filters "Name=vpc-id,Values=vpc-0123456789abcdef0"

# Obtenir NACL spécifique
aws ec2 describe-network-acls \
  --network-acl-ids acl-0123456789abcdef0

# Voir règles d'une NACL (format lisible)
aws ec2 describe-network-acls \
  --network-acl-ids acl-0123456789abcdef0 \
  --query 'NetworkAcls[0].Entries' \
  --output table

# RÉSULTAT EXEMPLE:
# --------------------------------------------------------------------------------
# | RuleNumber | Protocol | RuleAction | Egress | CidrBlock     | PortRange    |
# +------------+----------+------------+--------+---------------+--------------+
# | 50         | -1       | deny       | False  | 198.51.100.50/32 | None      |
# | 100        | 6        | allow      | False  | 0.0.0.0/0     | 80-80        |
# | 110        | 6        | allow      | False  | 0.0.0.0/0     | 443-443      |
# | 130        | 6        | allow      | False  | 0.0.0.0/0     | 1024-65535   |
# | 32767      | -1       | deny       | False  | 0.0.0.0/0     | None         |
# | 100        | 6        | allow      | True   | 0.0.0.0/0     | 80-80        |
# | 110        | 6        | allow      | True   | 0.0.0.0/0     | 443-443      |
# | 120        | 6        | allow      | True   | 0.0.0.0/0     | 1024-65535   |
# | 32767      | -1       | deny       | True   | 0.0.0.0/0     | None         |
# --------------------------------------------------------------------------------

# Voir subnets associés à NACL
aws ec2 describe-network-acls \
  --network-acl-ids acl-0123456789abcdef0 \
  --query 'NetworkAcls[0].Associations[*].[SubnetId]' \
  --output table

# MODIFIER/SUPPRIMER RÈGLES
════════════════════════════════════════════════════════════════════════════════

# Remplacer règle existante
aws ec2 replace-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 100 \
  --protocol 6 \
  --port-range From=8080,To=8080 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --ingress

# EXPLICATION:
# Remplace règle 100 (était port 80, maintenant port 8080)

# Supprimer règle
aws ec2 delete-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 100 \
  --ingress

# Supprimer règle sortante
aws ec2 delete-network-acl-entry \
  --network-acl-id acl-0123456789abcdef0 \
  --rule-number 100 \
  --egress

# SUPPRIMER NETWORK ACL
════════════════════════════════════════════════════════════════════════════════

# Supprimer NACL
aws ec2 delete-network-acl \
  --network-acl-id acl-0123456789abcdef0

# [ATTENTION] ERREUR POSSIBLE:
# "network ACL acl-xxx has dependencies and cannot be deleted"
# CAUSE: NACL encore associée à subnets
# SOLUTION: Désassocier d'abord (subnets retournent à NACL par défaut)

# EXEMPLE CONFIGURATION COMPLÈTE - WEB SERVER PUBLIC
════════════════════════════════════════════════════════════════════════════════

# NACL pour subnet public avec web servers

# Créer NACL
aws ec2 create-network-acl \
  --vpc-id vpc-0123456789abcdef0 \
  --tag-specifications 'ResourceType=network-acl,Tags=[{Key=Name,Value=Public-Web-NACL}]'

# Notez: acl-web123

# RÈGLES ENTRANTES:
# Bloquer IP malveillante (priorité haute)
aws ec2 create-network-acl-entry \
  --network-acl-id acl-web123 \
  --rule-number 10 \
  --protocol -1 \
  --cidr-block 198.51.100.50/32 \
  --rule-action deny \
  --ingress

# HTTP depuis Internet
aws ec2 create-network-acl-entry \
  --network-acl-id acl-web123 \
  --rule-number 100 \
  --protocol 6 \
  --port-range From=80,To=80 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --ingress

# HTTPS depuis Internet
aws ec2 create-network-acl-entry \
  --network-acl-id acl-web123 \
  --rule-number 110 \
  --protocol 6 \
  --port-range From=443,To=443 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --ingress

# SSH depuis IP admin
aws ec2 create-network-acl-entry \
  --network-acl-id acl-web123 \
  --rule-number 120 \
  --protocol 6 \
  --port-range From=22,To=22 \
  --cidr-block 203.0.113.25/32 \
  --rule-action allow \
  --ingress

# Ports éphémères (réponses)
aws ec2 create-network-acl-entry \
  --network-acl-id acl-web123 \
  --rule-number 140 \
  --protocol 6 \
  --port-range From=1024,To=65535 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --ingress

# RÈGLES SORTANTES:
# HTTP vers Internet (pour updates)
aws ec2 create-network-acl-entry \
  --network-acl-id acl-web123 \
  --rule-number 100 \
  --protocol 6 \
  --port-range From=80,To=80 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --egress

# HTTPS vers Internet
aws ec2 create-network-acl-entry \
  --network-acl-id acl-web123 \
  --rule-number 110 \
  --protocol 6 \
  --port-range From=443,To=443 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --egress

# Ports éphémères (réponses HTTP/HTTPS vers clients)
aws ec2 create-network-acl-entry \
  --network-acl-id acl-web123 \
  --rule-number 140 \
  --protocol 6 \
  --port-range From=1024,To=65535 \
  --cidr-block 0.0.0.0/0 \
  --rule-action allow \
  --egress


═══════════════════════════════════════════════════════════════════════════════
[OK] VPC PEERING - CONNECTER 2 VPCs
═══════════════════════════════════════════════════════════════════════════════

VPC PEERING = "Pont réseau entre 2 VPCs"

UTILISATION:
- Connecter VPC prod <-> VPC dev
- Connecter VPC différentes régions
- Connecter VPC différents comptes AWS
- Partager ressources entre VPCs

CARACTÉRISTIQUES:
- Communication privée (pas via Internet)
- Pas de single point of failure
- Pas de bandwidth bottleneck
- Pas de transitivité (A<->B, B<->C ≠ A<->C)

EXEMPLE SCENARIO:
VPC-A (10.0.0.0/16) <--> VPC-B (172.31.0.0/16)
- Instances VPC-A peuvent communiquer avec VPC-B
- Via IPs privées (pas IPs publiques)

[ATTENTION] PRÉREQUIS:
- CIDRs ne doivent PAS se chevaucher
- Exemple INVALIDE: VPC-A (10.0.0.0/16) + VPC-B (10.0.0.0/24)
- Exemple VALIDE: VPC-A (10.0.0.0/16) + VPC-B (172.31.0.0/16)

# CRÉER VPC PEERING CONNECTION
════════════════════════════════════════════════════════════════════════════════

# Créer peering entre 2 VPCs (même région)
aws ec2 create-vpc-peering-connection \
  --vpc-id vpc-0123456789abcdef0 \
  --peer-vpc-id vpc-abcdef0123456789 \
  --tag-specifications 'ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=VPC-A-to-VPC-B}]'

# RÉSULTAT:
# {
#   "VpcPeeringConnection": {
#     "VpcPeeringConnectionId": "pcx-0123456789abcdef0",
#     "Status": {"Code": "pending-acceptance"},
#     "RequesterVpcInfo": {
#       "VpcId": "vpc-0123456789abcdef0",
#       "CidrBlock": "10.0.0.0/16"
#     },
#     "AccepterVpcInfo": {
#       "VpcId": "vpc-abcdef0123456789",
#       "CidrBlock": "172.31.0.0/16"
#     }
#   }
# }

# EXPLICATION:
# --vpc-id = VPC requester (qui initie)
# --peer-vpc-id = VPC accepter (qui doit accepter)
# Status = pending-acceptance (en attente)
# [ATTENTION] Notez VpcPeeringConnectionId: pcx-0123456789abcdef0

# Créer peering entre 2 VPCs (différentes régions)
aws ec2 create-vpc-peering-connection \
  --vpc-id vpc-0123456789abcdef0 \
  --peer-vpc-id vpc-abcdef0123456789 \
  --peer-region us-west-2 \
  --tag-specifications 'ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=US-East-to-US-West}]'

# EXPLICATION:
# --peer-region = Région du VPC peer
# Inter-region peering = plus de latence mais possible

# Créer peering entre 2 comptes AWS
aws ec2 create-vpc-peering-connection \
  --vpc-id vpc-0123456789abcdef0 \
  --peer-vpc-id vpc-abcdef0123456789 \
  --peer-owner-id 123456789012 \
  --tag-specifications 'ResourceType=vpc-peering-connection,Tags=[{Key=Name,Value=Account-A-to-Account-B}]'

# EXPLICATION:
# --peer-owner-id = AWS Account ID du VPC peer
# Utile pour organisations multi-comptes

# ACCEPTER VPC PEERING CONNECTION
════════════════════════════════════════════════════════════════════════════════

# Accepter peering (depuis VPC accepter)
aws ec2 accept-vpc-peering-connection \
  --vpc-peering-connection-id pcx-0123456789abcdef0

# RÉSULTAT:
# {
#   "VpcPeeringConnection": {
#     "Status": {"Code": "active"}
#   }
# }

# EXPLICATION:
# Status devient "active"
# Peering maintenant fonctionnel
# [ATTENTION] MAIS: Faut encore ajouter routes!

# Accepter peering (différente région)
aws ec2 accept-vpc-peering-connection \
  --vpc-peering-connection-id pcx-0123456789abcdef0 \
  --region us-west-2

# EXPLICATION:
# --region = Région du VPC accepter
# Commande doit être exécutée dans la région du peer

# AJOUTER ROUTES POUR PEERING
════════════════════════════════════════════════════════════════════════════════

# [ATTENTION] IMPORTANT:
# Peering actif ≠ communication possible
# Il faut ajouter routes dans CHAQUE VPC!

# VPC-A (10.0.0.0/16): Ajouter route vers VPC-B (172.31.0.0/16)
aws ec2 create-route \
  --route-table-id rtb-vpc-a-123456 \
  --destination-cidr-block 172.31.0.0/16 \
  --vpc-peering-connection-id pcx-0123456789abcdef0

# EXPLICATION:
# rtb-vpc-a-123456 = Route table de VPC-A
# Destination = CIDR de VPC-B
# Trafic vers 172.31.x.x -> peering connection

# VPC-B (172.31.0.0/16): Ajouter route vers VPC-A (10.0.0.0/16)
aws ec2 create-route \
  --route-table-id rtb-vpc-b-789012 \
  --destination-cidr-block 10.0.0.0/16 \
  --vpc-peering-connection-id pcx-0123456789abcdef0

# EXPLICATION:
# Routes bidirectionnelles nécessaires
# VPC-A peut maintenant communiquer avec VPC-B et vice versa

# EXEMPLE COMPLET:
# Instance VPC-A (10.0.1.50) ping Instance VPC-B (172.31.10.100)
# 1. Instance VPC-A: ping 172.31.10.100
# 2. Route table VPC-A: 172.31.0.0/16 -> pcx-xxx
# 3. Trafic traverse peering connection
# 4. Instance VPC-B reçoit ping
# 5. Instance VPC-B répond
# 6. Route table VPC-B: 10.0.0.0/16 -> pcx-xxx
# 7. Réponse revient à VPC-A

# [ATTENTION] SÉCURITÉ: SECURITY GROUPS
# Autoriser trafic depuis VPC peer dans security groups!

# Security Group VPC-B: Autoriser SSH depuis VPC-A
aws ec2 authorize-security-group-ingress \
  --group-id sg-vpc-b-123 \
  --protocol tcp \
  --port 22 \
  --cidr 10.0.0.0/16

# LISTER VPC PEERING CONNECTIONS
════════════════════════════════════════════════════════════════════════════════

# Lister toutes les peering connections
aws ec2 describe-vpc-peering-connections

# Lister peering d'un VPC spécifique
aws ec2 describe-vpc-peering-connections \
  --filters "Name=requester-vpc-info.vpc-id,Values=vpc-0123456789abcdef0"

# Format lisible
aws ec2 describe-vpc-peering-connections \
  --query 'VpcPeeringConnections[*].[VpcPeeringConnectionId,Status.Code,RequesterVpcInfo.VpcId,AccepterVpcInfo.VpcId,Tags[?Key==`Name`].Value|[0]]' \
  --output table

# RÉSULTAT EXEMPLE:
# ----------------------------------------------------------------------------------
# | pcx-abc123  | active  | vpc-0123456789abcdef0 | vpc-abcdef0123456789 | VPC-A-to-VPC-B |
# ----------------------------------------------------------------------------------

# Obtenir peering par ID
aws ec2 describe-vpc-peering-connections \
  --vpc-peering-connection-ids pcx-0123456789abcdef0

# SUPPRIMER VPC PEERING CONNECTION
════════════════════════════════════════════════════════════════════════════════

# Supprimer peering (par requester ou accepter)
aws ec2 delete-vpc-peering-connection \
  --vpc-peering-connection-id pcx-0123456789abcdef0

# EXPLICATION:
# Suppression immédiate
# Communication entre VPCs coupée
# [ATTENTION] Penser à supprimer routes aussi!

# Supprimer routes associées (VPC-A)
aws ec2 delete-route \
  --route-table-id rtb-vpc-a-123456 \
  --destination-cidr-block 172.31.0.0/16

# Supprimer routes associées (VPC-B)
aws ec2 delete-route \
  --route-table-id rtb-vpc-b-789012 \
  --destination-cidr-block 10.0.0.0/16

# REFUSER VPC PEERING REQUEST
════════════════════════════════════════════════════════════════════════════════

# Si vous ne voulez pas accepter
aws ec2 reject-vpc-peering-connection \
  --vpc-peering-connection-id pcx-0123456789abcdef0


═══════════════════════════════════════════════════════════════════════════════
[OK] VPC ENDPOINTS - ACCÉDER SERVICES AWS SANS INTERNET
═══════════════════════════════════════════════════════════════════════════════

VPC ENDPOINT = "Connexion privée vers services AWS"

PROBLÈME SANS VPC ENDPOINT:
- Instance privée veut accéder S3
- Trafic doit passer par NAT Gateway -> Internet -> S3
- Coût NAT Gateway + Bande passante
- Latence plus élevée

SOLUTION: VPC ENDPOINT
- Connexion directe VPC -> Service AWS
- Trafic reste dans réseau AWS (pas Internet)
- Pas de NAT Gateway nécessaire
- Gratuit (pour Gateway Endpoints)

TYPES D'ENDPOINTS:
1. GATEWAY ENDPOINT (gratuit)
   - S3 et DynamoDB seulement
   - Route ajoutée à route table
   
2. INTERFACE ENDPOINT (payant: ~$0.01/h)
   - Tous les autres services AWS
   - ENI (Elastic Network Interface) dans subnet
   - Plus de 100 services supportés

# GATEWAY ENDPOINT - S3
════════════════════════════════════════════════════════════════════════════════

# Créer endpoint S3 (Gateway)
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0123456789abcdef0 \
  --service-name com.amazonaws.us-east-1.s3 \
  --route-table-ids rtb-private123456 \
  --tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=S3-Endpoint}]'

# RÉSULTAT:
# {
#   "VpcEndpoint": {
#     "VpcEndpointId": "vpce-0123456789abcdef0",
#     "VpcEndpointType": "Gateway",
#     "VpcId": "vpc-0123456789abcdef0",
#     "ServiceName": "com.amazonaws.us-east-1.s3",
#     "State": "available",
#     "RouteTableIds": ["rtb-private123456"]
#   }
# }

# EXPLICATION:
# --service-name = Service AWS (format: com.amazonaws.REGION.SERVICE)
# --route-table-ids = Route tables à modifier (ajoute route automatique)
# VpcEndpointType = Gateway (gratuit)
# [ATTENTION] Notez VpcEndpointId: vpce-0123456789abcdef0

# Vérifier route ajoutée automatiquement
aws ec2 describe-route-tables \
  --route-table-ids rtb-private123456 \
  --query 'RouteTables[0].Routes'

# RÉSULTAT MONTRE:
# {
#   "DestinationPrefixListId": "pl-63a5400a",  # S3 prefix list
#   "GatewayId": "vpce-0123456789abcdef0",
#   "State": "active"
# }

# EXPLICATION:
# pl-63a5400a = Prefix list S3 (toutes IPs S3 région)
# Route automatique: Trafic S3 -> VPC Endpoint

# COMMENT ÇA MARCHE:
# 1. Instance privée: aws s3 ls s3://my-bucket
# 2. DNS résout: s3.amazonaws.com -> IP S3
# 3. Route table: IP S3 match prefix list -> VPC Endpoint
# 4. Trafic va directement à S3 (pas via NAT/IGW)
# 5. Gratuit et rapide!

# Créer endpoint DynamoDB (Gateway)
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0123456789abcdef0 \
  --service-name com.amazonaws.us-east-1.dynamodb \
  --route-table-ids rtb-private123456 rtb-private789012 \
  --tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=DynamoDB-Endpoint}]'

# EXPLICATION:
# Même principe que S3
# Peut spécifier plusieurs route tables

# INTERFACE ENDPOINT - AUTRES SERVICES
════════════════════════════════════════════════════════════════════════════════

# Lister services disponibles
aws ec2 describe-vpc-endpoint-services \
  --query 'ServiceNames' \
  --output table

# RÉSULTAT PARTIEL:
# com.amazonaws.us-east-1.ec2
# com.amazonaws.us-east-1.lambda
# com.amazonaws.us-east-1.sns
# com.amazonaws.us-east-1.sqs
# com.amazonaws.us-east-1.ssm
# ... 100+ services

# Créer endpoint EC2 (Interface)
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0123456789abcdef0 \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.us-east-1.ec2 \
  --subnet-ids subnet-private1 subnet-private2 \
  --security-group-ids sg-endpoint123 \
  --tag-specifications 'ResourceType=vpc-endpoint,Tags=[{Key=Name,Value=EC2-Endpoint}]'

# RÉSULTAT:
# {
#   "VpcEndpoint": {
#     "VpcEndpointId": "vpce-abc123def456",
#     "VpcEndpointType": "Interface",
#     "ServiceName": "com.amazonaws.us-east-1.ec2",
#     "State": "pending",
#     "SubnetIds": ["subnet-private1", "subnet-private2"],
#     "Groups": [{"GroupId": "sg-endpoint123"}],
#     "PrivateDnsEnabled": true
#   }
# }

# EXPLICATION:
# --vpc-endpoint-type Interface = ENI créée dans subnets
# --subnet-ids = Subnets pour ENIs (recommandé: 2+ pour HA)
# --security-group-ids = Security group pour ENIs
# PrivateDnsEnabled = true (résolution DNS privée)

# [ATTENTION] DIFFÉRENCE vs GATEWAY:
# Interface Endpoint = ENI avec IP privée dans subnet
# Instances communiquent via cette IP
# Coût: ~$0.01/heure + $0.01/GB

# Security Group pour Interface Endpoint
aws ec2 create-security-group \
  --group-name vpc-endpoint-sg \
  --description "Security group for VPC endpoints" \
  --vpc-id vpc-0123456789abcdef0

# Autoriser HTTPS depuis VPC
aws ec2 authorize-security-group-ingress \
  --group-id sg-endpoint123 \
  --protocol tcp \
  --port 443 \
  --cidr 10.0.0.0/16

# EXPLICATION:
# Interface Endpoints utilisent HTTPS (port 443)
# Autoriser depuis tout le VPC

# Créer endpoint SSM (Systems Manager) pour Session Manager
aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0123456789abcdef0 \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.us-east-1.ssm \
  --subnet-ids subnet-private1 subnet-private2 \
  --security-group-ids sg-endpoint123

# EXPLICATION:
# SSM Endpoint permet Session Manager sans bastion host
# Se connecter à instances privées sans SSH/RDP public

# Pour Session Manager complet, créer 3 endpoints:
# 1. ssm
# 2. ssmmessages
# 3. ec2messages

aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0123456789abcdef0 \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.us-east-1.ssmmessages \
  --subnet-ids subnet-private1 subnet-private2 \
  --security-group-ids sg-endpoint123

aws ec2 create-vpc-endpoint \
  --vpc-id vpc-0123456789abcdef0 \
  --vpc-endpoint-type Interface \
  --service-name com.amazonaws.us-east-1.ec2messages \
  --subnet-ids subnet-private1 subnet-private2 \
  --security-group-ids sg-endpoint123

# LISTER VPC ENDPOINTS
════════════════════════════════════════════════════════════════════════════════

# Lister tous les endpoints
aws ec2 describe-vpc-endpoints

# Lister endpoints d'un VPC
aws ec2 describe-vpc-endpoints \
  --filters "Name=vpc-id,Values=vpc-0123456789abcdef0"

# Format lisible
aws ec2 describe-vpc-endpoints \
  --filters "Name=vpc-id,Values=vpc-0123456789abcdef0" \
  --query 'VpcEndpoints[*].[VpcEndpointId,VpcEndpointType,ServiceName,State,Tags[?Key==`Name`].Value|[0]]' \
  --output table

# RÉSULTAT EXEMPLE:
# ------------------------------------------------------------------------------------
# | vpce-abc123  | Gateway   | com.amazonaws.us-east-1.s3      | available | S3-Endpoint    |
# | vpce-def456  | Interface | com.amazonaws.us-east-1.ec2     | available | EC2-Endpoint   |
# | vpce-ghi789  | Interface | com.amazonaws.us-east-1.ssm     | available | SSM-Endpoint   |
# ------------------------------------------------------------------------------------

# Obtenir détails endpoint
aws ec2 describe-vpc-endpoints \
  --vpc-endpoint-ids vpce-0123456789abcdef0

# MODIFIER VPC ENDPOINT
════════════════════════════════════════════════════════════════════════════════

# Ajouter route table à Gateway Endpoint
aws ec2 modify-vpc-endpoint \
  --vpc-endpoint-id vpce-0123456789abcdef0 \
  --add-route-table-ids rtb-789012

# Retirer route table
aws ec2 modify-vpc-endpoint \
  --vpc-endpoint-id vpce-0123456789abcdef0 \
  --remove-route-table-ids rtb-789012

# Modifier subnets (Interface Endpoint)
aws ec2 modify-vpc-endpoint \
  --vpc-endpoint-id vpce-abc123def456 \
  --add-subnet-ids subnet-private3

# Modifier security groups (Interface Endpoint)
aws ec2 modify-vpc-endpoint \
  --vpc-endpoint-id vpce-abc123def456 \
  --add-security-group-ids sg-newgroup123

# SUPPRIMER VPC ENDPOINT
════════════════════════════════════════════════════════════════════════════════

aws ec2 delete-vpc-endpoints \
  --vpc-endpoint-ids vpce-0123456789abcdef0

# EXPLICATION:
# Suppression immédiate
# Routes automatiquement retirées (Gateway)
# ENIs supprimées (Interface)


═══════════════════════════════════════════════════════════════════════════════
[OK] VPC FLOW LOGS - CAPTURER TRAFIC RÉSEAU
═══════════════════════════════════════════════════════════════════════════════

FLOW LOGS = "Enregistrement trafic réseau"

UTILISATION:
- Troubleshooting connectivité
- Analyse sécurité (détecter attaques)
- Audit et conformité
- Monitoring patterns trafic

NIVEAUX CAPTURE:
1. VPC -> Tout le trafic VPC
2. Subnet -> Trafic d'un subnet
3. ENI -> Trafic d'une interface réseau spécifique

DESTINATIONS:
1. CloudWatch Logs -> Analyse en temps réel
2. S3 -> Stockage long terme (moins cher)
3. Kinesis Data Firehose -> Streaming vers outils tiers

FORMAT FLOW LOG (exemple):
2 123456789012 eni-abc123 10.0.1.5 172.217.14.206 49152 443 6 20 4000 1620000000 1620000060 ACCEPT OK

CHAMPS:
- version: 2
- account-id: 123456789012
- interface-id: eni-abc123
- srcaddr: 10.0.1.5 (source)
- dstaddr: 172.217.14.206 (destination)
- srcport: 49152
- dstport: 443 (HTTPS)
- protocol: 6 (TCP)
- packets: 20
- bytes: 4000
- start: timestamp
- end: timestamp
- action: ACCEPT (ou REJECT)
- log-status: OK

# CRÉER FLOW LOGS - CLOUDWATCH
════════════════════════════════════════════════════════════════════════════════

# Créer IAM role pour Flow Logs
cat > flow-logs-trust-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "vpc-flow-logs.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF

aws iam create-role \
  --role-name VPC-FlowLogs-Role \
  --assume-role-policy-document file://flow-logs-trust-policy.json

# Créer policy pour écrire dans CloudWatch Logs
cat > flow-logs-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents",
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams"
      ],
      "Resource": "*"
    }
  ]
}
EOF

aws iam put-role-policy \
  --role-name VPC-FlowLogs-Role \
  --policy-name VPC-FlowLogs-Policy \
  --policy-document file://flow-logs-policy.json

# Créer log group CloudWatch
aws logs create-log-group \
  --log-group-name /aws/vpc/flowlogs

# Définir retention (optionnel - éviter coûts)
aws logs put-retention-policy \
  --log-group-name /aws/vpc/flowlogs \
  --retention-in-days 7

# EXPLICATION:
# --retention-in-days = Garder logs 7 jours
# Options: 1, 3, 5, 7, 14, 30, 60, 90, etc.
# Sans retention = logs gardés indéfiniment = $$$

# Créer Flow Logs pour VPC (vers CloudWatch)
aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids vpc-0123456789abcdef0 \
  --traffic-type ALL \
  --log-destination-type cloud-watch-logs \
  --log-group-name /aws/vpc/flowlogs \
  --deliver-logs-permission-arn arn:aws:iam::123456789012:role/VPC-FlowLogs-Role \
  --tag-specifications 'ResourceType=vpc-flow-log,Tags=[{Key=Name,Value=VPC-FlowLogs}]'

# RÉSULTAT:
# {
#   "FlowLogIds": ["fl-0123456789abcdef0"],
#   "Unsuccessful": []
# }

# EXPLICATION:
# --resource-type VPC = Capturer tout le VPC
#   Options: VPC, Subnet, NetworkInterface
# --resource-ids = ID de la ressource
# --traffic-type ALL = Tout le trafic
#   Options: ALL, ACCEPT (seulement accepté), REJECT (seulement rejeté)
# --log-destination-type = Où envoyer logs
# --deliver-logs-permission-arn = IAM role

# [ATTENTION] Notez FlowLogIds: fl-0123456789abcdef0

# Créer Flow Logs pour subnet spécifique
aws ec2 create-flow-logs \
  --resource-type Subnet \
  --resource-ids subnet-0123456789abcdef0 \
  --traffic-type ALL \
  --log-destination-type cloud-watch-logs \
  --log-group-name /aws/vpc/flowlogs \
  --deliver-logs-permission-arn arn:aws:iam::123456789012:role/VPC-FlowLogs-Role

# Créer Flow Logs pour ENI spécifique
aws ec2 create-flow-logs \
  --resource-type NetworkInterface \
  --resource-ids eni-0123456789abcdef0 \
  --traffic-type REJECT \
  --log-destination-type cloud-watch-logs \
  --log-group-name /aws/vpc/flowlogs-rejected \
  --deliver-logs-permission-arn arn:aws:iam::123456789012:role/VPC-FlowLogs-Role

# EXPLICATION:
# --traffic-type REJECT = Seulement trafic rejeté
# Utile pour analyser problèmes de connectivité

# CRÉER FLOW LOGS - S3 (MOINS CHER)
════════════════════════════════════════════════════════════════════════════════

# Créer bucket S3
aws s3 mb s3://my-vpc-flow-logs-bucket

# Créer bucket policy pour Flow Logs
cat > s3-flow-logs-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AWSLogDeliveryWrite",
      "Effect": "Allow",
      "Principal": {
        "Service": "delivery.logs.amazonaws.com"
      },
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::my-vpc-flow-logs-bucket/*",
      "Condition": {
        "StringEquals": {
          "s3:x-amz-acl": "bucket-owner-full-control"
        }
      }
    },
    {
      "Sid": "AWSLogDeliveryAclCheck",
      "Effect": "Allow",
      "Principal": {
        "Service": "delivery.logs.amazonaws.com"
      },
      "Action": "s3:GetBucketAcl",
      "Resource": "arn:aws:s3:::my-vpc-flow-logs-bucket"
    }
  ]
}
EOF

aws s3api put-bucket-policy \
  --bucket my-vpc-flow-logs-bucket \
  --policy file://s3-flow-logs-policy.json

# Créer Flow Logs vers S3
aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids vpc-0123456789abcdef0 \
  --traffic-type ALL \
  --log-destination-type s3 \
  --log-destination arn:aws:s3:::my-vpc-flow-logs-bucket \
  --tag-specifications 'ResourceType=vpc-flow-log,Tags=[{Key=Name,Value=VPC-FlowLogs-S3}]'

# EXPLICATION:
# --log-destination = ARN du bucket S3
# Logs stockés comme fichiers compressés (.gz)
# Format: s3://bucket/prefix/AWSLogs/account_id/vpcflowlogs/region/year/month/day/

# Organiser avec préfixe
aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids vpc-0123456789abcdef0 \
  --traffic-type ALL \
  --log-destination-type s3 \
  --log-destination arn:aws:s3:::my-vpc-flow-logs-bucket/production/ \
  --log-format '${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status}'

# EXPLICATION:
# --log-format = Format custom des logs
# Permet choisir quels champs inclure
# Plus compact = moins cher

# FORMAT PAR DÉFAUT:
# ${version} ${account-id} ${interface-id} ${srcaddr} ${dstaddr} ${srcport} ${dstport} ${protocol} ${packets} ${bytes} ${start} ${end} ${action} ${log-status}

# FORMAT CUSTOM MINIMAL (économiser):
# ${srcaddr} ${dstaddr} ${action}

# CRÉER FLOW LOGS - KINESIS DATA FIREHOSE
════════════════════════════════════════════════════════════════════════════════

# Pour streaming vers outils externes (Splunk, Datadog, etc.)

aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids vpc-0123456789abcdef0 \
  --traffic-type ALL \
  --log-destination-type kinesis-data-firehose \
  --log-destination arn:aws:firehose:us-east-1:123456789012:deliverystream/my-flow-logs-stream

# LISTER FLOW LOGS
════════════════════════════════════════════════════════════════════════════════

# Lister tous les flow logs
aws ec2 describe-flow-logs

# Lister flow logs d'un VPC
aws ec2 describe-flow-logs \
  --filter "Name=resource-id,Values=vpc-0123456789abcdef0"

# Format lisible
aws ec2 describe-flow-logs \
  --query 'FlowLogs[*].[FlowLogId,ResourceId,TrafficType,LogDestinationType,FlowLogStatus,Tags[?Key==`Name`].Value|[0]]' \
  --output table

# RÉSULTAT EXEMPLE:
# --------------------------------------------------------------------------------
# | fl-abc123  | vpc-0123456789abcdef0 | ALL | cloud-watch-logs | ACTIVE | VPC-FlowLogs |
# | fl-def456  | subnet-abc123         | ALL | s3               | ACTIVE | Subnet-Logs  |
# --------------------------------------------------------------------------------

# Obtenir flow log spécifique
aws ec2 describe-flow-logs \
  --flow-log-ids fl-0123456789abcdef0

# VOIR LES LOGS (CLOUDWATCH)
════════════════════════════════════════════════════════════════════════════════

# Voir logs en temps réel
aws logs tail /aws/vpc/flowlogs --follow

# Filtrer par IP source
aws logs filter-log-events \
  --log-group-name /aws/vpc/flowlogs \
  --filter-pattern "10.0.1.5"

# Filtrer trafic rejeté
aws logs filter-log-events \
  --log-group-name /aws/vpc/flowlogs \
  --filter-pattern "REJECT"

# Filtrer par port (ex: SSH port 22)
aws logs filter-log-events \
  --log-group-name /aws/vpc/flowlogs \
  --filter-pattern "[version, account, eni, source, destination, srcport, dstport=22, protocol, packets, bytes, windowstart, windowend, action, flowlogstatus]"

# Voir logs d'une période spécifique
aws logs filter-log-events \
  --log-group-name /aws/vpc/flowlogs \
  --start-time 1620000000000 \
  --end-time 1620086400000

# ANALYSER LOGS (EXEMPLES PRATIQUES)
════════════════════════════════════════════════════════════════════════════════

# EXEMPLE 1: Trouver top IPs qui génèrent le plus de trafic
# (Requiert CloudWatch Insights ou télécharger logs S3)

# CloudWatch Insights query:
# fields @timestamp, srcaddr, dstaddr, bytes
# | stats sum(bytes) as totalBytes by srcaddr
# | sort totalBytes desc
# | limit 10

# EXEMPLE 2: Identifier connexions rejetées (problèmes connectivité)
# Filter pattern CloudWatch: "REJECT"

# EXEMPLE 3: Analyser trafic vers port spécifique
# Filter: [version, account, eni, source, destination, srcport, dstport=3306, ...]

# EXEMPLE 4: Détecter scan de ports
# Chercher nombreuses connexions rejetées depuis même IP
# Filter: "REJECT" puis grouper par srcaddr

# EXEMPLE LOG ENTRY EXPLIQUÉ:
# 2 123456789012 eni-abc123 10.0.1.5 172.217.14.206 49152 443 6 20 4000 1620000000 1620000060 ACCEPT OK
# 
# version: 2
# account-id: 123456789012
# interface-id: eni-abc123
# srcaddr: 10.0.1.5 (IP source - instance)
# dstaddr: 172.217.14.206 (IP destination - Google)
# srcport: 49152 (port éphémère)
# dstport: 443 (HTTPS)
# protocol: 6 (TCP)
# packets: 20
# bytes: 4000
# start: 1620000000 (timestamp début)
# end: 1620000060 (timestamp fin)
# action: ACCEPT (autorisé)
# log-status: OK (log valide)

# SUPPRIMER FLOW LOGS
════════════════════════════════════════════════════════════════════════════════

# Supprimer flow log
aws ec2 delete-flow-logs \
  --flow-log-ids fl-0123456789abcdef0

# EXPLICATION:
# Logs existants restent dans CloudWatch/S3
# Nouveaux logs ne seront plus capturés

# Supprimer log group CloudWatch (si plus utilisé)
aws logs delete-log-group \
  --log-group-name /aws/vpc/flowlogs

# [ATTENTION] Cela supprime TOUS les logs!

# COÛTS FLOW LOGS
════════════════════════════════════════════════════════════════════════════════

# CloudWatch Logs:
# - Ingestion: $0.50 per GB
# - Stockage: $0.03 per GB par mois
# - CHER pour gros volumes!

# S3:
# - Pas de frais ingestion
# - Stockage: $0.023 per GB par mois (Standard)
# - $0.0125 per GB (Intelligent-Tiering)
# - MOINS CHER pour long terme

# RECOMMANDATION:
# - CloudWatch: Analyse temps réel, troubleshooting
# - S3: Archive, conformité, analyse batch


═══════════════════════════════════════════════════════════════════════════════
[OK] ARCHITECTURE VPC COMPLÈTE - EXEMPLE PRODUCTION
═══════════════════════════════════════════════════════════════════════════════

# ARCHITECTURE 3-TIER (Web + App + Database)
# 
# VPC: 10.0.0.0/16 (us-east-1)
# 
# PUBLIC SUBNETS (2 AZs):
#   - 10.0.1.0/24 (us-east-1a) -> Web servers, Load Balancer
#   - 10.0.2.0/24 (us-east-1b) -> Web servers, Load Balancer
#   - Internet Gateway
#   - NAT Gateway (1 par AZ)
# 
# PRIVATE SUBNETS (Application - 2 AZs):
#   - 10.0.10.0/24 (us-east-1a) -> App servers
#   - 10.0.11.0/24 (us-east-1b) -> App servers
#   - Route vers NAT Gateway pour Internet sortant
# 
# PRIVATE SUBNETS (Database - 2 AZs):
#   - 10.0.20.0/24 (us-east-1a) -> RDS Primary
#   - 10.0.21.0/24 (us-east-1b) -> RDS Standby
#   - Isolé (pas d'accès Internet)
# 
# SECURITY GROUPS:
#   - ALB-SG: 80,443 depuis 0.0.0.0/0
#   - Web-SG: 80,443 depuis ALB-SG
#   - App-SG: 8080 depuis Web-SG
#   - DB-SG: 3306 depuis App-SG
#   - Bastion-SG: 22 depuis IP admin

# SCRIPT COMPLET CRÉATION ARCHITECTURE
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Création VPC production complet

set -e  # Arrêter si erreur

# Variables
VPC_CIDR="10.0.0.0/16"
REGION="us-east-1"
AZ1="us-east-1a"
AZ2="us-east-1b"

echo "=== Création VPC ==="
VPC_ID=$(aws ec2 create-vpc \
  --cidr-block $VPC_CIDR \
  --tag-specifications 'ResourceType=vpc,Tags=[{Key=Name,Value=Production-VPC}]' \
  --query 'Vpc.VpcId' \
  --output text)

echo "VPC créé: $VPC_ID"

# Activer DNS
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-support
aws ec2 modify-vpc-attribute --vpc-id $VPC_ID --enable-dns-hostnames

echo "=== Création Subnets ==="

# Public Subnets
PUBLIC_SUBNET_1=$(aws ec2 create-subnet \
  --vpc-id $VPC_ID \
  --cidr-block 10.0.1.0/24 \
  --availability-zone $AZ1 \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Public-Subnet-1A}]' \
  --query 'Subnet.SubnetId' \
  --output text)

PUBLIC_SUBNET_2=$(aws ec2 create-subnet \
  --vpc-id $VPC_ID \
  --cidr-block 10.0.2.0/24 \
  --availability-zone $AZ2 \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Public-Subnet-1B}]' \
  --query 'Subnet.SubnetId' \
  --output text)

# Activer auto-assign public IP
aws ec2 modify-subnet-attribute --subnet-id $PUBLIC_SUBNET_1 --map-public-ip-on-launch
aws ec2 modify-subnet-attribute --subnet-id $PUBLIC_SUBNET_2 --map-public-ip-on-launch

# Private Subnets (App)
PRIVATE_APP_SUBNET_1=$(aws ec2 create-subnet \
  --vpc-id $VPC_ID \
  --cidr-block 10.0.10.0/24 \
  --availability-zone $AZ1 \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Private-App-Subnet-1A}]' \
  --query 'Subnet.SubnetId' \
  --output text)

PRIVATE_APP_SUBNET_2=$(aws ec2 create-subnet \
  --vpc-id $VPC_ID \
  --cidr-block 10.0.11.0/24 \
  --availability-zone $AZ2 \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Private-App-Subnet-1B}]' \
  --query 'Subnet.SubnetId' \
  --output text)

# Private Subnets (Database)
PRIVATE_DB_SUBNET_1=$(aws ec2 create-subnet \
  --vpc-id $VPC_ID \
  --cidr-block 10.0.20.0/24 \
  --availability-zone $AZ1 \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Private-DB-Subnet-1A}]' \
  --query 'Subnet.SubnetId' \
  --output text)

PRIVATE_DB_SUBNET_2=$(aws ec2 create-subnet \
  --vpc-id $VPC_ID \
  --cidr-block 10.0.21.0/24 \
  --availability-zone $AZ2 \
  --tag-specifications 'ResourceType=subnet,Tags=[{Key=Name,Value=Private-DB-Subnet-1B}]' \
  --query 'Subnet.SubnetId' \
  --output text)

echo "Subnets créés"

echo "=== Création Internet Gateway ==="
IGW_ID=$(aws ec2 create-internet-gateway \
  --tag-specifications 'ResourceType=internet-gateway,Tags=[{Key=Name,Value=Production-IGW}]' \
  --query 'InternetGateway.InternetGatewayId' \
  --output text)

aws ec2 attach-internet-gateway --internet-gateway-id $IGW_ID --vpc-id $VPC_ID
echo "Internet Gateway créé et attaché: $IGW_ID"

echo "=== Création NAT Gateways ==="

# Elastic IPs pour NAT Gateways
EIP1_ALLOC=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)
EIP2_ALLOC=$(aws ec2 allocate-address --domain vpc --query 'AllocationId' --output text)

# NAT Gateway AZ1
NAT_GW_1=$(aws ec2 create-nat-gateway \
  --subnet-id $PUBLIC_SUBNET_1 \
  --allocation-id $EIP1_ALLOC \
  --tag-specifications 'ResourceType=nat-gateway,Tags=[{Key=Name,Value=NAT-Gateway-1A}]' \
  --query 'NatGateway.NatGatewayId' \
  --output text)

# NAT Gateway AZ2
NAT_GW_2=$(aws ec2 create-nat-gateway \
  --subnet-id $PUBLIC_SUBNET_2 \
  --allocation-id $EIP2_ALLOC \
  --tag-specifications 'ResourceType=nat-gateway,Tags=[{Key=Name,Value=NAT-Gateway-1B}]' \
  --query 'NatGateway.NatGatewayId' \
  --output text)

echo "Attente NAT Gateways disponibles..."
aws ec2 wait nat-gateway-available --nat-gateway-ids $NAT_GW_1 $NAT_GW_2
echo "NAT Gateways créés: $NAT_GW_1, $NAT_GW_2"

echo "=== Création Route Tables ==="

# Route Table Public
PUBLIC_RT=$(aws ec2 create-route-table \
  --vpc-id $VPC_ID \
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=Public-RT}]' \
  --query 'RouteTable.RouteTableId' \
  --output text)

# Route vers Internet Gateway
aws ec2 create-route \
  --route-table-id $PUBLIC_RT \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id $IGW_ID

# Associer subnets publics
aws ec2 associate-route-table --route-table-id $PUBLIC_RT --subnet-id $PUBLIC_SUBNET_1
aws ec2 associate-route-table --route-table-id $PUBLIC_RT --subnet-id $PUBLIC_SUBNET_2

# Route Table Private AZ1
PRIVATE_RT_1=$(aws ec2 create-route-table \
  --vpc-id $VPC_ID \
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=Private-RT-1A}]' \
  --query 'RouteTable.RouteTableId' \
  --output text)

aws ec2 create-route \
  --route-table-id $PRIVATE_RT_1 \
  --destination-cidr-block 0.0.0.0/0 \
  --nat-gateway-id $NAT_GW_1

aws ec2 associate-route-table --route-table-id $PRIVATE_RT_1 --subnet-id $PRIVATE_APP_SUBNET_1

# Route Table Private AZ2
PRIVATE_RT_2=$(aws ec2 create-route-table \
  --vpc-id $VPC_ID \
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=Private-RT-1B}]' \
  --query 'RouteTable.RouteTableId' \
  --output text)

aws ec2 create-route \
  --route-table-id $PRIVATE_RT_2 \
  --destination-cidr-block 0.0.0.0/0 \
  --nat-gateway-id $NAT_GW_2

aws ec2 associate-route-table --route-table-id $PRIVATE_RT_2 --subnet-id $PRIVATE_APP_SUBNET_2

# Route Table Database (pas d'Internet)
DB_RT=$(aws ec2 create-route-table \
  --vpc-id $VPC_ID \
  --tag-specifications 'ResourceType=route-table,Tags=[{Key=Name,Value=Database-RT}]' \
  --query 'RouteTable.RouteTableId' \
  --output text)

aws ec2 associate-route-table --route-table-id $DB_RT --subnet-id $PRIVATE_DB_SUBNET_1
aws ec2 associate-route-table --route-table-id $DB_RT --subnet-id $PRIVATE_DB_SUBNET_2

echo "Route Tables créées"

echo "=== Création Security Groups ==="

# ALB Security Group
ALB_SG=$(aws ec2 create-security-group \
  --group-name ALB-SG \
  --description "Security group for Application Load Balancer" \
  --vpc-id $VPC_ID \
  --query 'GroupId' \
  --output text)

aws ec2 authorize-security-group-ingress \
  --group-id $ALB_SG \
  --ip-permissions '[
    {"IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "IpRanges": [{"CidrIp": "0.0.0.0/0"}]},
    {"IpProtocol": "tcp", "FromPort": 443, "ToPort": 443, "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}
  ]'

# Web Security Group
WEB_SG=$(aws ec2 create-security-group \
  --group-name Web-SG \
  --description "Security group for web servers" \
  --vpc-id $VPC_ID \
  --query 'GroupId' \
  --output text)

aws ec2 authorize-security-group-ingress \
  --group-id $WEB_SG \
  --ip-permissions '[
    {"IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "UserIdGroupPairs": [{"GroupId": "'$ALB_SG'"}]},
    {"IpProtocol": "tcp", "FromPort": 443, "ToPort": 443, "UserIdGroupPairs": [{"GroupId": "'$ALB_SG'"}]}
  ]'

# App Security Group
APP_SG=$(aws ec2 create-security-group \
  --group-name App-SG \
  --description "Security group for application servers" \
  --vpc-id $VPC_ID \
  --query 'GroupId' \
  --output text)

aws ec2 authorize-security-group-ingress \
  --group-id $APP_SG \
  --protocol tcp \
  --port 8080 \
  --source-group $WEB_SG

# Database Security Group
DB_SG=$(aws ec2 create-security-group \
  --group-name DB-SG \
  --description "Security group for database" \
  --vpc-id $VPC_ID \
  --query 'GroupId' \
  --output text)

aws ec2 authorize-security-group-ingress \
  --group-id $DB_SG \
  --protocol tcp \
  --port 3306 \
  --source-group $APP_SG

echo "Security Groups créés"

echo "=== Création VPC Endpoints ==="

# S3 Gateway Endpoint
S3_ENDPOINT=$(aws ec2 create-vpc-endpoint \
  --vpc-id $VPC_ID \
  --service-name com.amazonaws.$REGION.s3 \
  --route-table-ids $PRIVATE_RT_1 $PRIVATE_RT_2 \
  --query 'VpcEndpoint.VpcEndpointId' \
  --output text)

echo "S3 Endpoint créé: $S3_ENDPOINT"

echo "=== Activation Flow Logs ==="

# Créer log group
aws logs create-log-group --log-group-name /aws/vpc/production-flowlogs
aws logs put-retention-policy --log-group-name /aws/vpc/production-flowlogs --retention-in-days 7

# Flow Logs (supposant role déjà créé)
aws ec2 create-flow-logs \
  --resource-type VPC \
  --resource-ids $VPC_ID \
  --traffic-type ALL \
  --log-destination-type cloud-watch-logs \
  --log-group-name /aws/vpc/production-flowlogs \
  --deliver-logs-permission-arn arn:aws:iam::123456789012:role/VPC-FlowLogs-Role

echo "=== ARCHITECTURE COMPLÈTE CRÉÉE ==="
echo "VPC ID: $VPC_ID"
echo "Public Subnets: $PUBLIC_SUBNET_1, $PUBLIC_SUBNET_2"
echo "Private App Subnets: $PRIVATE_APP_SUBNET_1, $PRIVATE_APP_SUBNET_2"
echo "Private DB Subnets: $PRIVATE_DB_SUBNET_1, $PRIVATE_DB_SUBNET_2"
echo "Security Groups: ALB=$ALB_SG, Web=$WEB_SG, App=$APP_SG, DB=$DB_SG"


═══════════════════════════════════════════════════════════════════════════════
[OK] TROUBLESHOOTING VPC - PROBLÈMES COURANTS
═══════════════════════════════════════════════════════════════════════════════

# PROBLÈME 1: Instance ne peut pas accéder Internet
════════════════════════════════════════════════════════════════════════════════

# CAUSE POSSIBLE 1: Pas de route vers IGW/NAT
# Vérifier route table
aws ec2 describe-route-tables \
  --filters "Name=association.subnet-id,Values=subnet-xxx" \
  --query 'RouteTables[0].Routes'

# SOLUTION: Ajouter route
aws ec2 create-route \
  --route-table-id rtb-xxx \
  --destination-cidr-block 0.0.0.0/0 \
  --gateway-id igw-xxx  # ou --nat-gateway-id nat-xxx

# CAUSE POSSIBLE 2: Security Group bloque trafic sortant
# Vérifier règles sortantes
aws ec2 describe-security-groups \
  --group-ids sg-xxx \
  --query 'SecurityGroups[0].IpPermissionsEgress'

# SOLUTION: Autoriser trafic sortant
aws ec2 authorize-security-group-egress \
  --group-id sg-xxx \
  --protocol -1 \
  --cidr 0.0.0.0/0

# CAUSE POSSIBLE 3: NACL bloque trafic
# Vérifier NACL
aws ec2 describe-network-acls \
  --filters "Name=association.subnet-id,Values=subnet-xxx"

# SOLUTION: Ajouter règles NACL (voir section NACL)


# PROBLÈME 2: Impossible de SSH vers instance
════════════════════════════════════════════════════════════════════════════════

# CAUSE 1: Security Group ne permet pas SSH
aws ec2 describe-security-groups \
  --group-ids sg-xxx \
  --query 'SecurityGroups[0].IpPermissions'

# SOLUTION: Autoriser SSH
aws ec2 authorize-security-group-ingress \
  --group-id sg-xxx \
  --protocol tcp \
  --port 22 \
  --cidr YOUR_IP/32

# CAUSE 2: Instance dans subnet privé sans bastion
# SOLUTION: Utiliser bastion host ou AWS Systems Manager Session Manager

# CAUSE 3: NACL bloque port 22 ou ports éphémères
# Vérifier NACL entrante (port 22) et sortante (ports 1024-65535)


# PROBLÈME 3: RDS dans VPC inaccessible depuis Lambda
════════════════════════════════════════════════════════════════════════════════

# CAUSE: Lambda pas dans même VPC que RDS

# SOLUTION: Mettre Lambda dans VPC
aws lambda update-function-configuration \
  --function-name my-function \
  --vpc-config SubnetIds=subnet-private1,subnet-private2,SecurityGroupIds=sg-lambda

# Puis autoriser Lambda SG dans RDS SG
aws ec2 authorize-security-group-ingress \
  --group-id sg-rds \
  --protocol tcp \
  --port 3306 \
  --source-group sg-lambda


# PROBLÈME 4: VPC Peering ne fonctionne pas
════════════════════════════════════════════════════════════════════════════════

# CAUSE 1: Peering accepté mais pas de routes
# Vérifier routes des DEUX côtés

aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=vpc-A" \
  --query 'RouteTables[*].Routes'

# SOLUTION: Ajouter routes dans les deux VPCs

# CAUSE 2: Security Groups bloquent trafic
# Security Groups ne reconnaissent pas automatiquement peering
# SOLUTION: Autoriser par CIDR, pas par SG

aws ec2 authorize-security-group-ingress \
  --group-id sg-vpc-b \
  --protocol tcp \
  --port 80 \
  --cidr 10.0.0.0/16  # CIDR de VPC-A


# PROBLÈME 5: Flow Logs ne montrent rien
════════════════════════════════════════════════════════════════════════════════

# CAUSE 1: IAM role manque permissions
# Vérifier permissions role

aws iam get-role-policy \
  --role-name VPC-FlowLogs-Role \
  --policy-name VPC-FlowLogs-Policy

# CAUSE 2: Flow Logs juste créés (délai 10-15 min)
# Attendre quelques minutes

# CAUSE 3: Pas de trafic réseau
# Générer trafic pour tester
ping 8.8.8.8


# PROBLÈME 6: NAT Gateway trop cher
════════════════════════════════════════════════════════════════════════════════

# COÛT: ~$0.045/heure + $0.045/GB

# SOLUTION 1: Utiliser VPC Endpoints pour services AWS
# Évite NAT Gateway pour S3, DynamoDB, etc.

# SOLUTION 2: Utiliser 1 NAT Gateway au lieu de 1 par AZ
# [ATTENTION] Perd haute disponibilité!

# SOLUTION 3: Arrêter instances privées quand non utilisées
# Réduire trafic sortant


# PROBLÈME 7: "CIDR block overlaps with existing CIDR block"
════════════════════════════════════════════════════════════════════════════════

# CAUSE: Tentative créer subnet/VPC avec CIDR qui existe déjà

# Lister CIDRs existants
aws ec2 describe-vpcs \
  --query 'Vpcs[*].[VpcId,CidrBlock]' \
  --output table

aws ec2 describe-subnets \
  --filters "Name=vpc-id,Values=vpc-xxx" \
  --query 'Subnets[*].[SubnetId,CidrBlock]' \
  --output table

# SOLUTION: Utiliser CIDR différent non-chevauchant


# PROBLÈME 8: "Network acl acl-xxx has dependencies"
════════════════════════════════════════════════════════════════════════════════

# CAUSE: NACL encore associée à subnets

# Voir associations
aws ec2 describe-network-acls \
  --network-acl-ids acl-xxx \
  --query 'NetworkAcls[0].Associations'

# SOLUTION: Désassocier d'abord
# (Subnets retournent à NACL par défaut automatiquement)


═══════════════════════════════════════════════════════════════════════════════
[OK] BEST PRACTICES VPC
═══════════════════════════════════════════════════════════════════════════════

# [OK] SÉCURITÉ
════════════════════════════════════════════════════════════════════════════════

1. PRINCIPE DU MOINDRE PRIVILÈGE
   - Security Groups: Autoriser SEULEMENT ports nécessaires
   - NACL: Bloquer IPs malveillantes connues
   - Ne JAMAIS autoriser 0.0.0.0/0 sur SSH (port 22)

2. DÉFENSE EN PROFONDEUR
   - Security Groups (instance) + NACL (subnet)
   - Public subnet -> Web servers seulement
   - Private subnet -> App servers, databases
   - Database subnet -> Isolé, pas d'Internet

3. BASTION HOST ou SESSION MANAGER
   - Ne PAS exposer SSH directement sur Internet
   - Utiliser bastion dans subnet public
   - OU AWS Systems Manager Session Manager (meilleur)

4. VPC FLOW LOGS
   - Activer pour audit et troubleshooting
   - Retention court (7 jours) pour réduire coûts
   - Analyser logs régulièrement

5. ENCRYPTION
   - VPC endpoints pour S3/DynamoDB
   - HTTPS entre services
   - TLS pour RDS

# [OK] HAUTE DISPONIBILITÉ
════════════════════════════════════════════════════════════════════════════════

1. MULTI-AZ
   - Au moins 2 subnets dans 2 AZs différentes
   - Load Balancer couvre 2+ AZs
   - RDS Multi-AZ

2. NAT GATEWAY
   - 1 par AZ pour HA (sinon single point of failure)
   - Coût: 2× mais critique

3. ROUTE TABLES
   - Route table privée par AZ
   - Chaque AZ route vers son propre NAT Gateway

# [OK] COÛTS
════════════════════════════════════════════════════════════════════════════════

1. VPC ENDPOINTS
   - S3/DynamoDB Gateway Endpoints (gratuit)
   - Évite coûts NAT Gateway pour AWS services

2. NAT GATEWAY vs NAT INSTANCE
   - NAT Gateway: Simple mais $$$
   - NAT Instance: Moins cher mais complexe
   - Pour prod: NAT Gateway (fiabilité)

3. FLOW LOGS
   - S3 moins cher que CloudWatch
   - Retention court
   - Filtrer traffic type (REJECT seulement)

4. ELASTIC IPs
   - Libérer IPs non utilisées ($0.005/h)
   - 1 EIP gratuite par instance en cours

# [OK] DESIGN CIDR
════════════════════════════════════════════════════════════════════════════════

1. TAILLE VPC
   - Commencer grand: /16 (65k IPs)
   - Permet croissance future
   - Impossible d'agrandir VPC après!

2. SUBNETS
   - Utiliser /24 (256 IPs) pour subnets
   - Réserver ranges pour expansion:
     * 10.0.0.0/20 = Public (16 subnets /24)
     * 10.0.16.0/20 = Private App (16 subnets /24)
     * 10.0.32.0/20 = Private DB (16 subnets /24)
     * 10.0.48.0/20 = Réservé futur

3. ÉVITER OVERLAPS
   - On-premise: 192.168.0.0/16
   - AWS VPC: 10.0.0.0/16
   - Jamais même range!

# [OK] NAMING CONVENTIONS
════════════════════════════════════════════════════════════════════════════════

# Tags standardisés:
# - Name: Production-VPC
# - Environment: prod / dev / staging
# - Owner: team-platform
# - CostCenter: engineering
# - Project: webapp

# Exemple:
aws ec2 create-tags \
  --resources vpc-xxx subnet-xxx \
  --tags \
    Key=Name,Value=Production-VPC \
    Key=Environment,Value=prod \
    Key=Owner,Value=team-platform \
    Key=CostCenter,Value=engineering


# [OK] DOCUMENTATION
════════════════════════════════════════════════════════════════════════════════

# Documenter:
# - Diagramme architecture réseau
# - CIDR allocations
# - Security Groups et leurs règles
# - NACLs et leurs règles
# - VPC Peerings
# - VPC Endpoints

# Exemple diagram:
# Production VPC (10.0.0.0/16)
# ├─ Public Subnets
# │  ├─ 10.0.1.0/24 (us-east-1a) - ALB, Bastion
# │  └─ 10.0.2.0/24 (us-east-1b) - ALB, Bastion
# ├─ Private App Subnets
# │  ├─ 10.0.10.0/24 (us-east-1a) - App Servers
# │  └─ 10.0.11.0/24 (us-east-1b) - App Servers
# └─ Private DB Subnets
#    ├─ 10.0.20.0/24 (us-east-1a) - RDS Primary
#    └─ 10.0.21.0/24 (us-east-1b) - RDS Standby


═══════════════════════════════════════════════════════════════════════════════
[OK] SUPPRIMER VPC - NETTOYAGE COMPLET
═══════════════════════════════════════════════════════════════════════════════

# [ATTENTION] ORDRE IMPORTANT: Supprimer dépendances d'abord!

# 1. Supprimer instances EC2
aws ec2 terminate-instances --instance-ids i-xxx i-yyy

# Attendre terminaison
aws ec2 wait instance-terminated --instance-ids i-xxx i-yyy

# 2. Supprimer NAT Gateways
aws ec2 delete-nat-gateway --nat-gateway-id nat-xxx

# Attendre suppression (prend 5-10 min)
aws ec2 wait nat-gateway-deleted --nat-gateway-ids nat-xxx

# 3. Libérer Elastic IPs
aws ec2 release-address --allocation-id eipalloc-xxx

# 4. Supprimer VPC Endpoints
aws ec2 delete-vpc-endpoints --vpc-endpoint-ids vpce-xxx vpce-yyy

# 5. Supprimer Load Balancers (si présents)
aws elbv2 delete-load-balancer --load-balancer-arn arn:xxx

# Attendre suppression
aws elbv2 wait load-balancer-deleted --load-balancer-arns arn:xxx

# 6. Supprimer Target Groups
aws elbv2 delete-target-group --target-group-arn arn:xxx

# 7. Supprimer RDS instances (si présentes)
aws rds delete-db-instance \
  --db-instance-identifier mydb \
  --skip-final-snapshot

# 8. Supprimer VPC Peering Connections
aws ec2 delete-vpc-peering-connection --vpc-peering-connection-id pcx-xxx

# 9. Supprimer Custom Route Tables
# (Obtenir IDs d'abord)
aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=vpc-xxx" \
  --query 'RouteTables[?Associations[0].Main==`false`].RouteTableId' \
  --output text

# Désassocier subnets
aws ec2 disassociate-route-table --association-id rtbassoc-xxx

# Supprimer route table
aws ec2 delete-route-table --route-table-id rtb-xxx

# 10. Détacher et supprimer Internet Gateway
aws ec2 detach-internet-gateway \
  --internet-gateway-id igw-xxx \
  --vpc-id vpc-xxx

aws ec2 delete-internet-gateway --internet-gateway-id igw-xxx

# 11. Supprimer Subnets
aws ec2 delete-subnet --subnet-id subnet-xxx

# 12. Supprimer Custom NACLs
aws ec2 delete-network-acl --network-acl-id acl-xxx

# 13. Supprimer Custom Security Groups
aws ec2 delete-security-group --group-id sg-xxx

# 14. Supprimer VPC
aws ec2 delete-vpc --vpc-id vpc-xxx

# SCRIPT AUTOMATIQUE NETTOYAGE
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Supprimer VPC et toutes dépendances

VPC_ID="vpc-0123456789abcdef0"

echo "Suppression VPC: $VPC_ID"

# Instances
echo "Terminaison instances..."
INSTANCES=$(aws ec2 describe-instances \
  --filters "Name=vpc-id,Values=$VPC_ID" "Name=instance-state-name,Values=running,stopped" \
  --query 'Reservations[*].Instances[*].InstanceId' \
  --output text)

if [ ! -z "$INSTANCES" ]; then
  aws ec2 terminate-instances --instance-ids $INSTANCES
  aws ec2 wait instance-terminated --instance-ids $INSTANCES
fi

# NAT Gateways
echo "Suppression NAT Gateways..."
NAT_GWS=$(aws ec2 describe-nat-gateways \
  --filter "Name=vpc-id,Values=$VPC_ID" "Name=state,Values=available" \
  --query 'NatGateways[*].NatGatewayId' \
  --output text)

for nat in $NAT_GWS; do
  aws ec2 delete-nat-gateway --nat-gateway-id $nat
done

# Attendre suppression NAT Gateways
if [ ! -z "$NAT_GWS" ]; then
  sleep 60
fi

# Elastic IPs
echo "Libération Elastic IPs..."
EIPS=$(aws ec2 describe-addresses \
  --filters "Name=domain,Values=vpc" \
  --query 'Addresses[?AssociationId==null].AllocationId' \
  --output text)

for eip in $EIPS; do
  aws ec2 release-address --allocation-id $eip 2>/dev/null || true
done

# VPC Endpoints
echo "Suppression VPC Endpoints..."
ENDPOINTS=$(aws ec2 describe-vpc-endpoints \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --query 'VpcEndpoints[*].VpcEndpointId' \
  --output text)

if [ ! -z "$ENDPOINTS" ]; then
  aws ec2 delete-vpc-endpoints --vpc-endpoint-ids $ENDPOINTS
fi

# VPC Peering Connections
echo "Suppression Peering Connections..."
PEERINGS=$(aws ec2 describe-vpc-peering-connections \
  --filters "Name=requester-vpc-info.vpc-id,Values=$VPC_ID" \
  --query 'VpcPeeringConnections[*].VpcPeeringConnectionId' \
  --output text)

for peer in $PEERINGS; do
  aws ec2 delete-vpc-peering-connection --vpc-peering-connection-id $peer
done

# Internet Gateways
echo "Suppression Internet Gateways..."
IGWS=$(aws ec2 describe-internet-gateways \
  --filters "Name=attachment.vpc-id,Values=$VPC_ID" \
  --query 'InternetGateways[*].InternetGatewayId' \
  --output text)

for igw in $IGWS; do
  aws ec2 detach-internet-gateway --internet-gateway-id $igw --vpc-id $VPC_ID
  aws ec2 delete-internet-gateway --internet-gateway-id $igw
done

# Subnets
echo "Suppression Subnets..."
SUBNETS=$(aws ec2 describe-subnets \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --query 'Subnets[*].SubnetId' \
  --output text)

for subnet in $SUBNETS; do
  aws ec2 delete-subnet --subnet-id $subnet 2>/dev/null || true
done

# Route Tables (non-main)
echo "Suppression Route Tables..."
ROUTE_TABLES=$(aws ec2 describe-route-tables \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --query 'RouteTables[?Associations[0].Main==`false`].RouteTableId' \
  --output text)

for rt in $ROUTE_TABLES; do
  aws ec2 delete-route-table --route-table-id $rt 2>/dev/null || true
done

# Network ACLs (non-default)
echo "Suppression Network ACLs..."
NACLS=$(aws ec2 describe-network-acls \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --query 'NetworkAcls[?IsDefault==`false`].NetworkAclId' \
  --output text)

for nacl in $NACLS; do
  aws ec2 delete-network-acl --network-acl-id $nacl 2>/dev/null || true
done

# Security Groups (non-default)
echo "Suppression Security Groups..."
SGS=$(aws ec2 describe-security-groups \
  --filters "Name=vpc-id,Values=$VPC_ID" \
  --query 'SecurityGroups[?GroupName!=`default`].GroupId' \
  --output text)

for sg in $SGS; do
  aws ec2 delete-security-group --group-id $sg 2>/dev/null || true
done

# VPC
echo "Suppression VPC..."
aws ec2 delete-vpc --vpc-id $VPC_ID

echo "VPC supprimé: $VPC_ID"


═══════════════════════════════════════════════════════════════════════════════
[OK] COMMANDES RAPIDES - QUICK REFERENCE
═══════════════════════════════════════════════════════════════════════════════

# VPC
aws ec2 create-vpc --cidr-block 10.0.0.0/16
aws ec2 describe-vpcs
aws ec2 delete-vpc --vpc-id vpc-xxx

# Subnets
aws ec2 create-subnet --vpc-id vpc-xxx --cidr-block 10.0.1.0/24 --availability-zone us-east-1a
aws ec2 describe-subnets --filters "Name=vpc-id,Values=vpc-xxx"
aws ec2 delete-subnet --subnet-id subnet-xxx

# Internet Gateway
aws ec2 create-internet-gateway
aws ec2 attach-internet-gateway --internet-gateway-id igw-xxx --vpc-id vpc-xxx
aws ec2 detach-internet-gateway --internet-gateway-id igw-xxx --vpc-id vpc-xxx
aws ec2 delete-internet-gateway --internet-gateway-id igw-xxx

# NAT Gateway
aws ec2 allocate-address --domain vpc
aws ec2 create-nat-gateway --subnet-id subnet-xxx --allocation-id eipalloc-xxx
aws ec2 delete-nat-gateway --nat-gateway-id nat-xxx
aws ec2 release-address --allocation-id eipalloc-xxx

# Route Tables
aws ec2 create-route-table --vpc-id vpc-xxx
aws ec2 create-route --route-table-id rtb-xxx --destination-cidr-block 0.0.0.0/0 --gateway-id igw-xxx
aws ec2 associate-route-table --route-table-id rtb-xxx --subnet-id subnet-xxx
aws ec2 describe-route-tables --filters "Name=vpc-id,Values=vpc-xxx"

# Security Groups
aws ec2 create-security-group --group-name NAME --description "DESC" --vpc-id vpc-xxx
aws ec2 authorize-security-group-ingress --group-id sg-xxx --protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 describe-security-groups --group-ids sg-xxx
aws ec2 delete-security-group --group-id sg-xxx

# VPC Peering
aws ec2 create-vpc-peering-connection --vpc-id vpc-xxx --peer-vpc-id vpc-yyy
aws ec2 accept-vpc-peering-connection --vpc-peering-connection-id pcx-xxx
aws ec2 delete-vpc-peering-connection --vpc-peering-connection-id pcx-xxx

# VPC Endpoints
aws ec2 create-vpc-endpoint --vpc-id vpc-xxx --service-name com.amazonaws.REGION.s3 --route-table-ids rtb-xxx
aws ec2 describe-vpc-endpoints --filters "Name=vpc-id,Values=vpc-xxx"
aws ec2 delete-vpc-endpoints --vpc-endpoint-ids vpce-xxx

# Flow Logs
aws ec2 create-flow-logs --resource-type VPC --resource-ids vpc-xxx --traffic-type ALL \
  --log-destination-type cloud-watch-logs --log-group-name /aws/vpc/flowlogs \
  --deliver-logs-permission-arn arn:xxx
aws ec2 describe-flow-logs
aws ec2 delete-flow-logs --flow-log-ids fl-xxx


═══════════════════════════════════════════════════════════════════════════════
[OK] RESSOURCES UTILES
═══════════════════════════════════════════════════════════════════════════════

# Documentation officielle
https://docs.aws.amazon.com/vpc/

# VPC Pricing
https://aws.amazon.com/vpc/pricing/

# CIDR Calculator
https://www.ipaddressguide.com/cidr

# VPC Best Practices
https://docs.aws.amazon.com/vpc/latest/userguide/vpc-security-best-practices.html

# VPC Flow Logs Analysis
https://docs.aws.amazon.com/vpc/latest/userguide/flow-logs.html

# Outil visualisation: VPC Reachability Analyzer
https://docs.aws.amazon.com/vpc/latest/reachability/

# AWS Network Firewall (protection avancée)
https://aws.amazon.com/network-firewall/



# Fichier: aws_cheats/cheatsheets/cloudwatch.txt
# Cheatsheet AWS CloudWatch - Guide Complet pour Débutants

═══════════════════════════════════════════════════════════════════════════════
[OK] CLOUDWATCH - MONITORING, LOGS & ALARMES
═══════════════════════════════════════════════════════════════════════════════

CloudWatch = Service AWS de monitoring et observabilité
- Collecter des métriques (CPU, RAM, réseau, custom...)
- Stocker et analyser des logs
- Créer des alarmes et notifications
- Créer des dashboards de visualisation

═══════════════════════════════════════════════════════════════════════════════
[OK] MÉTRIQUES - COLLECTER & CONSULTER
═══════════════════════════════════════════════════════════════════════════════

# === LISTER LES MÉTRIQUES DISPONIBLES ===

# Lister toutes les métriques EC2
aws cloudwatch list-metrics --namespace AWS/EC2

# Lister toutes les métriques d'un service spécifique
aws cloudwatch list-metrics --namespace AWS/RDS
aws cloudwatch list-metrics --namespace AWS/Lambda
aws cloudwatch list-metrics --namespace AWS/S3
aws cloudwatch list-metrics --namespace AWS/ELB

# Namespaces AWS courants:
# AWS/EC2          - Instances EC2
# AWS/RDS          - Bases de données
# AWS/Lambda       - Fonctions serverless
# AWS/S3           - Stockage objet
# AWS/ELB          - Load balancers
# AWS/DynamoDB     - Base NoSQL
# AWS/Billing      - Facturation
# Custom/MyApp     - Vos métriques personnalisées

# Lister une métrique spécifique
aws cloudwatch list-metrics \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization

# Lister métriques avec dimension spécifique
aws cloudwatch list-metrics \
  --namespace AWS/EC2 \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0

# Lister avec filtre sur nom de métrique
aws cloudwatch list-metrics \
  --namespace AWS/EC2 \
  --metric-name NetworkIn

# Lister récemment utilisées
aws cloudwatch list-metrics \
  --recently-active PT3H    # Dernières 3 heures


# === CONSULTER LES STATISTIQUES ===

# Obtenir CPU moyen des 5 dernières minutes
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --start-time 2024-01-15T10:00:00Z \
  --end-time 2024-01-15T10:05:00Z \
  --period 300 \
  --statistics Average

# Statistiques disponibles:
# Average      - Moyenne
# Sum          - Somme
# Minimum      - Valeur minimale
# Maximum      - Valeur maximale
# SampleCount  - Nombre d'échantillons

# Obtenir plusieurs statistiques en même temps
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --start-time 2024-01-15T10:00:00Z \
  --end-time 2024-01-15T10:05:00Z \
  --period 300 \
  --statistics Average Maximum Minimum

# Périodes courantes:
# 60 secondes   - 1 minute
# 300 secondes  - 5 minutes (période par défaut EC2)
# 3600 secondes - 1 heure
# 86400 secondes - 1 jour

# Utiliser dates relatives (avec GNU date)
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300 \
  --statistics Average

# Obtenir mémoire disponible (avec CloudWatch Agent)
aws cloudwatch get-metric-statistics \
  --namespace CWAgent \
  --metric-name mem_used_percent \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300 \
  --statistics Average

# Obtenir trafic réseau
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name NetworkIn \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --start-time $(date -u -d '1 day ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 3600 \
  --statistics Sum \
  --unit Bytes


# === PUBLIER DES MÉTRIQUES PERSONNALISÉES ===

# Publier une métrique simple
aws cloudwatch put-metric-data \
  --namespace MyApp \
  --metric-name RequestCount \
  --value 10 \
  --unit Count

# Unités disponibles:
# Count        - Compteur
# Bytes        - Octets
# Seconds      - Temps en secondes
# Milliseconds - Temps en millisecondes
# Percent      - Pourcentage
# None         - Sans unité

# Publier avec timestamp personnalisé
aws cloudwatch put-metric-data \
  --namespace MyApp \
  --metric-name RequestCount \
  --value 10 \
  --timestamp 2024-01-15T10:00:00Z \
  --unit Count

# Publier avec dimensions (pour filtrer/grouper)
aws cloudwatch put-metric-data \
  --namespace MyApp \
  --metric-name ResponseTime \
  --value 250 \
  --unit Milliseconds \
  --dimensions Environment=Production,Server=web-01

# Publier avec plusieurs dimensions
aws cloudwatch put-metric-data \
  --namespace MyApp \
  --metric-name APILatency \
  --value 125 \
  --unit Milliseconds \
  --dimensions Environment=Production,Region=us-east-1,Endpoint=/api/users

# Publier plusieurs métriques en une seule commande
aws cloudwatch put-metric-data \
  --namespace MyApp \
  --metric-data \
    MetricName=RequestCount,Value=100,Unit=Count \
    MetricName=ErrorCount,Value=5,Unit=Count

# Publier des statistiques pré-agrégées (optimisé)
aws cloudwatch put-metric-data \
  --namespace MyApp \
  --metric-name ResponseTime \
  --statistic-values Sum=1250,Minimum=100,Maximum=500,SampleCount=10 \
  --unit Milliseconds

# Exemples de métriques custom utiles:
# - Nombre de connexions actives
# - Temps de réponse API
# - Nombre d'erreurs applicatives
# - Taille de file d'attente
# - Nombre de transactions
# - Utilisation mémoire applicative


═══════════════════════════════════════════════════════════════════════════════
[OK] ALARMES - CRÉER & GÉRER
═══════════════════════════════════════════════════════════════════════════════

# === CRÉER DES ALARMES ===

# Alarme simple: CPU > 80%
aws cloudwatch put-metric-alarm \
  --alarm-name high-cpu-alarm \
  --alarm-description "Alert when CPU exceeds 80%" \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --statistic Average \
  --period 300 \
  --evaluation-periods 2 \
  --threshold 80 \
  --comparison-operator GreaterThanThreshold

# Opérateurs de comparaison disponibles:
# GreaterThanThreshold           - Supérieur à
# GreaterThanOrEqualToThreshold  - Supérieur ou égal à
# LessThanThreshold              - Inférieur à
# LessThanOrEqualToThreshold     - Inférieur ou égal à

# Alarme avec action SNS (envoi email/SMS)
aws cloudwatch put-metric-alarm \
  --alarm-name high-cpu-alarm \
  --alarm-description "Alert when CPU exceeds 80%" \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --statistic Average \
  --period 300 \
  --evaluation-periods 2 \
  --threshold 80 \
  --comparison-operator GreaterThanThreshold \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:my-topic

# Créer SNS topic d'abord:
aws sns create-topic --name my-cloudwatch-alerts
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-cloudwatch-alerts \
  --protocol email \
  --notification-endpoint your-email@example.com

# Alarme avec plusieurs actions
aws cloudwatch put-metric-alarm \
  --alarm-name critical-cpu-alarm \
  --alarm-description "Critical CPU usage" \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0 \
  --statistic Average \
  --period 300 \
  --evaluation-periods 2 \
  --threshold 90 \
  --comparison-operator GreaterThanThreshold \
  --alarm-actions \
    arn:aws:sns:us-east-1:123456789012:critical-alerts \
    arn:aws:autoscaling:us-east-1:123456789012:scalingPolicy:xxx \
  --ok-actions arn:aws:sns:us-east-1:123456789012:resolved-alerts

# Actions disponibles:
# --alarm-actions        - Quand alarme se déclenche (état ALARM)
# --ok-actions          - Quand alarme se résout (état OK)
# --insufficient-data-actions - Quand données insuffisantes

# Alarme sur métrique de billing
aws cloudwatch put-metric-alarm \
  --alarm-name billing-alarm \
  --alarm-description "Alert when bill exceeds $100" \
  --namespace AWS/Billing \
  --metric-name EstimatedCharges \
  --dimensions Name=Currency,Value=USD \
  --statistic Maximum \
  --period 21600 \
  --evaluation-periods 1 \
  --threshold 100 \
  --comparison-operator GreaterThanThreshold \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:billing-alerts

# Note: Activer les alertes de billing dans la console AWS d'abord
# Console > Billing > Billing preferences > Receive Billing Alerts

# Alarme sur métrique custom
aws cloudwatch put-metric-alarm \
  --alarm-name high-error-rate \
  --alarm-description "Alert on high error rate" \
  --namespace MyApp \
  --metric-name ErrorRate \
  --dimensions Environment=Production \
  --statistic Average \
  --period 60 \
  --evaluation-periods 3 \
  --threshold 5 \
  --comparison-operator GreaterThanThreshold \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:app-alerts

# Alarme avec traitement de données manquantes
aws cloudwatch put-metric-alarm \
  --alarm-name disk-space-alarm \
  --alarm-description "Disk space low" \
  --namespace CWAgent \
  --metric-name disk_used_percent \
  --dimensions Name=InstanceId,Value=i-1234567890abcdef0,Name=path,Value=/ \
  --statistic Average \
  --period 300 \
  --evaluation-periods 2 \
  --threshold 80 \
  --comparison-operator GreaterThanThreshold \
  --treat-missing-data notBreaching

# Options treat-missing-data:
# notBreaching  - Considérer comme OK (recommandé)
# breaching     - Considérer comme en alarme
# ignore        - Maintenir l'état actuel
# missing       - Considérer comme données insuffisantes

# Alarme composite (combine plusieurs alarmes)
aws cloudwatch put-composite-alarm \
  --alarm-name app-health-alarm \
  --alarm-description "App unhealthy if CPU high AND errors high" \
  --alarm-rule "ALARM(high-cpu-alarm) AND ALARM(high-error-rate)" \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:critical-alerts

# Règles composites possibles:
# AND  - Toutes les alarmes doivent être en ALARM
# OR   - Au moins une alarme en ALARM
# NOT  - Inverser l'état


# === CONSULTER LES ALARMES ===

# Lister toutes les alarmes
aws cloudwatch describe-alarms

# Lister alarmes par état
aws cloudwatch describe-alarms --state-value ALARM
aws cloudwatch describe-alarms --state-value OK
aws cloudwatch describe-alarms --state-value INSUFFICIENT_DATA

# États possibles:
# OK                 - Métrique en dessous du seuil
# ALARM              - Métrique au-dessus du seuil
# INSUFFICIENT_DATA  - Pas assez de données

# Lister alarmes spécifiques
aws cloudwatch describe-alarms \
  --alarm-names high-cpu-alarm billing-alarm

# Lister alarmes avec préfixe
aws cloudwatch describe-alarms --alarm-name-prefix prod-

# Lister alarmes pour une action spécifique
aws cloudwatch describe-alarms \
  --action-prefix arn:aws:sns:us-east-1:123456789012:my-topic

# Obtenir historique d'une alarme
aws cloudwatch describe-alarm-history \
  --alarm-name high-cpu-alarm

# Historique sur période spécifique
aws cloudwatch describe-alarm-history \
  --alarm-name high-cpu-alarm \
  --start-date 2024-01-15T00:00:00Z \
  --end-date 2024-01-16T00:00:00Z

# Historique par type d'événement
aws cloudwatch describe-alarm-history \
  --alarm-name high-cpu-alarm \
  --history-item-type StateUpdate    # ConfigurationUpdate, Action


# === GÉRER LES ALARMES ===

# Désactiver alarme (ne se déclenche plus)
aws cloudwatch disable-alarm-actions --alarm-names high-cpu-alarm

# Activer alarme
aws cloudwatch enable-alarm-actions --alarm-names high-cpu-alarm

# Désactiver plusieurs alarmes
aws cloudwatch disable-alarm-actions \
  --alarm-names alarm1 alarm2 alarm3

# Définir manuellement l'état (pour tests)
aws cloudwatch set-alarm-state \
  --alarm-name high-cpu-alarm \
  --state-value ALARM \
  --state-reason "Testing alarm notification"

# Supprimer alarme
aws cloudwatch delete-alarms --alarm-names high-cpu-alarm

# Supprimer plusieurs alarmes
aws cloudwatch delete-alarms \
  --alarm-names alarm1 alarm2 alarm3

# Supprimer toutes les alarmes d'un préfixe (ATTENTION!)
aws cloudwatch describe-alarms --alarm-name-prefix test- \
  --query 'MetricAlarms[*].AlarmName' \
  --output text | xargs aws cloudwatch delete-alarms --alarm-names


═══════════════════════════════════════════════════════════════════════════════
[OK] LOGS - COLLECTER & ANALYSER
═══════════════════════════════════════════════════════════════════════════════

# === CRÉER LOG GROUPS & STREAMS ===

# Créer log group
aws logs create-log-group --log-group-name /aws/myapp

# Conventions de nommage:
# /aws/lambda/function-name    - Lambda
# /aws/ec2/instance-name       - EC2
# /aws/rds/instance-name       - RDS
# /aws/ecs/cluster/service     - ECS
# /custom/myapp                - Custom

# Créer log group avec tags
aws logs create-log-group \
  --log-group-name /aws/myapp \
  --tags Environment=Production,Application=MyApp

# Créer log stream
aws logs create-log-stream \
  --log-group-name /aws/myapp \
  --log-stream-name app-server-01

# Créer plusieurs log streams
aws logs create-log-stream \
  --log-group-name /aws/myapp \
  --log-stream-name app-server-01

aws logs create-log-stream \
  --log-group-name /aws/myapp \
  --log-stream-name app-server-02


# === ENVOYER DES LOGS ===

# Envoyer un log event simple
aws logs put-log-events \
  --log-group-name /aws/myapp \
  --log-stream-name app-server-01 \
  --log-events timestamp=$(date +%s000),message="Application started"

# Timestamp en millisecondes depuis epoch
# $(date +%s000) = timestamp actuel

# Envoyer plusieurs events
aws logs put-log-events \
  --log-group-name /aws/myapp \
  --log-stream-name app-server-01 \
  --log-events \
    timestamp=1705320000000,message="User login: john@example.com" \
    timestamp=1705320001000,message="Request processed successfully"

# Envoyer avec sequence token (requis après premier envoi)
# Obtenir le token:
NEXT_TOKEN=$(aws logs describe-log-streams \
  --log-group-name /aws/myapp \
  --log-stream-name-prefix app-server-01 \
  --query 'logStreams[0].uploadSequenceToken' \
  --output text)

aws logs put-log-events \
  --log-group-name /aws/myapp \
  --log-stream-name app-server-01 \
  --sequence-token $NEXT_TOKEN \
  --log-events timestamp=$(date +%s000),message="New log entry"

# Note: En pratique, utiliser CloudWatch Agent ou SDK pour envoyer logs


# === LISTER & CONSULTER ===

# Lister tous les log groups
aws logs describe-log-groups

# Lister avec filtre sur nom
aws logs describe-log-groups --log-group-name-prefix /aws/

# Lister avec limite
aws logs describe-log-groups --limit 10

# Obtenir info sur log group spécifique
aws logs describe-log-groups --log-group-name /aws/myapp

# Lister log streams d'un group
aws logs describe-log-streams --log-group-name /aws/myapp

# Lister log streams triés par dernière activité
aws logs describe-log-streams \
  --log-group-name /aws/myapp \
  --order-by LastEventTime \
  --descending

# Lister avec préfixe
aws logs describe-log-streams \
  --log-group-name /aws/myapp \
  --log-stream-name-prefix app-server


# === CONSULTER LES LOGS ===

# Voir logs en temps réel (tail -f)
aws logs tail /aws/myapp --follow

# Tail avec filtre
aws logs tail /aws/myapp --follow --filter-pattern ERROR

# Tail depuis un moment donné
aws logs tail /aws/myapp --since 1h
aws logs tail /aws/myapp --since 30m
aws logs tail /aws/myapp --since 2024-01-15T10:00:00

# Tail d'un stream spécifique
aws logs tail /aws/myapp/app-server-01 --follow

# Filtrer logs entre deux dates
aws logs filter-log-events \
  --log-group-name /aws/myapp \
  --start-time $(date -d '1 hour ago' +%s000) \
  --end-time $(date +%s000)

# Filtrer avec pattern
aws logs filter-log-events \
  --log-group-name /aws/myapp \
  --filter-pattern "ERROR"

# Patterns de filtrage courants:
# "ERROR"                    - Contient ERROR
# "?ERROR ?WARN"            - ERROR OU WARN
# "[ERROR]"                 - Terme exact ERROR
# "{ $.level = \"ERROR\" }" - JSON avec level=ERROR
# "[time, request_id, level = ERROR]" - Format spécifique

# Filtrer logs JSON
aws logs filter-log-events \
  --log-group-name /aws/myapp \
  --filter-pattern '{ $.statusCode = 500 }'

# Filtrer par log stream
aws logs filter-log-events \
  --log-group-name /aws/myapp \
  --log-stream-names app-server-01 app-server-02 \
  --filter-pattern "ERROR"

# Obtenir événements spécifiques
aws logs get-log-events \
  --log-group-name /aws/myapp \
  --log-stream-name app-server-01 \
  --limit 100

# Obtenir dans l'ordre inverse (plus récents d'abord)
aws logs get-log-events \
  --log-group-name /aws/myapp \
  --log-stream-name app-server-01 \
  --limit 100 \
  --start-from-head false


# === LOGS INSIGHTS (REQUÊTES AVANCÉES) ===

# Requête simple
aws logs start-query \
  --log-group-name /aws/myapp \
  --start-time $(date -d '1 hour ago' +%s) \
  --end-time $(date +%s) \
  --query-string 'fields @timestamp, @message | sort @timestamp desc | limit 20'

# Sauvegarder query ID pour récupérer résultats
QUERY_ID=$(aws logs start-query \
  --log-group-name /aws/myapp \
  --start-time $(date -d '1 hour ago' +%s) \
  --end-time $(date +%s) \
  --query-string 'fields @timestamp, @message | limit 20' \
  --query 'queryId' --output text)

# Attendre et obtenir résultats
sleep 5
aws logs get-query-results --query-id $QUERY_ID

# Requête avec filtrage
aws logs start-query \
  --log-group-name /aws/myapp \
  --start-time $(date -d '1 hour ago' +%s) \
  --end-time $(date +%s) \
  --query-string 'fields @timestamp, @message | filter @message like /ERROR/ | sort @timestamp desc | limit 20'

# Requête avec agrégation
aws logs start-query \
  --log-group-name /aws/myapp \
  --start-time $(date -d '1 day ago' +%s) \
  --end-time $(date +%s) \
  --query-string 'stats count() by bin(5m)'

# Requête sur logs JSON
aws logs start-query \
  --log-group-name /aws/myapp \
  --start-time $(date -d '1 hour ago' +%s) \
  --end-time $(date +%s) \
  --query-string 'fields @timestamp, level, message | filter level = "ERROR" | sort @timestamp desc'

# Requête avec calculs
aws logs start-query \
  --log-group-name /aws/myapp \
  --start-time $(date -d '1 hour ago' +%s) \
  --end-time $(date +%s) \
  --query-string 'stats avg(responseTime), max(responseTime), min(responseTime) by bin(5m)'

# Exemples de requêtes Logs Insights utiles:
# - Top 10 erreurs:
#   fields @message | filter @message like /ERROR/ | stats count() by @message | sort count desc | limit 10
# 
# - Latence moyenne par endpoint:
#   stats avg(duration) by endpoint
#
# - Nombre de requêtes par heure:
#   stats count() by bin(1h)
#
# - Erreurs 500:
#   filter statusCode = 500 | fields @timestamp, @message


# === GÉRER LES LOGS ===

# Définir période de rétention
aws logs put-retention-policy \
  --log-group-name /aws/myapp \
  --retention-in-days 7

# Périodes de rétention disponibles (jours):
# 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1096, 1827, 2192, 2557, 2922, 3288, 3653

# Rétention recommandée par type:
# Logs dev/test: 7 jours
# Logs production: 30-90 jours
# Logs compliance/audit: 365+ jours

# Supprimer politique de rétention (garde indéfiniment)
aws logs delete-retention-policy --log-group-name /aws/myapp

# Supprimer log stream
aws logs delete-log-stream \
  --log-group-name /aws/myapp \
  --log-stream-name app-server-01

# Supprimer log group (ATTENTION: supprime tous les logs!)
aws logs delete-log-group --log-group-name /aws/myapp

# Tagger log group
aws logs tag-log-group \
  --log-group-name /aws/myapp \
  --tags Environment=Production,Team=Backend

# Supprimer tags
aws logs untag-log-group \
  --log-group-name /aws/myapp \
  --tags Environment Team


# === EXPORTER LES LOGS ===

# Créer export task vers S3
aws logs create-export-task \
  --log-group-name /aws/myapp \
  --from $(date -d '1 day ago' +%s000) \
  --to $(date +%s000) \
  --destination my-logs-bucket \
  --destination-prefix logs/myapp/

# Note: Créer bucket S3 et policy d'abord
# Policy exemple: permettre CloudWatch Logs d'écrire

# Vérifier statut export
aws logs describe-export-tasks

# Vérifier export spécifique
aws logs describe-export-tasks --task-id <TASK_ID>


# === SUBSCRIPTION FILTERS ===

# Créer filtre pour streamer vers Lambda
aws logs put-subscription-filter \
  --log-group-name /aws/myapp \
  --filter-name error-processor \
  --filter-pattern "ERROR" \
  --destination-arn arn:aws:lambda:us-east-1:123456789012:function:ProcessErrors

# Créer filtre vers Kinesis
aws logs put-subscription-filter \
  --log-group-name /aws/myapp \
  --filter-name log-stream \
  --filter-pattern "" \
  --destination-arn arn:aws:kinesis:us-east-1:123456789012:stream/LogStream

# Lister subscription filters
aws logs describe-subscription-filters \
  --log-group-name /aws/myapp

# Supprimer subscription filter
aws logs delete-subscription-filter \
  --log-group-name /aws/myapp \
  --filter-name error-processor


═══════════════════════════════════════════════════════════════════════════════
[OK] DASHBOARDS - VISUALISATION
═══════════════════════════════════════════════════════════════════════════════

# === CRÉER DASHBOARD ===

# Créer fichier JSON de dashboard
# dashboard.json
{
  "widgets": [
    {
      "type": "metric",
      "x": 0,
      "y": 0,
      "width": 12,
      "height": 6,
      "properties": {
        "metrics": [
          [ "AWS/EC2", "CPUUtilization", { "stat": "Average" } ]
        ],
        "period": 300,
        "stat": "Average",
        "region": "us-east-1",
        "title": "Recent Errors"
      }
    }
  ]
}


# === GÉRER DASHBOARDS ===

# Lister tous les dashboards
aws cloudwatch list-dashboards

# Lister avec préfixe
aws cloudwatch list-dashboards --dashboard-name-prefix prod-

# Obtenir dashboard spécifique
aws cloudwatch get-dashboard --dashboard-name MyDashboard

# Mettre à jour dashboard (écrase l'ancien)
aws cloudwatch put-dashboard \
  --dashboard-name MyDashboard \
  --dashboard-body file://dashboard-updated.json

# Supprimer dashboard
aws cloudwatch delete-dashboards --dashboard-names MyDashboard

# Supprimer plusieurs dashboards
aws cloudwatch delete-dashboards \
  --dashboard-names Dashboard1 Dashboard2 Dashboard3


# === EXEMPLES DE DASHBOARDS COMPLETS ===

# Dashboard application complète
{
  "widgets": [
    {
      "type": "metric",
      "x": 0,
      "y": 0,
      "width": 12,
      "height": 6,
      "properties": {
        "title": "Requests per Minute",
        "metrics": [
          [ "MyApp", "RequestCount", { "stat": "Sum" } ]
        ],
        "period": 60,
        "stat": "Sum",
        "region": "us-east-1"
      }
    },
    {
      "type": "metric",
      "x": 12,
      "y": 0,
      "width": 12,
      "height": 6,
      "properties": {
        "title": "Error Rate",
        "metrics": [
          [ "MyApp", "ErrorCount", { "stat": "Sum" } ]
        ],
        "period": 60,
        "stat": "Sum",
        "region": "us-east-1"
      }
    },
    {
      "type": "metric",
      "x": 0,
      "y": 6,
      "width": 24,
      "height": 6,
      "properties": {
        "title": "Response Time (ms)",
        "metrics": [
          [ "MyApp", "ResponseTime", { "stat": "Average", "label": "Avg" } ],
          [ "...", { "stat": "Maximum", "label": "Max" } ],
          [ "...", { "stat": "p95", "label": "p95" } ]
        ],
        "period": 300,
        "region": "us-east-1",
        "yAxis": {
          "left": {
            "min": 0
          }
        }
      }
    }
  ]
}

# Créer dashboard depuis fichier
aws cloudwatch put-dashboard \
  --dashboard-name MyDashboard \
  --dashboard-body file://dashboard.json

# Dashboard multi-métriques
{
  "widgets": [
    {
      "type": "metric",
      "properties": {
        "metrics": [
          [ "AWS/EC2", "CPUUtilization", { "label": "CPU", "stat": "Average" } ],
          [ "CWAgent", "mem_used_percent", { "label": "Memory", "stat": "Average" } ],
          [ "AWS/EC2", "disk_used_percent", { "label": "Disk", "stat": "Average" } ]
        ],
        "period": 300,
        "stat": "Average",
        "region": "us-east-1",
        "title": "System Resources",
        "yAxis": {
          "left": {
            "min": 0,
            "max": 100
          }
        }
      }
    }
  ]
}

# Widget de logs
{
  "widgets": [
    {
      "type": "log",
      "properties": {
        "query": "SOURCE '/aws/myapp' | fields @timestamp, @message | filter @message like /ERROR/ | sort @timestamp desc | limit 20",
        "region": "us-east-1",
        "title": "EC2 CPU Utilization"
      }
    },
    {
      "type": "metric",
      "x": 12,
      "y": 0,
      "width": 12,
      "height": 6,
      "properties": {
        "metrics": [
          [ "AWS/EC2", "NetworkIn" ],
          [ ".", "NetworkOut" ]
        ],
        "period": 300,
        "stat": "Average",
        "region": "us-east-1",
        "title": "Network Traffic"
      }
    }
  ]
}


═══════════════════════════════════════════════════════════════════════════════
[OK] CLOUDWATCH AGENT - MÉTRIQUES AVANCÉES
═══════════════════════════════════════════════════════════════════════════════

# Le CloudWatch Agent permet de collecter:
# - Métriques système avancées (RAM, disk, processes...)
# - Logs applicatifs
# - Métriques custom

# === INSTALLATION ===

# Télécharger agent (Amazon Linux 2 / RHEL / CentOS)
wget https://s3.amazonaws.com/amazoncloudwatch-agent/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm
sudo rpm -U ./amazon-cloudwatch-agent.rpm

# Ubuntu / Debian
wget https://s3.amazonaws.com/amazoncloudwatch-agent/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb
sudo dpkg -i -E ./amazon-cloudwatch-agent.deb

# Windows (PowerShell en admin)
Invoke-WebRequest -Uri https://s3.amazonaws.com/amazoncloudwatch-agent/windows/amd64/latest/amazon-cloudwatch-agent.msi -OutFile amazon-cloudwatch-agent.msi
msiexec /i amazon-cloudwatch-agent.msi


# === CONFIGURATION ===

# Créer fichier de configuration
# /opt/aws/amazon-cloudwatch-agent/etc/config.json

{
  "agent": {
    "metrics_collection_interval": 60,
    "run_as_user": "cwagent"
  },
  "metrics": {
    "namespace": "CWAgent",
    "metrics_collected": {
      "cpu": {
        "measurement": [
          {
            "name": "cpu_usage_idle",
            "rename": "CPU_IDLE",
            "unit": "Percent"
          },
          "cpu_usage_iowait"
        ],
        "metrics_collection_interval": 60,
        "totalcpu": false
      },
      "disk": {
        "measurement": [
          {
            "name": "used_percent",
            "rename": "DISK_USED",
            "unit": "Percent"
          }
        ],
        "metrics_collection_interval": 60,
        "resources": [
          "*"
        ]
      },
      "mem": {
        "measurement": [
          {
            "name": "mem_used_percent",
            "rename": "MEM_USED",
            "unit": "Percent"
          }
        ],
        "metrics_collection_interval": 60
      }
    }
  },
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/myapp/app.log",
            "log_group_name": "/aws/myapp",
            "log_stream_name": "{instance_id}/app.log"
          }
        ]
      }
    }
  }
}

# Démarrer agent avec configuration
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a fetch-config \
  -m ec2 \
  -s \
  -c file:/opt/aws/amazon-cloudwatch-agent/etc/config.json

# Vérifier statut
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a query \
  -m ec2 \
  -s

# Arrêter agent
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a stop \
  -m ec2 \
  -s


═══════════════════════════════════════════════════════════════════════════════
[OK] BONNES PRATIQUES
═══════════════════════════════════════════════════════════════════════════════

# === ORGANISATION DES LOGS ===

# 1. Utiliser une hiérarchie claire
/aws/service/environment/application
/aws/lambda/prod/user-service
/aws/ec2/staging/web-server

# 2. Définir des rétentions appropriées
# Dev/Test: 7 jours
aws logs put-retention-policy \
  --log-group-name /aws/dev/myapp \
  --retention-in-days 7

# Production: 30-90 jours
aws logs put-retention-policy \
  --log-group-name /aws/prod/myapp \
  --retention-in-days 90

# Compliance/Audit: 1+ an
aws logs put-retention-policy \
  --log-group-name /aws/audit/myapp \
  --retention-in-days 365

# 3. Utiliser des tags pour organisation
aws logs tag-log-group \
  --log-group-name /aws/myapp \
  --tags \
    Environment=Production \
    Team=Backend \
    CostCenter=Engineering \
    Application=MyApp


# === ALARMES INTELLIGENTES ===

# 1. Utiliser des périodes d'évaluation multiples
# Évite les faux positifs
aws cloudwatch put-metric-alarm \
  --alarm-name cpu-sustained-high \
  --evaluation-periods 3 \
  --period 300 \
  --threshold 80
# = Alarme si CPU > 80% pendant 15 minutes (3 x 5min)

# 2. Créer des alarmes en cascade
# Alarme WARNING (70%)
aws cloudwatch put-metric-alarm \
  --alarm-name cpu-warning \
  --threshold 70 \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:warnings

# Alarme CRITICAL (90%)
aws cloudwatch put-metric-alarm \
  --alarm-name cpu-critical \
  --threshold 90 \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:critical

# 3. Alarme sur données manquantes
aws cloudwatch put-metric-alarm \
  --alarm-name app-heartbeat \
  --metric-name Heartbeat \
  --namespace MyApp \
  --statistic Sum \
  --period 300 \
  --evaluation-periods 2 \
  --threshold 1 \
  --comparison-operator LessThanThreshold \
  --treat-missing-data breaching
# = Alarme si pas de heartbeat


# === MÉTRIQUES CUSTOM UTILES ===

# 1. Application health check
aws cloudwatch put-metric-data \
  --namespace MyApp \
  --metric-name HealthCheck \
  --value 1 \
  --unit Count

# 2. Business metrics
aws cloudwatch put-metric-data \
  --namespace MyApp \
  --metric-name OrdersCompleted \
  --value 42 \
  --unit Count \
  --dimensions Environment=Production

# 3. Queue depth
aws cloudwatch put-metric-data \
  --namespace MyApp \
  --metric-name QueueDepth \
  --value 1250 \
  --unit Count

# 4. Cache hit rate
aws cloudwatch put-metric-data \
  --namespace MyApp \
  --metric-name CacheHitRate \
  --value 87.5 \
  --unit Percent


# === DASHBOARDS EFFICACES ===

# 1. Dashboard par service
# - CPU, Memory, Disk
# - Network In/Out
# - Requests, Errors
# - Response time

# 2. Dashboard par environnement
# - Production overview
# - Staging overview
# - Dev overview

# 3. Dashboard SLA/Business
# - Uptime percentage
# - Error rate
# - Response time p99
# - Business KPIs


# === OPTIMISATION COÛTS ===

# 1. Limiter rétention des logs
# Coût: $0.50/GB ingestion + $0.03/GB storage/mois
aws logs put-retention-policy \
  --log-group-name /aws/dev/myapp \
  --retention-in-days 7

# 2. Filtrer les logs avant envoi
# Dans CloudWatch Agent config, filtrer logs peu utiles

# 3. Utiliser S3 pour archivage long terme
# Exporter logs anciens vers S3 (moins cher)
aws logs create-export-task \
  --log-group-name /aws/myapp \
  --from $(date -d '30 days ago' +%s000) \
  --to $(date -d '7 days ago' +%s000) \
  --destination my-archive-bucket

# 4. Supprimer log groups inutilisés
# Lister log groups sans activité récente
aws logs describe-log-groups \
  --query 'logGroups[?!contains(logGroupName, `prod`)].logGroupName' \
  --output text

# 5. Métriques custom: publier en batch
# Plus efficace que multiples appels individuels


# === SÉCURITÉ ===

# 1. Chiffrer log groups
aws logs create-log-group \
  --log-group-name /aws/myapp \
  --kms-key-id arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012

# 2. Restreindre accès avec IAM
# Policy exemple: lecture seule logs production
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:DescribeLogGroups",
        "logs:DescribeLogStreams",
        "logs:GetLogEvents",
        "logs:FilterLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:log-group:/aws/prod/*"
    }
  ]
}

# 3. Masquer données sensibles avant envoi
# Configurer dans application, pas dans CloudWatch


═══════════════════════════════════════════════════════════════════════════════
[OK] DÉPANNAGE & ERREURS COURANTES
═══════════════════════════════════════════════════════════════════════════════

# === ERREUR: ResourceNotFoundException ===
# Log group ou stream n'existe pas

# Solution: Créer d'abord
aws logs create-log-group --log-group-name /aws/myapp
aws logs create-log-stream \
  --log-group-name /aws/myapp \
  --log-stream-name mystream


# === ERREUR: InvalidSequenceTokenException ===
# Sequence token invalide lors d'envoi logs

# Solution: Obtenir le token actuel
NEXT_TOKEN=$(aws logs describe-log-streams \
  --log-group-name /aws/myapp \
  --log-stream-name-prefix mystream \
  --query 'logStreams[0].uploadSequenceToken' \
  --output text)

aws logs put-log-events \
  --log-group-name /aws/myapp \
  --log-stream-name mystream \
  --sequence-token $NEXT_TOKEN \
  --log-events timestamp=$(date +%s000),message="Test"


# === ERREUR: DataAlreadyAcceptedException ===
# Événement déjà envoyé (timestamp dupliqué)

# Solution: Utiliser timestamps uniques (millisecondes)
TIMESTAMP=$(($(date +%s) * 1000 + RANDOM % 1000))


# === ERREUR: InvalidParameterException (timestamp) ===
# Timestamp trop vieux ou dans le futur

# Solution: CloudWatch accepte logs de -14 jours à +2 heures
# Vérifier timestamp est en millisecondes
date +%s000  # Correct
date +%s     # INCORRECT (manque millisecondes)


# === PROBLÈME: Logs n'apparaissent pas ===

# 1. Vérifier IAM permissions
# EC2 doit avoir rôle avec CloudWatchAgentServerPolicy

# 2. Vérifier agent est démarré
sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl \
  -a query -m ec2 -s

# 3. Vérifier configuration agent
cat /opt/aws/amazon-cloudwatch-agent/etc/config.json

# 4. Vérifier logs agent
sudo tail -f /opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log


# === PROBLÈME: Alarme ne se déclenche pas ===

# 1. Vérifier état alarme
aws cloudwatch describe-alarms --alarm-names my-alarm

# 2. Vérifier données métriques disponibles
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-xxx \
  --start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 300 \
  --statistics Average

# 3. Vérifier actions SNS sont configurées
aws cloudwatch describe-alarms \
  --alarm-names my-alarm \
  --query 'MetricAlarms[0].AlarmActions'

# 4. Tester manuellement
aws cloudwatch set-alarm-state \
  --alarm-name my-alarm \
  --state-value ALARM \
  --state-reason "Manual test"


# === PROBLÈME: Coûts élevés ===

# 1. Identifier log groups volumineux
aws logs describe-log-groups \
  --query 'sort_by(logGroups, &storedBytes)[-10:].{Name:logGroupName,Size:storedBytes}' \
  --output table

# 2. Vérifier rétention
aws logs describe-log-groups \
  --query 'logGroups[?retentionInDays==`null`].logGroupName'

# 3. Calculer coût estimé par log group
# Ingestion: $0.50/GB
# Storage: $0.03/GB/mois
# Exemple: 100 GB ingestion/mois + 500 GB stockés
# = (100 * $0.50) + (500 * $0.03) = $50 + $15 = $65/mois


═══════════════════════════════════════════════════════════════════════════════
[OK] SCRIPTS UTILES
═══════════════════════════════════════════════════════════════════════════════

# === Script: Nettoyer vieux log groups ===

#!/bin/bash
# cleanup-old-logs.sh
# Supprime log groups sans activité depuis 30 jours

CUTOFF_DATE=$(date -d '30 days ago' +%s000)

aws logs describe-log-groups --query 'logGroups[*].logGroupName' --output text | \
while read LOG_GROUP; do
  LAST_EVENT=$(aws logs describe-log-streams \
    --log-group-name "$LOG_GROUP" \
    --order-by LastEventTime \
    --descending \
    --max-items 1 \
    --query 'logStreams[0].lastEventTimestamp' \
    --output text)
  
  if [ "$LAST_EVENT" != "None" ] && [ "$LAST_EVENT" -lt "$CUTOFF_DATE" ]; then
    echo "Deleting inactive log group: $LOG_GROUP"
    aws logs delete-log-group --log-group-name "$LOG_GROUP"
  fi
done


# === Script: Définir rétention pour tous les log groups ===

#!/bin/bash
# set-retention.sh
# Définit rétention 7 jours pour tous les log groups dev/test

aws logs describe-log-groups \
  --query 'logGroups[?contains(logGroupName, `dev`) || contains(logGroupName, `test`)].logGroupName' \
  --output text | \
while read LOG_GROUP; do
  echo "Setting 7-day retention for: $LOG_GROUP"
  aws logs put-retention-policy \
    --log-group-name "$LOG_GROUP" \
    --retention-in-days 7
done


# === Script: Surveiller coûts CloudWatch ===

#!/bin/bash
# monitor-cloudwatch-costs.sh
# Calcule taille totale des logs

TOTAL_SIZE=0

aws logs describe-log-groups --query 'logGroups[*].[logGroupName,storedBytes]' --output text | \
while read LOG_GROUP SIZE; do
  TOTAL_SIZE=$((TOTAL_SIZE + SIZE))
  SIZE_GB=$(echo "scale=2; $SIZE / 1024 / 1024 / 1024" | bc)
  echo "$LOG_GROUP: ${SIZE_GB} GB"
done

TOTAL_GB=$(echo "scale=2; $TOTAL_SIZE / 1024 / 1024 / 1024" | bc)
MONTHLY_COST=$(echo "scale=2; $TOTAL_GB * 0.03" | bc)

echo "---"
echo "Total: ${TOTAL_GB} GB"
echo "Estimated monthly storage cost: \${MONTHLY_COST}"


# === Script: Exporter logs vers S3 (batch) ===

#!/bin/bash
# export-logs-to-s3.sh
# Exporte tous les logs du mois dernier vers S3

LOG_GROUP="/aws/myapp"
BUCKET="my-logs-archive"
YEAR=$(date -d 'last month' +%Y)
MONTH=$(date -d 'last month' +%m)

START_TIME=$(date -d "${YEAR}-${MONTH}-01" +%s000)
END_TIME=$(date -d "${YEAR}-${MONTH}-01 +1 month" +%s000)

aws logs create-export-task \
  --log-group-name "$LOG_GROUP" \
  --from "$START_TIME" \
  --to "$END_TIME" \
  --destination "$BUCKET" \
  --destination-prefix "logs/${YEAR}/${MONTH}/"


# === Script: Créer alarmes standard pour instance EC2 ===

#!/bin/bash
# create-ec2-alarms.sh INSTANCE_ID SNS_TOPIC_ARN

INSTANCE_ID=$1
SNS_TOPIC=$2

if [ -z "$INSTANCE_ID" ] || [ -z "$SNS_TOPIC" ]; then
  echo "Usage: $0 INSTANCE_ID SNS_TOPIC_ARN"
  exit 1
fi

# CPU > 80%
aws cloudwatch put-metric-alarm \
  --alarm-name "${INSTANCE_ID}-high-cpu" \
  --alarm-description "CPU exceeds 80%" \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=$INSTANCE_ID \
  --statistic Average \
  --period 300 \
  --evaluation-periods 2 \
  --threshold 80 \
  --comparison-operator GreaterThanThreshold \
  --alarm-actions $SNS_TOPIC

# Disk > 85%
aws cloudwatch put-metric-alarm \
  --alarm-name "${INSTANCE_ID}-high-disk" \
  --alarm-description "Disk usage exceeds 85%" \
  --namespace CWAgent \
  --metric-name disk_used_percent \
  --dimensions Name=InstanceId,Value=$INSTANCE_ID,Name=path,Value=/ \
  --statistic Average \
  --period 300 \
  --evaluation-periods 2 \
  --threshold 85 \
  --comparison-operator GreaterThanThreshold \
  --alarm-actions $SNS_TOPIC

# Memory > 80%
aws cloudwatch put-metric-alarm \
  --alarm-name "${INSTANCE_ID}-high-memory" \
  --alarm-description "Memory exceeds 80%" \
  --namespace CWAgent \
  --metric-name mem_used_percent \
  --dimensions Name=InstanceId,Value=$INSTANCE_ID \
  --statistic Average \
  --period 300 \
  --evaluation-periods 2 \
  --threshold 80 \
  --comparison-operator GreaterThanThreshold \
  --alarm-actions $SNS_TOPIC

echo "Alarmes créées pour instance $INSTANCE_ID"


# === Script: Query logs avec retry ===

#!/bin/bash
# query-logs.sh
# Lance query Logs Insights et attend résultats

LOG_GROUP="/aws/myapp"
QUERY='fields @timestamp, @message | filter @message like /ERROR/ | sort @timestamp desc | limit 20'
START_TIME=$(date -d '1 hour ago' +%s)
END_TIME=$(date +%s)

# Lancer query
QUERY_ID=$(aws logs start-query \
  --log-group-name "$LOG_GROUP" \
  --start-time $START_TIME \
  --end-time $END_TIME \
  --query-string "$QUERY" \
  --query 'queryId' \
  --output text)

echo "Query lancée: $QUERY_ID"

# Attendre résultats
MAX_ATTEMPTS=30
ATTEMPT=0

while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do
  STATUS=$(aws logs get-query-results \
    --query-id "$QUERY_ID" \
    --query 'status' \
    --output text)
  
  if [ "$STATUS" = "Complete" ]; then
    echo "Query terminée!"
    aws logs get-query-results --query-id "$QUERY_ID"
    exit 0
  elif [ "$STATUS" = "Failed" ] || [ "$STATUS" = "Cancelled" ]; then
    echo "Query échouée: $STATUS"
    exit 1
  fi
  
  echo "Attente... ($STATUS)"
  sleep 2
  ATTEMPT=$((ATTEMPT + 1))
done

echo "Timeout: query trop longue"
exit 1


═══════════════════════════════════════════════════════════════════════════════
[OK] PATTERNS AVANCÉS
═══════════════════════════════════════════════════════════════════════════════

# === Logs structurés JSON ===

# Application envoie logs JSON
{
  "timestamp": "2024-01-15T10:30:00Z",
  "level": "ERROR",
  "service": "user-service",
  "endpoint": "/api/users",
  "statusCode": 500,
  "message": "Database connection failed",
  "duration": 250
}

# Query Logs Insights pour analyser
aws logs start-query \
  --log-group-name /aws/myapp \
  --start-time $(date -d '1 hour ago' +%s) \
  --end-time $(date +%s) \
  --query-string '
    fields @timestamp, level, service, endpoint, statusCode, duration
    | filter level = "ERROR"
    | stats count() by endpoint, statusCode
    | sort count desc
  '

# Alarme sur taux d'erreur calculé
aws cloudwatch put-metric-alarm \
  --alarm-name high-error-rate \
  --metrics '[
    {
      "Id": "errors",
      "MetricStat": {
        "Metric": {
          "Namespace": "MyApp",
          "MetricName": "ErrorCount"
        },
        "Period": 300,
        "Stat": "Sum"
      }
    },
    {
      "Id": "requests",
      "MetricStat": {
        "Metric": {
          "Namespace": "MyApp",
          "MetricName": "RequestCount"
        },
        "Period": 300,
        "Stat": "Sum"
      }
    },
    {
      "Id": "error_rate",
      "Expression": "(errors / requests) * 100"
    }
  ]' \
  --threshold 5 \
  --comparison-operator GreaterThanThreshold \
  --evaluation-periods 2


# === Métriques composites ===

# Créer alarme basée sur math entre métriques
# Exemple: Alarme si (errors / requests) > 5%

# Fichier: composite-alarm.json
{
  "AlarmName": "high-error-rate",
  "ComparisonOperator": "GreaterThanThreshold",
  "EvaluationPeriods": 2,
  "Threshold": 5,
  "Metrics": [
    {
      "Id": "m1",
      "ReturnData": false,
      "MetricStat": {
        "Metric": {
          "Namespace": "MyApp",
          "MetricName": "ErrorCount"
        },
        "Period": 300,
        "Stat": "Sum"
      }
    },
    {
      "Id": "m2",
      "ReturnData": false,
      "MetricStat": {
        "Metric": {
          "Namespace": "MyApp",
          "MetricName": "RequestCount"
        },
        "Period": 300,
        "Stat": "Sum"
      }
    },
    {
      "Id": "e1",
      "Expression": "(m1/m2)*100"
    }
  ]
}


═══════════════════════════════════════════════════════════════════════════════
[OK] RESSOURCES & DOCUMENTATION
═══════════════════════════════════════════════════════════════════════════════

# Documentation officielle AWS:
# CloudWatch: https://docs.aws.amazon.com/cloudwatch/
# CloudWatch Logs: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/
# CloudWatch Agent: https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Install-CloudWatch-Agent.html
# Logs Insights: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/AnalyzingLogData.html

# Tarification:
# https://aws.amazon.com/cloudwatch/pricing/
# Logs: $0.50/GB ingestion, $0.03/GB/mois storage
# Métriques custom: $0.30/métrique/mois
# Alarmes: $0.10/alarme/mois
# Dashboards: $3/dashboard/mois

# Limites de service:
# https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/cloudwatch_limits_cwl.html
# - 10,000 log groups par région par compte
# - 1 MB/s max put-log-events par log stream
# - 5 requests/seconde par log stream

# Outils tiers utiles:
# - Datadog: Alternative monitoring avec plus de features
# - Grafana: Visualisation avancée (peut utiliser CloudWatch)
# - awslogs: CLI tool pour tail logs facilement
#   pip install awslogs
#   awslogs get /aws/myapp --watch

# CloudWatch Logs Insights exemples:
# https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax-examples.html


═══════════════════════════════════════════════════════════════════════════════
[OK] CHECKLIST PRODUCTION
═══════════════════════════════════════════════════════════════════════════════

# Avant mise en production:

# [ ] CloudWatch Agent installé et configuré
# [ ] Logs applicatifs envoyés vers CloudWatch
# [ ] Rétention définie sur tous les log groups (pas "Never expire")
# [ ] Alarmes créées pour métriques critiques:
#     - CPU, Memory, Disk > seuils
#     - Erreurs applicatives
#     - Latence API
#     - Health check failures
# [ ] SNS topics configurés avec emails/SMS
# [ ] Dashboards créés pour monitoring temps réel
# [ ] Tags appliqués sur log groups (Environment, Application, Team)
# [ ] IAM roles configurés (CloudWatchAgentServerPolicy pour EC2)
# [ ] Budget alertes activées (CloudWatch coûts)
# [ ] Logs sensibles masqués avant envoi
# [ ] KMS encryption activée si données sensibles
# [ ] Subscription filters configurés si besoin (Lambda, Kinesis)
# [ ] Export S3 automatisé pour archivage long terme
# [ ] Documentation runbook pour alarmes courantes

# Surveillance continue:
# [ ] Review logs quotidien
# [ ] Review alarmes hebdomadaire
# [ ] Audit coûts CloudWatch mensuel
# [ ] Cleanup log groups inutilisés trimestriel


═══════════════════════════════════════════════════════════════════════════════
FIN DU GUIDE CLOUDWATCH
═══════════════════════════════════════════════════════════════════════════════


═══════════════════════════════════════════════════════════════════════════════
[OK] ELB (ELASTIC LOAD BALANCING) - LOAD BALANCERS
═══════════════════════════════════════════════════════════════════════════════

3 types de load balancers:
- ALB (Application Load Balancer) - HTTP/HTTPS, Layer 7
- NLB (Network Load Balancer) - TCP/UDP, Layer 4, haute performance
- CLB (Classic Load Balancer) - Ancien, pas recommandé

# === APPLICATION LOAD BALANCER (ALB) ===

# Créer target group (groupe de cibles)
aws elbv2 create-target-group \
  --name my-targets \
  --protocol HTTP \
  --port 80 \
  --vpc-id vpc-1a2b3c4d \
  --health-check-path /health \
  --health-check-interval-seconds 30 \
  --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 2

# Créer ALB
aws elbv2 create-load-balancer \
  --name my-alb \
  --subnets subnet-12345678 subnet-87654321 \
  --security-groups sg-12345678 \
  --scheme internet-facing \
  --type application \
  --ip-address-type ipv4

# Créer listener HTTP
aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/50dc6c495c0c9188 \
  --protocol HTTP \
  --port 80 \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-targets/73e2d6bc24d8a067

# Créer listener HTTPS (nécessite certificat)
aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/50dc6c495c0c9188 \
  --protocol HTTPS \
  --port 443 \
  --certificates CertificateArn=arn:aws:acm:us-east-1:123456789012:certificate/12345678-1234-1234-1234-123456789012 \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-targets/73e2d6bc24d8a067

# Enregistrer instances dans target group
aws elbv2 register-targets \
  --target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-targets/73e2d6bc24d8a067 \
  --targets Id=i-1234567890abcdef0 Id=i-0987654321fedcba0

# Dés-enregistrer instances
aws elbv2 deregister-targets \
  --target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-targets/73e2d6bc24d8a067 \
  --targets Id=i-1234567890abcdef0

# Lister load balancers
aws elbv2 describe-load-balancers

# Lister target groups
aws elbv2 describe-target-groups

# Voir santé des targets
aws elbv2 describe-target-health \
  --target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-targets/73e2d6bc24d8a067

# === RÈGLES DE ROUTAGE ===

# Ajouter règle path-based routing
aws elbv2 create-rule \
  --listener-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/50dc6c495c0c9188/f2f7dc8efc522ab2 \
  --priority 10 \
  --conditions Field=path-pattern,Values='/api/*' \
  --actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/api-targets/73e2d6bc24d8a067

# Règle host-based routing
aws elbv2 create-rule \
  --listener-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/50dc6c495c0c9188/f2f7dc8efc522ab2 \
  --priority 20 \
  --conditions Field=host-header,Values='api.example.com' \
  --actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/api-targets/73e2d6bc24d8a067

# === NETWORK LOAD BALANCER (NLB) ===

# Créer NLB (similaire à ALB mais protocol=TCP)
aws elbv2 create-load-balancer \
  --name my-nlb \
  --subnets subnet-12345678 subnet-87654321 \
  --type network \
  --scheme internet-facing

# Target group pour NLB
aws elbv2 create-target-group \
  --name my-nlb-targets \
  --protocol TCP \
  --port 80 \
  --vpc-id vpc-1a2b3c4d \
  --target-type instance

# === SUPPRIMER ===

# Supprimer load balancer
aws elbv2 delete-load-balancer \
  --load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-alb/50dc6c495c0c9188

# Supprimer target group
aws elbv2 delete-target-group \
  --target-group-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-targets/73e2d6bc24d8a067


# Fichier: python_cheats/cheatsheets/auto-scaling.txt
# Cheatsheet AWS Auto Scaling - Scaling Automatique Expliqué en Détail


═══════════════════════════════════════════════════════════════════════════════
[OK] AWS AUTO SCALING - C'EST QUOI?
═══════════════════════════════════════════════════════════════════════════════

AUTO SCALING = "Ajuster automatiquement le nombre d'instances selon la charge"

ANALOGIE RESTAURANT:
- Heure creuse (10h) -> 2 serveurs suffisent
- Heure de pointe (12h) -> 10 serveurs nécessaires
- Auto Scaling = Embaucher/renvoyer serveurs automatiquement

PROBLÈME SANS AUTO SCALING:
[X] Trafic faible -> Instances inutilisées = Gaspillage d'argent
[X] Trafic élevé -> Pas assez d'instances = Site lent/crash
[X] Gestion manuelle = Réaction lente, erreurs humaines

SOLUTION AUTO SCALING:
[OK] Trafic augmente -> Ajouter instances automatiquement (scale out)
[OK] Trafic diminue -> Retirer instances automatiquement (scale in)
[OK] Payer seulement pour ce qui est nécessaire
[OK] Haute disponibilité (remplace instances défectueuses)
[OK] Réaction rapide (quelques minutes)

COMPOSANTS PRINCIPAUX:
1. LAUNCH TEMPLATE -> "Recette" pour créer instances
2. AUTO SCALING GROUP (ASG) -> Groupe gérant les instances
3. SCALING POLICIES -> Règles pour scale up/down
4. HEALTH CHECKS -> Vérifier santé instances

TERMINOLOGIE:
- SCALE OUT = Ajouter instances (horizontal scaling)
- SCALE IN = Retirer instances
- SCALE UP = Augmenter taille instance (vertical scaling) [X] Pas Auto Scaling
- DESIRED CAPACITY = Nombre d'instances souhaité actuellement
- MIN SIZE = Nombre minimum d'instances (jamais en dessous)
- MAX SIZE = Nombre maximum d'instances (jamais au-dessus)

EXEMPLE CHIFFRES:
- Min: 2 instances (toujours au moins 2 pour HA)
- Desired: 4 instances (actuellement)
- Max: 10 instances (limite pour contrôler coûts)

QUAND UTILISER AUTO SCALING?
[OK] Applications web avec trafic variable
[OK] APIs avec pics de charge
[OK] Applications batch/traitement données
[OK] Services microservices
[OK] Tout ce qui peut être horizontal (stateless)

QUAND NE PAS UTILISER?
[X] Applications stateful (sessions locales)
[X] Bases de données (utiliser RDS Auto Scaling)
[X] Trafic constant prévisible
[X] Applications legacy non-scalables


═══════════════════════════════════════════════════════════════════════════════
[OK] LAUNCH TEMPLATE - LA "RECETTE" POUR CRÉER INSTANCES
═══════════════════════════════════════════════════════════════════════════════

LAUNCH TEMPLATE = "Instructions pour créer instances EC2"

CONTIENT:
- AMI (image) à utiliser
- Type d'instance (t3.micro, t3.medium, etc.)
- Security groups
- Key pair pour SSH
- User data (script lancement)
- IAM role
- Stockage (EBS)
- Network settings

DIFFÉRENCE Launch Template vs Launch Configuration:
┌────────────────────────┬─────────────────────┬────────────────────┐
│                        │  Launch Template    │ Launch Config      │
├────────────────────────┼─────────────────────┼────────────────────┤
│ Versions               │  [OK] Oui             │  [X] Non            │
│ Modification           │  [OK] Oui             │  [X] Non (immutable)│
│ T2/T3 Unlimited        │  [OK] Oui             │  [X] Non            │
│ Multiple types         │  [OK] Oui             │  [X] Non            │
│ Spot instances         │  [OK] Meilleur        │  [ATTENTION] Limité         │
│ Recommandation AWS     │  [OK] Utiliser        │  [X] Déprécié       │
└────────────────────────┴─────────────────────┴────────────────────┘

[ATTENTION] TOUJOURS utiliser Launch Template (pas Launch Configuration)!

# CRÉER LAUNCH TEMPLATE - EXPLICATIONS DÉTAILLÉES
════════════════════════════════════════════════════════════════════════════════

# Créer launch template basique
aws ec2 create-launch-template \
  --launch-template-name web-server-template \
  --version-description "Version 1 - Initial release" \
  --launch-template-data '{
    "ImageId": "ami-0c55b159cbfafe1f0",
    "InstanceType": "t3.micro",
    "KeyName": "my-key-pair",
    "SecurityGroupIds": ["sg-0123456789abcdef0"]
  }'

# EXPLICATION PARAMÈTRES:

# --launch-template-name
#   = Nom unique du template
#   = Minuscules, tirets OK

# --version-description
#   = Description de cette version
#   = Utile pour tracking changements

# --launch-template-data (JSON):

#   "ImageId": "ami-0c55b159cbfafe1f0"
#     = AMI à utiliser (Amazon Linux 2, Ubuntu, etc.)
#     = Trouver AMI: aws ec2 describe-images
#     = IMPORTANT: Utiliser AMI de la même région!

#   "InstanceType": "t3.micro"
#     = Taille instance
#     = t3.micro = 2 vCPU, 1 GB RAM (~$7/mois)
#     = Options: t3.small, t3.medium, m5.large, etc.

#   "KeyName": "my-key-pair"
#     = Key pair pour SSH
#     = Doit exister déjà (créer avec aws ec2 create-key-pair)

#   "SecurityGroupIds": ["sg-xxx"]
#     = Security groups pour instances
#     = Liste d'IDs (peut en avoir plusieurs)

# RÉSULTAT:
# {
#   "LaunchTemplate": {
#     "LaunchTemplateId": "lt-0123456789abcdef0",
#     "LaunchTemplateName": "web-server-template",
#     "CreateTime": "2024-01-15T10:30:00.000Z",
#     "DefaultVersionNumber": 1,
#     "LatestVersionNumber": 1
#   }
# }

# [ATTENTION] Notez LaunchTemplateId: lt-0123456789abcdef0

# CRÉER LAUNCH TEMPLATE COMPLET (PRODUCTION)
════════════════════════════════════════════════════════════════════════════════

# Préparer User Data (script bash encodé base64)
cat > user-data.sh << 'EOF'
#!/bin/bash
# Script exécuté au démarrage instance

# Update système
yum update -y

# Installer Apache
yum install -y httpd

# Créer page web
cat > /var/www/html/index.html << 'HTML'
<!DOCTYPE html>
<html>
<head><title>Auto Scaling Demo</title></head>
<body>
  <h1>Instance: $(hostname)</h1>
  <p>Servie par Auto Scaling!</p>
</body>
</html>
HTML

# Démarrer Apache
systemctl start httpd
systemctl enable httpd
EOF

# Encoder en base64
USER_DATA_BASE64=$(base64 -w 0 user-data.sh)

# Créer template complet
aws ec2 create-launch-template \
  --launch-template-name production-web-template \
  --version-description "Production v1 - Apache web server" \
  --launch-template-data '{
    "ImageId": "ami-0c55b159cbfafe1f0",
    "InstanceType": "t3.micro",
    "KeyName": "my-key-pair",
    "SecurityGroupIds": ["sg-web123"],
    "UserData": "'$USER_DATA_BASE64'",
    "IamInstanceProfile": {
      "Name": "EC2-WebServer-Role"
    },
    "BlockDeviceMappings": [
      {
        "DeviceName": "/dev/xvda",
        "Ebs": {
          "VolumeSize": 20,
          "VolumeType": "gp3",
          "DeleteOnTermination": true,
          "Encrypted": true
        }
      }
    ],
    "Monitoring": {
      "Enabled": true
    },
    "TagSpecifications": [
      {
        "ResourceType": "instance",
        "Tags": [
          {"Key": "Name", "Value": "Web-Server-ASG"},
          {"Key": "Environment", "Value": "Production"}
        ]
      }
    ],
    "MetadataOptions": {
      "HttpTokens": "required",
      "HttpPutResponseHopLimit": 1
    }
  }'

# EXPLICATION PARAMÈTRES AVANCÉS:

# "UserData": "'$USER_DATA_BASE64'"
#   = Script bash exécuté au lancement
#   = DOIT être encodé en base64
#   = Installe logiciels, configure instance, etc.

# "IamInstanceProfile": {"Name": "EC2-WebServer-Role"}
#   = IAM role pour l'instance
#   = Donne permissions (ex: accès S3, DynamoDB)
#   = Créer avec: aws iam create-instance-profile

# "BlockDeviceMappings"
#   = Configuration disques (EBS)
#   * VolumeSize: 20 GB
#   * VolumeType: gp3 (SSD rapide, recommandé)
#   * DeleteOnTermination: true (supprimer avec instance)
#   * Encrypted: true (chiffrement au repos)

# "Monitoring": {"Enabled": true}
#   = Detailed monitoring (métriques chaque 1 min vs 5 min)
#   = Coût: $0.14/instance/mois
#   = Recommandé pour production

# "TagSpecifications"
#   = Tags appliqués aux instances créées
#   = Facilite organisation et facturation

# "MetadataOptions"
#   = Sécurité metadata service (IMDSv2)
#   * HttpTokens: required = Force IMDSv2 (plus sécurisé)
#   * HttpPutResponseHopLimit: 1 = Limite accès metadata

# CRÉER LAUNCH TEMPLATE AVEC SPOT INSTANCES
════════════════════════════════════════════════════════════════════════════════

# Spot = Instances jusqu'à 90% moins chères (mais peuvent être interrompues)

aws ec2 create-launch-template \
  --launch-template-name spot-template \
  --version-description "Spot instances template" \
  --launch-template-data '{
    "ImageId": "ami-0c55b159cbfafe1f0",
    "InstanceType": "t3.micro",
    "KeyName": "my-key-pair",
    "SecurityGroupIds": ["sg-web123"],
    "InstanceMarketOptions": {
      "MarketType": "spot",
      "SpotOptions": {
        "MaxPrice": "0.02",
        "SpotInstanceType": "one-time",
        "InstanceInterruptionBehavior": "terminate"
      }
    }
  }'

# EXPLICATION SPOT:

# "MarketType": "spot"
#   = Utiliser instances Spot (vs on-demand)

# "MaxPrice": "0.02"
#   = Prix maximum par heure ($0.02)
#   = Si prix Spot > max -> instance terminée
#   = Laisser vide = prix on-demand (recommandé)

# "SpotInstanceType": "one-time"
#   = Requête ponctuelle (vs persistent)

# "InstanceInterruptionBehavior": "terminate"
#   = Que faire si AWS interrompt instance
#   = Options: terminate, stop, hibernate

# [ATTENTION] SPOT INSTANCES:
# Parfait pour: Batch processing, CI/CD, workloads flexibles
# Éviter pour: Bases de données, applications critiques

# CRÉER LAUNCH TEMPLATE MULTI-TYPES
════════════════════════════════════════════════════════════════════════════════

# Permet Auto Scaling d'utiliser plusieurs types d'instances

aws ec2 create-launch-template \
  --launch-template-name multi-type-template \
  --version-description "Multiple instance types" \
  --launch-template-data '{
    "ImageId": "ami-0c55b159cbfafe1f0",
    "KeyName": "my-key-pair",
    "SecurityGroupIds": ["sg-web123"],
    "InstanceRequirements": {
      "VCpuCount": {"Min": 2, "Max": 4},
      "MemoryMiB": {"Min": 2048, "Max": 8192}
    }
  }'

# EXPLICATION:
# InstanceRequirements = Spécifier besoins vs type exact
# Auto Scaling choisit instances disponibles matching critères
# Plus flexible et souvent moins cher

# LISTER LAUNCH TEMPLATES
════════════════════════════════════════════════════════════════════════════════

# Lister tous les templates
aws ec2 describe-launch-templates

# Format lisible
aws ec2 describe-launch-templates \
  --query 'LaunchTemplates[*].[LaunchTemplateId,LaunchTemplateName,DefaultVersionNumber,LatestVersionNumber]' \
  --output table

# RÉSULTAT EXEMPLE:
# -------------------------------------------------------------------------
# | lt-abc123  | web-server-template       | 1  | 3  |
# | lt-def456  | production-web-template   | 2  | 2  |
# -------------------------------------------------------------------------

# Obtenir template spécifique
aws ec2 describe-launch-templates \
  --launch-template-names web-server-template

# Voir détails version spécifique
aws ec2 describe-launch-template-versions \
  --launch-template-id lt-0123456789abcdef0 \
  --versions 1

# Voir dernière version
aws ec2 describe-launch-template-versions \
  --launch-template-id lt-0123456789abcdef0 \
  --versions '$Latest'

# Voir version par défaut
aws ec2 describe-launch-template-versions \
  --launch-template-id lt-0123456789abcdef0 \
  --versions '$Default'

# CRÉER NOUVELLE VERSION LAUNCH TEMPLATE
════════════════════════════════════════════════════════════════════════════════

# Créer version 2 (instance type changé)
aws ec2 create-launch-template-version \
  --launch-template-id lt-0123456789abcdef0 \
  --version-description "Version 2 - Upgraded to t3.small" \
  --launch-template-data '{
    "ImageId": "ami-0c55b159cbfafe1f0",
    "InstanceType": "t3.small",
    "KeyName": "my-key-pair",
    "SecurityGroupIds": ["sg-web123"]
  }'

# EXPLICATION:
# Créer nouvelle version sans modifier version 1
# ASG peut continuer utiliser version 1 si configuré
# Permet rollback facile si problème

# Définir version par défaut
aws ec2 modify-launch-template \
  --launch-template-id lt-0123456789abcdef0 \
  --default-version 2

# EXPLICATION:
# Version par défaut = utilisée si ASG spécifie $Default
# Pas obligatoire de changer (peut garder $Latest)

# SUPPRIMER LAUNCH TEMPLATE
════════════════════════════════════════════════════════════════════════════════

# Supprimer version spécifique
aws ec2 delete-launch-template-versions \
  --launch-template-id lt-0123456789abcdef0 \
  --versions 1

# Supprimer template complet (toutes versions)
aws ec2 delete-launch-template \
  --launch-template-id lt-0123456789abcdef0

# [ATTENTION] ERREUR SI:
# Template utilisé par ASG actif
# SOLUTION: Supprimer/modifier ASG d'abord


═══════════════════════════════════════════════════════════════════════════════
[OK] AUTO SCALING GROUP (ASG) - LE GESTIONNAIRE
═══════════════════════════════════════════════════════════════════════════════

AUTO SCALING GROUP = "Gestionnaire qui crée/supprime instances"

RESPONSABILITÉS ASG:
1. Maintenir desired capacity (nombre souhaité)
2. Remplacer instances défectueuses
3. Distribuer instances entre AZs
4. Enregistrer instances dans Load Balancer
5. Respecter min/max size

EXEMPLE FONCTIONNEMENT:
- Min: 2, Desired: 4, Max: 10
- ASG maintient toujours 4 instances
- Si instance crash -> ASG en lance une nouvelle
- Si scaling policy déclenche -> ASG ajuste desired capacity

HEALTH CHECKS:
- EC2: Instance répond-elle? (basic)
- ELB: Load balancer considère-t-elle instance healthy? (recommandé)

# CRÉER AUTO SCALING GROUP - BASIQUE
════════════════════════════════════════════════════════════════════════════════

aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name my-web-asg \
  --launch-template LaunchTemplateName=web-server-template,Version='$Latest' \
  --min-size 2 \
  --max-size 10 \
  --desired-capacity 4 \
  --vpc-zone-identifier "subnet-0123456789abcdef0,subnet-abcdef0123456789"

# EXPLICATION PARAMÈTRES:

# --auto-scaling-group-name
#   = Nom unique du ASG

# --launch-template LaunchTemplateName=web-server-template,Version='$Latest'
#   = Template à utiliser
#   = Version='$Latest' = toujours dernière version
#   = Version='$Default' = version par défaut
#   = Version='1' = version spécifique

# --min-size 2
#   = Nombre MINIMUM d'instances
#   = ASG ne descendra JAMAIS en dessous
#   = Recommandé: Au moins 2 pour HA

# --max-size 10
#   = Nombre MAXIMUM d'instances
#   = ASG ne dépassera JAMAIS
#   = Protection contre facture surprise

# --desired-capacity 4
#   = Nombre d'instances souhaité MAINTENANT
#   = min-size ≤ desired-capacity ≤ max-size
#   = ASG lance/termine instances pour atteindre ce nombre

# --vpc-zone-identifier
#   = Subnets où lancer instances
#   = Format: "subnet1,subnet2,subnet3"
#   = Recommandé: 2+ subnets dans AZs différentes
#   = ASG distribue instances équitablement

# RÉSULTAT:
# ASG créé et commence à lancer 4 instances immédiatement!

# CRÉER AUTO SCALING GROUP - PRODUCTION COMPLET
════════════════════════════════════════════════════════════════════════════════

aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name production-web-asg \
  --launch-template LaunchTemplateId=lt-0123456789abcdef0,Version='$Latest' \
  --min-size 2 \
  --max-size 20 \
  --desired-capacity 4 \
  --default-cooldown 300 \
  --health-check-type ELB \
  --health-check-grace-period 300 \
  --vpc-zone-identifier "subnet-public1,subnet-public2,subnet-public3" \
  --target-group-arns arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-web-targets/73e2d6bc24d8a067 \
  --termination-policies "OldestInstance" \
  --tags Key=Name,Value=Web-Server-ASG,PropagateAtLaunch=true \
         Key=Environment,Value=Production,PropagateAtLaunch=true

# EXPLICATION PARAMÈTRES AVANCÉS:

# --default-cooldown 300
#   = Période refroidissement (secondes)
#   = Après scaling action, attendre 300s avant autre action
#   = Évite scaling trop agressif
#   = Défaut: 300s (5 minutes)

# --health-check-type ELB
#   = Comment vérifier santé instances
#   = EC2: Seulement si instance répond (basic)
#   = ELB: Load balancer health checks (RECOMMANDÉ)
#   = ELB + EC2: Les deux (plus strict)

# --health-check-grace-period 300
#   = Délai avant vérifier santé (secondes)
#   = Donne temps à instance de démarrer complètement
#   = 300s = 5 minutes (ajuster selon temps démarrage app)
#   = Trop court -> instances terminées prématurément

# --target-group-arns
#   = Target group du Load Balancer
#   = ASG enregistre automatiquement instances
#   = Format ARN complet
#   = Obtenir avec: aws elbv2 describe-target-groups

# --termination-policies "OldestInstance"
#   = Quelle instance terminer en premier lors scale in
#   = Options:
#     * Default: Équilibrer AZs, puis plus vieille launch config
#     * OldestInstance: Plus vieille instance
#     * NewestInstance: Plus récente instance
#     * OldestLaunchTemplate: Instance avec template le plus vieux
#     * AllocationStrategy: Pour Spot (prix)

# --tags
#   = Tags appliqués au ASG ET instances si PropagateAtLaunch=true
#   = Format: Key=Name,Value=Value,PropagateAtLaunch=true

# CRÉER ASG AVEC MULTIPLE INSTANCE TYPES (FLEXIBILITÉ)
════════════════════════════════════════════════════════════════════════════════

aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name flexible-asg \
  --mixed-instances-policy '{
    "LaunchTemplate": {
      "LaunchTemplateSpecification": {
        "LaunchTemplateId": "lt-0123456789abcdef0",
        "Version": "$Latest"
      },
      "Overrides": [
        {"InstanceType": "t3.micro", "WeightedCapacity": "1"},
        {"InstanceType": "t3.small", "WeightedCapacity": "2"},
        {"InstanceType": "t3.medium", "WeightedCapacity": "4"}
      ]
    },
    "InstancesDistribution": {
      "OnDemandPercentageAboveBaseCapacity": 50,
      "SpotInstancePools": 2
    }
  }' \
  --min-size 4 \
  --max-size 20 \
  --desired-capacity 8 \
  --vpc-zone-identifier "subnet-1,subnet-2,subnet-3"

# EXPLICATION MIXED INSTANCES POLICY:

# "Overrides"
#   = Liste types d'instances à utiliser
#   = ASG choisit selon disponibilité et prix

# "WeightedCapacity"
#   = "Poids" de chaque type
#   = t3.micro = 1 unit, t3.small = 2 units, t3.medium = 4 units
#   = Si desired=8: Peut être 8×t3.micro OU 4×t3.small OU 2×t3.medium

# "OnDemandPercentageAboveBaseCapacity": 50
#   = 50% on-demand, 50% spot
#   = Économie avec Spot tout en gardant stabilité

# "SpotInstancePools": 2
#   = Utiliser 2 Spot pools différents
#   = Répartir risque interruption

# LISTER AUTO SCALING GROUPS
════════════════════════════════════════════════════════════════════════════════

# Lister tous les ASGs
aws autoscaling describe-auto-scaling-groups

# Format lisible
aws autoscaling describe-auto-scaling-groups \
  --query 'AutoScalingGroups[*].[AutoScalingGroupName,MinSize,MaxSize,DesiredCapacity,length(Instances)]' \
  --output table

# RÉSULTAT EXEMPLE:
# -------------------------------------------------------------------------
# | my-web-asg         | 2  | 10 | 4  | 4  |
# | production-web-asg | 2  | 20 | 8  | 8  |
# -------------------------------------------------------------------------
# Colonnes: Name, Min, Max, Desired, Current instances

# Obtenir ASG spécifique
aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names my-web-asg

# Voir instances dans ASG
aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names my-web-asg \
  --query 'AutoScalingGroups[0].Instances[*].[InstanceId,LifecycleState,HealthStatus,AvailabilityZone]' \
  --output table

# RÉSULTAT EXEMPLE:
# -------------------------------------------------------------------------
# | i-abc123  | InService  | Healthy   | us-east-1a |
# | i-def456  | InService  | Healthy   | us-east-1b |
# | i-ghi789  | InService  | Healthy   | us-east-1a |
# | i-jkl012  | Pending    | Unknown   | us-east-1b |
# -------------------------------------------------------------------------

# LifecycleState:
# - Pending: En cours de lancement
# - InService: Active et reçoit trafic
# - Terminating: En cours de terminaison
# - Terminated: Terminée

# Voir activités récentes (scaling events)
aws autoscaling describe-scaling-activities \
  --auto-scaling-group-name my-web-asg \
  --max-records 10

# MODIFIER AUTO SCALING GROUP
════════════════════════════════════════════════════════════════════════════════

# Changer capacités (min, max, desired)
aws autoscaling update-auto-scaling-group \
  --auto-scaling-group-name my-web-asg \
  --min-size 3 \
  --max-size 15 \
  --desired-capacity 6

# EXPLICATION:
# Changement immédiat
# Si desired-capacity augmente -> ASG lance nouvelles instances
# Si desired-capacity diminue -> ASG termine instances

# Changer health check
aws autoscaling update-auto-scaling-group \
  --auto-scaling-group-name my-web-asg \
  --health-check-type ELB \
  --health-check-grace-period 600

# Changer launch template
aws autoscaling update-auto-scaling-group \
  --auto-scaling-group-name my-web-asg \
  --launch-template LaunchTemplateId=lt-new123,Version='$Latest'

# [ATTENTION] IMPORTANT:
# Changement launch template n'affecte PAS instances existantes!
# Seulement nouvelles instances utilisent nouveau template
# Pour mettre à jour instances existantes:
#   1. Terminer anciennes instances manuellement
#   2. Utiliser instance refresh (voir section dédiée)

# Activer métriques détaillées
aws autoscaling enable-metrics-collection \
  --auto-scaling-group-name my-web-asg \
  --granularity "1Minute" \
  --metrics GroupMinSize GroupMaxSize GroupDesiredCapacity GroupInServiceInstances

# EXPLICATION:
# Envoie métriques CloudWatch toutes les 1 minute
# Utile pour monitoring et alarmes

# DÉFINIR CAPACITÉ MANUELLE (BYPASS SCALING)
════════════════════════════════════════════════════════════════════════════════

# Forcer nombre d'instances spécifique
aws autoscaling set-desired-capacity \
  --auto-scaling-group-name my-web-asg \
  --desired-capacity 10

# EXPLICATION:
# Ignore scaling policies temporairement
# ASG lance/termine instances pour atteindre 10
# Scaling policies peuvent changer ça après

# Forcer desired capacity (ignorer cooldown)
aws autoscaling set-desired-capacity \
  --auto-scaling-group-name my-web-asg \
  --desired-capacity 10 \
  --honor-cooldown

# EXPLICATION:
# --honor-cooldown = Respecter cooldown period
# Sans = Changer immédiatement (ignorer cooldown)

# SUSPEND/RESUME PROCESSUS ASG
════════════════════════════════════════════════════════════════════════════════

# Suspendre ALL processus (ex: pendant maintenance)
aws autoscaling suspend-processes \
  --auto-scaling-group-name my-web-asg

# EXPLICATION:
# ASG arrête toutes actions automatiques:
# - Pas de nouveau lancement
# - Pas de terminaison
# - Pas de health checks
# Instances existantes continuent de tourner

# Suspendre processus spécifiques
aws autoscaling suspend-processes \
  --auto-scaling-group-name my-web-asg \
  --scaling-processes Launch Terminate HealthCheck

# PROCESSUS DISPONIBLES:
# - Launch: Lancer nouvelles instances
# - Terminate: Terminer instances
# - HealthCheck: Vérifier santé instances
# - ReplaceUnhealthy: Remplacer instances malades
# - AZRebalance: Équilibrer entre AZs
# - AlarmNotification: Réagir aux alarmes CloudWatch
# - ScheduledActions: Actions planifiées
# - AddToLoadBalancer: Enregistrer dans LB

# Reprendre processus
aws autoscaling resume-processes \
  --auto-scaling-group-name my-web-asg

# Reprendre processus spécifiques
aws autoscaling resume-processes \
  --auto-scaling-group-name my-web-asg \
  --scaling-processes Launch Terminate

# DÉTACHER INSTANCES DU ASG
════════════════════════════════════════════════════════════════════════════════

# Détacher instance (garder instance en cours)
aws autoscaling detach-instances \
  --instance-ids i-0123456789abcdef0 \
  --auto-scaling-group-name my-web-asg \
  --no-should-decrement-desired-capacity

# EXPLICATION:
# Instance retirée du ASG mais continue de tourner
# --no-should-decrement-desired-capacity
#   = ASG lance nouvelle instance pour remplacer
# --should-decrement-desired-capacity
#   = Desired capacity diminue (pas de remplacement)

# USE CASE:
# Debugging instance problématique sans la terminer
# Migrer instance hors ASG

# ATTACHER INSTANCE EXISTANTE AU ASG
════════════════════════════════════════════════════════════════════════════════

# Attacher instance EC2 existante
aws autoscaling attach-instances \
  --instance-ids i-0123456789abcdef0 \
  --auto-scaling-group-name my-web-asg

# EXPLICATION:
# Instance doit être dans état "running"
# Instance doit être dans même VPC/subnets que ASG
# Desired capacity augmente automatiquement

# TERMINER INSTANCE DANS ASG
════════════════════════════════════════════════════════════════════════════════

# Terminer instance spécifique
aws autoscaling terminate-instance-in-auto-scaling-group \
  --instance-id i-0123456789abcdef0 \
  --should-decrement-desired-capacity

# EXPLICATION:
# --should-decrement-desired-capacity
#   = Diminuer desired capacity (pas de remplacement)
#   = Use case: Scale in manuellement

# --no-should-decrement-desired-capacity
#   = Garder desired capacity (lancer nouvelle instance)
#   = Use case: Remplacer instance problématique

# METTRE INSTANCE EN STANDBY
════════════════════════════════════════════════════════════════════════════════

# Mettre instance en standby (temporaire)
aws autoscaling enter-standby \
  --instance-ids i-0123456789abcdef0 \
  --auto-scaling-group-name my-web-asg \
  --should-decrement-desired-capacity

# EXPLICATION:
# Instance reste en cours mais:
# - Retirée du Load Balancer (pas de trafic)
# - Pas de health checks
# - Compte pas dans desired capacity
# Parfait pour: Updates, debugging, maintenance

# Sortir du standby
aws autoscaling exit-standby \
  --instance-ids i-0123456789abcdef0 \
  --auto-scaling-group-name my-web-asg

# EXPLICATION:
# Instance réintégrée:
# - Enregistrée dans Load Balancer
# - Health checks reprennent
# - Compte dans desired capacity

# INSTANCE REFRESH - METTRE À JOUR INSTANCES PROGRESSIVEMENT
════════════════════════════════════════════════════════════════════════════════

# Remplacer toutes instances avec nouveau launch template
aws autoscaling start-instance-refresh \
  --auto-scaling-group-name my-web-asg \
  --preferences '{
    "MinHealthyPercentage": 90,
    "InstanceWarmup": 300
  }'

# EXPLICATION:
# Remplace instances progressivement (rolling update)
# Pas d'interruption service

# "MinHealthyPercentage": 90
#   = Garder au moins 90% instances healthy pendant refresh
#   = Si 10 instances: Remplace max 1 à la fois

# "InstanceWarmup": 300
#   = Attendre 300s avant considérer nouvelle instance ready
#   = Donne temps à l'application de démarrer

# COMMENT ÇA MARCHE:
# 1. ASG termine 1 instance (10%)
# 2. Lance nouvelle instance avec nouveau template
# 3. Attend 300s (warmup)
# 4. Vérifie health check
# 5. Si healthy -> continue avec instance suivante
# 6. Si unhealthy -> rollback

# Voir statut instance refresh
aws autoscaling describe-instance-refreshes \
  --auto-scaling-group-name my-web-asg

# Annuler instance refresh en cours
aws autoscaling cancel-instance-refresh \
  --auto-scaling-group-name my-web-asg

# SUPPRIMER AUTO SCALING GROUP
════════════════════════════════════════════════════════════════════════════════

# Supprimer ASG (termine toutes instances)
aws autoscaling delete-auto-scaling-group \
  --auto-scaling-group-name my-web-asg \
  --force-delete

# EXPLICATION:
# --force-delete
#   = Forcer suppression même si instances en cours
#   = Sans ce flag: Doit mettre min-size et desired-capacity à 0 d'abord

# Méthode "graceful" (recommandée):
# 1. Mettre capacités à 0
aws autoscaling update-auto-scaling-group \
  --auto-scaling-group-name my-web-asg \
  --min-size 0 \
  --max-size 0 \
  --desired-capacity 0

# 2. Attendre terminaison instances
# 3. Supprimer ASG
aws autoscaling delete-auto-scaling-group \
  --auto-scaling-group-name my-web-asg


═══════════════════════════════════════════════════════════════════════════════
[OK] SCALING POLICIES - RÈGLES DE SCALING
═══════════════════════════════════════════════════════════════════════════════

SCALING POLICY = "Règle qui dit QUAND et COMBIEN scaler"

TYPES DE POLICIES:
1. TARGET TRACKING -> "Maintenir métrique à valeur cible" (RECOMMANDÉ)
2. STEP SCALING -> "Scaler par paliers selon métrique"
3. SIMPLE SCALING -> "Scaler montant fixe" (DÉPRÉCIÉ)
4. SCHEDULED SCALING -> "Scaler à heures précises"
5. PREDICTIVE SCALING -> "Scaler selon prédictions ML"

COMPARAISON:
┌──────────────────────┬─────────────────────┬────────────────────┐
│                      │  Target Tracking    │   Step Scaling     │
├──────────────────────┼─────────────────────┼────────────────────┤
│ Simplicité           │  [OK] Très simple     │  [ATTENTION] Complexe       │
│ Cas d'usage          │  La plupart         │  Cas spéciaux      │
│ Précision            │  [OK] Bonne           │  [OK] Meilleure      │
│ Recommandation       │  [OK] Commencer ici   │  Avancé            │
└──────────────────────┴─────────────────────┴────────────────────┘

# TARGET TRACKING SCALING - LE PLUS SIMPLE (RECOMMANDÉ)
════════════════════════════════════════════════════════════════════════════════

TARGET TRACKING = "Maintenir métrique à valeur cible"

EXEMPLE:
- Métrique: CPU utilization
- Cible: 70%
- ASG scale out si CPU > 70%
- ASG scale in si CPU < 70%

AVANTAGES:
[OK] Super simple (1 seule policy)
[OK] AWS gère scaling up ET down automatiquement
[OK] Évite flapping (oscillations)
[OK] Recommandé par AWS

# Policy basée sur CPU (plus commun)
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name my-web-asg \
  --policy-name target-tracking-cpu \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ASGAverageCPUUtilization"
    },
    "TargetValue": 70.0
  }'

# EXPLICATION:

# --policy-type TargetTrackingScaling
#   = Type target tracking

# "PredefinedMetricSpecification"
#   = Utiliser métrique prédéfinie AWS
#   = Options disponibles:
#     * ASGAverageCPUUtilization (CPU moyen)
#     * ASGAverageNetworkIn (trafic réseau entrant)
#     * ASGAverageNetworkOut (trafic réseau sortant)
#     * ALBRequestCountPerTarget (requêtes par instance)

# "TargetValue": 70.0
#   = Valeur cible (70% CPU)
#   = ASG ajuste pour maintenir cette valeur

# COMMENT ÇA MARCHE:
# - CPU moyen = 80% -> Scale out (ajouter instances)
# - CPU moyen = 60% -> Scale in (retirer instances)
# - CPU moyen = 70% -> Rien faire (parfait!)

# RÉSULTAT:
# {
#   "PolicyARN": "arn:aws:autoscaling:us-east-1:123456789012:scalingPolicy:...",
#   "Alarms": [
#     {"AlarmName": "TargetTracking-my-web-asg-AlarmHigh-..."},
#     {"AlarmName": "TargetTracking-my-web-asg-AlarmLow-..."}
#   ]
# }

# AWS crée automatiquement 2 CloudWatch Alarms:
# - AlarmHigh: Déclenche scale out
# - AlarmLow: Déclenche scale in

# Policy basée sur requêtes ALB (Application Load Balancer)
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name my-web-asg \
  --policy-name target-tracking-alb \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ALBRequestCountPerTarget",
      "ResourceLabel": "app/my-load-balancer/50dc6c495c0c9188/targetgroup/my-targets/73e2d6bc24d8a067"
    },
    "TargetValue": 1000.0
  }'

# EXPLICATION:

# "ALBRequestCountPerTarget"
#   = Nombre requêtes par instance/minute

# "ResourceLabel"
#   = Identifiant ALB + Target Group
#   = Format: app/LB_NAME/LB_ID/targetgroup/TG_NAME/TG_ID
#   = Obtenir avec: aws elbv2 describe-load-balancers

# "TargetValue": 1000.0
#   = 1000 requêtes/instance/minute
#   = Si plus -> scale out
#   = Si moins -> scale in

# Policy basée sur métrique custom
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name my-web-asg \
  --policy-name target-tracking-custom \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "CustomizedMetricSpecification": {
      "MetricName": "QueueDepth",
      "Namespace": "MyApp",
      "Statistic": "Average"
    },
    "TargetValue": 100.0
  }'

# EXPLICATION:
# Utiliser métrique CloudWatch custom
# Exemple: Queue depth SQS, Memory utilization, etc.

# Options avancées Target Tracking
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name my-web-asg \
  --policy-name target-tracking-advanced \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ASGAverageCPUUtilization"
    },
    "TargetValue": 70.0,
    "ScaleInCooldown": 300,
    "ScaleOutCooldown": 60,
    "DisableScaleIn": false
  }'

# EXPLICATION OPTIONS:

# "ScaleInCooldown": 300
#   = Attendre 300s après scale in avant autre scale in
#   = Évite scale in trop agressif
#   = Défaut: 300s

# "ScaleOutCooldown": 60
#   = Attendre 60s après scale out avant autre scale out
#   = Scale out peut être plus rapide que scale in
#   = Défaut: 60s

# "DisableScaleIn": false
#   = Permettre scale in (true = seulement scale out)
#   = Use case: Black Friday (jamais scale in)

# STEP SCALING - SCALING PAR PALIERS
════════════════════════════════════════════════════════════════════════════════

STEP SCALING = "Scaler différemment selon gravité"

EXEMPLE:
- CPU 70-80% -> Ajouter 1 instance
- CPU 80-90% -> Ajouter 2 instances
- CPU > 90% -> Ajouter 4 instances

QUAND UTILISER:
- Besoin contrôle précis
- Patterns complexes
- Réaction plus rapide que Target Tracking

# Créer CloudWatch Alarm d'abord
aws cloudwatch put-metric-alarm \
  --alarm-name high-cpu-alarm \
  --alarm-description "CPU above 70%" \
  --metric-name CPUUtilization \
  --namespace AWS/EC2 \
  --statistic Average \
  --period 60 \
  --evaluation-periods 2 \
  --threshold 70 \
  --comparison-operator GreaterThanThreshold \
  --dimensions Name=AutoScalingGroupName,Value=my-web-asg

# EXPLICATION ALARM:
# --period 60 = Vérifier chaque 60 secondes
# --evaluation-periods 2 = 2 périodes consécutives > 70%
# --threshold 70 = Seuil 70%
# Résultat: Alarme si CPU > 70% pendant 2 minutes

# Créer Step Scaling Policy
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name my-web-asg \
  --policy-name scale-out-steps \
  --policy-type StepScaling \
  --adjustment-type PercentChangeInCapacity \
  --metric-aggregation-type Average \
  --step-adjustments '[
    {
      "MetricIntervalLowerBound": 0,
      "MetricIntervalUpperBound": 10,
      "ScalingAdjustment": 10
    },
    {
      "MetricIntervalLowerBound": 10,
      "MetricIntervalUpperBound": 20,
      "ScalingAdjustment": 20
    },
    {
      "MetricIntervalLowerBound": 20,
      "ScalingAdjustment": 30
    }
  ]' \
  --cooldown 300

# EXPLICATION:

# --adjustment-type PercentChangeInCapacity
#   = Ajustement en pourcentage
#   = Options:
#     * ChangeInCapacity: Nombre absolu (ex: +2 instances)
#     * PercentChangeInCapacity: Pourcentage (ex: +20%)
#     * ExactCapacity: Nombre exact (ex: 10 instances)

# --metric-aggregation-type Average
#   = Comment agréger métrique
#   = Average, Minimum, Maximum

# STEP ADJUSTMENTS:
# Seuil alarme = 70% CPU

# Step 1: CPU 70-80% (breach de 0-10%)
#   -> +10% instances
#   -> Si 10 instances -> 11 instances

# Step 2: CPU 80-90% (breach de 10-20%)
#   -> +20% instances
#   -> Si 10 instances -> 12 instances

# Step 3: CPU > 90% (breach > 20%)
#   -> +30% instances
#   -> Si 10 instances -> 13 instances

# "MetricIntervalLowerBound": 0
#   = Borne inférieure relative au seuil
#   = 0 = exactement au seuil (70%)

# "MetricIntervalUpperBound": 10
#   = Borne supérieure relative au seuil
#   = 10 = seuil + 10% (80%)

# "ScalingAdjustment": 10
#   = Montant ajustement (10%)

# Lier Alarm et Policy
aws cloudwatch put-metric-alarm \
  --alarm-name high-cpu-alarm \
  --alarm-actions arn:aws:autoscaling:us-east-1:123456789012:scalingPolicy:...

# Step Scaling pour scale IN
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name my-web-asg \
  --policy-name scale-in-steps \
  --policy-type StepScaling \
  --adjustment-type PercentChangeInCapacity \
  --step-adjustments '[
    {
      "MetricIntervalUpperBound": 0,
      "MetricIntervalLowerBound": -10,
      "ScalingAdjustment": -10
    },
    {
      "MetricIntervalUpperBound": -10,
      "ScalingAdjustment": -20
    }
  ]'

# EXPLICATION SCALE IN:
# Seuil = 30% CPU

# Step 1: CPU 20-30% (breach de -10 à 0)
#   -> -10% instances

# Step 2: CPU < 20% (breach < -10)
#   -> -20% instances

# SIMPLE SCALING - DÉPRÉCIÉ (NE PAS UTILISER)
════════════════════════════════════════════════════════════════════════════════

# [ATTENTION] AWS recommande Target Tracking ou Step Scaling
# Simple Scaling conservé pour compatibilité seulement

aws autoscaling put-scaling-policy \
  --auto-scaling-group-name my-web-asg \
  --policy-name simple-scale-out \
  --scaling-adjustment 1 \
  --adjustment-type ChangeInCapacity \
  --cooldown 300

# PROBLÈMES SIMPLE SCALING:
# - Cooldown période bloque TOUS les scaling
# - Pas de scale différencié selon gravité
# - Moins réactif

# SCHEDULED SCALING - SCALING PLANIFIÉ
════════════════════════════════════════════════════════════════════════════════

SCHEDULED SCALING = "Scaler à heures précises"

USE CASES:
- Heures de pointe prévisibles
- Heures de bureau (9h-18h)
- Weekend vs semaine
- Black Friday, événements

# Scaler le matin (augmenter capacité)
aws autoscaling put-scheduled-update-group-action \
  --auto-scaling-group-name my-web-asg \
  --scheduled-action-name scale-up-morning \
  --recurrence "0 9 * * MON-FRI" \
  --min-size 4 \
  --max-size 20 \
  --desired-capacity 8

# EXPLICATION:

# --recurrence "0 9 * * MON-FRI"
#   = Format Cron
#   = 0 = minute 0
#   = 9 = 9h du matin
#   = * = tous les jours du mois
#   = * = tous les mois
#   = MON-FRI = Lundi à Vendredi
#   = Résultat: Tous les jours ouvrables à 9h

# FORMAT CRON:
# minute hour day-of-month month day-of-week
# 0-59   0-23 1-31         1-12  0-6 (0=dimanche)

# Capacités appliquées à cette heure:
# - min-size: 4
# - max-size: 20
# - desired-capacity: 8
# ASG lance instances pour atteindre 8

# Scaler le soir (réduire capacité)
aws autoscaling put-scheduled-update-group-action \
  --auto-scaling-group-name my-web-asg \
  --scheduled-action-name scale-down-evening \
  --recurrence "0 18 * * MON-FRI" \
  --min-size 2 \
  --max-size 10 \
  --desired-capacity 2

# EXPLICATION:
# Tous les jours à 18h
# Réduire à 2 instances (économie nuit)

# Action unique (pas récurrente)
aws autoscaling put-scheduled-update-group-action \
  --auto-scaling-group-name my-web-asg \
  --scheduled-action-name black-friday-prep \
  --start-time "2024-11-29T00:00:00Z" \
  --min-size 10 \
  --max-size 50 \
  --desired-capacity 20

# EXPLICATION:
# --start-time = Date/heure précise (format ISO 8601)
# Action unique le 29 novembre 2024 à minuit UTC
# Préparer pour Black Friday

# Avec end time (fenêtre temporelle)
aws autoscaling put-scheduled-update-group-action \
  --auto-scaling-group-name my-web-asg \
  --scheduled-action-name weekend-scale-down \
  --recurrence "0 0 * * SAT" \
  --start-time "2024-01-01T00:00:00Z" \
  --end-time "2024-12-31T23:59:59Z" \
  --min-size 1 \
  --max-size 5 \
  --desired-capacity 1

# EXPLICATION:
# Tous les samedis à minuit
# Mais seulement pendant 2024
# Après 2024 -> action ne s'exécute plus

# EXEMPLES CRON PATTERNS
════════════════════════════════════════════════════════════════════════════════

# Tous les jours à 8h
"0 8 * * *"

# Tous les lundis à 9h
"0 9 * * MON"

# Premier jour du mois à minuit
"0 0 1 * *"

# Toutes les heures
"0 * * * *"

# Toutes les 30 minutes
"0,30 * * * *"

# Weekend (samedi et dimanche) à 10h
"0 10 * * SAT,SUN"

# Dernier jour du mois (tricky - utiliser Lambda)
# Cron ne supporte pas directement

# LISTER SCHEDULED ACTIONS
════════════════════════════════════════════════════════════════════════════════

# Lister toutes les scheduled actions
aws autoscaling describe-scheduled-actions \
  --auto-scaling-group-name my-web-asg

# Format lisible
aws autoscaling describe-scheduled-actions \
  --auto-scaling-group-name my-web-asg \
  --query 'ScheduledUpdateGroupActions[*].[ScheduledActionName,Recurrence,MinSize,MaxSize,DesiredCapacity]' \
  --output table

# SUPPRIMER SCHEDULED ACTION
════════════════════════════════════════════════════════════════════════════════

aws autoscaling delete-scheduled-action \
  --auto-scaling-group-name my-web-asg \
  --scheduled-action-name scale-up-morning

# PREDICTIVE SCALING - MACHINE LEARNING
════════════════════════════════════════════════════════════════════════════════

PREDICTIVE SCALING = "AWS ML prédit charge future et scale proactivement"

AVANTAGES:
[OK] Scale AVANT pic de charge (pas après)
[OK] ML apprend patterns historiques
[OK] Combine avec Target Tracking

PRÉREQUIS:
- Au moins 24h données historiques
- Patterns répétitifs (journaliers, hebdomadaires)

aws autoscaling put-scaling-policy \
  --auto-scaling-group-name my-web-asg \
  --policy-name predictive-scaling \
  --policy-type PredictiveScaling \
  --predictive-scaling-configuration '{
    "MetricSpecifications": [
      {
        "TargetValue": 70.0,
        "PredefinedMetricPairSpecification": {
          "PredefinedMetricType": "ASGCPUUtilization"
        }
      }
    ],
    "Mode": "ForecastAndScale",
    "SchedulingBufferTime": 600
  }'

# EXPLICATION:

# "Mode": "ForecastAndScale"
#   = Prédire ET scaler automatiquement
#   = Options:
#     * ForecastOnly: Seulement prédire (pas scaler)
#     * ForecastAndScale: Prédire + scaler

# "SchedulingBufferTime": 600
#   = Scaler 600s (10 min) AVANT pic prédit
#   = Donne temps aux instances de démarrer

# COMMENT ÇA MARCHE:
# 1. ML analyse patterns CPU dernières semaines
# 2. Prédit: "Demain 12h, CPU va monter à 85%"
# 3. À 11h50, ASG scale out proactivement
# 4. À 12h, instances prêtes pour charge

# LISTER SCALING POLICIES
════════════════════════════════════════════════════════════════════════════════

# Lister toutes les policies d'un ASG
aws autoscaling describe-policies \
  --auto-scaling-group-name my-web-asg

# Format lisible
aws autoscaling describe-policies \
  --auto-scaling-group-name my-web-asg \
  --query 'ScalingPolicies[*].[PolicyName,PolicyType,Enabled]' \
  --output table

# RÉSULTAT EXEMPLE:
# -------------------------------------------------------------------------
# | target-tracking-cpu     | TargetTrackingScaling | true  |
# | scale-out-steps         | StepScaling           | true  |
# | predictive-scaling      | PredictiveScaling     | true  |
# -------------------------------------------------------------------------

# Obtenir policy spécifique
aws autoscaling describe-policies \
  --auto-scaling-group-name my-web-asg \
  --policy-names target-tracking-cpu

# SUPPRIMER SCALING POLICY
════════════════════════════════════════════════════════════════════════════════

aws autoscaling delete-policy \
  --auto-scaling-group-name my-web-asg \
  --policy-name target-tracking-cpu

# EXPLICATION:
# CloudWatch Alarms associées supprimées automatiquement


═══════════════════════════════════════════════════════════════════════════════
[OK] LIFECYCLE HOOKS - ACTIONS PERSONNALISÉES
═══════════════════════════════════════════════════════════════════════════════

LIFECYCLE HOOK = "Pause scaling pour exécuter actions custom"

USE CASES:
- Enregistrer instance dans système externe
- Télécharger données/config avant servir trafic
- Sauvegarder logs avant terminer instance
- Dé-enregistrer de DNS custom

ÉTATS LIFECYCLE:
1. Pending -> Hook -> InService (lancement)
2. InService -> Hook -> Terminating (terminaison)

# Créer Lifecycle Hook (au lancement)
aws autoscaling put-lifecycle-hook \
  --lifecycle-hook-name setup-hook \
  --auto-scaling-group-name my-web-asg \
  --lifecycle-transition autoscaling:EC2_INSTANCE_LAUNCHING \
  --default-result CONTINUE \
  --heartbeat-timeout 300 \
  --notification-target-arn arn:aws:sns:us-east-1:123456789012:my-topic

# EXPLICATION:

# --lifecycle-transition
#   = Quand déclencher hook
#   = Options:
#     * autoscaling:EC2_INSTANCE_LAUNCHING (au lancement)
#     * autoscaling:EC2_INSTANCE_TERMINATING (avant terminaison)

# --default-result CONTINUE
#   = Que faire si timeout
#   = Options:
#     * CONTINUE: Continuer (lancer/terminer instance)
#     * ABANDON: Abandonner (ne pas lancer/terminer)

# --heartbeat-timeout 300
#   = Temps maximum pour compléter action (secondes)
#   = Après 300s -> default-result appliqué

# --notification-target-arn
#   = SNS topic pour notifications
#   = Lambda peut écouter et exécuter actions

# COMMENT ÇA MARCHE:
# 1. ASG décide lancer nouvelle instance
# 2. Instance lancée mais état = Pending:Wait
# 3. SNS notification envoyée
# 4. Lambda/script exécute actions (install software, etc.)
# 5. Script appelle complete-lifecycle-action
# 6. Instance passe à InService

# Lifecycle Hook (avant terminaison)
aws autoscaling put-lifecycle-hook \
  --lifecycle-hook-name cleanup-hook \
  --auto-scaling-group-name my-web-asg \
  --lifecycle-transition autoscaling:EC2_INSTANCE_TERMINATING \
  --default-result CONTINUE \
  --heartbeat-timeout 600

# USE CASE:
# Sauvegarder logs vers S3 avant terminer instance

# Compléter Lifecycle Action (depuis Lambda/script)
aws autoscaling complete-lifecycle-action \
  --lifecycle-hook-name setup-hook \
  --auto-scaling-group-name my-web-asg \
  --lifecycle-action-result CONTINUE \
  --instance-id i-0123456789abcdef0

# EXPLICATION:
# --lifecycle-action-result
#   = CONTINUE: Instance passe à InService
#   = ABANDON: Instance terminée

# Envoyer heartbeat (étendre timeout)
aws autoscaling record-lifecycle-action-heartbeat \
  --lifecycle-hook-name setup-hook \
  --auto-scaling-group-name my-web-asg \
  --instance-id i-0123456789abcdef0

# EXPLICATION:
# Réinitialise timeout
# Use case: Action prend plus de temps que prévu

# Lister Lifecycle Hooks
aws autoscaling describe-lifecycle-hooks \
  --auto-scaling-group-name my-web-asg

# Supprimer Lifecycle Hook
aws autoscaling delete-lifecycle-hook \
  --lifecycle-hook-name setup-hook \
  --auto-scaling-group-name my-web-asg


═══════════════════════════════════════════════════════════════════════════════
[OK] WARM POOLS - INSTANCES PRÉ-INITIALISÉES
═══════════════════════════════════════════════════════════════════════════════

WARM POOL = "Pool d'instances pré-initialisées prêtes à servir"

PROBLÈME:
- Lancer instance = 2-5 minutes
- Pendant ce temps = capacité insuffisante
- Applications avec long startup = pire

SOLUTION: WARM POOL
- Instances pré-lancées en état "stopped" ou "hibernated"
- Quand scaling needed -> démarrer instance (30 secondes)
- Beaucoup plus rapide!

ÉTATS:
- Stopped: Instance arrêtée (pas de coût compute)
- Hibernated: RAM sauvegardée sur disque (startup ultra-rapide)
- Running: Instance en cours (coût normal)

# Créer Warm Pool
aws autoscaling put-warm-pool \
  --auto-scaling-group-name my-web-asg \
  --max-group-prepared-capacity 10 \
  --min-size 2 \
  --pool-state Stopped

# EXPLICATION:

# --max-group-prepared-capacity 10
#   = Maximum instances (ASG + Warm Pool)
#   = Si ASG a 6 instances -> Warm Pool max 4
#   = Limite coûts totaux

# --min-size 2
#   = Minimum instances dans Warm Pool
#   = Toujours 2 instances prêtes

# --pool-state Stopped
#   = État instances dans pool
#   = Options:
#     * Stopped: Arrêtées (coût EBS seulement)
#     * Hibernated: Hibernées (RAM -> disque, startup 30s)
#     * Running: En cours (coût normal, pas recommandé)

# COMMENT ÇA MARCHE:
# 1. Warm Pool: 2 instances stopped
# 2. ASG: 4 instances InService
# 3. Scaling policy déclenche: Need +2 instances
# 4. ASG démarre 2 instances du Warm Pool (~30s)
# 5. ASG lance 2 nouvelles instances pour Warm Pool

# Warm Pool avec Hibernation (plus rapide)
aws autoscaling put-warm-pool \
  --auto-scaling-group-name my-web-asg \
  --max-group-prepared-capacity 15 \
  --min-size 3 \
  --pool-state Hibernated \
  --instance-reuse-policy '{
    "ReuseOnScaleIn": true
  }'

# EXPLICATION HIBERNATION:

# --pool-state Hibernated
#   = RAM sauvegardée sur EBS
#   = Démarrage: 30 secondes (vs 2-5 minutes)
#   = Application déjà chargée en RAM!
#   = [ATTENTION] Nécessite hibernation activée dans launch template

# "ReuseOnScaleIn": true
#   = Quand scale in, instances retournent au Warm Pool
#   = Si false, instances terminées complètement
#   = true = économie (réutiliser instances)

# COÛTS WARM POOL:
# Stopped: EBS seulement (~$10/mois par instance)
# Hibernated: EBS + snapshot RAM (~$15/mois)
# Running: Coût normal (~$7-50/mois selon type)

# Voir Warm Pool
aws autoscaling describe-warm-pool \
  --auto-scaling-group-name my-web-asg

# Supprimer Warm Pool
aws autoscaling delete-warm-pool \
  --auto-scaling-group-name my-web-asg

# [ATTENTION] Instances dans pool sont terminées


═══════════════════════════════════════════════════════════════════════════════
[OK] MONITORING & MÉTRIQUES
═══════════════════════════════════════════════════════════════════════════════

# MÉTRIQUES AUTO SCALING (CloudWatch)
════════════════════════════════════════════════════════════════════════════════

# Activer métriques détaillées
aws autoscaling enable-metrics-collection \
  --auto-scaling-group-name my-web-asg \
  --granularity "1Minute" \
  --metrics GroupMinSize GroupMaxSize GroupDesiredCapacity GroupInServiceInstances GroupPendingInstances GroupTerminatingInstances

# MÉTRIQUES DISPONIBLES:
# - GroupMinSize: Min size configuré
# - GroupMaxSize: Max size configuré
# - GroupDesiredCapacity: Desired capacity actuel
# - GroupInServiceInstances: Instances en service
# - GroupPendingInstances: Instances en cours de lancement
# - GroupStandbyInstances: Instances en standby
# - GroupTerminatingInstances: Instances en terminaison
# - GroupTotalInstances: Total toutes instances

# Voir métriques dans CloudWatch
aws cloudwatch get-metric-statistics \
  --namespace AWS/AutoScaling \
  --metric-name GroupInServiceInstances \
  --dimensions Name=AutoScalingGroupName,Value=my-web-asg \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-16T00:00:00Z \
  --period 3600 \
  --statistics Average,Maximum,Minimum

# Désactiver métriques
aws autoscaling disable-metrics-collection \
  --auto-scaling-group-name my-web-asg

# CRÉER CLOUDWATCH DASHBOARD
════════════════════════════════════════════════════════════════════════════════

# Dashboard pour monitoring ASG
aws cloudwatch put-dashboard \
  --dashboard-name AutoScaling-Dashboard \
  --dashboard-body '{
    "widgets": [
      {
        "type": "metric",
        "properties": {
          "metrics": [
            ["AWS/AutoScaling", "GroupDesiredCapacity", {"stat": "Average"}],
            [".", "GroupInServiceInstances", {"stat": "Average"}],
            [".", "GroupMinSize", {"stat": "Average"}],
            [".", "GroupMaxSize", {"stat": "Average"}]
          ],
          "period": 300,
          "stat": "Average",
          "region": "us-east-1",
          "title": "Auto Scaling Group Capacity"
        }
      },
      {
        "type": "metric",
        "properties": {
          "metrics": [
            ["AWS/EC2", "CPUUtilization", {"stat": "Average"}]
          ],
          "period": 300,
          "stat": "Average",
          "region": "us-east-1",
          "title": "Average CPU Utilization"
        }
      }
    ]
  }'

# ALARMES CLOUDWATCH
════════════════════════════════════════════════════════════════════════════════

# Alarme si ASG atteint max capacity
aws cloudwatch put-metric-alarm \
  --alarm-name asg-at-max-capacity \
  --alarm-description "ASG reached maximum capacity" \
  --metric-name GroupInServiceInstances \
  --namespace AWS/AutoScaling \
  --statistic Average \
  --period 60 \
  --evaluation-periods 2 \
  --threshold 19 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --dimensions Name=AutoScalingGroupName,Value=my-web-asg \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:ops-alerts

# EXPLICATION:
# Alerte équipe si ASG proche de max (20)
# Permet augmenter max-size si nécessaire

# Alarme si instances unhealthy
aws cloudwatch put-metric-alarm \
  --alarm-name unhealthy-instances \
  --alarm-description "Unhealthy instances detected" \
  --metric-name UnhealthyHostCount \
  --namespace AWS/ApplicationELB \
  --statistic Average \
  --period 60 \
  --evaluation-periods 2 \
  --threshold 1 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:ops-alerts

# VOIR ACTIVITÉS SCALING
════════════════════════════════════════════════════════════════════════════════

# Voir historique scaling
aws autoscaling describe-scaling-activities \
  --auto-scaling-group-name my-web-asg \
  --max-records 20

# Format lisible
aws autoscaling describe-scaling-activities \
  --auto-scaling-group-name my-web-asg \
  --max-records 10 \
  --query 'Activities[*].[StartTime,StatusCode,Description]' \
  --output table

# RÉSULTAT EXEMPLE:
# -------------------------------------------------------------------------
# | 2024-01-15T14:30:00.000Z | Successful | Launching a new EC2 instance |
# | 2024-01-15T12:15:00.000Z | Successful | Terminating EC2 instance     |
# | 2024-01-15T09:00:00.000Z | Successful | Launching a new EC2 instance |
# -------------------------------------------------------------------------

# Filtrer par période
aws autoscaling describe-scaling-activities \
  --auto-scaling-group-name my-web-asg \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-16T00:00:00Z


═══════════════════════════════════════════════════════════════════════════════
[OK] INTEGRATION AVEC LOAD BALANCER
═══════════════════════════════════════════════════════════════════════════════

# CRÉER APPLICATION LOAD BALANCER (ALB)
════════════════════════════════════════════════════════════════════════════════

# Créer ALB
aws elbv2 create-load-balancer \
  --name my-web-alb \
  --subnets subnet-public1 subnet-public2 \
  --security-groups sg-alb \
  --scheme internet-facing \
  --type application \
  --ip-address-type ipv4

# Notez LoadBalancerArn: arn:aws:elasticloadbalancing:...

# Créer Target Group
aws elbv2 create-target-group \
  --name my-web-targets \
  --protocol HTTP \
  --port 80 \
  --vpc-id vpc-0123456789abcdef0 \
  --health-check-enabled \
  --health-check-protocol HTTP \
  --health-check-path /health \
  --health-check-interval-seconds 30 \
  --health-check-timeout-seconds 5 \
  --healthy-threshold-count 2 \
  --unhealthy-threshold-count 3

# Notez TargetGroupArn: arn:aws:elasticloadbalancing:...

# Créer Listener
aws elbv2 create-listener \
  --load-balancer-arn arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/app/my-web-alb/... \
  --protocol HTTP \
  --port 80 \
  --default-actions Type=forward,TargetGroupArn=arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-web-targets/...

# ATTACHER ASG À LOAD BALANCER
════════════════════════════════════════════════════════════════════════════════

# Méthode 1: Lors création ASG
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name my-web-asg \
  --launch-template LaunchTemplateName=web-template,Version='$Latest' \
  --min-size 2 \
  --max-size 10 \
  --desired-capacity 4 \
  --vpc-zone-identifier "subnet-1,subnet-2" \
  --target-group-arns arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-web-targets/... \
  --health-check-type ELB \
  --health-check-grace-period 300

# EXPLICATION:
# --target-group-arns = Target group du ALB
# --health-check-type ELB = Utiliser health checks ALB
# --health-check-grace-period 300 = Attendre 5 min avant health check

# Méthode 2: Attacher à ASG existant
aws autoscaling attach-load-balancer-target-groups \
  --auto-scaling-group-name my-web-asg \
  --target-group-arns arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-web-targets/...

# COMMENT ÇA MARCHE:
# 1. ASG lance nouvelle instance
# 2. Instance démarre et exécute user data
# 3. Attendre health-check-grace-period (300s)
# 4. ALB commence health checks (GET /health)
# 5. Si 2 health checks consécutifs OK -> instance "Healthy"
# 6. ALB commence envoyer trafic vers instance
# 7. Si health checks échouent -> instance "Unhealthy"
# 8. ASG termine instance unhealthy et lance remplacement

# Détacher Target Group
aws autoscaling detach-load-balancer-target-groups \
  --auto-scaling-group-name my-web-asg \
  --target-group-arns arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-web-targets/...

# INTEGRATION AVEC CLASSIC LOAD BALANCER (DÉPRÉCIÉ)
════════════════════════════════════════════════════════════════════════════════

# [ATTENTION] Classic Load Balancer déprécié - Utiliser ALB!

# Si legacy system:
aws autoscaling attach-load-balancers \
  --auto-scaling-group-name my-web-asg \
  --load-balancer-names my-classic-lb


═══════════════════════════════════════════════════════════════════════════════
[OK] BEST PRACTICES AUTO SCALING
═══════════════════════════════════════════════════════════════════════════════

# [OK] SIZING & CAPACITIES
════════════════════════════════════════════════════════════════════════════════

1. MIN SIZE
   - Au moins 2 pour haute disponibilité
   - 1 par AZ minimum
   - Jamais 0 en production!

2. MAX SIZE
   - 2-3× desired capacity normal
   - Protection contre facture surprise
   - Exemple: Desired=4 -> Max=10-12

3. DESIRED CAPACITY
   - Basé sur trafic normal
   - Laisser scaling policies ajuster
   - Ne pas modifier manuellement souvent

4. MULTIPLE AZs
   - Minimum 2 AZs (3 recommandé)
   - ASG distribue équitablement
   - Protection contre panne AZ

# [OK] SCALING POLICIES
════════════════════════════════════════════════════════════════════════════════

1. TARGET TRACKING (recommandé)
   - Plus simple à configurer
   - AWS gère complexité
   - Cible: 70% CPU (laisse marge)

2. COOLDOWN PERIODS
   - Scale out: 60-120s (rapide)
   - Scale in: 300-600s (prudent)
   - Évite scaling trop agressif

3. MULTIPLE POLICIES
   - CPU + Requêtes ALB
   - Permet réaction à différents patterns
   - AWS choisit policy la plus agressive

4. SCHEDULED SCALING
   - Pour patterns prévisibles
   - Combine avec dynamic scaling
   - Exemple: Bureau hours, weekend

# [OK] HEALTH CHECKS
════════════════════════════════════════════════════════════════════════════════

1. TYPE: ELB (recommandé)
   - Plus précis que EC2
   - Vérifie application, pas seulement instance
   - Health check path: /health ou /ping

2. GRACE PERIOD
   - 300s (5 min) minimum
   - Plus si application lente à démarrer
   - Trop court -> instances terminées prématurément

3. HEALTH CHECK ALB
   - Interval: 30s
   - Timeout: 5s
   - Healthy threshold: 2
   - Unhealthy threshold: 3
   - Path: Endpoint léger (pas DB query!)

# [OK] LAUNCH TEMPLATE
════════════════════════════════════════════════════════════════════════════════

1. VERSIONING
   - Toujours versionner
   - Tester nouvelle version avant production
   - Garder anciennes versions pour rollback

2. USER DATA
   - Idempotent (peut exécuter plusieurs fois)
   - Logger vers CloudWatch Logs
   - Rapide (< 2 minutes idéal)

3. IAM ROLE
   - Principe moindre privilège
   - Permissions spécifiques à application
   - Pas de credentials hardcodés!

4. SECURITY
   - IMDSv2 required
   - EBS encryption enabled
   - Minimal security groups

# [OK] COST OPTIMIZATION
════════════════════════════════════════════════════════════════════════════════

1. INSTANCE TYPES
   - T3/T3a pour workloads variables (burstable)
   - M5 pour workloads stables
   - C5 pour CPU-intensive
   - Graviton (T4g, M6g) = 20% moins cher!

2. SPOT INSTANCES
   - 50-90% moins cher
   - Mix on-demand + spot
   - Parfait pour stateless apps

3. SCALING POLICIES
   - Scale out rapide, scale in lent
   - Éviter flapping (coûts lancement)
   - Warmup period approprié

4. SCHEDULED ACTIONS
   - Scale down hors heures
   - Exemple: 2 instances nuit, 10 jour
   - Économie: 60-70% pour apps bureau

5. WARM POOL
   - Stopped state = coût EBS seulement
   - Plus rapide que lancer nouvelle instance
   - Économie + performance

# [OK] MONITORING
════════════════════════════════════════════════════════════════════════════════

1. MÉTRIQUES CLÉS
   - GroupDesiredCapacity
   - GroupInServiceInstances
   - CPU Utilization (par instance)
   - Target Response Time (ALB)

2. ALARMES
   - ASG at max capacity
   - Unhealthy instances
   - Frequent scaling (flapping)
   - High CPU sustained

3. LOGS
   - CloudWatch Logs pour user data
   - Scaling activities history
   - Health check failures

# [OK] TESTING
════════════════════════════════════════════════════════════════════════════════

1. LOAD TESTING
   - Vérifier scaling policy fonctionne
   - Temps scale out < 5 minutes
   - Application stable pendant scaling

2. CHAOS ENGINEERING
   - Terminer instances aléatoirement
   - ASG doit remplacer automatiquement
   - Application reste disponible

3. ROLLBACK PLAN
   - Garder anciennes versions launch template
   - Tester rollback avant production
   - Documentation procédure

# [OK] SECURITY
════════════════════════════════════════════════════════════════════════════════

1. NETWORK
   - Instances dans private subnets
   - Load Balancer dans public subnets
   - Security groups restrictifs

2. IAM
   - Instance profile avec permissions minimales
   - Pas de credentials dans user data
   - Rotation credentials régulière

3. ENCRYPTION
   - EBS encryption enabled
   - HTTPS entre ALB et instances
   - Secrets Manager pour credentials


═══════════════════════════════════════════════════════════════════════════════
[OK] TROUBLESHOOTING AUTO SCALING
═══════════════════════════════════════════════════════════════════════════════

# PROBLÈME 1: Instances ne lancent pas
════════════════════════════════════════════════════════════════════════════════

# Vérifier activités scaling
aws autoscaling describe-scaling-activities \
  --auto-scaling-group-name my-web-asg \
  --max-records 5

# CAUSES COMMUNES:
# 1. Launch template invalide (AMI supprimée, security group)
# 2. Quotas EC2 dépassés
# 3. Pas de capacité dans AZ
# 4. IAM role manquant

# SOLUTION:
# Vérifier launch template
aws ec2 describe-launch-template-versions \
  --launch-template-id lt-xxx \
  --versions '$Latest'

# Tester lancer instance manuellement
aws ec2 run-instances \
  --launch-template LaunchTemplateId=lt-xxx


# PROBLÈME 2: Scaling ne se déclenche pas
════════════════════════════════════════════════════════════════════════════════

# Vérifier policies
aws autoscaling describe-policies \
  --auto-scaling-group-name my-web-asg

# Vérifier alarmes CloudWatch
aws cloudwatch describe-alarms \
  --alarm-names TargetTracking-my-web-asg-AlarmHigh-...

# CAUSES:
# 1. Policy désactivée
# 2. Alarme en INSUFFICIENT_DATA
# 3. Cooldown period actif
# 4. ASG at max capacity

# SOLUTION:
# Forcer scaling manuel pour tester
aws autoscaling set-desired-capacity \
  --auto-scaling-group-name my-web-asg \
  --desired-capacity 6


# PROBLÈME 3: Instances terminées immédiatement
════════════════════════════════════════════════════════════════════════════════

# Vérifier health checks
aws autoscaling describe-auto-scaling-groups \
  --auto-scaling-group-names my-web-asg \
  --query 'AutoScalingGroups[0].Instances[*].[InstanceId,HealthStatus,LifecycleState]'

# CAUSES:
# 1. Health check grace period trop court
# 2. Application ne démarre pas assez vite
# 3. Health check endpoint échoue
# 4. Security group bloque health checks

# SOLUTION:
# Augmenter grace period
aws autoscaling update-auto-scaling-group \
  --auto-scaling-group-name my-web-asg \
  --health-check-grace-period 600

# Vérifier health check ALB
aws elbv2 describe-target-health \
  --target-group-arn arn:...


# PROBLÈME 4: Scale in trop agressif
════════════════════════════════════════════════════════════════════════════════

# CAUSE:
# Cooldown trop court ou target value trop bas

# SOLUTION:
# Augmenter scale in cooldown
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name my-web-asg \
  --policy-name target-tracking-cpu \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ASGAverageCPUUtilization"
    },
    "TargetValue": 70.0,
    "ScaleInCooldown": 600
  }'


# PROBLÈME 5: Coûts élevés
════════════════════════════════════════════════════════════════════════════════

# Voir historique scaling
aws autoscaling describe-scaling-activities \
  --auto-scaling-group-name my-web-asg \
  --max-records 50

# CAUSES:
# 1. Flapping (scale out/in répété)
# 2. Max size trop élevé
# 3. Target value trop bas
# 4. Pas de scheduled scale down

# SOLUTIONS:
# 1. Augmenter cooldown periods
# 2. Ajuster target value (60% -> 70%)
# 3. Ajouter scheduled actions (nuit/weekend)
# 4. Utiliser Spot instances


═══════════════════════════════════════════════════════════════════════════════
[OK] EXEMPLE COMPLET - ARCHITECTURE PRODUCTION
═══════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Script création Auto Scaling complet pour production

set -e

# === VARIABLES ===
ASG_NAME="production-web-asg"
REGION="us-east-1"
VPC_ID="vpc-0123456789abcdef0"
SUBNET_1="subnet-public1"
SUBNET_2="subnet-public2"
SUBNET_3="subnet-public3"

echo "=== Création Launch Template ==="

# User data
cat > user-data.sh << 'EOF'
#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd

# Application web
cat > /var/www/html/index.php << 'HTML'
<?php
$instance_id = file_get_contents('http://169.254.169.254/latest/meta-data/instance-id');
$az = file_get_contents('http://169.254.169.254/latest/meta-data/placement/availability-zone');
?>
<!DOCTYPE html>
<html>
<head><title>Auto Scaling Demo</title></head>
<body>
  <h1>Servie par: <?php echo $instance_id; ?></h1>
  <p>Availability Zone: <?php echo $az; ?></p>
  <p>Load: <?php echo sys_getloadavg()[0]; ?></p>
</body>
</html>
HTML

# Health check endpoint
echo "OK" > /var/www/html/health
EOF

USER_DATA=$(base64 -w 0 user-data.sh)

# Créer Launch Template
LT_ID=$(aws ec2 create-launch-template \
  --launch-template-name $ASG_NAME-template \
  --version-description "Production v1" \
  --launch-template-data '{
    "ImageId": "ami-0c55b159cbfafe1f0",
    "InstanceType": "t3.micro",
    "KeyName": "my-key",
    "SecurityGroupIds": ["sg-web123"],
    "UserData": "'$USER_DATA'",
    "IamInstanceProfile": {"Name": "EC2-WebServer-Role"},
    "Monitoring": {"Enabled": true},
    "MetadataOptions": {
      "HttpTokens": "required",
      "HttpPutResponseHopLimit": 1
    },
    "TagSpecifications": [{
      "ResourceType": "instance",
      "Tags": [
        {"Key": "Name", "Value": "Web-Server-ASG"},
        {"Key": "Environment", "Value": "Production"}
      ]
    }]
  }' \
  --query 'LaunchTemplate.LaunchTemplateId' \
  --output text)

echo "Launch Template créé: $LT_ID"

echo "=== Création Auto Scaling Group ==="

# Créer ASG
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name $ASG_NAME \
  --launch-template LaunchTemplateId=$LT_ID,Version='$Latest' \
  --min-size 2 \
  --max-size 20 \
  --desired-capacity 4 \
  --default-cooldown 300 \
  --health-check-type ELB \
  --health-check-grace-period 300 \
  --vpc-zone-identifier "$SUBNET_1,$SUBNET_2,$SUBNET_3" \
  --target-group-arns arn:aws:elasticloadbalancing:$REGION:123456789012:targetgroup/web-targets/... \
  --termination-policies "OldestInstance" \
  --tags Key=Name,Value=Web-Server-ASG,PropagateAtLaunch=true \
         Key=Environment,Value=Production,PropagateAtLaunch=true

echo "ASG créé: $ASG_NAME"

echo "=== Configuration Scaling Policies ==="

# Target Tracking - CPU
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name $ASG_NAME \
  --policy-name target-tracking-cpu \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ASGAverageCPUUtilization"
    },
    "TargetValue": 70.0,
    "ScaleInCooldown": 300,
    "ScaleOutCooldown": 60
  }'

# Target Tracking - ALB Requests
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name $ASG_NAME \
  --policy-name target-tracking-alb \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ALBRequestCountPerTarget",
      "ResourceLabel": "app/my-alb/.../targetgroup/web-targets/..."
    },
    "TargetValue": 1000.0
  }'

echo "=== Configuration Scheduled Actions ==="

# Scale up le matin (heures de bureau)
aws autoscaling put-scheduled-update-group-action \
  --auto-scaling-group-name $ASG_NAME \
  --scheduled-action-name scale-up-morning \
  --recurrence "0 8 * * MON-FRI" \
  --min-size 4 \
  --max-size 20 \
  --desired-capacity 8

# Scale down le soir
aws autoscaling put-scheduled-update-group-action \
  --auto-scaling-group-name $ASG_NAME \
  --scheduled-action-name scale-down-evening \
  --recurrence "0 20 * * MON-FRI" \
  --min-size 2 \
  --max-size 10 \
  --desired-capacity 2

# Scale down weekend
aws autoscaling put-scheduled-update-group-action \
  --auto-scaling-group-name $ASG_NAME \
  --scheduled-action-name scale-down-weekend \
  --recurrence "0 0 * * SAT" \
  --min-size 2 \
  --max-size 5 \
  --desired-capacity 2

echo "=== Activation Métriques ==="

aws autoscaling enable-metrics-collection \
  --auto-scaling-group-name $ASG_NAME \
  --granularity "1Minute" \
  --metrics GroupDesiredCapacity GroupInServiceInstances GroupPendingInstances

echo "=== Configuration Alarmes ==="

# Alarme max capacity
aws cloudwatch put-metric-alarm \
  --alarm-name $ASG_NAME-max-capacity \
  --alarm-description "ASG approaching max capacity" \
  --metric-name GroupInServiceInstances \
  --namespace AWS/AutoScaling \
  --statistic Average \
  --period 60 \
  --evaluation-periods 2 \
  --threshold 18 \
  --comparison-operator GreaterThanOrEqualToThreshold \
  --dimensions Name=AutoScalingGroupName,Value=$ASG_NAME \
  --alarm-actions arn:aws:sns:$REGION:123456789012:ops-alerts

echo "=== ARCHITECTURE COMPLETE ==="
echo "ASG Name: $ASG_NAME"
echo "Min: 2, Max: 20, Desired: 4"
echo "Scaling Policies: CPU (70%), ALB Requests (1000)"
echo "Scheduled: Scale up 8h, down 20h (weekdays)"


═══════════════════════════════════════════════════════════════════════════════
[OK] COMMANDES RAPIDES - QUICK REFERENCE
═══════════════════════════════════════════════════════════════════════════════

# Launch Template
aws ec2 create-launch-template --launch-template-name NAME --launch-template-data '{...}'
aws ec2 describe-launch-templates
aws ec2 delete-launch-template --launch-template-id lt-xxx

# Auto Scaling Group
aws autoscaling create-auto-scaling-group --auto-scaling-group-name NAME \
  --launch-template LaunchTemplateName=NAME,Version='$Latest' \
  --min-size 2 --max-size 10 --desired-capacity 4
  --vpc-zone-identifier "subnet-1,subnet-2"
aws autoscaling describe-auto-scaling-groups
aws autoscaling update-auto-scaling-group --auto-scaling-group-name NAME --min-size 3 --max-size 15
aws autoscaling delete-auto-scaling-group --auto-scaling-group-name NAME --force-delete

# Scaling Policies
aws autoscaling put-scaling-policy --auto-scaling-group-name NAME \
  --policy-name target-cpu --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{...}'
aws autoscaling describe-policies --auto-scaling-group-name NAME
aws autoscaling delete-policy --auto-scaling-group-name NAME --policy-name NAME

# Scheduled Actions
aws autoscaling put-scheduled-update-group-action --auto-scaling-group-name NAME \
  --scheduled-action-name NAME --recurrence "0 9 * * MON-FRI" --desired-capacity 6
aws autoscaling describe-scheduled-actions --auto-scaling-group-name NAME
aws autoscaling delete-scheduled-action --auto-scaling-group-name NAME --scheduled-action-name NAME

# Manual Scaling
aws autoscaling set-desired-capacity --auto-scaling-group-name NAME --desired-capacity 8
aws autoscaling terminate-instance-in-auto-scaling-group --instance-id i-xxx --should-decrement-desired-capacity

# Processes
aws autoscaling suspend-processes --auto-scaling-group-name NAME
aws autoscaling resume-processes --auto-scaling-group-name NAME

# Instances
aws autoscaling describe-auto-scaling-instances
aws autoscaling enter-standby --instance-ids i-xxx --auto-scaling-group-name NAME --should-decrement-desired-capacity
aws autoscaling exit-standby --instance-ids i-xxx --auto-scaling-group-name NAME

# Monitoring
aws autoscaling enable-metrics-collection --auto-scaling-group-name NAME --granularity "1Minute"
aws autoscaling describe-scaling-activities --auto-scaling-group-name NAME --max-records 10

# Load Balancer
aws autoscaling attach-load-balancer-target-groups --auto-scaling-group-name NAME --target-group-arns arn:...
aws autoscaling detach-load-balancer-target-groups --auto-scaling-group-name NAME --target-group-arns arn:...


═══════════════════════════════════════════════════════════════════════════════
[OK] SCÉNARIOS AVANCÉS
═══════════════════════════════════════════════════════════════════════════════

# SCÉNARIO 1: BLUE/GREEN DEPLOYMENT AVEC AUTO SCALING
════════════════════════════════════════════════════════════════════════════════

# Objectif: Déployer nouvelle version sans downtime

# 1. Créer nouveau Launch Template (version 2)
aws ec2 create-launch-template-version \
  --launch-template-id lt-0123456789abcdef0 \
  --version-description "Version 2 - New feature" \
  --source-version 1 \
  --launch-template-data '{
    "ImageId": "ami-NEW123",
    "UserData": "'$NEW_USER_DATA'"
  }'

# 2. Créer ASG "Green" avec nouveau template
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name green-asg \
  --launch-template LaunchTemplateId=lt-xxx,Version='2' \
  --min-size 2 \
  --max-size 10 \
  --desired-capacity 4 \
  --vpc-zone-identifier "subnet-1,subnet-2" \
  --target-group-arns arn:aws:elasticloadbalancing:...:targetgroup/green-targets/...

# 3. Attendre que instances Green soient healthy
aws autoscaling wait group-in-service \
  --auto-scaling-group-names green-asg

# 4. Basculer trafic ALB (weighted target groups)
# Blue: 100% -> 50% -> 0%
# Green: 0% -> 50% -> 100%

aws elbv2 modify-rule \
  --rule-arn arn:... \
  --actions Type=forward,ForwardConfig='{
    "TargetGroups": [
      {"TargetGroupArn": "arn:.../blue-targets/...", "Weight": 50},
      {"TargetGroupArn": "arn:.../green-targets/...", "Weight": 50}
    ]
  }'

# 5. Vérifier métriques (erreurs, latence)
# Si OK -> 100% Green
# Si problème -> Rollback 100% Blue

# 6. Basculer complètement vers Green
aws elbv2 modify-rule \
  --rule-arn arn:... \
  --actions Type=forward,TargetGroupArn=arn:.../green-targets/...

# 7. Supprimer Blue ASG
aws autoscaling delete-auto-scaling-group \
  --auto-scaling-group-name blue-asg \
  --force-delete


# SCÉNARIO 2: CANARY DEPLOYMENT (TESTER SUR 5%)
════════════════════════════════════════════════════════════════════════════════

# Déployer nouvelle version sur 5% trafic seulement

# 1. Créer Canary ASG (petite)
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name canary-asg \
  --launch-template LaunchTemplateId=lt-new,Version='$Latest' \
  --min-size 1 \
  --max-size 2 \
  --desired-capacity 1 \
  --vpc-zone-identifier "subnet-1,subnet-2" \
  --target-group-arns arn:.../canary-targets/...

# 2. Configurer ALB: 95% prod, 5% canary
aws elbv2 modify-rule \
  --rule-arn arn:... \
  --actions Type=forward,ForwardConfig='{
    "TargetGroups": [
      {"TargetGroupArn": "arn:.../prod-targets/...", "Weight": 95},
      {"TargetGroupArn": "arn:.../canary-targets/...", "Weight": 5}
    ]
  }'

# 3. Monitorer métriques canary vs prod
# CloudWatch Metrics:
# - Error rate
# - Latency p50, p99
# - CPU/Memory

# 4. Si métriques OK après 1h -> déployer complètement
# Si problème -> terminer canary

# 5. Rollout progressif
# 5% -> 25% -> 50% -> 100%


# SCÉNARIO 3: BATCH PROCESSING AVEC AUTO SCALING
════════════════════════════════════════════════════════════════════════════════

# Scale basé sur queue depth SQS

# 1. Créer métrique custom CloudWatch
# (Dans code worker, publier queue depth)

# 2. Scaling policy basé sur queue depth
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name batch-workers-asg \
  --policy-name scale-on-queue \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "CustomizedMetricSpecification": {
      "MetricName": "ApproximateNumberOfMessagesVisible",
      "Namespace": "AWS/SQS",
      "Statistic": "Average",
      "Dimensions": [
        {
          "Name": "QueueName",
          "Value": "my-processing-queue"
        }
      ]
    },
    "TargetValue": 100.0
  }'

# EXPLICATION:
# Target: 100 messages par instance
# Si queue = 1000 messages -> 10 instances
# Si queue = 50 messages -> 1 instance

# 3. Configuration ASG
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name batch-workers-asg \
  --launch-template LaunchTemplateName=worker-template,Version='$Latest' \
  --min-size 0 \
  --max-size 50 \
  --desired-capacity 0 \
  --vpc-zone-identifier "subnet-private-1,subnet-private-2"

# EXPLICATION:
# min-size 0 = Pas de workers quand queue vide
# Économie maximale


# SCÉNARIO 4: MULTI-REGION AUTO SCALING
════════════════════════════════════════════════════════════════════════════════

# Auto Scaling dans plusieurs régions

# Région 1 (us-east-1) - Primary
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name us-east-asg \
  --launch-template LaunchTemplateName=web-template,Version='$Latest' \
  --min-size 4 \
  --max-size 20 \
  --desired-capacity 8 \
  --vpc-zone-identifier "subnet-us-east-1a,subnet-us-east-1b" \
  --target-group-arns arn:aws:elasticloadbalancing:us-east-1:...

# Région 2 (eu-west-1) - Secondary
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name eu-west-asg \
  --launch-template LaunchTemplateName=web-template,Version='$Latest' \
  --min-size 2 \
  --max-size 10 \
  --desired-capacity 4 \
  --vpc-zone-identifier "subnet-eu-west-1a,subnet-eu-west-1b" \
  --target-group-arns arn:aws:elasticloadbalancing:eu-west-1:... \
  --region eu-west-1

# Utiliser Route 53 pour distribuer trafic:
# - Geolocation routing: US -> us-east-1, EU -> eu-west-1
# - Latency routing: Route vers région la plus proche
# - Failover: Si us-east-1 down -> eu-west-1


# SCÉNARIO 5: SPOT + ON-DEMAND MIX (ÉCONOMIE)
════════════════════════════════════════════════════════════════════════════════

# 50% Spot (économie) + 50% On-Demand (stabilité)

aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name mixed-asg \
  --mixed-instances-policy '{
    "LaunchTemplate": {
      "LaunchTemplateSpecification": {
        "LaunchTemplateId": "lt-0123456789abcdef0",
        "Version": "$Latest"
      },
      "Overrides": [
        {"InstanceType": "t3.micro"},
        {"InstanceType": "t3.small"},
        {"InstanceType": "t3a.micro"},
        {"InstanceType": "t3a.small"}
      ]
    },
    "InstancesDistribution": {
      "OnDemandBaseCapacity": 2,
      "OnDemandPercentageAboveBaseCapacity": 50,
      "SpotAllocationStrategy": "capacity-optimized",
      "SpotInstancePools": 4,
      "SpotMaxPrice": ""
    }
  }' \
  --min-size 4 \
  --max-size 20 \
  --desired-capacity 8 \
  --vpc-zone-identifier "subnet-1,subnet-2,subnet-3"

# EXPLICATION:

# "OnDemandBaseCapacity": 2
#   = Toujours 2 instances On-Demand minimum
#   = Base stable

# "OnDemandPercentageAboveBaseCapacity": 50
#   = Au-dessus de base, 50% On-Demand, 50% Spot
#   = Si desired=8: 2 On-Demand (base) + 3 On-Demand + 3 Spot

# "SpotAllocationStrategy": "capacity-optimized"
#   = AWS choisit Spot pools avec le plus de capacité
#   = Réduit interruptions
#   = Options: lowest-price, capacity-optimized, diversified

# "SpotInstancePools": 4
#   = Diversifier sur 4 instance types différents
#   = Réduit risque interruption

# "SpotMaxPrice": ""
#   = Vide = Prix On-Demand (recommandé)
#   = Ou spécifier max price

# ÉCONOMIE:
# On-Demand t3.micro: $0.0104/h
# Spot t3.micro: ~$0.0031/h (70% économie)
# Mix 50/50: ~35% économie totale


# SCÉNARIO 6: GAMING - SCALE RAPIDE POUR ÉVÉNEMENTS
════════════════════════════════════════════════════════════════════════════════

# Jeu avec événements spéciaux (tournois)

# Configuration normale
aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name game-servers-asg \
  --launch-template LaunchTemplateName=game-server,Version='$Latest' \
  --min-size 10 \
  --max-size 100 \
  --desired-capacity 20 \
  --vpc-zone-identifier "subnet-1,subnet-2,subnet-3"

# Scaling ultra-agressif
aws autoscaling put-scaling-policy \
  --auto-scaling-group-name game-servers-asg \
  --policy-name fast-scale-out \
  --policy-type TargetTrackingScaling \
  --target-tracking-configuration '{
    "PredefinedMetricSpecification": {
      "PredefinedMetricType": "ASGAverageCPUUtilization"
    },
    "TargetValue": 60.0,
    "ScaleOutCooldown": 30
  }'

# Warm Pool pour démarrage rapide
aws autoscaling put-warm-pool \
  --auto-scaling-group-name game-servers-asg \
  --max-group-prepared-capacity 120 \
  --min-size 20 \
  --pool-state Hibernated

# Scheduled action pour tournoi
aws autoscaling put-scheduled-update-group-action \
  --auto-scaling-group-name game-servers-asg \
  --scheduled-action-name tournament-prep \
  --start-time "2024-06-15T19:00:00Z" \
  --min-size 50 \
  --max-size 200 \
  --desired-capacity 80

# EXPLICATION:
# 19h: Scale up avant tournoi (20h)
# Warm Pool: 20 servers prêts (30s startup)
# Cooldown: 30s (scale rapide)
# Max: 200 servers pour pic


# SCÉNARIO 7: MACHINE LEARNING TRAINING
════════════════════════════════════════════════════════════════════════════════

# Cluster training avec GPU instances

aws autoscaling create-auto-scaling-group \
  --auto-scaling-group-name ml-training-asg \
  --mixed-instances-policy '{
    "LaunchTemplate": {
      "LaunchTemplateSpecification": {
        "LaunchTemplateId": "lt-gpu-template",
        "Version": "$Latest"
      },
      "Overrides": [
        {"InstanceType": "g4dn.xlarge"},
        {"InstanceType": "g4dn.2xlarge"},
        {"InstanceType": "g5.xlarge"}
      ]
    },
    "InstancesDistribution": {
      "OnDemandBaseCapacity": 0,
      "OnDemandPercentageAboveBaseCapacity": 0,
      "SpotAllocationStrategy": "capacity-optimized"
    }
  }' \
  --min-size 0 \
  --max-size 20 \
  --desired-capacity 0 \
  --vpc-zone-identifier "subnet-private-1,subnet-private-2"

# EXPLICATION:
# 100% Spot (économie maximale)
# min-size 0 = Pas de coût quand pas de training
# Démarrer manuellement pour training:

aws autoscaling set-desired-capacity \
  --auto-scaling-group-name ml-training-asg \
  --desired-capacity 10

# Après training terminé:
aws autoscaling set-desired-capacity \
  --auto-scaling-group-name ml-training-asg \
  --desired-capacity 0

# ÉCONOMIE:
# g4dn.xlarge On-Demand: $0.526/h
# g4dn.xlarge Spot: ~$0.158/h (70% économie)
# 10 instances × 8h training = $12.64 (vs $42.08)


═══════════════════════════════════════════════════════════════════════════════
[OK] COÛTS AUTO SCALING - CALCULATEUR
═══════════════════════════════════════════════════════════════════════════════

# AUTO SCALING = GRATUIT!
# Vous payez seulement pour les instances EC2

# EXEMPLE CALCUL:
════════════════════════════════════════════════════════════════════════════════

# Configuration:
# - Type: t3.micro ($0.0104/h)
# - Min: 2, Max: 10
# - Trafic: Variable

# SCÉNARIO 1: SANS AUTO SCALING
# Provisionner pour pic: 10 instances 24/7
# Coût: 10 × $0.0104 × 730h = $75.92/mois
# Utilisation réelle: 30% du temps
# Gaspillage: $53.14/mois

# SCÉNARIO 2: AVEC AUTO SCALING
# Heures bureau (8h-18h, 5j/sem): 8 instances
# Heure creuse (18h-8h): 2 instances
# Weekend: 2 instances

# Calcul:
# - Bureau: 50h/sem × 8 inst = 400 inst-h/sem
# - Creuse: 70h/sem × 2 inst = 140 inst-h/sem
# - Weekend: 48h/sem × 2 inst = 96 inst-h/sem
# Total: 636 inst-h/sem × 4.3 sem = 2,735 inst-h/mois

# Coût: 2,735 × $0.0104 = $28.44/mois
# ÉCONOMIE: $47.48/mois (62%)

# SCÉNARIO 3: AVEC SPOT INSTANCES
# Mix: 50% On-Demand, 50% Spot
# Spot price: $0.0031/h (70% économie)

# Calcul:
# - 50% On-Demand: 1,368 inst-h × $0.0104 = $14.23
# - 50% Spot: 1,368 inst-h × $0.0031 = $4.24
# Total: $18.47/mois
# ÉCONOMIE: $57.45/mois (76% vs sans Auto Scaling)

# CALCULATOR SCRIPT
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Calculateur coût Auto Scaling

# Variables
INSTANCE_TYPE="t3.micro"
ON_DEMAND_PRICE=0.0104  # $/heure
SPOT_PRICE=0.0031       # $/heure (70% discount)

# Heures par semaine
BUSINESS_HOURS=50       # 8h-18h × 5 jours
OFF_HOURS=70           # 18h-8h × 5 jours
WEEKEND_HOURS=48       # 24h × 2 jours

# Instances par période
BUSINESS_INSTANCES=8
OFF_INSTANCES=2
WEEKEND_INSTANCES=2

# Calcul instance-heures par mois
WEEKS_PER_MONTH=4.3

BUSINESS_INST_H=$(echo "$BUSINESS_HOURS * $BUSINESS_INSTANCES * $WEEKS_PER_MONTH" | bc)
OFF_INST_H=$(echo "$OFF_HOURS * $OFF_INSTANCES * $WEEKS_PER_MONTH" | bc)
WEEKEND_INST_H=$(echo "$WEEKEND_HOURS * $WEEKEND_INSTANCES * $WEEKS_PER_MONTH" | bc)

TOTAL_INST_H=$(echo "$BUSINESS_INST_H + $OFF_INST_H + $WEEKEND_INST_H" | bc)

# Coût 100% On-Demand
COST_ON_DEMAND=$(echo "$TOTAL_INST_H * $ON_DEMAND_PRICE" | bc)

# Coût Mix 50/50
HALF_INST_H=$(echo "$TOTAL_INST_H / 2" | bc)
COST_MIX=$(echo "($HALF_INST_H * $ON_DEMAND_PRICE) + ($HALF_INST_H * $SPOT_PRICE)" | bc)

# Coût sans Auto Scaling (10 instances 24/7)
HOURS_PER_MONTH=730
COST_NO_ASG=$(echo "10 * $ON_DEMAND_PRICE * $HOURS_PER_MONTH" | bc)

echo "=== CALCULATEUR COÛT AUTO SCALING ==="
echo ""
echo "Instance Type: $INSTANCE_TYPE"
echo "Total instance-heures/mois: $TOTAL_INST_H"
echo ""
echo "SANS Auto Scaling (10 inst 24/7):"
echo "  Coût: \$$COST_NO_ASG/mois"
echo ""
echo "AVEC Auto Scaling (100% On-Demand):"
echo "  Coût: \$$COST_ON_DEMAND/mois"
SAVINGS=$(echo "$COST_NO_ASG - $COST_ON_DEMAND" | bc)
PERCENT=$(echo "scale=1; ($SAVINGS / $COST_NO_ASG) * 100" | bc)
echo "  Économie: \$$SAVINGS/mois ($PERCENT%)"
echo ""
echo "AVEC Auto Scaling + Spot (50/50):"
echo "  Coût: \$$COST_MIX/mois"
SAVINGS_SPOT=$(echo "$COST_NO_ASG - $COST_MIX" | bc)
PERCENT_SPOT=$(echo "scale=1; ($SAVINGS_SPOT / $COST_NO_ASG) * 100" | bc)
echo "  Économie: \$$SAVINGS_SPOT/mois ($PERCENT_SPOT%)"


═══════════════════════════════════════════════════════════════════════════════
[OK] RESSOURCES UTILES
═══════════════════════════════════════════════════════════════════════════════

# Documentation officielle
https://docs.aws.amazon.com/autoscaling/

# Auto Scaling Best Practices
https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-best-practices.html

# Pricing Calculator
https://calculator.aws/#/

# Instance Types & Pricing
https://aws.amazon.com/ec2/instance-types/
https://aws.amazon.com/ec2/pricing/

# Spot Instance Pricing History
https://aws.amazon.com/ec2/spot/pricing/

# CloudWatch Metrics
https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-instance-monitoring.html

# Launch Template User Guide
https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-launch-templates.html

# Scaling Policy Types
https://docs.aws.amazon.com/autoscaling/ec2/userguide/as-scale-based-on-demand.html

# Lifecycle Hooks
https://docs.aws.amazon.com/autoscaling/ec2/userguide/lifecycle-hooks.html

# Warm Pools
https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-warm-pools.html

# AWS CLI Reference
https://awscli.amazonaws.com/v2/documentation/api/latest/reference/autoscaling/index.html

# Service Quotas
https://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html

# AWS Forums (community support)
https://forums.aws.amazon.com/forum.jspa?forumID=61


═══════════════════════════════════════════════════════════════════════════════
[OK] GLOSSAIRE
═══════════════════════════════════════════════════════════════════════════════

ASG: Auto Scaling Group
AZ: Availability Zone
ALB: Application Load Balancer
ELB: Elastic Load Balancer
ENI: Elastic Network Interface
IAM: Identity and Access Management
IMDSv2: Instance Metadata Service version 2
LT: Launch Template
SG: Security Group
VPC: Virtual Private Cloud

CAPACITY: Nombre d'instances
COOLDOWN: Période d'attente après scaling
DESIRED CAPACITY: Nombre d'instances souhaité actuellement
FLAPPING: Scaling up/down répété (oscillation)
GRACE PERIOD: Délai avant health checks
HEALTH CHECK: Vérification santé instance
LAUNCH TEMPLATE: Modèle pour créer instances
MAX SIZE: Capacité maximum
MIN SIZE: Capacité minimum
SCALE IN: Retirer instances
SCALE OUT: Ajouter instances
SPOT INSTANCE: Instance jusqu'à 90% moins chère (interruptible)
TARGET TRACKING: Maintenir métrique à valeur cible
WARM POOL: Pool d'instances pré-initialisées
WEIGHTED CAPACITY: Poids d'un instance type


═══════════════════════════════════════════════════════════════════════════════
[OK] CHECKLIST DÉPLOIEMENT PRODUCTION
═══════════════════════════════════════════════════════════════════════════════

AVANT LANCEMENT:
[ ] Launch Template testé manuellement
[ ] User data fonctionne correctement
[ ] AMI à jour avec derniers patches
[ ] Security Groups configurés (moindre privilège)
[ ] IAM Role avec permissions minimales
[ ] Health check endpoint répond (/health)
[ ] Min size ≥ 2 (haute disponibilité)
[ ] Multiple AZs configurées (≥ 2)
[ ] Load Balancer configuré et testé
[ ] Target Group health checks OK
[ ] Scaling policies testées
[ ] CloudWatch alarmes créées
[ ] SNS notifications configurées
[ ] Documentation à jour

APRÈS LANCEMENT:
[ ] Vérifier instances lancent correctement
[ ] Vérifier health checks passent
[ ] Tester scaling up/down manuellement
[ ] Load testing (simuler trafic)
[ ] Vérifier métriques CloudWatch
[ ] Tester failover (terminer instance)
[ ] Vérifier logs CloudWatch
[ ] Documenter configuration
[ ] Former équipe ops
[ ] Plan rollback prêt

MONITORING CONTINU:
[ ] Daily: Vérifier ASG healthy
[ ] Daily: Review scaling activities
[ ] Weekly: Review coûts
[ ] Weekly: Review métriques performance
[ ] Monthly: Review scaling policies
[ ] Monthly: Tester disaster recovery
[ ] Quarterly: Update AMIs
[ ] Quarterly: Review architecture


# Fichier: python_cheats/cheatsheets/route_53.txt
# Cheatsheet AWS Route 53 - DNS Service Expliqué en Détail


═══════════════════════════════════════════════════════════════════════════════
[OK] AWS ROUTE 53 - C'EST QUOI?
═══════════════════════════════════════════════════════════════════════════════

ROUTE 53 = "Service DNS géré par AWS"

DNS (Domain Name System) = "Annuaire téléphonique d'Internet"

ANALOGIE:
- Vous tapez: www.example.com (nom facile à retenir)
- DNS traduit: 203.0.113.25 (adresse IP réelle)
- Comme annuaire: "Jean Dupont" -> "01 23 45 67 89"

POURQUOI "ROUTE 53"?
- Port DNS = 53
- Route = Router le trafic
- 53 = Clin d'œil au protocole DNS

PROBLÈME SANS ROUTE 53:
[X] Gérer serveurs DNS soi-même (complexe)
[X] Pas de haute disponibilité automatique
[X] Pas d'intégration AWS services
[X] Configuration manuelle fastidieuse

SOLUTION ROUTE 53:
[OK] DNS géré (pas de serveurs à maintenir)
[OK] 100% disponibilité (SLA)
[OK] Intégration AWS (ELB, CloudFront, S3)
[OK] Routing policies avancées
[OK] Health checks automatiques
[OK] Domain registration (acheter domaines)

FONCTIONNALITÉS PRINCIPALES:
1. DOMAIN REGISTRATION -> Acheter domaines (.com, .fr, etc.)
2. DNS HOSTING -> Héberger zones DNS
3. HEALTH CHECKS -> Surveiller santé endpoints
4. TRAFFIC MANAGEMENT -> Router intelligemment trafic
5. DNSSEC -> Sécurité DNS

CONCEPTS CLÉS:
- HOSTED ZONE = Zone DNS pour un domaine
- RECORD = Entrée DNS (A, CNAME, MX, etc.)
- TTL (Time To Live) = Durée cache DNS
- ALIAS = Record spécial AWS (gratuit, meilleur)
- NAMESERVERS = Serveurs DNS autoritaires

TYPES DE RECORDS DNS:
- A -> Nom -> IPv4 (ex: example.com -> 203.0.113.25)
- AAAA -> Nom -> IPv6
- CNAME -> Alias vers autre nom (ex: www -> example.com)
- MX -> Serveurs mail
- TXT -> Texte (vérification domaine, SPF)
- NS -> Nameservers
- SOA -> Start of Authority (info zone)
- ALIAS -> Spécial AWS (comme CNAME mais mieux)

PRICING:
- Hosted zone: $0.50/mois par zone
- Requêtes DNS: $0.40 par million (premier milliard)
- Health checks: $0.50/mois par check
- Domain registration: Variable ($12-50/an)

QUAND UTILISER ROUTE 53?
[OK] Site web/application avec domaine
[OK] Load balancing intelligent
[OK] Disaster recovery (failover)
[OK] Applications multi-régions
[OK] Microservices discovery

SLA:
- 100% disponibilité pour DNS queries
- Réseau anycast global (faible latence)


═══════════════════════════════════════════════════════════════════════════════
[OK] HOSTED ZONES - ZONES DNS
═══════════════════════════════════════════════════════════════════════════════

HOSTED ZONE = "Container pour tous les records d'un domaine"

TYPES:
1. PUBLIC HOSTED ZONE -> Internet public (example.com)
2. PRIVATE HOSTED ZONE -> VPC privé (internal.company.local)

EXEMPLE:
Domaine: example.com
Hosted Zone contient:
- example.com -> 203.0.113.25
- www.example.com -> 203.0.113.25
- mail.example.com -> 203.0.113.50
- api.example.com -> ALB DNS

# CRÉER HOSTED ZONE PUBLIC
════════════════════════════════════════════════════════════════════════════════

aws route53 create-hosted-zone \
  --name example.com \
  --caller-reference $(date +%s)

# EXPLICATION PARAMÈTRES:

# --name example.com
#   = Nom du domaine
#   = Peut être: example.com, subdomain.example.com
#   = Doit être unique

# --caller-reference $(date +%s)
#   = ID unique pour cette requête
#   = $(date +%s) = timestamp Unix
#   = Évite créer duplicatas par accident
#   = Toute valeur unique fonctionne

# RÉSULTAT:
# {
#   "HostedZone": {
#     "Id": "/hostedzone/Z1234567890ABC",
#     "Name": "example.com.",
#     "CallerReference": "1705334400",
#     "Config": {
#       "PrivateZone": false
#     },
#     "ResourceRecordSetCount": 2
#   },
#   "ChangeInfo": {
#     "Id": "/change/C1234567890DEF",
#     "Status": "PENDING",
#     "SubmittedAt": "2024-01-15T10:30:00.000Z"
#   },
#   "DelegationSet": {
#     "NameServers": [
#       "ns-123.awsdns-12.com",
#       "ns-456.awsdns-45.net",
#       "ns-789.awsdns-78.org",
#       "ns-012.awsdns-01.co.uk"
#     ]
#   }
# }

# IMPORTANT:
# [ATTENTION] Notez HostedZone.Id: Z1234567890ABC (besoin pour créer records)
# [ATTENTION] Notez NameServers (configurer chez registrar)

# QU'EST-CE QUI EST CRÉÉ:
# 1. Hosted Zone (container)
# 2. Record NS (nameservers)
# 3. Record SOA (Start of Authority)

# Créer hosted zone avec tags
aws route53 create-hosted-zone \
  --name example.com \
  --caller-reference $(date +%s) \
  --hosted-zone-config Comment="Production website" \
  --tags Key=Environment,Value=Production Key=Owner,Value=DevTeam

# EXPLICATION:
# --hosted-zone-config Comment="..."
#   = Description de la zone
# --tags = Tags pour organisation/facturation

# CRÉER HOSTED ZONE PRIVÉE (VPC)
════════════════════════════════════════════════════════════════════════════════

aws route53 create-hosted-zone \
  --name internal.company.local \
  --caller-reference $(date +%s) \
  --vpc VPCRegion=us-east-1,VPCId=vpc-0123456789abcdef0 \
  --hosted-zone-config PrivateZone=true

# EXPLICATION:

# --vpc VPCRegion=us-east-1,VPCId=vpc-xxx
#   = VPC où cette zone est accessible
#   = DNS résolu SEULEMENT dans ce VPC
#   = Parfait pour noms internes

# --hosted-zone-config PrivateZone=true
#   = Zone privée (pas publique)

# USE CASE PRIVATE ZONE:
# - database.internal.company.local -> RDS endpoint
# - api.internal.company.local -> Internal ALB
# - cache.internal.company.local -> ElastiCache
# Pas exposé à Internet!

# Associer VPC additionnel à zone privée
aws route53 associate-vpc-with-hosted-zone \
  --hosted-zone-id Z1234567890ABC \
  --vpc VPCRegion=us-west-2,VPCId=vpc-abcdef0123456789

# EXPLICATION:
# Même zone DNS accessible depuis plusieurs VPCs
# Utile pour multi-région

# LISTER HOSTED ZONES
════════════════════════════════════════════════════════════════════════════════

# Lister toutes les hosted zones
aws route53 list-hosted-zones

# Format lisible
aws route53 list-hosted-zones \
  --query 'HostedZones[*].[Id,Name,Config.PrivateZone,ResourceRecordSetCount]' \
  --output table

# RÉSULTAT EXEMPLE:
# ---------------------------------------------------------------------------
# | /hostedzone/Z123ABC | example.com.      | False | 5  |
# | /hostedzone/Z456DEF | internal.local.   | True  | 3  |
# ---------------------------------------------------------------------------

# Filtrer par nom
aws route53 list-hosted-zones \
  --query "HostedZones[?Name=='example.com.']"

# [ATTENTION] NOTEZ LE POINT FINAL:
# AWS ajoute automatiquement "." à la fin
# example.com -> example.com.
# C'est normal (FQDN = Fully Qualified Domain Name)

# OBTENIR HOSTED ZONE SPÉCIFIQUE
════════════════════════════════════════════════════════════════════════════════

# Obtenir détails hosted zone
aws route53 get-hosted-zone \
  --id Z1234567890ABC

# EXPLICATION:
# --id = ID de la hosted zone
# Peut être avec ou sans /hostedzone/ prefix
# Z1234567890ABC = valide
# /hostedzone/Z1234567890ABC = valide aussi

# Voir seulement nameservers
aws route53 get-hosted-zone \
  --id Z1234567890ABC \
  --query 'DelegationSet.NameServers' \
  --output table

# RÉSULTAT:
# ---------------------------------
# | ns-123.awsdns-12.com          |
# | ns-456.awsdns-45.net          |
# | ns-789.awsdns-78.org          |
# | ns-012.awsdns-01.co.uk        |
# ---------------------------------

# CONFIGURER NAMESERVERS CHEZ REGISTRAR
════════════════════════════════════════════════════════════════════════════════

# Après création hosted zone:
# 1. Obtenir nameservers Route 53
aws route53 get-hosted-zone \
  --id Z1234567890ABC \
  --query 'DelegationSet.NameServers'

# 2. Aller chez registrar (GoDaddy, Namecheap, etc.)
# 3. Remplacer nameservers par ceux de Route 53
# 4. Attendre propagation (5 min - 48h)

# EXEMPLE CONFIGURATION:
# Chez GoDaddy:
# Nameserver 1: ns-123.awsdns-12.com
# Nameserver 2: ns-456.awsdns-45.net
# Nameserver 3: ns-789.awsdns-78.org
# Nameserver 4: ns-012.awsdns-01.co.uk

# VÉRIFIER PROPAGATION:
# dig example.com NS
# nslookup -type=NS example.com
# Ou: https://www.whatsmydns.net/

# SUPPRIMER HOSTED ZONE
════════════════════════════════════════════════════════════════════════════════

# [ATTENTION] ATTENTION: Supprimer zone = supprimer tous les records!

# D'abord, lister et supprimer tous les records (sauf NS et SOA)
aws route53 list-resource-record-sets \
  --hosted-zone-id Z1234567890ABC

# Supprimer hosted zone
aws route53 delete-hosted-zone \
  --id Z1234567890ABC

# ERREUR SI:
# "The specified hosted zone contains non-required resource record sets"
# CAUSE: Records autres que NS/SOA existent
# SOLUTION: Supprimer tous les records d'abord


═══════════════════════════════════════════════════════════════════════════════
[OK] DNS RECORDS - ENTRÉES DNS
═══════════════════════════════════════════════════════════════════════════════

DNS RECORD = "Mapping nom -> valeur"

STRUCTURE RECORD:
- Name: Nom (ex: www.example.com)
- Type: A, AAAA, CNAME, MX, TXT, etc.
- TTL: Durée cache (secondes)
- Value: Valeur (IP, nom, texte)

TTL (Time To Live):
- 300s (5 min) = Standard
- 60s (1 min) = Changements fréquents
- 3600s (1h) = Stable, économise requêtes
- Plus court = Plus flexible mais plus de requêtes DNS

# CRÉER RECORD A (IPv4)
════════════════════════════════════════════════════════════════════════════════

# Préparer change batch JSON
cat > create-a-record.json << 'EOF'
{
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "203.0.113.25"
          }
        ]
      }
    }
  ]
}
EOF

# Appliquer changement
aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://create-a-record.json

# EXPLICATION DÉTAILLÉE:

# "Action": "CREATE"
#   = Créer nouveau record
#   = Options: CREATE, DELETE, UPSERT
#   = UPSERT = Create ou Update (recommandé)

# "Name": "www.example.com"
#   = Nom complet (FQDN)
#   = Peut omettre domaine: "www" (AWS ajoute .example.com)

# "Type": "A"
#   = Type de record
#   = A = IPv4 address

# "TTL": 300
#   = Time To Live (secondes)
#   = 300s = 5 minutes
#   = Cache DNS gardera cette valeur 5 min

# "ResourceRecords": [{"Value": "203.0.113.25"}]
#   = Liste de valeurs (peut avoir plusieurs IPs)

# RÉSULTAT:
# {
#   "ChangeInfo": {
#     "Id": "/change/C1234567890DEF",
#     "Status": "PENDING",
#     "SubmittedAt": "2024-01-15T10:35:00.000Z"
#   }
# }

# Status: PENDING -> Changement en cours (30-60 secondes)
# Status: INSYNC -> Changement appliqué

# Vérifier statut changement
aws route53 get-change --id C1234567890DEF

# CRÉER RECORD AVEC PLUSIEURS IPs (ROUND-ROBIN)
════════════════════════════════════════════════════════════════════════════════

cat > multi-ip-record.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
          {"Value": "203.0.113.25"},
          {"Value": "203.0.113.26"},
          {"Value": "203.0.113.27"}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://multi-ip-record.json

# COMMENT ÇA MARCHE:
# DNS retourne les 3 IPs en rotation
# Client choisit aléatoirement
# Résultat: Load balancing basique
# [ATTENTION] Pas de health checks!

# CRÉER RECORD CNAME (ALIAS)
════════════════════════════════════════════════════════════════════════════════

cat > cname-record.json << 'EOF'
{
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "blog.example.com",
        "Type": "CNAME",
        "TTL": 300,
        "ResourceRecords": [
          {"Value": "example.wordpress.com"}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://cname-record.json

# EXPLICATION CNAME:
# blog.example.com -> example.wordpress.com
# Client résout:
# 1. blog.example.com -> CNAME -> example.wordpress.com
# 2. example.wordpress.com -> A -> IP
# Deux résolutions DNS!

# [ATTENTION] LIMITATIONS CNAME:
# NE PEUT PAS être utilisé pour apex/root domain!
# [X] example.com CNAME -> autre.com (INVALIDE)
# [OK] www.example.com CNAME -> autre.com (VALIDE)
# Solution pour apex: Utiliser ALIAS record

# CRÉER ALIAS RECORD (SPÉCIAL AWS) - RECOMMANDÉ
════════════════════════════════════════════════════════════════════════════════

# ALIAS = Comme CNAME mais:
# [OK] Fonctionne pour apex/root domain
# [OK] Gratuit (pas de frais DNS queries)
# [OK] Health checks intégrés
# [OK] Résolution DNS directe (plus rapide)

# Alias vers Application Load Balancer
cat > alias-to-alb.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "example.com",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "Z35SXDOTRQ7X7K",
          "DNSName": "my-alb-1234567890.us-east-1.elb.amazonaws.com",
          "EvaluateTargetHealth": true
        }
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://alias-to-alb.json

# EXPLICATION PARAMÈTRES:

# "Type": "A"
#   = Toujours A ou AAAA pour Alias
#   = Pas de TTL (géré automatiquement)

# "AliasTarget"
#   = Configuration alias

# "HostedZoneId": "Z35SXDOTRQ7X7K"
#   = Hosted Zone ID du service AWS cible
#   = DIFFÉRENT de votre hosted zone!
#   = Chaque région ELB a son propre ID
#   = us-east-1 ALB = Z35SXDOTRQ7X7K
#   = us-west-2 ALB = Z1H1FL5HABSF5

# TROUVER HOSTED ZONE ID ELB:
aws elbv2 describe-load-balancers \
  --names my-alb \
  --query 'LoadBalancers[0].CanonicalHostedZoneId' \
  --output text

# "DNSName": "my-alb-123....elb.amazonaws.com"
#   = DNS name du ALB
#   = Obtenir avec: describe-load-balancers

# "EvaluateTargetHealth": true
#   = Vérifier santé ALB
#   = Si ALB unhealthy -> Route 53 ne route pas vers lui
#   = Recommandé: true

# HOSTED ZONE IDs COMMUNS:
# ALB/NLB us-east-1: Z35SXDOTRQ7X7K
# ALB/NLB us-west-2: Z1H1FL5HABSF5
# CloudFront: Z2FDTNDATAQYW2
# S3 Website us-east-1: Z3AQBSTGFYJSTF
# Liste complète: https://docs.aws.amazon.com/general/latest/gr/elb.html

# Alias vers CloudFront
cat > alias-to-cloudfront.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "cdn.example.com",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "Z2FDTNDATAQYW2",
          "DNSName": "d123456789.cloudfront.net",
          "EvaluateTargetHealth": false
        }
      }
    }
  ]
}
EOF

# EXPLICATION:
# HostedZoneId CloudFront = Toujours Z2FDTNDATAQYW2 (global)
# EvaluateTargetHealth = false (CloudFront n'a pas health checks)

# Alias vers S3 Static Website
cat > alias-to-s3.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "static.example.com",
        "Type": "A",
        "AliasTarget": {
          "HostedZoneId": "Z3AQBSTGFYJSTF",
          "DNSName": "my-bucket.s3-website-us-east-1.amazonaws.com",
          "EvaluateTargetHealth": false
        }
      }
    }
  ]
}
EOF

# [ATTENTION] IMPORTANT S3:
# Nom bucket DOIT matcher nom DNS!
# Domaine: static.example.com
# Bucket: static.example.com
# Sinon ça ne marche pas!

# CRÉER RECORD MX (MAIL)
════════════════════════════════════════════════════════════════════════════════

cat > mx-record.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "example.com",
        "Type": "MX",
        "TTL": 300,
        "ResourceRecords": [
          {"Value": "10 mail1.example.com"},
          {"Value": "20 mail2.example.com"}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://mx-record.json

# EXPLICATION MX:
# "10 mail1.example.com"
#   = 10 = priorité (plus bas = plus prioritaire)
#   = mail1.example.com = serveur mail
# Emails essaient mail1 d'abord, puis mail2 si échec

# Exemple Gmail:
# {"Value": "1 aspmx.l.google.com"},
# {"Value": "5 alt1.aspmx.l.google.com"},
# {"Value": "5 alt2.aspmx.l.google.com"}

# CRÉER RECORD TXT (VERIFICATION, SPF)
════════════════════════════════════════════════════════════════════════════════

cat > txt-record.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "example.com",
        "Type": "TXT",
        "TTL": 300,
        "ResourceRecords": [
          {"Value": "\"v=spf1 include:_spf.google.com ~all\""},
          {"Value": "\"google-site-verification=ABC123DEF456\""}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://txt-record.json

# EXPLICATION TXT:
# Valeur DOIT être entre guillemets doubles échappés
# \"texte\" = correct
# SPF = Autorise serveurs à envoyer emails
# Vérification = Prouver ownership domaine

# LISTER TOUS LES RECORDS
════════════════════════════════════════════════════════════════════════════════

# Lister tous les records d'une zone
aws route53 list-resource-record-sets \
  --hosted-zone-id Z1234567890ABC

# Format lisible
aws route53 list-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --query 'ResourceRecordSets[*].[Name,Type,TTL]' \
  --output table

# RÉSULTAT EXEMPLE:
# ----------------------------------------------------------
# | example.com.           | SOA   | 900  |
# | example.com.           | NS    | 172800 |
# | example.com.           | A     | 300  |
# | www.example.com.       | A     | 300  |
# | blog.example.com.      | CNAME | 300  |
# | mail.example.com.      | A     | 300  |
# ----------------------------------------------------------

# Filtrer par type
aws route53 list-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --query "ResourceRecordSets[?Type=='A']"

# Filtrer par nom
aws route53 list-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --query "ResourceRecordSets[?Name=='www.example.com.']"

# MODIFIER RECORD EXISTANT
════════════════════════════════════════════════════════════════════════════════

# Utiliser Action: UPSERT (Update or Insert)
cat > update-record.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "TTL": 60,
        "ResourceRecords": [
          {"Value": "203.0.113.100"}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://update-record.json

# EXPLICATION:
# UPSERT = Si record existe -> update, sinon -> create
# Recommandé car idempotent

# SUPPRIMER RECORD
════════════════════════════════════════════════════════════════════════════════

# Pour supprimer, doit spécifier EXACT mêmes valeurs
cat > delete-record.json << 'EOF'
{
  "Changes": [
    {
      "Action": "DELETE",
      "ResourceRecordSet": {
        "Name": "blog.example.com",
        "Type": "CNAME",
        "TTL": 300,
        "ResourceRecords": [
          {"Value": "example.wordpress.com"}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://delete-record.json

# [ATTENTION] IMPORTANT:
# Tous les champs doivent matcher exactement!
# Name, Type, TTL, ResourceRecords
# Sinon erreur: "Invalid change batch"

# BATCH OPERATIONS (MULTIPLE CHANGEMENTS)
════════════════════════════════════════════════════════════════════════════════

# Créer/modifier/supprimer plusieurs records en une commande
cat > batch-changes.json << 'EOF'
{
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "api.example.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [{"Value": "203.0.113.50"}]
      }
    },
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "TTL": 60,
        "ResourceRecords": [{"Value": "203.0.113.100"}]
      }
    },
    {
      "Action": "DELETE",
      "ResourceRecordSet": {
        "Name": "old.example.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [{"Value": "203.0.113.99"}]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://batch-changes.json

# EXPLICATION:
# Tous les changements appliqués atomiquement
# Si un échoue -> tous échouent (rollback)
# Max 1000 changes par batch


═══════════════════════════════════════════════════════════════════════════════
[OK] HEALTH CHECKS - SURVEILLER DISPONIBILITÉ
═══════════════════════════════════════════════════════════════════════════════

HEALTH CHECK = "Surveiller si endpoint est accessible"

TYPES:
1. ENDPOINT -> Vérifie URL/IP spécifique
2. CALCULATED -> Combine plusieurs health checks
3. CLOUDWATCH ALARM -> Basé sur métrique CloudWatch

USE CASES:
- Failover automatique
- Load balancing intelligent
- Alertes si service down

# CRÉER HEALTH CHECK ENDPOINT (HTTPS)
════════════════════════════════════════════════════════════════════════════════

aws route53 create-health-check \
  --health-check-config '{
    "Type": "HTTPS",
    "ResourcePath": "/health",
    "FullyQualifiedDomainName": "api.example.com",
    "Port": 443,
    "RequestInterval": 30,
    "FailureThreshold": 3
  }' \
  --caller-reference $(date +%s)

# EXPLICATION PARAMÈTRES:

# "Type": "HTTPS"
#   = Protocole à vérifier
#   = Options: HTTP, HTTPS, TCP, HTTP_STR_MATCH, HTTPS_STR_MATCH

# "ResourcePath": "/health"
#   = Chemin URL à vérifier
#   = GET https://api.example.com/health
#   = Doit retourner 200 OK

# "FullyQualifiedDomainName": "api.example.com"
#   = Domaine ou IP à vérifier

# "Port": 443
#   = Port à vérifier
#   = 443 = HTTPS, 80 = HTTP

# "RequestInterval": 30
#   = Intervalle entre checks (secondes)
#   = Options: 30 (standard, $0.50/mois), 10 (fast, $1/mois)

# "FailureThreshold": 3
#   = Nombre échecs consécutifs avant unhealthy
#   = 3 = Standard (équilibre entre faux positifs et rapidité)
#   = Range: 1-10

# RÉSULTAT:
# {
#   "HealthCheck": {
#     "Id": "12345678-1234-1234-1234-123456789012",
#     "CallerReference": "1705334400",
#     "HealthCheckConfig": {
#       "Type": "HTTPS",
#       "ResourcePath": "/health",
#       "FullyQualifiedDomainName": "api.example.com",
#       "Port": 443,
#       "RequestInterval": 30,
#       "FailureThreshold": 3
#     },
#     "HealthCheckVersion": 1
#   }
# }

# [ATTENTION] Notez HealthCheck.Id: 12345678-1234-1234-1234-123456789012

# COMMENT ÇA MARCHE:
# 1. Route 53 envoie requêtes depuis ~15 emplacements mondiaux
# 2. Toutes les 30 secondes, chaque emplacement vérifie
# 3. Si ≥18% emplacements (3+) échouent -> unhealthy
# 4. Si unhealthy -> failover déclenché (si configuré)

# CRÉER HEALTH CHECK AVEC STRING MATCHING
════════════════════════════════════════════════════════════════════════════════

# Vérifier que réponse contient texte spécifique

aws route53 create-health-check \
  --health-check-config '{
    "Type": "HTTPS_STR_MATCH",
    "ResourcePath": "/status",
    "FullyQualifiedDomainName": "api.example.com",
    "Port": 443,
    "RequestInterval": 30,
    "FailureThreshold": 3,
    "SearchString": "OK"
  }' \
  --caller-reference $(date +%s)

# EXPLICATION:

# "Type": "HTTPS_STR_MATCH"
#   = Vérifier string dans réponse
#   = HTTP_STR_MATCH pour HTTP

# "SearchString": "OK"
#   = Texte à chercher dans réponse
#   = Réponse doit contenir "OK" pour être healthy
#   = Case sensitive
#   = Premiers 5120 bytes seulement

# USE CASE:
# Endpoint /status retourne: {"status": "OK", "db": "connected"}
# Health check cherche "OK" -> Healthy
# Si retourne "ERROR" -> Unhealthy

# CRÉER HEALTH CHECK TCP
════════════════════════════════════════════════════════════════════════════════

# Juste vérifier si port ouvert (pas de HTTP)

aws route53 create-health-check \
  --health-check-config '{
    "Type": "TCP",
    "IPAddress": "203.0.113.25",
    "Port": 3306,
    "RequestInterval": 30,
    "FailureThreshold": 3
  }' \
  --caller-reference $(date +%s)

# EXPLICATION:
# Vérifie seulement connexion TCP
# Parfait pour: Databases, Redis, services non-HTTP
# "IPAddress" = IP directe (pas de DNS)

# CRÉER HEALTH CHECK CALCULATED (COMBINER)
════════════════════════════════════════════════════════════════════════════════

# Combine plusieurs health checks avec logique

aws route53 create-health-check \
  --health-check-config '{
    "Type": "CALCULATED",
    "ChildHealthChecks": [
      "12345678-1234-1234-1234-123456789012",
      "abcdefgh-abcd-abcd-abcd-abcdefghijkl"
    ],
    "HealthThreshold": 1
  }' \
  --caller-reference $(date +%s)

# EXPLICATION:

# "Type": "CALCULATED"
#   = Combiner plusieurs health checks

# "ChildHealthChecks": [...]
#   = Liste de health check IDs à surveiller

# "HealthThreshold": 1
#   = Minimum health checks healthy pour être healthy
#   = 1 = Au moins 1 doit être healthy (OR logic)
#   = 2 = Au moins 2 doivent être healthy (AND logic partiel)

# USE CASE:
# Surveiller API dans 2 régions
# Healthy si au moins 1 région UP

# CRÉER HEALTH CHECK CLOUDWATCH ALARM
════════════════════════════════════════════════════════════════════════════════

# Basé sur métrique CloudWatch (ex: CPU, custom metric)

# 1. Créer CloudWatch Alarm d'abord
aws cloudwatch put-metric-alarm \
  --alarm-name high-error-rate \
  --alarm-description "Error rate above 5%" \
  --metric-name ErrorRate \
  --namespace MyApp \
  --statistic Average \
  --period 60 \
  --evaluation-periods 2 \
  --threshold 5 \
  --comparison-operator GreaterThanThreshold

# 2. Créer Health Check basé sur alarm
aws route53 create-health-check \
  --health-check-config '{
    "Type": "CLOUDWATCH_METRIC",
    "AlarmIdentifier": {
      "Region": "us-east-1",
      "Name": "high-error-rate"
    },
    "InsufficientDataHealthStatus": "Healthy"
  }' \
  --caller-reference $(date +%s)

# EXPLICATION:

# "Type": "CLOUDWATCH_METRIC"
#   = Basé sur CloudWatch

# "AlarmIdentifier"
#   = Quelle alarme surveiller
#   = Region + Name

# "InsufficientDataHealthStatus": "Healthy"
#   = Statut si données insuffisantes
#   = Options: Healthy, Unhealthy, LastKnownStatus

# USE CASE:
# Surveiller métriques applicatives complexes
# Ex: Taux d'erreur, latence p99, queue depth

# AJOUTER TAGS À HEALTH CHECK
════════════════════════════════════════════════════════════════════════════════

aws route53 change-tags-for-resource \
  --resource-type healthcheck \
  --resource-id 12345678-1234-1234-1234-123456789012 \
  --add-tags Key=Environment,Value=Production Key=Owner,Value=DevTeam

# LISTER HEALTH CHECKS
════════════════════════════════════════════════════════════════════════════════

# Lister tous les health checks
aws route53 list-health-checks

# Format lisible
aws route53 list-health-checks \
  --query 'HealthChecks[*].[Id,HealthCheckConfig.Type,HealthCheckConfig.FullyQualifiedDomainName]' \
  --output table

# RÉSULTAT EXEMPLE:
# -------------------------------------------------------------------------
# | 12345678-... | HTTPS     | api.example.com           |
# | abcdefgh-... | TCP       | None (IP: 203.0.113.25)   |
# | ijklmnop-... | CALCULATED| None                      |
# -------------------------------------------------------------------------

# VOIR STATUT HEALTH CHECK
════════════════════════════════════════════════════════════════════════════════

# Obtenir statut actuel
aws route53 get-health-check-status \
  --health-check-id 12345678-1234-1234-1234-123456789012

# RÉSULTAT EXEMPLE:
# {
#   "HealthCheckObservations": [
#     {
#       "Region": "us-east-1",
#       "IPAddress": "54.239.98.1",
#       "StatusReport": {
#         "Status": "Success",
#         "CheckedTime": "2024-01-15T10:40:00.000Z"
#       }
#     },
#     {
#       "Region": "eu-west-1",
#       "IPAddress": "54.239.98.2",
#       "StatusReport": {
#         "Status": "Success",
#         "CheckedTime": "2024-01-15T10:40:00.000Z"
#       }
#     },
#     ...
#   ]
# }

# EXPLICATION:
# ~15 emplacements vérifient indépendamment
# Status pour chaque emplacement
# Healthy global si ≥18% successful

# CONFIGURER ALARMES SNS
════════════════════════════════════════════════════════════════════════════════

# Recevoir notification si unhealthy

# 1. Créer SNS topic
aws sns create-topic --name healthcheck-alerts

# 2. Souscrire email
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:healthcheck-alerts \
  --protocol email \
  --notification-endpoint ops@example.com

# 3. Configurer health check pour notifier
aws route53 update-health-check \
  --health-check-id 12345678-1234-1234-1234-123456789012 \
  --alarm-identifier Region=us-east-1,Name=healthcheck-alarm

# 4. Créer CloudWatch Alarm pour health check
aws cloudwatch put-metric-alarm \
  --alarm-name healthcheck-alarm \
  --alarm-description "Health check failed" \
  --namespace AWS/Route53 \
  --metric-name HealthCheckStatus \
  --dimensions Name=HealthCheckId,Value=12345678-1234-1234-1234-123456789012 \
  --statistic Minimum \
  --period 60 \
  --evaluation-periods 1 \
  --threshold 1 \
  --comparison-operator LessThanThreshold \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:healthcheck-alerts

# MODIFIER HEALTH CHECK
════════════════════════════════════════════════════════════════════════════════

# Changer configuration existante
aws route53 update-health-check \
  --health-check-id 12345678-1234-1234-1234-123456789012 \
  --failure-threshold 5 \
  --resource-path "/healthz"

# Désactiver health check temporairement
aws route53 update-health-check \
  --health-check-id 12345678-1234-1234-1234-123456789012 \
  --disabled

# Réactiver
aws route53 update-health-check \
  --health-check-id 12345678-1234-1234-1234-123456789012 \
  --no-disabled

# SUPPRIMER HEALTH CHECK
════════════════════════════════════════════════════════════════════════════════

aws route53 delete-health-check \
  --health-check-id 12345678-1234-1234-1234-123456789012

# [ATTENTION] ERREUR SI:
# Health check utilisé par records DNS
# SOLUTION: Supprimer/modifier records d'abord


═══════════════════════════════════════════════════════════════════════════════
[OK] ROUTING POLICIES - STRATEGIES DE ROUTAGE
═══════════════════════════════════════════════════════════════════════════════

ROUTING POLICY = "Comment Route 53 répond aux requêtes DNS"

7 TYPES:
1. SIMPLE -> Une seule ressource ou plusieurs (random)
2. WEIGHTED -> Répartition par pourcentage (A/B testing)
3. LATENCY -> Route vers région la plus proche
4. FAILOVER -> Primary/Secondary (disaster recovery)
5. GEOLOCATION -> Route basé sur localisation utilisateur
6. GEOPROXIMITY -> Route basé sur proximité géographique + bias
7. MULTIVALUE -> Multiple IPs avec health checks

# 1. SIMPLE ROUTING - LE PLUS BASIQUE
════════════════════════════════════════════════════════════════════════════════

# Simple = 1 record, 1+ valeurs
# DNS retourne toutes les valeurs en rotation aléatoire

cat > simple-routing.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
          {"Value": "203.0.113.25"},
          {"Value": "203.0.113.26"}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://simple-routing.json

# COMMENT ÇA MARCHE:
# Client A: Reçoit 203.0.113.25, puis 203.0.113.26
# Client B: Reçoit 203.0.113.26, puis 203.0.113.25
# Ordre aléatoire

# [ATTENTION] LIMITATIONS:
# Pas de health checks
# Si 203.0.113.25 down -> clients peuvent l'obtenir quand même

# USE CASE:
# Petit site, pas besoin complexité
# Ressources toutes équivalentes


# 2. WEIGHTED ROUTING - RÉPARTITION PAR POURCENTAGE
════════════════════════════════════════════════════════════════════════════════

# Weighted = Contrôler % trafic vers chaque ressource
# Parfait pour A/B testing, canary deployments

# Record 1: 70% trafic
cat > weighted-record-1.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "Production-Server-1",
        "Weight": 70,
        "TTL": 60,
        "ResourceRecords": [
          {"Value": "203.0.113.25"}
        ]
      }
    }
  ]
}
EOF

# Record 2: 30% trafic
cat > weighted-record-2.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "Production-Server-2",
        "Weight": 30,
        "TTL": 60,
        "ResourceRecords": [
          {"Value": "203.0.113.26"}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://weighted-record-1.json

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://weighted-record-2.json

# EXPLICATION PARAMÈTRES:

# "SetIdentifier": "Production-Server-1"
#   = ID unique pour ce record
#   = OBLIGATOIRE pour weighted/latency/failover/geolocation
#   = Doit être différent pour chaque record même nom

# "Weight": 70
#   = Poids relatif
#   = 70 + 30 = 100 total
#   = 70/100 = 70% trafic
#   = Peut utiliser n'importe quels nombres (ex: 7 et 3)

# CALCUL POURCENTAGE:
# Poids record / Somme tous poids = %
# 70 / (70+30) = 70%
# 30 / (70+30) = 30%

# COMMENT ÇA MARCHE:
# 100 requêtes DNS:
# ~70 reçoivent 203.0.113.25
# ~30 reçoivent 203.0.113.26

# USE CASES:
# - A/B testing: 90% version A, 10% version B
# - Canary deployment: 95% old, 5% new
# - Load distribution: 50/50 entre 2 datacenters
# - Blue/Green: 100% blue -> 50/50 -> 100% green

# Weighted avec Health Checks
cat > weighted-with-health.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "US-East",
        "Weight": 70,
        "TTL": 60,
        "HealthCheckId": "12345678-1234-1234-1234-123456789012",
        "ResourceRecords": [
          {"Value": "203.0.113.25"}
        ]
      }
    }
  ]
}
EOF

# EXPLICATION:
# Si health check échoue -> record ignoré
# Tout trafic va vers autres records healthy


# 3. LATENCY ROUTING - PLUS PROCHE GÉOGRAPHIQUEMENT
════════════════════════════════════════════════════════════════════════════════

# Latency = Router vers région avec latence la plus faible

# US East (Virginie)
cat > latency-us-east.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "US-East-Servers",
        "Region": "us-east-1",
        "TTL": 60,
        "ResourceRecords": [
          {"Value": "203.0.113.25"}
        ]
      }
    }
  ]
}
EOF

# EU West (Irlande)
cat > latency-eu-west.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "EU-West-Servers",
        "Region": "eu-west-1",
        "TTL": 60,
        "ResourceRecords": [
          {"Value": "198.51.100.50"}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://latency-us-east.json

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://latency-eu-west.json

# EXPLICATION:

# "Region": "us-east-1"
#   = Région AWS de cette ressource
#   = Route 53 mesure latence utilisateur vers chaque région
#   = Choisit région avec latence la plus faible

# COMMENT ÇA MARCHE:
# Utilisateur New York -> us-east-1 (latence 5ms)
# Utilisateur Paris -> eu-west-1 (latence 10ms)
# Utilisateur Tokyo -> ap-northeast-1 (si configuré)

# [ATTENTION] IMPORTANT:
# Basé sur latence réseau AWS, pas distance géographique
# Latence mesurée par AWS entre user et région

# Latency avec Alias (vers ALB)
cat > latency-alias-us.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "US-East-ALB",
        "Region": "us-east-1",
        "AliasTarget": {
          "HostedZoneId": "Z35SXDOTRQ7X7K",
          "DNSName": "my-alb-us.us-east-1.elb.amazonaws.com",
          "EvaluateTargetHealth": true
        }
      }
    }
  ]
}
EOF


# 4. FAILOVER ROUTING - PRIMARY/SECONDARY
════════════════════════════════════════════════════════════════════════════════

# Failover = Active/Passive disaster recovery

# Primary (actif)
cat > failover-primary.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "Primary-Site",
        "Failover": "PRIMARY",
        "TTL": 60,
        "HealthCheckId": "12345678-1234-1234-1234-123456789012",
        "ResourceRecords": [
          {"Value": "203.0.113.25"}
        ]
      }
    }
  ]
}
EOF

# Secondary (backup)
cat > failover-secondary.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "Secondary-Site",
        "Failover": "SECONDARY",
        "TTL": 60,
        "ResourceRecords": [
          {"Value": "198.51.100.50"}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://failover-primary.json

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://failover-secondary.json

# EXPLICATION:

# "Failover": "PRIMARY"
#   = Ressource principale
#   = Toujours utilisée si healthy

# "Failover": "SECONDARY"
#   = Ressource backup
#   = Utilisée SEULEMENT si primary unhealthy

# "HealthCheckId"
#   = Health check du primary (OBLIGATOIRE)
#   = Si échoue -> bascule vers secondary

# COMMENT ÇA MARCHE:
# 1. Primary healthy -> Tout trafic vers primary
# 2. Primary unhealthy -> Tout trafic vers secondary
# 3. Primary redevient healthy -> Retour vers primary

# USE CASE:
# - Site principal us-east-1
# - Site backup eu-west-1
# - Si us-east-1 down -> failover automatique vers eu-west-1

# Failover avec Alias (Active-Passive ALBs)
cat > failover-alb-primary.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "Primary-ALB-US",
        "Failover": "PRIMARY",
        "AliasTarget": {
          "HostedZoneId": "Z35SXDOTRQ7X7K",
          "DNSName": "primary-alb.us-east-1.elb.amazonaws.com",
          "EvaluateTargetHealth": true
        }
      }
    }
  ]
}
EOF


# 5. GEOLOCATION ROUTING - PAR LOCALISATION
════════════════════════════════════════════════════════════════════════════════

# Geolocation = Router basé sur localisation géographique utilisateur

# Europe
cat > geo-europe.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "Europe-Servers",
        "GeoLocation": {
          "ContinentCode": "EU"
        },
        "TTL": 60,
        "ResourceRecords": [
          {"Value": "198.51.100.50"}
        ]
      }
    }
  ]
}
EOF

# Amérique du Nord
cat > geo-north-america.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "North-America-Servers",
        "GeoLocation": {
          "ContinentCode": "NA"
        },
        "TTL": 60,
        "ResourceRecords": [
          {"Value": "203.0.113.25"}
        ]
      }
    }
  ]
}
EOF

# Default (si aucun match)
cat > geo-default.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "Default-Servers",
        "GeoLocation": {
          "ContinentCode": "*"
        },
        "TTL": 60,
        "ResourceRecords": [
          {"Value": "203.0.113.100"}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://geo-europe.json

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://geo-north-america.json

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://geo-default.json

# EXPLICATION:

# "GeoLocation": {"ContinentCode": "EU"}
#   = Continent Europe
#   = Options: AF, AN, AS, EU, NA, OC, SA

# "GeoLocation": {"ContinentCode": "*"}
#   = Default/fallback
#   = Utilisé si aucun autre match
#   = [ATTENTION] RECOMMANDÉ d'avoir un default!

# GRANULARITÉ GEOLOCATION:
# 1. Continent (le moins spécifique)
# 2. Pays
# 3. État/Province (US seulement)

# Par pays
cat > geo-france.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "France-Servers",
        "GeoLocation": {
          "CountryCode": "FR"
        },
        "TTL": 60,
        "ResourceRecords": [
          {"Value": "198.51.100.75"}
        ]
      }
    }
  ]
}
EOF

# Par état US
cat > geo-california.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "California-Servers",
        "GeoLocation": {
          "CountryCode": "US",
          "SubdivisionCode": "CA"
        },
        "TTL": 60,
        "ResourceRecords": [
          {"Value": "203.0.113.150"}
        ]
      }
    }
  ]
}
EOF

# PRIORITÉ MATCHING:
# 1. État/Province (plus spécifique)
# 2. Pays
# 3. Continent
# 4. Default

# EXEMPLE:
# Utilisateur Californie:
#   1. Cherche CA, US -> Trouve -> 203.0.113.150
# Utilisateur New York:
#   1. Cherche NY, US -> Pas trouvé
#   2. Cherche US -> Pas trouvé
#   3. Cherche NA -> Trouve -> 203.0.113.25

# USE CASES:
# - Conformité légale (données en UE pour users UE)
# - Content localization (langue, currency)
# - Restrictions géographiques (licensing)


# 6. GEOPROXIMITY ROUTING - PROXIMITÉ + BIAS
════════════════════════════════════════════════════════════════════════════════

# Geoproximity = Comme latency mais avec contrôle bias

# [ATTENTION] Nécessite Traffic Flow (interface graphique)
# Pas disponible directement via CLI
# Doit utiliser console ou API Traffic Flow

# CONCEPT:
# Bias = Augmenter/réduire zone d'influence
# Bias +50 = Attirer plus de trafic (zone plus grande)
# Bias -50 = Repousser trafic (zone plus petite)

# USE CASE:
# 2 datacenters:
# - US: Capacité énorme -> Bias +30
# - EU: Capacité limitée -> Bias -20
# Résultat: Plus de trafic vers US même si latence similaire


# 7. MULTIVALUE ROUTING - MULTIPLE IPs + HEALTH CHECKS
════════════════════════════════════════════════════════════════════════════════

# Multivalue = Multiple records avec health checks individuels
# Comme Simple mais avec health checks

# Server 1
cat > multivalue-1.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "Server-1",
        "MultiValueAnswer": true,
        "TTL": 60,
        "HealthCheckId": "health-check-1",
        "ResourceRecords": [
          {"Value": "203.0.113.25"}
        ]
      }
    }
  ]
}
EOF

# Server 2
cat > multivalue-2.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "Server-2",
        "MultiValueAnswer": true,
        "TTL": 60,
        "HealthCheckId": "health-check-2",
        "ResourceRecords": [
          {"Value": "203.0.113.26"}
        ]
      }
    }
  ]
}
EOF

# Server 3
cat > multivalue-3.json << 'EOF'
{
  "Changes": [
    {
      "Action": "UPSERT",
      "ResourceRecordSet": {
        "Name": "www.example.com",
        "Type": "A",
        "SetIdentifier": "Server-3",
        "MultiValueAnswer": true,
        "TTL": 60,
        "HealthCheckId": "health-check-3",
        "ResourceRecords": [
          {"Value": "203.0.113.27"}
        ]
      }
    }
  ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://multivalue-1.json

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://multivalue-2.json

aws route53 change-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --change-batch file://multivalue-3.json

# EXPLICATION:

# "MultiValueAnswer": true
#   = Activer multivalue routing

# "HealthCheckId"
#   = Health check pour ce record spécifique
#   = Si unhealthy -> record exclu des réponses

# COMMENT ÇA MARCHE:
# Route 53 retourne jusqu'à 8 IPs healthy
# Client choisit aléatoirement
# Si Server-2 unhealthy:
#   -> Retourne seulement Server-1 et Server-3

# DIFFÉRENCE vs SIMPLE:
# Simple: Retourne tous, même unhealthy
# Multivalue: Retourne seulement healthy

# USE CASE:
# Load balancing simple avec health checks
# Alternative économique à ELB pour cas simples


═══════════════════════════════════════════════════════════════════════════════
[OK] DOMAIN REGISTRATION - ACHETER DOMAINES
═══════════════════════════════════════════════════════════════════════════════

# Route 53 = Aussi registrar (acheter domaines)

# VÉRIFIER DISPONIBILITÉ DOMAINE
════════════════════════════════════════════════════════════════════════════════

# Vérifier si domaine disponible
aws route53domains check-domain-availability \
  --domain-name example.com

# RÉSULTAT:
# {
#   "Availability": "AVAILABLE"
# }
# ou "UNAVAILABLE", "DONT_KNOW"

# VOIR PRIX DOMAINES
════════════════════════════════════════════════════════════════════════════════

# Obtenir prix pour extension
aws route53domains get-domain-detail \
  --domain-name example.com

# PRIX COMMUNS (USD/an):
# .com: $12-13
# .net: $11-12
# .org: $12-13
# .io: $39
# .ai: $49-99
# .fr: $12
# .co.uk: $9

# ENREGISTRER DOMAINE
════════════════════════════════════════════════════════════════════════════════

# [ATTENTION] Commande complexe, préférer console AWS
# Nécessite informations contact complètes

aws route53domains register-domain \
  --domain-name example.com \
  --duration-in-years 1 \
  --auto-renew \
  --admin-contact '{
    "FirstName": "John",
    "LastName": "Doe",
    "ContactType": "PERSON",
    "AddressLine1": "123 Main St",
    "City": "Seattle",
    "State": "WA",
    "CountryCode": "US",
    "ZipCode": "98101",
    "PhoneNumber": "+1.2065551234",
    "Email": "john@example.com"
  }' \
  --registrant-contact <same-as-admin> \
  --tech-contact <same-as-admin>

# EXPLICATION:
# --duration-in-years: 1-10 ans
# --auto-renew: Renouvellement automatique
# Contacts: Admin, Registrant, Tech (peuvent être identiques)

# LISTER DOMAINES ENREGISTRÉS
════════════════════════════════════════════════════════════════════════════════

aws route53domains list-domains

# TRANSFÉRER DOMAINE VERS ROUTE 53
════════════════════════════════════════════════════════════════════════════════

# 1. Déverrouiller domaine chez registrar actuel
# 2. Obtenir authorization code (EPP code)
# 3. Transférer

aws route53domains transfer-domain \
  --domain-name example.com \
  --duration-in-years 1 \
  --auth-code "ABC123DEF456"

# RENOUVELER DOMAINE
════════════════════════════════════════════════════════════════════════════════

aws route53domains renew-domain \
  --domain-name example.com \
  --duration-in-years 1

# PRIVACY PROTECTION
════════════════════════════════════════════════════════════════════════════════

# Cacher informations contact WHOIS (recommandé)
aws route53domains update-domain-contact-privacy \
  --domain-name example.com \
  --admin-privacy true \
  --registrant-privacy true \
  --tech-privacy true


═══════════════════════════════════════════════════════════════════════════════
[OK] TRAFFIC POLICIES - CONFIGURATIONS COMPLEXES
═══════════════════════════════════════════════════════════════════════════════

# Traffic Policy = Configuration routage complexe réutilisable
# Interface visuelle (console AWS recommandée)

# EXEMPLE COMBINAISON:
# 1. Geolocation (EU vs US)
# 2. Puis Weighted (50/50) dans chaque région
# 3. Puis Failover (Primary/Secondary) pour chaque

# STRUCTURE:
# www.example.com
# ├─ EU users
# │  ├─ 50% -> eu-west-1 (Primary)
# │  │        └─ Failover -> eu-central-1 (Secondary)
# │  └─ 50% -> eu-west-2 (Primary)
# │           └─ Failover -> eu-central-1 (Secondary)
# └─ US users
#    ├─ 50% -> us-east-1 (Primary)
#    │        └─ Failover -> us-west-2 (Secondary)
#    └─ 50% -> us-east-2 (Primary)
#             └─ Failover -> us-west-2 (Secondary)

# Traffic Policy CLI (création version JSON)
aws route53 create-traffic-policy \
  --name complex-routing \
  --document file://traffic-policy.json

# [ATTENTION] Complexe, préférer console AWS Traffic Flow


═══════════════════════════════════════════════════════════════════════════════
[OK] DNSSEC - SÉCURITÉ DNS
═══════════════════════════════════════════════════════════════════════════════

# DNSSEC = Signature cryptographique DNS
# Protège contre DNS spoofing/poisoning

# ACTIVER DNSSEC
════════════════════════════════════════════════════════════════════════════════

# 1. Enable DNSSEC signing
aws route53 enable-hosted-zone-dnssec \
  --hosted-zone-id Z1234567890ABC

# 2. Obtenir Delegation Signer (DS) records
aws route53 get-dnssec \
  --hosted-zone-id Z1234567890ABC

# 3. Ajouter DS records chez registrar
# (Via interface registrar)

# DÉSACTIVER DNSSEC
════════════════════════════════════════════════════════════════════════════════

aws route53 disable-hosted-zone-dnssec \
  --hosted-zone-id Z1234567890ABC

# [ATTENTION] ATTENTION:
# DNSSEC augmente complexité
# Peut causer problèmes si mal configuré
# Recommandé seulement si nécessaire (haute sécurité)


═══════════════════════════════════════════════════════════════════════════════
[OK] QUERY LOGGING - LOGS REQUÊTES DNS
═══════════════════════════════════════════════════════════════════════════════

# Query Logging = Enregistrer toutes les requêtes DNS

# ACTIVER QUERY LOGGING
════════════════════════════════════════════════════════════════════════════════

# 1. Créer CloudWatch Log Group
aws logs create-log-group \
  --log-group-name /aws/route53/example.com

# 2. Créer resource policy pour Route 53
cat > log-policy.json << 'EOF'
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "route53.amazonaws.com"
      },
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/route53/example.com:*"
    }
  ]
}
EOF

aws logs put-resource-policy \
  --policy-name route53-query-logging \
  --policy-document file://log-policy.json

# 3. Créer query logging config
aws route53 create-query-logging-config \
  --hosted-zone-id Z1234567890ABC \
  --cloud-watch-logs-log-group-arn arn:aws:logs:us-east-1:123456789012:log-group:/aws/route53/example.com

# LOGS CONTIENNENT:
# - Timestamp
# - Hosted Zone ID
# - Query Name (ex: www.example.com)
# - Query Type (A, AAAA, CNAME, etc.)
# - Response Code (NOERROR, NXDOMAIN, etc.)
# - Query Source IP
# - Edge Location

# EXEMPLE LOG:
# {
#   "version": "1.0",
#   "timestamp": "2024-01-15T10:45:00Z",
#   "query_name": "www.example.com",
#   "query_type": "A",
#   "response_code": "NOERROR",
#   "query_source": "203.0.113.50",
#   "edge_location": "IAD50"
# }

# LISTER QUERY LOGGING CONFIGS
════════════════════════════════════════════════════════════════════════════════

aws route53 list-query-logging-configs

# SUPPRIMER QUERY LOGGING
════════════════════════════════════════════════════════════════════════════════

aws route53 delete-query-logging-config \
  --id qlc-12345678-1234-1234-1234-123456789012

# [ATTENTION] COÛTS:
# Logs CloudWatch = $0.50/GB
# Peut devenir cher pour sites à fort trafic!


═══════════════════════════════════════════════════════════════════════════════
[OK] RESOLVER - DNS PRIVÉ HYBRIDE
═══════════════════════════════════════════════════════════════════════════════

# Resolver = Connecter DNS on-premise <-> AWS VPC

# CONCEPTS:
# - Inbound Endpoint: VPC -> On-premise (requêtes entrantes)
# - Outbound Endpoint: On-premise -> VPC (requêtes sortantes)
# - Forwarding Rules: Quels domaines forwarded

# CRÉER INBOUND ENDPOINT
════════════════════════════════════════════════════════════════════════════════

# Permettre on-premise de résoudre noms privés AWS

aws route53resolver create-resolver-endpoint \
  --name vpc-inbound-endpoint \
  --direction INBOUND \
  --security-group-ids sg-0123456789abcdef0 \
  --ip-addresses SubnetId=subnet-1,Ip=10.0.1.10 SubnetId=subnet-2,Ip=10.0.2.10

# EXPLICATION:
# Direction INBOUND = Recevoir requêtes de l'extérieur
# IPs: Route 53 Resolver endpoints dans VPC
# On-premise DNS forward vers ces IPs

# CRÉER OUTBOUND ENDPOINT
════════════════════════════════════════════════════════════════════════════════

# Permettre VPC de résoudre noms on-premise

aws route53resolver create-resolver-endpoint \
  --name vpc-outbound-endpoint \
  --direction OUTBOUND \
  --security-group-ids sg-0123456789abcdef0 \
  --ip-addresses SubnetId=subnet-1 SubnetId=subnet-2

# CRÉER FORWARDING RULE
════════════════════════════════════════════════════════════════════════════════

# Forward requêtes pour domaine vers serveurs on-premise

aws route53resolver create-resolver-rule \
  --creator-request-id $(date +%s) \
  --name forward-to-onprem \
  --rule-type FORWARD \
  --domain-name internal.company.com \
  --target-ips Ip=192.168.1.10,Port=53 Ip=192.168.1.11,Port=53 \
  --resolver-endpoint-id rslvr-out-abc123

# Associer rule au VPC
aws route53resolver associate-resolver-rule \
  --resolver-rule-id rslvr-rr-abc123 \
  --vpc-id vpc-0123456789abcdef0


═══════════════════════════════════════════════════════════════════════════════
[OK] MONITORING & MÉTRIQUES
═══════════════════════════════════════════════════════════════════════════════

# MÉTRIQUES CLOUDWATCH
════════════════════════════════════════════════════════════════════════════════

# Route 53 publie automatiquement métriques

# Voir nombre de requêtes DNS
aws cloudwatch get-metric-statistics \
  --namespace AWS/Route53 \
  --metric-name QueryCount \
  --dimensions Name=HostedZoneId,Value=Z1234567890ABC \
  --start-time 2024-01-15T00:00:00Z \
  --end-time 2024-01-16T00:00:00Z \
  --period 3600 \
  --statistics Sum

# MÉTRIQUES DISPONIBLES:
# - QueryCount: Nombre requêtes DNS
# - HealthCheckStatus: Statut health checks (0=unhealthy, 1=healthy)
# - HealthCheckPercentageHealthy: % healthy
# - ConnectionTime: Temps connexion (health checks TCP)
# - TimeToFirstByte: TTFB (health checks HTTP/HTTPS)
# - SSLHandshakeTime: Temps SSL handshake

# ALARMES CLOUDWATCH
════════════════════════════════════════════════════════════════════════════════

# Alarme si health check échoue
aws cloudwatch put-metric-alarm \
  --alarm-name route53-healthcheck-failed \
  --alarm-description "Health check unhealthy" \
  --namespace AWS/Route53 \
  --metric-name HealthCheckStatus \
  --dimensions Name=HealthCheckId,Value=12345678-1234-1234-1234-123456789012 \
  --statistic Minimum \
  --period 60 \
  --evaluation-periods 2 \
  --threshold 1 \
  --comparison-operator LessThanThreshold \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:ops-alerts

# Alarme si trop de requêtes DNS (anomalie)
aws cloudwatch put-metric-alarm \
  --alarm-name route53-high-query-count \
  --alarm-description "Unusual DNS query volume" \
  --namespace AWS/Route53 \
  --metric-name QueryCount \
  --dimensions Name=HostedZoneId,Value=Z1234567890ABC \
  --statistic Sum \
  --period 300 \
  --evaluation-periods 1 \
  --threshold 1000000 \
  --comparison-operator GreaterThanThreshold \
  --alarm-actions arn:aws:sns:us-east-1:123456789012:ops-alerts


═══════════════════════════════════════════════════════════════════════════════
[OK] BEST PRACTICES ROUTE 53
═══════════════════════════════════════════════════════════════════════════════

# [OK] TTL (TIME TO LIVE)
════════════════════════════════════════════════════════════════════════════════

1. TTL COURT (60-300s)
   - Changements fréquents
   - Failover rapide
   - Coût: Plus de requêtes DNS

2. TTL LONG (3600-86400s)
   - Configuration stable
   - Économise requêtes DNS ($$$)
   - Changements lents à propager

3. RECOMMANDATION:
   - Production stable: 300-3600s
   - Avant migration: 60s (permet changement rapide)
   - Après migration stable: Augmenter à 3600s

# [OK] ALIAS vs CNAME
════════════════════════════════════════════════════════════════════════════════

TOUJOURS préférer ALIAS pour ressources AWS:

ALIAS:
[OK] Gratuit (pas de frais query)
[OK] Fonctionne pour apex/root (example.com)
[OK] Plus rapide (1 query vs 2)
[OK] Health checks intégrés

CNAME:
[X] Payant ($0.40/million queries)
[X] Ne fonctionne PAS pour apex
[X] Plus lent (2 queries)

# [OK] HEALTH CHECKS
════════════════════════════════════════════════════════════════════════════════

1. TOUJOURS configurer health checks pour:
   - Failover routing
   - Weighted routing (production)
   - Multivalue routing

2. ENDPOINT HEALTH CHECK:
   - Path léger: /health, /ping
   - Pas de DB queries lourdes
   - Retourner 200 OK si healthy

3. FAILURE THRESHOLD:
   - 3 = Standard (bon équilibre)
   - 2 = Plus sensible (failover rapide)
   - 5 = Moins sensible (éviter faux positifs)

4. REQUEST INTERVAL:
   - 30s = Standard ($0.50/mois)
   - 10s = Fast ($1/mois) - Pour failover critique

# [OK] ROUTING POLICIES
════════════════════════════════════════════════════════════════════════════════

CHOISIR SELON USE CASE:

- Simple: Petit site, 1 serveur
- Weighted: A/B testing, canary deployment
- Latency: Multi-région, performance
- Failover: Disaster recovery, HA
- Geolocation: Conformité, localization
- Multivalue: Load balancing simple + health checks

COMBINER pour cas complexes:
Geolocation -> Latency -> Failover

# [OK] SÉCURITÉ
════════════════════════════════════════════════════════════════════════════════

1. DOMAIN LOCKING:
   - Activer transfer lock
   - Empêche transfert non autorisé

2. PRIVACY PROTECTION:
   - Cacher infos WHOIS
   - Éviter spam/phishing

3. MFA:
   - Activer MFA sur compte AWS
   - Protection contre hijacking

4. DNSSEC:
   - Si haute sécurité requise
   - Finance, gouvernement, santé

# [OK] COÛTS
════════════════════════════════════════════════════════════════════════════════

OPTIMISER COÛTS:

1. ALIAS vs CNAME:
   - Alias = GRATUIT
   - CNAME = $0.40/million
   - Économie significative!

2. HEALTH CHECKS:
   - Seulement où nécessaire
   - $0.50/mois par check
   - 10 checks = $5/mois

3. QUERY LOGGING:
   - Désactiver si pas utilisé
   - Peut coûter cher (CloudWatch Logs)

4. HOSTED ZONES:
   - Consolider domaines si possible
   - $0.50/mois par zone

# [OK] HAUTE DISPONIBILITÉ
════════════════════════════════════════════════════════════════════════════════

1. MULTIPLE RÉGIONS:
   - Au moins 2 régions AWS
   - Latency ou Failover routing

2. HEALTH CHECKS:
   - Surveiller TOUS les endpoints
   - Failover automatique

3. TTL APPROPRIÉ:
   - 60-300s pour failover rapide
   - Pas trop court (coût)

4. TESTED REGULARLY:
   - Tester failover mensuellement
   - Simuler pannes


═══════════════════════════════════════════════════════════════════════════════
[OK] TROUBLESHOOTING ROUTE 53
═══════════════════════════════════════════════════════════════════════════════

# PROBLÈME 1: DNS ne résout pas
════════════════════════════════════════════════════════════════════════════════

# Vérifier propagation nameservers
dig example.com NS
nslookup -type=NS example.com

# Vérifier chez registrar:
# Nameservers doivent être ceux de Route 53
# ns-123.awsdns-12.com, etc.

# Délai propagation: 5 min - 48h (généralement < 1h)

# PROBLÈME 2: Record existe mais ne résout pas
════════════════════════════════════════════════════════════════════════════════

# Vérifier record créé
aws route53 list-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --query "ResourceRecordSets[?Name=='www.example.com.']"

# Vérifier TTL:
# Si changé récemment, attendre TTL expirer

# Flush DNS cache local:
# Windows: ipconfig /flushdns
# macOS: sudo dscacheutil -flushcache
# Linux: sudo systemd-resolve --flush-caches

# PROBLÈME 3: Failover ne fonctionne pas
════════════════════════════════════════════════════════════════════════════════

# Vérifier health check status
aws route53 get-health-check-status \
  --health-check-id 12345678-1234-1234-1234-123456789012

# Causes communes:
# 1. Health check endpoint bloqué (security group)
# 2. Path /health n'existe pas
# 3. Retourne 500 au lieu de 200
# 4. Timeout trop court

# Solution:
# Tester endpoint manuellement:
curl -I https://api.example.com/health

# PROBLÈME 4: Weighted routing inégal
════════════════════════════════════════════════════════════════════════════════

# Vérifier poids configurés
aws route53 list-resource-record-sets \
  --hosted-zone-id Z1234567890ABC \
  --query "ResourceRecordSets[?Name=='www.example.com.'].[SetIdentifier,Weight]"

# [ATTENTION] IMPORTANT:
# Distribution pas exacte à cause TTL et cache
# Peut prendre plusieurs heures pour stabiliser

# PROBLÈME 5: Coûts élevés
════════════════════════════════════════════════════════════════════════════════

# Vérifier nombre de queries
aws cloudwatch get-metric-statistics \
  --namespace AWS/Route53 \
  --metric-name QueryCount \
  --dimensions Name=HostedZoneId,Value=Z1234567890ABC \
  --start-time 2024-01-01T00:00:00Z \
  --end-time 2024-01-31T00:00:00Z \
  --period 86400 \
  --statistics Sum

# Solutions:
# 1. Augmenter TTL (réduire queries)
# 2. Utiliser ALIAS au lieu de CNAME
# 3. Désactiver query logging si pas nécessaire


═══════════════════════════════════════════════════════════════════════════════
[OK] EXEMPLES COMPLETS PAR CAS D'USAGE
═══════════════════════════════════════════════════════════════════════════════

# EXEMPLE 1: SITE SIMPLE (1 SERVEUR)
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Configuration DNS pour site simple

ZONE_ID="Z1234567890ABC"

# Apex (example.com) -> Serveur
cat > apex.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "example.com",
      "Type": "A",
      "TTL": 300,
      "ResourceRecords": [{"Value": "203.0.113.25"}]
    }
  }]
}
EOF

# WWW -> Serveur
cat > www.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "www.example.com",
      "Type": "A",
      "TTL": 300,
      "ResourceRecords": [{"Value": "203.0.113.25"}]
    }
  }]
}
EOF

# Mail (Gmail)
cat > mail.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "example.com",
      "Type": "MX",
      "TTL": 3600,
      "ResourceRecords": [
        {"Value": "1 aspmx.l.google.com"},
        {"Value": "5 alt1.aspmx.l.google.com"},
        {"Value": "5 alt2.aspmx.l.google.com"}
      ]
    }
  }]
}
EOF

# SPF record
cat > spf.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "example.com",
      "Type": "TXT",
      "TTL": 3600,
      "ResourceRecords": [
        {"Value": "\"v=spf1 include:_spf.google.com ~all\""}
      ]
    }
  }]
}
EOF

# Appliquer tous
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://apex.json
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://www.json
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://mail.json
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://spf.json


# EXEMPLE 2: SITE AVEC ALB (ALIAS)
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Site avec Application Load Balancer

ZONE_ID="Z1234567890ABC"
ALB_DNS="my-alb-1234567890.us-east-1.elb.amazonaws.com"
ALB_ZONE_ID="Z35SXDOTRQ7X7K"  # us-east-1 ALB

# Apex -> ALB
cat > apex-alb.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "example.com",
      "Type": "A",
      "AliasTarget": {
        "HostedZoneId": "'$ALB_ZONE_ID'",
        "DNSName": "'$ALB_DNS'",
        "EvaluateTargetHealth": true
      }
    }
  }]
}
EOF

# WWW -> ALB
cat > www-alb.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "www.example.com",
      "Type": "A",
      "AliasTarget": {
        "HostedZoneId": "'$ALB_ZONE_ID'",
        "DNSName": "'$ALB_DNS'",
        "EvaluateTargetHealth": true
      }
    }
  }]
}
EOF

aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://apex-alb.json
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://www-alb.json


# EXEMPLE 3: MULTI-RÉGION AVEC FAILOVER
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Haute disponibilité multi-région

ZONE_ID="Z1234567890ABC"

# Health checks
HC_US=$(aws route53 create-health-check \
  --health-check-config '{
    "Type": "HTTPS",
    "ResourcePath": "/health",
    "FullyQualifiedDomainName": "us.example.com",
    "Port": 443,
    "RequestInterval": 30,
    "FailureThreshold": 3
  }' \
  --caller-reference $(date +%s) \
  --query 'HealthCheck.Id' \
  --output text)

HC_EU=$(aws route53 create-health-check \
  --health-check-config '{
    "Type": "HTTPS",
    "ResourcePath": "/health",
    "FullyQualifiedDomainName": "eu.example.com",
    "Port": 443,
    "RequestInterval": 30,
    "FailureThreshold": 3
  }' \
  --caller-reference $(date +%s) \
  --query 'HealthCheck.Id' \
  --output text)

# Primary (US)
cat > failover-primary.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "www.example.com",
      "Type": "A",
      "SetIdentifier": "US-Primary",
      "Failover": "PRIMARY",
      "HealthCheckId": "'$HC_US'",
      "AliasTarget": {
        "HostedZoneId": "Z35SXDOTRQ7X7K",
        "DNSName": "us-alb.us-east-1.elb.amazonaws.com",
        "EvaluateTargetHealth": true
      }
    }
  }]
}
EOF

# Secondary (EU)
cat > failover-secondary.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "www.example.com",
      "Type": "A",
      "SetIdentifier": "EU-Secondary",
      "Failover": "SECONDARY",
      "HealthCheckId": "'$HC_EU'",
      "AliasTarget": {
        "HostedZoneId": "Z32O12XQLNTSW2",
        "DNSName": "eu-alb.eu-west-1.elb.amazonaws.com",
        "EvaluateTargetHealth": true
      }
    }
  }]
}
EOF

aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://failover-secondary.json

echo "Failover configuré:"
echo "- Primary: US (Health Check: $HC_US)"
echo "- Secondary: EU (Health Check: $HC_EU)"


# EXEMPLE 4: GLOBAL AVEC GEOLOCATION
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Routage global par localisation

ZONE_ID="Z1234567890ABC"

# Europe
cat > geo-europe.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "www.example.com",
      "Type": "A",
      "SetIdentifier": "Europe",
      "GeoLocation": {"ContinentCode": "EU"},
      "AliasTarget": {
        "HostedZoneId": "Z32O12XQLNTSW2",
        "DNSName": "eu-alb.eu-west-1.elb.amazonaws.com",
        "EvaluateTargetHealth": true
      }
    }
  }]
}
EOF

# Asie
cat > geo-asia.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "www.example.com",
      "Type": "A",
      "SetIdentifier": "Asia",
      "GeoLocation": {"ContinentCode": "AS"},
      "AliasTarget": {
        "HostedZoneId": "Z14GRHDCWA56QT",
        "DNSName": "asia-alb.ap-southeast-1.elb.amazonaws.com",
        "EvaluateTargetHealth": true
      }
    }
  }]
}
EOF

# Amérique du Nord
cat > geo-northamerica.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "www.example.com",
      "Type": "A",
      "SetIdentifier": "North-America",
      "GeoLocation": {"ContinentCode": "NA"},
      "AliasTarget": {
        "HostedZoneId": "Z35SXDOTRQ7X7K",
        "DNSName": "us-alb.us-east-1.elb.amazonaws.com",
        "EvaluateTargetHealth": true
      }
    }
  }]
}
EOF

# Default (reste du monde)
cat > geo-default.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "www.example.com",
      "Type": "A",
      "SetIdentifier": "Default",
      "GeoLocation": {"ContinentCode": "*"},
      "AliasTarget": {
        "HostedZoneId": "Z35SXDOTRQ7X7K",
        "DNSName": "us-alb.us-east-1.elb.amazonaws.com",
        "EvaluateTargetHealth": true
      }
    }
  }]
}
EOF

aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://geo-europe.json
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://geo-asia.json
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://geo-northamerica.json
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://geo-default.json

echo "Geolocation configuré globalement"


# EXEMPLE 5: A/B TESTING AVEC WEIGHTED
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# A/B Testing: 90% version A, 10% version B

ZONE_ID="Z1234567890ABC"

# Version A (90%)
cat > weighted-a.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "www.example.com",
      "Type": "A",
      "SetIdentifier": "Version-A",
      "Weight": 90,
      "TTL": 60,
      "ResourceRecords": [{"Value": "203.0.113.25"}]
    }
  }]
}
EOF

# Version B (10%)
cat > weighted-b.json << 'EOF'
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "www.example.com",
      "Type": "A",
      "SetIdentifier": "Version-B",
      "Weight": 10,
      "TTL": 60,
      "ResourceRecords": [{"Value": "203.0.113.26"}]
    }
  }]
}
EOF

aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://weighted-a.json
aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://weighted-b.json

echo "A/B Testing: 90% A, 10% B"

# Après analyse, basculer progressivement:
# 90/10 -> 80/20 -> 70/30 -> 50/50 -> 30/70 -> 10/90 -> 0/100


# EXEMPLE 6: CANARY DEPLOYMENT
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Canary: Déployer nouvelle version progressivement

ZONE_ID="Z1234567890ABC"

function set_weights() {
    OLD_WEIGHT=$1
    NEW_WEIGHT=$2
    
    cat > weighted-old.json << EOF
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "api.example.com",
      "Type": "A",
      "SetIdentifier": "Old-Version",
      "Weight": $OLD_WEIGHT,
      "TTL": 60,
      "ResourceRecords": [{"Value": "203.0.113.25"}]
    }
  }]
}
EOF

    cat > weighted-new.json << EOF
{
  "Changes": [{
    "Action": "UPSERT",
    "ResourceRecordSet": {
      "Name": "api.example.com",
      "Type": "A",
      "SetIdentifier": "New-Version",
      "Weight": $NEW_WEIGHT,
      "TTL": 60,
      "ResourceRecords": [{"Value": "203.0.113.26"}]
    }
  }]
}
EOF

    aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://weighted-old.json
    aws route53 change-resource-record-sets --hosted-zone-id $ZONE_ID --change-batch file://weighted-new.json
    
    echo "Weights updated: Old=$OLD_WEIGHT%, New=$NEW_WEIGHT%"
}

# Phase 1: 100% old
set_weights 100 0
sleep 60

# Phase 2: 95% old, 5% new (canary)
set_weights 95 5
echo "Monitoring canary for 30 minutes..."
sleep 1800

# Phase 3: Si métriques OK, continuer
set_weights 50 50
sleep 600

# Phase 4: Finaliser
set_weights 0 100
echo "Deployment complete: 100% new version"


═══════════════════════════════════════════════════════════════════════════════
[OK] MIGRATION VERS ROUTE 53
═══════════════════════════════════════════════════════════════════════════════

# PROCÉDURE MIGRATION COMPLÈTE
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Migrer domaine vers Route 53 sans downtime

DOMAIN="example.com"

echo "=== MIGRATION ROUTE 53: $DOMAIN ==="

# ÉTAPE 1: RÉDUIRE TTL CHEZ ANCIEN PROVIDER
echo "ÉTAPE 1: Réduire TTL à 60s chez ancien provider"
echo "Attendre propagation (24-48h si TTL était élevé)"
read -p "TTL réduit? (y/n) " -n 1 -r
echo

# ÉTAPE 2: CRÉER HOSTED ZONE ROUTE 53
echo "ÉTAPE 2: Création hosted zone..."
ZONE_ID=$(aws route53 create-hosted-zone \
  --name $DOMAIN \
  --caller-reference $(date +%s) \
  --query 'HostedZone.Id' \
  --output text)

echo "Hosted Zone créée: $ZONE_ID"

# Obtenir nameservers
NAMESERVERS=$(aws route53 get-hosted-zone \
  --id $ZONE_ID \
  --query 'DelegationSet.NameServers' \
  --output text)

echo "Nameservers Route 53:"
echo "$NAMESERVERS"

# ÉTAPE 3: EXPORTER RECORDS ANCIEN PROVIDER
echo "ÉTAPE 3: Exporter tous les records de l'ancien provider"
echo "Format: Name, Type, TTL, Value"
echo "Créer fichier: records-export.csv"
read -p "Export prêt? (y/n) " -n 1 -r
echo

# ÉTAPE 4: IMPORTER RECORDS DANS ROUTE 53
echo "ÉTAPE 4: Import records..."

# Exemple avec CSV (adapter selon format)
while IFS=',' read -r name type ttl value; do
    cat > temp-record.json << EOF
{
  "Changes": [{
    "Action": "CREATE",
    "ResourceRecordSet": {
      "Name": "$name",
      "Type": "$type",
      "TTL": $ttl,
      "ResourceRecords": [{"Value": "$value"}]
    }
  }]
}
EOF
    
    aws route53 change-resource-record-sets \
      --hosted-zone-id $ZONE_ID \
      --change-batch file://temp-record.json
done < records-export.csv

echo "Records importés"

# ÉTAPE 5: VÉRIFIER RECORDS
echo "ÉTAPE 5: Vérification records..."
aws route53 list-resource-record-sets \
  --hosted-zone-id $ZONE_ID \
  --query 'ResourceRecordSets[*].[Name,Type,TTL]' \
  --output table

read -p "Records corrects? (y/n) " -n 1 -r
echo

# ÉTAPE 6: TESTER AVEC NAMESERVERS ROUTE 53
echo "ÉTAPE 6: Test avec nameservers Route 53..."
NS=$(echo "$NAMESERVERS" | head -1)
echo "Test: dig @$NS $DOMAIN"
dig @$NS $DOMAIN

read -p "Tests OK? (y/n) " -n 1 -r
echo

# ÉTAPE 7: CHANGER NAMESERVERS CHEZ REGISTRAR
echo "ÉTAPE 7: CHANGEMENT NAMESERVERS"
echo "Aller chez registrar et configurer:"
echo "$NAMESERVERS"
echo ""
echo "[ATTENTION] ATTENTION: Après ce changement, propagation 5min-48h"
read -p "Nameservers changés chez registrar? (y/n) " -n 1 -r
echo

# ÉTAPE 8: SURVEILLER PROPAGATION
echo "ÉTAPE 8: Surveillance propagation..."
echo "Vérifier avec: https://www.whatsmydns.net/"

# ÉTAPE 9: AUGMENTER TTL APRÈS STABILISATION
echo "ÉTAPE 9: Après 24-48h stabilisation, augmenter TTL à 3600s"

echo "=== MIGRATION TERMINÉE ==="


═══════════════════════════════════════════════════════════════════════════════
[OK] OUTILS UTILES
═══════════════════════════════════════════════════════════════════════════════

# VÉRIFIER DNS
════════════════════════════════════════════════════════════════════════════════

# dig (Linux/macOS)
dig example.com
dig example.com A
dig example.com MX
dig @8.8.8.8 example.com  # Via serveur spécifique

# nslookup (Windows/Linux/macOS)
nslookup example.com
nslookup -type=MX example.com
nslookup example.com 8.8.8.8

# host (Linux/macOS)
host example.com
host -t MX example.com

# VÉRIFIER PROPAGATION GLOBALE
════════════════════════════════════════════════════════════════════════════════

# Online tools:
# https://www.whatsmydns.net/
# https://dnschecker.org/
# https://www.dnswatch.info/

# CLI multiple locations
curl "https://dns.google/resolve?name=example.com&type=A"

# TESTER HEALTH CHECK
════════════════════════════════════════════════════════════════════════════════

# Simuler health check Route 53
curl -I https://api.example.com/health
curl -w "@curl-format.txt" -o /dev/null -s https://api.example.com/health

# curl-format.txt:
# time_namelookup: %{time_namelookup}\n
# time_connect: %{time_connect}\n
# time_appconnect: %{time_appconnect}\n
# time_pretransfer: %{time_pretransfer}\n
# time_starttransfer: %{time_starttransfer}\n
# time_total: %{time_total}\n
# http_code: %{http_code}\n

# SCRIPT MONITORING CONTINU
════════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Monitor DNS resolution continu

DOMAIN="www.example.com"

while true; do
    RESULT=$(dig +short $DOMAIN)
    TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
    
    if [ -z "$RESULT" ]; then
        echo "[$TIMESTAMP] [X] DNS FAILED"
    else
        echo "[$TIMESTAMP] [OK] DNS OK: $RESULT"
    fi
    
    sleep 60
done


═══════════════════════════════════════════════════════════════════════════════
[OK] CALCULATEUR COÛTS ROUTE 53
═══════════════════════════════════════════════════════════════════════════════

#!/bin/bash
# Calculateur coûts Route 53

# INPUTS
HOSTED_ZONES=2
DNS_QUERIES_MILLION=10  # Millions de queries/mois
HEALTH_CHECKS=5
ALIAS_QUERIES_MILLION=5  # Alias = gratuit

# PRICING
HOSTED_ZONE_COST=0.50
QUERY_COST_FIRST_BILLION=0.40  # Per million
HEALTH_CHECK_COST=0.50

# CALCUL
HOSTED_ZONES_TOTAL=$(echo "$HOSTED_ZONES * $HOSTED_ZONE_COST" | bc)

# Queries (premiers milliard)
if [ $DNS_QUERIES_MILLION -le 1000 ]; then
    QUERIES_TOTAL=$(echo "$DNS_QUERIES_MILLION * $QUERY_COST_FIRST_BILLION" | bc)
else
    FIRST_BILLION=$(echo "1000 * $QUERY_COST_FIRST_BILLION" | bc)
    REMAINING=$(echo "($DNS_QUERIES_MILLION - 1000) * 0.20" | bc)
    QUERIES_TOTAL=$(echo "$FIRST_BILLION + $REMAINING" | bc)
fi

HEALTH_CHECKS_TOTAL=$(echo "$HEALTH_CHECKS * $HEALTH_CHECK_COST" | bc)

TOTAL=$(echo "$HOSTED_ZONES_TOTAL + $QUERIES_TOTAL + $HEALTH_CHECKS_TOTAL" | bc)

echo "=== COÛTS ROUTE 53 MENSUELS ==="
echo ""
echo "Hosted Zones: $HOSTED_ZONES × \$$HOSTED_ZONE_COST = \$$HOSTED_ZONES_TOTAL"
echo "DNS Queries: $DNS_QUERIES_MILLION million × \$$QUERY_COST_FIRST_BILLION = \$$QUERIES_TOTAL"
echo "Alias Queries: $ALIAS_QUERIES_MILLION million × \$0.00 = \$0.00 (GRATUIT)"
echo "Health Checks: $HEALTH_CHECKS × \$$HEALTH_CHECK_COST = \$$HEALTH_CHECKS_TOTAL"
echo ""
echo "TOTAL: \$$TOTAL/mois"
echo ""
echo "[IDEE] OPTIMISATIONS:"
echo "- Utiliser ALIAS au lieu de CNAME (gratuit)"
echo "- Augmenter TTL (réduire queries)"
echo "- Health checks seulement où nécessaire"


═══════════════════════════════════════════════════════════════════════════════
[OK] COMMANDES RAPIDES - QUICK REFERENCE
═══════════════════════════════════════════════════════════════════════════════

# Hosted Zones
aws route53 create-hosted-zone --name example.com --caller-reference $(date +%s)
aws route53 list-hosted-zones
aws route53 get-hosted-zone --id Z1234567890ABC
aws route53 delete-hosted-zone --id Z1234567890ABC

# Records
aws route53 change-resource-record-sets --hosted-zone-id Z1234567890ABC --change-batch file://record.json
aws route53 list-resource-record-sets --hosted-zone-id Z1234567890ABC

# Health Checks
aws route53 create-health-check --health-check-config '{...}' --caller-reference $(date +%s)
aws route53 list-health-checks
aws route53 get-health-check-status --health-check-id 12345678-1234-1234-1234-123456789012
aws route53 delete-health-check --health-check-id 12345678-1234-1234-1234-123456789012

# Domain Registration
aws route53domains check-domain-availability --domain-name example.com
aws route53domains list-domains
aws route53domains register-domain --domain-name example.com --duration-in-years 1 ...

# Query Logging
aws route53 create-query-logging-config --hosted-zone-id Z1234567890ABC \
  --cloud-watch-logs-log-group-arn arn:aws:logs:...
aws route53 list-query-logging-configs
aws route53 delete-query-logging-config --id qlc-...


═══════════════════════════════════════════════════════════════════════════════
[OK] RESSOURCES UTILES
═══════════════════════════════════════════════════════════════════════════════

# Documentation officielle
https://docs.aws.amazon.com/route53/

# Routing Policies Guide
https://docs.aws.amazon.com/route53/latest/DeveloperGuide/routing-policy.html

# Health Checks
https://docs.aws.amazon.com/route53/latest/DeveloperGuide/health-checks.html

# Pricing
https://aws.amazon.com/route53/pricing/

# Hosted Zone IDs (ALB, CloudFront, etc.)
https://docs.aws.amazon.com/general/latest/gr/elb.html

# DNS Checkers
https://www.whatsmydns.net/
https://dnschecker.org/
https://mxtoolbox.com/

# WHOIS Lookup
https://www.whois.com/

# DNS Propagation
https://www.dnswatch.info/

# AWS Service Limits
https://docs.aws.amazon.com/route53/latest/DeveloperGuide/DNSLimitations.html


═══════════════════════════════════════════════════════════════════════════════
[OK] GLOSSARY
═══════════════════════════════════════════════════════════════════════════════

A RECORD: Maps domain name to IPv4 address
AAAA RECORD: Maps domain name to IPv6 address
ALIAS: AWS special record (like CNAME but better)
APEX/ROOT: Domain without subdomain (example.com vs www.example.com)
CNAME: Canonical name (alias to another domain)
DNS: Domain Name System
DNSSEC: DNS Security Extensions
FQDN: Fully Qualified Domain Name (with trailing dot)
HOSTED ZONE: Container for DNS records
MX RECORD: Mail exchange servers
NAMESERVER: Server that holds DNS records
NS RECORD: Nameserver record
REGISTRAR: Company that sells domain names
SOA: Start of Authority record
TXT RECORD: Text record (verification, SPF, etc.)
TTL: Time To Live (cache duration)
WHOIS: Database of domain ownership


═══════════════════════════════════════════════════════════════════════════════
[OK] CHECKLIST DÉPLOIEMENT PRODUCTION
═══════════════════════════════════════════════════════════════════════════════

AVANT MISE EN PRODUCTION:
[ ] Hosted zone créée
[ ] Tous les records importés (A, CNAME, MX, TXT)
[ ] TTL configurés (300-3600s)
[ ] ALIAS utilisés pour ressources AWS
[ ] Health checks configurés
[ ] Routing policies testées
[ ] Nameservers notés
[ ] SPF/DMARC records ajoutés (email)
[ ] SSL certificates validés (if using TXT validation)
[ ] Tests DNS avec dig/nslookup
[ ] Documentation mise à jour

CHANGEMENT NAMESERVERS:
[ ] TTL réduit à 60s (24-48h avant)
[ ] Backup configuration actuelle
[ ] Nameservers changés chez registrar
[ ] Propagation surveillée (whatsmydns.net)
[ ] Tests depuis multiple locations
[ ] Email fonctionnel vérifié
[ ] Applications testées
[ ] TTL augmenté après stabilisation (24-48h)

APRÈS MISE EN PRODUCTION:
[ ] Monitoring actif (CloudWatch)
[ ] Health checks surveillés
[ ] Query logging analysé (si activé)
[ ] Coûts suivis
[ ] Documentation finalisée
[ ] Équipe formée sur failover
[ ] Procédure rollback documentée
[ ] Tests disaster recovery planifiés


═══════════════════════════════════════════════════════════════════════════════
[OK] CLOUDFRONT - CDN (CONTENT DELIVERY NETWORK)
═══════════════════════════════════════════════════════════════════════════════

CloudFront = CDN global pour distribuer contenu avec faible latence

# === CRÉER DISTRIBUTION ===

# distribution-config.json
{
  "CallerReference": "$(date +%s)",
  "Comment": "My CloudFront distribution",
  "Enabled": true,
  "Origins": {
    "Quantity": 1,
    "Items": [
      {
        "Id": "S3-my-bucket",
        "DomainName": "my-bucket.s3.amazonaws.com",
        "S3OriginConfig": {
          "OriginAccessIdentity": ""
        }
      }
    ]
  },
  "DefaultCacheBehavior": {
    "TargetOriginId": "S3-my-bucket",
    "ViewerProtocolPolicy": "redirect-to-https",
    "AllowedMethods": {
      "Quantity": 2,
      "Items": ["GET", "HEAD"]
    },
    "ForwardedValues": {
      "QueryString": false,
      "Cookies": {"Forward": "none"}
    },
    "MinTTL": 0,
    "DefaultTTL": 86400,
    "MaxTTL": 31536000
  }
}

# Créer distribution
aws cloudfront create-distribution \
  --distribution-config file://distribution-config.json

# Lister distributions
aws cloudfront list-distributions

# Obtenir distribution
aws cloudfront get-distribution --id E1234567890ABC

# === INVALIDATION (PURGER CACHE) ===

# Invalider fichiers spécifiques
aws cloudfront create-invalidation \
  --distribution-id E1234567890ABC \
  --paths /index.html /images/*

# Invalider tout le cache
aws cloudfront create-invalidation \
  --distribution-id E1234567890ABC \
  --paths "/*"

# Lister invalidations
aws cloudfront list-invalidations --distribution-id E1234567890ABC

# === ORIGIN ACCESS IDENTITY (OAI) ===

# Créer OAI pour sécuriser S3
aws cloudfront create-cloud-front-origin-access-identity \
  --cloud-front-origin-access-identity-config '{
    "CallerReference": "my-oai-'$(date +%s)'",
    "Comment": "OAI for my-bucket"
  }'

# Mettre à jour bucket policy S3 pour autoriser OAI
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity E1234567890ABC"
      },
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*"
    }
  ]
}


═══════════════════════════════════════════════════════════════════════════════
[OK] SQS (SIMPLE QUEUE SERVICE) - FILES D'ATTENTE
═══════════════════════════════════════════════════════════════════════════════

SQS = Service de file d'attente de messages (communication asynchrone)
2 types: Standard (best effort ordering) et FIFO (ordre garanti)

# === CRÉER QUEUE ===

# Créer queue standard
aws sqs create-queue --queue-name my-queue

# Output: {"QueueUrl": "https://sqs.us-east-1.amazonaws.com/123456789012/my-queue"}

# Créer queue FIFO
aws sqs create-queue \
  --queue-name my-queue.fifo \
  --attributes FifoQueue=true,ContentBasedDeduplication=true

# Créer avec attributs custom
aws sqs create-queue \
  --queue-name my-queue \
  --attributes '{
    "DelaySeconds": "0",
    "MaximumMessageSize": "262144",
    "MessageRetentionPeriod": "345600",
    "ReceiveMessageWaitTimeSeconds": "0",
    "VisibilityTimeout": "30"
  }'

# Lister queues
aws sqs list-queues

# Lister queues par préfixe
aws sqs list-queues --queue-name-prefix my

# Obtenir URL de queue
aws sqs get-queue-url --queue-name my-queue

# === ENVOYER MESSAGES ===

# Envoyer message
aws sqs send-message \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --message-body "Hello from SQS"

# Envoyer avec attributs
aws sqs send-message \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --message-body "Order completed" \
  --message-attributes '{
    "OrderId": {"DataType": "String", "StringValue": "12345"},
    "Priority": {"DataType": "Number", "StringValue": "1"}
  }'

# Envoyer avec délai
aws sqs send-message \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --message-body "Delayed message" \
  --delay-seconds 60

# Envoyer message FIFO
aws sqs send-message \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue.fifo \
  --message-body "FIFO message" \
  --message-group-id "group1" \
  --message-deduplication-id "$(uuidgen)"

# Envoyer batch (jusqu'à 10 messages)
aws sqs send-message-batch \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --entries '[
    {"Id": "1", "MessageBody": "Message 1"},
    {"Id": "2", "MessageBody": "Message 2"},
    {"Id": "3", "MessageBody": "Message 3"}
  ]'

# === RECEVOIR MESSAGES ===

# Recevoir message
aws sqs receive-message \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue

# Recevoir avec attributs
aws sqs receive-message \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --attribute-names All \
  --message-attribute-names All

# Recevoir plusieurs messages (max 10)
aws sqs receive-message \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --max-number-of-messages 10

# Long polling (attendre jusqu'à 20s si queue vide)
aws sqs receive-message \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --wait-time-seconds 20

# === SUPPRIMER MESSAGES ===

# Supprimer message (après traitement)
aws sqs delete-message \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --receipt-handle "AQEBwJnKyrH...EXAMPLE"

# Supprimer batch
aws sqs delete-message-batch \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --entries '[
    {"Id": "1", "ReceiptHandle": "AQEBwJnKyrH...EXAMPLE1"},
    {"Id": "2", "ReceiptHandle": "AQEBRXTo...EXAMPLE2"}
  ]'

# === CHANGER VISIBILITY TIMEOUT ===

# Prolonger temps de traitement d'un message
aws sqs change-message-visibility \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --receipt-handle "AQEBwJnKyrH...EXAMPLE" \
  --visibility-timeout 3600

# === ATTRIBUTS QUEUE ===

# Obtenir attributs
aws sqs get-queue-attributes \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --attribute-names All

# Modifier attributs
aws sqs set-queue-attributes \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --attributes VisibilityTimeout=60,MessageRetentionPeriod=86400

# === DEAD LETTER QUEUE ===

# Créer DLQ
aws sqs create-queue --queue-name my-dlq

# Configurer DLQ sur queue principale
aws sqs set-queue-attributes \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue \
  --attributes '{
    "RedrivePolicy": "{\"deadLetterTargetArn\":\"arn:aws:sqs:us-east-1:123456789012:my-dlq\",\"maxReceiveCount\":\"3\"}"
  }'

# === PURGER & SUPPRIMER ===

# Purger tous les messages
aws sqs purge-queue \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue

# Supprimer queue
aws sqs delete-queue \
  --queue-url https://sqs.us-east-1.amazonaws.com/123456789012/my-queue


═══════════════════════════════════════════════════════════════════════════════
[OK] SNS (SIMPLE NOTIFICATION SERVICE) - PUB/SUB
═══════════════════════════════════════════════════════════════════════════════

SNS = Service de publication/abonnement (push notifications)
Un message publié -> Envoyé à tous les abonnés

# === CRÉER TOPIC ===

# Créer topic
aws sns create-topic --name my-topic

# Output: {"TopicArn": "arn:aws:sns:us-east-1:123456789012:my-topic"}

# Créer topic FIFO
aws sns create-topic \
  --name my-topic.fifo \
  --attributes FifoTopic=true,ContentBasedDeduplication=true

# Lister topics
aws sns list-topics

# Obtenir attributs topic
aws sns get-topic-attributes \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic

# === ABONNEMENTS ===

# S'abonner avec email
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --protocol email \
  --notification-endpoint user@example.com

# [ATTENTION] Email doit confirmer abonnement via lien reçu

# S'abonner avec SMS
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --protocol sms \
  --notification-endpoint +33612345678

# S'abonner avec Lambda
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --protocol lambda \
  --notification-endpoint arn:aws:lambda:us-east-1:123456789012:function:my-function

# S'abonner avec SQS
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --protocol sqs \
  --notification-endpoint arn:aws:sqs:us-east-1:123456789012:my-queue

# S'abonner avec HTTPS endpoint
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --protocol https \
  --notification-endpoint https://example.com/sns-endpoint

# Lister abonnements
aws sns list-subscriptions

# Lister abonnements d'un topic
aws sns list-subscriptions-by-topic \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic

# Confirmer abonnement (si nécessaire)
aws sns confirm-subscription \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --token <TOKEN_FROM_EMAIL>

# Désabonner
aws sns unsubscribe \
  --subscription-arn arn:aws:sns:us-east-1:123456789012:my-topic:12345678-1234-1234-1234-123456789012

# === PUBLIER MESSAGES ===

# Publier message simple
aws sns publish \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --message "Hello from SNS"

# Publier avec sujet
aws sns publish \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --subject "Important Alert" \
  --message "Server is down!"

# Publier avec attributs
aws sns publish \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --message "Order completed" \
  --message-attributes '{
    "OrderId": {"DataType": "String", "StringValue": "12345"},
    "Priority": {"DataType": "Number", "StringValue": "1"}
  }'

# Publier message structuré (différent par protocole)
aws sns publish \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --message-structure json \
  --message '{
    "default": "Default message",
    "email": "Email version of message",
    "sms": "SMS version",
    "sqs": "{\"key\": \"value\"}"
  }'

# Publier directement à endpoint (sans topic)
aws sns publish \
  --target-arn arn:aws:sns:us-east-1:123456789012:endpoint/GCM/MyApp/12345678-1234-1234-1234-123456789012 \
  --message "Direct message to mobile"

# === FILTRES D'ABONNEMENT ===

# Créer abonnement avec filtre
aws sns subscribe \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --protocol email \
  --notification-endpoint user@example.com \
  --attributes '{
    "FilterPolicy": "{\"priority\": [\"high\", \"urgent\"]}"
  }'

# Modifier filtre
aws sns set-subscription-attributes \
  --subscription-arn arn:aws:sns:us-east-1:123456789012:my-topic:12345678-1234-1234-1234-123456789012 \
  --attribute-name FilterPolicy \
  --attribute-value '{"status": ["completed", "failed"]}'

# === ENVOI SMS ===

# Envoyer SMS direct
aws sns publish \
  --phone-number +33612345678 \
  --message "Your verification code is 123456"

# Définir attributs SMS par défaut
aws sns set-sms-attributes \
  --attributes '{
    "DefaultSMSType": "Transactional",
    "DeliveryStatusSuccessSamplingRate": "100"
  }'

# === TAGS ===

# Ajouter tags
aws sns tag-resource \
  --resource-arn arn:aws:sns:us-east-1:123456789012:my-topic \
  --tags Key=Environment,Value=Production Key=Team,Value=Backend

# Lister tags
aws sns list-tags-for-resource \
  --resource-arn arn:aws:sns:us-east-1:123456789012:my-topic

# === SUPPRIMER ===

# Supprimer topic
aws sns delete-topic \
  --topic-arn arn:aws:sns:us-east-1:123456789012:my-topic


═══════════════════════════════════════════════════════════════════════════════
[OK] DYNAMODB - BASE DE DONNÉES NOSQL
═══════════════════════════════════════════════════════════════════════════════

DynamoDB = Base de données NoSQL serverless, haute performance
Concepts: Tables, Items (lignes), Attributes (colonnes)

# === CRÉER TABLE ===

# Créer table simple (partition key seulement)
aws dynamodb create-table \
  --table-name Users \
  --attribute-definitions \
    AttributeName=UserId,AttributeType=S \
  --key-schema \
    AttributeName=UserId,KeyType=HASH \
  --billing-mode PAY_PER_REQUEST

# Créer table avec partition + sort key
aws dynamodb create-table \
  --table-name Orders \
  --attribute-definitions \
    AttributeName=UserId,AttributeType=S \
    AttributeName=OrderId,AttributeType=S \
  --key-schema \
    AttributeName=UserId,KeyType=HASH \
    AttributeName=OrderId,KeyType=RANGE \
  --billing-mode PAY_PER_REQUEST

# Créer avec provisioned capacity
aws dynamodb create-table \
  --table-name Products \
  --attribute-definitions \
    AttributeName=ProductId,AttributeType=S \
  --key-schema \
    AttributeName=ProductId,KeyType=HASH \
  --billing-mode PROVISIONED \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5

# Créer avec Global Secondary Index (GSI)
aws dynamodb create-table \
  --table-name Users \
  --attribute-definitions \
    AttributeName=UserId,AttributeType=S \
    AttributeName=Email,AttributeType=S \
  --key-schema \
    AttributeName=UserId,KeyType=HASH \
  --global-secondary-indexes '[
    {
      "IndexName": "EmailIndex",
      "KeySchema": [{"AttributeName": "Email", "KeyType": "HASH"}],
      "Projection": {"ProjectionType": "ALL"},
      "ProvisionedThroughput": {
        "ReadCapacityUnits": 5,
        "WriteCapacityUnits": 5
      }
    }
  ]' \
  --billing-mode PROVISIONED \
  --provisioned-throughput ReadCapacityUnits=5,WriteCapacityUnits=5

# Lister tables
aws dynamodb list-tables

# Décrire table
aws dynamodb describe-table --table-name Users

# Attendre que table soit active
aws dynamodb wait table-exists --table-name Users

# === INSÉRER ITEMS ===

# Put item (créer ou remplacer)
aws dynamodb put-item \
  --table-name Users \
  --item '{
    "UserId": {"S": "user123"},
    "Name": {"S": "John Doe"},
    "Email": {"S": "john@example.com"},
    "Age": {"N": "30"}
  }'

# Put avec condition (seulement si n'existe pas)
aws dynamodb put-item \
  --table-name Users \
  --item '{
    "UserId": {"S": "user123"},
    "Name": {"S": "John Doe"}
  }' \
  --condition-expression "attribute_not_exists(UserId)"

# Put depuis JSON file
# user.json
{
  "UserId": {"S": "user456"},
  "Name": {"S": "Jane Smith"},
  "Email": {"S": "jane@example.com"},
  "Age": {"N": "25"},
  "Active": {"BOOL": true}
}

aws dynamodb put-item \
  --table-name Users \
  --item file://user.json

# === LIRE ITEMS ===

# Get item (par clé primaire)
aws dynamodb get-item \
  --table-name Users \
  --key '{"UserId": {"S": "user123"}}'

# Get avec partition + sort key
aws dynamodb get-item \
  --table-name Orders \
  --key '{
    "UserId": {"S": "user123"},
    "OrderId": {"S": "order789"}
  }'

# Get avec projection (seulement certains attributs)
aws dynamodb get-item \
  --table-name Users \
  --key '{"UserId": {"S": "user123"}}' \
  --projection-expression "Name, Email"

# Batch get (jusqu'à 100 items)
aws dynamodb batch-get-item \
  --request-items '{
    "Users": {
      "Keys": [
        {"UserId": {"S": "user123"}},
        {"UserId": {"S": "user456"}}
      ],
      "ProjectionExpression": "Name, Email"
    }
  }'

# === QUERY (RECHERCHER) ===

# Query par partition key
aws dynamodb query \
  --table-name Orders \
  --key-condition-expression "UserId = :userId" \
  --expression-attribute-values '{":userId": {"S": "user123"}}'

# Query avec sort key condition
aws dynamodb query \
  --table-name Orders \
  --key-condition-expression "UserId = :userId AND OrderId > :orderId" \
  --expression-attribute-values '{
    ":userId": {"S": "user123"},
    ":orderId": {"S": "order500"}
  }'

# Query avec filter (appliqué APRÈS lecture)
aws dynamodb query \
  --table-name Orders \
  --key-condition-expression "UserId = :userId" \
  --filter-expression "TotalAmount > :amount" \
  --expression-attribute-values '{
    ":userId": {"S": "user123"},
    ":amount": {"N": "100"}
  }'

# Query avec index
aws dynamodb query \
  --table-name Users \
  --index-name EmailIndex \
  --key-condition-expression "Email = :email" \
  --expression-attribute-values '{":email": {"S": "john@example.com"}}'

# Query avec limite et ordre
aws dynamodb query \
  --table-name Orders \
  --key-condition-expression "UserId = :userId" \
  --expression-attribute-values '{":userId": {"S": "user123"}}' \
  --limit 10 \
  --scan-index-forward false

# === SCAN (TOUT LIRE) ===

# [ATTENTION] Scan = Coûteux, lit TOUTE la table

# Scan basique
aws dynamodb scan --table-name Users

# Scan avec filtre
aws dynamodb scan \
  --table-name Users \
  --filter-expression "Age > :age" \
  --expression-attribute-values '{":age": {"N": "25"}}'

# Scan avec projection
aws dynamodb scan \
  --table-name Users \
  --projection-expression "UserId, Name"

# Scan parallèle (pour grandes tables)
aws dynamodb scan \
  --table-name Users \
  --total-segments 4 \
  --segment 0

# === METTRE À JOUR ITEMS ===

# Update item
aws dynamodb update-item \
  --table-name Users \
  --key '{"UserId": {"S": "user123"}}' \
  --update-expression "SET Age = :age, Email = :email" \
  --expression-attribute-values '{
    ":age": {"N": "31"},
    ":email": {"S": "newemail@example.com"}
  }'

# Incrémenter valeur
aws dynamodb update-item \
  --table-name Users \
  --key '{"UserId": {"S": "user123"}}' \
  --update-expression "SET LoginCount = LoginCount + :inc" \
  --expression-attribute-values '{":inc": {"N": "1"}}'

# Ajouter à liste
aws dynamodb update-item \
  --table-name Users \
  --key '{"UserId": {"S": "user123"}}' \
  --update-expression "SET Hobbies = list_append(Hobbies, :hobby)" \
  --expression-attribute-values '{":hobby": {"L": [{"S": "Reading"}]}}'

# Supprimer attribut
aws dynamodb update-item \
  --table-name Users \
  --key '{"UserId": {"S": "user123"}}' \
  --update-expression "REMOVE TempAttribute"

# Update avec condition
aws dynamodb update-item \
  --table-name Users \
  --key '{"UserId": {"S": "user123"}}' \
  --update-expression "SET Age = :age" \
  --condition-expression "Age < :maxAge" \
  --expression-attribute-values '{
    ":age": {"N": "31"},
    ":maxAge": {"N": "100"}
  }'

# === SUPPRIMER ITEMS ===

# Delete item
aws dynamodb delete-item \
  --table-name Users \
  --key '{"UserId": {"S": "user123"}}'

# Delete avec condition
aws dynamodb delete-item \
  --table-name Users \
  --key '{"UserId": {"S": "user123"}}' \
  --condition-expression "attribute_exists(UserId)"

# Batch delete (via batch-write-item)
aws dynamodb batch-write-item \
  --request-items '{
    "Users": [
      {"DeleteRequest": {"Key": {"UserId": {"S": "user123"}}}},
      {"DeleteRequest": {"Key": {"UserId": {"S": "user456"}}}}
    ]
  }'

# === TRANSACTIONS ===

# Transact write (atomique)
aws dynamodb transact-write-items \
  --transact-items '[
    {
      "Put": {
        "TableName": "Users",
        "Item": {"UserId": {"S": "user999"}, "Name": {"S": "New User"}}
      }
    },
    {
      "Update": {
        "TableName": "Orders",
        "Key": {"UserId": {"S": "user123"}, "OrderId": {"S": "order789"}},
        "UpdateExpression": "SET #status = :status",
        "ExpressionAttributeNames": {"#status": "Status"},
        "ExpressionAttributeValues": {":status": {"S": "Completed"}}
      }
    },
    {
      "Delete": {
        "TableName": "TempData",
        "Key": {"TempId": {"S": "temp123"}}
      }
    }
  ]'

# Transact read
aws dynamodb transact-get-items \
  --transact-items '[
    {
      "Get": {
        "TableName": "Users",
        "Key": {"UserId": {"S": "user123"}}
      }
    },
    {
      "Get": {
        "TableName": "Orders",
        "Key": {"UserId": {"S": "user123"}, "OrderId": {"S": "order789"}}
      }
    }
  ]'

# === BACKUP & RESTORE ===

# Créer backup
aws dynamodb create-backup \
  --table-name Users \
  --backup-name users-backup-$(date +%Y%m%d)

# Lister backups
aws dynamodb list-backups --table-name Users

# Restaurer depuis backup
aws dynamodb restore-table-from-backup \
  --target-table-name Users-Restored \
  --backup-arn arn:aws:dynamodb:us-east-1:123456789012:table/Users/backup/01234567890123-12345678

# Point-in-time recovery (PITR)
# Activer PITR
aws dynamodb update-continuous-backups \
  --table-name Users \
  --point-in-time-recovery-specification PointInTimeRecoveryEnabled=true

# Restaurer à point dans le temps
aws dynamodb restore-table-to-point-in-time \
  --source-table-name Users \
  --target-table-name Users-PITR \
  --restore-date-time 2024-01-15T10:30:00Z

# === STREAMS ===

# Activer streams (capture changements)
aws dynamodb update-table \
  --table-name Users \
  --stream-specification StreamEnabled=true,StreamViewType=NEW_AND_OLD_IMAGES

# Lister streams
aws dynamodbstreams list-streams --table-name Users

# Décrire stream
aws dynamodbstreams describe-stream \
  --stream-arn arn:aws:dynamodb:us-east-1:123456789012:table/Users/stream/2024-01-15T10:00:00.000

# === MODIFIER TABLE ===

# Changer billing mode
aws dynamodb update-table \
  --table-name Users \
  --billing-mode PAY_PER_REQUEST

# Changer capacity
aws dynamodb update-table \
  --table-name Users \
  --provisioned-throughput ReadCapacityUnits=10,WriteCapacityUnits=10

# Activer auto scaling
aws application-autoscaling register-scalable-target \
  --service-namespace dynamodb \
  --resource-id table/Users \
  --scalable-dimension dynamodb:table:ReadCapacityUnits \
  --min-capacity 5 \
  --max-capacity 100

# Ajouter GSI
aws dynamodb update-table \
  --table-name Users \
  --attribute-definitions AttributeName=Status,AttributeType=S \
  --global-secondary-index-updates '[
    {
      "Create": {
        "IndexName": "StatusIndex",
        "KeySchema": [{"AttributeName": "Status", "KeyType": "HASH"}],
        "Projection": {"ProjectionType": "ALL"},
        "ProvisionedThroughput": {
          "ReadCapacityUnits": 5,
          "WriteCapacityUnits": 5
        }
      }
    }
  ]'

# === TTL (TIME TO LIVE) ===

# Activer TTL (suppression automatique)
aws dynamodb update-time-to-live \
  --table-name Users \
  --time-to-live-specification Enabled=true,AttributeName=ExpiresAt

# Item avec TTL (ExpiresAt = epoch timestamp)
aws dynamodb put-item \
  --table-name Users \
  --item '{
    "UserId": {"S": "user123"},
    "Name": {"S": "John"},
    "ExpiresAt": {"N": "'$(date -d '+30 days' +%s)'"}
  }'

# === SUPPRIMER TABLE ===

aws dynamodb delete-table --table-name Users


═══════════════════════════════════════════════════════════════════════════════
[OK] ELASTIC BEANSTALK - DÉPLOIEMENT FACILE
═══════════════════════════════════════════════════════════════════════════════

Elastic Beanstalk = PaaS pour déployer applications sans gérer infrastructure
Supporte: Node.js, Python, Ruby, Java, PHP, .NET, Go, Docker

# === CRÉER APPLICATION ===

# Initialiser application (dans dossier projet)
eb init

# Init avec options
eb init \
  --platform python-3.11 \
  --region us-east-1

# Créer environnement et déployer
eb create my-env

# Créer avec options
eb create my-env \
  --instance-type t3.small \
  --scale 2 \
  --envvars KEY1=value1,KEY2=value2

# === DÉPLOYER ===

# Déployer application
eb deploy

# Déployer version spécifique
eb deploy --version my-app-v1

# === GÉRER ENVIRONNEMENT ===

# Lister environnements
eb list

# Status environnement
eb status

# Ouvrir dans navigateur
eb open

# Voir logs
eb logs

# Voir logs en temps réel
eb logs --stream

# SSH dans instance
eb ssh

# === CONFIGURATION ===

# Modifier configuration
eb config

# Définir variables d'environnement
eb setenv DB_HOST=mydb.rds.amazonaws.com DB_NAME=mydb

# Scaling
eb scale 5

# Changer type d'instance
eb scale --instance-type t3.medium

# === HEALTH ===

# Voir santé
eb health

# Monitoring détaillé
eb health --refresh

# === TERMINER ===

# Terminer environnement
eb terminate my-env

# Terminer sans confirmation
eb terminate my-env --force

# === FICHIERS DE CONFIGURATION ===

# .ebextensions/01-packages.config
packages:
  yum:
    postgresql-devel: []
    
option_settings:
  aws:elasticbeanstalk:application:environment:
    DATABASE_URL: "postgresql://user:pass@host/db"
  aws:autoscaling:launchconfiguration:
    InstanceType: t3.small
  aws:autoscaling:asg:
    MinSize: 2
    MaxSize: 6


═══════════════════════════════════════════════════════════════════════════════
[OK] ECS (ELASTIC CONTAINER SERVICE) - DOCKER
═══════════════════════════════════════════════════════════════════════════════

ECS = Orchestrateur de conteneurs Docker (comme Kubernetes)
2 modes: EC2 (gérer instances) ou Fargate (serverless)

# === CRÉER CLUSTER ===

# Créer cluster Fargate
aws ecs create-cluster --cluster-name my-cluster

# Créer cluster avec capacité providers
aws ecs create-cluster \
  --cluster-name my-cluster \
  --capacity-providers FARGATE FARGATE_SPOT \
  --default-capacity-provider-strategy \
    capacityProvider=FARGATE,weight=1 \
    capacityProvider=FARGATE_SPOT,weight=4

# Lister clusters
aws ecs list-clusters

# Décrire cluster
aws ecs describe-clusters --clusters my-cluster

# === TASK DEFINITIONS ===

# task-definition.json
{
  "family": "my-app",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "256",
  "memory": "512",
  "containerDefinitions": [
    {
      "name": "my-container",
      "image": "nginx:latest",
      "portMappings": [
        {
          "containerPort": 80,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {"name": "ENV", "value": "production"}
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/my-app",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ],
  "executionRoleArn": "arn:aws:iam::123456789012:role/ecsTaskExecutionRole",
  "taskRoleArn": "arn:aws:iam::123456789012:role/ecsTaskRole"
}

# Enregistrer task definition
aws ecs register-task-definition \
  --cli-input-json file://task-definition.json

# Lister task definitions
aws ecs list-task-definitions

# Décrire task definition
aws ecs describe-task-definition --task-definition my-app

# === SERVICES ===

# Créer service (maintient nombre de tasks)
aws ecs create-service \
  --cluster my-cluster \
  --service-name my-service \
  --task-definition my-app:1 \
  --desired-count 2 \
  --launch-type FARGATE \
  --network-configuration '{
    "awsvpcConfiguration": {
      "subnets": ["subnet-12345678", "subnet-87654321"],
      "securityGroups": ["sg-12345678"],
      "assignPublicIp": "ENABLED"
    }
  }'

# Créer service avec load balancer
aws ecs create-service \
  --cluster my-cluster \
  --service-name my-service \
  --task-definition my-app:1 \
  --desired-count 2 \
  --launch-type FARGATE \
  --network-configuration '{
    "awsvpcConfiguration": {
      "subnets": ["subnet-12345678"],
      "securityGroups": ["sg-12345678"]
    }
  }' \
  --load-balancers '[
    {
      "targetGroupArn": "arn:aws:elasticloadbalancing:us-east-1:123456789012:targetgroup/my-targets/73e2d6bc24d8a067",
      "containerName": "my-container",
      "containerPort": 80
    }
  ]'

# Lister services
aws ecs list-services --cluster my-cluster

# Décrire service
aws ecs describe-services \
  --cluster my-cluster \
  --services my-service

# Mettre à jour service
aws ecs update-service \
  --cluster my-cluster \
  --service my-service \
  --desired-count 4

# Mettre à jour task definition
aws ecs update-service \
  --cluster my-cluster \
  --service my-service \
  --task-definition my-app:2 \
  --force-new-deployment

# === TASKS ===

# Lancer task (one-off)
aws ecs run-task \
  --cluster my-cluster \
  --task-definition my-app:1 \
  --launch-type FARGATE \
  --network-configuration '{
    "awsvpcConfiguration": {
      "subnets": ["subnet-12345678"],
      "securityGroups": ["sg-12345678"],
      "assignPublicIp": "ENABLED"
    }
  }'

# Lister tasks
aws ecs list-tasks --cluster my-cluster

# Lister tasks d'un service
aws ecs list-tasks \
  --cluster my-cluster \
  --service-name my-service

# Décrire task
aws ecs describe-tasks \
  --cluster my-cluster \
  --tasks <TASK_ARN>

# Arrêter task
aws ecs stop-task \
  --cluster my-cluster \
  --task <TASK_ARN>

# === EXEC (SSH DANS CONTENEUR) ===

# Activer execute command sur service
aws ecs update-service \
  --cluster my-cluster \
  --service my-service \
  --enable-execute-command

# Se connecter au conteneur
aws ecs execute-command \
  --cluster my-cluster \
  --task <TASK_ARN> \
  --container my-container \
  --interactive \
  --command "/bin/bash"

# === ECR (ELASTIC CONTAINER REGISTRY) ===

# Créer repository
aws ecr create-repository --repository-name my-app

# Obtenir login
aws ecr get-login-password --region us-east-1 | \
  docker login --username AWS --password-stdin 123456789012.dkr.ecr.us-east-1.amazonaws.com

# Tag et push image
docker tag my-app:latest 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest
docker push 123456789012.dkr.ecr.us-east-1.amazonaws.com/my-app:latest

# Lister images
aws ecr list-images --repository-name my-app

# === SUPPRIMER ===

# Supprimer service
aws ecs delete-service \
  --cluster my-cluster \
  --service my-service \
  --force

# Supprimer cluster
aws ecs delete-cluster --cluster my-cluster


═══════════════════════════════════════════════════════════════════════════════
[OK] SECRETS MANAGER - GESTION DES SECRETS
═══════════════════════════════════════════════════════════════════════════════

Secrets Manager = Stocker et gérer secrets (mots de passe, clés API, etc.)
Rotation automatique, chiffrement, audit

# === CRÉER SECRET ===

# Créer secret simple
aws secretsmanager create-secret \
  --name MySecret \
  --secret-string "MySecretPassword123!"

# Créer secret JSON
aws secretsmanager create-secret \
  --name DatabaseCredentials \
  --secret-string '{
    "username": "admin",
    "password": "MySecurePassword123!",
    "host": "mydb.rds.amazonaws.com",
    "port": "3306"
  }'

# Créer depuis fichier
aws secretsmanager create-secret \
  --name APIKeys \
  --secret-string file://secrets.json

# Créer avec description et tags
aws secretsmanager create-secret \
  --name MySecret \
  --description "Production database password" \
  --secret-string "password123" \
  --tags Key=Environment,Value=Production Key=Team,Value=Backend

# === LIRE SECRET ===

# Obtenir secret
aws secretsmanager get-secret-value --secret-id MySecret

# Obtenir seulement la valeur
aws secretsmanager get-secret-value \
  --secret-id MySecret \
  --query SecretString \
  --output text

# Obtenir version spécifique
aws secretsmanager get-secret-value \
  --secret-id MySecret \
  --version-id <VERSION_ID>

# Lister secrets
aws secretsmanager list-secrets

# Décrire secret (métadonnées)
aws secretsmanager describe-secret --secret-id MySecret

# === METTRE À JOUR SECRET ===

# Mettre à jour valeur
aws secretsmanager update-secret \
  --secret-id MySecret \
  --secret-string "NewPassword456!"

# Mettre à jour JSON
aws secretsmanager update-secret \
  --secret-id DatabaseCredentials \
  --secret-string '{
    "username": "admin",
    "password": "NewPassword456!",
    "host": "mydb.rds.amazonaws.com",
    "port": "3306"
  }'

# Mettre à jour description
aws secretsmanager update-secret \
  --secret-id MySecret \
  --description "Updated description"

# === ROTATION ===

# Configurer rotation automatique (Lambda required)
aws secretsmanager rotate-secret \
  --secret-id MySecret \
  --rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:MyRotationFunction \
  --rotation-rules AutomaticallyAfterDays=30

# Rotation immédiate
aws secretsmanager rotate-secret \
  --secret-id MySecret \
  --rotation-lambda-arn arn:aws:lambda:us-east-1:123456789012:function:MyRotationFunction

# === VERSIONS ===

# Lister versions
aws secretsmanager list-secret-version-ids --secret-id MySecret

# Promouvoir version
aws secretsmanager update-secret-version-stage \
  --secret-id MySecret \
  --version-stage AWSCURRENT \
  --move-to-version-id <NEW_VERSION_ID> \
  --remove-from-version-id <OLD_VERSION_ID>

# === SUPPRIMER SECRET ===

# Supprimer (avec période de récupération 30 jours)
aws secretsmanager delete-secret --secret-id MySecret

# Supprimer avec période custom
aws secretsmanager delete-secret \
  --secret-id MySecret \
  --recovery-window-in-days 7

# Suppression immédiate ([ATTENTION] IRRÉVERSIBLE)
aws secretsmanager delete-secret \
  --secret-id MySecret \
  --force-delete-without-recovery

# Restaurer secret supprimé
aws secretsmanager restore-secret --secret-id MySecret

# === UTILISATION DANS CODE ===

# Python
import boto3
import json

client = boto3.client('secretsmanager', region_name='us-east-1')

response = client.get_secret_value(SecretId='DatabaseCredentials')
secret = json.loads(response['SecretString'])

username = secret['username']
password = secret['password']

# Node.js
const AWS = require('aws-sdk');
const client = new AWS.SecretsManager({region: 'us-east-1'});

const secret = await client.getSecretValue({
  SecretId: 'DatabaseCredentials'
}).promise();

const credentials = JSON.parse(secret.SecretString);


═══════════════════════════════════════════════════════════════════════════════
[OK] KMS (KEY MANAGEMENT SERVICE) - CHIFFREMENT
═══════════════════════════════════════════════════════════════════════════════

KMS = Gestion de clés de chiffrement

# === CRÉER CLÉ ===

# Créer clé symétrique
aws kms create-key --description "My encryption key"

# Créer avec alias
aws kms create-key --description "My key"
aws kms create-alias \
  --alias-name alias/my-key \
  --target-key-id <KEY_ID>

# Lister clés
aws kms list-keys

# Lister alias
aws kms list-aliases

# Décrire clé
aws kms describe-key --key-id <KEY_ID>

# === CHIFFRER/DÉCHIFFRER ===

# Chiffrer texte
aws kms encrypt \
  --key-id alias/my-key \
  --plaintext "Secret data" \
  --output text \
  --query CiphertextBlob

# Chiffrer fichier
aws kms encrypt \
  --key-id alias/my-key \
  --plaintext fileb://plaintext.txt \
  --output text \
  --query CiphertextBlob | base64 --decode > encrypted.bin

# Déchiffrer
aws kms decrypt \
  --ciphertext-blob fileb://encrypted.bin \
  --output text \
  --query Plaintext | base64 --decode

# === DATA KEYS ===

# Générer data key (pour chiffrement local)
aws kms generate-data-key \
  --key-id alias/my-key \
  --key-spec AES_256

# === DÉSACTIVER/SUPPRIMER ===

# Désactiver clé
aws kms disable-key --key-id <KEY_ID>

# Activer clé
aws kms enable-key --key-id <KEY_ID>

# Planifier suppression (7-30 jours)
aws kms schedule-key-deletion \
  --key-id <KEY_ID> \
  --pending-window-in-days 30

# Annuler suppression
aws kms cancel-key-deletion --key-id <KEY_ID>


═══════════════════════════════════════════════════════════════════════════════
[OK] CLOUDFORMATION - INFRASTRUCTURE AS CODE
═══════════════════════════════════════════════════════════════════════════════

CloudFormation = Décrire infrastructure en JSON/YAML
Automatiser création/modification d'infrastructure

# === TEMPLATE YAML ===

# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Description: 'Simple EC2 instance'

Parameters:
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: EC2 Key Pair
  
  InstanceType:
    Type: String
    Default: t2.micro
    AllowedValues:
      - t2.micro
      - t2.small
      - t2.medium

Resources:
  MySecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: Allow SSH and HTTP
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
        - IpProtocol: tcp
          FromPort: 80
          ToPort: 80
          CidrIp: 0.0.0.0/0
  
  MyInstance:
    Type: AWS::EC2::Instance
    Properties:
      ImageId: ami-0c55b159cbfafe1f0
      InstanceType: !Ref InstanceType
      KeyName: !Ref KeyName
      SecurityGroups:
        - !Ref MySecurityGroup
      Tags:
        - Key: Name
          Value: MyWebServer
      UserData:
        Fn::Base64: |
          #!/bin/bash
          yum update -y
          yum install -y httpd
          systemctl start httpd
          systemctl enable httpd
          echo "<h1>Hello from CloudFormation</h1>" > /var/www/html/index.html

Outputs:
  InstanceId:
    Description: Instance ID
    Value: !Ref MyInstance
  
  PublicIP:
    Description: Public IP address
    Value: !GetAtt MyInstance.PublicIp

# === CRÉER STACK ===

# Créer stack
aws cloudformation create-stack \
  --stack-name my-stack \
  --template-body file://template.yaml \
  --parameters ParameterKey=KeyName,ParameterValue=my-key-pair

# Créer depuis S3
aws cloudformation create-stack \
  --stack-name my-stack \
  --template-url https://s3.amazonaws.com/my-bucket/template.yaml

# Créer avec IAM capabilities
aws cloudformation create-stack \
  --stack-name my-stack \
  --template-body file://template.yaml \
  --capabilities CAPABILITY_IAM CAPABILITY_NAMED_IAM

# Créer avec tags
aws cloudformation create-stack \
  --stack-name my-stack \
  --template-body file://template.yaml \
  --tags Key=Environment,Value=Production

# === GÉRER STACKS ===

# Lister stacks
aws cloudformation list-stacks

# Lister stacks actives
aws cloudformation list-stacks \
  --stack-status-filter CREATE_COMPLETE UPDATE_COMPLETE

# Décrire stack
aws cloudformation describe-stacks --stack-name my-stack

# Obtenir outputs
aws cloudformation describe-stacks \
  --stack-name my-stack \
  --query 'Stacks[0].Outputs'

# === METTRE À JOUR STACK ===

# Mettre à jour stack
aws cloudformation update-stack \
  --stack-name my-stack \
  --template-body file://template-v2.yaml

# Mettre à jour avec paramètres
aws cloudformation update-stack \
  --stack-name my-stack \
  --use-previous-template \
  --parameters ParameterKey=InstanceType,ParameterValue=t2.small

# === CHANGE SETS (PREVIEW) ===

# Créer change set (prévisualiser changements)
aws cloudformation create-change-set \
  --stack-name my-stack \
  --change-set-name my-changes \
  --template-body file://template-v2.yaml

# Décrire change set
aws cloudformation describe-change-set \
  --stack-name my-stack \
  --change-set-name my-changes

# Exécuter change set
aws cloudformation execute-change-set \
  --stack-name my-stack \
  --change-set-name my-changes

# Supprimer change set
aws cloudformation delete-change-set \
  --stack-name my-stack \
  --change-set-name my-changes

# === ÉVÉNEMENTS ===

# Voir événements (logs création)
aws cloudformation describe-stack-events --stack-name my-stack

# Suivre progression
aws cloudformation describe-stack-events \
  --stack-name my-stack \
  --query 'StackEvents[*].[Timestamp,ResourceStatus,ResourceType,LogicalResourceId]' \
  --output table

# === RESSOURCES ===

# Lister ressources de stack
aws cloudformation list-stack-resources --stack-name my-stack

# Décrire ressource
aws cloudformation describe-stack-resource \
  --stack-name my-stack \
  --logical-resource-id MyInstance

# === SUPPRIMER STACK ===

# Supprimer stack (supprime toutes les ressources)
aws cloudformation delete-stack --stack-name my-stack

# Attendre suppression
aws cloudformation wait stack-delete-complete --stack-name my-stack

# === VALIDER TEMPLATE ===

# Valider syntaxe
aws cloudformation validate-template \
  --template-body file://template.yaml

# === DRIFT DETECTION ===

# Détecter changements manuels
aws cloudformation detect-stack-drift --stack-name my-stack

# Voir résultats drift
aws cloudformation describe-stack-drift-detection-status \
  --stack-drift-detection-id <DRIFT_DETECTION_ID>


═══════════════════════════════════════════════════════════════════════════════
[OK] SYSTEMS MANAGER (SSM) - GESTION SERVEURS
═══════════════════════════════════════════════════════════════════════════════

Systems Manager = Gérer instances EC2 sans SSH
Parameter Store, Session Manager, Run Command, Patch Manager

# === SESSION MANAGER (SSH SANS CLÉS) ===

# Se connecter à instance
aws ssm start-session --target i-1234567890abcdef0

# Port forwarding
aws ssm start-session \
  --target i-1234567890abcdef0 \
  --document-name AWS-StartPortForwardingSession \
  --parameters '{"portNumber":["3306"],"localPortNumber":["3306"]}'

# === PARAMETER STORE ===

# Créer paramètre
aws ssm put-parameter \
  --name /myapp/db/host \
  --value "mydb.rds.amazonaws.com" \
  --type String

# Créer paramètre sécurisé (chiffré)
aws ssm put-parameter \
  --name /myapp/db/password \
  --value "MySecurePassword123!" \
  --type SecureString

# Créer avec KMS key custom
aws ssm put-parameter \
  --name /myapp/api/key \
  --value "api-key-123" \
  --type SecureString \
  --key-id alias/my-key

# Obtenir paramètre
aws ssm get-parameter --name /myapp/db/host

# Obtenir avec déchiffrement
aws ssm get-parameter \
  --name /myapp/db/password \
  --with-decryption

# Obtenir plusieurs paramètres
aws ssm get-parameters \
  --names /myapp/db/host /myapp/db/password \
  --with-decryption

# Obtenir par path (tous sous /myapp/)
aws ssm get-parameters-by-path \
  --path /myapp \
  --recursive \
  --with-decryption

# Lister paramètres
aws ssm describe-parameters

# Mettre à jour paramètre
aws ssm put-parameter \
  --name /myapp/db/host \
  --value "newdb.rds.amazonaws.com" \
  --overwrite

# Supprimer paramètre
aws ssm delete-parameter --name /myapp/db/host

# Supprimer plusieurs
aws ssm delete-parameters \
  --names /myapp/db/host /myapp/db/user

# === RUN COMMAND ===

# Exécuter commande sur instances
aws ssm send-command \
  --document-name "AWS-RunShellScript" \
  --targets "Key=instanceids,Values=i-1234567890abcdef0" \
  --parameters 'commands=["sudo yum update -y"]'

# Exécuter sur plusieurs instances (par tag)
aws ssm send-command \
  --document-name "AWS-RunShellScript" \
  --targets "Key=tag:Environment,Values=Production" \
  --parameters 'commands=["df -h","free -m"]'

# Obtenir résultat commande
aws ssm get-command-invocation \
  --command-id <COMMAND_ID> \
  --instance-id i-1234567890abcdef0

# === MAINTENANCE WINDOWS ===

# Créer maintenance window
aws ssm create-maintenance-window \
  --name "Weekly-Patching" \
  --schedule "cron(0 2 ? * SUN *)" \
  --duration 4 \
  --cutoff 1 \
  --allow-unassociated-targets

# === PATCH MANAGER ===

# Créer patch baseline
aws ssm create-patch-baseline \
  --name "MyPatchBaseline" \
  --operating-system AMAZON_LINUX_2 \
  --approval-rules '{
    "PatchRules": [{
      "PatchFilterGroup": {
        "PatchFilters": [{
          "Key": "CLASSIFICATION",
          "Values": ["Security", "Bugfix"]
        }]
      },
      "ApproveAfterDays": 7
    }]
  }'


═══════════════════════════════════════════════════════════════════════════════
[OK] COÛTS & BILLING
═══════════════════════════════════════════════════════════════════════════════

# === COÛTS ===

# Obtenir coûts du mois
aws ce get-cost-and-usage \
  --time-period Start=$(date -d 'first day of this month' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity MONTHLY \
  --metrics UnblendedCost

# Coûts par service
aws ce get-cost-and-usage \
  --time-period Start=$(date -d '7 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity DAILY \
  --metrics UnblendedCost \
  --group-by Type=DIMENSION,Key=SERVICE

# Forecast (prévision)
aws ce get-cost-forecast \
  --time-period Start=$(date +%Y-%m-%d),End=$(date -d '+30 days' +%Y-%m-%d) \
  --metric UNBLENDED_COST \
  --granularity MONTHLY

# === BUDGETS ===

# Créer budget
# budget.json
{
  "BudgetName": "Monthly-Budget",
  "BudgetLimit": {
    "Amount": "100",
    "Unit": "USD"
  },
  "TimeUnit": "MONTHLY",
  "BudgetType": "COST"
}

aws budgets create-budget \
  --account-id 123456789012 \
  --budget file://budget.json

# Lister budgets
aws budgets describe-budgets --account-id 123456789012

# === COÛTS PAR SERVICE (ESTIMATION) ===

# EC2:
# - t2.micro: ~$0.0116/h = ~$8.50/mois (Free tier: 750h/mois)
# - t3.small: ~$0.0208/h = ~$15/mois
# - t3.medium: ~$0.0416/h = ~$30/mois

# RDS:
# - db.t3.micro: ~$0.017/h = ~$12/mois (Free tier: 750h/mois)
# - Stockage: $0.115/GB/mois

# S3:
# - Stockage: $0.023/GB/mois (premiers 50TB)
# - Requêtes GET: $0.0004/1000
# - Requêtes PUT: $0.005/1000
# - Transfer OUT: $0.09/GB (après 100GB/mois gratuit)

# Lambda:
# - $0.20 par million de requêtes
# - $0.0000166667 par GB-seconde
# - 1M requêtes gratuits/mois + 400,000 GB-sec/mois

# DynamoDB (On-Demand):
# - Write: $1.25 par million
# - Read: $0.25 par million
# - Stockage: $0.25/GB/mois

# CloudWatch:
# - Logs ingestion: $0.50/GB
# - Logs storage: $0.03/GB/mois
# - Métriques custom: $0.30/métrique/mois

# === FREE TIER (12 MOIS) ===

# EC2: 750h/mois t2.micro (Linux/Windows)
# RDS: 750h/mois db.t2.micro + 20GB stockage
# S3: 5GB stockage + 20,000 GET + 2,000 PUT
# Lambda: 1M requêtes + 400,000 GB-sec
# DynamoDB: 25GB stockage + 25 RCU/WCU
# CloudFront: 50GB data transfer out
# SNS: 1,000 publications + 1,000 emails


═══════════════════════════════════════════════════════════════════════════════
[OK] BONNES PRATIQUES
═══════════════════════════════════════════════════════════════════════════════

# === SÉCURITÉ ===

# [OK] Activer MFA sur compte root
# [OK] Ne jamais utiliser compte root pour opérations quotidiennes
# [OK] Utiliser IAM roles (pas access keys) pour EC2/Lambda
# [OK] Principe du moindre privilège (least privilege)
# [OK] Rotation régulière des clés d'accès
# [OK] Activer CloudTrail (audit logs)
# [OK] Chiffrer données sensibles (S3, RDS, EBS)
# [OK] Utiliser Secrets Manager pour credentials
# [OK] Security Groups: autoriser seulement nécessaire
# [OK] Activer VPC Flow Logs
# [X] Ne jamais hardcoder credentials dans code
# [X] Ne jamais partager clés d'accès
# [X] Ne jamais ouvrir ports au monde entier (0.0.0.0/0) sauf HTTP/HTTPS

# === COÛTS ===

# [OK] Utiliser tags pour tracer coûts
# [OK] Créer budgets et alarmes
# [OK] Arrêter instances dev/test la nuit/weekend
# [OK] Utiliser Reserved Instances pour production
# [OK] Utiliser Spot Instances pour workloads flexibles
# [OK] Utiliser S3 Lifecycle policies (archivage)
# [OK] Supprimer snapshots/volumes inutilisés
# [OK] Monitorer coûts avec Cost Explorer
# [OK] Utiliser Auto Scaling (payer seulement nécessaire)
# [X] Laisser instances running inutilement
# [X] Oublier Elastic IPs non attachées (facturées!)
# [X] Ignorer recommandations Trusted Advisor

# === HAUTE DISPONIBILITÉ ===

# [OK] Déployer dans plusieurs Availability Zones
# [OK] Utiliser Multi-AZ pour RDS
# [OK] Utiliser Auto Scaling Groups
# [OK] Utiliser Load Balancers
# [OK] Répliquer données critiques (S3 Cross-Region)
# [OK] Tester disaster recovery régulièrement
# [OK] Utiliser Route 53 health checks
# [OK] Backups automatiques activés

# === PERFORMANCE ===

# [OK] Utiliser CloudFront pour contenu statique
# [OK] Caching (ElastiCache, CloudFront)
# [OK] Choisir type d'instance approprié
# [OK] Utiliser EBS gp3 (meilleur rapport qualité/prix)
# [OK] Read Replicas RDS pour scaling lecture
# [OK] DynamoDB pour haute performance NoSQL
# [OK] Monitorer avec CloudWatch

# === ARCHITECTURE ===

# [OK] Microservices avec Lambda/ECS
# [OK] Découplage avec SQS/SNS
# [OK] Serverless quand possible (Lambda, Fargate)
# [OK] Infrastructure as Code (CloudFormation, Terraform)
# [OK] CI/CD (CodePipeline, GitHub Actions)
# [OK] Conteneurisation (Docker, ECS, EKS)

# === TAGS STANDARDS ===

# Utiliser tags cohérents sur toutes ressources:
Environment: Production/Staging/Development
Project: nom-du-projet
Team: backend/frontend/data
CostCenter: finance/engineering
Owner: email@example.com
Application: nom-application


═══════════════════════════════════════════════════════════════════════════════
[OK] DÉPANNAGE COURANT
═══════════════════════════════════════════════════════════════════════════════

# === PROBLÈME: "Access Denied" ===

# Solution: Vérifier permissions IAM
aws iam get-user
aws iam list-attached-user-policies --user-name <USERNAME>

# Vérifier si MFA requis
aws sts get-caller-identity

# === PROBLÈME: EC2 inaccessible ===

# 1. Vérifier Security Group
aws ec2 describe-security-groups --group-ids sg-12345678

# 2. Vérifier status instance
aws ec2 describe-instance-status --instance-ids i-1234567890abcdef0

# 3. Voir console logs
aws ec2 get-console-output --instance-id i-1234567890abcdef0

# 4. Vérifier Network ACL
aws ec2 describe-network-acls

# === PROBLÈME: RDS connexion timeout ===

# 1. Vérifier Security Group
aws rds describe-db-instances \
  --db-instance-identifier mydb \
  --query 'DBInstances[0].VpcSecurityGroups'

# 2. Vérifier publicly accessible
aws rds describe-db-instances \
  --db-instance-identifier mydb \
  --query 'DBInstances[0].PubliclyAccessible'

# 3. Tester depuis EC2 dans même VPC
telnet mydb.abc.rds.amazonaws.com 3306

# === PROBLÈME: S3 "Access Denied" ===

# 1. Vérifier bucket policy
aws s3api get-bucket-policy --bucket my-bucket

# 2. Vérifier ACL
aws s3api get-bucket-acl --bucket my-bucket

# 3. Vérifier Block Public Access
aws s3api get-public-access-block --bucket my-bucket

# === PROBLÈME: Lambda timeout ===

# 1. Augmenter timeout
aws lambda update-function-configuration \
  --function-name my-function \
  --timeout 300

# 2. Augmenter mémoire (plus de CPU)
aws lambda update-function-configuration \
  --function-name my-function \
  --memory-size 1024

# 3. Vérifier logs CloudWatch
aws logs tail /aws/lambda/my-function --follow

# === PROBLÈME: High costs ===

# 1. Identifier services coûteux
aws ce get-cost-and-usage \
  --time-period Start=$(date -d '30 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity MONTHLY \
  --metrics UnblendedCost \
  --group-by Type=DIMENSION,Key=SERVICE

# 2. Trouver ressources inutilisées
# - Elastic IPs non attachées
aws ec2 describe-addresses --query 'Addresses[?AssociationId==null]'

# - Volumes EBS non attachés
aws ec2 describe-volumes \
  --filters Name=status,Values=available

# - Load Balancers sans cibles
aws elbv2 describe-target-health \
  --target-group-arn <ARN>

# - RDS/EC2 arrêtés mais facturés (stockage)

# === PROBLÈME: "Instance limit exceeded" ===

# Demander augmentation quota
aws service-quotas request-service-quota-increase \
  --service-code ec2 \
  --quota-code L-1216C47A \
  --desired-value 50

# Voir quotas actuels
aws service-quotas list-service-quotas \
  --service-code ec2


═══════════════════════════════════════════════════════════════════════════════
[OK] SERVICES ADDITIONNELS - APERÇU RAPIDE
═══════════════════════════════════════════════════════════════════════════════

# === ELASTICACHE - CACHE ===

# Redis ou Memcached managé
# Pour améliorer performance applications

# Créer cluster Redis
aws elasticache create-cache-cluster \
  --cache-cluster-id my-redis \
  --engine redis \
  --cache-node-type cache.t3.micro \
  --num-cache-nodes 1

# === KINESIS - STREAMING DATA ===

# Traitement temps réel de données streaming

# Créer stream
aws kinesis create-stream \
  --stream-name my-stream \
  --shard-count 1

# Publier record
aws kinesis put-record \
  --stream-name my-stream \
  --partition-key key1 \
  --data "Hello Kinesis"

# === ATHENA - QUERY S3 ===

# Requêter données S3 avec SQL

# Créer database
aws athena start-query-execution \
  --query-string "CREATE DATABASE mydb" \
  --result-configuration OutputLocation=s3://my-results-bucket/

# Query
aws athena start-query-execution \
  --query-string "SELECT * FROM mytable WHERE date='2024-01-15'" \
  --query-execution-context Database=mydb \
  --result-configuration OutputLocation=s3://my-results-bucket/

# === GLUE - ETL ===

# ETL serverless (Extract, Transform, Load)

# Créer crawler (découvre schéma)
aws glue create-crawler \
  --name my-crawler \
  --role arn:aws:iam::123456789012:role/GlueRole \
  --database-name mydb \
  --targets '{
    "S3Targets": [{"Path": "s3://my-bucket/data/"}]
  }'

# Démarrer crawler
aws glue start-crawler --name my-crawler

# === STEP FUNCTIONS - ORCHESTRATION ===

# Orchestrer workflows (Lambda, ECS, etc.)

# state-machine.json
{
  "Comment": "Simple workflow",
  "StartAt": "ProcessData",
  "States": {
    "ProcessData": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:us-east-1:123456789012:function:process",
      "End": true
    }
  }
}

# Créer state machine
aws stepfunctions create-state-machine \
  --name my-workflow \
  --definition file://state-machine.json \
  --role-arn arn:aws:iam::123456789012:role/StepFunctionsRole

# Exécuter
aws stepfunctions start-execution \
  --state-machine-arn arn:aws:states:us-east-1:123456789012:stateMachine:my-workflow \
  --input '{"key": "value"}'

# === EVENTBRIDGE - EVENT BUS ===

# Bus d'événements serverless

# Créer règle (trigger Lambda chaque heure)
aws events put-rule \
  --name hourly-trigger \
  --schedule-expression "rate(1 hour)"

# Ajouter cible
aws events put-targets \
  --rule hourly-trigger \
  --targets "Id=1,Arn=arn:aws:lambda:us-east-1:123456789012:function:my-function"

# === API GATEWAY - REST API ===

# Créer APIs managées

# Créer REST API
aws apigateway create-rest-api \
  --name my-api \
  --description "My API"

# Créer HTTP API (plus simple, moins cher)
aws apigatewayv2 create-api \
  --name my-http-api \
  --protocol-type HTTP

# === COGNITO - AUTHENTIFICATION ===

# Gestion utilisateurs, authentification

# Créer user pool
aws cognito-idp create-user-pool \
  --pool-name my-users \
  --auto-verified-attributes email

# Créer utilisateur
aws cognito-idp admin-create-user \
  --user-pool-id us-east-1_abcdefghi \
  --username john@example.com \
  --user-attributes Name=email,Value=john@example.com

# === SES (SIMPLE EMAIL SERVICE) ===

# Envoyer emails

# Vérifier email
aws ses verify-email-identity \
  --email-address sender@example.com

# Envoyer email
aws ses send-email \
  --from sender@example.com \
  --to recipient@example.com \
  --subject "Hello from SES" \
  --text "Email body"

# === REKOGNITION - VISION ===

# Analyse images/vidéos avec ML

# Détecter labels dans image
aws rekognition detect-labels \
  --image '{"S3Object":{"Bucket":"my-bucket","Name":"photo.jpg"}}' \
  --max-labels 10

# Détecter visages
aws rekognition detect-faces \
  --image '{"S3Object":{"Bucket":"my-bucket","Name":"photo.jpg"}}' \
  --attributes ALL

# === COMPREHEND - NLP ===

# Analyse de texte avec ML

# Détecter sentiment
aws comprehend detect-sentiment \
  --text "I love AWS!" \
  --language-code en

# Détecter langue
aws comprehend detect-dominant-language \
  --text "Bonjour le monde"

# === TEXTRACT - OCR ===

# Extraire texte de documents

# Analyser document
aws textract analyze-document \
  --document '{"S3Object":{"Bucket":"my-bucket","Name":"document.pdf"}}' \
  --feature-types TABLES FORMS

# === POLLY - TEXT TO SPEECH ===

# Convertir texte en parole

# Synthétiser parole
aws polly synthesize-speech \
  --text "Hello from AWS Polly" \
  --output-format mp3 \
  --voice-id Joanna \
  output.mp3

# === TRANSCRIBE - SPEECH TO TEXT ===

# Convertir audio en texte

# Démarrer job transcription
aws transcribe start-transcription-job \
  --transcription-job-name my-job \
  --media MediaFileUri=s3://my-bucket/audio.mp3 \
  --language-code en-US

# === SAGEMAKER - MACHINE LEARNING ===

# Plateforme ML complète

# Créer notebook instance
aws sagemaker create-notebook-instance \
  --notebook-instance-name my-notebook \
  --instance-type ml.t3.medium \
  --role-arn arn:aws:iam::123456789012:role/SageMakerRole

# === BATCH - JOBS BATCH ===

# Exécuter jobs batch à grande échelle

# Créer compute environment
aws batch create-compute-environment \
  --compute-environment-name my-compute-env \
  --type MANAGED \
  --compute-resources '{
    "type": "FARGATE",
    "maxvCpus": 256
  }'

# === BACKUP - BACKUPS CENTRALISÉS ===

# Gérer backups de plusieurs services

# Créer plan backup
aws backup create-backup-plan \
  --backup-plan '{
    "BackupPlanName": "DailyBackup",
    "Rules": [{
      "RuleName": "DailyRule",
      "TargetBackupVaultName": "Default",
      "ScheduleExpression": "cron(0 2 * * ? *)",
      "Lifecycle": {"DeleteAfterDays": 30}
    }]
  }'


═══════════════════════════════════════════════════════════════════════════════
[OK] SCRIPTS UTILES
═══════════════════════════════════════════════════════════════════════════════

# === SCRIPT: Arrêter toutes instances EC2 ===

#!/bin/bash
# stop-all-ec2.sh

# Obtenir toutes instances running
instances=$(aws ec2 describe-instances \
  --filters "Name=instance-state-name,Values=running" \
  --query 'Reservations[*].Instances[*].InstanceId' \
  --output text)

if [ -z "$instances" ]; then
  echo "Aucune instance running"
  exit 0
fi

echo "Arrêt des instances: $instances"
aws ec2 stop-instances --instance-ids $instances

# === SCRIPT: Backup tous buckets S3 ===

#!/bin/bash
# backup-s3-buckets.sh

BACKUP_DIR="./s3-backups"
mkdir -p $BACKUP_DIR

# Lister tous les buckets
buckets=$(aws s3 ls | awk '{print $3}')

for bucket in $buckets; do
  echo "Backup de $bucket..."
  aws s3 sync s3://$bucket $BACKUP_DIR/$bucket/
done

echo "Backup terminé: $BACKUP_DIR"

# === SCRIPT: Nettoyer snapshots anciens ===

#!/bin/bash
# cleanup-old-snapshots.sh

# Snapshots plus vieux que 30 jours
cutoff_date=$(date -d '30 days ago' +%Y-%m-%d)

aws ec2 describe-snapshots --owner-ids self \
  --query "Snapshots[?StartTime<='$cutoff_date'].[SnapshotId,StartTime]" \
  --output text | while read snapshot_id start_time; do
  
  echo "Suppression snapshot: $snapshot_id ($start_time)"
  aws ec2 delete-snapshot --snapshot-id $snapshot_id
done

# === SCRIPT: Coûts par tag ===

#!/bin/bash
# costs-by-tag.sh

TAG_KEY="Environment"

aws ce get-cost-and-usage \
  --time-period Start=$(date -d '30 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --granularity MONTHLY \
  --metrics UnblendedCost \
  --group-by Type=TAG,Key=$TAG_KEY \
  --query 'ResultsByTime[*].Groups[*].[Keys[0],Metrics.UnblendedCost.Amount]' \
  --output table

# === SCRIPT: Trouver ressources non taggées ===

#!/bin/bash
# find-untagged-resources.sh

echo "=== Instances EC2 sans tags ==="
aws ec2 describe-instances \
  --query 'Reservations[*].Instances[?length(Tags)==`0`].[InstanceId]' \
  --output table

echo "=== Volumes EBS sans tags ==="
aws ec2 describe-volumes \
  --query 'Volumes[?length(Tags)==`0`].[VolumeId]' \
  --output table

echo "=== Buckets S3 sans tags ==="
for bucket in $(aws s3 ls | awk '{print $3}'); do
  tags=$(aws s3api get-bucket-tagging --bucket $bucket 2>/dev/null)
  if [ -z "$tags" ]; then
    echo $bucket
  fi
done

# === SCRIPT: Monitorer instance CPU ===

#!/bin/bash
# monitor-cpu.sh

INSTANCE_ID=$1
if [ -z "$INSTANCE_ID" ]; then
  echo "Usage: $0 <instance-id>"
  exit 1
fi

while true; do
  cpu=$(aws cloudwatch get-metric-statistics \
    --namespace AWS/EC2 \
    --metric-name CPUUtilization \
    --dimensions Name=InstanceId,Value=$INSTANCE_ID \
    --start-time $(date -u -d '5 minutes ago' +%Y-%m-%dT%H:%M:%S) \
    --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
    --period 300 \
    --statistics Average \
    --query 'Datapoints[0].Average' \
    --output text)
  
  echo "$(date): CPU = $cpu%"
  sleep 60
done

# === SCRIPT: Auto-start instances matin ===

#!/bin/bash
# auto-start-instances.sh
# À mettre dans crontab: 0 8 * * 1-5 /path/to/script.sh

# Démarrer instances avec tag AutoStart=true
instances=$(aws ec2 describe-instances \
  --filters "Name=tag:AutoStart,Values=true" "Name=instance-state-name,Values=stopped" \
  --query 'Reservations[*].Instances[*].InstanceId' \
  --output text)

if [ ! -z "$instances" ]; then
  echo "Démarrage instances: $instances"
  aws ec2 start-instances --instance-ids $instances
fi

# === SCRIPT: Rotation clés IAM ===

#!/bin/bash
# rotate-iam-keys.sh

USER_NAME=$1
if [ -z "$USER_NAME" ]; then
  echo "Usage: $0 <username>"
  exit 1
fi

# Créer nouvelle clé
new_key=$(aws iam create-access-key --user-name $USER_NAME)
echo "$new_key" > new-key.json

echo "Nouvelle clé créée. Testez-la avant de supprimer l'ancienne."
echo "Access Key ID: $(echo $new_key | jq -r .AccessKey.AccessKeyId)"
echo "Secret: $(echo $new_key | jq -r .AccessKey.SecretAccessKey)"

# Après test, supprimer ancienne clé:
# aws iam delete-access-key --user-name $USER_NAME --access-key-id <OLD_KEY_ID>


═══════════════════════════════════════════════════════════════════════════════
[OK] RESSOURCES & DOCUMENTATION
═══════════════════════════════════════════════════════════════════════════════

# Documentation officielle:
https://docs.aws.amazon.com/

# AWS CLI Reference:
https://awscli.amazonaws.com/v2/documentation/api/latest/index.html

# Pricing:
https://aws.amazon.com/pricing/

# Free Tier:
https://aws.amazon.com/free/

# Architecture Center:
https://aws.amazon.com/architecture/

# Well-Architected Framework:
https://aws.amazon.com/architecture/well-architected/

# Training & Certification:
https://aws.amazon.com/training/

# Blog AWS:
https://aws.amazon.com/blogs/

# Forums:
https://forums.aws.amazon.com/

# Support:
https://console.aws.amazon.com/support/

# Status (incidents en cours):
https://status.aws.amazon.com/

# Calculateur de coûts:
https://calculator.aws/

# Trusted Advisor:
https://console.aws.amazon.com/trustedadvisor/

# Service Health Dashboard:
https://health.aws.amazon.com/health/status

# AWS Samples (exemples code):
https://github.com/aws-samples

# Awesome AWS (liste ressources):
https://github.com/donnemartin/awesome-aws


═══════════════════════════════════════════════════════════════════════════════
[OK] CERTIFICATIONS AWS
═══════════════════════════════════════════════════════════════════════════════

# Niveau débutant:
AWS Certified Cloud Practitioner (CLF-C02)
- Vue d'ensemble générale AWS
- Concepts cloud
- Sécurité de base
- Pricing

# Niveau Associate:
AWS Certified Solutions Architect - Associate (SAA-C03)
- Architecture applications cloud
- Services principaux
- Haute disponibilité
- Sécurité

AWS Certified Developer - Associate (DVA-C02)
- Développement applications sur AWS
- Lambda, API Gateway, DynamoDB
- CI/CD
- Monitoring

AWS Certified SysOps Administrator - Associate (SOA-C02)
- Opérations AWS
- Déploiement
- Monitoring
- Sécurité opérationnelle

# Niveau Professional:
AWS Certified Solutions Architect - Professional (SAP-C02)
AWS Certified DevOps Engineer - Professional (DOP-C02)

# Certifications spécialisées:
AWS Certified Security - Specialty
AWS Certified Machine Learning - Specialty
AWS Certified Database - Specialty
AWS Certified Data Analytics - Specialty
AWS Certified Advanced Networking - Specialty


═══════════════════════════════════════════════════════════════════════════════
[OK] GLOSSAIRE
═══════════════════════════════════════════════════════════════════════════════

Region: Zone géographique (ex: us-east-1, eu-west-1)
Availability Zone (AZ): Data center dans une région
Edge Location: Point de présence CloudFront
VPC: Virtual Private Cloud - réseau privé isolé
Subnet: Sous-réseau dans VPC
CIDR: Notation pour plages IP (ex: 10.0.0.0/16)
AMI: Amazon Machine Image - image serveur
Instance: Serveur virtuel EC2
EBS: Elastic Block Store - disque dur virtuel
Snapshot: Backup d'un volume EBS
Security Group: Pare-feu virtuel (stateful)
NACL: Network ACL - pare-feu au niveau subnet (stateless)
IAM: Identity and Access Management
Role: Rôle IAM (permissions pour services)
Policy: Document JSON définissant permissions
Bucket: Conteneur S3 pour objets
Object: Fichier dans S3
Key: Nom/chemin d'un objet S3
ARN: Amazon Resource Name - identifiant unique ressource
Endpoint: URL pour accéder à un service
Tag: Étiquette clé-valeur pour organiser ressources
Elastic IP: Adresse IP statique
Load Balancer: Répartiteur de charge
Target Group: Groupe de cibles pour load balancer
Auto Scaling: Ajout/suppression automatique d'instances
Lambda: Fonction serverless
Fargate: Container serverless
ECR: Elastic Container Registry - Docker registry
ECS: Elastic Container Service - orchestrateur containers
RDS: Relational Database Service
DynamoDB: Base de données NoSQL
ElastiCache: Cache in-memory (Redis/Memcached)
CloudWatch: Service de monitoring et logs
CloudTrail: Audit logs API calls
CloudFront: CDN (Content Delivery Network)
Route 53: Service DNS
SQS: Simple Queue Service - file d'attente
SNS: Simple Notification Service - pub/sub
SES: Simple Email Service
S3: Simple Storage Service
EC2: Elastic Compute Cloud
EBS: Elastic Block Store
ELB: Elastic Load Balancing
VPC: Virtual Private Cloud
NAT: Network Address Translation
IGW: Internet Gateway
VPN: Virtual Private Network
VGW: Virtual Private Gateway
CGW: Customer Gateway
CLI: Command Line Interface
SDK: Software Development Kit
API: Application Programming Interface


═══════════════════════════════════════════════════════════════════════════════
[OK] COMMANDES AWS CLI - RÉFÉRENCE RAPIDE
═══════════════════════════════════════════════════════════════════════════════

# Configuration
aws configure                                    # Configuration interactive
aws configure list                               # Lister configuration
aws sts get-caller-identity                      # Qui suis-je?

# EC2
aws ec2 describe-instances                       # Lister instances
aws ec2 run-instances                            # Lancer instance
aws ec2 stop-instances --instance-ids <ID>       # Arrêter
aws ec2 start-instances --instance-ids <ID>      # Démarrer
aws ec2 terminate-instances --instance-ids <ID>  # Terminer

# S3
aws s3 ls                                        # Lister buckets
aws s3 ls s3://bucket/                           # Lister contenu
aws s3 cp file.txt s3://bucket/                  # Upload
aws s3 cp s3://bucket/file.txt ./                # Download
aws s3 sync ./dir s3://bucket/dir/               # Sync
aws s3 mb s3://bucket                            # Créer bucket
aws s3 rb s3://bucket --force                    # Supprimer bucket

# IAM
aws iam list-users                               # Lister utilisateurs
aws iam create-user --user-name <NAME>           # Créer utilisateur
aws iam list-groups                              # Lister groupes
aws iam list-roles                               # Lister roles
aws iam list-policies --scope Local              # Lister policies

# RDS
aws rds describe-db-instances                    # Lister databases
aws rds create-db-instance                       # Créer database
aws rds stop-db-instance --db-instance-id <ID>   # Arrêter
aws rds start-db-instance --db-instance-id <ID>  # Démarrer

# Lambda
aws lambda list-functions                        # Lister fonctions
aws lambda invoke --function-name <NAME>         # Invoquer
aws lambda update-function-code                  # Mettre à jour code

# CloudWatch
aws logs tail /aws/lambda/function --follow      # Voir logs
aws cloudwatch describe-alarms                   # Lister alarmes
aws cloudwatch put-metric-alarm                  # Créer alarme

# CloudFormation
aws cloudformation list-stacks                   # Lister stacks
aws cloudformation create-stack                  # Créer stack
aws cloudformation update-stack                  # Mettre à jour
aws cloudformation delete-stack                  # Supprimer

# VPC
aws ec2 describe-vpcs                            # Lister VPCs
aws ec2 describe-subnets                         # Lister subnets
aws ec2 describe-security-groups                 # Lister security groups

# DynamoDB
aws dynamodb list-tables                         # Lister tables
aws dynamodb scan --table-name <NAME>            # Lire tout
aws dynamodb get-item --table-name <NAME>        # Lire item
aws dynamodb put-item --table-name <NAME>        # Écrire item

# SQS
aws sqs list-queues                              # Lister queues
aws sqs send-message --queue-url <URL>           # Envoyer message
aws sqs receive-message --queue-url <URL>        # Recevoir message

# SNS
aws sns list-topics                              # Lister topics
aws sns publish --topic-arn <ARN>                # Publier message

# ECS
aws ecs list-clusters                            # Lister clusters
aws ecs list-services --cluster <NAME>           # Lister services
aws ecs list-tasks --cluster <NAME>              # Lister tasks

# Options communes
--profile <NAME>                                 # Utiliser profil
--region <REGION>                                # Spécifier région
--output json|yaml|table|text                    # Format output
--query <JMESPATH>                               # Filtrer output
--no-paginate                                    # Désactiver pagination
--dry-run                                        # Test sans exécuter


═══════════════════════════════════════════════════════════════════════════════

[OBJECTIF] FIN DE LA CHEATSHEET AWS

Cette cheatsheet couvre les services AWS essentiels pour débuter.
Pour approfondir, consultez la documentation officielle AWS.

Bon cloud computing! [CLOUD]

══════════════════════════════════════════════════════════════════════