Jenkinsfile-uat-build-deploy.groovy 19 KB

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