From 75c3f84e98016366c01ef3c63592a32dc05814dc Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 6 Jan 2026 20:12:32 +0000 Subject: [PATCH] Add apps/demo-nginx/Jenkinsfile.telegram --- apps/demo-nginx/Jenkinsfile.telegram | 727 +++++++++++++++++++++++++++ 1 file changed, 727 insertions(+) create mode 100644 apps/demo-nginx/Jenkinsfile.telegram diff --git a/apps/demo-nginx/Jenkinsfile.telegram b/apps/demo-nginx/Jenkinsfile.telegram new file mode 100644 index 0000000..b0a4d1b --- /dev/null +++ b/apps/demo-nginx/Jenkinsfile.telegram @@ -0,0 +1,727 @@ +pipeline { + agent any + + environment { + APP_NAME = 'demo-nginx' + NAMESPACE = 'demo-app' + DOCKER_REGISTRY = 'docker.io' + DOCKER_REPO = 'vladcrypto' + GITEA_URL = 'http://gitea-http.gitea.svc.cluster.local:3000' + GITEA_REPO = 'admin/k3s-gitops' + GITEA_BRANCH = 'main' + BUILD_TAG = "${env.BUILD_NUMBER}" + IMAGE_TAG = "${env.BRANCH_NAME}-${env.BUILD_NUMBER}" + + // Rollback configuration + ROLLBACK_ENABLED = 'true' + DEPLOYMENT_TIMEOUT = '300s' + ARGOCD_SYNC_TIMEOUT = '120' + SKIP_HEALTH_CHECK = 'true' + + // Notification configuration + TELEGRAM_BOT_TOKEN = credentials('telegram-bot-token') + TELEGRAM_CHAT_ID = credentials('telegram-chat-id') + + // Build info + BUILD_URL = "${env.BUILD_URL}" + GIT_COMMIT_SHORT = "" + DEPLOYMENT_START_TIME = "" + DEPLOYMENT_END_TIME = "" + } + + stages { + stage('Initialization') { + steps { + script { + echo "πŸš€ Starting deployment pipeline..." + DEPLOYMENT_START_TIME = sh(script: 'date +%s', returnStdout: true).trim() + + sendTelegramNotification( + status: 'STARTED', + message: """ +πŸš€ Deployment Started + +Application: ${APP_NAME} +Build: #${BUILD_NUMBER} +Branch: ${env.BRANCH_NAME} +Namespace: ${NAMESPACE} +Started by: ${env.BUILD_USER ?: 'Jenkins'} + +Building and deploying... + """, + color: 'πŸ”΅' + ) + } + } + } + + stage('Save Current State') { + when { branch 'main' } + steps { + script { + echo "πŸ“Έ Saving current deployment state for rollback..." + + sh """ + kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \ + -o jsonpath='{.spec.template.spec.containers[0].image}' \ + > /tmp/previous_image_${BUILD_NUMBER}.txt || echo "none" > /tmp/previous_image_${BUILD_NUMBER}.txt + + PREV_IMAGE=\$(cat /tmp/previous_image_${BUILD_NUMBER}.txt) + echo "Previous image: \${PREV_IMAGE}" + """ + + sh """ + kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \ + -o jsonpath='{.spec.replicas}' \ + > /tmp/previous_replicas_${BUILD_NUMBER}.txt || echo "2" > /tmp/previous_replicas_${BUILD_NUMBER}.txt + """ + + echo "βœ… Current state saved" + } + } + } + + stage('Checkout Source') { + steps { + echo "πŸ“¦ Creating application artifacts..." + + sh """ + cat > Dockerfile << 'EOF' +FROM nginx:1.25.3-alpine +COPY index.html /usr/share/nginx/html/index.html +COPY nginx.conf /etc/nginx/nginx.conf +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +EOF + """ + + sh """ + cat > index.html << EOF + + + + Demo Nginx + + + +
+

πŸš€ Demo Nginx - Build #${BUILD_NUMBER}

+

Environment: Production

+

Version: ${IMAGE_TAG}

+

+ Image: ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG} +

+
+ + +EOF + """ + + sh ''' + cat > nginx.conf << 'EOF' +user nginx; +worker_processes auto; +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; +events { worker_connections 1024; } +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + access_log /var/log/nginx/access.log main; + sendfile on; + keepalive_timeout 65; + server { + listen 80; + server_name _; + location / { + root /usr/share/nginx/html; + index index.html; + } + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + } +} +EOF + ''' + } + } + + stage('Build Docker Image') { + steps { + script { + echo "πŸ—οΈ Building Docker image..." + + sendTelegramNotification( + status: 'BUILDING', + message: """ +πŸ—οΈ Building Docker Image + +Image: ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG} +Stage: Build + """, + color: 'πŸ”΅' + ) + + sh """ + docker build \ + -t ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG} \ + -t ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:latest \ + . + """ + echo "βœ… Image built successfully!" + } + } + } + + stage('Push to Registry') { + when { branch 'main' } + steps { + script { + echo "πŸ“€ Pushing image to registry..." + + sendTelegramNotification( + status: 'PUSHING', + message: """ +πŸ“€ Pushing to Registry + +Registry: ${DOCKER_REGISTRY} +Image: ${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG} + """, + color: 'πŸ”΅' + ) + + withCredentials([usernamePassword( + credentialsId: 'docker-registry-credentials', + usernameVariable: 'DOCKER_USER', + passwordVariable: 'DOCKER_PASS' + )]) { + sh """ + echo "\${DOCKER_PASS}" | docker login ${DOCKER_REGISTRY} -u "\${DOCKER_USER}" --password-stdin + docker push ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG} + docker push ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:latest + docker logout ${DOCKER_REGISTRY} + """ + } + echo "βœ… Image pushed successfully!" + } + } + } + + stage('Update GitOps Manifests') { + when { branch 'main' } + steps { + script { + echo "πŸ“ Updating Kubernetes manifests..." + + sendTelegramNotification( + status: 'UPDATING', + message: """ +πŸ“ Updating GitOps Manifests + +Repository: ${GITEA_REPO} +Branch: ${GITEA_BRANCH} +File: apps/demo-nginx/deployment.yaml + """, + color: 'πŸ”΅' + ) + + withCredentials([usernamePassword( + credentialsId: 'gitea-credentials', + usernameVariable: 'GIT_USER', + passwordVariable: 'GIT_PASS' + )]) { + sh """ + rm -rf k3s-gitops || true + git clone http://\${GIT_USER}:\${GIT_PASS}@gitea-http.gitea.svc.cluster.local:3000/admin/k3s-gitops.git + cd k3s-gitops + git config user.name "Jenkins" + git config user.email "jenkins@thedevops.dev" + + # Save current commit for rollback + git rev-parse HEAD > /tmp/previous_commit_${BUILD_NUMBER}.txt + + sed -i 's|image: .*|image: ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG}|' apps/demo-nginx/deployment.yaml + git add apps/demo-nginx/deployment.yaml + git commit -m "chore(demo-nginx): Update image to ${IMAGE_TAG}" || echo "No changes" + git push origin main + """ + } + echo "βœ… Manifests updated!" + } + } + } + + stage('Wait for ArgoCD Sync') { + when { branch 'main' } + steps { + script { + echo "⏳ Waiting for ArgoCD to sync..." + + sendTelegramNotification( + status: 'SYNCING', + message: """ +⏳ ArgoCD Syncing + +Application: ${APP_NAME} +Namespace: argocd +Timeout: ${ARGOCD_SYNC_TIMEOUT}s + """, + color: 'πŸ”΅' + ) + + def syncSuccess = false + def attempts = 0 + def maxAttempts = Integer.parseInt(env.ARGOCD_SYNC_TIMEOUT) / 10 + + while (!syncSuccess && attempts < maxAttempts) { + attempts++ + echo "ArgoCD sync check attempt ${attempts}/${maxAttempts}..." + + def syncStatus = sh( + script: """ + kubectl get application ${APP_NAME} -n argocd \ + -o jsonpath='{.status.sync.status}' + """, + returnStdout: true + ).trim() + + def currentImage = sh( + script: """ + kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \ + -o jsonpath='{.spec.template.spec.containers[0].image}' + """, + returnStdout: true + ).trim() + + echo "ArgoCD sync status: ${syncStatus}" + echo "Current deployment image: ${currentImage}" + + if (syncStatus == 'Synced' && currentImage.contains(env.IMAGE_TAG)) { + syncSuccess = true + echo "βœ… ArgoCD synced successfully!" + break + } + + if (attempts < maxAttempts) { + sleep 10 + } + } + + if (!syncSuccess) { + error "❌ ArgoCD sync timeout!" + } + } + } + } + + stage('Wait for Deployment') { + when { branch 'main' } + steps { + script { + echo "⏳ Waiting for deployment to complete..." + + sendTelegramNotification( + status: 'DEPLOYING', + message: """ +πŸš€ Deploying to Kubernetes + +Deployment: ${APP_NAME} +Namespace: ${NAMESPACE} +Timeout: ${DEPLOYMENT_TIMEOUT} + """, + color: 'πŸ”΅' + ) + + try { + sh """ + kubectl rollout status deployment/${APP_NAME} -n ${NAMESPACE} --timeout=${DEPLOYMENT_TIMEOUT} + """ + echo "βœ… Deployment rolled out successfully!" + } catch (Exception e) { + echo "❌ Deployment failed: ${e.message}" + throw e + } + } + } + } + + stage('Verify Deployment') { + when { branch 'main' } + steps { + script { + echo "βœ… Verifying deployment..." + + try { + def verifyResult = sh(script: """#!/bin/bash + set -e + + # Check pod status + READY_PODS=\$(kubectl get deployment ${APP_NAME} -n ${NAMESPACE} -o jsonpath='{.status.readyReplicas}') + DESIRED_PODS=\$(kubectl get deployment ${APP_NAME} -n ${NAMESPACE} -o jsonpath='{.spec.replicas}') + + echo "Ready pods: \${READY_PODS}/\${DESIRED_PODS}" + + if [ "\${READY_PODS}" != "\${DESIRED_PODS}" ]; then + echo "❌ Not all pods are ready!" + exit 1 + fi + + # Verify image version + DEPLOYED_IMAGE=\$(kubectl get deployment ${APP_NAME} -n ${NAMESPACE} -o jsonpath='{.spec.template.spec.containers[0].image}') + echo "Deployed image: \${DEPLOYED_IMAGE}" + + if [[ "\${DEPLOYED_IMAGE}" != *"${IMAGE_TAG}"* ]]; then + echo "❌ Image version mismatch!" + exit 1 + fi + + echo "βœ… All verification checks passed!" + """, returnStdout: true).trim() + + echo verifyResult + echo "βœ… Deployment verified successfully!" + + } catch (Exception e) { + echo "❌ Deployment verification failed: ${e.message}" + throw e + } + } + } + } + } + + post { + success { + script { + DEPLOYMENT_END_TIME = sh(script: 'date +%s', returnStdout: true).trim() + def duration = (DEPLOYMENT_END_TIME.toInteger() - DEPLOYMENT_START_TIME.toInteger()) + def durationMin = duration / 60 + def durationSec = duration % 60 + + // Get deployment details + def podStatus = sh( + script: """ + kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} \ + -o jsonpath='{range .items[*]}{.metadata.name}{" "}{.status.phase}{" "}{.status.containerStatuses[0].restartCount}{"\\n"}{end}' + """, + returnStdout: true + ).trim() + + def deployedImage = sh( + script: """ + kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \ + -o jsonpath='{.spec.template.spec.containers[0].image}' + """, + returnStdout: true + ).trim() + + def replicas = sh( + script: """ + kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \ + -o jsonpath='{.status.replicas}' + """, + returnStdout: true + ).trim() + + echo """ + βœ… DEPLOYMENT SUCCESS! + + Application: ${APP_NAME} + Image: ${deployedImage} + Namespace: ${NAMESPACE} + Build: #${BUILD_NUMBER} + Duration: ${durationMin}m ${durationSec}s + + All checks passed! ✨ + """ + + // Send detailed success notification + sendTelegramNotification( + status: 'SUCCESS', + message: """ +βœ… Deployment Successful! + +━━━━━━━━━━━━━━━━━━━━━━ +πŸ“¦ Application Details +Name: ${APP_NAME} +Build: #${BUILD_NUMBER} +Branch: ${env.BRANCH_NAME} +Namespace: ${NAMESPACE} + +━━━━━━━━━━━━━━━━━━━━━━ +🐳 Docker Image +Registry: ${DOCKER_REGISTRY} +Repository: ${DOCKER_REPO}/${APP_NAME} +Tag: ${IMAGE_TAG} +Full Image: ${deployedImage} + +━━━━━━━━━━━━━━━━━━━━━━ +☸️ Kubernetes Status +Replicas: ${replicas}/${replicas} Ready +Rollout: Completed βœ… +Health: All pods healthy + +━━━━━━━━━━━━━━━━━━━━━━ +⏱️ Deployment Metrics +Duration: ${durationMin}m ${durationSec}s +Started: ${new Date(DEPLOYMENT_START_TIME.toLong() * 1000).format('HH:mm:ss')} +Completed: ${new Date(DEPLOYMENT_END_TIME.toLong() * 1000).format('HH:mm:ss')} + +━━━━━━━━━━━━━━━━━━━━━━ +πŸ”— Links +Jenkins Build +GitOps Repository + +Deployed by Jenkins CI/CD Pipeline πŸš€ + """, + color: 'βœ…' + ) + + // Cleanup rollback files + sh """ + rm -f /tmp/previous_image_${BUILD_NUMBER}.txt + rm -f /tmp/previous_replicas_${BUILD_NUMBER}.txt + rm -f /tmp/previous_commit_${BUILD_NUMBER}.txt + """ + } + } + + failure { + script { + def errorDetails = "" + def previousImage = "unknown" + + try { + errorDetails = sh( + script: """ + kubectl get events -n ${NAMESPACE} --sort-by='.lastTimestamp' | tail -10 + """, + returnStdout: true + ).trim() + } catch (Exception e) { + errorDetails = "Could not fetch events: ${e.message}" + } + + if (env.BRANCH_NAME == 'main' && env.ROLLBACK_ENABLED == 'true') { + echo """ + ❌ DEPLOYMENT FAILED - INITIATING ROLLBACK! + + Rolling back to previous version... + """ + + sendTelegramNotification( + status: 'ROLLING_BACK', + message: """ +πŸ”„ Deployment Failed - Rolling Back + +Application: ${APP_NAME} +Failed Build: #${BUILD_NUMBER} +Image: ${IMAGE_TAG} + +Initiating automatic rollback... + """, + color: '⚠️' + ) + + try { + previousImage = sh( + script: "cat /tmp/previous_image_${BUILD_NUMBER}.txt 2>/dev/null || echo 'none'", + returnStdout: true + ).trim() + + if (previousImage != 'none' && previousImage != '') { + echo "πŸ”„ Rolling back to: ${previousImage}" + + sh """ + kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE} + kubectl rollout status deployment/${APP_NAME} -n ${NAMESPACE} --timeout=180s + """ + + withCredentials([usernamePassword( + credentialsId: 'gitea-credentials', + usernameVariable: 'GIT_USER', + passwordVariable: 'GIT_PASS' + )]) { + sh """ + cd k3s-gitops || git clone http://\${GIT_USER}:\${GIT_PASS}@gitea-http.gitea.svc.cluster.local:3000/admin/k3s-gitops.git + cd k3s-gitops + git config user.name "Jenkins" + git config user.email "jenkins@thedevops.dev" + git revert --no-edit HEAD || true + git push origin main || true + """ + } + + sendTelegramNotification( + status: 'ROLLBACK_SUCCESS', + message: """ +βœ… Rollback Completed + +━━━━━━━━━━━━━━━━━━━━━━ +πŸ“¦ Application +Name: ${APP_NAME} +Failed Build: #${BUILD_NUMBER} +Rolled Back To: ${previousImage} + +━━━━━━━━━━━━━━━━━━━━━━ +πŸ”„ Rollback Status +Status: Success βœ… +Method: kubectl rollout undo +Git: Commit reverted + +━━━━━━━━━━━━━━━━━━━━━━ +πŸ“‹ Recent Events +${errorDetails.take(500)} + +━━━━━━━━━━━━━━━━━━━━━━ +⚠️ Action Required +Please review logs and fix issues before redeploying + +View Build Logs + """, + color: 'βœ…' + ) + + } else { + sendTelegramNotification( + status: 'ROLLBACK_FAILED', + message: """ +❌ Rollback Failed + +Application: ${APP_NAME} +Build: #${BUILD_NUMBER} + +Error: No previous version found + +⚠️ MANUAL INTERVENTION REQUIRED +kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE} + +View Build Logs + """, + color: 'πŸ”΄' + ) + } + + } catch (Exception e) { + sendTelegramNotification( + status: 'ROLLBACK_FAILED', + message: """ +❌ Rollback Failed + +Application: ${APP_NAME} +Build: #${BUILD_NUMBER} + +Error: ${e.message} + +⚠️ MANUAL ROLLBACK REQUIRED +kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE} + +View Build Logs + """, + color: 'πŸ”΄' + ) + } + + } else { + sendTelegramNotification( + status: 'FAILED', + message: """ +❌ Deployment Failed + +━━━━━━━━━━━━━━━━━━━━━━ +πŸ“¦ Application +Name: ${APP_NAME} +Build: #${BUILD_NUMBER} +Branch: ${env.BRANCH_NAME} +Image: ${IMAGE_TAG} + +━━━━━━━━━━━━━━━━━━━━━━ +πŸ“‹ Recent Events +${errorDetails.take(500)} + +━━━━━━━━━━━━━━━━━━━━━━ +πŸ”— Links +View Console Output +Build Details + +Rollback: ${env.ROLLBACK_ENABLED == 'true' ? 'Enabled but not on main branch' : 'Disabled'} + """, + color: 'πŸ”΄' + ) + } + } + } + + always { + sh """ + docker rmi ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${IMAGE_TAG} || true + docker rmi ${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:latest || true + docker stop test-${BUILD_NUMBER} 2>/dev/null || true + docker rm test-${BUILD_NUMBER} 2>/dev/null || true + """ + cleanWs() + } + } +} + +// Telegram notification function +def sendTelegramNotification(Map args) { + def status = args.status + def message = args.message + def color = args.color ?: 'πŸ”΅' + + try { + // HTML formatting for Telegram + def formattedMessage = message.trim() + + sh """ + curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ + -d chat_id="${TELEGRAM_CHAT_ID}" \ + -d parse_mode="HTML" \ + -d disable_web_page_preview=true \ + -d text="${formattedMessage}" + """ + + echo "πŸ“± Telegram notification sent: ${status}" + } catch (Exception e) { + echo "⚠️ Failed to send Telegram notification: ${e.message}" + // Don't fail the pipeline if notification fails + } +} \ No newline at end of file