Jenkinsfile-uat-build-deploy.groovy 16 KB

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