Jenkins运维之路(共享库集成流水线发布)
1. 集成共享库后Pipeline
总体思路,将一些常用且用法比较固定的流水线抽离出来做为方法,在其他流水线中可以进行服用减少代码量
#!groovy
//引入共享库
@Library('ops-share-librarya@master') _
//引入方法
def nexus = new org.devops.nexus()
def sonarq = new org.devops.sonarq()
def dingtalk = new org.devops.dingtalk()
//这里因为有些插件不能使用environment定义的变量所以直接放在这里
String robotId = "BuildBoy"
String GIT_REPO_URL_String = 'git@gitee.com:xxx/spring-boot-3-hello-world-jdk21.git'pipeline {agent { node { label "node47"}}tools {//...}environment {GIT_REPO_URL = 'git@gitee.com:xxxx/spring-boot-3-hello-world-jdk21.git'REGISTRY_URL = 'harbor.xxxx.top'HARBOR_URL = 'https://harbor.xxxx.top'PROJECT_GROUP = 'devops'PROJECT_NAME = 'hello-world-jdk21'CONTAINER_NAME = 'hello-world-jdk21'OPS_SHARE_LIBRARY = 'git@gitee.com:xxxx/ops-share-librarya.git'REPOSITORY = 'DevopsArtifact'}options {timeout(time: 10, unit: 'MINUTES')disableConcurrentBuilds()timestamps()}parameters {gitParameter(name: 'BRANCH_TAG',type: 'PT_BRANCH_TAG',defaultValue: 'master',description: '请选择你要部署的分支或Tag',useRepository: GIT_REPO_URL_String, // 这里要使用完整的Git仓库地址quickFilterEnabled: true // 启用快速筛选)booleanParam(defaultValue: false, description: '是否进行项目回滚?', name: 'ROLLBACK_TAG')}stages {stage('Example') {steps {script {// 测试了下打印方法PrintMes("Generated version: green", 'green')//def version = createVersion()//PrintMes("Generated version: ${version}", 'red')}}}stage('Check requirement') {steps {script {// 判断条件//...省略}}}stage('CleanWorkDir') {steps {//...省略}}stage('Checkout') {steps {script {echo "Using repository: ${env.GIT_REPO_URL}"checkout([$class: 'GitSCM', branches: [[name: params.BRANCH_TAG]], userRemoteConfigs: [[url: "${GIT_REPO_URL}", credentialsId: "GiteeKey"]]])// 增加了一些构建信息变量def PULL_TIME = sh(script: "echo `date +'%Y-%m-%d %H:%M:%S'`", returnStdout: true).trim() // 构建开始时间def COMMIT_ID = sh(script: 'git log -1 --pretty=format:%h', returnStdout: true).trim() // 代码COMMIT_IDdef TRACE_ID = sh(script: "echo `head -c 32 /dev/random | base64`", returnStdout: true).trim() // 随机生成TRACE_IDdef COMMIT_USER = sh(script: 'git log -1 --pretty=format:%an', returnStdout: true).trim() // 代码最后提交者def COMMIT_TIME = sh(script: 'git log -1 --pretty=format:%ai', returnStdout: true).trim() // 提交最后时间def COMMIT_INFO = sh(script: 'git log -1 --pretty=format:%s', returnStdout: true).trim() // 提交最后信息PrintMes("Commit User: ${COMMIT_USER}", 'purple')PrintMes("Commit Time: ${COMMIT_TIME}", 'purple')PrintMes("Commit Info: ${COMMIT_INFO}", 'purple')if (params.ROLLBACK_TAG) {wrap([$class: 'BuildUser']) {echo "Built by: ${env.BUILD_USER_ID}"currentBuild.description = "流水线执行者: ${env.BUILD_USER_ID}\n" +"项目回滚ID: ${env.BRANCH_TAG}\n" +"tag: '项目回滚'"}} else {env._tag = createVersion()wrap([$class: 'BuildUser']) {echo "Built by: ${env.BUILD_USER_ID}"currentBuild.description = "流水线执行者: ${env.BUILD_USER_ID}\n" +"分支: ${env.BRANCH_TAG}\n" +"tag: ${_tag}\n" +"代码提交者: ${COMMIT_USER}\n" +"构建开始时间: ${PULL_TIME}\n" +"提交时间: ${COMMIT_TIME}\n" +"提交信息: ${COMMIT_INFO}"}}}}}stage('Build') {when {expression { return !params.BRANCH_TAG.startsWith('rel-')}}steps {PrintMes("项目开始构建", 'green')sh 'mvn clean package -Dmaven.test.skip=true'sh 'tar zcf ${PROJECT_NAME}.tar.gz target/spring-boot-3-hello-world-1.0.0-SNAPSHOT.jar'}}stage('Upload to Nexus') {when {expression { return !params.BRANCH_TAG.startsWith('rel-')}}steps {script {PrintMes("Upload to Nexus", 'green')echo "Uploading ${PROJECT_NAME} to ${REPOSITORY} with tag ${_tag}"//使用方法进行上传,传入参数的时候不要带有${},直接使用变量名即可,否则报错nexus.NexusUploadTargz(PROJECT_NAME,REPOSITORY,_tag)}}}stage("SonarQube Analysis") {when {expression { // 检查 BRANCH_TAG 是否不以 'rel-' 开头return !params.BRANCH_TAG.startsWith('rel-')}}steps {script {PrintMes("SonarQube Analysis", 'green')//使用方法进行 代码检查sonarq.SonarQubeAnalysis("${JOB_NAME}", 'SonarqubeServ13', 'Jenkins-SonarqubeServ')}}}stage("Quality Gate") {when {expression { // 检查 BRANCH_TAG 是否不以 'rel-' 开头return !params.BRANCH_TAG.startsWith('rel-')}}steps {script {//使用方法进行 代码检查状态查询PrintMes("Checking Quality Gate", 'green')sonarq.checkQualityGate(5)}}}stage('Wait for SonarQube Analysis') {when {expression { // 检查 BRANCH_TAG 是否不以 'rel-' 开头return !params.BRANCH_TAG.startsWith('rel-')}}steps {script {PrintMes("Wait for SonarQube Analysis'", 'green')//使用方法进行 代码检查状态查询sonarq.waitForSonarQubeAnalysis('SonarqubeServ13', 'Jenkins-SonarqubeServ', "${JOB_NAME}", 10)}}}//部署过程这里就省略了,可以根据自己实际情况或者我前面的ansible脚本进行修改}post {always{script {println("流水线结束后,经常做的事情")}}success {script {//使用dingtalk方法进行告警println("流水线结束后,经常做的事情")dingtalk.DingdingReq(robotId, "构建成功 ✅")}}failure{script {println("流水线结束后,经常做的事情")dingtalk.DingdingReq(robotId, "构建失败 ❌")}}aborted{script {println("流水线取消后,做的事情 ")dingtalk.DingdingReq(robotId, "构建取消 ⚠️")}}}
}
2.方法代码
src/org/devops/dingtalk.groovy(钉钉告警方法),这里采集了下构建信息然后定义了2个参数在使用时进行传参。
package org.devopsdef GetChangeString() {MAX_MSG_LEN = 100def changeString = ""def changeLogSets = currentBuild.changeSetsfor (int i = 0; i < changeLogSets.size(); i++) {def entries = changeLogSets[i].itemsfor (int j = 0; j < entries.length; j++) {def entry = entries[j]truncated_msg = entry.msg.take(MAX_MSG_LEN)commitTime = new Date(entry.timestamp).format("yyyy-MM-dd HH:mm:ss")changeString += "> - {truncated_msg} [{entry.author} {commitTime}]\n"}}if (!changeString) {changeString = "> - No new changes"}return changeString
}def DingdingReq(RobotID, Status) {// 确保使用正确的上下文wrap([$class: 'BuildUser']) {def changeString = GetChangeString()dingtalk (robot: RobotID,type: 'MARKDOWN',title: '你有新的消息,请注意查收',text: ["### 构建信息","> - 应用名称:**${env.JOB_NAME}**","> - 构建结果:**${Status}**","> - 构建分支Or回滚Tag:**${env.BRANCH_TAG}**","> - 构建发起:**${env.BUILD_USER}**","> - 持续时间:**${currentBuild.durationString}**","> - 构建日志:[点击查看详情](${env.BUILD_URL}console)","### 更新记录:","${changeString}"],at: ['xxx'])}
}
src/org/devops/nexus.groovy Nexus上传制品防范,定义了2个方法,一个上传tar.gz方法,一个上传jar,上传jar的方法如果你要使用根据自己要求,要调试下
package org.devops
def NexusUploadTargz(String PROJECT_NAME, String REPOSITORY, String VERSION){// 使用之前生成的 version 变量nexusArtifactUploader artifacts: [[artifactId: "${PROJECT_NAME}", // 替换为您的 artifactIdclassifier: '',file: "${PROJECT_NAME}.tar.gz", // 替换为您的文件路径type: 'tar.gz' // 根据您的文件类型进行修改]],//文件file类型有jar,pom,war,zip,tar.gzcredentialsId: 'Nexus3-DevOps',groupId: 'top.xxx',nexusUrl: 'registryv.xxx.top',nexusVersion: 'nexus3',protocol: 'https',repository: "${REPOSITORY}",version: "${VERSION}"
}def NexusUploadJar(){// 使用之前生成的 version 变量nexusArtifactUploader artifacts: [[artifactId: 'basejar', // 替换为您的 artifactIdclassifier: '',file: 'target/spring-boot-3-hello-world-1.0.0-SNAPSHOT.jar', // 替换为您的文件路径type: 'jar' // 根据您的文件类型进行修改],[artifactId: 'spring-boot-3-hello-world', // 同样的 artifactId,或根据需要修改classifier: 'pom',file: 'pom.xml', // 指向您的 pom.xml 文件type: 'pom' // 文件类型为 pom]],//文件file类型有jar,pom,war,zip,tar.gzcredentialsId: 'Nexus3-DevOps',groupId: 'top.xxxx',nexusUrl: 'registryv.xxxx.top',nexusVersion: 'nexus3',protocol: 'https',repository: 'DevopsArtifact',version: "v8"
}
src/org/devops/sonarq.groovy 代码审查方法,这里定义了3个方法,扫描的,探测状态的,虽然这玩意没人用,但是还是先做出来以后万一用呢
package org.devops
def SonarQubeAnalysis(String projectName, String sonarServer, String credentialsId) {def sonarqubeScanner = tool name: 'SonarScanner501'// 使用 SonarQube 环境withSonarQubeEnv(sonarServer) {// 使用 Jenkins 凭据中的 SonarQube 令牌withCredentials([string(credentialsId: credentialsId, variable: 'SONAR_TOKEN')]) {// 执行 SonarQube Scanner CLI 分析sh """${sonarqubeScanner}/bin/sonar-scanner \-Dsonar.projectKey=${projectName} \-Dsonar.projectName=${projectName} \-Dsonar.sources=src \-Dsonar.java.binaries=target/classes \-Dsonar.host.url=${env.SONAR_HOST_URL} \-Dsonar.login=${env.SONAR_AUTH_TOKEN}"""}}
}def checkQualityGate(int timeoutMinutes) {timeout(time: timeoutMinutes, unit: 'MINUTES') {script {def qg = waitForQualityGate() // 等待 SonarQube 的质量门结果if (qg.status != 'OK') { // 检查质量门的状态error "Pipeline aborted due to quality gate failure: ${qg.status}" // 中止流水线}}}
}def waitForSonarQubeAnalysis(String sonarServer, String credentialsId, String projectName, int timeoutMinutes) {//echo "Project Name: ${projectName}"withSonarQubeEnv(sonarServer) {withCredentials([string(credentialsId: credentialsId, variable: 'SONAR_AUTH_TOKEN')]) {timeout(time: timeoutMinutes, unit: 'MINUTES') {waitUntil {def curlCommand = "curl -s -u \$SONAR_AUTH_TOKEN: \$SONAR_HOST_URL/api/project_analyses/search?project=${projectName}"def response = sh(script: curlCommand, returnStdout: true).trim()def exitCode = sh(script: curlCommand, returnStatus: true)echo "Response from SonarQube: ${response}"if (exitCode != 0) {error("Curl command failed with exit code: ${exitCode}. Response: ${response}")}if (response) {try {def jsonResponse = readJSON(text: response)if (jsonResponse.analyses && jsonResponse.analyses.size() > 0) {def latestAnalysis = jsonResponse.analyses[0]echo "Latest Analysis Key: ${latestAnalysis.key}, Date: ${latestAnalysis.date}"if (latestAnalysis.events.size() > 0) {echo "New events found in analysis."} else {echo "No new events found in analysis."}return true} else {error("No analyses found in the response.")}} catch (Exception e) {error("Failed to parse JSON response: ${e.message}. Response: ${response}")}} else {error("Received empty response from SonarQube.")}}}}}
}
3.全局方法
src/org/vars/createVersion.groovy 创建tag的全局方法
def call(){return new Date().format('yyyyMMddHHmmss') + "_${env.BUILD_ID}"
}
src/org/vars/PrintMes.groovy 打印彩色字体方法
def call(String value, String color) {def colors = ['red' : "\033[40;31m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m",'green' : "\033[40;32m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m",'purple' : "\033[40;35m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m",'yellow' : "\033[40;33m >>>>>>>>>>>${value}<<<<<<<<<<< \033[0m"]ansiColor('xterm') {echo colors[color]}
}
4.共享流水线代码库目录结构
image-20250924162741866
5.引用共享库前后对比
image-20250924163127359
image-20250924163311175
可以看出来,如果将一些共性的东西抽离出来可以极大的简化你的流水线,同时减少你重复写流水线过程,还是很nice的,好了大概Jenkins就这么完结撒花了,等以后在有什么新的发现在更新(这周必须要完工虽有流水线更新,压力大,可能晚点更新内容了撒! 如果你觉得文章还算有用可以帮我点个赞,转个发 嘿嘿!)