# Fichier: python_cheats/cheatsheets/github_action.txt
# Cheatsheet GitHub Actions - Guide Complet


[OK] INTRODUCTION - QU'EST-CE QUE GITHUB ACTIONS ?


# === C'EST QUOI ? ===
# GitHub Actions est un système d'automatisation intégré directement dans GitHub
# Pensez-y comme un "robot" qui exécute des tâches automatiquement pour vous

# POURQUOI L'UTILISER ?
# 1. Tester automatiquement votre code à chaque push
# 2. Déployer automatiquement votre application
# 3. Automatiser des tâches répétitives
# 4. Assurer la qualité du code avant merge
# 5. Générer des rapports, documentation, releases...

# EXEMPLE CONCRET:
# Vous poussez du code -> GitHub Actions:
#   1. Télécharge votre code
#   2. Installe Python et dépendances
#   3. Exécute vos tests
#   4. Vérifie le style de code
#   5. Vous informe si tout est OK ou si erreurs

# === CONCEPTS DE BASE ===

# WORKFLOW
# Un fichier YAML qui définit ce qui doit être fait automatiquement
# Exemple: .github/workflows/test.yml

# ÉVÉNEMENT (Event)
# Ce qui déclenche le workflow
# Exemples: push de code, création de PR, schedule (cron)

# JOB
# Un ensemble d'étapes qui s'exécutent sur une même machine
# Exemples: job "test", job "deploy", job "build"

# STEP
# Une action individuelle dans un job
# Exemples: installer Python, exécuter pytest, envoyer notification

# RUNNER
# La machine (serveur) qui exécute votre workflow
# GitHub fournit des machines gratuitement (Linux, Windows, macOS)

# ACTION
# Un bloc de code réutilisable (comme une fonction)
# Exemples: actions/checkout (télécharger code), actions/setup-python (installer Python)


# === ANATOMIE D'UN WORKFLOW ===

# Fichier: .github/workflows/mon-workflow.yml
name: Mon Premier Workflow        # Nom visible dans GitHub
on: push                          # Quand l'exécuter (ici: à chaque push)
jobs:                             # Liste des jobs à exécuter
  test:                           # Nom du job
    runs-on: ubuntu-latest        # Machine à utiliser
    steps:                        # Liste des étapes
      - name: Dire bonjour        # Nom de l'étape
        run: echo "Bonjour!"      # Commande à exécuter


# === OÙ VOIR LES RÉSULTATS ? ===
# Sur GitHub -> Onglet "Actions" de votre repository
# Vous verrez:
# - Liste des workflows exécutés
# - Statut ([OK] succès, [X] échec, [JAUNE] en cours)
# - Logs détaillés de chaque step
# - Temps d'exécution
# - Artifacts (fichiers générés)


# === TARIFICATION ===

# REPOS PUBLICS: GRATUIT ET ILLIMITÉ ! [BRAVO]

# REPOS PRIVÉS (minutes gratuites/mois):
# - Free: 2000 minutes, 500MB storage
# - Pro: 3000 minutes, 1GB storage  
# - Team: 3000 minutes, 2GB storage
# - Enterprise: 50000 minutes, 50GB storage

# MULTIPLICATEURS (comptage des minutes):
# - Linux: 1x (10 min = 10 min comptées)
# - Windows: 2x (10 min = 20 min comptées)
# - macOS: 10x (10 min = 100 min comptées) [ATTENTION] Cher!
# Astuce: Utilisez Linux sauf si besoin spécifique


# === COMMENT COMMENCER ? ===

# ÉTAPE 1: Créer le dossier
# Dans votre repo: créer .github/workflows/

# ÉTAPE 2: Créer un fichier YAML
# Exemple: .github/workflows/test.yml

# ÉTAPE 3: Écrire votre workflow (voir exemples ci-dessous)

# ÉTAPE 4: Commit et push
git add .github/workflows/test.yml
git commit -m "Add CI workflow"
git push

# ÉTAPE 5: Voir le résultat
# Aller sur GitHub -> onglet Actions
# Votre workflow s'exécute automatiquement!


# === PREMIER WORKFLOW SIMPLE ===

# Fichier: .github/workflows/hello.yml
name: Hello World                 # Nom du workflow

on: push                          # Se déclenche à chaque push

jobs:                             # On définit les jobs
  greet:                          # Nom du job: "greet"
    runs-on: ubuntu-latest        # Machine Linux
    
    steps:                        # Liste des étapes
      - name: Say hello           # Étape 1: Dire bonjour
        run: echo "Hello, World!"
      
      - name: Show date           # Étape 2: Afficher la date
        run: date
      
      - name: List files          # Étape 3: Lister fichiers
        run: ls -la

# Résultat: Ces 3 commandes s'exécutent automatiquement à chaque push!


[OK] STRUCTURE DE BASE - COMPRENDRE LA SYNTAXE


# === FORMAT YAML ===
# GitHub Actions utilise YAML (Yet Another Markup Language)
# C'est comme du JSON mais plus lisible pour les humains

# RÈGLES IMPORTANTES:
# 1. L'indentation compte! Utiliser 2 espaces (pas de tabs!)
# 2. Les tirets (-) indiquent une liste
# 3. Les deux-points (:) séparent clé et valeur
# 4. Les # sont des commentaires

# Exemple de syntaxe YAML:
name: Mon Workflow              # name: valeur
on: push                        # Une seule ligne

jobs:                           # Début d'une section
  build:                        # Sous-section (indentée)
    runs-on: ubuntu-latest      # Propriété (encore plus indentée)
    steps:                      # Liste d'étapes
      - name: Step 1            # Élément de liste (commence par -)
        run: echo "Hello"
      - name: Step 2            # Deuxième élément
        run: echo "World"


# === WORKFLOW MINIMAL EXPLIQUÉ ===

name: Mon Workflow
# ^ Le nom affiché dans l'interface GitHub
# Optionnel mais recommandé pour s'y retrouver

on: push
# ^ QUAND exécuter ce workflow
# Ici: à chaque fois qu'on fait un "git push"

jobs:
# ^ Section qui contient tous les jobs (tâches)

  mon-job:
  # ^ ID du job (vous le nommez comme vous voulez)
  # Utilisez des tirets, pas d'espaces
  
    runs-on: ubuntu-latest
    # ^ Sur quelle machine exécuter (Linux/Windows/macOS)
    # ubuntu-latest = Linux (le plus courant)
    
    steps:
    # ^ Liste des étapes à exécuter dans l'ordre
    
      - name: Étape 1
        # ^ Nom descriptif de l'étape (optionnel mais utile)
        
        run: echo "Hello World"
        # ^ La commande shell à exécuter
        # C'est comme si vous tapiez ça dans un terminal


# === EXEMPLE COMPLET ANNOTÉ ===

name: Test Python Application
# Ce workflow teste une application Python

on: 
  push:
    # Se déclenche sur les pushs...
    branches:
      - main
      - develop
    # ...mais seulement sur ces branches
  pull_request:
    # Et aussi sur les Pull Requests
    branches:
      - main
    # ...vers la branche main

jobs:
  test:
    # Job nommé "test"
    
    runs-on: ubuntu-latest
    # Utilise une machine Linux (gratuit et rapide)
    
    steps:
      # ÉTAPE 1: Télécharger le code
      - name: Checkout code
        uses: actions/checkout@v4
        # 'uses' utilise une action pré-faite
        # checkout@v4 télécharge votre code du repo
        
      # ÉTAPE 2: Installer Python
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
        # 'with' passe des paramètres à l'action
        # Ici: on veut Python 3.11
        
      # ÉTAPE 3: Installer les dépendances
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
        # 'run' exécute une commande shell
        # | permet d'écrire plusieurs lignes
        
      # ÉTAPE 4: Exécuter les tests
      - name: Run tests
        run: pytest
        # Exécute pytest (comme dans votre terminal)


# === COMPRENDRE LES SECTIONS ===

# NAME (optionnel)
name: Mon Super Workflow
# Affiché dans GitHub > Actions
# Si absent, GitHub utilise le nom du fichier

# ON (obligatoire)
on: push
# Définit l'événement déclencheur
# Peut être simple (push) ou complexe (voir section suivante)

# JOBS (obligatoire)
jobs:
  job1:
    # Un job = une séquence d'étapes
    # Plusieurs jobs = exécution en parallèle
  job2:
    # Ce job s'exécute EN MÊME TEMPS que job1

# RUNS-ON (obligatoire dans chaque job)
runs-on: ubuntu-latest
# La "machine virtuelle" qui exécute le job
# Options: ubuntu-latest, windows-latest, macos-latest

# STEPS (obligatoire dans chaque job)
steps:
  - name: Ma première étape
    run: echo "Hello"
  - name: Ma deuxième étape
    run: echo "World"
# Liste ordonnée d'actions à effectuer
# Exécutées l'une après l'autre (séquentiellement)


# === DEUX FAÇONS D'EXÉCUTER DU CODE ===

# MÉTHODE 1: run (commande shell)
- name: Exécuter une commande
  run: echo "Hello"
  # Comme taper dans un terminal

# MÉTHODE 2: uses (action pré-faite)
- name: Utiliser une action
  uses: actions/checkout@v4
  # Utilise du code déjà écrit par quelqu'un d'autre
  # checkout = télécharger le code du repo


# === VOTRE PREMIER WORKFLOW FONCTIONNEL ===

# Fichier: .github/workflows/first.yml
name: My First Workflow

on: push
# Se déclenche à chaque push

jobs:
  hello:
    runs-on: ubuntu-latest
    
    steps:
      - name: Say hello
        run: echo "Hello from GitHub Actions!"
      
      - name: Show system info
        run: |
          echo "Runner: $RUNNER_OS"
          echo "User: $(whoami)"
          echo "Directory: $(pwd)"
      
      - name: Create a file
        run: echo "Test" > test.txt
      
      - name: Show file content
        run: cat test.txt

# Que fait ce workflow?
# 1. Dit bonjour
# 2. Affiche des infos système
# 3. Crée un fichier test.txt
# 4. Affiche son contenu

# Pour le tester:
# 1. Créez le fichier .github/workflows/first.yml
# 2. Copiez le code ci-dessus
# 3. git add, commit, push
# 4. Allez sur GitHub > Actions
# 5. Regardez le workflow s'exécuter! [BRAVO]


[OK] ÉVÉNEMENTS DÉCLENCHEURS (ON) - QUAND EXÉCUTER ?


# === CONCEPT ===
# L'événement (event) définit QUAND votre workflow s'exécute
# C'est le "déclencheur" automatique

# ÉVÉNEMENTS COURANTS:
# - push: Quand on pousse du code
# - pull_request: Quand on crée/modifie une PR
# - schedule: À intervalle régulier (comme cron)
# - workflow_dispatch: Déclenchement manuel


# === PUSH - À CHAQUE PUSH DE CODE ===

# Simple: sur tous les pushs
on: push

# Sur certaines branches uniquement
on:
  push:
    branches:
      - main          # Seulement sur main
      - develop       # Et sur develop

# Explication:
# Ce workflow s'exécute UNIQUEMENT quand vous faites:
# git push origin main
# ou
# git push origin develop
# Mais PAS sur les autres branches

# Avec pattern de branches
on:
  push:
    branches:
      - main
      - 'releases/**'    # Toutes les branches releases/*
      - 'feature/*'      # Toutes les branches feature/*

# Sur certains fichiers uniquement
on:
  push:
    paths:
      - '**.py'          # Seulement si fichiers Python modifiés
      - 'src/**'         # Ou fichiers dans src/

# Explication:
# Le workflow s'exécute SEULEMENT si vous modifiez:
# - Un fichier .py
# - OU un fichier dans le dossier src/

# Ignorer certains fichiers
on:
  push:
    paths-ignore:
      - '**.md'          # Ignore les fichiers Markdown
      - 'docs/**'        # Ignore le dossier docs/

# Cas d'usage:
# "Je ne veux PAS exécuter les tests si je modifie juste la doc"


# === PULL REQUEST - QUAND ON OUVRE/MODIFIE UNE PR ===

# Simple: sur toutes les PR
on: pull_request

# Expliqué:
on:
  pull_request:
    types:
      - opened          # PR créée
      - synchronize     # Nouveaux commits ajoutés
      - reopened        # PR réouverte
    branches:
      - main            # Seulement vers main

# Cas d'usage typique:
# "Exécuter les tests automatiquement sur chaque PR vers main"

# Exemple pratique:
name: PR Checks

on:
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: pytest

# Résultat: À chaque PR vers main, les tests s'exécutent
# GitHub affiche [OK] ou [X] sur la PR


# === SCHEDULE - EXÉCUTION PÉRIODIQUE (CRON) ===

# Tous les jours à minuit UTC
on:
  schedule:
    - cron: '0 0 * * *'

# Explication du format cron:
# '0 0 * * *'
#  │ │ │ │ │
#  │ │ │ │ └─── Jour de la semaine (0-6, 0=Dimanche)
#  │ │ │ └───── Mois (1-12)
#  │ │ └─────── Jour du mois (1-31)
#  │ └───────── Heure (0-23)
#  └─────────── Minute (0-59)

# Exemples pratiques:

# Envoyer notification Slack seulement si échec:
- name: Notify Slack on failure
  if: failure()
  run: |
    curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
      -d '{"text": "Build failed!"}'

# Deployer seulement si tests OK et branche main:
- name: Deploy
  if: success() && github.ref == 'refs/heads/main'
  run: ./deploy.sh


# === OUTPUTS - PARTAGER DES VALEURS ENTRE STEPS ===

# Step qui génère une valeur:
- name: Generate version
  id: version                 # <- ID pour référencer ce step
  run: echo "number=1.2.3" >> $GITHUB_OUTPUT

# Step qui utilise cette valeur:
- name: Use version
  run: echo "Version: ${{ steps.version.outputs.number }}"

# Explication:
# 1. Premier step génère "number=1.2.3"
# 2. Deuxième step lit cette valeur
# 3. Affiche "Version: 1.2.3"


# === TIMEOUT SUR UN STEP ===

- name: Long running task
  timeout-minutes: 10       # Max 10 minutes
  run: ./long-script.sh

# Si dépassé: Step annulé et workflow échoue
# Utile pour éviter les blocages


[OK] ACTIONS OFFICIELLES - LES PLUS UTILISÉES


# === POURQUOI DES ACTIONS ? ===
# Au lieu d'écrire 20 lignes de bash à chaque fois,
# on réutilise du code déjà écrit et testé!

# Les actions viennent de:
# 1. GitHub (actions/*)
# 2. Éditeurs (comme aws-actions/*)
# 3. Communauté


# === ACTIONS/CHECKOUT - TÉLÉCHARGER VOTRE CODE ===

# Version simple (99% des cas):
- uses: actions/checkout@v4

# Ce que ça fait:
# 1. Clone votre repository
# 2. Checkout la branche/commit de l'événement
# 3. Votre code est dans /home/runner/work/repo/repo

# [ATTENTION] À METTRE EN PREMIER STEP TOUJOURS!
# Sans ça, votre code n'est pas disponible!

