diff --git a/Jenkinsfile b/Jenkinsfile index 6f46529..3570691 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,10 +1,42 @@ +/** + * ============================================================================ + * PIPELINE CI/CD - THÉ TIP TOP FRONTEND + * ============================================================================ + * + * Ce pipeline automatise le processus de déploiement continu du frontend. + * + * ÉTAPES DU PIPELINE : + * 1. Init - Détection de l'environnement (dev/preprod/prod) + * 2. Checkout - Récupération du code source depuis Git + * 3. Install - Installation des dépendances Node.js + * 4. Quality - Vérification qualité (ESLint + Jest + SonarQube) + * 5. Build - Construction de l'image Docker (avec variables env) + * 6. Push - Envoi de l'image vers le registre Docker privé + * 7. Deploy - Déploiement sur le serveur cible + * + * ENVIRONNEMENTS : + * - dev : Développement (branche dev) + * - preprod : Pré-production (branche preprod) + * - prod : Production (branche main) + * + * TECHNOLOGIE : Next.js 14 avec React 18 + * + * ============================================================================ + */ + pipeline { agent any + // ========================================================================= + // DÉCLENCHEUR : Vérifie les changements Git chaque minute + // ========================================================================= triggers { pollSCM('* * * * *') } + // ========================================================================= + // PARAMÈTRES : Permet de choisir manuellement l'environnement + // ========================================================================= parameters { choice( name: 'ENV', @@ -13,6 +45,9 @@ pipeline { ) } + // ========================================================================= + // VARIABLES D'ENVIRONNEMENT GLOBALES + // ========================================================================= environment { REGISTRY_URL = "registry.wk-archi-o24a-15m-g3.fr" IMAGE_NAME = "the-tip-top-frontend" @@ -21,7 +56,15 @@ pipeline { stages { - stage('Init') { + // ===================================================================== + // ÉTAPE 1 : INITIALISATION + // --------------------------------------------------------------------- + // But : Détecter automatiquement l'environnement selon la branche Git + // - Branche 'dev' → Environnement dev + // - Branche 'preprod' → Environnement preprod + // - Branche 'main' → Environnement prod + // ===================================================================== + stage('🧭 Init - Détection environnement') { steps { script { def currentBranch = sh(script: "git rev-parse --abbrev-ref HEAD", returnStdout: true).trim() @@ -37,22 +80,41 @@ pipeline { env.DEPLOY_PATH = "/srv/devops/the-tip-top/${env.ENV}" echo """ - 🌍 Environnement = ${env.ENV} - 🏷️ Tag Docker = ${env.TAG} - 📂 Chemin de déploiement = ${env.DEPLOY_PATH} + ╔══════════════════════════════════════════╗ + ║ CONFIGURATION PIPELINE ║ + ╠══════════════════════════════════════════╣ + ║ 🌍 Environnement : ${env.ENV.padRight(18)} ║ + ║ 🏷️ Tag Docker : ${env.TAG.padRight(18)} ║ + ║ 📂 Chemin déploie. : ${env.DEPLOY_PATH.take(18).padRight(18)} ║ + ╚══════════════════════════════════════════╝ """ } } } - stage('Checkout') { + // ===================================================================== + // ÉTAPE 2 : CHECKOUT + // --------------------------------------------------------------------- + // But : Récupérer le code source depuis le dépôt Gitea + // Action : Clone ou pull du code selon l'état du workspace + // ===================================================================== + stage('📦 Checkout - Récupération code source') { steps { - echo "📦 Récupération du code source..." + echo "📦 Récupération du code source depuis Gitea..." checkout scm + echo "✅ Code source récupéré avec succès" } } - stage('Install Dependencies') { + // ===================================================================== + // ÉTAPE 3 : INSTALLATION DES DÉPENDANCES + // --------------------------------------------------------------------- + // But : Installer les packages Node.js nécessaires + // Container : node:20 (requis pour Next.js 14) + // Commande : npm ci (installation propre depuis package-lock.json) + // Cache : Utilise un cache NPM partagé pour accélérer les builds + // ===================================================================== + stage('📦 Install - Dépendances Node.js') { agent { docker { image 'node:20' @@ -60,15 +122,30 @@ pipeline { } } steps { - echo "📦 Installation des dépendances..." + echo "📦 Installation des dépendances Node.js..." sh 'npm ci --prefer-offline' stash includes: 'node_modules/**', name: 'node_modules' + echo "✅ Dépendances installées avec succès" } } - stage('Quality Checks') { + // ===================================================================== + // ÉTAPE 4 : CONTRÔLES QUALITÉ (Exécution parallèle) + // --------------------------------------------------------------------- + // Exécute en parallèle : + // - Lint & Tests : ESLint + Jest avec couverture de code + // - SonarQube : Analyse statique de la qualité du code + // ===================================================================== + stage('🔍 Quality - Contrôles qualité') { parallel { - stage('Lint & Tests') { + // ------------------------------------------------------------- + // 4a. LINT & TESTS + // ------------------------------------------------------------- + // ESLint : Vérifie le style et les erreurs de code + // Jest : Exécute les tests unitaires React/Next.js + // Couverture : Génère un rapport de couverture (lcov) + // ------------------------------------------------------------- + stage('🧪 Lint & Tests') { agent { docker { image 'node:20' @@ -77,23 +154,33 @@ pipeline { } steps { unstash 'node_modules' - echo "🧪 Lancement des tests et lint..." + echo "🧪 Lancement ESLint et Jest..." script { def lintStatus = sh(script: 'npm run lint', returnStatus: true) def testStatus = sh(script: 'npm test', returnStatus: true) if (lintStatus != 0) { - error "❌ ESLint a échoué" + error "❌ ESLint a échoué - Corrigez les erreurs de style" } if (testStatus != 0) { - error "❌ Les tests ont échoué" + error "❌ Les tests ont échoué - Vérifiez les tests unitaires" } - echo "✅ Tests et lint passés" + echo "✅ Lint et tests passés avec succès" } } } - stage('SonarQube') { + // ------------------------------------------------------------- + // 4b. SONARQUBE + // ------------------------------------------------------------- + // Analyse statique du code pour détecter : + // - Bugs potentiels + // - Vulnérabilités de sécurité + // - Code smells (mauvaises pratiques) + // - Couverture de code insuffisante + // - Duplications de code + // ------------------------------------------------------------- + stage('📊 SonarQube Analysis') { agent { docker { image 'sonarsource/sonar-scanner-cli:latest' @@ -101,24 +188,37 @@ pipeline { } } steps { - echo "🔍 Analyse SonarQube..." + echo "📊 Analyse SonarQube en cours..." withSonarQubeEnv('SonarQube') { sh """ sonar-scanner """ } + echo "✅ Analyse SonarQube terminée" } } } } - stage('Build Docker image') { + // ===================================================================== + // ÉTAPE 5 : BUILD IMAGE DOCKER + // --------------------------------------------------------------------- + // But : Construire l'image Docker du frontend Next.js + // Spécificité : Injection des variables NEXT_PUBLIC_* au build time + // Variables lues depuis : .env.{dev|preprod|prod} + // Tags créés : + // - {env}-latest (ex: dev-latest, prod-latest) + // - latest + // ===================================================================== + stage('🐳 Build - Image Docker') { steps { - echo "🐳 Construction de l'image Docker frontend..." + echo "🐳 Construction de l'image Docker frontend Next.js..." script { + // Lecture des variables d'environnement NEXT_PUBLIC_* def envFile = ".env.${env.ENV}" def envVars = sh(script: "cat ${envFile} | grep -E '^NEXT_PUBLIC_' | xargs", returnStdout: true).trim() + // Construction des arguments de build Docker def buildArgs = "" envVars.split().each { envVar -> def parts = envVar.split('=', 2) @@ -132,12 +232,20 @@ pipeline { docker tag ${REGISTRY_URL}/${IMAGE_NAME}:${env.TAG} ${REGISTRY_URL}/${IMAGE_NAME}:latest """ } + echo "✅ Image Docker construite : ${REGISTRY_URL}/${IMAGE_NAME}:${env.TAG}" } } - stage('Push to Registry') { + // ===================================================================== + // ÉTAPE 6 : PUSH VERS LE REGISTRE + // --------------------------------------------------------------------- + // But : Envoyer l'image Docker vers le registre privé + // Registre : registry.wk-archi-o24a-15m-g3.fr + // Authentification : Credentials Jenkins sécurisés + // ===================================================================== + stage('📤 Push - Registre Docker') { steps { - echo "📤 Envoi de l'image vers le registre Docker..." + echo "📤 Envoi de l'image vers le registre Docker privé..." withCredentials([usernamePassword(credentialsId: 'registry-credentials', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS')]) { sh """ echo "$REG_PASS" | docker login ${REGISTRY_URL} -u "$REG_USER" --password-stdin @@ -145,27 +253,57 @@ pipeline { docker push ${REGISTRY_URL}/${IMAGE_NAME}:latest """ } + echo "✅ Image envoyée avec succès vers ${REGISTRY_URL}" } } - stage('Deploy') { + // ===================================================================== + // ÉTAPE 7 : DÉPLOIEMENT + // --------------------------------------------------------------------- + // But : Déployer le frontend sur le serveur cible + // Actions : + // 1. Pull de la nouvelle image depuis le registre + // 2. Recréation du container avec la nouvelle image + // Option : --no-deps pour ne pas redémarrer le backend + // Chemins : /srv/devops/the-tip-top/{dev|preprod|prod} + // ===================================================================== + stage('🚀 Deploy - Mise en production') { steps { - echo "🚀 Déploiement du frontend sur ${env.ENV}..." + echo "🚀 Déploiement du frontend sur l'environnement ${env.ENV}..." sh """ cd "${env.DEPLOY_PATH}" docker compose pull frontend docker compose up -d --no-deps --force-recreate frontend """ + echo "✅ Frontend déployé avec succès sur ${env.ENV}" } } } + // ========================================================================= + // ACTIONS POST-PIPELINE + // ========================================================================= post { success { - echo "✅ Pipeline frontend ${env.ENV} terminé avec succès !" + echo """ + ╔══════════════════════════════════════════╗ + ║ ✅ PIPELINE TERMINÉ AVEC SUCCÈS ║ + ╠══════════════════════════════════════════╣ + ║ Environnement : ${env.ENV.padRight(23)} ║ + ║ Image : ${IMAGE_NAME.padRight(23)} ║ + ║ Tag : ${env.TAG.padRight(23)} ║ + ╚══════════════════════════════════════════╝ + """ } failure { - echo "❌ Échec du pipeline frontend (${env.ENV})." + echo """ + ╔══════════════════════════════════════════╗ + ║ ❌ ÉCHEC DU PIPELINE ║ + ╠══════════════════════════════════════════╣ + ║ Environnement : ${env.ENV.padRight(23)} ║ + ║ Vérifiez les logs pour plus de détails ║ + ╚══════════════════════════════════════════╝ + """ } } }