demo-app/Jenkinsfile

253 lines
9.5 KiB
Plaintext
Raw Normal View History

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
"
'''
}
}
2026-09-16 13:25:35 +00:00
stage('Code quality (SonarQube)') {
when { expression { env.IS_ROLLBACK != 'true' } }
steps {
sh '''
set +x
ST=$(cat /var/jenkins_secrets/sonar_token)
HOST_WS=$(echo "$PWD" | sed "s|^${JENKINS_HOME_CONTAINER}|${JENKINS_HOME_HOST}|")
docker run --rm \
--network devops \
-e SONAR_HOST_URL=http://sonarqube:9000 \
-e SONAR_TOKEN="$ST" \
-v "$HOST_WS":/usr/src \
sonarsource/sonar-scanner-cli:11 \
-Dsonar.projectKey=swim-demo-app
'''
}
}
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 STAGING') {
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 -e target=staging
'''
}
}
stage('Verify STAGING') {
steps {
sh '''
SVM=$(grep "^staging-vm" deploy/inventory.ini | sed "s/.*ansible_host=//" | tr -d " ")
GOT=$(curl -sf --max-time 10 http://${SVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)[\'version\'])")
echo " staging expected: ${IMAGE_TAG} | live: ${GOT}"
[ "$GOT" = "${IMAGE_TAG}" ] || { echo "STAGING MISMATCH"; exit 1; }
echo " STAGING VERIFIED"
'''
}
}
stage('Production approval') {
steps {
script {
// Enterprise gate: insaan ki manzoori ke bina production par kuch nahi.
// 15 min me jawab na aaye to build abort - production safe rehta hai.
// Simple proceed/abort input. Parameters wale input ko
// API se submit karna jhanjhat hai; UI ke liye bhi ye saaf hai.
timeout(time: 15, unit: 'MINUTES') {
input(message: "Staging par ${env.IMAGE_TAG} verified hai. PRODUCTION par deploy karein?",
ok: 'Deploy to Production')
}
echo "Production deploy approve ho gaya"
}
}
}
stage('Deploy to PRODUCTION') {
steps {
sh '''
set +x
. ./.vault-registry.env
export REG_USER REG_PASS
export ANSIBLE_HOST_KEY_CHECKING=False
export IMAGE_TAG=${IMAGE_TAG}
ansible-playbook -i deploy/inventory.ini deploy/deploy.yml -e target=production
'''
}
}
stage('Verify PRODUCTION') {
steps {
sh '''
PVM=$(grep "^app-vm" deploy/inventory.ini | sed "s/.*ansible_host=//" | tr -d " ")
GOT=$(curl -sf --max-time 10 http://${PVM}:8000/version | python3 -c "import sys,json;print(json.load(sys.stdin)[\'version\'])")
echo " production expected: ${IMAGE_TAG} | live: ${GOT}"
[ "$GOT" = "${IMAGE_TAG}" ] || { echo "PROD MISMATCH"; exit 1; }
echo " PRODUCTION 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" }
}
}