Skip to main content

Shift Left & Pre-commit

🔄 Shift Left - Bonnes Pratiques DevOps

📋 Table des Matières


Qu'est-ce que le Shift Left ?

Définition

Le Shift Left est une approche DevOps qui consiste à déplacer les tests, la sécurité et le contrôle qualité le plus tôt possible dans le cycle de développement.

Objectifs

Traditionnelle:  Développement → Tests → Sécurité → Production ❌
Shift Left:      Tests + Sécurité + Qualité dès le développement ✅

Avantages

  • 🚀 Détection précoce des bugs : Correction 10x moins coûteuse
  • 🔒 Sécurité intégrée : Vulnérabilités détectées avant le commit
  • 💰 Réduction des coûts : Moins de bugs en production
  • Déploiements plus rapides : Pipeline CI/CD plus fluide
  • 👥 Collaboration améliorée : Standards partagés par l'équipe

Principes fondamentaux

1. Automatisation

# Tout doit être automatique
✅ Formatage du code
✅ Tests unitaires
✅ Analyse de sécurité
✅ Validation des commits
❌ Processus manuels

2. Feedback rapide

Idéal : < 5 secondes pour les checks locaux
Acceptable : < 2 minutes pour les tests complets

3. Fail Fast

# Bloquer le commit si erreurs
git commit → Pre-commit hooks → ❌ Échec → Correction
                              → ✅ Succès → Push

4. Standards d'équipe

Toute l'équipe utilise les mêmes :
- Hooks Git
- Formatters
- Linters
- Configuration

Pre-commit Hooks Git

Installation du framework pre-commit

# Installation
pip install pre-commit

# Vérification
pre-commit --version

Configuration de base

.pre-commit-config.yaml

# Voir https://pre-commit.com/hooks.html pour plus de hooks
repos:
  # Hooks génériques
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace       # Supprime espaces en fin de ligne
      - id: end-of-file-fixer        # Ajoute newline en fin de fichier
      - id: check-yaml                # Valide syntaxe YAML
      - id: check-json                # Valide syntaxe JSON
      - id: check-added-large-files  # Bloque fichiers > 500KB
        args: ['--maxkb=500']
      - id: check-merge-conflict     # Détecte marqueurs de conflit
      - id: detect-private-key       # Détecte clés privées
      - id: check-case-conflict      # Vérifie conflits de casse
      - id: mixed-line-ending        # Normalise fins de ligne

  # Secrets et sécurité
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

  # Commitizen pour conventional commits
  - repo: https://github.com/commitizen-tools/commitizen
    rev: v3.13.0
    hooks:
      - id: commitizen
        stages: [commit-msg]

Installation dans le projet

# Créer le fichier de config
touch .pre-commit-config.yaml

# Installer les hooks
pre-commit install

# Installer pour commit-msg aussi
pre-commit install --hook-type commit-msg

# Tester sur tous les fichiers
pre-commit run --all-files

Commandes utiles

# Lancer manuellement les hooks
pre-commit run --all-files

# Lancer un hook spécifique
pre-commit run trailing-whitespace

# Mettre à jour les versions des hooks
pre-commit autoupdate

# Désinstaller
pre-commit uninstall

# Bypass temporaire (à éviter !)
git commit --no-verify -m "message"

Outils de qualité de code

Python

.pre-commit-config.yaml

repos:
  # Black - Formatage automatique
  - repo: https://github.com/psf/black
    rev: 23.12.1
    hooks:
      - id: black
        language_version: python3.11

  # isort - Tri des imports
  - repo: https://github.com/PyCQA/isort
    rev: 5.13.2
    hooks:
      - id: isort
        args: ["--profile", "black"]

  # Flake8 - Linting
  - repo: https://github.com/PyCQA/flake8
    rev: 7.0.0
    hooks:
      - id: flake8
        args: ['--max-line-length=88', '--extend-ignore=E203']

  # Pylint - Analyse approfondie
  - repo: https://github.com/PyCQA/pylint
    rev: v3.0.3
    hooks:
      - id: pylint
        args: ['--disable=C0111,R0903']

  # MyPy - Type checking
  - repo: https://github.com/pre-commit/mirrors-mypy
    rev: v1.8.0
    hooks:
      - id: mypy
        additional_dependencies: [types-requests]

Configuration Black - pyproject.toml

[tool.black]
line-length = 88
target-version = ['py311']
include = '\.pyi?$'
extend-exclude = '''
/(
  # directories
  \.eggs
  | \.git
  | \.venv
  | build
  | dist
)/
'''

Configuration isort - pyproject.toml

[tool.isort]
profile = "black"
multi_line_output = 3
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
ensure_newline_before_comments = true
line_length = 88

JavaScript/TypeScript

.pre-commit-config.yaml

repos:
  # ESLint
  - repo: https://github.com/pre-commit/mirrors-eslint
    rev: v8.56.0
    hooks:
      - id: eslint
        files: \.(js|jsx|ts|tsx)$
        types: [file]
        additional_dependencies:
          - [email protected]
          - [email protected]
          - [email protected]

  # Prettier
  - repo: https://github.com/pre-commit/mirrors-prettier
    rev: v3.1.0
    hooks:
      - id: prettier
        types_or: [javascript, jsx, ts, tsx, json, yaml, markdown]