# Options avancées:
- uses: actions/checkout@v4
  with:
    fetch-depth: 0            # Télécharge tout l'historique git
    submodules: true          # Avec submodules git
    token: ${{ secrets.GITHUB_TOKEN }}  # Token d'accès
    ref: develop              # Branch spécifique


# === ACTIONS/SETUP-PYTHON - INSTALLER PYTHON ===

# Version simple:
- uses: actions/setup-python@v5
  with:
    python-version: '3.11'

# Ce que ça fait:
# 1. Installe Python 3.11
# 2. Configure pip
# 3. Ajoute python/pip au PATH
# 4. Vous pouvez faire: python, pip, pytest, etc.

# Avec cache (RECOMMANDÉ):
- uses: actions/setup-python@v5
  with:
    python-version: '3.11'
    cache: 'pip'              # <- Cache automatique pour pip

# Avantage du cache:
# Sans cache: Install dependencies = 2 minutes
# Avec cache: Install dependencies = 10 secondes!

# Cache fonctionne avec:
# - pip (requirements.txt)
# - poetry (poetry.lock)
# - pipenv (Pipfile.lock)

# Exemple complet:
- uses: actions/setup-python@v5
  with:
    python-version: '3.11'
    cache: 'pip'
    cache-dependency-path: '**/requirements*.txt'

- name: Install dependencies
  run: pip install -r requirements.txt
  # <- Cette étape sera très rapide grâce au cache!


# === ACTIONS/CACHE - CACHER DES FICHIERS ===

# Pourquoi? Réutiliser des fichiers entre runs
# Exemple: Dépendances pip, node_modules, etc.

- uses: actions/cache@v4
  with:
    path: ~/.cache/pip              # Quoi cacher
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    # ^ Clé unique basée sur OS + hash du requirements.txt
    restore-keys: |
      ${{ runner.os }}-pip-
    # ^ Fallback si la clé exacte n'existe pas

# Comment ça marche:
# 1er run: 
#   - Clé pas trouvée -> Pas de cache
#   - Installe tout normalement
#   - Sauvegarde le cache avec la clé
# 2e run:
#   - Clé trouvée -> Restaure le cache
#   - Installation très rapide!

# Cache multiple paths:
- uses: actions/cache@v4
  with:
    path: |
      ~/.cache/pip
      ~/.cache/pypoetry
      .venv
    key: ${{ runner.os }}-python-${{ hashFiles('**/poetry.lock') }}


# === ACTIONS/UPLOAD-ARTIFACT - SAUVEGARDER DES FICHIERS ===

# Pourquoi? Conserver des fichiers générés
# Exemples: Builds, rapports de tests, logs

- name: Build application
  run: npm run build

- name: Upload build artifacts
  uses: actions/upload-artifact@v4
  with:
    name: dist                      # Nom de l'artifact
    path: dist/                     # Fichiers à sauvegarder

# Ce qui se passe:
# 1. Fichiers uploadés vers GitHub
# 2. Disponibles dans l'interface Actions
# 3. Téléchargeables pendant 90 jours (par défaut)

# Upload multiple paths:
- uses: actions/upload-artifact@v4
  with:
    name: test-results
    path: |
      test-results/
      coverage/
      logs/*.log
    retention-days: 30              # Garde 30 jours


# === ACTIONS/DOWNLOAD-ARTIFACT - RÉCUPÉRER DES FICHIERS ===

# Utiliser dans un job différent:
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
  
  deploy:
    needs: build                    # Attend le job build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist                # Télécharge l'artifact
          path: dist/
      - name: Deploy
        run: ./deploy.sh dist/

# Flux:
# 1. Job build: Compile et upload
# 2. Job deploy: Download et déploie


# === ACTIONS/SETUP-NODE - INSTALLER NODE.JS ===

- uses: actions/setup-node@v4
  with:
    node-version: '18'
    cache: 'npm'                    # Cache node_modules

# Autres outils:
- uses: actions/setup-java@v4
  with:
    distribution: 'temurin'
    java-version: '17'

- uses: actions/setup-go@v5
  with:
    go-version: '1.21'

- uses: ruby/setup-ruby@v1
  with:
    ruby-version: '3.2'


# === EXEMPLE WORKFLOW COMPLET ANNOTÉ ===

name: Complete Python Workflow

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      # 1. Télécharger le code (TOUJOURS EN PREMIER!)
      - name: Checkout repository
        uses: actions/checkout@v4
      
      # 2. Installer Python avec cache
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'
      
      # 3. Installer dépendances (rapide grâce au cache)
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install pytest pytest-cov
      
      # 4. Exécuter les tests
      - name: Run tests
        run: pytest --cov=src --cov-report=xml
      
      # 5. Sauvegarder le rapport de couverture
      - name: Upload coverage report
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage.xml
      
      # 6. Upload vers Codecov (service externe)
      - name: Upload to Codecov
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.xml
          fail_ci_if_error: true


[OK] VARIABLES & SECRETS - GÉRER LES DONNÉES


# === CONCEPT ===
# Variables = Données normales (API URL, noms...)
# Secrets = Données sensibles (passwords, tokens...)

# [ATTENTION] RÈGLE D'OR:
# Ne JAMAIS mettre de secrets en dur dans le code!


# === VARIABLES D'ENVIRONNEMENT ===

# Au niveau du workflow (globales):
env:
  NODE_ENV: production
  API_URL: https://api.example.com

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo $NODE_ENV
      # Affiche: production

# Au niveau d'un job:
jobs:
  test:
    runs-on: ubuntu-latest
    env:
      DATABASE_URL: postgresql://localhost:5432/test
    steps:
      - run: echo $DATABASE_URL

# Au niveau d'un step:
steps:
  - name: Build
    run: npm run build
    env:
      NODE_OPTIONS: --max-old-space-size=4096


# === VARIABLES GITHUB AUTOMATIQUES ===

# GitHub crée automatiquement des variables:
- run: echo "Repository: ${{ github.repository }}"
  # Affiche: username/repo-name

- run: echo "Branch: ${{ github.ref }}"
  # Affiche: refs/heads/main

- run: echo "Commit: ${{ github.sha }}"
  # Affiche: abc123... (SHA du commit)

- run: echo "Actor: ${{ github.actor }}"
  # Affiche: username (qui a déclenché)

# Autres variables utiles:
${{ github.event_name }}          # push, pull_request, etc.
${{ github.run_id }}              # ID unique du run
${{ github.run_number }}          # Numéro du run (1, 2, 3...)
${{ runner.os }}                  # Linux, Windows, macOS
${{ runner.temp }}                # Dossier temporaire

# Variables d'environnement (style shell):
$GITHUB_WORKSPACE                 # /home/runner/work/repo/repo
$GITHUB_REPOSITORY                # owner/repo
$GITHUB_SHA                       # Commit SHA
$GITHUB_REF                       # refs/heads/branch
$GITHUB_ACTOR                     # username
$RUNNER_OS                        # Linux, Windows, macOS


# === SECRETS - DONNÉES SENSIBLES ===

# Comment ajouter un secret:
# 1. GitHub -> Settings -> Secrets and variables -> Actions
# 2. Click "New repository secret"
# 3. Name: API_KEY
# 4. Value: votre_clé_secrète_123
# 5. Add secret

# Utiliser dans un workflow:
- name: Deploy
  run: ./deploy.sh
  env:
    API_KEY: ${{ secrets.API_KEY }}

# [ATTENTION] SÉCURITÉ:
# - Secrets sont automatiquement masqués dans les logs
# - Si vous faites: echo $API_KEY
# - GitHub affiche: echo ***

# Ne JAMAIS faire:
- run: echo "My secret is ${{ secrets.API_KEY }}"
# [X] Même si masqué, c'est dangereux!

# Faire plutôt:
- run: ./script.sh
  env:
    API_KEY: ${{ secrets.API_KEY }}
# [OK] Le script utilise $API_KEY sans l'afficher


# === SECRETS COURANTS ===

# Token GitHub (auto-fourni, pas besoin de le créer):
env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Permet d'interagir avec l'API GitHub

# Token PyPI (pour publier des packages):
- name: Publish to PyPI
  env:
    TWINE_USERNAME: __token__
    TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
  run: twine upload dist/*

# AWS Credentials:
env:
  AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
  AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

# Docker Hub:
- name: Login to Docker Hub
  uses: docker/login-action@v3
  with:
    username: ${{ secrets.DOCKERHUB_USERNAME }}
    password: ${{ secrets.DOCKERHUB_TOKEN }}


# === VARIABLES DE REPOSITORY ===

# Pour données non-sensibles mais configurables:
# Settings -> Secrets and variables -> Actions -> Variables

# Créer variable: API_URL = https://api.example.com

# Utiliser:
- run: echo "API: ${{ vars.API_URL }}"

# Différence Variables vs Secrets:
# Variables: Visibles dans l'interface, pour config
# Secrets: Masqués, pour données sensibles


# === DÉFINIR DES VARIABLES DYNAMIQUEMENT ===

# Créer une variable pour les steps suivants:
- name: Set environment variable
  run: echo "VERSION=1.2.3" >> $GITHUB_ENV

- name: Use variable
  run: echo "Version is $VERSION"
  # Affiche: Version is 1.2.3

# Exemple pratique - Extraire version depuis fichier:
- name: Get version
  run: |
    VERSION=$(python -c "import setup; print(setup.__version__)")
    echo "VERSION=$VERSION" >> $GITHUB_ENV

- name: Build with version
  run: docker build -t myapp:$VERSION .


# === VARIABLES MULTILIGNES ===

- name: Set multiline variable
  run: |
    echo 'CONFIG<<EOF' >> $GITHUB_ENV
    echo '{"key": "value"}' >> $GITHUB_ENV
    echo 'EOF' >> $GITHUB_ENV

- name: Use multiline variable
  run: echo "$CONFIG"


# === EXEMPLE COMPLET AVEC SECRETS ===

name: Deploy to Production

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Build Docker image
        run: docker build -t myapp:latest .
      
      - name: Login to Docker Hub
        uses: docker/login-action@v3
        with:
          username: ${{ secrets.DOCKERHUB_USERNAME }}
          password: ${{ secrets.DOCKERHUB_TOKEN }}
      
      - name: Push image
        run: docker push myapp:latest
      
      - name: Deploy to server
        env:
          SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          SERVER_HOST: ${{ vars.SERVER_HOST }}
        run: |
          echo "$SSH_KEY" > key.pem
          chmod 600 key.pem
          ssh -i key.pem user@$SERVER_HOST "docker pull myapp:latest && docker restart myapp"


[OK] WORKFLOWS PYTHON - EXEMPLES COMPLETS POUR DÉBUTANTS


# === WORKFLOW 1: TESTS SIMPLES ===

# Fichier: .github/workflows/test.yml
name: Tests

on:
  push:                             # À chaque push
  pull_request:                     # Et chaque PR

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      # Télécharger votre code
      - name: Checkout code
        uses: actions/checkout@v4
      
      # Installer Python
      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'              # Cache pour aller vite
      
      # Installer vos dépendances
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
      
      # Exécuter vos tests
      - name: Run tests
        run: pytest

# Ce workflow:
# 1. S'exécute à chaque push/PR
# 2. Installe Python 3.11
# 3. Installe vos dépendances
# 4. Lance pytest
# 5. Vous montre [OK] ou [X]


# === WORKFLOW 2: TESTS + LINTING ===

name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'
      
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
          pip install flake8 black pytest
      
      # Vérifier le style de code
      - name: Lint with flake8
        run: flake8 .
      
      # Vérifier le formatage
      - name: Check formatting with black
        run: black --check .
      
      # Lancer les tests
      - name: Run tests
        run: pytest

# Ce workflow vérifie:
# 1. Style de code (flake8)
# 2. Formatage (black)
# 3. Tests (pytest)
# = Code quality checks complets!


# === WORKFLOW 3: TESTER PLUSIEURS VERSIONS PYTHON ===

name: Test Multiple Versions

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.10', '3.11', '3.12']
        # ^ Teste avec 4 versions de Python
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Python ${{ matrix.python-version }}
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          cache: 'pip'
      
      - name: Install dependencies
        run: pip install -r requirements.txt
      
      - name: Run tests
        run: pytest

# Résultat: 4 jobs en parallèle
# - Test with Python 3.9 [OK]
# - Test with Python 3.10 [OK]
# - Test with Python 3.11 [OK]
# - Test with Python 3.12 [OK]


# === WORKFLOW 4: PROJET DJANGO AVEC POSTGRESQL ===

name: Django Tests

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    # Démarrer PostgreSQL automatiquement
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'
      
      - name: Install dependencies
        run: |
          pip install -r requirements.txt
      
      # Lancer migrations Django
      - name: Run migrations
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
        run: python manage.py migrate
      
      # Lancer tests Django
      - name: Run tests
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
        run: python manage.py test

# Ce workflow:
# 1. Démarre une vraie base PostgreSQL
# 2. Lance les migrations Django
# 3. Exécute les tests avec la DB
# = Tests comme en production!


# === WORKFLOW 5: PUBLIER SUR PYPI ===

name: Publish to PyPI

on:
  release:
    types: [published]              # Quand vous créez une release GitHub

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      
      # Construire le package
      - name: Build package
        run: |
          pip install build
          python -m build
      
      # Publier sur PyPI
      - name: Publish to PyPI
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
        run: |
          pip install twine
          twine upload dist/*

# Comment utiliser:
# 1. Créez PYPI_API_TOKEN dans les secrets
# 2. Sur GitHub, créez une Release (tag v1.0.0)
# 3. Le workflow publie automatiquement sur PyPI!


# === WORKFLOW 6: DÉPLOIEMENT SIMPLE ===

name: Deploy

on:
  push:
    branches: [main]                # Seulement sur main

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy via SSH
        env:
          SSH_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
          SERVER: ${{ vars.SERVER_HOST }}
        run: |
          # Configurer SSH
          mkdir -p ~/.ssh
          echo "$SSH_KEY" > ~/.ssh/id_rsa
          chmod 600 ~/.ssh/id_rsa
          
          # Déployer
          ssh -o StrictHostKeyChecking=no user@$SERVER << 'EOF'
            cd /var/www/myapp
            git pull
            pip install -r requirements.txt
            sudo systemctl restart myapp
          EOF

# Prérequis:
# 1. Créer SSH_PRIVATE_KEY secret (votre clé SSH privée)
# 2. Créer SERVER_HOST variable (IP de votre serveur)
# 3. Push sur main = déploiement automatique!:
on:
  schedule:
    - cron: '0 9 * * 1'      # Tous les lundis à 9h UTC
    - cron: '*/15 * * * *'   # Toutes les 15 minutes
    - cron: '0 0 * * 0'      # Tous les dimanches à minuit
    - cron: '30 14 * * *'    # Tous les jours à 14h30 UTC

# [ATTENTION] ATTENTION: 
# - L'heure est en UTC, pas votre heure locale!
# - Conversion: UTC = GMT (Paris = UTC+1 ou UTC+2)
# - 9h à Paris = 8h ou 7h UTC selon saison

# Cas d'usage:
# - Sauvegardes régulières
# - Nettoyage de données
# - Rapports quotidiens/hebdomadaires
# - Tests de nuit


