commit 741e4043fa787d55e559375ed4ec4ee0e040be12 Author: Thibault Pouch Date: Thu Mar 19 21:02:01 2026 +0100 feat : Add Jenkins pipeline scripts for building Docker images and deploying stacks diff --git a/Nest/build-docker-image.groovy b/Nest/build-docker-image.groovy new file mode 100644 index 0000000..9a9e9c8 --- /dev/null +++ b/Nest/build-docker-image.groovy @@ -0,0 +1,102 @@ +pipeline { + agent any + + parameters { + string( + name: 'REPO_URL', + defaultValue: 'https://git.crowmate.fr/crowmate/Nest.git', + description: 'URL du repo Gitea à cloner' + ) + string( + name: 'BRANCH', + defaultValue: 'main', + description: 'Branche à builder' + ) + string( + name: 'IMAGE_TAG', + defaultValue: 'latest', + description: 'Tag des images Docker (ex: latest, v1.0.0)' + ) + } + + environment { + GITEA_CREDENTIALS_ID = 'gitea-token' + REGISTRY = 'git.crowmate.fr' + REGISTRY_OWNER = 'crowmate' + } + + stages { + + stage('Clone') { + steps { + cleanWs() + checkout([ + $class: 'GitSCM', + branches: [[name: "*/${params.BRANCH}"]], + userRemoteConfigs: [[ + url: params.REPO_URL, + credentialsId: env.GITEA_CREDENTIALS_ID + ]] + ]) + echo "✅ Repo cloné" + } + } + + stage('Build & Push images') { + steps { + withCredentials([usernamePassword( + credentialsId: env.GITEA_CREDENTIALS_ID, + usernameVariable: 'REGISTRY_USER', + passwordVariable: 'REGISTRY_PASS' + )]) { + sh ''' + echo "$REGISTRY_PASS" | docker login ''' + env.REGISTRY + ''' -u "$REGISTRY_USER" --password-stdin + echo "✅ Connecté au registry Gitea" + ''' + + sh """ + echo "🔨 Build nest-api..." + docker build -t ${env.REGISTRY}/${env.REGISTRY_OWNER}/nest-api:${params.IMAGE_TAG} ./nest-backend + + echo "🔨 Build nest-front..." + docker build -t ${env.REGISTRY}/${env.REGISTRY_OWNER}/nest-front:${params.IMAGE_TAG} ./nest-front + + echo "🔨 Build nest-intra..." + docker build -t ${env.REGISTRY}/${env.REGISTRY_OWNER}/nest-intra:${params.IMAGE_TAG} ./nest-intra + """ + + sh """ + echo "📤 Push nest-api..." + docker push ${env.REGISTRY}/${env.REGISTRY_OWNER}/nest-api:${params.IMAGE_TAG} + + echo "📤 Push nest-front..." + docker push ${env.REGISTRY}/${env.REGISTRY_OWNER}/nest-front:${params.IMAGE_TAG} + + echo "📤 Push nest-intra..." + docker push ${env.REGISTRY}/${env.REGISTRY_OWNER}/nest-intra:${params.IMAGE_TAG} + """ + + sh "docker logout ${env.REGISTRY}" + } + } + } + + } + + post { + success { + echo "🎉 Images buildées et pushées avec succès (tag: ${params.IMAGE_TAG})" + } + failure { + echo "💥 Échec du build/push" + } + always { + // Nettoyage des images locales pour ne pas saturer le disque + sh """ + docker rmi ${env.REGISTRY}/${env.REGISTRY_OWNER}/nest-api:${params.IMAGE_TAG} || true + docker rmi ${env.REGISTRY}/${env.REGISTRY_OWNER}/nest-front:${params.IMAGE_TAG} || true + docker rmi ${env.REGISTRY}/${env.REGISTRY_OWNER}/nest-intra:${params.IMAGE_TAG} || true + """ + } + } +} \ No newline at end of file diff --git a/Nest/deploy-stack.groovy b/Nest/deploy-stack.groovy new file mode 100644 index 0000000..4d1157b --- /dev/null +++ b/Nest/deploy-stack.groovy @@ -0,0 +1,110 @@ +pipeline { + agent any + + parameters { + string( + name: 'REPO_URL', + defaultValue: 'https://git.crowmate.fr/crowmate/Nest.git', + description: 'URL du repo Gitea (pour récupérer le docker-compose.prod.yml)' + ) + string( + name: 'BRANCH', + defaultValue: 'main', + description: 'Branche' + ) + string( + name: 'STACK_NAME', + defaultValue: 'nest', + description: 'Nom de la stack dans Portainer' + ) + string( + name: 'IMAGE_TAG', + defaultValue: 'latest', + description: 'Tag des images à déployer' + ) + } + + environment { + GITEA_CREDENTIALS_ID = 'gitea-token' + PORTAINER_URL = 'https://docker.devgoblin.fr' + PORTAINER_ENV_ID = '3' + } + + stages { + + stage('Clone') { + steps { + cleanWs() + checkout([ + $class: 'GitSCM', + branches: [[name: "*/${params.BRANCH}"]], + userRemoteConfigs: [[ + url: params.REPO_URL, + credentialsId: env.GITEA_CREDENTIALS_ID + ]] + ]) + echo "✅ Repo cloné" + } + } + + stage('Validation') { + steps { + script { + if (!fileExists('docker-compose.prod.yml')) { + error("❌ docker-compose.prod.yml introuvable à la racine du repo !") + } + echo "✅ docker-compose.prod.yml trouvé" + } + } + } + + stage('Deploy sur Portainer') { + steps { + withCredentials([string(credentialsId: 'portainer-token', variable: 'PORTAINER_TOKEN')]) { + sh ''' + COMPOSE_CONTENT=$(base64 -w 0 docker-compose.prod.yml) + + # Récupère la liste des stacks + STACKS=$(curl -s -X GET "''' + env.PORTAINER_URL + '''/api/stacks" \ + -H "X-API-Key: $PORTAINER_TOKEN") + + # Cherche si la stack existe déjà + STACK_ID=$(echo "$STACKS" | jq -r '.[] | select(.Name == "''' + params.STACK_NAME + '''") | .Id') + + if [ -n "$STACK_ID" ]; then + echo "🔄 Stack existante (ID: $STACK_ID), mise à jour..." + RESPONSE=$(curl -s -X PUT "''' + env.PORTAINER_URL + '''/api/stacks/$STACK_ID?endpointId=''' + env.PORTAINER_ENV_ID + '''" \ + -H "X-API-Key: $PORTAINER_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"stackFileContent\": \"$COMPOSE_CONTENT\", \"prune\": true, \"pullImage\": true}") + else + echo "🚀 Création de la stack ''' + params.STACK_NAME + '''..." + RESPONSE=$(curl -s -X POST "''' + env.PORTAINER_URL + '''/api/stacks/create/standalone/string?endpointId=''' + env.PORTAINER_ENV_ID + '''" \ + -H "X-API-Key: $PORTAINER_TOKEN" \ + -H "Content-Type: application/json" \ + -d "{\"name\": \"''' + params.STACK_NAME + '''\", \"stackFileContent\": \"$COMPOSE_CONTENT\"}") + fi + + # Vérifie si la réponse contient une erreur + if echo "$RESPONSE" | jq -e '.message' > /dev/null 2>&1; then + echo "❌ Erreur Portainer : $(echo $RESPONSE | jq -r '.message')" + exit 1 + fi + + echo "✅ Stack déployée avec succès !" + ''' + } + } + } + + } + + post { + success { + echo "🎉 Stack '${params.STACK_NAME}' déployée sur Portainer (tag: ${params.IMAGE_TAG})" + } + failure { + echo "💥 Échec du déploiement de la stack '${params.STACK_NAME}'" + } + } +} \ No newline at end of file