diff --git a/apps/demo-nginx/Jenkinsfile.rollback b/apps/demo-nginx/Jenkinsfile.rollback index 9b8c0b4..5205147 100644 --- a/apps/demo-nginx/Jenkinsfile.rollback +++ b/apps/demo-nginx/Jenkinsfile.rollback @@ -1,5 +1,6 @@ // Jenkinsfile for Manual Rollback // This allows rolling back to any previous version +// Now rebuilds image with updated design! pipeline { agent any @@ -22,14 +23,19 @@ pipeline { ) booleanParam( name: 'SKIP_HEALTH_CHECK', - defaultValue: false, - description: 'Skip health checks (use with caution!)' + 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 { @@ -40,6 +46,7 @@ pipeline { DOCKER_REPO = 'vladcrypto' GITEA_URL = 'http://gitea-http.gitea.svc.cluster.local:3000' HEALTH_CHECK_TIMEOUT = '300s' + ARGOCD_SYNC_TIMEOUT = '120' } stages { @@ -59,6 +66,7 @@ pipeline { 📋 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} """ @@ -81,18 +89,12 @@ pipeline { -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 ===" + echo "=== Rollout History ===\" kubectl rollout history deployment/${APP_NAME} -n ${NAMESPACE} """ } @@ -107,8 +109,12 @@ pipeline { 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') { @@ -116,21 +122,172 @@ pipeline { 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('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 } @@ -140,7 +297,7 @@ pipeline { echo "🚀 Executing rollback..." if (params.ROLLBACK_METHOD == 'IMAGE_TAG') { - // Method 1: Update image directly using correct container name + // Update deployment sh """ echo "Setting image to: ${env.TARGET_IMAGE}" kubectl set image deployment/${APP_NAME} \ @@ -170,7 +327,6 @@ pipeline { } } else if (params.ROLLBACK_METHOD == 'REVISION_NUMBER') { - // Method 2: Rollback to specific revision sh """ kubectl rollout undo deployment/${APP_NAME} \ -n ${NAMESPACE} \ @@ -178,7 +334,6 @@ pipeline { """ } else if (params.ROLLBACK_METHOD == 'GIT_COMMIT') { - // Method 3: Checkout specific git commit withCredentials([usernamePassword( credentialsId: 'gitea-credentials', usernameVariable: 'GIT_USER', @@ -191,7 +346,6 @@ pipeline { 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}') @@ -211,6 +365,61 @@ pipeline { } } + 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 } @@ -225,27 +434,23 @@ pipeline { --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') { + stage('Verify Deployment') { when { - expression { !params.DRY_RUN && !params.SKIP_HEALTH_CHECK } + expression { !params.DRY_RUN } } steps { script { - echo "🏥 Running health checks..." + echo "✅ Verifying deployment..." sh """#!/bin/bash set -e - # Check all pods are ready + # 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}') @@ -256,32 +461,12 @@ pipeline { exit 1 fi - # Verify image version on running pods + # Verify image 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 "✅ Deployment verified!" """ - - echo "✅ Health checks passed" } } } @@ -307,10 +492,6 @@ pipeline { 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} """ } } @@ -329,12 +510,15 @@ pipeline { Method: ${params.ROLLBACK_METHOD} Target: ${env.TARGET_VERSION_CLEAN} + Rebuild Image: ${params.REBUILD_IMAGE} - 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'} + 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. """ @@ -353,12 +537,14 @@ pipeline { ✅ ROLLBACK SUCCESSFUL! Application: ${APP_NAME} - Container: ${CONTAINER_NAME} Method: ${params.ROLLBACK_METHOD} - Target: ${env.TARGET_VERSION_CLEAN} + 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! ✨ + The application has been rolled back successfully! 🔄 + + Check: https://demo-nginx.thedevops.dev """ } } @@ -370,12 +556,16 @@ pipeline { Please check the logs and try again. - Manual rollback commands: + Manual rollback: kubectl rollout undo deployment/${APP_NAME} -n ${NAMESPACE} - - Or set image directly: - kubectl set image deployment/${APP_NAME} ${CONTAINER_NAME}= -n ${NAMESPACE} """ } + + always { + sh """ + docker rmi ${env.TARGET_IMAGE} 2>/dev/null || true + """ + cleanWs() + } } }