/** * Harbor image promote + optional ACK deploy (loaded from Jenkinsfile, not a job entry). */ def DEFAULT_SOURCE_TAG = 'uat-latest' def resolveTargetTag(def script, String targetTagParam) { def t = (targetTagParam ?: '').trim() return t ?: "produ-${script.env.BUILD_NUMBER}" } /** Empty SOURCE_TAG (incl. old Job cached params) falls back to uat-latest. */ def requireSourceTag(def script, String sourceTag) { def srcTag = (sourceTag ?: '').trim() if (!srcTag) { script.echo ">>> SOURCE_TAG empty, using default: ${DEFAULT_SOURCE_TAG}" return DEFAULT_SOURCE_TAG } return srcTag } def promoteHarborImages(def script, List services, Map cfg) { def regHost = cfg.harborRegistry.trim() def proj = cfg.harborProject.trim() def srcTag = requireSourceTag(script, cfg.sourceTag) def tgtTag = cfg.targetTag def dryRun = cfg.dryRun == true def method = (cfg.promoteMethod ?: 'harbor-api').trim() def repos = services*.prodDir.join(' ') services.each { s -> def src = "${regHost}/${proj}/${s.prodDir}:${srcTag}" def tgt = "${regHost}/${proj}/${s.prodDir}:${tgtTag}" echo ">>> promote ${s.prodDir}: ${src} -> ${tgt} (method=${method})" } if (dryRun) { return } script.withCredentials([script.usernamePassword( credentialsId: cfg.harborCredentialsId, usernameVariable: 'HARBOR_USER', passwordVariable: 'HARBOR_PASS', )]) { script.sh """ set -e REG='${regHost}' PROJ='${proj}' SRC='${srcTag}' TGT='${tgtTag}' METHOD='${method}' if [ "\${METHOD}" != "docker-pull" ] && ! command -v jq >/dev/null 2>&1; then echo '>>> jq missing, fallback to docker-pull' METHOD=docker-pull fi if [ "\${METHOD}" = "docker-pull" ]; then echo "\${HARBOR_PASS}" | docker login \${REG} -u "\${HARBOR_USER}" --password-stdin fi promote_repo() { local repo="\$1" local src_img="\${REG}/\${PROJ}/\${repo}:\${SRC}" local tgt_img="\${REG}/\${PROJ}/\${repo}:\${TGT}" if [ "\${METHOD}" = "docker-pull" ]; then echo ">>> docker promote \${repo}" docker pull "\${src_img}" docker tag "\${src_img}" "\${tgt_img}" docker push "\${tgt_img}" docker rmi "\${tgt_img}" "\${src_img}" 2>/dev/null || true return 0 fi local enc_repo enc_repo=\$(printf '%s' "\${repo}" | jq -sRr @uri) echo ">>> Harbor API promote \${repo}: \${SRC} -> \${TGT}" if curl -fsS -X POST -u "\${HARBOR_USER}:\${HARBOR_PASS}" \\ "http://\${REG}/api/v2.0/projects/\${PROJ}/repositories/\${enc_repo}/artifacts/\${SRC}/tags" \\ -H 'Content-Type: application/json' \\ -d "{\\"name\\":\\"\${TGT}\\"}"; then return 0 fi echo ">>> WARN: Harbor API failed for \${repo}, fallback docker" docker pull "\${src_img}" docker tag "\${src_img}" "\${tgt_img}" docker push "\${tgt_img}" docker rmi "\${tgt_img}" "\${src_img}" 2>/dev/null || true } if [ "\${METHOD}" != "docker-pull" ]; then echo "\${HARBOR_PASS}" | docker login \${REG} -u "\${HARBOR_USER}" --password-stdin 2>/dev/null || true fi for repo in ${repos}; do promote_repo "\${repo}" done """ } } def deployServicesToAck(def script, List services, Map cfg) { def parallelDeploy = cfg.parallelDeploy != false def regHost = cfg.harborRegistry.trim() def proj = cfg.harborProject.trim() def tgtTag = cfg.targetTag def strategy = cfg.deployStrategy ?: 'rolling' def ns = cfg.k8sNamespace def canaryWeight = (cfg.canaryWeight ?: '10').trim() def kubeCreds = cfg.kubeCredentialsId def runDeploy = { s -> def imageRef = "${regHost}/${proj}/${s.prodDir}:${tgtTag}" deployToAck(script, [ k8sNamespace: ns, imageRef: imageRef, deployStrategy: strategy == 'canary' ? 'canary' : 'rolling', deploymentStable: s.deployName, deploymentCanary: "${s.deployName}-canary", ingressCanary: "${s.deployName}-canary", canaryWeight: canaryWeight, kubeCredentialsId: kubeCreds, ]) } if (!parallelDeploy || services.size() <= 1) { services.each { runDeploy(it) } return } def branches = [:] services.each { s -> branches[s.prodDir] = { runDeploy(s) } } script.parallel branches } def deployToAck(def script, Map cfg) { def ns = cfg.k8sNamespace def imageRef = cfg.imageRef def strategy = cfg.deployStrategy def targetDeploy = strategy == 'canary' ? cfg.deploymentCanary : cfg.deploymentStable def canaryWeight = cfg.canaryWeight def ingressCanary = cfg.ingressCanary def kubeCreds = cfg.kubeCredentialsId script.withCredentials([script.file(credentialsId: kubeCreds, variable: 'KUBECONFIG')]) { script.sh """ set -e kubectl config current-context kubectl -n ${ns} set image deployment/${targetDeploy} app=${imageRef} --record kubectl -n ${ns} rollout status deployment/${targetDeploy} --timeout=300s """ if (strategy == 'canary') { script.sh """ set -e kubectl -n ${ns} annotate ingress ${ingressCanary} \\ nginx.ingress.kubernetes.io/canary=true \\ nginx.ingress.kubernetes.io/canary-weight=${canaryWeight} \\ --overwrite """ } } } /** Single-service promote + ACK. Use script.params / script.env (do not pass params/env as args: CPS breaks method dispatch). */ def promoteOneServiceToAck(def script, Map svc) { def regHost = script.params.HARBOR_REGISTRY.trim() def proj = script.params.HARBOR_PROJECT.trim() def srcTag = requireSourceTag(script, script.params.SOURCE_TAG) def tgtTag = resolveTargetTag(script, script.params.TARGET_TAG) def dryRun = script.params.DRY_RUN == true def strategy = script.params.DEPLOY_STRATEGY ?: 'rolling' script.env.TARGET_TAG_RESOLVED = tgtTag script.env.IMAGE_REF = "${regHost}/${proj}/${svc.prodDir}:${tgtTag}" promoteHarborImages(script, [svc], [ harborRegistry: regHost, harborProject: proj, sourceTag: srcTag, targetTag: tgtTag, harborCredentialsId: script.env.HARBOR_CREDENTIALS, promoteMethod: (script.params.PROMOTE_METHOD ?: 'harbor-api').trim(), dryRun: dryRun, ]) if (dryRun || strategy == 'skip') { return } deployToAck(script, [ k8sNamespace: script.params.K8S_NAMESPACE, imageRef: script.env.IMAGE_REF, deployStrategy: strategy == 'canary' ? 'canary' : 'rolling', deploymentStable: svc.deployName, deploymentCanary: "${svc.deployName}-canary", ingressCanary: "${svc.deployName}-canary", canaryWeight: (script.params.CANARY_WEIGHT ?: '10').trim(), kubeCredentialsId: script.env.KUBECONFIG_CREDENTIALS, ]) } return this