将Jenkins共享库的Jenkinsfile放到ci静态检测的实践
# 1,前言
我们所有的构建都已经托管在 Jenkins 上,Jenkins 所有的 Job 都采用的 pipeline 形式,pipeline 所有的引导文件都已经托管在 gitlab 统一调用共享库,共享库也都托管在 gitlab 像普通项目那样走开发-review 的流程进行上线。
but,有一个问题是,共享库这种提纲挈领的,以一持万的,纲举目张的存在,到如今都还没有走严格意义上的开发流程。事实上前几天已经吃过这种亏了,一个贡献库文件被上百个发布 job 引用非常常见,而因为运维同学稍稍粗心,少了个后关闭的大括号,push 到共享库之后就下班走人了,结果影响了不少项目的构建。而这种比较低级的错误,本来可以通过前置工作进行一些检测的。
# 2,调研
今天就来做一下共享库内主要发布逻辑脚本的语法静态检测,看了一下,网上有不少的相关资料,整理如下:
- Jenkins 官方文档–管道开发工具 (opens new window)
- stack overflow
- jflint (opens new window)
- 一个开源的检测工具,使用相当简便,本文使用这个。
- npm-groovy-lint (opens new window)
- 同样是一个检测工具,看起来功能更强大,有点复杂。
# 3,准备
有了如上介绍以及文档基础之后,我们选择 jflint 这个工具来进行检测,结合 jenkins 作为 ci 检测代码是否合并的实践 (opens new window)此文章配置流水线 ci,将共享库托管起来。
由于如上工具没有直接给到容器化的版本,所以我这里构建一个容器,读者可以直接拿去使用:
- 官方:
eryajf/jflint
- 国内:
registry.cn-hangzhou.aliyuncs.com/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
2
3
4
5
6
7
8
9
10
11
12
其中在构建镜像时,声明了两个 ENV,以便于调用:
ENV USER=admin
ENV PASS=admin
2
# 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
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.
2
3
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
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.
2
3
可以看到不仅报错了,还提示了我们错误的行数,可以说非常清晰了。
# 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"
}
}
}
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 的方式,并且有静态自动检测语法,成功之后方可合并到主干分支,以期将影响降到最低。