# === WORKFLOW_DISPATCH - DÉCLENCHEMENT MANUEL ===

# Simple: bouton "Run workflow" dans GitHub
on: workflow_dispatch

# Explication:
# Vous allez sur GitHub > Actions > Votre workflow
# Et vous voyez un bouton "Run workflow"
# Clic = ça lance le workflow manuellement

# Avec paramètres (inputs):
on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Environnement à déployer'
        required: true
        type: choice
        options:
          - staging
          - production
      
      version:
        description: 'Numéro de version'
        required: false
        type: string
        default: 'latest'

# Explication détaillée:
# - description: Texte affiché dans l'interface
# - required: Si obligatoire ou optionnel
# - type: Type de donnée (string, boolean, choice)
# - options: Liste de choix (pour type: choice)
# - default: Valeur par défaut

# Comment l'utiliser dans le workflow:
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy to ${{ inputs.environment }}
        run: |
          echo "Deploying version ${{ inputs.version }}"
          echo "To environment ${{ inputs.environment }}"

# Interface dans GitHub:
# Vous voyez un formulaire avec:
# - Un menu déroulant (staging/production)
# - Un champ texte (version)
# Vous remplissez et cliquez "Run workflow"


# === MULTIPLES ÉVÉNEMENTS ===

# Combiner plusieurs déclencheurs
on:
  push:
    branches: [main]      # Push sur main
  pull_request:
    branches: [main]      # PR vers main
  schedule:
    - cron: '0 0 * * *'   # Quotidien
  workflow_dispatch:      # Manuel

# Cas d'usage:
# - Tests sur push ET PR
# - Tests nocturnes en plus
# - Possibilité de lancer manuellement


# === EXEMPLES PRATIQUES ===

# Exemple 1: CI Classique
name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

# Explication: Tests automatiques sur:
# - Chaque push vers main ou develop
# - Chaque PR vers main

# Exemple 2: Déploiement Production
name: Deploy

on:
  push:
    branches: [main]
    paths-ignore:
      - 'docs/**'
      - '**.md'

# Explication: Déploie uniquement si:
# - Push sur main
# - ET fichiers de code modifiés (pas juste la doc)

# Exemple 3: Tests Nightly
name: Nightly Tests

on:
  schedule:
    - cron: '0 2 * * *'   # 2h du matin UTC
  workflow_dispatch:      # + manuel si besoin

# Explication: Tests complets chaque nuit
# Avec possibilité de lancer manuellement


[OK] RUNNERS - LES MACHINES QUI EXÉCUTENT VOTRE CODE


# === CONCEPT ===
# Un runner = une machine virtuelle (VM) qui exécute votre workflow
# C'est comme avoir un ordinateur distant qui fait le travail pour vous

# GitHub fournit des runners GRATUITEMENT:
# - Linux (Ubuntu)
# - Windows
# - macOS

# Chaque runner a Python, Node.js, Docker, Git... déjà installés!


# === RUNNERS HÉBERGÉS PAR GITHUB ===

# Ubuntu (Linux) - LE PLUS UTILISÉ
runs-on: ubuntu-latest
# Pourquoi? Gratuit, rapide, bien supporté
# Équivalent à: Ubuntu 22.04 actuellement

# Versions spécifiques
runs-on: ubuntu-22.04    # Ubuntu 22.04
runs-on: ubuntu-20.04    # Ubuntu 20.04

# Windows
runs-on: windows-latest
# Utilisez si: Tests Windows, .NET, PowerShell
# [ATTENTION] Compte 2x plus de minutes!

# macOS
runs-on: macos-latest
# Utilisez si: Apps iOS/macOS, tests Safari
# [ATTENTION] Compte 10x plus de minutes! Très cher!

# macOS avec différentes puces
runs-on: macos-13        # Intel
runs-on: macos-14        # Apple Silicon (M1)


# === CHOIX DU RUNNER - GUIDE DÉCISION ===

# UTILISEZ UBUNTU si:
# [OK] Projet Python/Node.js/Java standard
# [OK] Besoin de Docker
# [OK] Serveur web/API
# [OK] 99% des cas! C'est le default recommandé

# UTILISEZ WINDOWS si:
# [OK] Projet .NET/C#
# [OK] Tests d'apps Windows
# [OK] PowerShell spécifique

# UTILISEZ MACOS si:
# [OK] Apps iOS/macOS
# [OK] Tests Safari
# [OK] Sinon évitez (cher!)


# === EXEMPLE PRATIQUE - PROJET PYTHON ===

jobs:
  test:
    runs-on: ubuntu-latest
    # ^ Ubuntu suffit pour 99% des projets Python
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -r requirements.txt
      - run: pytest


# === TESTER SUR PLUSIEURS OS (MATRICE) ===

# Si vous voulez être sûr que ça marche partout:
jobs:
  test:
    runs-on: ${{ matrix.os }}
    # ^ Variable dynamique depuis la matrice
    
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        # ^ Liste des OS à tester
    
    steps:
      - uses: actions/checkout@v4
      - name: Run tests
        run: pytest

# Ce que ça fait:
# GitHub crée 3 jobs en parallèle:
# 1. test sur ubuntu-latest
# 2. test sur windows-latest  
# 3. test sur macos-latest

# Résultat: Vous êtes sûr que ça marche sur Linux/Windows/Mac!
# [ATTENTION] Attention au coût si repo privé (Windows 2x, macOS 10x)


# === CE QUI EST PRÉ-INSTALLÉ ===

# Sur tous les runners:
# - Git
# - curl, wget
# - zip, unzip
# - Docker (sauf macOS)
# - Node.js (plusieurs versions)
# - Python (plusieurs versions)
# - Ruby, Go, Java...

# Voir liste complète:
# https://github.com/actions/runner-images

# Exemples d'utilisation:
- name: Check versions
  run: |
    python --version
    node --version
    git --version
    docker --version


# === SELF-HOSTED RUNNERS (AVANCÉ) ===

# Si vous avez votre propre serveur:
runs-on: self-hosted

# Pourquoi?
# - Accès à ressources internes
# - GPU pour ML
# - Pas de limite de temps
# - Gratuit (vous payez votre serveur)

# Configuration:
# Voir section "Self-hosted runners" plus bas


[OK] JOBS - ORGANISER VOTRE WORKFLOW


# === CONCEPT ===
# Un job = une séquence d'étapes qui s'exécutent ensemble
# Plusieurs jobs = exécution EN PARALLÈLE par défaut

# Pensez à un job comme une "tâche" à accomplir
# Exemples: job "test", job "build", job "deploy"


# === JOB SIMPLE ===

jobs:
  mon-job:                    # Nom du job (votre choix)
    runs-on: ubuntu-latest    # Machine à utiliser
    steps:                    # Étapes à exécuter
      - name: Step 1
        run: echo "Hello"
      - name: Step 2
        run: echo "World"

# Ce job:
# 1. Démarre une machine Ubuntu
# 2. Exécute Step 1
# 3. Exécute Step 2
# 4. Se termine


# === PLUSIEURS JOBS EN PARALLÈLE ===

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Build app
        run: echo "Building..."
  
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Run tests
        run: echo "Testing..."
  
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Check style
        run: echo "Linting..."

# Ce que GitHub fait:
# Lance 3 machines EN MÊME TEMPS
# - Une pour build
# - Une pour test
# - Une pour lint
# = Plus rapide qu'en séquence!


# === JOBS DÉPENDANTS (needs) ===

# Souvent, vous voulez un ORDRE:
# 1. D'abord build
# 2. Puis tests
# 3. Enfin deploy

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building..."
  
  test:
    needs: build              # <- Attend que build soit fini
    runs-on: ubuntu-latest
    steps:
      - run: echo "Testing..."
  
  deploy:
    needs: [build, test]      # <- Attend build ET test
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying..."

# Ordre d'exécution:
# 1. build s'exécute
# 2. test attend que build finisse, puis s'exécute
# 3. deploy attend que build ET test finissent, puis s'exécute


# === EXEMPLE RÉEL - PROJET PYTHON ===

name: Python CI/CD

on: push

jobs:
  # JOB 1: Vérifier le style
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install flake8
      - run: flake8 .
  
  # JOB 2: Exécuter les tests
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -r requirements.txt
      - run: pytest
  
  # JOB 3: Déployer (seulement si lint + test OK)
  deploy:
    needs: [lint, test]       # Attend les 2 jobs
    if: github.ref == 'refs/heads/main'  # Seulement sur main
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying to production..."

# Comportement:
# - lint et test s'exécutent EN PARALLÈLE
# - deploy attend que les 2 soient OK
# - deploy s'exécute SEULEMENT sur branche main


# === CONDITIONS (if) ===

# Exécuter un job conditionnellement:
jobs:
  deploy:
    if: github.ref == 'refs/heads/main'
    # ^ S'exécute SEULEMENT si on est sur main
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying..."

# Autres exemples de conditions:
if: github.event_name == 'push'              # Seulement sur push
if: github.event_name == 'pull_request'      # Seulement sur PR
if: contains(github.ref, 'release')          # Si branche contient "release"
if: startsWith(github.ref, 'refs/tags/')     # Si c'est un tag


# === TIMEOUT ===

# Définir un temps maximum:
jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 10       # <- Max 10 minutes
    # Si dépassé: job annulé automatiquement
    steps:
      - run: pytest

# Pourquoi?
# - Éviter qu'un job bloqué consomme des minutes
# - Détecter les tests qui "hangent"
# - Default: 360 minutes (6 heures)


# === MATRICE - TESTER PLUSIEURS VERSIONS ===

# Concept: Tester avec Python 3.9, 3.10, 3.11, 3.12
# Sans matrice: Copier-coller 4 fois le job [TIRED_FACE]
# Avec matrice: Un seul job, GitHub fait 4 copies! [BRAVO]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.10', '3.11', '3.12']
        # ^ GitHub crée 4 jobs automatiquement
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
          # ^ Utilise la version de la matrice
      - run: pip install -r requirements.txt
      - run: pytest

# Résultat dans GitHub:
# [OK] test (3.9)
# [OK] test (3.10)
# [OK] test (3.11)
# [OK] test (3.12)
# = 4 jobs exécutés en parallèle!


# === MATRICE MULTI-DIMENSIONS ===

# Tester plusieurs Python + plusieurs OS:
strategy:
  matrix:
    python-version: ['3.10', '3.11', '3.12']
    os: [ubuntu-latest, windows-latest]

# Combinaisons créées automatiquement:
# 1. Python 3.10 sur Ubuntu
# 2. Python 3.10 sur Windows
# 3. Python 3.11 sur Ubuntu
# 4. Python 3.11 sur Windows
# 5. Python 3.12 sur Ubuntu
# 6. Python 3.12 sur Windows
# = 6 jobs au total (3 × 2)


# === SERVICES (BASES DE DONNÉES) ===

# Si vos tests ont besoin d'une base de données:
jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:15        # Image Docker à utiliser
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432             # Expose le port
    
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -r requirements.txt
      - run: pytest
        env:
          DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb

# Ce que ça fait:
# 1. GitHub démarre un container PostgreSQL
# 2. Attend qu'il soit prêt (health checks)
# 3. Votre code peut s'y connecter sur localhost:5432
# 4. À la fin: container automatiquement supprimé

# Autres services courants:
services:
  redis:
    image: redis:7
    ports:
      - 6379:6379
  
  mysql:
    image: mysql:8
    env:
      MYSQL_ROOT_PASSWORD: root
    ports:
      - 3306:3306


[OK] STEPS - LES ACTIONS À EXÉCUTER


# === CONCEPT ===
# Un step = une action individuelle dans un job
# Steps s'exécutent DANS L'ORDRE, l'un après l'autre

# Deux types de steps:
# 1. run: Exécuter une commande shell
# 2. uses: Utiliser une action pré-faite


# === RUN - EXÉCUTER DES COMMANDES ===

# Commande simple:
- name: Say hello
  run: echo "Hello World"

# Équivalent à taper dans votre terminal:
# $ echo "Hello World"

# Plusieurs commandes (avec |):
- name: Install and test
  run: |
    pip install pytest
    pytest tests/
    echo "Tests done!"

# Le | permet d'écrire plusieurs lignes
# Chaque ligne = une commande

# Avec répertoire spécifique:
- name: Install frontend
  working-directory: ./frontend
  run: npm install

# Équivalent à:
# $ cd frontend
# $ npm install


# === USES - UTILISER DES ACTIONS ===

# Concept: Réutiliser du code déjà écrit
# Au lieu de réécrire 20 lignes, on utilise une action!

# Exemple 1: Télécharger votre code
- name: Checkout repository
  uses: actions/checkout@v4

# Pourquoi "checkout"?
# GitHub ne télécharge PAS votre code automatiquement!
# Cette action le fait pour vous
# [ATTENTION] À mettre en PREMIER dans presque tous les workflows

# Exemple 2: Installer Python
- name: Setup Python
  uses: actions/setup-python@v5
  with:
    python-version: '3.11'

# Ce que ça fait:
# - Installe Python 3.11
# - Configure pip
# - Ajoute python au PATH

# Exemple 3: Cache (accélérer les installations)
- name: Cache pip packages
  uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}

# Ce que ça fait:
# - Sauvegarde ~/.cache/pip après premier run
# - Réutilise ce cache si requirements.txt n'a pas changé
# - = Installation beaucoup plus rapide!


# === STRUCTURE D'UN STEP ===

- name: Nom descriptif du step
  # ^ Optionnel mais FORTEMENT recommandé
  # Affiché dans les logs GitHub
  
  run: echo "Hello"
  # ^ OU uses: actions/...
  # (run et uses sont mutuellement exclusifs)
  
  env:
    MY_VAR: value
    # ^ Variables d'environnement pour ce step
  
  working-directory: ./src
  # ^ Répertoire d'exécution
  
  continue-on-error: true
  # ^ Continue même si ce step échoue
  
  timeout-minutes: 5
  # ^ Timeout max pour ce step


# === EXEMPLE COMPLET - WORKFLOW PYTHON ===

name: Python CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
      # STEP 1: Télécharger le code
      - name: Checkout code
        uses: actions/checkout@v4
        # [ATTENTION] TOUJOURS en premier!
      
      # STEP 2: Installer Python
      - name: Setup Python 3.11
        uses: actions/setup-python@v5
        with:
          python-version: '3.11'
          cache: 'pip'        # Active le cache pip automatiquement
      
      # STEP 3: Installer les dépendances
      - name: Install dependencies
        run: |
          python -m pip install --upgrade pip
          pip install -r requirements.txt
          pip install -r requirements-dev.txt
      
      # STEP 4: Linting
      - name: Lint with flake8
        run: |
          flake8 src/ tests/
      
      # STEP 5: Type checking
      - name: Type check with mypy
        run: mypy src/
      
      # STEP 6: Run tests
      - name: Test with pytest
        run: |
          pytest --cov=src --cov-report=xml
      
      # STEP 7: Upload coverage
      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v4
        with:
          file: ./coverage.xml

