diff --git a/Jenkinsfile b/Jenkinsfile index 745cb6c..7184a15 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,269 +1,7 @@ -pipeline { - // Koi default agent nahi. Har stage khud batati hai kahan chalegi - - // CI builder VM par, CD controller par. Isse build ko production ki - // chaabi kabhi nahi milti. - agent none +// Poori pipeline devops-platform library me hai. Yahan sirf module ka naam. +// Naya module banaoge to bas yehi 3 lines copy karni hain. +@Library('devops-platform') _ - 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 = 'ns31240276.ip-51-195-4.eu:5000' - IMAGE_NAME = 'swim/demo-app' - // Sab jagah se ek hi naam. Controller isse proxy par bhejta hai - // (extra_hosts), builder VM bridge IP par (/etc/hosts). TLS cert - // bhi isi naam ka hai, isliye andar ke DNS naam use nahi karte. - VAULT_ADDR = 'https://ns31240276.ip-51-195-4.eu:8444' - SONAR_URL = 'https://ns31240276.ip-51-195-4.eu:8446' - HOST_BRIDGE_IP = '192.168.124.1' - TRIVY_SEVERITY = 'HIGH,CRITICAL' - } - - // Trigger ko YAHAN declare karna zaroori hai. Sirf config.xml me hone par - // DeclarativeJobPropertyTrackerAction NPE deta hai. - triggers { - pollSCM('') - } - - options { - timestamps() - buildDiscarder(logRotator(numToKeepStr: '20')) - timeout(time: 40, unit: 'MINUTES') - disableConcurrentBuilds() - } - - stages { - - // ============ CI: sab kuch builder VM par ============ - // Yahan docker daemon builder VM ka hai. Build escape hui to attacker ko - // sirf ye VM milti hai - na secrets, na Vault, na production ki key. - stage('CI (builder-vm-01)') { - agent { label 'builder' } - - stages { - stage('Init') { - steps { - script { - // params ko environment block me mat chhuo - wahan null hone par NPE. - def rb = (params.ROLLBACK_TAG ?: '').trim() - env.IS_ROLLBACK = rb ? 'true' : 'false' - env.IMAGE_TAG = rb ? rb : "${env.BUILD_NUMBER}" - echo "NODE : ${env.NODE_NAME}" - 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 { - // Token ab file se nahi, Jenkins credential se aata hai aur - // log me masked rehta hai. Ye token sirf swim/registry padh - // sakta hai - baaki Vault iske liye band hai. - withCredentials([string(credentialsId: 'vault-builder-token', variable: 'VAULT_TOKEN')]) { - sh ''' - set +x - curl -sf -H "X-Vault-Token: $VAULT_TOKEN" \ - "${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 ''' - docker run --rm -v "$PWD":/src -w /src python:3.12-slim sh -c " - pip install --quiet -r requirements-dev.txt && - python -m pytest tests/ -v - " - ''' - } - } - - stage('Code quality (SonarQube)') { - when { expression { env.IS_ROLLBACK != 'true' } } - steps { - withCredentials([string(credentialsId: 'sonar-token', variable: 'SONAR_TOKEN')]) { - sh ''' - set +x - docker run --rm \ - --add-host "ns31240276.ip-51-195-4.eu:${HOST_BRIDGE_IP}" \ - -e SONAR_HOST_URL="${SONAR_URL}" \ - -e SONAR_TOKEN="$SONAR_TOKEN" \ - -v "$PWD":/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 ''' - # GATE hai, report nahi: HIGH/CRITICAL mila to build yahin fail hogi. - # Jo CVE business ne accept kiye hain wo .trivyignore me hain. - docker run --rm \ - -v /var/run/docker.sock:/var/run/docker.sock \ - -v trivy-cache:/root/.cache/ \ - -v "$PWD/.trivyignore":/.trivyignore:ro \ - aquasec/trivy:0.58.1 image \ - --severity ${TRIVY_SEVERITY} \ - --ignore-unfixed \ - --ignorefile /.trivyignore \ - --exit-code 1 \ - --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}) - trap "docker rm -f $CID >/dev/null 2>&1" EXIT - 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!' -" - ''' - } - } - - 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} - ''' - } - } - } - - post { - always { - sh 'rm -f .vault-registry.env || true' - sh 'docker image prune -f --filter "until=24h" || true' - cleanWs() - } - } - } - - // ============ CD: controller par ============ - // Deploy key sirf yahan aati hai. Builder VM ise kabhi nahi dekhti. - stage('Deploy STAGING') { - agent { label 'built-in' } - steps { - checkout scm - withCredentials([ - sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'DEPLOY_KEY', usernameVariable: 'DEPLOY_USER'), - usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS') - ]) { - sh ''' - set +x - export ANSIBLE_HOST_KEY_CHECKING=False - ansible-galaxy collection install community.docker --force -q || true - ansible-playbook -i deploy/inventory.ini deploy/deploy.yml \ - -u "$DEPLOY_USER" --private-key "$DEPLOY_KEY" \ - -e target=staging - ''' - } - 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') { - // agent none = intezaar ke 15 minute me koi executor block nahi hota - agent none - steps { - script { - 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 PRODUCTION') { - agent { label 'built-in' } - steps { - checkout scm - withCredentials([ - sshUserPrivateKey(credentialsId: 'deploy-key', keyFileVariable: 'DEPLOY_KEY', usernameVariable: 'DEPLOY_USER'), - usernamePassword(credentialsId: 'registry-creds', usernameVariable: 'REG_USER', passwordVariable: 'REG_PASS') - ]) { - sh ''' - set +x - export ANSIBLE_HOST_KEY_CHECKING=False - ansible-playbook -i deploy/inventory.ini deploy/deploy.yml \ - -u "$DEPLOY_USER" --private-key "$DEPLOY_KEY" \ - -e target=production - ''' - } - 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 { cleanWs() } } - } - } - - post { - success { echo "BUILD ${env.BUILD_NUMBER} SAFAL -> ${env.REGISTRY}/${env.IMAGE_NAME}:${env.IMAGE_TAG}" } - failure { echo "BUILD ${env.BUILD_NUMBER} FAIL" } - } -} +devopsPipeline( + module: 'demo-app' +)