Configuration ESLint - .eslintrc.json

{
  "extends": ["airbnb", "prettier"],
  "plugins": ["react", "prettier"],
  "rules": {
    "prettier/prettier": "error",
    "no-console": "warn",
    "no-unused-vars": "error"
  },
  "env": {
    "browser": true,
    "node": true,
    "es6": true
  }
}

Configuration Prettier - .prettierrc

{
  "semi": true,
  "trailingComma": "es5",
  "singleQuote": true,
  "printWidth": 80,
  "tabWidth": 2
}

Java

.pre-commit-config.yaml

repos:
  # Google Java Format
  - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
    rev: v2.12.0
    hooks:
      - id: pretty-format-java
        args: [--autofix]

  # Checkstyle
  - repo: local
    hooks:
      - id: checkstyle
        name: checkstyle
        entry: bash -c 'java -jar checkstyle.jar -c google_checks.xml src/'
        language: system
        types: [java]

Go

.pre-commit-config.yaml

repos:
  # Go format
  - repo: https://github.com/dnephin/pre-commit-golang
    rev: v0.5.1
    hooks:
      - id: go-fmt
      - id: go-vet
      - id: go-imports
      - id: go-cyclo
        args: [-over=15]
      - id: golangci-lint

Tests automatisés

Configuration pour Python

.pre-commit-config.yaml

repos:
  # Pytest
  - repo: local
    hooks:
      - id: pytest
        name: pytest
        entry: pytest
        language: system
        pass_filenames: false
        always_run: true
        args: ['-v', '--tb=short', '--cov=src', '--cov-fail-under=80']

  # Coverage
  - repo: local
    hooks:
      - id: coverage
        name: coverage
        entry: bash -c 'coverage run -m pytest && coverage report --fail-under=80'
        language: system
        pass_filenames: false
        always_run: true

Configuration pour JavaScript

.pre-commit-config.yaml

repos:
  # Jest
  - repo: local
    hooks:
      - id: jest
        name: jest
        entry: npm test
        language: system
        pass_filenames: false
        always_run: true

Tests seulement sur fichiers modifiés

.pre-commit-config.yaml

repos:
  - repo: local
    hooks:
      - id: pytest-quick
        name: pytest-quick
        entry: bash -c 'pytest --picked --testmon'
        language: system
        pass_filenames: false
        stages: [commit]

Sécurité (SAST)

Detect Secrets - Détection de secrets

Installation et configuration

# Installation
pip install detect-secrets

# Créer un baseline
detect-secrets scan > .secrets.baseline

# Scanner manuellement
detect-secrets scan --baseline .secrets.baseline

.pre-commit-config.yaml

repos:
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']
        exclude: package-lock.json

Bandit - Analyse de sécurité Python

.pre-commit-config.yaml

repos:
  - repo: https://github.com/PyCQA/bandit
    rev: 1.7.6
    hooks:
      - id: bandit
        args: ['-r', 'src/', '-f', 'json', '-o', 'bandit-report.json']

Configuration - .bandit

exclude_dirs:
  - /tests/
  - /venv/

tests:
  - B201  # Flask debug mode
  - B301  # Pickle usage
  - B501  # Request with verify=False

Semgrep - Analyse multi-langages

# Installation
pip install semgrep

# Scanner le code
semgrep --config=auto .

.pre-commit-config.yaml

repos:
  - repo: https://github.com/returntocorp/semgrep
    rev: v1.52.0
    hooks:
      - id: semgrep
        args: ['--config', 'auto', '--error']

Trivy - Scan des dépendances

# Scanner les vulnérabilités
trivy fs --security-checks vuln,config .

# Scanner une image Docker
trivy image myimage:latest

Gitleaks - Détection de fuites

.pre-commit-config.yaml

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.1
    hooks:
      - id: gitleaks

Configuration - .gitleaks.toml

title = "Gitleaks Config"

[[rules]]
id = "aws-access-key"
description = "AWS Access Key"
regex = '''AKIA[0-9A-Z]{16}'''

[[rules]]
id = "github-token"
description = "GitHub Token"
regex = '''ghp_[0-9a-zA-Z]{36}'''

Infrastructure as Code

Terraform

.pre-commit-config.yaml

repos:
  - repo: https://github.com/antonbabenko/pre-commit-terraform
    rev: v1.86.0
    hooks:
      - id: terraform_fmt
      - id: terraform_validate
      - id: terraform_docs
      - id: terraform_tflint
        args:
          - --args=--config=__GIT_WORKING_DIR__/.tflint.hcl
      - id: terraform_tfsec
      - id: terraform_checkov

Configuration TFLint - .tflint.hcl

plugin "aws" {
  enabled = true
  version = "0.28.0"
  source  = "github.com/terraform-linters/tflint-ruleset-aws"
}

rule "aws_instance_invalid_type" {
  enabled = true
}

Ansible

.pre-commit-config.yaml