# Ordre d'exécution:
# 1. Télécharge votre code
# 2. Installe Python
# 3. Installe vos dépendances
# 4. Vérifie le style (flake8)
# 5. Vérifie les types (mypy)
# 6. Exécute les tests
# 7. Envoie le rapport de couverture


# === CONDITIONS SUR LES STEPS ===

# Exécuter un step conditionnellement:
- name: Deploy to production
  if: github.ref == 'refs/heads/main'
  run: ./deploy.sh

# Seulement si step précédent a réussi (défaut):
- name: Notify success
  if: success()
  run: echo "Everything OK!"

# Seulement si step précédent a échoué:
- name: Notify failure
  if: failure()
  run: echo "Something failed!"

# Toujours exécuter (même si échec):
- name: Cleanup
  if: always()
  run: rm -rf temp/

# Exemples pratiques

on:
  push:
    branches:
      - main                          # Seulement branche main
      - 'releases/**'                 # Pattern de branches
    branches-ignore:
      - dev                           # Exclure branche
    paths:
      - '**.py'                       # Seulement fichiers Python
      - 'src/**'                      # Dossier spécifique
    paths-ignore:
      - 'docs/**'                     # Ignorer dossier
    tags:
      - v*                            # Tous les tags v*
      - v1.*.*                        # Tags v1.x.x


# === Pull Request ===
on: pull_request

on:
  pull_request:
    types:
      - opened                        # PR ouverte
      - synchronize                   # Nouveaux commits
      - reopened                      # PR réouverte
      - closed                        # PR fermée
      - assigned                      # Assignation
      - labeled                       # Label ajouté
      - review_requested              # Review demandée
    branches:
      - main
      - develop


# === Schedule (Cron) ===
on:
  schedule:
    - cron: '0 0 * * *'               # Tous les jours à minuit UTC
    - cron: '*/15 * * * *'            # Toutes les 15 minutes
    - cron: '0 9 * * 1'               # Lundis à 9h UTC

# Format cron: minute heure jour mois jour_semaine
# * * * * *
# │ │ │ │ │
# │ │ │ │ └─── jour semaine (0-6, 0=dimanche)
# │ │ │ └───── mois (1-12)
# │ │ └─────── jour mois (1-31)
# │ └───────── heure (0-23)
# └─────────── minute (0-59)


# === Workflow Dispatch (Manuel) ===
on:
  workflow_dispatch:                  # Déclenchement manuel
    inputs:
      environment:
        description: 'Environment to deploy'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production
      version:
        description: 'Version number'
        required: false
        type: string
      debug:
        description: 'Enable debug mode'
        required: false
        type: boolean
        default: false


# === Issues ===
on:
  issues:
    types:
      - opened
      - closed
      - labeled
      - assigned


# === Release ===
on:
  release:
    types:
      - published
      - created
      - edited


# === Repository Dispatch (API) ===
on:
  repository_dispatch:
    types:
      - webhook                       # Type personnalisé


# === Workflow Call (Réutilisable) ===
on:
  workflow_call:
    inputs:
      config-path:
        required: true
        type: string
    secrets:
      token:
        required: true


# === Multiples événements ===
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * 0'               # Dimanche minuit
  workflow_dispatch:


# === Événements moins courants ===
on:
  fork:                               # Repo forké
  watch:                              # Repo starred
  create:                             # Branche/tag créé
  delete:                             # Branche/tag supprimé
  gollum:                             # Wiki modifié
  issue_comment:                      # Commentaire issue
  pull_request_review:                # Review PR
  deployment:                         # Déploiement
  status:                             # Status check
  check_run:                          # Check run
  check_suite:                        # Check suite
  page_build:                         # GitHub Pages build


[OK] RUNNERS


# === Runners hébergés par GitHub ===

runs-on: ubuntu-latest                # Ubuntu (recommandé)
runs-on: ubuntu-22.04                 # Version spécifique
runs-on: ubuntu-20.04

runs-on: windows-latest               # Windows
runs-on: windows-2022
runs-on: windows-2019

runs-on: macos-latest                 # macOS
runs-on: macos-13                     # macOS 13 (Intel)
runs-on: macos-14                     # macOS 14 (ARM64 M1)
runs-on: macos-12

# Images disponibles:
# https://github.com/actions/runner-images


# === Matrice de runners ===
jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]


# === Self-hosted runners ===
runs-on: self-hosted

runs-on: [self-hosted, linux, x64]   # Avec labels
runs-on: [self-hosted, ARM64]


# === Runner groups (Enterprise) ===
runs-on:
  group: production-runners


[OK] JOBS


# === Job simple ===
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo "Building..."


# === Jobs multiples ===
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: npm run build
  
  test:
    runs-on: ubuntu-latest
    steps:
      - run: npm test


# === Dépendances entre jobs ===
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: npm run build
  
  test:
    needs: build                      # Attend build
    runs-on: ubuntu-latest
    steps:
      - run: npm test
  
  deploy:
    needs: [build, test]              # Attend plusieurs jobs
    runs-on: ubuntu-latest
    steps:
      - run: npm run deploy


# === Conditions ===
jobs:
  deploy:
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - run: deploy.sh


# === Timeout ===
jobs:
  build:
    runs-on: ubuntu-latest
    timeout-minutes: 30               # Timeout du job (défaut: 360)
    steps:
      - run: build.sh


# === Stratégie de matrice ===
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: [3.8, 3.9, '3.10', 3.11, 3.12]
        os: [ubuntu-latest, windows-latest]
    steps:
      - uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}


# === Matrice avec include/exclude ===
strategy:
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
    node: [14, 16, 18]
    exclude:
      - os: macos-latest              # Exclure combinaison
        node: 14
    include:
      - os: ubuntu-latest             # Ajouter combinaison
        node: 19
        experimental: true


# === Fail-fast ===
strategy:
  fail-fast: false                    # Continue si un job échoue
  matrix:
    version: [1, 2, 3]


# === Max parallel ===
strategy:
  max-parallel: 2                     # Max 2 jobs en parallèle
  matrix:
    version: [1, 2, 3, 4, 5]


# === Environnement du job ===
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production                # Environnement
      url: https://example.com        # URL de déploiement


# === Conteneur ===
jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: node:18
      env:
        NODE_ENV: test
      ports:
        - 80
      volumes:
        - my_volume:/volume_mount
      options: --cpus 1


# === Services (databases, etc.) ===
jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
      
      redis:
        image: redis:7
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379


# === Outputs ===
jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.value }}
    steps:
      - id: version
        run: echo "value=1.0.0" >> $GITHUB_OUTPUT
  
  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: echo "Deploying version ${{ needs.build.outputs.version }}"


[OK] STEPS


# === Run command ===
steps:
  - name: Simple command
    run: echo "Hello World"

  - name: Multiple commands
    run: |
      echo "Line 1"
      echo "Line 2"
      npm install

  - name: Working directory
    run: npm install
    working-directory: ./frontend


# === Shell ===
steps:
  - name: Bash (default Linux/macOS)
    run: echo $HOME
    shell: bash

  - name: PowerShell (default Windows)
    run: Write-Host $env:HOME
    shell: pwsh

  - name: Python
    run: |
      import os
      print(os.environ['HOME'])
    shell: python

  - name: Node
    run: console.log(process.env.HOME)
    shell: node {0}


# === Uses (actions) ===
steps:
  - name: Checkout code
    uses: actions/checkout@v4

  - name: Setup Python
    uses: actions/setup-python@v5
    with:
      python-version: '3.11'

  - name: Action spécifique version
    uses: actions/setup-node@v4.0.1

  - name: Action depuis commit
    uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab

  - name: Action local
    uses: ./.github/actions/my-action

  - name: Action Docker
    uses: docker://alpine:3.8


# === Conditions (if) ===
steps:
  - name: Run on main only
    if: github.ref == 'refs/heads/main'
    run: deploy.sh

  - name: Run on success
    if: success()
    run: echo "Previous steps succeeded"

  - name: Run on failure
    if: failure()
    run: echo "Previous step failed"

  - name: Always run
    if: always()
    run: echo "Runs even if previous steps failed"

  - name: Cancelled
    if: cancelled()
    run: echo "Workflow was cancelled"

  - name: Complex condition
    if: |
      github.event_name == 'push' &&
      github.ref == 'refs/heads/main' &&
      !contains(github.event.head_commit.message, '[skip ci]')
    run: deploy.sh


# === Continue on error ===
steps:
  - name: Step that might fail
    continue-on-error: true
    run: exit 1

  - name: This will still run
    run: echo "Previous step failed but we continue"


# === Timeout ===
steps:
  - name: Step with timeout
    timeout-minutes: 5
    run: long-running-script.sh


# === ID et outputs ===
steps:
  - name: Generate version
    id: version
    run: echo "number=1.2.3" >> $GITHUB_OUTPUT

  - name: Use output
    run: echo "Version is ${{ steps.version.outputs.number }}"


# === Environnement variables ===
steps:
  - name: Set env for step
    run: echo "Hello $NAME"
    env:
      NAME: World

  - name: Set env for workflow
    run: echo "MY_VAR=value" >> $GITHUB_ENV

  - name: Use env set previously
    run: echo $MY_VAR


[OK] ACTIONS OFFICIELLES COURANTES


# === Checkout ===
- uses: actions/checkout@v4                      # Version récente

- uses: actions/checkout@v4
  with:
    fetch-depth: 0                               # Tout l'historique
    submodules: true                             # Avec submodules
    token: ${{ secrets.GITHUB_TOKEN }}           # Token personnalisé
    ref: develop                                 # Branche spécifique
    path: custom-directory                       # Dossier personnalisé


# === Setup Python ===
- uses: actions/setup-python@v5
  with:
    python-version: '3.11'

- uses: actions/setup-python@v5
  with:
    python-version: '3.11'
    cache: 'pip'                                 # Cache pip packages

- uses: actions/setup-python@v5
  with:
    python-version: '3.11'
    cache: 'poetry'                              # Cache poetry

# Matrice Python
strategy:
  matrix:
    python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
steps:
  - uses: actions/setup-python@v5
    with:
      python-version: ${{ matrix.python-version }}


# === Setup Node.js ===
- uses: actions/setup-node@v4
  with:
    node-version: '18'

- uses: actions/setup-node@v4
  with:
    node-version: '18'
    cache: 'npm'                                 # Cache npm

- uses: actions/setup-node@v4
  with:
    node-version-file: '.nvmrc'                  # Version depuis fichier


# === Cache ===
- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-

# Cache multiple paths
- uses: actions/cache@v4
  with:
    path: |
      ~/.cache/pip
      ~/.local/share/virtualenvs
    key: ${{ runner.os }}-python-${{ hashFiles('**/Pipfile.lock') }}


# === Upload Artifact ===
- uses: actions/upload-artifact@v4
  with:
    name: my-artifact
    path: dist/

- uses: actions/upload-artifact@v4
  with:
    name: test-results
    path: |
      test-results/
      coverage/
    retention-days: 30                           # Conservation (défaut: 90)


# === Download Artifact ===
- uses: actions/download-artifact@v4
  with:
    name: my-artifact
    path: dist/

- uses: actions/download-artifact@v4             # Tous les artifacts


# === Setup autres langages ===

# Java
- uses: actions/setup-java@v4
  with:
    distribution: 'temurin'
    java-version: '17'
    cache: 'maven'

# Go
- uses: actions/setup-go@v5
  with:
    go-version: '1.21'
    cache: true

# Ruby
- uses: ruby/setup-ruby@v1
  with:
    ruby-version: '3.2'
    bundler-cache: true

# .NET
- uses: actions/setup-dotnet@v4
  with:
    dotnet-version: '8.0.x'


# === GitHub CLI ===
- name: Create issue
  run: gh issue create --title "Bug" --body "Description"
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}


# === GitHub Script ===
- uses: actions/github-script@v7
  with:
    script: |
      const issue = await github.rest.issues.create({
        owner: context.repo.owner,
        repo: context.repo.repo,
        title: 'New issue',
        body: 'Created from workflow'
      })
      console.log(`Created issue #${issue.data.number}`)


[OK] VARIABLES & SECRETS


# === Variables d'environnement ===

# Niveau workflow
env:
  NODE_ENV: production
  API_URL: https://api.example.com

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - run: echo $NODE_ENV

# Niveau job
jobs:
  build:
    runs-on: ubuntu-latest
    env:
      BUILD_NUMBER: 123
    steps:
      - run: echo $BUILD_NUMBER

# Niveau step
steps:
  - name: Build
    run: npm run build
    env:
      NODE_OPTIONS: --max-old-space-size=4096


# === Variables GitHub (automatiques) ===

# Contexte github
- run: echo "${{ github.repository }}"           # owner/repo
- run: echo "${{ github.ref }}"                  # refs/heads/main
- run: echo "${{ github.sha }}"                  # commit SHA
- run: echo "${{ github.actor }}"                # Utilisateur déclencheur
- run: echo "${{ github.event_name }}"           # push, pull_request, etc.
- run: echo "${{ github.run_id }}"               # ID du workflow run
- run: echo "${{ github.run_number }}"           # Numéro du run

# Contexte runner
- run: echo "${{ runner.os }}"                   # Linux, Windows, macOS
- run: echo "${{ runner.arch }}"                 # X64, ARM64
- run: echo "${{ runner.temp }}"                 # Dossier temporaire

# Contexte job
- run: echo "${{ job.status }}"                  # success, failure, cancelled

# Variables d'environnement par défaut
- run: echo "$GITHUB_WORKSPACE"                  # /home/runner/work/repo/repo
- run: echo "$GITHUB_REPOSITORY"                 # owner/repo
- run: echo "$GITHUB_SHA"                        # commit SHA
- run: echo "$GITHUB_REF"                        # refs/heads/main
- run: echo "$GITHUB_REF_NAME"                   # main
- run: echo "$GITHUB_ACTOR"                      # username
- run: echo "$RUNNER_OS"                         # Linux, Windows, macOS


# === Variables de repository ===

# Définir dans Settings > Secrets and variables > Actions > Variables
# Utilisation:
- run: echo "${{ vars.MY_VARIABLE }}"


# === Secrets ===

# Définir dans Settings > Secrets and variables > Actions > Secrets
# Utilisation:
- run: echo "${{ secrets.MY_SECRET }}"

# Ne jamais logger les secrets directement!
# Ils sont automatiquement masqués dans les logs

# Exemple API key
- name: Deploy
  run: deploy.sh
  env:
    API_KEY: ${{ secrets.API_KEY }}

# Exemple tokens
env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}      # Token auto-généré
  NPM_TOKEN: ${{ secrets.NPM_TOKEN }}            # Token personnalisé


# === Variables d'environnement dynamiques ===

steps:
  - name: Set variable
    run: echo "TIMESTAMP=$(date +%s)" >> $GITHUB_ENV

  - name: Use variable
    run: echo "Timestamp: $TIMESTAMP"


# === Multiline variables ===
steps:
  - name: Set multiline variable
    run: |
      echo 'JSON_DATA<<EOF' >> $GITHUB_ENV
      echo '{"key": "value", "foo": "bar"}' >> $GITHUB_ENV
      echo 'EOF' >> $GITHUB_ENV

  - name: Use multiline variable
    run: echo "$JSON_DATA"


