Module 4 : Qualité de Code & Linting
Objectif du Module
Comprendre l'importance de l'analyse statique du code (linting) et intégrer des quality gates dans le pipeline CI/CD pour garantir des standards de qualité élevés avant chaque déploiement.
Durée : 1 heure
Introduction : Pourquoi les Tests Automatisés ?
Le Coût d'un Bug en Production
Le principe "Shift Left" (décalage à gauche) signifie détecter les bugs le plus tôt possible dans le cycle de développement.
flowchart LR
A[Dev Local<br/>💰 10€] -->|Push| B[CI Pipeline<br/>💰 100€]
B -->|Merge| C[Staging<br/>💰 1000€]
C -->|Deploy| D[Production<br/>💰💰 10 000€]
D -->|Incident| E[Downtime<br/>💰💰💰 100 000€]
style A fill:#4CAF50,stroke:#0b5a0d,color:#fff
style B fill:#FF9800800800,stroke:#d39300,color:#000
style C fill:#FF9800800800,stroke:#d97400,color:#fff
style D fill:#f44336,stroke:#b00016,color:#fff
style E fill:#9C27B0,stroke:#3d1e5f,color:#fff
Coût croissant d'un bug :
| Phase | Coût de correction | Exemple |
|---|---|---|
| Développement | 10€ (5 min) | Le dev corrige immédiatement |
| CI Pipeline | 100€ (30 min) | Le pipeline échoue, il faut débugger |
| Staging | 1 000€ (2h) | QA trouve un bug, retour dev |
| Production | 10 000€ (1 jour) | Rollback, hotfix, communication |
| Downtime | 100 000€ (incident) | Perte de revenus, atteinte réputation |
Conclusion : Un bug détecté localement coûte 10 000 fois moins cher qu'un incident en production !
Le Paradigme "Shift Left"
Principe : Déplacer les tests et validations vers la gauche du cycle de vie (le plus tôt possible).
Avant (Shift Right - ❌) :
- Dev → Commit → Merge
- QA teste manuellement
- Bug trouvé en production
- Hotfix urgent
Après (Shift Left - ✅) :
- Dev → Linting local → Commit
- CI Pipeline : Tests auto + Linting
- Quality Gates : Impossible de merger si échec
- QA teste uniquement la logique métier (pas les bugs de syntaxe)
Concept : Analyse Statique vs Dynamique
Analyse Statique (Linting)
Définition : Examiner le code sans l'exécuter pour détecter des erreurs potentielles.
Exemples d'erreurs détectées :
- 🐛 Syntaxe incorrecte : Guillemets manquants, parenthèses mal fermées
- 🔒 Failles de sécurité : Variables non quotées (risque d'injection)
- 💡 Mauvaises pratiques : Variables non utilisées, code mort
- 📏 Non-respect des standards : Indentation, longueur de ligne
Outils de linting :
| Langage/Format | Linter | Exemple d'erreur détectée |
|---|---|---|
| Bash | ShellCheck | SC2086: Variable non quotée |
| YAML | yamllint | Indentation incorrecte (2 espaces requis) |
| Python | pylint, flake8 | E501: Ligne trop longue (>79 caractères) |
| JavaScript | eslint | no-unused-vars: Variable 'x' déclarée mais non utilisée |
| Markdown | markdownlint | MD013: Ligne trop longue |
Analyse Dynamique (Tests)
Définition : Exécuter le code et vérifier son comportement.
Exemples :
- Tests unitaires :
pytest,jest - Tests d'intégration : API, base de données
- Tests E2E : Selenium, Playwright
Complémentarité
Linting (statique) et Tests (dynamique) sont complémentaires :
- Linting : Détecte les erreurs de syntaxe et de style (rapide, avant exécution)
- Tests : Vérifie la logique métier (plus lent, nécessite l'exécution)
Best Practice : Exécuter le linting avant les tests dans le pipeline (fail fast).
Quality Gates
Définition : Points de contrôle obligatoires dans le pipeline CI/CD. Si un critère n'est pas respecté, le pipeline échoue.
Exemples de Quality Gates :
- ✅ Linting : 0 erreur ShellCheck
- ✅ Tests : Couverture de code ≥ 80%
- ✅ Sécurité : 0 vulnérabilité critique (Snyk, Trivy)
- ✅ Performance : Temps de réponse API < 200ms
Principe : "Red Pipeline = No Merge"
flowchart LR
A[Dev Push] --> B{Pipeline CI}
B -->|❌ Linting Fail| C[Bloqué<br/>Pas de merge]
B -->|✅ All Green| D[Merge autorisé]
D --> E[Deploy Staging]
style C fill:#f44336,stroke:#b00016,color:#fff
style D fill:#4CAF50,stroke:#0b5a0d,color:#fff
Les Outils du SysAdmin
ShellCheck : Le Standard pour Bash
ShellCheck est le linter de référence pour les scripts Bash. Il détecte plus de 300 types d'erreurs courantes.
Installation :
Exemple : Script Avec Erreurs (❌)
#!/bin/bash
# deploy.sh - Script de déploiement (MAUVAISES PRATIQUES)
APP_NAME=mon-app
ENV=$1
# Erreur 1 : Variable non quotée (SC2086)
echo Deploying $APP_NAME to $ENV
# Erreur 2 : Variable non utilisée (SC2034)
UNUSED_VAR="test"
# Erreur 3 : Condition incorrecte (SC2166)
if [ $ENV = "production" -o $ENV = "staging" ]; then
echo "Environment valide"
fi
# Erreur 4 : Commande non vérifiée (SC2181)
cp app.tar.gz /tmp/
if [ $? -ne 0 ]; then
echo "Erreur de copie"
exit 1
fi
# Erreur 5 : Variable dans rm non quotée (DANGER!)
FILES_TO_DELETE=$2
rm -rf $FILES_TO_DELETE # Risque d'injection si $2 contient des espaces
Exécution ShellCheck :
Sortie :
In deploy.sh line 7:
echo Deploying $APP_NAME to $ENV
^--------^ SC2086: Double quote to prevent globbing and word splitting.
^--^ SC2086: Double quote to prevent globbing and word splitting.
In deploy.sh line 10:
UNUSED_VAR="test"
^--------^ SC2034: UNUSED_VAR appears unused. Verify use (or export if used externally).
In deploy.sh line 13:
if [ $ENV = "production" -o $ENV = "staging" ]; then
^--^ SC2086: Double quote to prevent globbing and word splitting.
^--^ SC2086: Double quote to prevent globbing and word splitting.
^-- SC2166: Prefer [ p ] || [ q ] as [ p -o q ] is not well defined.
In deploy.sh line 19:
if [ $? -ne 0 ]; then
^-- SC2181: Check exit code directly with e.g. 'if ! mycmd;', not indirectly with $?.
In deploy.sh line 25:
rm -rf $FILES_TO_DELETE
^--------------^ SC2086: Double quote to prevent globbing and word splitting.
Script Corrigé (✅)
#!/bin/bash
# deploy.sh - Script de déploiement (BONNES PRATIQUES)
set -euo pipefail # Arrêter sur erreur, variables non définies, erreurs de pipe
APP_NAME="mon-app"
ENV="${1:-staging}" # Valeur par défaut si $1 vide
# ✅ Variables quotées
echo "Deploying ${APP_NAME} to ${ENV}"
# ✅ Variable supprimée (non utilisée)
# ✅ Condition moderne avec [[
if [[ "${ENV}" == "production" ]] || [[ "${ENV}" == "staging" ]]; then
echo "Environment valide"
else
echo "Erreur: Environment doit être production ou staging"
exit 1
fi
# ✅ Vérification d'erreur directe
if ! cp app.tar.gz /tmp/; then
echo "Erreur de copie"
exit 1
fi
# ✅ Variable quotée et vérification
FILES_TO_DELETE="${2:-}"
if [[ -n "${FILES_TO_DELETE}" ]]; then
rm -rf "${FILES_TO_DELETE}"
fi
Vérification :
Améliorations Appliquées
- ✅
set -euo pipefail: Arrêter le script sur toute erreur - ✅ Variables quotées :
"${VAR}"prévient les injections - ✅
[[au lieu de[: Syntaxe moderne et plus sûre - ✅ Vérification directe :
if ! cmdau lieu deif [ $? -ne 0 ] - ✅ Valeurs par défaut :
${VAR:-default}
Yamllint : Essentiel pour Ansible/Kubernetes
Yamllint vérifie la syntaxe et le style des fichiers YAML (utilisés partout : Ansible, Kubernetes, GitHub Actions).
Installation :
Exemple : YAML avec Erreurs (❌)
# playbook.yml (MAUVAISES PRATIQUES)
---
- name: Install Nginx
hosts: webservers
tasks:
- name: Install nginx package
yum:
name: nginx
state: present
- name: Start nginx # Indentation incorrecte (4 espaces au lieu de 2)
service:
name: nginx
state: started
- name: Copy config
copy:
src: /tmp/nginx.conf
dest: /etc/nginx/nginx.conf
# Commentaire trop long qui dépasse la limite de 80 caractères recommandée pour la lisibilité
Exécution yamllint :
Sortie :
playbook.yml
10:5 error wrong indentation: expected 2 but found 4 (indentation)
18:7 warning line too long (106 > 80 characters) (line-length)
YAML Corrigé (✅)
# playbook.yml (BONNES PRATIQUES)
---
- name: Install Nginx
hosts: webservers
tasks:
- name: Install nginx package
yum:
name: nginx
state: present
- name: Start nginx
service:
name: nginx
state: started
- name: Copy config
copy:
src: /tmp/nginx.conf
dest: /etc/nginx/nginx.conf
# Config Nginx principale
Vérification :
Pratique : Intégration dans GitHub Actions
Pipeline avec Job Linting Parallèle
# .github/workflows/ci.yml
name: CI - Quality Checks
on:
push:
branches: [main, dev]
pull_request:
branches: [main]
jobs:
# Job 1 : Linting (s'exécute en parallèle)
lint:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install ShellCheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
- name: Install yamllint
run: pip install yamllint
- name: Run ShellCheck
run: |
echo "Checking shell scripts..."
find . -name "*.sh" -type f | while read -r script; do
echo "Checking $script"
shellcheck "$script"
done
- name: Run yamllint
run: yamllint .
# Job 2 : Tests (s'exécute en parallèle)
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
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
- name: Run tests
run: pytest
# Job 3 : Build (dépend du succès de lint et test)
build:
runs-on: ubuntu-latest
needs: [lint, test] # Attend que lint ET test soient ✅
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Build application
run: ./build.sh
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: app-build
path: dist/
Workflow :
- lint et test s'exécutent en parallèle (gain de temps)
- build attend que lint ET test soient ✅
- Si lint échoue → build ne s'exécute jamais (fail fast)
Exercice : Scénario "Red-to-Green"
À Vous de Jouer - Cycle Red → Green
Contexte : Vous développez un script de déploiement. Vous voulez garantir qu'aucun script avec des mauvaises pratiques ne soit mergé.
Objectif : Expérimenter le cycle Red → Fix → Green en intégrant ShellCheck dans le pipeline.
Étapes :
Step 1 : Créer un script avec mauvaises pratiques
Créez scripts/deploy.sh :
Step 2 : Créer le pipeline CI
Créez .github/workflows/lint.yml :
name: Lint
on: [push]
jobs:
shellcheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get install -y shellcheck
- run: shellcheck scripts/deploy.sh
Step 3 : Push et observer l'ÉCHEC (🔴 Red)
git add scripts/deploy.sh .github/workflows/lint.yml
git commit -m "ci: Ajout script deploy (avec erreurs)"
git push
→ Le pipeline échoue 🔴 avec des erreurs ShellCheck.
Step 4 : Corriger le script
Modifiez scripts/deploy.sh :
#!/bin/bash
set -euo pipefail
APP="${1:-app}"
echo "Deploying ${APP}"
FILES="${2:-}"
if [[ -n "${FILES}" ]]; then
rm -rf "${FILES}"
fi
Step 5 : Re-push et observer le SUCCÈS (🟢 Green)
→ Le pipeline passe 🟢 sans erreur.
Bonus :
- Ajouter
yamllintau pipeline pour vérifier.github/workflows/lint.yml - Ajouter un badge GitHub Actions dans
README.md
Durée estimée : 20 minutes
Critères de réussite :
- ✅ Pipeline échoue initialement (Red)
- ✅ Erreurs ShellCheck identifiées et corrigées
- ✅ Pipeline passe après correction (Green)
- ✅ Aucune erreur de linting restante
Solution : Configuration Complète
Solution Détaillée
Fichier scripts/deploy.sh (Corrigé)
#!/bin/bash
# deploy.sh - Script de déploiement sécurisé
# Arrêter sur erreur, variables non définies, erreurs de pipe
set -euo pipefail
# Variables avec valeurs par défaut
APP_NAME="${1:-mon-app}"
FILES_TO_DELETE="${2:-}"
# Logging
echo "===================================="
echo "Déploiement de l'application"
echo "===================================="
echo "Application: ${APP_NAME}"
echo "Date: $(date '+%Y-%m-%d %H:%M:%S')"
# Vérification que l'artefact existe
ARTIFACT="build/${APP_NAME}.tar.gz"
if [[ ! -f "${ARTIFACT}" ]]; then
echo "Erreur: Artefact ${ARTIFACT} introuvable"
exit 1
fi
# Copie sécurisée
if ! cp "${ARTIFACT}" /tmp/; then
echo "Erreur: Impossible de copier ${ARTIFACT}"
exit 1
fi
echo "✅ Copie réussie vers /tmp/"
# Nettoyage optionnel (avec vérification)
if [[ -n "${FILES_TO_DELETE}" ]]; then
echo "Nettoyage: ${FILES_TO_DELETE}"
rm -rf "${FILES_TO_DELETE}"
fi
echo "✅ Déploiement terminé avec succès"
Fichier .github/workflows/lint.yml (Complet)
name: Lint & Quality
on:
push:
branches: [main, dev]
pull_request:
branches: [main]
jobs:
shellcheck:
name: ShellCheck
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Install ShellCheck
run: sudo apt-get update && sudo apt-get install -y shellcheck
- name: Run ShellCheck on all scripts
run: |
echo "🔍 Vérification des scripts Shell..."
find . -name "*.sh" -type f | while read -r script; do
echo "Checking: $script"
shellcheck "$script"
done
echo "✅ Tous les scripts sont conformes"
yamllint:
name: Yamllint
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install yamllint
run: pip install yamllint
- name: Run yamllint
run: |
echo "🔍 Vérification des fichiers YAML..."
yamllint .
echo "✅ Tous les fichiers YAML sont conformes"
summary:
name: Quality Summary
runs-on: ubuntu-latest
needs: [shellcheck, yamllint]
steps:
- name: Success
run: |
echo "=================================="
echo "✅ Tous les checks de qualité passent"
echo "=================================="
Workflow Git
# 1. Créer la structure
mkdir -p scripts .github/workflows
# 2. Créer le script (version avec erreurs)
cat > scripts/deploy.sh <<'EOF'
#!/bin/bash
APP=$1
echo Deploying $APP
rm -rf $2
EOF
chmod +x scripts/deploy.sh
# 3. Créer le pipeline
cat > .github/workflows/lint.yml <<'EOF'
# (Contenu du YAML ci-dessus)
EOF
# 4. Premier commit (Red)
git add scripts/ .github/
git commit -m "ci: Ajout script deploy et pipeline lint"
git push
# Observer l'échec dans GitHub Actions ❌
# 5. Corriger le script
cat > scripts/deploy.sh <<'EOF'
#!/bin/bash
set -euo pipefail
APP="${1:-mon-app}"
echo "Deploying ${APP}"
FILES="${2:-}"
if [[ -n "${FILES}" ]]; then
rm -rf "${FILES}"
fi
EOF
# 6. Second commit (Green)
git add scripts/deploy.sh
git commit -m "fix: Correction erreurs ShellCheck (SC2086)"
git push
# Observer le succès dans GitHub Actions ✅
Résultat GitHub Actions (Red → Green)
Premier Push (Red) :
❌ Lint & Quality
❌ ShellCheck (12s)
✅ Checkout code
✅ Install ShellCheck
❌ Run ShellCheck on all scripts
Checking: ./scripts/deploy.sh
In scripts/deploy.sh line 3:
echo Deploying $APP
^--^ SC2086: Double quote to prevent globbing
In scripts/deploy.sh line 4:
rm -rf $2
^-- SC2086: Double quote to prevent globbing
Second Push (Green) :
✅ Lint & Quality
✅ ShellCheck (10s)
✅ Checkout code
✅ Install ShellCheck
✅ Run ShellCheck on all scripts
Checking: ./scripts/deploy.sh
✅ Tous les scripts sont conformes
✅ Yamllint (8s)
✅ Checkout code
✅ Setup Python
✅ Install yamllint
✅ Run yamllint
✅ Tous les fichiers YAML sont conformes
✅ Quality Summary (2s)
✅ Success
==================================
✅ Tous les checks de qualité passent
==================================
Apprentissage du Cycle Red-Green
Vous avez expérimenté le workflow DevOps typique :
- Écrire du code (avec erreurs potentielles)
- Pipeline échoue 🔴 (détection automatique)
- Lire les logs (comprendre les erreurs)
- Corriger le code (appliquer les bonnes pratiques)
- Pipeline passe 🟢 (validation automatique)
Ce cycle Red → Fix → Green est au cœur du TDD (Test-Driven Development) et du DevOps.
Points Clés à Retenir
Ce module vous a appris :
- 📊 Shift Left : Détecter les bugs tôt coûte 10 000x moins cher
- 🔍 Analyse Statique : Linting (ShellCheck, yamllint) détecte les erreurs sans exécution
- 🚦 Quality Gates : Pipeline doit échouer si standards non respectés
- 🐚 ShellCheck : Linter pour Bash (300+ règles, détecte injections)
- 📄 Yamllint : Vérification syntaxe/style YAML (Ansible, K8s, GitHub Actions)
- 🔴🟢 Cycle Red-Green : Échec → Correction → Succès (apprentissage par la pratique)
- ⚡ Parallélisation : Jobs lint/test en parallèle (gain de temps)
- 🛡️ Sécurité : Variables quotées préviennent les injections de commande
Commandes Essentielles :
# Installer les linters
sudo apt install shellcheck
pip install yamllint
# Vérifier localement (avant commit)
shellcheck script.sh
yamllint playbook.yml
# Corriger automatiquement (si possible)
shellcheck --format=diff script.sh | git apply
Règles ShellCheck Critiques :
- SC2086 : Toujours quoter les variables (
"${VAR}") - SC2046 : Quoter
$(commandes)pour éviter word splitting - SC2181 : Vérifier
if ! cmdau lieu deif [ $? -ne 0 ] - SC2034 : Supprimer les variables non utilisées
Best Practices :
- ✅ Toujours utiliser
set -euo pipefailen début de script - ✅ Quoter toutes les variables :
"${VAR}" - ✅ Préférer
[[à[(syntaxe moderne) - ✅ Fournir valeurs par défaut :
${VAR:-default} - ✅ Exécuter linting avant les tests (fail fast)
- ✅ Intégrer linting dans pre-commit hooks (localement)
Prochaine étape : Module 5 : TP Final - Projet Complet (en cours de rédaction)
Retour au : Programme de la Formation | Catalogue
Navigation
| ← Module 3 : Mon Premier Pipeline CI/CD | Module 5 : TP Final - Le Projet Ops-T... → |