1、工具简介
SonarQube是一个用于代码质量管理的开源平台,用于管理源代码的质量。通过插件机制,SonarQube可以集成不同的测试工具,代码分析工具,以及持续集成工具,比如pmd-cpd、checkstyle、findbugs、Jenkins。通过不同的插件对这些结果进行再加工处理,通过量化的方式度量代码质量的变化,从而可以方便地对不同规模和种类的工程进行代码质量管理。
SonarQube 在进行代码质量管理时,会在七个纬度来分析项目的质量:算法设计,注释,编码规则,潜在缺陷,单元测试,重复块,复杂度。
2、方案介绍
Sonarqube的强大之处在于它可以跟持续集成工具(比如jenkins)非常好的对接,作为工具链的的一环,搭建适合自己公司研发项目模式的持续集成流水线,做到快速扫描即时反馈。
这里提供三种我实践过的方式给大家参考。
2.1 本地化代码扫描
为了能让研发同学在编写代码的过程中,可以随时扫描本地代码以做到尽快修复,我们推荐使用sonarlint插件。
SonarLint(http://www.sonarlint.org/eclipse/index.html )是一个可以安装在IDE(包括IDEA,Eclipse,Visual Studio,VS Code,Atom)中的插件,它可以向开发人员及时反馈Java、JavaScript、PHP、Python…代码中编码时产生的新的缺陷和质量问题
SonarLint是开源、免费的,使用时需要注意,它需要运行在Java 8 环境下,SonarQube 5.6 +
关于sonarlint的使用,后面会单独在工具介绍里面详细。
本方案实践情况:按需选择,建议开发本地安装。
2.2 代码提交后触发扫描
开发提交或者合并代码到功能分支后,会自动触发Jenkins上配置的代码扫描任务,进行构建静测,并自动将静测结果返回开发人员。目的是为了确保每次提交的代码都经过集成验证,代码质量合格且能通过编译。
实现方式:
jenkins中需要创建一个Multibranch Pipeline类型的任务,配置gitlab仓库地址,并且往gitlab仓库中传入一个jenkinsfile文件,用于判断分支是否需要进行代码扫描。(实现方式参考:【Gitlab+Jenkins+Sonarqube】 ---- 多分支类型项目代码提交后 触发静态代码扫描的实现)。
对于提测分支和上线分支,不设置代码提交后触发静测(在我们目前的发布上线流程中,提测分支有单独的流程),只针对功能分支(feature)和热修复(hotfix)分支设置。下面的脚本是针对我目前的设置进行配置的,仅供参考。
脚本示例:
#!groovy
def projectProperties = [
//设置禁止并发
disableConcurrentBuilds(),
//保留历史构建记录为3
buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '', numToKeepStr: '3')),
//预设邮件通知人参数
parameters([
string(defaultValue: 'qa-ci@xxxx.cn', description: '', name: 'Mail_List', trim: false)
])
]
properties(projectProperties)
def Job_Name = "CI.TEST.JOB"
def failed_text = '''<hr/>
很遗憾的通知这次执行失败啦,一定有哪里出了问题,还请点开构建日志仔细检查,或者跟管理员联系 <br/><hr/>
项目名称:$PROJECT_NAME<br/><hr/>
触发原因:${CAUSE}<br/><hr/>
构建日志地址:<a href="http://xxxxx:8080/blue/organizations/jenkins/${Job_Name}/detail/${env.BRANCH_NAME}/${BUILD_NUMBER}/pipeline">http://xxxxx:8080/blue/organizations/jenkins/${Job_Name}/detail/${JOB_NAME}/${env.BRANCH_NAME}/pipeline</a><br/><hr/>
静测结果:<a href="http://xxxxx:9000/dashboard/index/${Job_Name}.${env.BRANCH_NAME}">http://xxxxx:9000/dashboard/index/${Job_Name}.${env.BRANCH_NAME}</a><br/><hr/>
变更集:\${SCRIPT, template="groovy-html.template"}<br/><hr/>'''
//需要触发提交即刻代码扫描的功能分支和热修复分支,根据前缀匹配判断
def Sonar_BranchList = ['feature', 'hotfix']
node('slave-centos') {
permission = SonarRunBranch(Sonar_BranchList)
echo "当前分支名:[${env.BRANCH_NAME}]"
echo "[分支${env.BRANCH_NAME}]:" + "${permission == true ? '将要执行静态代码扫描' : '非feature和hotfix分支无需单独进行代码扫描'}"
if (permission) {
stage('Checkout') {
try {
retry(2) {
checkout scm
}
}
catch (exc) {
echo '代码下载失败了, 请检查配置!'
emailext body: "${failed_text}",
subject: "${JOB_NAME} - Failure!",
to: "${params.Mail_List}"
sh 'exit 1'
}
}
stage('Build') {
try {
retry(2) {
sh "yarn install"
sh "npm run build"
}
}
catch (exc) {
echo '构建失败了, 请检查配置!'
emailext body: "${failed_text}",
subject: "${JOB_NAME} - Failure!",
to: "${params.Mail_List}"
sh 'exit 1'
}
}
stage('SonarAnalysis') {
try {
retry(2) {
echo '静态代码检查开始:'
withSonarQubeEnv('sonarqube6.5') {
sh '/disk1/jenkins/tools/hudson.plugins.sonar.SonarRunnerInstallation/SonarQube_Scanner/bin/sonar-scanner ' +
"-Dsonar.projectKey=${Job_Name}.${env.BRANCH_NAME} " +
"-Dsonar.projectName=${Job_Name}.${env.BRANCH_NAME} " +
'-Dsonar.projectVersion=${BUILD_NUMBER} ' +
'-Dsonar.sources=./ ' +
'-Dsonar.exclusions=**/node_modules/** ' +
'-Dsonar.sourceEncoding=UTF-8'
}
}
}
catch (exc) {
echo '静态代码检查失败了, 请检查配置!'
emailext body: "${failed_text}",
subject: "${JOB_NAME} - Failure!",
to: "${params.Mail_List}"
sh 'exit 1'
}
}
stage ('Mail'){
emailext body: """<hr/>
项目名称:\${PROJECT_NAME}<br/><hr/>
构建日志地址:<a href="http://xxxxx:8080/blue/organizations/jenkins/${Job_Name}/detail/${env.BRANCH_NAME}/${BUILD_NUMBER}/pipeline">http://xxxxx:8080/blue/organizations/jenkins/${Job_Name}/detail/${JOB_NAME}/${env.BRANCH_NAME}/pipeline</a><br/><hr/>
静测结果:<a href="http://xxxxx:9000/dashboard/index/${Job_Name}.${env.BRANCH_NAME}">http://xxxxx:9000/dashboard/index/${Job_Name}.${env.BRANCH_NAME}</a><br/><hr/>
变更集:\${SCRIPT, template="groovy-html.template"}<br/><hr/>""" ,
subject: "${JOB_NAME} -静态代码扫描- Successful!",
to: "${params.Mail_List}"
}
}
}
def SonarRunBranch(Sonar_BranchList) {
result = false
for (BranchName in Sonar_BranchList){
if(env.BRANCH_NAME.startsWith(BranchName)){
result = true
}
}
result
}
然后开发同学就能从邮件中收到静测结果报告啦。
2.3 测试版本出包前扫描
在提交构建测试版本时,会在构建后进行代码扫描,在整个过程完成后,将构建以及静测结果返回测试,开发人员等。
脚本示例:
3、人员角色
选用了方案之后,我们需要对人员角色进行定义,确保流程得以贯彻。另外,由于Sonarqube原生的规则非常多,需要经过一段时间的摸索,这期间,我们需要经验丰富的开发人员对扫描问题进行把控,判断是否符合业务场景。所以在使用方案初期,我们队不同的人员角色和承担的职责做了划分,配合Sonarqube6.7版本支持的问题分配功能,定制如下:
根据不同人员将承担的职责,我们针对项目每个项目进行如下角色的定义。
角色(权限群组) | 权限 | 说明 |
Project-Read | 能查看该项目的静测报表,不能看到源码仓库。 | 非开发人员。 |
Project-DEV | 能查看该项目的静测报表,浏览源码仓库。 | 一般为开发人员。 |
Project-Master | 能查看该项目的静测报表,浏览源码仓库等,同时拥有项目的管理权限,进行权限分配,以及问题管理权限(包括问题等级验证性修改,是否误判和无需修复等) | 一般为项目开发负责人,需要对项目的静测扫描结果进行把控。 |