Jenkinsfile-uat-build-deploy.groovy 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. /**
  2. * UAT: Checkout -> Maven -> (optional) push images to Harbor -> deploy jar + docker restart.
  3. *
  4. * Jenkins Job: Pipeline script from SCM
  5. * Script Path: docs/jenkins/Jenkinsfile-uat-build-deploy.groovy
  6. *
  7. * Harbor (153.68): when PUSH_TO_HARBOR=true, push uat-latest (+ archive prior as uat-build-<N>).
  8. * Prod promote: SOURCE_TAG=uat-latest
  9. */
  10. def filterHarborPushScope(List allServices, String scope) {
  11. def s = (scope ?: 'all-java-services').trim()
  12. if (s == 'all-java-services') {
  13. return allServices
  14. }
  15. if (s.endsWith('-only')) {
  16. def repo = s.substring(0, s.length() - '-only'.length())
  17. def picked = allServices.findAll { it.repo == repo }
  18. if (picked.isEmpty()) {
  19. error("Unknown HARBOR_PUSH_SCOPE: ${scope}")
  20. }
  21. return picked
  22. }
  23. error("Unknown HARBOR_PUSH_SCOPE: ${scope}")
  24. }
  25. /** POSIX sh only — Jenkins sh step uses /bin/sh, not bash. */
  26. def pruneHarborUatTags(def script, String reg, String proj, List repoNames, int keepCount, String tagPrefix, String currentBuildTag, String latestTag) {
  27. if (repoNames == null || repoNames.isEmpty() || keepCount < 1) {
  28. return
  29. }
  30. def repos = repoNames.join(' ')
  31. script.sh """
  32. set -e
  33. REG='${reg}'
  34. PROJ='${proj}'
  35. KEEP=${keepCount}
  36. PREFIX='${tagPrefix}'
  37. CURRENT='${currentBuildTag}'
  38. LATEST='${latestTag}'
  39. if ! command -v jq >/dev/null 2>&1; then
  40. echo '>>> Harbor prune skipped: jq not installed on Jenkins agent'
  41. exit 0
  42. fi
  43. for repo in ${repos}; do
  44. enc_repo=\$(printf '%s' "\${repo}" | jq -sRr @uri)
  45. tags_file=\$(mktemp)
  46. curl -fsS -u "\${HARBOR_USER}:\${HARBOR_PASS}" \\
  47. "http://\${REG}/api/v2.0/projects/\${PROJ}/repositories/\${enc_repo}/artifacts?page_size=100" \\
  48. | jq -r '.[] | .tags[]? | .name' | grep "^\${PREFIX}" | sort -t- -k3 -n > "\${tags_file}" || true
  49. count=\$(wc -l < "\${tags_file}" | tr -d ' ')
  50. echo ">>> prune \${repo}: \${count} tag(s) matching \${PREFIX}*"
  51. if [ "\${count}" -le "\${KEEP}" ]; then
  52. rm -f "\${tags_file}"
  53. continue
  54. fi
  55. del_count=\$((count - KEEP))
  56. deleted=0
  57. while IFS= read -r t; do
  58. [ -z "\${t}" ] && continue
  59. if [ "\${t}" = "\${CURRENT}" ] || [ "\${t}" = "\${LATEST}" ]; then
  60. continue
  61. fi
  62. if [ "\${deleted}" -ge "\${del_count}" ]; then
  63. break
  64. fi
  65. echo ">>> DELETE Harbor tag \${repo}:\${t}"
  66. if ! curl -fsS -X DELETE -u "\${HARBOR_USER}:\${HARBOR_PASS}" \\
  67. "http://\${REG}/api/v2.0/projects/\${PROJ}/repositories/\${enc_repo}/artifacts/\${t}/tags/\${t}"; then
  68. echo ">>> WARN: delete failed \${repo}:\${t}"
  69. fi
  70. deleted=\$((deleted + 1))
  71. done < "\${tags_file}"
  72. rm -f "\${tags_file}"
  73. done
  74. """
  75. }
  76. def pushOneHarborImage(String workspace, String dockerfile, String reg, String proj, Map svc, String latestTag, String buildTag) {
  77. def jarName = "${svc.module}-1.0.0.jar"
  78. def imageLatest = "${reg}/${proj}/${svc.repo}:${latestTag}"
  79. def imageBuild = "${reg}/${proj}/${svc.repo}:${buildTag}"
  80. def withLibFlag = svc.withLib ? 'true' : 'false'
  81. sh """
  82. set -e
  83. export DOCKER_BUILDKIT=1
  84. test -f ${workspace}/${svc.module}/target/${jarName}
  85. cd ${workspace}/${svc.module}
  86. rm -rf .jenkins_docker_ctx && mkdir -p .jenkins_docker_ctx/lib
  87. cp -f target/${jarName} .jenkins_docker_ctx/${jarName}
  88. if [ "${withLibFlag}" = "true" ] && [ -d target/lib ]; then
  89. cp -rf target/lib/. .jenkins_docker_ctx/lib/
  90. else
  91. touch .jenkins_docker_ctx/lib/.keep
  92. fi
  93. cd .jenkins_docker_ctx
  94. if docker pull ${imageLatest} 2>/dev/null; then
  95. echo ">>> archive previous ${latestTag} -> ${buildTag}"
  96. docker tag ${imageLatest} ${imageBuild}
  97. docker push ${imageBuild}
  98. fi
  99. docker build -f ${workspace}/${dockerfile} \\
  100. --build-arg BASE_IMAGE=${reg}/${proj}/base/openjdk8-ffmpeg:v1 \\
  101. --build-arg JAR_FILE=${jarName} \\
  102. --build-arg SERVER_PORT=${svc.port} \\
  103. --build-arg WITH_LIB=${svc.withLib} \\
  104. -t ${imageLatest} .
  105. docker push ${imageLatest}
  106. echo ">>> pushed ${imageLatest}"
  107. docker rmi ${imageLatest} 2>/dev/null || true
  108. cd ${workspace}/${svc.module}
  109. rm -rf .jenkins_docker_ctx
  110. """
  111. }
  112. def deployOneService(String workspace, String moduleName, String dirName) {
  113. def sourceJar = "${workspace}/${moduleName}/target/${moduleName}-1.0.0.jar"
  114. def sourceLib = "${workspace}/${moduleName}/target/lib"
  115. def targetDir = "/app_deploy_uat/${dirName}"
  116. sh """
  117. set -e
  118. echo ">>> Deploy module: ${moduleName}"
  119. if [ -f "${sourceJar}" ]; then
  120. mkdir -p "${targetDir}"
  121. if [ -d "${sourceLib}" ]; then
  122. rm -rf "${targetDir}/lib"
  123. cp -rf "${sourceLib}" "${targetDir}"
  124. fi
  125. cp -f "${sourceJar}" "${targetDir}/"
  126. if docker ps -a --format '{{.Names}}' | grep -wq "${dirName}"; then
  127. docker restart "${dirName}"
  128. echo ">>> [${dirName}] restarted"
  129. else
  130. echo ">>> [${dirName}] container missing, jar copied only"
  131. fi
  132. else
  133. echo ">>> [${dirName}] jar missing, skip"
  134. fi
  135. """
  136. }
  137. pipeline {
  138. agent any
  139. options {
  140. buildDiscarder(logRotator(numToKeepStr: '15'))
  141. disableConcurrentBuilds()
  142. timestamps()
  143. timeout(time: 90, unit: 'MINUTES')
  144. }
  145. parameters {
  146. string(
  147. name: 'GIT_BRANCH',
  148. defaultValue: 'uat-20260202',
  149. trim: true,
  150. description: 'Git branch, must match remote (e.g. uat-20260202)'
  151. )
  152. booleanParam(name: 'FORCE_UPDATE', defaultValue: false, description: 'mvn -U (leave unchecked for routine builds)')
  153. booleanParam(name: 'ALLOW_SNAPSHOTS', defaultValue: true, description: 'allow SNAPSHOT deps')
  154. booleanParam(
  155. name: 'PUSH_TO_HARBOR',
  156. defaultValue: true,
  157. description: 'After Maven: docker build + push Harbor (uat-latest). Uncheck for jar-only deploy.'
  158. )
  159. choice(
  160. name: 'HARBOR_PUSH_SCOPE',
  161. choices: [
  162. 'all-java-services',
  163. 'gateway-only',
  164. 'store-only',
  165. 'second-only',
  166. 'store-platform-only',
  167. 'lawyer-only',
  168. 'job-only',
  169. 'dining-only',
  170. ],
  171. description: 'Only when PUSH_TO_HARBOR=true'
  172. )
  173. string(name: 'HARBOR_REGISTRY', defaultValue: '39.105.153.68', trim: true)
  174. string(name: 'HARBOR_PROJECT', defaultValue: 'alien_cloud', trim: true)
  175. booleanParam(name: 'HARBOR_PRUNE_OLD_TAGS', defaultValue: true, description: 'Delete old uat-build-* tags (never uat-latest)')
  176. string(name: 'HARBOR_KEEP_TAG_COUNT', defaultValue: '10', trim: true)
  177. booleanParam(name: 'HARBOR_PUSH_PARALLEL', defaultValue: true, description: 'Build/push Harbor images in parallel (faster, uses more CPU/disk)')
  178. }
  179. environment {
  180. MAVEN_HOME = tool '3.6.3'
  181. PATH = "${MAVEN_HOME}/bin:${env.PATH}"
  182. GIT_URL = 'http://8.152.195.41:3000/alien/alien_cloud'
  183. GIT_CREDENTIALS = 'zhanghaomimapingzheng'
  184. HARBOR_CREDENTIALS = 'harbor-robot-alien'
  185. UAT_HARBOR_LATEST_TAG = 'uat-latest'
  186. UAT_HARBOR_BUILD_TAG = "uat-build-${env.BUILD_NUMBER}"
  187. DOCKERFILE_JAVA = 'docs/jenkins/produ/docker/Dockerfile.java-service'
  188. MAVEN_LOCAL_REPO = '/var/jenkins_home/.m2/repository'
  189. }
  190. stages {
  191. stage('Checkout') {
  192. steps {
  193. script {
  194. def branch = (params.GIT_BRANCH ?: 'uat-20260202').trim()
  195. if (!branch) {
  196. error('GIT_BRANCH is required')
  197. }
  198. env.GIT_BRANCH = branch
  199. echo ">>> Checkout branch: ${env.GIT_BRANCH}"
  200. git branch: "${env.GIT_BRANCH}",
  201. credentialsId: "${env.GIT_CREDENTIALS}",
  202. url: "${env.GIT_URL}"
  203. sh """
  204. set -e
  205. git fetch origin
  206. git reset --hard origin/${env.GIT_BRANCH}
  207. git log -1 --oneline
  208. """
  209. }
  210. }
  211. }
  212. stage('Prepare Maven Settings') {
  213. steps {
  214. writeFile file: 'settings.xml', text: """<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  215. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  216. xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
  217. <mirrors>
  218. <mirror>
  219. <id>aliyunmaven</id>
  220. <mirrorOf>central</mirrorOf>
  221. <name>Aliyun Maven Central mirror</name>
  222. <url>https://maven.aliyun.com/repository/central</url>
  223. </mirror>
  224. <mirror>
  225. <id>aliyun-public</id>
  226. <mirrorOf>*,!spring-milestones,!spring-snapshots</mirrorOf>
  227. <name>Aliyun public</name>
  228. <url>https://maven.aliyun.com/repository/public</url>
  229. </mirror>
  230. </mirrors>
  231. <profiles>
  232. <profile>
  233. <id>repo-mix</id>
  234. <repositories>
  235. <repository>
  236. <id>central</id>
  237. <url>https://repo.maven.apache.org/maven2</url>
  238. <releases><enabled>true</enabled><updatePolicy>daily</updatePolicy></releases>
  239. <snapshots><enabled>false</enabled></snapshots>
  240. </repository>
  241. <repository>
  242. <id>spring-milestones</id>
  243. <url>https://repo.spring.io/milestone</url>
  244. <releases><enabled>true</enabled><updatePolicy>daily</updatePolicy></releases>
  245. <snapshots><enabled>false</enabled></snapshots>
  246. </repository>
  247. <repository>
  248. <id>spring-snapshots</id>
  249. <url>https://repo.spring.io/snapshot</url>
  250. <releases><enabled>false</enabled></releases>
  251. <snapshots><enabled>true</enabled><updatePolicy>daily</updatePolicy></snapshots>
  252. </repository>
  253. </repositories>
  254. <pluginRepositories>
  255. <pluginRepository>
  256. <id>central</id>
  257. <url>https://repo.maven.apache.org/maven2</url>
  258. <releases><enabled>true</enabled></releases>
  259. <snapshots><enabled>false</enabled></snapshots>
  260. </pluginRepository>
  261. </pluginRepositories>
  262. </profile>
  263. </profiles>
  264. <activeProfiles>
  265. <activeProfile>repo-mix</activeProfile>
  266. </activeProfiles>
  267. </settings>
  268. """
  269. }
  270. }
  271. stage('Maven Build') {
  272. steps {
  273. script {
  274. def updateFlag = params.FORCE_UPDATE ? '-U' : ''
  275. retry(2) {
  276. sh """
  277. set -e
  278. mvn -version
  279. unset http_proxy https_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY all_proxy no_proxy NO_PROXY || true
  280. export MAVEN_OPTS="-Xms512m -Xmx2048m -Dmaven.wagon.http.ssl.insecure=true -Dmaven.wagon.http.ssl.allowall=true -Dmaven.wagon.http.ssl.ignore.validity.dates=true"
  281. mkdir -p ${MAVEN_LOCAL_REPO}
  282. du -sh ${MAVEN_LOCAL_REPO} 2>/dev/null || true
  283. echo ">>> Maven repo: ${MAVEN_LOCAL_REPO} (persists across builds; do NOT delete on disk cleanup)"
  284. mvn clean package -DskipTests -s settings.xml ${updateFlag} \\
  285. -T 1C \\
  286. -Dmaven.repo.local=${MAVEN_LOCAL_REPO} \\
  287. -Dmaven.artifact.threads=8
  288. """
  289. }
  290. }
  291. }
  292. }
  293. stage('Push images to Harbor') {
  294. when { expression { return params.PUSH_TO_HARBOR == true } }
  295. steps {
  296. script {
  297. def reg = params.HARBOR_REGISTRY.trim()
  298. def proj = params.HARBOR_PROJECT.trim()
  299. def latestTag = env.UAT_HARBOR_LATEST_TAG
  300. def buildTag = env.UAT_HARBOR_BUILD_TAG
  301. def dockerfile = env.DOCKERFILE_JAVA
  302. def workspace = env.WORKSPACE
  303. def allHarborServices = [
  304. [module: 'alien-gateway', repo: 'gateway', port: '8000', withLib: false],
  305. [module: 'alien-store', repo: 'store', port: '50014', withLib: true],
  306. [module: 'alien-second', repo: 'second', port: '50015', withLib: false],
  307. [module: 'alien-store-platform', repo: 'store-platform', port: '50016', withLib: false],
  308. [module: 'alien-lawyer', repo: 'lawyer', port: '50017', withLib: true],
  309. [module: 'alien-job', repo: 'job', port: '50108', withLib: false],
  310. [module: 'alien-dining', repo: 'dining', port: '50019', withLib: false],
  311. ]
  312. def harborServices = filterHarborPushScope(allHarborServices, params.HARBOR_PUSH_SCOPE)
  313. echo ">>> HARBOR_PUSH_SCOPE=${params.HARBOR_PUSH_SCOPE} repos=${harborServices*.repo.join(',')}"
  314. withCredentials([usernamePassword(
  315. credentialsId: env.HARBOR_CREDENTIALS,
  316. usernameVariable: 'HARBOR_USER',
  317. passwordVariable: 'HARBOR_PASS',
  318. )]) {
  319. sh """
  320. set -e
  321. echo "\${HARBOR_PASS}" | docker login ${reg} -u "\${HARBOR_USER}" --password-stdin
  322. df -h / 2>/dev/null || true
  323. docker system prune -f --filter until=48h 2>/dev/null || true
  324. """
  325. if (params.HARBOR_PUSH_PARALLEL && harborServices.size() > 1) {
  326. def branches = [:]
  327. harborServices.each { svc ->
  328. branches[svc.repo] = {
  329. pushOneHarborImage(workspace, dockerfile, reg, proj, svc, latestTag, buildTag)
  330. }
  331. }
  332. parallel branches
  333. } else {
  334. harborServices.each { svc ->
  335. pushOneHarborImage(workspace, dockerfile, reg, proj, svc, latestTag, buildTag)
  336. }
  337. }
  338. if (params.HARBOR_PRUNE_OLD_TAGS == true) {
  339. def keepN = (params.HARBOR_KEEP_TAG_COUNT ?: '10').trim() as int
  340. catchError(buildResult: 'SUCCESS', stageResult: 'UNSTABLE') {
  341. pruneHarborUatTags(
  342. this, reg, proj, harborServices*.repo,
  343. keepN, 'uat-build-', buildTag, latestTag,
  344. )
  345. }
  346. }
  347. }
  348. echo ">>> Prod promote: SOURCE_TAG=${env.UAT_HARBOR_LATEST_TAG}"
  349. }
  350. }
  351. }
  352. stage('Deploy Services') {
  353. steps {
  354. script {
  355. def workspace = env.WORKSPACE
  356. def services = [
  357. [module: 'alien-gateway', dir: 'gateway-uat'],
  358. [module: 'alien-job', dir: 'job-uat'],
  359. [module: 'alien-lawyer', dir: 'lawyer-uat'],
  360. [module: 'alien-second', dir: 'second-uat'],
  361. [module: 'alien-store', dir: 'store-uat'],
  362. [module: 'alien-dining', dir: 'dining-uat'],
  363. [module: 'alien-store-platform', dir: 'store-platform-uat'],
  364. ]
  365. def branches = [:]
  366. services.each { svc ->
  367. branches[svc.dir] = {
  368. deployOneService(workspace, svc.module, svc.dir)
  369. }
  370. }
  371. parallel branches
  372. }
  373. }
  374. }
  375. }
  376. post {
  377. always {
  378. sh 'rm -f settings.xml || true'
  379. }
  380. success {
  381. script {
  382. if (params.PUSH_TO_HARBOR) {
  383. echo ">>> Harbor latest: ${env.UAT_HARBOR_LATEST_TAG}"
  384. echo ">>> Prod promote: SOURCE_TAG=${env.UAT_HARBOR_LATEST_TAG}"
  385. }
  386. }
  387. }
  388. }
  389. }