使用jenkins pipeline共享库,各应用都可以引用共享库方法,更改共享库即可应用到所有使用此库的jenkins-job。我目前没有用到vars目录,但完全能够满足我们日常需求,使用方式上可能较low,直接开始体验。

下面列出了定义的部分方法,以作参考。

共享库目录结构:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
jenkins-pipeline-libraries git:(master) ✗ tree .
.
├── jenkins-ci
│   └── jenkinsfile-java
├── out
│   └── production
├── src
│   ├── ops
│   │   └── jk
│   │       ├── appDetail.groovy
│   │       └── tools.groovy
│   └── pipeline.gdsl
└── vars
    └── pipelineCfg.groovy

7 directories, 6 files

appDetail.groovy文件里面部分函数如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
// 获取时间 格式:20201208200419
def getTime() {
    return new Date().format('yyyyMMddHHmmss')
}

//格式化输出,需安装ansiColor插件
def printMes(value,level){
    colors = ['warning' : "\033[43;30m ==> ${value} \033[0m",
              'info'    : "\033[42;30m ==> ${value} \033[0m",
              'error'   : "\033[41;37m ==> ${value} \033[0m",
    ]
    ansiColor('xterm') {
        println(colors[level])
    }
}

// 远程主机上通过ssh执行命令
def runCmd(ip, user, pass, command, sshArgs = '') {
    return sh(returnStdout: true,
            script: "sshpass -p ${pass} ssh ${sshArgs} -oStrictHostKeyChecking=no -l ${user} ${ip} \"${command}\"")
}

// 格式化输出当前构建分支用于镜像tag
def getBranch() {
    def formatBranch = "${env.GIT_BRANCH}".replaceAll('origin/', '').replaceAll('/', '_')
    assert formatBranch != 'null' || formatBranch.trim() != ''
    return formatBranch
}

// 获取分支commitId
def getSHA() {
    def commitsha = "${env.GIT_COMMIT}".toString().substring(0, 6)
    assert commitsha != 'null' || commitsha.trim() != ''
    return commitsha
}

// 获取commit时间
def getCommitTime() {
    out = sh(returnStdout: true, script: "git show -s --format=%ct ${env.GIT_COMMIT}")
    def commitTime = out.toString().trim()
    assert commitTime != 'null' || commitTime.trim() != ''
    return commitTime
}

//获取git commit变更集
def getChangeString() {
    def result = []
    def changeString = []
    def authors = []
    def MAX_MSG_LEN = 20
    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]
            truncatedMsg = entry.msg.take(MAX_MSG_LEN)
            commitTime = new Date(entry.timestamp).format("yyyy-MM-dd HH:mm:ss")
            changeString << "${truncatedMsg} [${entry.author} ${commitTime}]\n"
//            changeString += ">- ${truncatedMsg} [${entry.author} ${commitTime}]\n"
            authors << "${entry.author} "
        }
    }
    if (!changeString) {
        changeString = ">- No new changes"
        authors = "No new changes, No authors"
        result << changeString << authors
        return result
    } else {
        if (changeString.size() >5) {
            changeString = changeString[0,4]
            changeString.add("......")
        }
        changeString = ">-" + changeString.join(">-")
        authors.join(", ")
        result << changeString << authors.unique()
        return result
    }
}

// java项目sonar扫描
def sonarScanner() {
    def sonarDir = tool name: 'scanner-docker', type: 'hudson.plugins.sonar.SonarRunnerInstallation'
    printMes("sonarqube scanner started. sonarHomeDir: ${sonarDir}", "info")
    withSonarQubeEnv(credentialsId: 'comp-sonar') {
        sh "${sonarDir}/bin/sonar-scanner \
           -Dsonar.projectKey=${projectName} \
           -Dsonar.projectName=${projectName} \
           -Dsonar.ws.timeout=60 \
           -Dsonar.sources=. \
           -Dsonar.sourceEncoding=UTF-8 \
           -Dsonar.java.binaries=. \
           -Dsonar.language=java \
           -Dsonar.java.source=1.8"
    }
    printMes("${projectName} scan success!", "info")
}

// 发送钉钉消息,按需自定义,需要安装httpRequest插件
def dingMes(){
    def user = ''
    def description = ''
    def specificCause = currentBuild.getBuildCauses('hudson.model.Cause$UserIdCause')
    if (specificCause) {
        user = "$specificCause.userName"
        description = "$specificCause.shortDescription"
    }
    def DingTalkHook = "https://oapi.dingtalk.com/robot/send?access_token=xxxxxxxxxxx"
    def ChangeStrings = getChangeString()[0]
    def ChangeAuthors = getChangeString()[1]
    def ReqBody = """{
            "msgtype": "markdown",
            "markdown": {
                "title": "项目构建信息",
                "text": "### [${JOB_NAME}](${BUILD_URL})\\n---\\n>- 分支:**${env.GIT_BRANCH}**\\n> - 执行人: **${user}**\\n>- 描述: ${description}\\n>#### 作者:\\n>- **${ChangeAuthors}**\\n>#### 更新记录: \\n${ChangeStrings}"
            },
            "at": {
                "atMobiles": [], 
                "isAtAll": false
                }
            }"""
    httpRequest acceptType: 'APPLICATION_JSON_UTF8',
            consoleLogResponseBody: true,
            contentType: 'APPLICATION_JSON_UTF8',
            httpMode: 'POST',
            ignoreSslErrors: true,
            requestBody: ReqBody,
            responseHandle: 'NONE',
            url: "${DingTalkHook}",
            timeout: 5,
            quiet: true
}

//邮件通知
def emailSuccess(){
    emailext(
        subject: "✅${env.JOB_NAME} - 更新成功",
        body: '${SCRIPT, template="email-html.template"}',
        recipientProviders: [requestor(), developers()]
    )
}

def emailFailure(){
    emailext(
        subject: "❌${env.JOB_NAME} - 更新失败",
        body: '${SCRIPT, template="email-html.template"}',
        recipientProviders: [requestor(), developers()]
    )
}

我们jenkins里面jobname都是按照环境-项目名的格式命名的,例如 dev-potatoappDetail.groovy中下面方法有一部分依赖了此jobname

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
// 通过jenkins-jobname获取项目名称
def getProjectName() {
    String projectName
    String jobName = "${env.JOB_NAME}"
    if ("${env.JOB_NAME}".contains('front-end')) {
        jobName = "${env.JOB_NAME}".replaceAll('front-end/', '')
    }
    projectName = jobName.substring(jobName.indexOf('-') + 1)
    assert projectName != 'null' || projectName.trim() != ''
    return projectName
}

// 通过jobname获取环境名称
def getNameSpace() {
    String namespace
    if ("${env.JOB_NAME}".contains('front-end')) {
        def realJobName = "${env.JOB_NAME}".replaceAll('front-end/', '')
        namespace = realJobName.split('-')[0]
    }else {
        namespace = "${env.JOB_NAME}".split('-')[0]
    }
    assert namespace != 'null' || namespace.trim() != ''
    return namespace
}

