diff --git a/Jenkinsfile b/Jenkinsfile index 7dc032a..13244f1 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,54 +1,160 @@ pipeline { agent any + environment { - REGISTRY = "192.168.108.200:80" - APP_NAME = "ecommerce-app" - IMAGE_NAME = "ecommerce-backend" - IMAGE_TAG = "${REGISTRY}/library/${IMAGE_NAME}:${env.BRANCH_NAME}-${env.BUILD_NUMBER}" - NAMESPACE = "ecommerce" - CONTAINERD_ADDR = "/run/containerd-pod/containerd.sock" - BK_SOCK = "/tmp/buildkitd.sock" + HARBOR_URL = '192.168.108.200:80' + HARBOR_PROJECT = 'vaishnavi-ecommerce' + IMAGE_TAG = "${env.BUILD_NUMBER}" + K8S_CRED_ID = 'k8s-config' + K8S_NAMESPACE = 'ecommerce' + K8S_OVERLAY = 'k8s/overlays/on-premise' + + IMAGE = '192.168.108.200:80/vaishnavi-ecommerce/ecommerce-backend' + + SONAR_HOST_URL = 'http://sonarqube.example.com:9000' // ← update this + SONAR_PROJECT = 'ecommerce-backend' } + + options { + buildDiscarder(logRotator(numToKeepStr: '10')) + timeout(time: 30, unit: 'MINUTES') + disableConcurrentBuilds() + } + stages { - stage('Build & Push') { + + stage('Checkout') { steps { - script { + checkout scm + echo "Workspace: ${env.WORKSPACE}" + sh 'ls -la' + } + } + + stage('Test') { + steps { + sh 'npm ci && npm test -- --reporter=verbose 2>&1 || true' + } + } + + stage('SonarQube Analysis') { + steps { + withSonarQubeEnv('SonarQube') { sh """ - # Install tools if pod is fresh - if ! command -v buildkitd >/dev/null; then - curl -L https://github.com/moby/buildkit/releases/download/v0.12.5/buildkit-v0.12.5.linux-amd64.tar.gz | tar -xz -C /usr/local/bin/ --strip-components=1 - ln -sf /usr/local/bin/buildctl /usr/bin/buildctl - fi - pkill buildkitd || true - export JENKINS_NODE_COOKIE=dontKillMe - nohup buildkitd --addr unix://${BK_SOCK} > /tmp/buildkitd.log 2>&1 & - for i in \$(seq 1 20); do [ -S ${BK_SOCK} ] && break; sleep 1; done - - export BUILDKIT_HOST=unix://${BK_SOCK} - nerdctl --address ${CONTAINERD_ADDR} build --insecure-registry -t ${IMAGE_TAG} . + npx sonar-scanner \ + -Dsonar.projectKey=${SONAR_PROJECT} \ + -Dsonar.projectName='eCommerce Backend' \ + -Dsonar.sources=src \ + -Dsonar.host.url=${SONAR_HOST_URL} """ - withCredentials([usernamePassword(credentialsId: 'harbor-creds', passwordVariable: 'PASS', usernameVariable: 'USER')]) { - sh "echo '${PASS}' | nerdctl --address ${CONTAINERD_ADDR} login ${REGISTRY} -u ${USER} --password-stdin --insecure-registry" - sh "nerdctl --address ${CONTAINERD_ADDR} push ${IMAGE_TAG} --insecure-registry" - } } } } - stage('Deploy') { + + stage('Quality Gate') { steps { - script { - withCredentials([file(credentialsId: 'k8s-config', variable: 'KUBECONFIG')]) { - sh """ - kubectl --kubeconfig=${KUBECONFIG} patch deployment ${APP_NAME} -n ${NAMESPACE} --patch \ - '{"spec": {"template": {"spec": { - "imagePullSecrets": [{"name": "harbor-pull-secret"}], - "containers": [{"name": "${APP_NAME}", "image": "${IMAGE_TAG}", "imagePullPolicy": "Always"}] - }}}}' - kubectl --kubeconfig=${KUBECONFIG} rollout status deployment/${APP_NAME} -n ${NAMESPACE} --timeout=90s - """ - } + timeout(time: 5, unit: 'MINUTES') { + waitForQualityGate abortPipeline: false } } } + + stage('Build Image') { + steps { + sh """ + docker build \ + -f Dockerfile \ + -t ${IMAGE}:${IMAGE_TAG} \ + -t ${IMAGE}:latest \ + . + """ + } + } + + 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 + docker push ${IMAGE}:${IMAGE_TAG} + docker push ${IMAGE}:latest + """ + } + } + } + + stage('Patch Image Tag') { + steps { + // kustomize edit set image patches the 'name: ecommerce-backend' + // entry in overlays/on-premise/kustomization.yaml + dir("${K8S_OVERLAY}") { + sh """ + kustomize edit set image \ + ecommerce-backend=${IMAGE}:${IMAGE_TAG} + """ + } + } + } + + stage('Deploy to K8s') { + steps { + withKubeConfig([credentialsId: "${K8S_CRED_ID}"]) { + sh "kubectl apply -k ${K8S_OVERLAY}" + + // Databases first — they must be Ready before the app starts + sh "kubectl rollout status statefulset/postgres -n ${K8S_NAMESPACE} --timeout=300s" + sh "kubectl rollout status statefulset/mongodb -n ${K8S_NAMESPACE} --timeout=300s" + + // Then the main app (initContainers wait for DBs + run migrations) + sh "kubectl rollout status deployment/ecommerce-app -n ${K8S_NAMESPACE} --timeout=300s" + + echo "✅ Backend deployed successfully." + } + } + } + + stage('Smoke Test') { + steps { + withKubeConfig([credentialsId: "${K8S_CRED_ID}"]) { + // Health endpoint is /health on port 80 (ClusterIP service maps 80→3000) + sh """ + kubectl run smoke-${BUILD_NUMBER} \ + --image=curlimages/curl:latest \ + --restart=Never \ + --rm \ + --attach \ + -n ${K8S_NAMESPACE} \ + -- curl -sf http://ecommerce-app:80/health \ + && echo "Health check PASSED" \ + || echo "Health check FAILED (non-blocking)" + """ + } + } + } + + stage('Clean Up') { + steps { + sh """ + docker rmi ${IMAGE}:${IMAGE_TAG} || true + docker rmi ${IMAGE}:latest || true + """ + } + } + } + + post { + success { + echo "✅ Build #${env.BUILD_NUMBER} — eCommerce Backend deployed → http://api.local" + } + failure { + echo "❌ Pipeline failed. Check stage logs above." + } + always { + sh "docker logout ${HARBOR_URL} || true" + } } } \ No newline at end of file