[OK] EXPRESSIONS & FONCTIONS


# === Syntaxe ===
${{ expression }}


# === Opérateurs ===
${{ 1 + 2 }}                                     # Addition
${{ 'hello' + ' world' }}                        # Concaténation
${{ github.ref == 'refs/heads/main' }}           # Égalité
${{ github.ref != 'refs/heads/main' }}           # Différent
${{ 5 > 3 }}                                     # Comparaison
${{ true && false }}                             # AND logique
${{ true || false }}                             # OR logique
${{ !false }}                                    # NOT logique


# === Fonctions ===

# contains - Vérifier présence
${{ contains('Hello World', 'Hello') }}          # true
${{ contains(github.ref, 'feature/') }}          # true si branche feature/*

# startsWith - Commence par
${{ startsWith(github.ref, 'refs/heads/feature/') }}

# endsWith - Finit par
${{ endsWith(github.ref, '/dev') }}

# format - Formatter string
${{ format('Hello {0} {1}', 'World', '!') }}

# join - Joindre array
${{ join(matrix.os, ', ') }}

# toJSON - Convertir en JSON
${{ toJSON(github) }}

# fromJSON - Parser JSON
${{ fromJSON('{"key": "value"}').key }}

# hashFiles - Hash de fichiers
${{ hashFiles('**/package-lock.json') }}
${{ hashFiles('**/*.py') }}


# === Fonctions de statut ===

# success - Étapes précédentes réussies
if: success()

# failure - Une étape a échoué
if: failure()

# always - Toujours exécuter
if: always()

# cancelled - Workflow annulé
if: cancelled()


# === Exemples complexes ===

# Exécuter seulement sur push main sans [skip ci]
if: |
  github.event_name == 'push' &&
  github.ref == 'refs/heads/main' &&
  !contains(github.event.head_commit.message, '[skip ci]')

# Exécuter sur PR de branches feature vers develop
if: |
  github.event_name == 'pull_request' &&
  github.base_ref == 'develop' &&
  startsWith(github.head_ref, 'feature/')

# Nom de job dynamique
name: Test-${{ matrix.python-version }}-${{ matrix.os }}

# Version dynamique depuis tag
- run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_ENV

# Déterminer environnement
- name: Set environment
  run: |
    if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
      echo "ENVIRONMENT=production" >> $GITHUB_ENV
    else
      echo "ENVIRONMENT=staging" >> $GITHUB_ENV
    fi


[OK] WORKFLOWS PYTHON COMPLETS


# === Test Python Simple ===
name: Python Tests

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install -r requirements-dev.txt
    
    - name: Run tests
      run: pytest
    
    - name: Run linter
      run: flake8 .
    
    - name: Run type checker
      run: mypy .


# === Test Matrice Multi-Python ===
name: Python Tests Matrix

on: [push, pull_request]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python ${{ matrix.python-version }}
      uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python-version }}
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
        pip install pytest pytest-cov
    
    - name: Run tests with coverage
      run: pytest --cov=./ --cov-report=xml
    
    - name: Upload coverage
      uses: codecov/codecov-action@v4
      with:
        file: ./coverage.xml
        flags: ${{ matrix.os }}-py${{ matrix.python-version }}


# === Django CI/CD ===
name: Django CI/CD

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

env:
  PYTHON_VERSION: '3.11'
  DJANGO_SETTINGS_MODULE: project.settings.test

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
      
      redis:
        image: redis:7
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
        ports:
          - 6379:6379
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: ${{ env.PYTHON_VERSION }}
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
    
    - name: Run migrations
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
      run: python manage.py migrate
    
    - name: Run tests
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
        REDIS_URL: redis://localhost:6379
      run: |
        python manage.py test
        coverage run --source='.' manage.py test
        coverage report
        coverage xml
    
    - name: Upload coverage
      uses: codecov/codecov-action@v4
      with:
        file: ./coverage.xml

  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Deploy to production
      env:
        DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
      run: |
        echo "Deploying to production..."
        ./deploy.sh


# === Poetry Workflow ===
name: Poetry CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Install Poetry
      uses: snok/install-poetry@v1
      with:
        version: 1.7.1
        virtualenvs-create: true
        virtualenvs-in-project: true
    
    - name: Load cached venv
      id: cached-poetry-dependencies
      uses: actions/cache@v4
      with:
        path: .venv
        key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}
    
    - name: Install dependencies
      if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
      run: poetry install --no-interaction --no-root
    
    - name: Install project
      run: poetry install --no-interaction
    
    - name: Run tests
      run: poetry run pytest
    
    - name: Run linting
      run: |
        poetry run black . --check
        poetry run isort . --check-only
        poetry run flake8 .


# === Publish PyPI ===
name: Publish to PyPI

on:
  release:
    types: [published]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install build twine
    
    - name: Build package
      run: python -m build
    
    - name: Publish to PyPI
      env:
        TWINE_USERNAME: __token__
        TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
      run: twine upload dist/*


# === Pre-commit Hooks ===
name: Pre-commit

on: [push, pull_request]

jobs:
  pre-commit:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Run pre-commit
      uses: pre-commit/action@v3.0.0


[OK] WORKFLOWS AVANCÉS


# === Monorepo avec paths ===
name: Monorepo CI

on:
  push:
    paths:
      - 'backend/**'
      - 'frontend/**'
      - '.github/workflows/**'

jobs:
  backend:
    if: contains(github.event.head_commit.message, 'backend') || contains(github.event.changed_files, 'backend/')
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Test backend
      working-directory: ./backend
      run: |
        pip install -r requirements.txt
        pytest

  frontend:
    if: contains(github.event.head_commit.message, 'frontend') || contains(github.event.changed_files, 'frontend/')
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Test frontend
      working-directory: ./frontend
      run: |
        npm install
        npm test


# === Workflow réutilisable ===

# .github/workflows/reusable-test.yml
name: Reusable Test Workflow

on:
  workflow_call:
    inputs:
      python-version:
        required: true
        type: string
      working-directory:
        required: false
        type: string
        default: '.'
    secrets:
      token:
        required: true

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: ${{ inputs.python-version }}
    
    - name: Run tests
      working-directory: ${{ inputs.working-directory }}
      run: pytest

# Utilisation
# .github/workflows/main.yml
name: Main CI

on: [push]

jobs:
  call-test:
    uses: ./.github/workflows/reusable-test.yml
    with:
      python-version: '3.11'
      working-directory: './src'
    secrets:
      token: ${{ secrets.GITHUB_TOKEN }}


# === Déploiement conditionnel ===
name: Deploy

on:
  push:
    branches: [main, staging, develop]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Determine environment
      id: env
      run: |
        if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
          echo "name=production" >> $GITHUB_OUTPUT
          echo "url=https://prod.example.com" >> $GITHUB_OUTPUT
        elif [[ "${{ github.ref }}" == "refs/heads/staging" ]]; then
          echo "name=staging" >> $GITHUB_OUTPUT
          echo "url=https://staging.example.com" >> $GITHUB_OUTPUT
        else
          echo "name=development" >> $GITHUB_OUTPUT
          echo "url=https://dev.example.com" >> $GITHUB_OUTPUT
        fi
    
    - name: Deploy to ${{ steps.env.outputs.name }}
      environment:
        name: ${{ steps.env.outputs.name }}
        url: ${{ steps.env.outputs.url }}
      run: |
        echo "Deploying to ${{ steps.env.outputs.name }}"
        ./deploy.sh ${{ steps.env.outputs.name }}


# === Release automatique ===
name: Auto Release

on:
  push:
    branches: [main]

jobs:
  release:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
      with:
        fetch-depth: 0
    
    - name: Get version from tag
      id: version
      run: |
        VERSION=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
        echo "current=$VERSION" >> $GITHUB_OUTPUT
        
        # Incrémenter version
        MAJOR=$(echo $VERSION | cut -d. -f1 | sed 's/v//')
        MINOR=$(echo $VERSION | cut -d. -f2)
        PATCH=$(echo $VERSION | cut -d. -f3)
        PATCH=$((PATCH + 1))
        NEW_VERSION="v$MAJOR.$MINOR.$PATCH"
        echo "new=$NEW_VERSION" >> $GITHUB_OUTPUT
    
    - name: Generate changelog
      id: changelog
      run: |
        CHANGELOG=$(git log ${{ steps.version.outputs.current }}..HEAD --pretty=format:"- %s" | head -20)
        echo "content<<EOF" >> $GITHUB_OUTPUT
        echo "$CHANGELOG" >> $GITHUB_OUTPUT
        echo "EOF" >> $GITHUB_OUTPUT
    
    - name: Create Release
      uses: actions/create-release@v1
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      with:
        tag_name: ${{ steps.version.outputs.new }}
        release_name: Release ${{ steps.version.outputs.new }}
        body: |
          ## Changes
          ${{ steps.changelog.outputs.content }}
        draft: false
        prerelease: false


# === Docker Build & Push ===
name: Docker Build

on:
  push:
    branches: [main]
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Docker Buildx
      uses: docker/setup-buildx-action@v3
    
    - name: Login to GitHub Container Registry
      uses: docker/login-action@v3
      with:
        registry: ${{ env.REGISTRY }}
        username: ${{ github.actor }}
        password: ${{ secrets.GITHUB_TOKEN }}
    
    - name: Extract metadata
      id: meta
      uses: docker/metadata-action@v5
      with:
        images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
        tags: |
          type=ref,event=branch
          type=semver,pattern={{version}}
          type=semver,pattern={{major}}.{{minor}}
          type=sha
    
    - name: Build and push
      uses: docker/build-push-action@v5
      with:
        context: .
        push: true
        tags: ${{ steps.meta.outputs.tags }}
        labels: ${{ steps.meta.outputs.labels }}
        cache-from: type=gha
        cache-to: type=gha,mode=max


# === Notification Slack ===
name: Slack Notification

on:
  push:
    branches: [main]

jobs:
  notify:
    runs-on: ubuntu-latest
    
    steps:
    - name: Notify Slack
      uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        text: 'Deployment completed!'
        webhook_url: ${{ secrets.SLACK_WEBHOOK }}
      if: always()


# === Dependency Review (PR) ===
name: Dependency Review

on: [pull_request]

jobs:
  dependency-review:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Dependency Review
      uses: actions/dependency-review-action@v4


# === Security Scanning ===
name: Security Scan

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * 0'  # Hebdomadaire

jobs:
  security:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Run Trivy vulnerability scanner
      uses: aquasecurity/trivy-action@master
      with:
        scan-type: 'fs'
        scan-ref: '.'
        format: 'sarif'
        output: 'trivy-results.sarif'
    
    - name: Upload Trivy results to GitHub Security
      uses: github/codeql-action/upload-sarif@v3
      with:
        sarif_file: 'trivy-results.sarif'


# === CodeQL Analysis ===
name: CodeQL

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * 1'

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      actions: read
      contents: read
      security-events: write
    
    strategy:
      matrix:
        language: ['python', 'javascript']
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Initialize CodeQL
      uses: github/codeql-action/init@v3
      with:
        languages: ${{ matrix.language }}
    
    - name: Autobuild
      uses: github/codeql-action/autobuild@v3
    
    - name: Perform CodeQL Analysis
      uses: github/codeql-action/analyze@v3


[OK] COMPOSITE ACTIONS


# Créer action réutilisable
# .github/actions/setup-python-env/action.yml

name: 'Setup Python Environment'
description: 'Setup Python with caching and dependencies'

inputs:
  python-version:
    description: 'Python version'
    required: true
    default: '3.11'
  cache-dependency-path:
    description: 'Path to requirements file'
    required: false
    default: '**/requirements*.txt'

runs:
  using: 'composite'
  steps:
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: ${{ inputs.python-version }}
        cache: 'pip'
        cache-dependency-path: ${{ inputs.cache-dependency-path }}
    
    - name: Install dependencies
      shell: bash
      run: |
        python -m pip install --upgrade pip
        if [ -f requirements.txt ]; then
          pip install -r requirements.txt
        fi

# Utilisation
steps:
  - uses: actions/checkout@v4
  - uses: ./.github/actions/setup-python-env
    with:
      python-version: '3.11'


# === Docker Action ===
# .github/actions/my-docker-action/action.yml

name: 'My Docker Action'
description: 'Run custom Docker action'

inputs:
  who-to-greet:
    description: 'Who to greet'
    required: true

outputs:
  time:
    description: 'The time we greeted you'

runs:
  using: 'docker'
  image: 'Dockerfile'
  args:
    - ${{ inputs.who-to-greet }}


# === JavaScript Action ===
# .github/actions/my-js-action/action.yml

name: 'My JavaScript Action'
description: 'Run custom JS action'

inputs:
  milliseconds:
    description: 'Time to wait'
    required: true
    default: '1000'

runs:
  using: 'node20'
  main: 'dist/index.js'


[OK] GITHUB PAGES DEPLOYMENT


# === Deploy Static Site ===
name: Deploy to GitHub Pages

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Node
      uses: actions/setup-node@v4
      with:
        node-version: '18'
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Build
      run: npm run build
    
    - name: Upload artifact
      uses: actions/upload-pages-artifact@v3
      with:
        path: './dist'
  
  deploy:
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    runs-on: ubuntu-latest
    needs: build
    steps:
    - name: Deploy to GitHub Pages
      id: deployment
      uses: actions/deploy-pages@v4


# === MkDocs Documentation ===
name: Deploy Documentation

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Install dependencies
      run: |
        pip install mkdocs mkdocs-material
    
    - name: Deploy docs
      run: mkdocs gh-deploy --force


# === Sphinx Documentation ===
name: Build Sphinx Docs

on: [push]

jobs:
  docs:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Install dependencies
      run: |
        pip install sphinx sphinx_rtd_theme
    
    - name: Build docs
      run: |
        cd docs
        make html
    
    - name: Deploy to GitHub Pages
      uses: peaceiris/actions-gh-pages@v3
      with:
        github_token: ${{ secrets.GITHUB_TOKEN }}
        publish_dir: ./docs/_build/html


[OK] MATRICES AVANCÉES


# === Matrice avec exclusions ===
strategy:
  matrix:
    os: [ubuntu-latest, windows-latest, macos-latest]
    python: ['3.8', '3.9', '3.10', '3.11', '3.12']
    exclude:
      # Python 3.8 pas supporté sur macOS 14 (ARM)
      - os: macos-latest
        python: '3.8'
      # Python 3.12 pas testé sur Windows
      - os: windows-latest
        python: '3.12'


# === Matrice avec inclusions ===
strategy:
  matrix:
    os: [ubuntu-latest]
    python: ['3.11']
    include:
      # Tests spéciaux sur Python 3.12
      - os: ubuntu-latest
        python: '3.12'
        experimental: true
      # Tests Windows seulement sur Python stable
      - os: windows-latest
        python: '3.11'
      # Tests macOS ARM64
      - os: macos-14
        python: '3.11'
        arch: ARM64


