pipeline { agent any parameters { string(name: 'ROLLBACK_TAG', defaultValue: '', description: 'Khaali = normal build. Purana build number (jaise 2) = seedha us image par rollback, bina rebuild ke') } environment { REGISTRY = '51.195.4.170:5000' IMAGE_NAME = 'swim/demo-app' VAULT_ADDR = 'http://vault:8200' // Jenkins container ka workspace /var/jenkins_home/... hai, par docker // daemon HOST par chalta hai jahan wahi folder /opt/devops/data/jenkins/... // par hai. `docker run -v` ko HOST ka path chahiye. JENKINS_HOME_CONTAINER = '/var/jenkins_home' JENKINS_HOME_HOST = '/opt/devops/data/jenkins' TRIVY_SEVERITY = 'HIGH,CRITICAL' } // Trigger ko YAHAN declare karna zaroori hai. Agar sirf job config.xml me ho // aur Jenkinsfile me na ho, to DeclarativeJobPropertyTrackerAction NPE deta hai. // Khaali spec = polling band, sirf webhook (notifyCommit) se chalega. triggers { pollSCM('') } options { timestamps() buildDiscarder(logRotator(numToKeepStr: '20')) timeout(time: 30, unit: 'MINUTES') disableConcurrentBuilds() } stages { stage('Init') { steps { script { // params ko environment block me mat chhuo - wahan null hone par // NPE aata hai. Yahan script block me safe hai. def rb = (params.ROLLBACK_TAG ?: '').trim() env.IS_ROLLBACK = rb ? 'true' : 'false' env.IMAGE_TAG = rb ? rb : "${env.BUILD_NUMBER}" echo "MODE : ${env.IS_ROLLBACK == 'true' ? 'ROLLBACK' : 'NORMAL BUILD'}" echo "IMAGE_TAG : ${env.IMAGE_TAG}" } } } stage('Checkout') { steps { checkout scm sh 'echo "commit: $(git rev-parse --short HEAD)"' } } stage('Secrets from Vault') { steps { sh ''' set +x VT=$(cat /var/jenkins_secrets/vault_token) # Vault se registry ka password lo - Jenkins credential se NAHI curl -sf -H "X-Vault-Token: $VT" \ "${VAULT_ADDR}/v1/swim/data/registry" \ | python3 -c " import sys, json d = json.load(sys.stdin)['data']['data'] open('.vault-registry.env','w').write( 'REG_USER=%s\\nREG_PASS=%s\\n' % (d['username'], d['password'])) print(' Vault se registry creds mil gaye (user=%s)' % d['username']) " chmod 600 .vault-registry.env ''' } } stage('Test') { when { expression { env.IS_ROLLBACK != 'true' } } steps { sh ''' HOST_WS=$(echo "$PWD" | sed "s|^${JENKINS_HOME_CONTAINER}|${JENKINS_HOME_HOST}|") docker run --rm -v "$HOST_WS":/src -w /src python:3.12-slim sh -c " pip install --quiet -r requirements-dev.txt && python -m pytest tests/ -v " ''' } } stage('Build image') { when { expression { env.IS_ROLLBACK != 'true' } } steps { sh ''' docker build \ --build-arg APP_VERSION=${IMAGE_TAG} \ -t ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} \ -t ${REGISTRY}/${IMAGE_NAME}:latest . ''' } } stage('Security scan (Trivy)') { when { expression { env.IS_ROLLBACK != 'true' } } steps { sh ''' # Trivy ko docker socket chahiye taaki local image scan kar sake. # Cache volume se dobara download nahi hoga. docker run --rm \ -v /var/run/docker.sock:/var/run/docker.sock \ -v trivy-cache:/root/.cache/ \ aquasec/trivy:0.58.1 image \ --severity ${TRIVY_SEVERITY} \ --ignore-unfixed \ --exit-code 0 \ --format table \ ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} ''' } } stage('Smoke test image') { when { expression { env.IS_ROLLBACK != 'true' } } steps { sh ''' CID=$(docker run -d -P ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG}) sleep 5 docker exec $CID python -c " import urllib.request, json r = json.loads(urllib.request.urlopen('http://127.0.0.1:8000/version').read()) print('version endpoint:', r) assert r['version'] == '${IMAGE_TAG}', 'version galat!' " docker rm -f $CID ''' } } stage('Push to registry') { when { expression { env.IS_ROLLBACK != 'true' } } steps { sh ''' set +x . ./.vault-registry.env echo "$REG_PASS" | docker login ${REGISTRY} -u "$REG_USER" --password-stdin docker push ${REGISTRY}/${IMAGE_NAME}:${IMAGE_TAG} docker push ${REGISTRY}/${IMAGE_NAME}:latest docker logout ${REGISTRY} ''' } } stage('Deploy to VM') { steps { sh ''' set +x . ./.vault-registry.env export REG_USER REG_PASS export ANSIBLE_HOST_KEY_CHECKING=False export IMAGE_TAG=${IMAGE_TAG} ansible-galaxy collection install community.docker --force -q || true ansible-playbook -i deploy/inventory.ini deploy/deploy.yml ''' } } stage('Post-deploy verify') { steps { sh ''' APPVM=$(awk -F= "/ansible_host/{print \\$2}" deploy/inventory.ini | tr -d " ") GOT=$(curl -sf --max-time 10 http://${APPVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)['version'])") echo " expected: ${IMAGE_TAG} | live app: ${GOT}" [ "$GOT" = "${IMAGE_TAG}" ] || { echo "VERSION MISMATCH - deploy fail"; exit 1; } echo " DEPLOY VERIFIED" ''' } } } post { always { script { if (env.WORKSPACE) { sh 'rm -f .vault-registry.env || true' sh 'docker image prune -f --filter "until=24h" || true' cleanWs() } } } success { echo "BUILD ${env.BUILD_NUMBER} SAFAL -> ${env.REGISTRY}/${env.IMAGE_NAME}:${env.IMAGE_TAG}" } failure { echo "BUILD ${env.BUILD_NUMBER} FAIL" } } }