Jenkinsfile 20 KB

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