// Jenkinsfile for Manual Rollback // This allows rolling back to any previous version // Now rebuilds image with updated design! pipeline { agent any parameters { choice( name: 'ROLLBACK_METHOD', choices: ['IMAGE_TAG', 'REVISION_NUMBER', 'GIT_COMMIT'], description: 'Select rollback method' ) string( name: 'TARGET_VERSION', defaultValue: '', description: ''' Enter target version based on method: - IMAGE_TAG: main-21, main-20, etc. - REVISION_NUMBER: 1, 2, 3 (from rollout history) - GIT_COMMIT: abc123def (git commit SHA) ''' ) booleanParam( name: 'SKIP_HEALTH_CHECK', defaultValue: true, description: 'Skip health checks (recommended for rollback)' ) booleanParam( name: 'DRY_RUN', defaultValue: false, description: 'Dry run - show what would happen without applying' ) booleanParam( name: 'REBUILD_IMAGE', defaultValue: true, description: 'Rebuild image with updated design (recommended)' ) } environment { APP_NAME = 'demo-nginx' CONTAINER_NAME = 'nginx' NAMESPACE = 'demo-app' DOCKER_REGISTRY = 'docker.io' DOCKER_REPO = 'vladcrypto' GITEA_URL = 'http://gitea-http.gitea.svc.cluster.local:3000' HEALTH_CHECK_TIMEOUT = '300s' ARGOCD_SYNC_TIMEOUT = '120' } stages { stage('Validate Input') { steps { script { echo "🔍 Validating rollback request..." // Trim whitespace from input env.TARGET_VERSION_CLEAN = params.TARGET_VERSION.trim() if (env.TARGET_VERSION_CLEAN == '') { error("❌ TARGET_VERSION cannot be empty!") } echo """ 📋 Rollback Configuration: Method: ${params.ROLLBACK_METHOD} Target: ${env.TARGET_VERSION_CLEAN} Rebuild Image: ${params.REBUILD_IMAGE} Skip Health Check: ${params.SKIP_HEALTH_CHECK} Dry Run: ${params.DRY_RUN} """ } } } stage('Show Current State') { steps { script { echo "📸 Current Deployment State:" sh """ echo "=== Current Deployment ===" kubectl get deployment ${APP_NAME} -n ${NAMESPACE} echo "" echo "=== Current Image ===" kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \ -o jsonpath='{.spec.template.spec.containers[0].image}' echo "" echo "" echo "=== Current Pods ===" kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} echo "" echo "=== Rollout History ===\" kubectl rollout history deployment/${APP_NAME} -n ${NAMESPACE} """ } } } stage('Prepare Rollback') { steps { script { echo "🔄 Preparing rollback to: ${env.TARGET_VERSION_CLEAN}" if (params.ROLLBACK_METHOD == 'IMAGE_TAG') { env.TARGET_IMAGE = "${DOCKER_REGISTRY}/${DOCKER_REPO}/${APP_NAME}:${env.TARGET_VERSION_CLEAN}" // Extract build number from tag env.BUILD_NUMBER_FROM_TAG = env.TARGET_VERSION_CLEAN.split('-')[1] sh """ echo "Target image: ${env.TARGET_IMAGE}" echo "Build number: ${env.BUILD_NUMBER_FROM_TAG}" """ } else if (params.ROLLBACK_METHOD == 'REVISION_NUMBER') { env.REVISION = env.TARGET_VERSION_CLEAN sh """ echo "Rolling back to revision: ${env.REVISION}" kubectl rollout history deployment/${APP_NAME} -n ${NAMESPACE} \ --revision=${env.REVISION} """ } else if (params.ROLLBACK_METHOD == 'GIT_COMMIT') { env.GIT_SHA = env.TARGET_VERSION_CLEAN echo "Rolling back to git commit: ${env.GIT_SHA}" } } } } stage('Rebuild Image') { when { expression { !params.DRY_RUN && params.REBUILD_IMAGE && params.ROLLBACK_METHOD == 'IMAGE_TAG' } } steps { script { echo "🔨 Rebuilding image with updated design..." // Create Dockerfile 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 """ // Create HTML with rollback marker sh """ cat > index.html << EOF Demo Nginx - Rollback
🔄 ROLLED BACK

🚀 Demo Nginx - Build #${env.BUILD_NUMBER_FROM_TAG}

Environment: Production

Version: ${env.TARGET_VERSION_CLEAN}

Image: ${env.TARGET_IMAGE}

⏮️ Restored from previous deployment

EOF """ // Create nginx.conf 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 ''' // Build new image sh """ docker build -t ${env.TARGET_IMAGE} . """ echo "✅ Image rebuilt successfully!" } } } stage('Push Rebuilt Image') { when { expression { !params.DRY_RUN && params.REBUILD_IMAGE && params.ROLLBACK_METHOD == 'IMAGE_TAG' } } steps { script { echo "📤 Pushing rebuilt image..." 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 ${env.TARGET_IMAGE} docker logout ${DOCKER_REGISTRY} """ } echo "✅ Image pushed successfully!" } } } stage('Execute Rollback') { when { expression { !params.DRY_RUN } } steps { script { echo "🚀 Executing rollback..." if (params.ROLLBACK_METHOD == 'IMAGE_TAG') { // Update deployment sh """ echo "Setting image to: ${env.TARGET_IMAGE}" kubectl set image deployment/${APP_NAME} \ ${CONTAINER_NAME}=${env.TARGET_IMAGE} \ -n ${NAMESPACE} \ --record """ // Update Git manifests 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" sed -i 's|image: .*|image: ${env.TARGET_IMAGE}|' apps/demo-nginx/deployment.yaml git add apps/demo-nginx/deployment.yaml git commit -m "rollback(demo-nginx): Manual rollback to ${env.TARGET_VERSION_CLEAN}" || echo "No changes" git push origin main """ } } else if (params.ROLLBACK_METHOD == 'REVISION_NUMBER') { sh """ kubectl rollout undo deployment/${APP_NAME} \ -n ${NAMESPACE} \ --to-revision=${env.REVISION} """ } else if (params.ROLLBACK_METHOD == 'GIT_COMMIT') { 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" git checkout ${env.GIT_SHA} -- apps/demo-nginx/deployment.yaml TARGET_IMAGE=\$(grep 'image:' apps/demo-nginx/deployment.yaml | awk '{print \$2}') git checkout main git checkout ${env.GIT_SHA} -- apps/demo-nginx/deployment.yaml git add apps/demo-nginx/deployment.yaml git commit -m "rollback(demo-nginx): Rollback to commit ${env.GIT_SHA}" || echo "No changes" git push origin main echo "Rolled back to image: \${TARGET_IMAGE}" """ } } echo "✅ Rollback command executed" } } } stage('Wait for ArgoCD Sync') { when { expression { !params.DRY_RUN } } steps { script { echo "⏳ Waiting for ArgoCD to sync..." 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}" echo "Expected image: ${env.TARGET_IMAGE}" if (syncStatus == 'Synced' && currentImage.contains(env.TARGET_VERSION_CLEAN)) { syncSuccess = true echo "✅ ArgoCD synced successfully!" break } if (attempts < maxAttempts) { echo "Waiting 10 seconds before next check..." sleep 10 } } if (!syncSuccess) { echo "⚠️ ArgoCD sync check timeout (non-critical for rollback)" } } } } stage('Wait for Rollout') { when { expression { !params.DRY_RUN } } steps { script { echo "⏳ Waiting for rollout to complete..." sh """ kubectl rollout status deployment/${APP_NAME} \ -n ${NAMESPACE} \ --timeout=${HEALTH_CHECK_TIMEOUT} """ echo "✅ Rollout completed" } } } stage('Verify Deployment') { when { expression { !params.DRY_RUN } } steps { script { echo "✅ Verifying deployment..." sh """#!/bin/bash set -e # Check pods ready 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 DEPLOYED_IMAGE=\$(kubectl get deployment ${APP_NAME} -n ${NAMESPACE} -o jsonpath='{.spec.template.spec.containers[0].image}') echo "Deployed image: \${DEPLOYED_IMAGE}" echo "✅ Deployment verified!" """ } } } stage('Show New State') { when { expression { !params.DRY_RUN } } steps { script { echo "📊 New Deployment State:" sh """ echo "=== New Deployment ===" kubectl get deployment ${APP_NAME} -n ${NAMESPACE} echo "" echo "=== New Image ===" kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \ -o jsonpath='{.spec.template.spec.containers[0].image}' echo "" echo "" echo "=== New Pods ===" kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} """ } } } stage('Dry Run Summary') { when { expression { params.DRY_RUN } } steps { script { echo """ 🔍 DRY RUN SUMMARY This is what would happen: Method: ${params.ROLLBACK_METHOD} Target: ${env.TARGET_VERSION_CLEAN} Rebuild Image: ${params.REBUILD_IMAGE} Steps: 1. ${params.REBUILD_IMAGE ? 'Rebuild image with rollback design' : 'Use existing image'} 2. Update deployment 3. Update Git manifests 4. Wait for ArgoCD sync 5. Wait for rollout 6. Verify deployment No actual changes were made. """ } } } } post { success { script { if (params.DRY_RUN) { echo "✅ DRY RUN COMPLETED - No changes made" } else { echo """ ✅ ROLLBACK SUCCESSFUL! Application: ${APP_NAME} Method: ${params.ROLLBACK_METHOD} Target Version: ${env.TARGET_VERSION_CLEAN} ${params.REBUILD_IMAGE ? 'Image: Rebuilt with updated design ✨' : 'Image: Using existing'} Namespace: ${NAMESPACE} The application has been rolled back successfully! 🔄 Check: https://demo-nginx.thedevops.dev """ } } } failure { echo """ ❌ ROLLBACK FAILED! Please check the logs and try again. Manual rollback: kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE} """ } always { sh """ docker rmi ${env.TARGET_IMAGE} 2>/dev/null || true """ cleanWs() } } }