# === Matrice dynamique ===
jobs:
  setup:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
    - id: set-matrix
      run: |
        if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
          echo "matrix={\"python\":[\"3.9\",\"3.10\",\"3.11\",\"3.12\"]}" >> $GITHUB_OUTPUT
        else
          echo "matrix={\"python\":[\"3.11\"]}" >> $GITHUB_OUTPUT
        fi
  
  test:
    needs: setup
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
    steps:
    - uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python }}


[OK] CONCURRENCY & CANCELLATION


# === Annuler workflows en cours ===
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

# Annule workflows précédents sur même branche
# Utile pour économiser minutes CI


# === Concurrency par PR ===
concurrency:
  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
  cancel-in-progress: true


# === Pas d'annulation sur main ===
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}


[OK] PERMISSIONS


# === Permissions par défaut (restrictives) ===
permissions: {}

# === Lecture seule ===
permissions: read-all

# === Permissions spécifiques ===
permissions:
  contents: read          # Lire code
  pull-requests: write    # Commenter PR
  issues: write           # Créer/modifier issues
  packages: write         # Publier packages
  deployments: write      # Créer déploiements
  statuses: write         # Créer status checks


# === Permissions au niveau job ===
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
    - run: echo "Building..."


# === Token GITHUB_TOKEN ===
# Token automatique avec permissions limitées
# Expire à la fin du workflow
# Utilisation:
env:
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


[OK] ENVIRONMENTS


# === Définir environnement ===
jobs:
  deploy:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    steps:
    - name: Deploy
      run: ./deploy.sh


# === Environnement avec approval ===
# Configuration dans Settings > Environments
# - Required reviewers
# - Wait timer
# - Deployment branches

environment:
  name: production
  url: ${{ steps.deploy.outputs.url }}


# === Variables d'environnement ===
# Définies dans Settings > Environments > production > Variables
jobs:
  deploy:
    environment: production
    steps:
    - run: echo "${{ vars.API_URL }}"
    - run: echo "${{ secrets.API_KEY }}"


[OK] DEBUGGING


# === Enable debug logging ===
# Settings > Secrets > New repository secret
# Name: ACTIONS_RUNNER_DEBUG
# Value: true

# Name: ACTIONS_STEP_DEBUG
# Value: true


# === Debug dans workflow ===
steps:
  - name: Debug info
    run: |
      echo "Event name: ${{ github.event_name }}"
      echo "Ref: ${{ github.ref }}"
      echo "SHA: ${{ github.sha }}"
      echo "Actor: ${{ github.actor }}"
      echo "Runner OS: ${{ runner.os }}"
      echo "Working directory: $PWD"
      env


# === Dump contexts ===
- name: Dump GitHub context
  run: echo '${{ toJSON(github) }}'

- name: Dump job context
  run: echo '${{ toJSON(job) }}'

- name: Dump steps context
  run: echo '${{ toJSON(steps) }}'

- name: Dump runner context
  run: echo '${{ toJSON(runner) }}'

- name: Dump env context
  run: echo '${{ toJSON(env) }}'


# === SSH Debug ===
- name: Setup tmate session
  uses: mxschmitt/action-tmate@v3
  if: failure()
  # Crée session SSH pour débugger


[OK] BEST PRACTICES


# === 1. Pin versions des actions ===
# [OK] BON
uses: actions/checkout@v4

# [OK] MEILLEUR (commit SHA)
uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab

# [X] MAUVAIS
uses: actions/checkout@main


# === 2. Utiliser cache ===
- uses: actions/cache@v4
  with:
    path: ~/.cache/pip
    key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
    restore-keys: |
      ${{ runner.os }}-pip-


# === 3. Fail-fast désactivé pour matrices ===
strategy:
  fail-fast: false      # Continue même si un test échoue
  matrix:
    python: ['3.9', '3.10', '3.11']


# === 4. Timeouts ===
jobs:
  build:
    timeout-minutes: 30
    steps:
    - name: Long task
      timeout-minutes: 10
      run: ./long-script.sh


# === 5. Ne pas logger secrets ===
# [X] MAUVAIS
- run: echo "Token: ${{ secrets.API_TOKEN }}"

# [OK] BON
- run: |
    echo "::add-mask::${{ secrets.API_TOKEN }}"
    ./script.sh
  env:
    API_TOKEN: ${{ secrets.API_TOKEN }}


# === 6. Concurrency pour économiser minutes ===
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true


# === 7. Conditions pour skip ===
if: "!contains(github.event.head_commit.message, '[skip ci]')"


# === 8. Artifacts pour partager entre jobs ===
jobs:
  build:
    steps:
    - run: npm run build
    - uses: actions/upload-artifact@v4
      with:
        name: dist
        path: dist/
  
  deploy:
    needs: build
    steps:
    - uses: actions/download-artifact@v4
      with:
        name: dist


# === 9. Séparer workflows ===
# .github/workflows/ci.yml - Tests
# .github/workflows/deploy.yml - Déploiement
# .github/workflows/cron.yml - Tâches périodiques


# === 10. Utiliser dependabot ===
# .github/dependabot.yml
version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"


[OK] ACTIONS MARKETPLACE POPULAIRES


# === Code Quality ===

# Super-Linter
- uses: github/super-linter@v5
  env:
    DEFAULT_BRANCH: main
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# SonarCloud
- uses: SonarSource/sonarcloud-github-action@master
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}


# === Coverage ===

# Codecov
- uses: codecov/codecov-action@v4
  with:
    token: ${{ secrets.CODECOV_TOKEN }}
    files: ./coverage.xml

# Coveralls
- uses: coverallsapp/github-action@master
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}


# === Release ===

# Semantic Release
- uses: cycjimmy/semantic-release-action@v4
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

# Release Drafter
- uses: release-drafter/release-drafter@v5
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


# === Notifications ===

# Slack
- uses: 8398a7/action-slack@v3
  with:
    status: ${{ job.status }}
    webhook_url: ${{ secrets.SLACK_WEBHOOK }}

# Discord
- uses: sarisia/actions-status-discord@v1
  with:
    webhook: ${{ secrets.DISCORD_WEBHOOK }}

# Telegram
- uses: appleboy/telegram-action@master
  with:
    to: ${{ secrets.TELEGRAM_TO }}
    token: ${{ secrets.TELEGRAM_TOKEN }}
    message: Deployment completed!


# === Deployment ===

# AWS
- uses: aws-actions/configure-aws-credentials@v4
  with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: us-east-1

# Heroku
- uses: akhileshns/heroku-deploy@v3.13.15
  with:
    heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
    heroku_app_name: "my-app"
    heroku_email: "email@example.com"

# Vercel
- uses: amondnet/vercel-action@v25
  with:
    vercel-token: ${{ secrets.VERCEL_TOKEN }}
    vercel-org-id: ${{ secrets.ORG_ID }}
    vercel-project-id: ${{ secrets.PROJECT_ID }}


# === Utilities ===

# Label PR
- uses: actions/labeler@v5
  with:
    repo-token: ${{ secrets.GITHUB_TOKEN }}

# Stale Bot
- uses: actions/stale@v9
  with:
    stale-issue-message: 'This issue is stale'
    days-before-stale: 30
    days-before-close: 7

# Auto-merge Dependabot
- uses: ahmadnassri/action-dependabot-auto-merge@v2
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}


[OK] TROUBLESHOOTING


# === Erreur: Permission denied ===
# Solution: Ajouter permissions
permissions:
  contents: write
  pull-requests: write


# === Erreur: Resource not accessible by integration ===
# Solution: Vérifier permissions GITHUB_TOKEN
permissions:
  contents: read
  packages: write


# === Workflow ne se déclenche pas ===
# Vérifier:
# 1. Fichier dans .github/workflows/
# 2. Extension .yml ou .yaml
# 3. Syntaxe YAML valide
# 4. Événement configuré correctement
# 5. Branches/paths correspondent


# === Cache ne fonctionne pas ===
# Vérifier:
# 1. Key unique et stable
# 2. Path correct
# 3. Restore-keys configuré
# 4. Cache pas expiré (7 jours)


# === Secrets non disponibles ===
# Vérifier:
# 1. Secret défini dans Settings > Secrets
# 2. Scope correct (repo/org/environment)
# 3. Syntaxe: ${{ secrets.SECRET_NAME }}


# === Workflow trop lent ===
# Optimisations:
# 1. Activer cache (pip, npm, etc.)
# 2. Parallel jobs
# 3. Matrice fail-fast: false
# 4. Concurrency cancel-in-progress
# 5. Réduire checkout fetch-depth


# === Limite de minutes dépassée ===
# Solutions:
# 1. Optimiser workflows
# 2. Self-hosted runners
# 3. Upgrade plan GitHub
# 4. Split workflows


# === Artifact trop gros ===
# Limite: 10GB par workflow
# Solutions:
# 1. Compresser artifacts
# 2. Réduire retention-days
# 3. Nettoyer artifacts inutiles


[OK] SELF-HOSTED RUNNERS


# === Installation ===

# Linux
mkdir actions-runner && cd actions-runner
curl -o actions-runner-linux-x64-2.311.0.tar.gz \
  -L https://github.com/actions/runner/releases/download/v2.311.0/actions-runner-linux-x64-2.311.0.tar.gz
tar xzf ./actions-runner-linux-x64-2.311.0.tar.gz

# Configuration
./config.sh --url https://github.com/OWNER/REPO --token TOKEN

# Lancer
./run.sh

# Service (Linux)
sudo ./svc.sh install
sudo ./svc.sh start


# === Labels ===
./config.sh --url URL --token TOKEN --labels prod,gpu,linux


# === Utilisation ===
runs-on: self-hosted

runs-on: [self-hosted, linux, x64, gpu]


# === Runner Groups (Enterprise) ===
runs-on:
  group: production-runners
  labels: [linux, x64]


# === Docker dans runner ===
jobs:
  build:
    runs-on: self-hosted
    container:
      image: python:3.11
    steps:
    - run: python --version


[OK] SECURITY


# === Dependabot ===
# .github/dependabot.yml
version: 2
updates:
  # GitHub Actions
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"
  
  # Python
  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "daily"


# === Secret scanning ===
# Automatique pour repos publics
# Settings > Code security and analysis


# === Code scanning (CodeQL) ===
name: "CodeQL"

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  schedule:
    - cron: '0 0 * * 1'

jobs:
  analyze:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
    - uses: actions/checkout@v4
    - uses: github/codeql-action/init@v3
      with:
        languages: python
    - uses: github/codeql-action/analyze@v3


# === OIDC (OpenID Connect) ===
# Authentification sans secrets
permissions:
  id-token: write
  contents: read

steps:
- uses: aws-actions/configure-aws-credentials@v4
  with:
    role-to-assume: arn:aws:iam::123456789012:role/my-role
    aws-region: us-east-1


[OK] RESSOURCES


# Documentation officielle
https://docs.github.com/en/actions

# Actions Marketplace
https://github.com/marketplace?type=actions

# Awesome Actions
https://github.com/sdras/awesome-actions

# Status GitHub Actions
https://www.githubstatus.com/

# Community Forum
https://github.community/c/github-actions/

# Pricing
https://docs.github.com/en/billing/managing-billing-for-github-actions


[OK] EXEMPLES COMPLETS PAR STACK


# === FastAPI ===
name: FastAPI CI/CD

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install pytest pytest-cov httpx
    
    - name: Run migrations
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
      run: alembic upgrade head
    
    - name: Run tests
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
      run: pytest --cov=app --cov-report=xml
    
    - name: Upload coverage
      uses: codecov/codecov-action@v4
      with:
        file: ./coverage.xml
  
  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Deploy to production
      run: |
        echo "Deploying FastAPI app..."


# === Flask ===
name: Flask CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      redis:
        image: redis:7
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
        ports:
          - 6379:6379
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install pytest pytest-flask pytest-cov
    
    - name: Run tests
      env:
        FLASK_ENV: testing
        REDIS_URL: redis://localhost:6379
      run: |
        pytest --cov=app --cov-report=xml tests/
    
    - name: Lint with flake8
      run: flake8 app/ tests/
    
    - name: Type check with mypy
      run: mypy app/


# === Celery + Redis ===
name: Celery Tasks

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    services:
      redis:
        image: redis:7
        ports:
          - 6379:6379
      
      rabbitmq:
        image: rabbitmq:3-management
        ports:
          - 5672:5672
          - 15672:15672
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install pytest pytest-celery
    
    - name: Run Celery tests
      env:
        CELERY_BROKER_URL: redis://localhost:6379/0
        CELERY_RESULT_BACKEND: redis://localhost:6379/0
      run: pytest tests/test_celery.py


# === Scrapy ===
name: Scrapy Spider

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 */6 * * *'  # Toutes les 6 heures

jobs:
  scrape:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Install dependencies
      run: |
        pip install scrapy scrapyd-client
    
    - name: Run spider
      run: |
        scrapy crawl myspider -o output.json
    
    - name: Upload results
      uses: actions/upload-artifact@v4
      with:
        name: scrape-results
        path: output.json


# === Jupyter Notebooks ===
name: Jupyter Notebooks

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        pip install jupyter nbconvert pytest nbval
    
    - name: Test notebooks
      run: pytest --nbval-lax notebooks/
    
    - name: Convert notebooks to HTML
      run: |
        jupyter nbconvert --to html notebooks/*.ipynb
    
    - name: Upload HTML
      uses: actions/upload-artifact@v4
      with:
        name: notebooks-html
        path: notebooks/*.html


# === Data Science Pipeline ===
name: ML Pipeline

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 0 * * *'  # Daily

jobs:
  train:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install mlflow scikit-learn pandas numpy
    
    - name: Download data
      run: python scripts/download_data.py
    
    - name: Train model
      run: python scripts/train_model.py
      env:
        MLFLOW_TRACKING_URI: ${{ secrets.MLFLOW_URI }}
    
    - name: Evaluate model
      run: python scripts/evaluate_model.py
    
    - name: Upload model
      uses: actions/upload-artifact@v4
      with:
        name: trained-model
        path: models/


# === AWS Lambda Deployment ===
name: Deploy Lambda

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt -t package/
        cp lambda_function.py package/
    
    - name: Create deployment package
      run: |
        cd package
        zip -r ../lambda.zip .
    
    - name: Configure AWS credentials
      uses: aws-actions/configure-aws-credentials@v4
      with:
        aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
        aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        aws-region: us-east-1
    
    - name: Deploy to Lambda
      run: |
        aws lambda update-function-code \
          --function-name my-function \
          --zip-file fileb://lambda.zip


# === Google Cloud Run ===
name: Deploy to Cloud Run

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Authenticate to Google Cloud
      uses: google-github-actions/auth@v2
      with:
        credentials_json: ${{ secrets.GCP_SA_KEY }}
    
    - name: Set up Cloud SDK
      uses: google-github-actions/setup-gcloud@v2
    
    - name: Build and push Docker image
      run: |
        gcloud builds submit \
          --tag gcr.io/${{ secrets.GCP_PROJECT }}/myapp
    
    - name: Deploy to Cloud Run
      run: |
        gcloud run deploy myapp \
          --image gcr.io/${{ secrets.GCP_PROJECT }}/myapp \
          --platform managed \
          --region us-central1 \
          --allow-unauthenticated


# === Azure Functions ===
name: Deploy to Azure Functions

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt --target=".python_packages/lib/site-packages"
    
    - name: Deploy to Azure Functions
      uses: Azure/functions-action@v1
      with:
        app-name: my-function-app
        package: .
        publish-profile: ${{ secrets.AZURE_FUNCTIONAPP_PUBLISH_PROFILE }}


# === Terraform ===
name: Terraform

on:
  push:
    branches: [main]
  pull_request:

jobs:
  terraform:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v3
      with:
        terraform_version: 1.6.0
    
    - name: Terraform Init
      run: terraform init
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    
    - name: Terraform Format
      run: terraform fmt -check
    
    - name: Terraform Validate
      run: terraform validate
    
    - name: Terraform Plan
      run: terraform plan -out=tfplan
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    
    - name: Terraform Apply
      if: github.ref == 'refs/heads/main' && github.event_name == 'push'
      run: terraform apply -auto-approve tfplan
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}


[OK] WORKFLOWS AVEC APPROBATION


# === Déploiement avec approbation manuelle ===
name: Deploy with Approval

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Build
      run: npm run build
    - uses: actions/upload-artifact@v4
      with:
        name: build
        path: dist/
  
  request-approval:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: production-approval
    steps:
    - name: Waiting for approval
      run: echo "Deployment approved!"
  
  deploy:
    needs: request-approval
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://example.com
    steps:
    - uses: actions/download-artifact@v4
      with:
        name: build
    - name: Deploy
      run: ./deploy.sh


# === Workflow avec review multiple ===
# Configuration dans Settings > Environments > production
# Required reviewers: 2 reviewers minimum


[OK] CACHING AVANCÉ


# === Cache avec fallback ===
- name: Cache Python dependencies
  uses: actions/cache@v4
  with:
    path: |
      ~/.cache/pip
      ~/.local/share/virtualenvs
    key: ${{ runner.os }}-python-${{ hashFiles('**/Pipfile.lock') }}
    restore-keys: |
      ${{ runner.os }}-python-${{ hashFiles('**/Pipfile.lock') }}
      ${{ runner.os }}-python-
      ${{ runner.os }}-


