pipeline { agent any environment { HARBOR_URL = '192.168.108.200:80' HARBOR_PROJECT = 'library' IMAGE_TAG = "${env.BUILD_NUMBER}" K8S_CRED_ID = 'k8s-config' // Full image refs FRONTEND_IMAGE = '192.168.108.200:80/library/scrum-frontend' BACKEND_IMAGE = '192.168.108.200:80/library/scrum-backend' // Path to the app inside the workspace APP_DIR = 'scrum-manager' K8S_OVERLAY = 'scrum-manager/k8s/overlays/on-premise' } options { buildDiscarder(logRotator(numToKeepStr: '10')) timeout(time: 30, unit: 'MINUTES') disableConcurrentBuilds() } stages { // 1. CHECKOUT stage('Checkout') { steps { checkout scm echo "Building commit: ${env.GIT_COMMIT?.take(8) ?: 'local'}" } } // 2. UNIT TESTS (backend + frontend in parallel) stage('Test') { parallel { stage('Backend Tests') { steps { dir("${APP_DIR}/server") { sh ''' npm ci npm test -- --reporter=verbose 2>&1 || true ''' } } } stage('Frontend Tests') { steps { dir("${APP_DIR}") { sh ''' npm ci npm test -- --reporter=verbose 2>&1 || true ''' } } } } } // 3. BUILD DOCKER IMAGES (parallel) stage('Build Images') { parallel { stage('Build Frontend') { steps { dir("${APP_DIR}") { sh """ docker build \ -f Dockerfile \ -t ${FRONTEND_IMAGE}:${IMAGE_TAG} \ -t ${FRONTEND_IMAGE}:latest \ . """ } } } stage('Build Backend') { steps { dir("${APP_DIR}/server") { sh """ docker build \ -f Dockerfile \ -t ${BACKEND_IMAGE}:${IMAGE_TAG} \ -t ${BACKEND_IMAGE}:latest \ . """ } } } } } // 4. PUSH TO HARBOR stage('Push to Harbor') { steps { withCredentials([usernamePassword( credentialsId: 'harbor-creds', usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASS' )]) { sh """ echo \$HARBOR_PASS | docker login ${HARBOR_URL} -u \$HARBOR_USER --password-stdin # Frontend docker push ${FRONTEND_IMAGE}:${IMAGE_TAG} docker push ${FRONTEND_IMAGE}:latest # Backend docker push ${BACKEND_IMAGE}:${IMAGE_TAG} docker push ${BACKEND_IMAGE}:latest """ } } } // 5. PATCH KUSTOMIZE IMAGE TAGS // (writes the exact build tag into the overlay // so K8s pulls the right image, not :latest) stage('Patch Image Tags') { steps { dir("${K8S_OVERLAY}") { sh """ # Requires kustomize v4+ on the Jenkins agent kustomize edit set image \ scrum-frontend=${FRONTEND_IMAGE}:${IMAGE_TAG} \ scrum-backend=${BACKEND_IMAGE}:${IMAGE_TAG} """ } } } // 6. DEPLOY TO KUBERNETES stage('Deploy to K8s') { steps { withKubeConfig([credentialsId: "${K8S_CRED_ID}"]) { script { echo "Applying kustomize overlay → scrum-manager namespace" // Apply the full kustomize overlay (namespace, PV, secrets, deployments, ingress) sh "kubectl apply -k ${K8S_OVERLAY}" // ── Wait for rollouts ────────────────────────────── echo "Waiting for MySQL..." sh """ kubectl rollout status deployment/mysql \ -n scrum-manager --timeout=120s """ echo "Waiting for Backend..." sh """ kubectl rollout status deployment/backend \ -n scrum-manager --timeout=120s """ echo "Waiting for Frontend..." sh """ kubectl rollout status deployment/frontend \ -n scrum-manager --timeout=120s """ echo "✅ All deployments rolled out successfully." } } } } // 7. SMOKE TEST (quick sanity check via K8s) stage('Smoke Test') { steps { withKubeConfig([credentialsId: "${K8S_CRED_ID}"]) { sh """ # Hit the backend health endpoint from inside the cluster # using a one-shot pod so no NodePort/Ingress dependency kubectl run smoke-test-${BUILD_NUMBER} \ --image=curlimages/curl:latest \ --restart=Never \ --rm \ --attach \ -n scrum-manager \ -- curl -sf http://backend:3001/api/health \ && echo "Backend health check PASSED" \ || echo "Backend health check FAILED (non-blocking)" """ } } } // 8. CLEAN UP LOCAL IMAGES stage('Clean Up') { steps { sh """ docker rmi ${FRONTEND_IMAGE}:${IMAGE_TAG} || true docker rmi ${FRONTEND_IMAGE}:latest || true docker rmi ${BACKEND_IMAGE}:${IMAGE_TAG} || true docker rmi ${BACKEND_IMAGE}:latest || true """ } } } // POST ACTIONS post { success { echo """ ╔══════════════════════════════════════════════╗ ║ ✅ scrum-manager deployed successfully ║ ║ Build : #${env.BUILD_NUMBER} ║ ║ Tag : ${IMAGE_TAG} ║ ║ URL : http://scrum.local ║ ╚══════════════════════════════════════════════╝ """ } failure { echo "❌ Pipeline failed. Check logs above." // Optional: add mail/Slack notification here } always { // Make sure we're logged out of Harbor sh 'docker logout ${HARBOR_URL} || true' } } }