repos:
  - repo: https://github.com/ansible/ansible-lint
    rev: v6.22.1
    hooks:
      - id: ansible-lint
        files: \.(yaml|yml)$

Dockerfile

.pre-commit-config.yaml

repos:
  - repo: https://github.com/hadolint/hadolint
    rev: v2.12.0
    hooks:
      - id: hadolint-docker

Kubernetes

.pre-commit-config.yaml

repos:
  - repo: https://github.com/gruntwork-io/pre-commit
    rev: v0.1.23
    hooks:
      - id: helmlint
      - id: kubeval

Documentation automatique

Conventional Commits

.pre-commit-config.yaml

repos:
  # Commitizen pour format standardisé
  - repo: https://github.com/commitizen-tools/commitizen
    rev: v3.13.0
    hooks:
      - id: commitizen
        stages: [commit-msg]

Format des commits

# Types de commits
feat:     Nouvelle fonctionnalité
fix:      Correction de bug
docs:     Documentation
style:    Formatage (pas de changement de code)
refactor: Refactoring
test:     Ajout de tests
chore:    Maintenance

# Exemples
git commit -m "feat: ajout authentification OAuth2"
git commit -m "fix: correction calcul TVA"
git commit -m "docs: mise à jour README"
git commit -m "feat(api)!: changement breaking de l'endpoint users"

Génération automatique de CHANGELOG

# Installation
pip install commitizen

# Générer le CHANGELOG
cz changelog

# Bump version et changelog
cz bump --changelog

Documentation du code

.pre-commit-config.yaml

repos:
  # Pydocstyle pour Python
  - repo: https://github.com/PyCQA/pydocstyle
    rev: 6.3.0
    hooks:
      - id: pydocstyle
        args: [--convention=google]

Workflow complet

1. Configuration initiale du projet

# Créer le projet
mkdir mon-projet && cd mon-projet
git init

# Installer pre-commit
pip install pre-commit

# Créer la configuration
cat > .pre-commit-config.yaml << EOF
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.5.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
      - id: detect-private-key
  
  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
  
  - repo: https://github.com/commitizen-tools/commitizen
    rev: v3.13.0
    hooks:
      - id: commitizen
        stages: [commit-msg]
EOF

# Installer les hooks
pre-commit install
pre-commit install --hook-type commit-msg

# Créer baseline pour secrets
pip install detect-secrets
detect-secrets scan > .secrets.baseline

# Créer .gitignore
cat > .gitignore << EOF
# Python
venv/
__pycache__/
*.pyc
.pytest_cache/

# IDEs
.vscode/
.idea/

# Secrets
.env
*.pem
*.key
EOF

2. Configuration spécifique au langage

Pour Python

# Ajouter les hooks Python
cat >> .pre-commit-config.yaml << EOF
  - repo: https://github.com/psf/black
    rev: 23.12.1
    hooks:
      - id: black

  - repo: https://github.com/PyCQA/isort
    rev: 5.13.2
    hooks:
      - id: isort

  - repo: https://github.com/PyCQA/flake8
    rev: 7.0.0
    hooks:
      - id: flake8

  - repo: https://github.com/PyCQA/bandit
    rev: 1.7.6
    hooks:
      - id: bandit
EOF

# Installer les outils
pip install black isort flake8 bandit pytest

# Créer pyproject.toml
cat > pyproject.toml << EOF
[tool.black]
line-length = 88
target-version = ['py311']

[tool.isort]
profile = "black"
line_length = 88

[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py"]
EOF

3. Workflow quotidien

# 1. Créer une branche
git checkout -b feature/nouvelle-fonctionnalite

# 2. Développer
# ... écrire du code ...

# 3. Lancer les checks manuellement (optionnel)
pre-commit run --all-files

# 4. Commit (hooks lancés automatiquement)
git add .
git commit -m "feat: ajout nouvelle fonctionnalité"
# → Les hooks s'exécutent automatiquement
# → Si échec, corriger et recommit

# 5. Push
git push origin feature/nouvelle-fonctionnalite

4. Pipeline CI/CD complet

.gitlab-ci.yml

stages:
  - quality
  - security
  - test
  - build
  - deploy

# Quality checks
quality:
  stage: quality
  image: python:3.11
  before_script:
    - pip install pre-commit
  script:
    - pre-commit run --all-files

# Security scan
security:
  stage: security
  image: python:3.11
  script:
    - pip install bandit detect-secrets
    - bandit -r src/
    - detect-secrets scan --baseline .secrets.baseline

# Unit tests
test:
  stage: test
  image: python:3.11
  script:
    - pip install -r requirements.txt
    - pytest --cov=src --cov-report=xml
  coverage: '/(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/'
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

# SAST with Semgrep
sast:
  stage: security
  image: returntocorp/semgrep
  script:
    - semgrep scan --config=auto --json --output=semgrep.json
  artifacts:
    reports:
      sast: semgrep.json

# Build Docker
build:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t myapp:$CI_COMMIT_SHA .
    - docker scan myapp:$CI_COMMIT_SHA  # Scan Trivy

Ressources

Documentation officielle

Outils de sécurité

Quality tools

Infrastructure


⬆ Retour en haut