# === Cache Docker layers ===
- name: Set up Docker Buildx
  uses: docker/setup-buildx-action@v3

- name: Cache Docker layers
  uses: actions/cache@v4
  with:
    path: /tmp/.buildx-cache
    key: ${{ runner.os }}-buildx-${{ github.sha }}
    restore-keys: |
      ${{ runner.os }}-buildx-

- name: Build
  uses: docker/build-push-action@v5
  with:
    cache-from: type=local,src=/tmp/.buildx-cache
    cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max

- name: Move cache
  run: |
    rm -rf /tmp/.buildx-cache
    mv /tmp/.buildx-cache-new /tmp/.buildx-cache


# === Cache npm/yarn/pnpm ===
- uses: actions/setup-node@v4
  with:
    node-version: '18'
    cache: 'npm'
    cache-dependency-path: '**/package-lock.json'


# === Cache Poetry ===
- name: Cache Poetry dependencies
  uses: actions/cache@v4
  with:
    path: ~/.cache/pypoetry
    key: ${{ runner.os }}-poetry-${{ hashFiles('**/poetry.lock') }}


# === Cache Gradle ===
- name: Cache Gradle packages
  uses: actions/cache@v4
  with:
    path: |
      ~/.gradle/caches
      ~/.gradle/wrapper
    key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }}


[OK] STRATÉGIES DE DÉPLOIEMENT


# === Blue-Green Deployment ===
name: Blue-Green Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Deploy to green environment
      run: ./deploy.sh green
    
    - name: Run smoke tests
      run: ./smoke-tests.sh green
    
    - name: Switch traffic to green
      run: ./switch-traffic.sh green
    
    - name: Monitor for 5 minutes
      run: sleep 300
    
    - name: Rollback if needed
      if: failure()
      run: ./switch-traffic.sh blue


# === Canary Deployment ===
name: Canary Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Deploy canary (10%)
      run: ./deploy-canary.sh --traffic=10
    
    - name: Wait and monitor
      run: sleep 600
    
    - name: Increase to 50%
      run: ./deploy-canary.sh --traffic=50
    
    - name: Wait and monitor
      run: sleep 600
    
    - name: Full deployment
      run: ./deploy-canary.sh --traffic=100


# === Rolling Deployment ===
name: Rolling Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        server: [server1, server2, server3, server4]
      max-parallel: 1  # Un serveur à la fois
    steps:
    - uses: actions/checkout@v4
    
    - name: Deploy to ${{ matrix.server }}
      run: ./deploy.sh ${{ matrix.server }}
    
    - name: Health check
      run: ./health-check.sh ${{ matrix.server }}
    
    - name: Wait before next
      run: sleep 60


[OK] MULTI-ENVIRONMENT


# === Stratégie par environnement ===
name: Multi-Environment Deploy

on:
  push:
    branches: [dev, staging, main]

jobs:
  determine-env:
    runs-on: ubuntu-latest
    outputs:
      environment: ${{ steps.set-env.outputs.environment }}
    steps:
    - id: set-env
      run: |
        if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
          echo "environment=production" >> $GITHUB_OUTPUT
        elif [[ "${{ github.ref }}" == "refs/heads/staging" ]]; then
          echo "environment=staging" >> $GITHUB_OUTPUT
        else
          echo "environment=development" >> $GITHUB_OUTPUT
        fi
  
  deploy:
    needs: determine-env
    runs-on: ubuntu-latest
    environment:
      name: ${{ needs.determine-env.outputs.environment }}
    steps:
    - uses: actions/checkout@v4
    
    - name: Deploy to ${{ needs.determine-env.outputs.environment }}
      run: |
        echo "Deploying to ${{ needs.determine-env.outputs.environment }}"
        ./deploy.sh ${{ needs.determine-env.outputs.environment }}
      env:
        API_KEY: ${{ secrets.API_KEY }}
        DATABASE_URL: ${{ vars.DATABASE_URL }}


[OK] WORKFLOW DISPATCH AVANCÉ


# === Déploiement manuel avec options ===
name: Manual Deploy

on:
  workflow_dispatch:
    inputs:
      environment:
        description: 'Target environment'
        required: true
        type: choice
        options:
          - development
          - staging
          - production
      
      version:
        description: 'Version to deploy (leave empty for latest)'
        required: false
        type: string
      
      run_migrations:
        description: 'Run database migrations'
        required: true
        type: boolean
        default: true
      
      send_notification:
        description: 'Send Slack notification'
        required: false
        type: boolean
        default: true
      
      rollback:
        description: 'Rollback deployment'
        required: false
        type: boolean
        default: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    
    steps:
    - uses: actions/checkout@v4
      with:
        ref: ${{ inputs.version || github.ref }}
    
    - name: Deploy application
      if: ${{ !inputs.rollback }}
      run: ./deploy.sh ${{ inputs.environment }}
    
    - name: Rollback
      if: ${{ inputs.rollback }}
      run: ./rollback.sh ${{ inputs.environment }}
    
    - name: Run migrations
      if: ${{ inputs.run_migrations && !inputs.rollback }}
      run: ./migrate.sh
    
    - name: Notify Slack
      if: ${{ inputs.send_notification }}
      uses: 8398a7/action-slack@v3
      with:
        status: ${{ job.status }}
        text: |
          Deployment to ${{ inputs.environment }}
          Version: ${{ inputs.version || 'latest' }}
          Migrations: ${{ inputs.run_migrations }}
        webhook_url: ${{ secrets.SLACK_WEBHOOK }}


[OK] CONDITIONAL WORKFLOWS


# === Exécuter selon fichiers modifiés ===
name: Smart CI

on:
  pull_request:
    paths:
      - 'backend/**'
      - 'frontend/**'
      - 'infrastructure/**'

jobs:
  detect-changes:
    runs-on: ubuntu-latest
    outputs:
      backend: ${{ steps.filter.outputs.backend }}
      frontend: ${{ steps.filter.outputs.frontend }}
      infrastructure: ${{ steps.filter.outputs.infrastructure }}
    steps:
    - uses: actions/checkout@v4
    
    - uses: dorny/paths-filter@v2
      id: filter
      with:
        filters: |
          backend:
            - 'backend/**'
          frontend:
            - 'frontend/**'
          infrastructure:
            - 'infrastructure/**'
  
  test-backend:
    needs: detect-changes
    if: needs.detect-changes.outputs.backend == 'true'
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Test backend
      run: cd backend && pytest
  
  test-frontend:
    needs: detect-changes
    if: needs.detect-changes.outputs.frontend == 'true'
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Test frontend
      run: cd frontend && npm test
  
  deploy-infrastructure:
    needs: detect-changes
    if: needs.detect-changes.outputs.infrastructure == 'true'
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Deploy infrastructure
      run: cd infrastructure && terraform apply


# === Skip CI ===
name: CI with Skip

on: [push, pull_request]

jobs:
  test:
    if: "!contains(github.event.head_commit.message, '[skip ci]')"
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - run: npm test


[OK] PERFORMANCE & OPTIMIZATION


# === Workflow parallèle optimisé ===
name: Optimized CI

on: [push, pull_request]

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

jobs:
  setup:
    runs-on: ubuntu-latest
    outputs:
      cache-key: ${{ steps.cache-key.outputs.value }}
    steps:
    - uses: actions/checkout@v4
    
    - id: cache-key
      run: echo "value=${{ hashFiles('**/requirements.txt') }}" >> $GITHUB_OUTPUT
    
    - uses: actions/cache@v4
      id: cache
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ steps.cache-key.outputs.value }}
    
    - name: Install dependencies
      if: steps.cache.outputs.cache-hit != 'true'
      run: pip install -r requirements.txt
  
  lint:
    needs: setup
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/cache@v4
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ needs.setup.outputs.cache-key }}
    - run: flake8 .
  
  test:
    needs: setup
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.10', '3.11']
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python-version }}
    - uses: actions/cache@v4
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ needs.setup.outputs.cache-key }}
    - run: pytest
  
  security:
    needs: setup
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/cache@v4
      with:
        path: ~/.cache/pip
        key: ${{ runner.os }}-pip-${{ needs.setup.outputs.cache-key }}
    - run: bandit -r .


[OK] GESTION D'ERREURS


# === Retry on failure ===
- name: Flaky test with retry
  uses: nick-fields/retry@v2
  with:
    timeout_minutes: 10
    max_attempts: 3
    retry_on: error
    command: pytest tests/integration


# === Notification sur échec ===
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: Run tests
      run: pytest
    
    - name: Notify on failure
      if: failure()
      uses: 8398a7/action-slack@v3
      with:
        status: failure
        text: 'Tests failed!'
        webhook_url: ${{ secrets.SLACK_WEBHOOK }}


# === Continue on error spécifique ===
- name: Optional step
  id: optional
  continue-on-error: true
  run: ./optional-script.sh

- name: Check optional result
  if: steps.optional.outcome == 'failure'
  run: echo "Optional step failed but we continue"


[OK] CUSTOM GITHUB ACTIONS (JavaScript)


# === Action JavaScript personnalisée ===
# .github/actions/hello/action.yml

name: 'Hello Action'
description: 'Say hello'
inputs:
  who-to-greet:
    description: 'Who to greet'
    required: true
    default: 'World'
outputs:
  time:
    description: 'Time of greeting'
runs:
  using: 'node20'
  main: 'index.js'

# .github/actions/hello/index.js
const core = require('@actions/core');
const github = require('@actions/github');

try {
  const nameToGreet = core.getInput('who-to-greet');
  console.log(`Hello ${nameToGreet}!`);
  
  const time = (new Date()).toTimeString();
  core.setOutput('time', time);
  
  const payload = JSON.stringify(github.context.payload, undefined, 2);
  console.log(`The event payload: ${payload}`);
} catch (error) {
  core.setFailed(error.message);
}

# Utilisation
- uses: ./.github/actions/hello
  with:
    who-to-greet: 'Alice'


[OK] PATTERNS AVANCÉS


# === Job dependencies complexes ===
jobs:
  build-backend:
    runs-on: ubuntu-latest
    steps:
    - run: echo "Building backend"
  
  build-frontend:
    runs-on: ubuntu-latest
    steps:
    - run: echo "Building frontend"
  
  test-backend:
    needs: build-backend
    runs-on: ubuntu-latest
    steps:
    - run: echo "Testing backend"
  
  test-frontend:
    needs: build-frontend
    runs-on: ubuntu-latest
    steps:
    - run: echo "Testing frontend"
  
  integration-tests:
    needs: [build-backend, build-frontend]
    runs-on: ubuntu-latest
    steps:
    - run: echo "Integration tests"
  
  deploy:
    needs: [test-backend, test-frontend, integration-tests]
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
    - run: echo "Deploying"


# === Dynamic job creation ===
jobs:
  setup:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.set-matrix.outputs.matrix }}
    steps:
    - uses: actions/checkout@v4
    
    - id: set-matrix
      run: |
        # Lire depuis fichier ou API
        MATRIX=$(cat .github/test-matrix.json)
        echo "matrix=$MATRIX" >> $GITHUB_OUTPUT
  
  test:
    needs: setup
    runs-on: ubuntu-latest
    strategy:
      matrix: ${{ fromJSON(needs.setup.outputs.matrix) }}
    steps:
    - uses: actions/checkout@v4
    - run: echo "Testing with ${{ matrix.version }}"


# === Workflow de workflow ===
name: Orchestrator

on:
  workflow_dispatch:

jobs:
  trigger-tests:
    runs-on: ubuntu-latest
    steps:
    - name: Trigger test workflow
      uses: actions/github-script@v7
      with:
        script: |
          await github.rest.actions.createWorkflowDispatch({
            owner: context.repo.owner,
            repo: context.repo.repo,
            workflow_id: 'test.yml',
            ref: 'main'
          })


[OK] MONITORING & OBSERVABILITY


