2

将Jenkins共享库的Jenkinsfile放到ci静态检测的实践

 3 years ago
source link: https://wiki.eryajf.net/pages/b732f5/#_5-%E5%AE%9E%E8%B7%B5
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

将Jenkins共享库的Jenkinsfile放到ci静态检测的实践

# 1,前言

我们所有的构建都已经托管在Jenkins上,Jenkins所有的Job都采用的pipeline形式,pipeline所有的引导文件都已经托管在gitlab统一调用共享库,共享库也都托管在gitlab像普通项目那样走开发-review的流程进行上线。

but,有一个问题是,共享库这种提纲挈领的,以一持万的,纲举目张的存在,到如今都还没有走严格意义上的开发流程。事实上前几天已经吃过这种亏了,一个贡献库文件被上百个发布job引用非常常见,而因为运维同学稍稍粗心,少了个后关闭的大括号,push到共享库之后就下班走人了,结果影响了不少项目的构建。而这种比较低级的错误,本来可以通过前置工作进行一些检测的。

# 2,调研

今天就来做一下共享库内主要发布逻辑脚本的语法静态检测,看了一下,网上有不少的相关资料,整理如下:

# 3,准备

有了如上介绍以及文档基础之后,我们选择jflint这个工具来进行检测,结合 jenkins作为ci检测代码是否合并的实践 (opens new window)此文章配置流水线ci,将共享库托管起来。

由于如上工具没有直接给到容器化的版本,所以我这里构建一个容器,读者可以直接拿去使用:

  • 官方:eryajf/jflint
  • 国内:registry.cn-hangzhou.aliyuncs.com/ali_eryajf/jflint

简单查看:

$ docker run -it  eryajf/jflint  jflint -h
Usage: jflint [options] Jenkinsfile

Options:
  -v, --version                output the version number
  -j, --jenkins-url <url>      Jenkins URL
  -u, --username <username>    Username for Jenkins
  -p, --password <password>    Password or API Token for Jenkins
  --csrf-disabled              Specify if CSRF prevention is disabled on Jenkins
  -c, --config <path>          Path to config json file
  --ssl-verification-disabled  Disable SSL verification
  -h, --help                   output usage information
1
2
3
4
5
6
7
8
9
10
11
12

其中在构建镜像时,声明了两个ENV,以便于调用:

ENV USER=admin
ENV PASS=admin

# 4,验证

此时准备一个非常简单的流水线作为测试使用:

cat >> test.jenkins << 'EOF'
pipeline {
    agent any
    environment {
        // 输出结果为 20200330142150_4
        _version = sh(script: "echo `date '+%Y%m%d%H%M%S'`" + "_${env.BUILD_ID}", returnStdout: true).trim()
    }
    stages {
        stage ("test") {
            steps {
                echo "${_version}"
            }
        }
    }
}
EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

然后运行如下命令进行检测:

$ docker run -itd --name jflint eryajf/jflint # 如果你的用户名密码并不是上边两个,可以通过-e进行覆盖
$ docker cp test.jenkins jflint:/opt
$ docker exec -it jflint jflint -u $USER -p $PASS -j https://ci.test.com /opt/test.jenkins
Jenkinsfile successfully validated.

此时将如上流水线进行简单调整:

