Jenkins运维之路(共享库集成流水线发布)

1. 集成共享库后Pipeline

总体思路,将一些常用且用法比较固定的流水线抽离出来做为方法,在其他流水线中可以进行服用减少代码量

php 复制代码
#!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_ID
                def TRACE_ID = sh(script: "echo `head -c 32 /dev/random | base64`",  returnStdout: true).trim() // 随机生成TRACE_ID
                def 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个参数在使用时进行传参。

perl 复制代码
package org.devops

def GetChangeString() {
    MAX_MSG_LEN = 100
    def changeString = ""
    def changeLogSets = currentBuild.changeSets
    for (int i = 0; i < changeLogSets.size(); i++) {
        def entries = changeLogSets[i].items
        for (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的方法如果你要使用根据自己要求,要调试下

dart 复制代码
package org.devops
def NexusUploadTargz(String PROJECT_NAME, String REPOSITORY, String VERSION){
    // 使用之前生成的 version 变量
    nexusArtifactUploader artifacts: [
        [
            artifactId: "${PROJECT_NAME}", // 替换为您的 artifactId
            classifier: '',
            file: "${PROJECT_NAME}.tar.gz", // 替换为您的文件路径
            type: 'tar.gz' // 根据您的文件类型进行修改
        ]
    ],
    //文件file类型有jar,pom,war,zip,tar.gz
    credentialsId: 'Nexus3-DevOps',
    groupId: 'top.xxx',
    nexusUrl: 'registryv.xxx.top',
    nexusVersion: 'nexus3',
    protocol: 'https',
    repository: "${REPOSITORY}",
    version: "${VERSION}"
}

def NexusUploadJar(){
    // 使用之前生成的 version 变量
    nexusArtifactUploader artifacts: [
                        [
                            artifactId: 'basejar', // 替换为您的 artifactId
                            classifier: '',
                            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.gz
    credentialsId: 'Nexus3-DevOps',
    groupId: 'top.xxxx',
    nexusUrl: 'registryv.xxxx.top',
    nexusVersion: 'nexus3',
    protocol: 'https',
    repository: 'DevopsArtifact',
    version: "v8"
}

src/org/devops/sonarq.groovy 代码审查方法,这里定义了3个方法,扫描的,探测状态的,虽然这玩意没人用,但是还是先做出来以后万一用呢

typescript 复制代码
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的全局方法

javascript 复制代码
def call(){
    return new Date().format('yyyyMMddHHmmss') + "_${env.BUILD_ID}"
}

src/org/vars/PrintMes.groovy 打印彩色字体方法

javascript 复制代码
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.共享流水线代码库目录结构

null

image-20250924162741866

5.引用共享库前后对比

null

image-20250924163127359

null

image-20250924163311175

可以看出来,如果将一些共性的东西抽离出来可以极大的简化你的流水线,同时减少你重复写流水线过程,还是很nice的,好了大概Jenkins就这么完结撒花了,等以后在有什么新的发现在更新(这周必须要完工虽有流水线更新,压力大,可能晚点更新内容了撒! 如果你觉得文章还算有用可以帮我点个赞,转个发 嘿嘿!)

相关推荐
会飞的小蛮猪7 小时前
Jenkins运维之路(初次调试共享库)
运维·经验分享·docker·容器·jenkins
会飞的小蛮猪5 天前
Jenkins运维之路(Slave容器节点)
运维·ci/cd·jenkins
云偶6 天前
从零搭建 Jenkins Android 自动发包体系
jenkins
libraG7 天前
Jenkins打包问题
前端·npm·jenkins
HONG_YANG7 天前
基于 Docker 部署 n8n 指南,新手一看就会
自动化运维
全栈工程师修炼指南9 天前
告别手动构建!Jenkins 与 Gitlab 完美协作,根据参数自动化触发CI/CD流水线实践
运维·ci/cd·自动化·gitlab·jenkins
白水清风9 天前
CI/CD学习记录(基于GitLab)
前端·自动化运维·前端工程化
一勺菠萝丶10 天前
Jenkins 构建 Node 项目报错解析与解决——pnpm lockfile 问题实战
elasticsearch·servlet·jenkins
xixingzhe210 天前
jenkins脚本触发部署
运维·jenkins