版本:Gitlab Community Edition 10.8.3、Jenkins 2.249.1 、Sonar 6.7.7
gilab 版本可能会有事件消息的不一样,其他应该不影响的。
一、Gitlab 配置
在工程项目下,settings > Integrations
添加url http://${ip}:${port}/jenkins/generic-webhook-trigger/invoke?token=${在 jenkins 中定义的token}
勾选push、tag 事件(触发)
Gitlab 上每次push、打tag,都会触发事件,调用 Jenkins 的 webhook ,消息格式如下:
{
"object_kind":"push",
"event_name":"push",
"before":"98a75a40e01978568c7e703bdf9aa0db8387679f",
"after":"cd5100f0737b5c046a358ba78ab961d01570b72d",
"ref":"refs/heads/${分支名称}",
"checkout_sha":"cd5100f0737b5c046a358ba78ab961d01570b72d",
"message":null,
"user_id":1529,
"user_name":"张三",
"user_username":"xx",
"user_email":"xx@xx.cn",
"user_avatar":"${用户的gitlab主页}",
"project_id":111,
"project":{
"id":111,
"name":"${项目名称}",
"description":"xx需求",
"web_url":"${project_url}",
"avatar_url":null,
"git_ssh_url":"${project_ssh_url}",
"git_http_url":"****",
"namespace":"${group}",
"visibility_level":0,
"path_with_namespace":"${group}/${project}",
"default_branch":"master",
"ci_config_path":null,
"homepage":"${project_url}",
"url":"${project_ssh_url}",
"ssh_url":"${project_ssh_url}",
"http_url":"${project_url}"
},
"commits":[
{
"id":"xxx",
"message":"xxx改造",
"timestamp":"2021-01-25T10:09:41+08:00",
"url":"https://gitlab.***.cn/${group}/${project}/commit/cd5100f0737b5c046a358ba78ab961d01570b72d",
"author":{
"name":"xxx",
"email":"xxx@xx.cn"
},
"added":[],
"modified":[
"module/src/main/java/cn/xxx/xxlog/xxx.java"
],
"removed":[]
}
],
"total_commits_count":1,
"repository":{
"name":"${project}",
"url":"${project_ssh_url}",
"description":"xx需求",
"homepage":"${project_url}",
"git_http_url":"${project_url}",
"git_ssh_url":"${project_ssh_url}",
"visibility_level":0
}
}
PS:上面消息中,用变量和xxx代替了实际值,手动打码~
二、Jenkins 配置
这里我用了两个Jenkins 任务,一个pipeline 用来处理消息,一个maven 任务用来构建指定模块(可以合并到pipeline,不过我对pipeline的sonar 插件不熟,所以用了独立模块来搞)
处理git 消息事件pipeline 配置如下
- 获取触发用户的用户名、邮箱、分支、改动点等数据(参考上一步的消息来获取)
2. 配置token(gitlab 配置中要用到),用来识别触发当前任务,如果多个jenkins 任务都配置了相同的token,那么gitlab 事件会触发所有这些任务
3. 配置pipeline 脚本
pipeline {
agent any
environment {
// 定义全局变量
REPOSITORY = 'https://gitlab.xxx.cn/xxx/xxx.git'
BRANCH = 'master'
TRGGER_USER = "master"
ADDED = 'a' // 不知道为啥不能定义成数组 [] 会报错
MODIFIED = 'a'
}
triggers {
GenericTrigger(
genericVariables: [
[key: 'ref', value: '$.ref'],
[key: 'user_name', value: '$.user_name'],
[key: 'user_email', value: '$.user_email'],
[key: 'PROJECT_NAME', value: '$.project.name'],
[key: 'aaa', value: '$.commits[0].added'],
[key: 'bbb', value: '$.commits[0].modified'],
[key: 'full_msg', value: '$']
],
token: 'xxxx' ,
causeString: 'Triggered on $ref by $user_name' ,
printContributedVariables: true,
printPostContent: true
)
}
stages {
stage('Init') {
steps {
script {
tmp = "${ref}".split('/');
BRANCH = tmp.size() > 0 ? tmp[tmp.size()-1] : tmp[0];
TRGGER_USER = "${user_name}";
MODIFIED = "${bbb}"
ADDED = "${aaa}"
println BRANCH;
println MODIFIED;
wrap([$class: 'BuildUser']) {
currentBuild.description = "由【${TRGGER_USER}】发起n 分支:${BRANCH}"
}
}
sh 'echo -n ' + REPOSITORY + '> REPOSITORY'
}
}
stage('Build') {
steps {
// checkout([$class: 'GitSCM', branches: [[name: "FETCH_HEAD"]], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CloneOption', noTags: true, reference: '', shallow: true]], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '{{拉代码的用户id,jenkins用户凭据那找一个}}', name: "", refspec: "+refs/heads/"+ BRANCH + ":refs/remotes/origin/" + BRANCH, url: REPOSITORY]]])
// sh "mvn clean org.jacoco:jacoco-maven-plugin:prepare-agent install -Dmaven.test.skip=true"
script {
def MODULES = getModules("${aaa}", "${bbb}")
for (module in MODULES){
// 每个模块,调用一个独立的任务去扫描代码,也可以直接写到这里
build job: 'xxxx-single-module', parameters: [string(name: 'branch', value: BRANCH), string(name: 'MODULE', value: module), string(name: 'TRGGER_USER', value: TRGGER_USER), string(name: 'USER_EMAIL', value: "${user_email}") ], wait: false
}
}
}
}
}
}
def getModules(aaa, bbb){
def MODULES = []
ADDED = aaa.replaceAll("[|]|"", "").split(",") // 这里不知道为啥数组被换成了字符串
if (ADDED != ""){
for (tmp in ADDED){
a = tmp.split('/')
String t = a[0]
println "${aaa}"
println tmp
println a.size()
println a.length
if (a.size()> 1){
t = t + '/' + a[1]
}
if (!MODULES.contains(t) && t != ""){
MODULES.add(t)
}
}
}
MODIFIED = bbb.replaceAll("[|]|"", "").split(",")
if (MODIFIED != ""){
for (tmp in MODIFIED){
def a = tmp.split('/')
println MODIFIED
println "${bbb}"
println MODIFIED.size()
String t = a[0]
if (a.size() > 1)
t = t + '/' + a[1]
if (!MODULES.contains(t) && t != ""){
MODULES.add(t)
}
}
}
for (tmp in MODULES){
println tmp
}
return MODULES
}
4. sonar扫描的任务配置如下:
1)定义传入参数,用来接收上面的任务传过来的参数
2)源码里面拉取指定分支,分支名称由上一个任务传过来,这里我采用的浅拷贝,加快速度
3)配置sonar property,这里我是用Pre steps 写一个脚本,把需要的配置写到文件里,sonar 扫描的配置里使用配置文件,这样做的 好处是可以比较灵活定义需要扫描的模块,代码文件等
4)最后再配置一个Jacoco,后续可以再加个邮件通知
三,sonar上的效果
每次push,Jenkins 任务就会解析改动模块,然后针对模块进行扫描,把结果推到sonar 上,由于我的sonar 是开放的,所以project 可以任意创建,按分支、模块来命名