cat >> test.jenkins << 'EOF'
pipeline {
    agent any
    environment {
        // 输出结果为 20200330142150_4
        _version = sh(script: "echo `date '+%Y%m%d%H%M%S'`" + "_${env.BUILD_ID}", returnStdout: true).trim()
    }
    stages {
        stage ("test") {
            steps {
                echo "${_version}"
            }
        }
    }
EOF
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

注意删除了最后一行的括号。

然后再次运行检测:

$ docker exec -it jflint jflint -u $USER -p $PASS -j https://ci.test.com /opt/test.jenkins
Errors encountered validating Jenkinsfile:
WorkflowScript: 14: expecting '}', found '' @ line 14, column 1.

可以看到不仅报错了,还提示了我们错误的行数,可以说非常清晰了。

11

# 5,实践

有了如上准备以及实验之后,我们就可以直接添加如下流水线,作为静态ci的配置信息:

pipeline {
    agent any
    environment {
        // 服务名称
        SERVICE_NAME="${JOB_BASE_NAME}"
        MODE="DEPLOY"
        REASON="无"
        REMOTE_HOST="占位"
        _VERSION="测试验证"
        ROBOT_KEY="6a781aaf-0cda-41ab-9bd2-ed81ee7fc7"
        // 主项目地址
        GIT_URL = "https://gitlab.test.com/jenkins/shared-library.git"
    }
    parameters {
        string(name: 'BRANCH', defaultValue: 'master', description: '请输入将要构建的代码分支')
    }
    options {
        timestamps() // 输出构建日志打印时间信息
        timeout(time: 10, unit: 'MINUTES') // 设置构建超时时间
        buildDiscarder(logRotator(numToKeepStr: '15')) // 设置历史构建保留次数
        gitLabConnection('gitlab-token') // 操作gitlab的token
    }
    triggers{
        gitlab(
            triggerOnPush: false,
            triggerOnMergeRequest: true,
            branchFilterType: 'All',
            secretToken: "${env.GIT_TOKEN}"
        ) // 预留Gitlab提交自动构建
    }
    stages {
        stage('置为pending') {
            steps {
                script {
                    try {
                        updateGitlabCommitStatus name: 'build', state: 'pending'
                    }catch(exc) {
                        env.REASON = "置为pending出错"
                        throw(exc)
                    }
                }
            }
        }
        stage('拉取代码') {
            steps {
                script {
                    try {
                        env.CAUSE = currentBuild.getBuildCauses()[0].(userId)
                        if ("${CAUSE}" == 'null') {
                            env.BRANCH = sh(script: 'echo ${gitlabBranch}',  returnStdout: true).trim()
                        }
                        checkout(
                            [$class: 'GitSCM', doGenerateSubmoduleConfigurations: false, submoduleCfg: [], extensions: [[$class: 'CloneOption', depth: 1, noTags: false, reference: '', shallow: true]],
                            branches: [[name: "$BRANCH"]],userRemoteConfigs: [[url: "${env.GIT_URL}", credentialsId: "cicd-pass"]]]
                        )
                        // 定义全局变量
                        env.COMMIT_ID   = sh(script: 'git log --pretty=format:%h',  returnStdout: true).trim() // 提交ID
                        env.COMMIT_USER = sh(script: 'git log --pretty=format:%an', returnStdout: true).trim() // 提交者
                        env.COMMIT_TIME = sh(script: 'git log --pretty=format:%ai', returnStdout: true).trim() // 提交时间
                        env.COMMIT_INFO = sh(script: 'git log --pretty=format:%s',  returnStdout: true).trim() // 提交信息
                    }catch(exc) {
                        env.REASON = "拉取代码出错"
                        throw(exc)
                    }
                }
            }
        }
        stage('检测项目') {
            steps {
                script {
                    try {
                        docker.image('eryajf/jflint').inside("-e USER=admin -e PASS=admin12345") {
                            sh '''
                                for i in `ls vars`;do
                                    jflint -u $USER -p $PASS -j https://ci.test.com vars/$i
                                done
                            '''
                        }
                        sh "printenv"
                    }catch(exc) {
                        env.REASON = "检测项目出错"
                        throw(exc)
                    }
                }
            }
        }
    }
    post {
        always {
            script{
                wrap([$class: 'BuildUser']){
                    buildName "#${BUILD_ID}-${BRANCH}-${BUILD_USER}" // 更改构建名称
                    currentBuild.description = "提交者: ${COMMIT_USER}" // 添加说明信息
                    currentBuild.description += "\n构建主机: ${REMOTE_HOST}" // 添加说明信息
                    currentBuild.description += "\n提交ID: ${COMMIT_ID}" // 添加说明信息
                    currentBuild.description += "\n提交时间: ${COMMIT_TIME}" // 添加说明信息
                    currentBuild.description += "\n提交内容: ${COMMIT_INFO}" // 添加说明信息
                }
                sh "printenv"
            }
            cleanWs()
        }
        success {
            updateGitlabCommitStatus(name: 'build', state: 'success')
            addGitLabMRComment comment: "🤖Jenkins ci check success🥳, see the log: ${BUILD_URL}console"
        }
        failure {
            updateGitlabCommitStatus(name: 'build', state: 'failed')
            addGitLabMRComment comment: "🤖Jenkins ci check failed🤯, see the log: ${BUILD_URL}console"
        }
    }
}
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

注意,基于容器运行的时候,可以通过后边的参数来指定自己的用户名信息,当然也可以使用token的方式。

这样我们就可以把共享库的维护,走类似常规项目开发的流程,通过check普通分支,走merge的方式,并且有静态自动检测语法,成功之后方可合并到主干分支,以期将影响降到最低。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK