来源: DevOpSec公众号 作者: DevOpSec
做CI的兄弟基本上都会用到代码质量检查,下面从五个部分给大家讲一下我的sonarqube代码质量检测实战经验,欢迎指正交流
一、需求
二、sonarqube介绍
三、部署
四、SonarQube使用
五、使用中遇到的问题
一、需求
代码质量检查,想知道团队成员代码质量情况,团队人多后每个人的代码风格和代码水平不一通过sonar检测能做到代码基准的一个把控。
master代码质量是基准,可以理解master分支的sonar检测是通过的,分支代码质量检查和master代码进行比对,我们是master上线。
sonar检测后把结果发送通知给对应的开发和其TL,开发能及时知道自己开发代码是否有问题
二、sonarqube 介绍
SonarQube 使所有开发人员能够编写更干净、更安全的代码。
SonarQube 是一种自动代码审查工具,用于检测代码中的错误、漏洞和代码异味。它可以与您现有的工作流程集成,以实现跨项目分支和拉取请求的持续代码检查。
SonarQube 是一个用于代码质量管理的开源平台,用于管理源代码的质量。 通过插件形式,可以支持包括 java, C#, C/C++, PL/SQL, Cobol, JavaScrip, Groovy 等等二十几种编程语言的代码质量管理与检测。
sonarqube 版本介绍
个人觉得社区版加上插件就够用了。
sonarqube分为社区版,开发者,企业版和数据中心版4种
版本功能比较统计:
社区版:在CI / CD中采用代码质量的起点
开发者版:最大应用程序安全性跨分支机构和PR的最大值
企业版:管理您的应用程序组合,在企业级别启用代码质量和安全性。
数据中心版:高可用性,适用于全球部署
版本列表,官网资料:https://www.sonarqube.org/downloads/
架构
sonarQube: web界面管理平台。
1)展示所有的项目代码的质量数据。
2)配置质量规则、管理项目、配置通知、配置SCM等。
sonarScanner: 代码扫描工具。
专门用来扫描和分析项目代码。支持20+语言。
代码扫描和分析完成之后,会将扫描结果存储到数据库当中,在sonarQube平台可以看到扫描数据。
工作流程
工作流程介绍
1.开发人员在IDE中开发和合并代码,并将代码签入到DevOps平台。
2.持续集成工具(如jenkins)检查、构建和运行单元测试,集成SonarQube扫描仪分析结果。
3.扫描程序将结果发布到SonarQube服务器,该服务器通过SonarQube界面、电子邮件、IDE内通知(通过SonarLint)和拉入或合并请求的装饰(使用Developer Edition及更高版本时)向开发人员提供反馈。
SonarQube主要作用
编写整洁代码
把出现在代码里的新问题都解决掉,就可以创建并维护一个干净的代码基础。即使是遗留项目,保持新代码的整洁,也能最终获得一个值得骄傲的代码基础。
修复代码缺陷
缺陷图例和默认质量阈都是基于新代码周期的 - 当前周期就是处理问题的时间。主要的关注点是上一个版本,通常会选择30天作为一个周期。
加强质量阈
项目的质量阈是在发布到生产环境之前所需要达到的一系列的条件标准。质量阈可以确保下一个版本的代码质量总能高于上一个版本。
Sonar的优点
(1)支持所有语言的检测。一个工具,搞定所有。
(2)灵活扩展,插拔式使用。自定义的代码检测规则,可自定义插件,独立打成JAR包放到SONARQUBE插件目录下,重启即生效,开发使用非常方便。而且自带UT验证框架,开发效率高。
(3)规则支持多租户隔离。租户可定制自己的规则集。
(4)生态强大,业界有诸多插件,与jenkins友好集成。
(5)部署使用便捷。
(6)架构松耦合,通过与maven/jenkins等集成,将代码扫描的计算消耗迁移到业务或者构建方的资源上,极大的提升了自身的吞吐能力。
衡量代码质量的几个指标
1.Bugs Bug是出现了明显错误或是高度近似期望之外行为的代码。
2.漏洞 漏洞是指代码中可能出现被黑客利用的潜在风险点。
3.安全热点 安全敏感代码需要手工审核,以便判断是否存在安全漏洞。
4.异味 代码异味会困扰代码的维护者并降低他们的开发效率。主要的衡量标准是修复它们所需的时间。
5.重复率 新代码中的重复行密度 (%),重复行数,重复代码块
6.行数 程序中代码的行数
可以根据需要创建自己的质量阈,比如如果公司单测覆盖率低或者基本没有单元测试可以把单元测试覆盖率检查规则去了。下图是自定义的代码检测规则:
三、部署
部署
部署之前先说两个插件,一是汉化国人毕竟中文看着舒服,二是多分支扫描支持
- 汉化插件
增加 sonar-l10n-zh-plugin-version.jar 到 /opt/sonarqube/extensions/plugins/ 重启启动sonarqube 即可
- 多分支检查插件(注意sonarqube的版本要和插件版本兼容)
2.1、下载对应版本的Community Branch Plugin插件,我们使用的sonarqube:9.6.1-community
,因此下载对应的1.12.0 版本的插件。
SonarQube Version | Plugin Version |
---|---|
9.8+ | 1.14.0 |
9.7 | 1.13.0 |
9.1-9.6 | 1.12.0 |
9.0 | 1.9.0 |
8.9 | 1.8.1 |
8.7 - 8.8 | 1.7.0 |
8.5 - 8.6 | 1.6.0 |
8.2 - 8.4 | 1.5.0 |
8.1 | 1.4.0 |
7.8 - 8.0 | 1.3.2 |
7.4 - 7.7 | 1.0.2 |
2.2、将下载的jar包放到拷贝到/opt/sonarqube/extensions/plugins
目录。
2.3、添加sonar.web.javaAdditionalOpts=-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-1.12.0.jar=web
和sonar.ce.javaAdditionalOpts=-javaagent:./extensions/plugins/sonarqube-community-branch-plugin-1.12.0.jar=ce
到/opt/sonarqube/conf/sonar.properties
。
2.4、重启SonarQube,接受关于使用第三方插件的警告。
2.5、检查安装情况,查看插件配置,进入Administration—>General Settings—>Housekeeping
。基本上不用设置,保持默认即可。
- 容器化部署 Dockerfile
FROM docker.io/library/sonarqube:9.6.1-community
ADD ./sonarqube-community-branch-plugin-1.12.0.jar /opt/sonarqube/extensions/plugins/
ADD ./sonar-l10n-zh-plugin-9.6.jar /opt/sonarqube/extensions/plugins/
ADD ./sonar-findbugs-plugin-4.2.2.jar /opt/sonarqube/extensions/plugins/
build 镜像,基于官方镜像把汉化和多分支扫描插件都打进去
docker build -t harbor.xxx.com/docker-admin/sonarqube:9.6.1-community-zh .
docker push harbor.xxx.com/docker-admin/sonarqube:9.6.1-community-zh
有镜像后通过docker方式或者k8s pod部署都可以的,推荐通过k8s的方式部署。
四、SonarQube使用
多分支扫描
分支代码的质量是和master分支代码质量对比 首先需要扫描master代码的质量,然后再扫描分支的代码质量
master分支ci 触发代码扫描sonarscanner 配置。
cat /opt/sonar-scanner/conf/sonar-scanner.properties
sonar.host.url=http://sonarqube.sonarqube:9000
sonar.login=xxxxxxxx
sonar.projectKey=webapp-tomcat
sonar.projectName=webapp-tomcat
sonar.projectVersion=master_fsfwe135
sonar.sourceEncoding=UTF-8
sonar.ws.timeout=60
sonar.exclusions=**/target/**/*.tar.gz,**/target/**/*.war, *.py, *.tar.gz, *.war, *.sh
sonar.java.source=1.8
sonar.java.binaries=**/*
sonar.sources=.
其他分支ci 触发代码扫描sonarscanner 配置,这里的分支名是:create_session
cat /opt/sonar-scanner/conf/sonar-scanner.properties
sonar.host.url=http://sonarqube.sonarqube:9000
sonar.login=xxxxxxxx
sonar.projectKey=webapp-tomcat
sonar.projectName=webapp-tomcat
sonar.projectVersion=master_fsfwe135
sonar.sourceEncoding=UTF-8
sonar.ws.timeout=60
sonar.exclusions=**/target/**/*.tar.gz,**/target/**/*.war, *.py, *.tar.gz, *.war, *.sh
sonar.java.source=1.8
sonar.java.binaries=**/*
sonar.sources=.
sonar.pullrequest.base=master
sonar.pullrequest.branch=create_session
sonar.pullrequest.key=create_session
运行之后的效果图,出现错误,说明create_session分支如果要merge到master分支会带来bug,建议研发修复
注意: a. 上面的配置文件没有跟着项目走,是全局的配置 b. 每个项目在跑sonar-scanner之前都会通过CI提前生成sonar-scanner.properties文件,然后在容器运行的sonar-scanner进行代码质量检查
质量检查不通过发送告警
master和其他分支代码检查不通过发送告警给开发者和开发者TL 这里使用sonarqube的API结合python实现
首先需要生成token
其次知道API怎么用,API帮助文档入口
第三拿到获取project sonar scan之后的 project_status
直接上伪代码
#/usr/bin/env python
# coding: utf-8
import requests
import sys
import json
import time
from requests.auth import HTTPBasicAuth
token = 'xxxxxxx'
# projectKey 最好是模块名,见名知意
project_key=sys.argv[1]
pull_request=sys.argv[2]
build_user=sys.argv[3]
#接口api
url = f'https://sonarqube.xxxx.com/api/qualitygates/project_status?projectKey={project_key}'
#展示页面,发送消息提醒,后在im里可以直接点击该链接进入sonarqube server web端
durl = f'https://sonarqube.xxxxx.com/dashboard?id={project_key}'
#默认url和durl对应的结果是master分支,如果不是master分支,加上pullRequest也即与master分支对别之后的结果。
if pull_request != "master":
url = url + f'&pullRequest={pull_request}'
durl = durl + f'&pullRequest={pull_request}'
#获取开发的leader,这个根据自己家公司api实现即可
def get_user_leader(build_user):
return leader
def check_scanner_result():
is_ok="OK"
try:
# sleep 5s ,scan后会有个入库时间有延迟,所以sleep 5s
time.sleep(5)
#获取scanner结果是否通过代码质量检查
r = requests.get(url % (), auth=HTTPBasicAuth(token, "")).json()
is_ok = r['projectStatus']['status']
except:
# master 分支没有构建
sys.exit(0)
userlist=""
#发送IM文案
msg=f"OK: 分支:{pull_request} sonar 代码质量检查通过\n具体请见URL:{durl} \n构建者:{build_user}"
if is_ok !="OK":
msg=f"ERROR: 分支:{pull_request} sonar 代码质量检查不通过\n具体请见URL:{durl} \n构建者:{build_user}"
leader=get_user_leader(build_user)
userlist=build_user+","+leader
#发送消息,这块也是根据自己家公司api实现即可
content = str(msg)
sendmsg(content, userlist)
#运行
check_scanner_result()
代码质量不通过阻断CI流程
不要上线后就阻断CI流程,是否阻断建议先和开发团队达成共识,然后在做决定
使用中遇到的问题
排除某些规则
和研发达成共识,某些策略可以忽略 比如:
调整如下:
排除某些文件不扫描
项目中包含Criteria.java 结尾的文件不扫码
某些项目编译出错,错误如下
ERROR: Error during SonarScanner execution
java.lang.IllegalStateException: Can not execute Findbugs
at org.sonar.plugins.findbugs.FindbugsExecutor.execute(FindbugsExecutor.java:188)
at org.sonar.plugins.findbugs.FindbugsSensor.execute(FindbugsSensor.java:130)
at org.sonar.scanner.sensor.AbstractSensorWrapper.analyse(AbstractSensorWrapper.java:64)
at org.sonar.scanner.sensor.ModuleSensorsExecutor.execute(ModuleSensorsExecutor.java:88)
at org.sonar.scanner.sensor.ModuleSensorsExecutor.lambda$execute$1(ModuleSensorsExecutor.java:61)
at org.sonar.scanner.sensor.ModuleSensorsExecutor.withModuleStrategy(ModuleSensorsExecutor.java:79)
at org.sonar.scanner.sensor.ModuleSensorsExecutor.execute(ModuleSensorsExecutor.java:61)
at org.sonar.scanner.scan.SpringModuleScanContainer.doAfterStart(SpringModuleScanContainer.java:81)
at org.sonar.core.platform.SpringComponentContainer.startComponents(SpringComponentContainer.java:188)
at org.sonar.core.platform.SpringComponentContainer.execute(SpringComponentContainer.java:167)
at org.sonar.scanner.scan.SpringProjectScanContainer.scan(SpringProjectScanContainer.java:396)
at org.sonar.scanner.scan.SpringProjectScanContainer.scanRecursively(SpringProjectScanContainer.java:392)
at org.sonar.scanner.scan.SpringProjectScanContainer.doAfterStart(SpringProjectScanContainer.java:361)
at org.sonar.core.platform.SpringComponentContainer.startComponents(SpringComponentContainer.java:188)
at org.sonar.core.platform.SpringComponentContainer.execute(SpringComponentContainer.java:167)
at org.sonar.scanner.bootstrap.SpringGlobalContainer.doAfterStart(SpringGlobalContainer.java:135)
at org.sonar.core.platform.SpringComponentContainer.startComponents(SpringComponentContainer.java:188)
at org.sonar.core.platform.SpringComponentContainer.execute(SpringComponentContainer.java:167)
at org.sonar.batch.bootstrapper.Batch.doExecute(Batch.java:72)
at org.sonar.batch.bootstrapper.Batch.execute(Batch.java:66)
at org.sonarsource.scanner.api.internal.batch.BatchIsolatedLauncher.execute(BatchIsolatedLauncher.java:46)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.base/java.lang.reflect.Method.invoke(Unknown Source)
at org.sonarsource.scanner.api.internal.IsolatedLauncherProxy.invoke(IsolatedLauncherProxy.java:60)
at com.sun.proxy.$Proxy0.execute(Unknown Source)
at org.sonarsource.scanner.api.EmbeddedScanner.doExecute(EmbeddedScanner.java:189)
at org.sonarsource.scanner.api.EmbeddedScanner.execute(EmbeddedScanner.java:138)
at org.sonarsource.scanner.cli.Main.execute(Main.java:112)
at org.sonarsource.scanner.cli.Main.execute(Main.java:75)
at org.sonarsource.scanner.cli.Main.main(Main.java:61)
Caused by: java.lang.IllegalStateException: One (sub)project contains Java source files that are not compiled (/data/data/jenkins-node/workspace/weapp-tomcat_master).
sonar.java.binaries was set to **/*
Sonar JavaResourceLocator.classFilesToAnalyze was empty
at org.sonar.plugins.findbugs.FindbugsConfiguration.buildMissingCompiledCodeException(FindbugsConfiguration.java:153)
at org.sonar.plugins.findbugs.FindbugsConfiguration.initializeFindbugsProject(FindbugsConfiguration.java:123)
at org.sonar.plugins.findbugs.FindbugsExecutor.execute(FindbugsExecutor.java:117)
... 31 more
ERROR:
ERROR: Re-run SonarScanner using the -X switch to enable full debug logging.
错误原因:当安装Findbugs插件后,JSP的默认profiles被设置为FindBugs Security JSP。
Findbugs分析的不是java源代码,而是编译后的class文件。通过下图进行配置忽略未编译的jsp文件。
下面是全局配置,对所有项目都生效:
针对某些项目生效:
在CI时针对该模块配置文件sonar-scanner.properties
单独写上 sonar.findbugs.allowuncompiledcode=true