# === Status badge ===
# README.md
[![CI](https://github.com/username/repo/actions/workflows/ci.yml/badge.svg)](https://github.com/username/repo/actions/workflows/ci.yml)


# === Métriques de workflow ===
name: Metrics

on:
  workflow_run:
    workflows: ["CI"]
    types: [completed]

jobs:
  metrics:
    runs-on: ubuntu-latest
    steps:
    - name: Get workflow metrics
      uses: actions/github-script@v7
      with:
        script: |
          const run = context.payload.workflow_run;
          console.log(`Workflow: ${run.name}`);
          console.log(`Status: ${run.conclusion}`);
          console.log(`Duration: ${run.run_started_at} - ${run.updated_at}`);
          
          // Envoyer à système monitoring
          const duration = new Date(run.updated_at) - new Date(run.run_started_at);
          console.log(`Duration ms: ${duration}`);


# === Custom metrics ===
- name: Record build time
  run: |
    START_TIME=$(date +%s)
    npm run build
    END_TIME=$(date +%s)
    DURATION=$((END_TIME - START_TIME))
    echo "Build took ${DURATION} seconds"
    echo "BUILD_DURATION=$DURATION" >> $GITHUB_ENV

- name: Send metrics to DataDog
  env:
    DD_API_KEY: ${{ secrets.DATADOG_API_KEY }}
  run: |
    curl -X POST "https://api.datadoghq.com/api/v1/series" \
      -H "Content-Type: application/json" \
      -H "DD-API-KEY: ${DD_API_KEY}" \
      -d "{\"series\": [{\"metric\": \"github.build.duration\", \"points\": [[$(date +%s), ${BUILD_DURATION}]]}]}"


[OK] COST OPTIMIZATION


# === Économiser les minutes CI ===

# 1. Cache agressif
- uses: actions/cache@v4
  with:
    path: |
      ~/.cache/pip
      ~/.npm
      ~/.cargo
    key: ${{ runner.os }}-deps-${{ hashFiles('**/*.lock') }}

# 2. Concurrency
concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

# 3. Conditional execution
on:
  push:
    branches: [main]
    paths-ignore:
      - '**.md'
      - 'docs/**'

# 4. Matrice réduite sur PR
strategy:
  matrix:
    python-version: ${{ github.event_name == 'push' && fromJSON('["3.9", "3.10", "3.11", "3.12"]') || fromJSON('["3.11"]') }}

# 5. Self-hosted runners pour projets intenses

# 6. Timeouts courts
timeout-minutes: 10

# 7. Skip CI commits
if: "!contains(github.event.head_commit.message, '[skip ci]')"


[OK] MIGRATION VERS GITHUB ACTIONS


# === Depuis Travis CI ===

# .travis.yml
language: python
python:
  - "3.9"
  - "3.10"
script:
  - pytest

# Équivalent GitHub Actions
name: CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.10']
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python-version }}
    - run: pip install -r requirements.txt
    - run: pytest


# === Depuis CircleCI ===

# .circleci/config.yml
version: 2.1
jobs:
  test:
    docker:
      - image: python:3.11
    steps:
      - checkout
      - run: pip install -r requirements.txt
      - run: pytest

# Équivalent GitHub Actions
name: CI

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: python:3.11
    steps:
    - uses: actions/checkout@v4
    - run: pip install -r requirements.txt
    - run: pytest


# === Depuis GitLab CI ===

# .gitlab-ci.yml
test:
  image: python:3.11
  script:
    - pip install -r requirements.txt
    - pytest

# Équivalent GitHub Actions
name: CI

on: [push]

jobs:
  test:
    runs-on: ubuntu-latest
    container:
      image: python:3.11
    steps:
    - uses: actions/checkout@v4
    - run: pip install -r requirements.txt
    - run: pytest


[OK] TESTING WORKFLOWS LOCALEMENT


# === act - Run GitHub Actions locally ===

# Installation
# macOS
brew install act

# Linux
curl https://raw.githubusercontent.com/nektos/act/master/install.sh | sudo bash

# Windows
choco install act-cli

# Usage
act                           # Exécute on: push
act pull_request              # Exécute on: pull_request
act -j test                   # Exécute job spécifique
act -l                        # Liste les workflows

# Avec secrets
act -s GITHUB_TOKEN=xxx

# Avec variables
act -var API_URL=http://localhost

# Spécifier runner
act -P ubuntu-latest=nektos/act-environments-ubuntu:18.04


[OK] GITHUB CLI INTEGRATION


# === Déclencher workflow ===
gh workflow run ci.yml

gh workflow run deploy.yml \
  -f environment=production \
  -f version=1.2.3

# === Lister workflows ===
gh workflow list

# === Voir runs ===
gh run list

gh run list --workflow=ci.yml

gh run list --status=failure

# === Voir run spécifique ===
gh run view 123456789

gh run view --log

# === Re-run workflow ===
gh run rerun 123456789

gh run rerun --failed

# === Annuler run ===
gh run cancel 123456789

# === Télécharger artifacts ===
gh run download 123456789

gh run download 123456789 -n artifact-name

# === Voir logs ===
gh run view --log

gh run view 123456789 --log-failed


[OK] BRANCH PROTECTION AVEC ACTIONS


# === Configuration recommandée ===
# Settings > Branches > Branch protection rules

# 1. Require status checks to pass before merging
#    [OK] CI / test
#    [OK] CI / lint
#    [OK] CI / security-scan

# 2. Require branches to be up to date before merging

# 3. Require pull request reviews before merging
#    - Required approvals: 1+

# 4. Require conversation resolution before merging

# === Status check workflow ===
name: Status Check

on:
  pull_request:
    types: [opened, synchronize, reopened]

jobs:
  status:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Check PR title
      run: |
        if [[ ! "${{ github.event.pull_request.title }}" =~ ^(feat|fix|docs|chore): ]]; then
          echo "PR title must start with feat:, fix:, docs:, or chore:"
          exit 1
        fi
    
    - name: Check PR description
      run: |
        if [ -z "${{ github.event.pull_request.body }}" ]; then
          echo "PR description cannot be empty"
          exit 1
        fi


[OK] LIMITATIONS & QUOTAS


# === Limites par workflow ===
- Max 1000 workflows par repository
- Max 256 jobs par workflow run
- Max 256 jobs en file d'attente ou running
- Max durée workflow: 35 jours
- Max durée job: 6 heures
- Max durée step: 6 heures (recommandé: timeout-minutes)
- API rate limit: 1000 requests/hour

# === Limites artifacts ===
- Max taille: 10 GB par workflow
- Max retention: 90 jours (configurable)
- Max uploads par workflow: Pas de limite stricte

# === Limites cache ===
- Max taille totale: 10 GB par repository
- Expiration: 7 jours (dernier accès)
- Max taille entrée: 10 GB

# === Limites matrices ===
- Max 256 jobs par matrice
- Max 256 combinaisons possibles

# === Minutes gratuites (privé) ===
# Free: 2000 min/mois
# Pro: 3000 min/mois
# Team: 3000 min/mois
# Enterprise: 50000 min/mois

# Multiplicateurs:
# Linux: 1x
# Windows: 2x
# macOS: 10x
# GPU: 50x


[OK] SNIPPETS UTILES


# === Obtenir version depuis package.json ===
- name: Get version
  id: version
  run: echo "value=$(node -p "require('./package.json').version")" >> $GITHUB_OUTPUT


# === Obtenir version depuis pyproject.toml ===
- name: Get version
  id: version
  run: |
    VERSION=$(python -c "import tomli; print(tomli.load(open('pyproject.toml', 'rb'))['tool']['poetry']['version'])")
    echo "value=$VERSION" >> $GITHUB_OUTPUT


# === Vérifier si tag existe ===
- name: Check if tag exists
  id: tag-check
  run: |
    if git rev-parse "v${{ steps.version.outputs.value }}" >/dev/null 2>&1; then
      echo "exists=true" >> $GITHUB_OUTPUT
    else
      echo "exists=false" >> $GITHUB_OUTPUT
    fi


# === Obtenir PR number depuis commit ===
- name: Get PR number
  id: pr
  run: |
    PR_NUMBER=$(gh pr list --search "${{ github.sha }}" --json number --jq '.[0].number')
    echo "number=$PR_NUMBER" >> $GITHUB_OUTPUT
  env:
    GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}


# === Commenter sur PR ===
- name: Comment on PR
  uses: actions/github-script@v7
  with:
    script: |
      github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: '[OK] Deployment successful!'
      })


# === Créer issue automatiquement ===
- name: Create issue on failure
  if: failure()
  uses: actions/github-script@v7
  with:
    script: |
      await github.rest.issues.create({
        owner: context.repo.owner,
        repo: context.repo.repo,
        title: 'CI Failure: ${{ github.workflow }}',
        body: `Workflow failed: ${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`,
        labels: ['bug', 'ci']
      })


# === Merger PR automatiquement ===
- name: Auto-merge PR
  if: github.event.pull_request.user.login == 'dependabot[bot]'
  uses: actions/github-script@v7
  with:
    script: |
      await github.rest.pulls.merge({
        owner: context.repo.owner,
        repo: context.repo.repo,
        pull_number: context.issue.number,
        merge_method: 'squash'
      })


# === Extraire branch name ===
- name: Extract branch name
  id: branch
  run: |
    if [[ "${{ github.event_name }}" == "pull_request" ]]; then
      echo "name=${{ github.head_ref }}" >> $GITHUB_OUTPUT
    else
      echo "name=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT
    fi


# === Générer changelog ===
- name: Generate changelog
  id: changelog
  run: |
    PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
    if [ -z "$PREV_TAG" ]; then
      CHANGELOG=$(git log --pretty=format:"- %s (%an)" HEAD)
    else
      CHANGELOG=$(git log --pretty=format:"- %s (%an)" $PREV_TAG..HEAD)
    fi
    echo "content<<EOF" >> $GITHUB_OUTPUT
    echo "$CHANGELOG" >> $GITHUB_OUTPUT
    echo "EOF" >> $GITHUB_OUTPUT


# === Comparer tailles de build ===
- name: Compare bundle sizes
  uses: actions/github-script@v7
  with:
    script: |
      const fs = require('fs');
      const currentSize = fs.statSync('dist/bundle.js').size;
      
      const comment = `[PACKAGE] Bundle size: ${(currentSize / 1024).toFixed(2)} KB`;
      
      await github.rest.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: comment
      });


[OK] TEMPLATES & STARTER WORKFLOWS


# === Python Package Template ===
name: Python Package

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
  release:
    types: [published]

jobs:
  test:
    runs-on: ${{ matrix.os }}
    strategy:
      fail-fast: false
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: ${{ matrix.python-version }}
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -e ".[dev]"
    
    - name: Lint
      run: |
        flake8 src/ tests/
        black --check src/ tests/
        isort --check src/ tests/
    
    - name: Type check
      run: mypy src/
    
    - name: Test
      run: pytest --cov=src --cov-report=xml
    
    - name: Upload coverage
      if: matrix.os == 'ubuntu-latest' && matrix.python-version == '3.11'
      uses: codecov/codecov-action@v4
      with:
        file: ./coverage.xml
  
  publish:
    needs: test
    if: github.event_name == 'release'
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: '3.11'
    
    - name: Build package
      run: |
        pip install build
        python -m build
    
    - name: Publish to PyPI
      uses: pypa/gh-action-pypi-publish@release/v1
      with:
        password: ${{ secrets.PYPI_API_TOKEN }}


# === Django Template ===
name: Django CI/CD

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

env:
  PYTHON_VERSION: '3.11'
  NODE_VERSION: '18'

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: ${{ env.PYTHON_VERSION }}
        cache: 'pip'
    
    - name: Install linting tools
      run: |
        pip install flake8 black isort pylint
    
    - name: Run linters
      run: |
        flake8 .
        black --check .
        isort --check .
        pylint **/*.py
  
  test:
    runs-on: ubuntu-latest
    
    services:
      postgres:
        image: postgres:15
        env:
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: testdb
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
      
      redis:
        image: redis:7
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
        ports:
          - 6379:6379
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: ${{ env.PYTHON_VERSION }}
        cache: 'pip'
    
    - name: Install dependencies
      run: |
        pip install -r requirements.txt
        pip install coverage
    
    - name: Run migrations
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
      run: python manage.py migrate
    
    - name: Run tests
      env:
        DATABASE_URL: postgresql://postgres:postgres@localhost:5432/testdb
        REDIS_URL: redis://localhost:6379
      run: |
        coverage run --source='.' manage.py test
        coverage report
        coverage xml
    
    - name: Upload coverage
      uses: codecov/codecov-action@v4
      with:
        file: ./coverage.xml
  
  security:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup Python
      uses: actions/setup-python@v5
      with:
        python-version: ${{ env.PYTHON_VERSION }}
    
    - name: Security check
      run: |
        pip install bandit safety
        bandit -r .
        safety check
  
  deploy:
    needs: [lint, test, security]
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Deploy to production
      env:
        DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
        DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
      run: |
        echo "Deploying to production..."
        ./deploy.sh


[OK] CHECKLISTE WORKFLOW


# === Checklist complète ===

# [OK] Configuration de base
# [WHITE_SQUARE] Nom du workflow descriptif
# [WHITE_SQUARE] Événements déclencheurs appropriés
# [WHITE_SQUARE] Branches/paths configurés correctement
# [WHITE_SQUARE] Concurrency si nécessaire

# [OK] Jobs
# [WHITE_SQUARE] Runner approprié (ubuntu/windows/macos)
# [WHITE_SQUARE] Dépendances entre jobs configurées
# [WHITE_SQUARE] Timeouts définis
# [WHITE_SQUARE] Permissions minimales

# [OK] Steps
# [WHITE_SQUARE] Checkout en premier
# [WHITE_SQUARE] Setup language/tools
# [WHITE_SQUARE] Cache configuré
# [WHITE_SQUARE] Tests exécutés
# [WHITE_SQUARE] Linting/formatting vérifié
# [WHITE_SQUARE] Security scanning

# [OK] Sécurité
# [WHITE_SQUARE] Secrets utilisés pour données sensibles
# [WHITE_SQUARE] Pas de logs de secrets
# [WHITE_SQUARE] Permissions restrictives
# [WHITE_SQUARE] Dependabot activé
# [WHITE_SQUARE] CodeQL configuré

# [OK] Performance
# [WHITE_SQUARE] Cache activé
# [WHITE_SQUARE] Concurrency cancel-in-progress
# [WHITE_SQUARE] Jobs parallèles quand possible
# [WHITE_SQUARE] Paths-ignore pour fichiers inutiles
# [WHITE_SQUARE] Matrices optimisées

# [OK] Déploiement
# [WHITE_SQUARE] Environnements configurés
# [WHITE_SQUARE] Approbations si nécessaire
# [WHITE_SQUARE] Rollback prévu
# [WHITE_SQUARE] Monitoring configuré
# [WHITE_SQUARE] Notifications configurées

# [OK] Documentation
# [WHITE_SQUARE] README avec badges
# [WHITE_SQUARE] Commentaires dans workflow
# [WHITE_SQUARE] Variables documentées
# [WHITE_SQUARE] Secrets documentés


[OK] RESSOURCES & LIENS


# Documentation officielle
https://docs.github.com/en/actions

# Actions Marketplace
https://github.com/marketplace?type=actions

# GitHub Actions Runner
https://github.com/actions/runner

# GitHub Actions Toolkit
https://github.com/actions/toolkit

# Awesome Actions (liste communautaire)
https://github.com/sdras/awesome-actions

# GitHub Actions par GitHub
https://github.com/actions

# Act (run locally)
https://github.com/nektos/act

# Status page
https://www.githubstatus.com/

# Community forum
https://github.community/c/github-actions/

# Blog GitHub Actions
https://github.blog/tag/github-actions/

# Pricing
https://docs.github.com/en/billing/managing-billing-for-github-actions

# Workflow syntax
https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions

# Expressions
https://docs.github.com/en/actions/learn-github-actions/expressions

# Contexts
https://docs.github.com/en/actions/learn-github-actions/contexts

# Variables
https://docs.github.com/en/actions/learn-github-actions/variables

# Encrypted secrets
https://docs.github.com/en/actions/security-guides/encrypted-secrets


# === FIN DE LA CHEATSHEET ===/