// Jenkinsfile for Manual Rollback // This allows rolling back to any previous version 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: false, description: 'Skip health checks (use with caution!)' ) booleanParam( name: 'DRY_RUN', defaultValue: false, description: 'Dry run - show what would happen without applying' ) } 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' } 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} 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 "=== Container Name ===" kubectl get deployment ${APP_NAME} -n ${NAMESPACE} \ -o jsonpath='{.spec.template.spec.containers[0].name}' 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}" sh """ echo "Target image: ${env.TARGET_IMAGE}" """ } else if (params.ROLLBACK_METHOD == 'REVISION_NUMBER') { env.REVISION = env.TARGET_VERSION_CLEAN sh """ echo "Rolling back to revision: ${env.REVISION}" # Verify revision exists 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('Execute Rollback') { when { expression { !params.DRY_RUN } } steps { script { echo "🚀 Executing rollback..." if (params.ROLLBACK_METHOD == 'IMAGE_TAG') { // Method 1: Update image directly using correct container name 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') { // Method 2: Rollback to specific revision sh """ kubectl rollout undo deployment/${APP_NAME} \ -n ${NAMESPACE} \ --to-revision=${env.REVISION} """ } else if (params.ROLLBACK_METHOD == 'GIT_COMMIT') { // Method 3: Checkout specific 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" # Get image from specific commit 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 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} """ // Wait a bit longer for pods to be fully ready echo "⏳ Waiting for new pods to stabilize..." sleep 10 echo "✅ Rollout completed" } } } stage('Health Check') { when { expression { !params.DRY_RUN && !params.SKIP_HEALTH_CHECK } } steps { script { echo "🏥 Running health checks..." sh """#!/bin/bash set -e # Check all pods are 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 version on running pods DEPLOYED_IMAGE=\$(kubectl get deployment ${APP_NAME} -n ${NAMESPACE} -o jsonpath='{.spec.template.spec.containers[0].image}') echo "Deployed image: \${DEPLOYED_IMAGE}" # Get a RUNNING pod (not terminating) POD_NAME=\$(kubectl get pods -n ${NAMESPACE} -l app=${APP_NAME} --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}') echo "Testing pod: \${POD_NAME}" # Test health endpoint with retry for i in 1 2 3 4 5; do echo "Health check attempt \$i/5..." if kubectl exec \${POD_NAME} -n ${NAMESPACE} -- wget -q -O- http://localhost/health 2>/dev/null; then echo "✅ Health check passed!" exit 0 fi if [ \$i -lt 5 ]; then echo "Retrying in 5 seconds..." sleep 5 fi done echo "❌ Health check failed after 5 attempts" exit 1 """ echo "✅ Health checks passed" } } } 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} echo "" echo "=== Updated Rollout History ===" kubectl rollout history deployment/${APP_NAME} -n ${NAMESPACE} """ } } } 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} Steps that would be executed: 1. Update deployment to target version 2. Update Git manifests 3. Wait for rollout (timeout: ${HEALTH_CHECK_TIMEOUT}) ${params.SKIP_HEALTH_CHECK ? '4. (Health check skipped)' : '4. Run health checks with retry'} 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} Container: ${CONTAINER_NAME} Method: ${params.ROLLBACK_METHOD} Target: ${env.TARGET_VERSION_CLEAN} Namespace: ${NAMESPACE} The application has been rolled back successfully! ✨ """ } } } failure { echo """ ❌ ROLLBACK FAILED! Please check the logs and try again. Manual rollback commands: kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE} Or set image directly: kubectl set image deployment/${APP_NAME} ${CONTAINER_NAME}= -n ${NAMESPACE} """ } } }