image tag me git SHA: multibranch build-number reset se tag reuse ki problem khatam
This commit is contained in:
parent
5559064dd8
commit
cc2549863d
@ -1,20 +1,19 @@
|
||||
// Ek hi pipeline, har module ke liye. Module ki Jenkinsfile sirf itni hoti hai:
|
||||
// Ek hi pipeline, har module ke liye. Module ki Jenkinsfile sirf itni:
|
||||
//
|
||||
// @Library('devops-platform') _
|
||||
// devopsPipeline(module: 'demo-app')
|
||||
//
|
||||
// Poora raasta: secret scan -> test -> SAST -> build -> IaC scan -> CVE gate ->
|
||||
// SBOM -> smoke -> push -> sign -> verify -> staging -> approval -> production.
|
||||
// Raasta: secret scan -> test -> SAST -> IaC scan -> build -> CVE gate -> SBOM
|
||||
// -> smoke -> push -> sign -> verify -> GitOps promote -> VM deploy.
|
||||
//
|
||||
// CI builder VM par chalta hai (wahan deploy key nahi hai),
|
||||
// CD controller par (wahan docker.sock nahi hai). Dono taraf blast radius chhota.
|
||||
// CD + signing controller par (wahan docker.sock nahi hai).
|
||||
|
||||
def call(Map cfg = [:]) {
|
||||
|
||||
def P = platformConfig()
|
||||
if (!cfg.module) { error "devopsPipeline: 'module' dena zaroori hai" }
|
||||
|
||||
// ---- defaults: module kuch na de to sensible value ----
|
||||
cfg.imageName = cfg.imageName ?: "swim/${cfg.module}"
|
||||
cfg.vaultMount = cfg.vaultMount ?: 'swim'
|
||||
cfg.testImage = cfg.testImage ?: 'python:3.12-slim'
|
||||
@ -28,18 +27,16 @@ def call(Map cfg = [:]) {
|
||||
cfg.runSonar = cfg.containsKey('runSonar') ? cfg.runSonar : true
|
||||
cfg.runDeploy = cfg.containsKey('runDeploy') ? cfg.runDeploy : true
|
||||
cfg.approval = cfg.containsKey('approval') ? cfg.approval : true
|
||||
// GitOps lane: k8s cluster me deploy ArgoCD karta hai, Jenkins sirf tag commit karta hai.
|
||||
cfg.gitops = cfg.containsKey('gitops') ? cfg.gitops : false
|
||||
cfg.deployVM = cfg.containsKey('deployVM') ? cfg.deployVM : true
|
||||
cfg.gitopsRepo = cfg.gitopsRepo ?: 'https://ns31240276.ip-51-195-4.eu/devops/gitops.git'
|
||||
cfg.gitopsPath = cfg.gitopsPath ?: "manifests/${cfg.module}"
|
||||
cfg.k8sVerifyUrl = cfg.k8sVerifyUrl ?: ''
|
||||
// VM lane: ansible se app-vm/staging-vm par deploy.
|
||||
cfg.deployVM = cfg.containsKey('deployVM') ? cfg.deployVM : true
|
||||
|
||||
properties([
|
||||
parameters([
|
||||
string(name: 'ROLLBACK_TAG', defaultValue: '',
|
||||
description: 'Khaali = normal build. Purana tag (jaise 7) = bina rebuild seedha us image par rollback.')
|
||||
description: 'Khaali = normal build. Purana image tag = bina rebuild seedha us image par rollback.')
|
||||
]),
|
||||
buildDiscarder(logRotator(numToKeepStr: '30')),
|
||||
disableConcurrentBuilds()
|
||||
@ -48,64 +45,51 @@ def call(Map cfg = [:]) {
|
||||
def branch = env.BRANCH_NAME ?: 'main'
|
||||
def isPR = branch.startsWith('PR-')
|
||||
def isMain = (branch == 'main' || branch == 'master')
|
||||
// PR par sirf CI. Production sirf main/master se jaata hai.
|
||||
// PR aur feature branch par sirf CI gates. Production sirf main/master se.
|
||||
def deployable = cfg.runDeploy && !isPR && isMain
|
||||
|
||||
def safeBranch = branch.replaceAll('[^A-Za-z0-9._-]', '-').toLowerCase()
|
||||
def rollbackTag = (env.ROLLBACK_TAG ?: '').trim()
|
||||
def isRollback = rollbackTag != ''
|
||||
// Multibranch me HAR branch ka build number 1 se shuru hota hai. Agar tag me
|
||||
// sirf build number rakha to main#1 aur feature#1 ek hi image tag bana ke
|
||||
// ek doosre ko overwrite kar denge. Isliye non-main branch ka naam tag me.
|
||||
def safeBranch = branch.replaceAll('[^A-Za-z0-9._-]', '-').toLowerCase()
|
||||
def imageTag = isRollback ? rollbackTag
|
||||
: (isMain ? "${env.BUILD_NUMBER}"
|
||||
: "${safeBranch}-${env.BUILD_NUMBER}")
|
||||
def fullImage = "${P.registry}/${cfg.imageName}:${imageTag}"
|
||||
|
||||
def commonEnv = [
|
||||
"MODULE=${cfg.module}",
|
||||
"REGISTRY=${P.registry}",
|
||||
"IMAGE_NAME=${cfg.imageName}",
|
||||
"IMAGE_TAG=${imageTag}",
|
||||
"FULL_IMAGE=${fullImage}",
|
||||
"VAULT_ADDR=${P.vaultAddr}",
|
||||
"VAULT_MOUNT=${cfg.vaultMount}",
|
||||
"SONAR_URL=${P.sonarUrl}",
|
||||
"SONAR_KEY=${cfg.sonarKey}",
|
||||
"BRIDGE_IP=${P.bridgeIp}",
|
||||
"TRIVY_SEVERITY=${cfg.trivySeverity}",
|
||||
"APP_PORT=${cfg.appPort}",
|
||||
"HEALTH_PATH=${cfg.healthPath}",
|
||||
"INVENTORY=${cfg.inventory}",
|
||||
"PLAYBOOK=${cfg.playbook}",
|
||||
"IMG_GITLEAKS=${P.gitleaks}",
|
||||
"IMG_TRIVY=${P.trivy}",
|
||||
"IMG_SYFT=${P.syft}",
|
||||
"IMG_SCANNER=${P.sonarScanner}",
|
||||
"TEST_IMAGE=${cfg.testImage}"
|
||||
]
|
||||
def imageTag, fullImage, commonEnv
|
||||
|
||||
timestamps {
|
||||
timeout(time: 60, unit: 'MINUTES') {
|
||||
try {
|
||||
|
||||
// ===================== CI: builder VM =====================
|
||||
node(P.builderLabel) {
|
||||
withEnv(commonEnv) {
|
||||
try {
|
||||
stage('Init') {
|
||||
stage('Checkout') {
|
||||
checkout scm
|
||||
def sha = sh(script: 'git rev-parse --short=7 HEAD', returnStdout: true).trim()
|
||||
// Tag me commit SHA isliye: multibranch me har branch ka build
|
||||
// number 1 se shuru hota hai, to sirf build number rakhne par
|
||||
// tag dobara use ho jaata hai aur purani image overwrite hoti hai.
|
||||
// SHA ke saath har tag hamesha unique aur traceable rehta hai.
|
||||
imageTag = isRollback ? rollbackTag
|
||||
: (isMain ? "${env.BUILD_NUMBER}-${sha}"
|
||||
: "${safeBranch}-${env.BUILD_NUMBER}-${sha}")
|
||||
fullImage = "${P.registry}/${cfg.imageName}:${imageTag}"
|
||||
commonEnv = [
|
||||
"MODULE=${cfg.module}", "REGISTRY=${P.registry}",
|
||||
"IMAGE_NAME=${cfg.imageName}", "IMAGE_TAG=${imageTag}",
|
||||
"FULL_IMAGE=${fullImage}", "VAULT_ADDR=${P.vaultAddr}",
|
||||
"VAULT_MOUNT=${cfg.vaultMount}", "SONAR_URL=${P.sonarUrl}",
|
||||
"SONAR_KEY=${cfg.sonarKey}", "BRIDGE_IP=${P.bridgeIp}",
|
||||
"TRIVY_SEVERITY=${cfg.trivySeverity}", "APP_PORT=${cfg.appPort}",
|
||||
"HEALTH_PATH=${cfg.healthPath}", "INVENTORY=${cfg.inventory}",
|
||||
"PLAYBOOK=${cfg.playbook}", "IMG_GITLEAKS=${P.gitleaks}",
|
||||
"IMG_TRIVY=${P.trivy}", "IMG_SYFT=${P.syft}",
|
||||
"IMG_SCANNER=${P.sonarScanner}", "TEST_IMAGE=${cfg.testImage}"
|
||||
]
|
||||
currentBuild.displayName = "#${env.BUILD_NUMBER} ${imageTag}"
|
||||
echo "module=${cfg.module} node=${env.NODE_NAME} branch=${branch} " +
|
||||
"mode=${isRollback ? 'ROLLBACK' : 'BUILD'} tag=${imageTag} deploy=${deployable}"
|
||||
}
|
||||
|
||||
stage('Checkout') {
|
||||
checkout scm
|
||||
sh 'echo "commit: $(git rev-parse --short HEAD)"'
|
||||
}
|
||||
|
||||
withEnv(commonEnv) {
|
||||
stage('Secret scan') {
|
||||
// Poori git history scan hoti hai, sirf latest commit nahi -
|
||||
// purana leak bhi pakda jaata hai. Findings = build fail.
|
||||
// Poori git history scan hoti hai, sirf latest commit nahi.
|
||||
sh '''
|
||||
docker run --rm -v "$PWD":/repo "$IMG_GITLEAKS" \
|
||||
detect --source=/repo --redact --no-banner -v
|
||||
@ -137,8 +121,6 @@ def call(Map cfg = [:]) {
|
||||
}
|
||||
|
||||
stage('IaC / Dockerfile scan') {
|
||||
// Dockerfile, compose, k8s manifests ki misconfig pakadta hai
|
||||
// (root user, missing healthcheck, privileged, etc.)
|
||||
sh '''
|
||||
docker run --rm -v "$PWD":/work "$IMG_TRIVY" config /work \
|
||||
--severity "$TRIVY_SEVERITY" --exit-code 1
|
||||
@ -166,7 +148,6 @@ def call(Map cfg = [:]) {
|
||||
}
|
||||
|
||||
stage('SBOM') {
|
||||
// Audit ka pehla sawaal: "is image me kya-kya hai?"
|
||||
sh '''
|
||||
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
|
||||
"$IMG_SYFT" "docker:$FULL_IMAGE" -o spdx-json > sbom.spdx.json
|
||||
@ -208,35 +189,36 @@ assert r['version'] == '${IMAGE_TAG}', 'version mismatch'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
sh 'docker image prune -f --filter "until=24h" >/dev/null 2>&1 || true'
|
||||
cleanWs()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Sign: controller par (key builder ko nahi milti) ============
|
||||
if (!isRollback) {
|
||||
node(P.deployLabel) {
|
||||
withEnv(commonEnv) {
|
||||
stage('Sign image') { cosignSign() }
|
||||
}
|
||||
withEnv(commonEnv) { stage('Sign image') { cosignSign() } }
|
||||
}
|
||||
}
|
||||
|
||||
if (!deployable) {
|
||||
echo isPR ? "PR build - deploy nahi hoga, sirf CI gates." : "Branch '${branch}' deployable nahi hai."
|
||||
echo isPR ? "PR build - deploy nahi hoga, sirf CI gates."
|
||||
: "Branch '${branch}' deployable nahi hai - sirf CI gates chale."
|
||||
return
|
||||
}
|
||||
|
||||
// ===================== CD: controller =====================
|
||||
// ===================== CD =====================
|
||||
node(P.deployLabel) {
|
||||
withEnv(commonEnv) {
|
||||
stage('Verify signature') { cosignVerify() }
|
||||
if (cfg.gitops) {
|
||||
stage('Promote to GitOps') { gitopsPromote(cfg) }
|
||||
if (cfg.k8sVerifyUrl) {
|
||||
stage('Verify k8s (ArgoCD)') { waitForRollout(cfg.k8sVerifyUrl, imageTag) }
|
||||
// ArgoCD ko Gitea webhook turant jagata hai, phir bhi image
|
||||
// pull + rollout me waqt lagta hai - 10 min ka window.
|
||||
stage('Verify k8s (ArgoCD)') { waitForRollout(cfg.k8sVerifyUrl, imageTag, 60) }
|
||||
}
|
||||
}
|
||||
if (cfg.deployVM) {
|
||||
@ -248,7 +230,7 @@ assert r['version'] == '${IMAGE_TAG}', 'version mismatch'
|
||||
|
||||
if (cfg.approval && cfg.deployVM) {
|
||||
stage('Production approval') {
|
||||
// Node ke bahar hai - intezaar me koi executor block nahi hota.
|
||||
// Node ke bahar - intezaar me koi executor block nahi hota.
|
||||
timeout(time: 30, unit: 'MINUTES') {
|
||||
input message: "${cfg.module}:${imageTag} staging par verified hai. PRODUCTION par bhejein?",
|
||||
ok: 'Deploy to Production'
|
||||
@ -264,13 +246,6 @@ assert r['version'] == '${IMAGE_TAG}', 'version mismatch'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
currentBuild.description = "${cfg.module}:${imageTag}${isRollback ? ' (rollback)' : ''}"
|
||||
|
||||
} catch (e) {
|
||||
currentBuild.result = 'FAILURE'
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user