// 定义镜像tag,输出格式 xxx.yy.com/comp/bnu:o_hotfix_update_publish_wwefan_0808_78ffds_10
def getImgTag() {
    String regUrl
    def imageTag = ''
    def namespace = getNameSpace()

    if ("${namespace}" != 'pre' && "${namespace}" != 'online') {
        regUrl = 'xxx.yy.net'
    }else {
        regUrl = 'xxx.yy.com'
    }

    def rollTag = "${params.ROLLTAG}"  // jenkins通过参数构建过程传进来的回滚版本镜像tag
    def gitProjectName = "${env.GIT_URL}".substring("${env.GIT_URL}".lastIndexOf('/') + 1, "${env.GIT_URL}".lastIndexOf('.git'))
    def projectNamespace = "${env.GIT_URL}".substring("${env.GIT_URL}".lastIndexOf(':') + 1, "${env.GIT_URL}".lastIndexOf('/'))
    def jkProjectName = getProjectName()
    if (jkProjectName != gitProjectName) {
        printMes('the jobname of Jenkins does not match the project name of Gitlab', 'warn')
//        error('the jobname of Jenkins does not match the project name of Gitlab')
    }

    if (rollTag) {
        imageTag = "${regUrl}/${projectNamespace}/${gitProjectName}:${rollTag}"
    } else {
        def commitShortSha = getSHA()
        // def COMMIT_TIME = getCommitTime()
        def formatBranch = getBranch()
        imageTag = "${regUrl}/${projectNamespace}/${gitProjectName}:${namespace}_${formatBranch}_${commitShortSha}_${env.BUILD_NUMBER}"
    }
    printMes(imageTag, 'info')
    return imageTag
}

// 构建docker容器镜像推送到仓库
def dockerBuild() {
    printMes("开始构建docker镜像", 'info')
    def namespace = getNameSpace()
    def projectName = getProjectName()
    String regUrl
    if ("${namespace}" != 'pre' && "${namespace}" != 'online') {
        regUrl = 'https://xxx.yy.net'
    }else {
        regUrl = 'https://xxx.oo.com'
    }
    if (!params.ROLLTAG) {
        def core = getCore()
        def dockerimg = ''
        docker.withRegistry("${regUrl}", "comp-image") {
            dockerimg = docker.build(getImgTag(), "-f Dockerfile ./${projectName}-${core}/target/")
            dockerimg.push()
        }
        printMes("镜像已推送到 ${regUrl}", 'info')
    }

}

以Java项目为例,引入共享库方式如下(前提是在jenkins中配置了共享库jenkinslib):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!groovy
@Library('jenkinslib') _

def mytool = new ops.jk.tools()
def app = new ops.jk.appDetail()
def appMap = app.getAppMap()
def projectName = app.getProjectName()
def namespace = app.getNameSpace()

pipeline {
    environment {
        CI_PROJECT_NAME = app.getProjectName()
        IMG_TAG = app.getImgTag()
        NAMESPACE = app.getNameSpace()
        APPTYPE = app.getAppType()
    }

    options {
        timestamps()
        timeout(time: 20, unit: 'MINUTES')
    }

    agent {
        docker {
            image 'xxx.yy.com/library/maven:3.6-git-kube16'
            registryUrl 'https://xxx.yy.com'
            registryCredentialsId 'comp-image'
            label app.getAppLabel()
            args '-v /var/run/docker.sock:/var/run/docker.sock -v /home/admin/:/home/admin/'
        }
    }

    parameters {
        string(name: 'ROLLTAG', defaultValue: '', description: '是否回滚到之前的版本,输入镜像tag进行回滚')
        choice(name: 'SONAR_SCANNER', choices: ['不执行', '执行'], description: '是否执行代码扫描')
    }

    stages {
        stage('mvnPackage'){
            when { expression { return ! params.ROLLTAG } }
            steps {
                script {
                    app.mvnPackage()
                }
            }
        }
        stage('sonar-scanner'){
            when { expression { return params.SONAR_SCANNER == '执行' } }
            steps {
                script {
                    app.sonarScanner()
                }
            }
        }
        stage("Quality Gate") {
            when { expression { return params.SONAR_SCANNER == '执行' } }
            steps {
                timeout(time: 5, unit: 'MINUTES') {
                    waitForQualityGate abortPipeline: true
                }
            }
        }
        stage('dockerBuild') {
            steps {
                script {
                    app.dockerBuild()
                }
            }
        }
        stage('publish') {
            steps {
                script {
                    app.publish()
                }
            }
        }
    }

    post {
        success {
            script{
                mytool.emailSuccess()
            }
        }
        failure {
            script{
                mytool.emailFailure()
            }
        }
    }
}

钉钉通知消息示例:

ding_notify

邮件通知示例:

email_notify