SonarQube 代码扫描平台
一、SonarQube 简介
SonarQube 是一个开源的代码质量分析平台,以源代码为输入源(IDE 或 SCM),基于输入和平台预定义的规则,检查代码质量是否能达到预期,并提供许多代码改进建议
1.1 SonarQube 产生背景
日常软件开发工作当中,随着项目时间变长,开发人员编写的代码量也会越来越多,长此以往,代码量越来越庞大,却无法横量整体代码质量?若是要优化,也不知道如何优化
针对上面图中的问题,出现了各种各样的工具,比如:
Java 语言:Findbugs,PMD,CheckStyle 等,帮助检测代码编写规范上存在的问题和漏洞
Python 语言:Pyflakes,Pylint,pep8 等
C# 语言:FxCop、StyleCop 等
通过这些工具扫描的结果分析后,根据结果来优化代码问题,以此提高代码质量
以上这些工具都是 代码的静态扫描分析 工具,所谓静态代码分析,就是针对开发人员编写的源代码,在不运行的情况下,仅通过分析或检查源程序的语法、结构、过程、接口等来检查程序的正确性,找出代码隐藏的错误和缺陷
在单独使用以上这些工具时,我们会面临:
需要一个平台,能够汇总呈现不同语言的项目、不同工具的扫描结果
需要一个平台,可以友好的呈现或者追溯,一段时间内每一次扫描的结果的差异
于是,SonarQube 的重要性就体现出来了
1.2 SonarQube 基本特点
SonarQube 平台最核心的两个功能,一、支持多种语言的静态代码扫描,二、多维护呈现项目代码的质量状态
之所以业内普遍选择 SonarQube,主要是因为其有以下一些特点:
- 规则丰富/灵活:SonarQube 集成了 PMD、FindBugs、Checkstyle 等静态扫描规则,还支持自定义属于自己的规则(代码编写规范+安全规范)
- 多语言支持:SonarQube 支持对 20 多种编程语言进行静态分析
- API 完备:SonarQube 提交完善的 API,以便于与其他系统进行集成
- CI/CD:能够与 SCM、CI、CD 等平台完美集成
1.3 SonarQube 架构组成
SonarQube 采用 B/S
架构,通过客户端插件分析源代码,SonarQube 客户端可以采用 IDE
插件、Sonar-Scanner
插件、Ant
插件和 Maven
插件方式,并通过各种不同的分析机制对项目源代码进行分析和扫描,并把分析扫描后的结果上传到 SonarQube 数据库,最终通过 SonarQube WebUI 界面对分析结果进行管理
1.4 SonarQube 基本概念
SonarQube 可以从七个维度检测代码质量指标:
- 复杂度分布(
complexity
):代码复杂度过高将难以理解 - 重复代码(
duplications
):程序中包含大量复制、粘贴的代码而导致代码臃肿,sonar 可以展示源码中重复严重的地方 - 单元测试统计(
unit tests
):统计并展示单元测试覆盖率,开发或测试可以清楚测试代码的覆盖情况 - 代码规则检查(
coding rules
):通过 Findbugs,PMD,CheckStyle 等检查代码是否符合规范 - 注释率(
comments
):若代码注释过少,特别是人员变动后,其他人接手比较难接手;若过多,又不利于阅读 - 潜在的 Bug(
potential bugs
):通过 Findbugs,PMD,CheckStyle 等检测潜在的 bug - 结构与设计(
architecture & design
):找出循环,展示包与包、类与类之间的依赖、检查程序之间耦合度
除此外,还有几个重要的概念
- 代码分析规则:SonarQube 中可以通过插件提供的规则对代码进行分析并生成问题。规则中定义了修复问题的成本(时间),解决问题的代价以及技术债可以通过这些问题进行计算,规则有三种类型
- 可靠性(Bugs)
- 可维护性(异味)
- 安全性(漏洞)
- 质量阈:质量阈是一系列对项目指标进行度量的条件形成的阈值
二、SonarQube 安装
SonarQube 允许数据库和服务分离,甚至可以在多台计算机上进行数据库的安装和服务器的部署,以获得更好的性能和可扩展性,这里主要目的是学习了解,所以选择最简单的 Docker 容器方式安装,把数据库服务于应用跑在一起,如果需要更好的性能,也可以在其他服务器上分别部署
安装部署参考官方文档,命令如下:
SONARQUBE_HOME="/prodata/sonarqube"
$ mkdir -p ${SONARQUBE_HOME}/{conf,extensions,logs,data}
$ chowo -R 999:999 ${SONARQUBE_HOME}
$ docker run -d --name sonarqube \
-p 9000:9000 \
-v ${SONARQUBE_HOME}/conf:/opt/sonarqube/conf \
-v ${SONARQUBE_HOME}/extensions:/opt/sonarqube/extensions \
-v ${SONARQUBE_HOME}/logs:/opt/sonarqube/logs \
-v ${SONARQUBE_HOME}/data:/opt/sonarqube/data \
sonarqube:7.9.2-community
观察容器启动日志,当看到下面内容,说明服务已经启动成功
$ docker tail -f sonarqube
# ...
2021.11.17 13:43:41 WARN ce[][o.s.db.dialect.H2] H2 database should be used for evaluation purpose only.
2021.11.17 13:43:43 INFO ce[][o.s.s.p.ServerFileSystemImpl] SonarQube home: /opt/sonarqube
2021.11.17 13:43:43 INFO ce[][o.s.c.c.CePluginRepository] Load plugins
2021.11.17 13:43:44 INFO ce[][o.s.c.c.ComputeEngineContainerImpl] Running Community edition
2021.11.17 13:43:44 INFO ce[][o.s.ce.app.CeServer] Compute Engine is operational
2021.11.17 13:43:44 INFO app[][o.s.a.SchedulerImpl] Process[ce] is up
2021.11.17 13:43:44 INFO app[][o.s.a.SchedulerImpl] SonarQube is up
访问 SonarQube WebUI,地址:http://sonarqube-server:9000/ 用户名:admin 密码:admin
三、SonarQube 配置
3.1 设置中文语言
如果满屏英文看着费劲的话,可以安装对应的中文插件,操作路径:Administration → Marketplace → 输入 chinese,选择 “Chinese Pack” 中文简体插件
当安装下载失败,手动到jar-download 搜索下载,放置在 /extensions/plugins 目录中
点击重启,应用插件
等待重启后,再次登录,查看中文插件效果
3.2 修改默认密码
为了安全考虑,更改默认 admin 密码
3.3 设置强制登录
并且配置为强制登录,不登录啥也不给看
3.4 集中用户认证
SonarQube 同样支持 LDAP 方式集中用户认证,不过相较于 Jekins,SonarQube 认证更强大,在支持 LDAP 认证的同时,内置用户也是可以登录的
闲话少说,直接开始安装 LDAP 插件
当插件安装失败 或 长时间 hang 时,建议手动下载处理,[具体步骤](#3.5 安装语言插件)
配置文件:sonar.properties
$ vim /prodata/sonarqube/conf/sonar.properties
# LDAP Config
# LDAP Admin user
sonar.security.realm=LDAP
ldap.url=ldap://47.115.121.119:389
ldap.bindDn=cn=admin,dc=lotusching,dc=com
ldap.bindPassword=admin_passwd_4_ldap
# LDAP user match
ldap.user.baseDn=ou=Beijing,dc=lotusching,dc=com
ldap.user.request=(&(objectClass=inetOrgPerson)(cn={login}))
ldap.user.realNameAttribute=cn
ldap.user.emailAttribute=mail
重启访问,应用配置
$ docker restart sonarqube
$ docker logs -f sonarqube
# ...
2021.11.17 14:59:17 INFO ce[][o.sonar.db.Database] Create JDBC data source for jdbc:h2:tcp://127.0.0.1:9092/sonar
2021.11.17 14:59:17 WARN ce[][o.s.db.dialect.H2] H2 database should be used for evaluation purpose only.
2021.11.17 14:59:18 INFO ce[][o.s.s.p.ServerFileSystemImpl] SonarQube home: /opt/sonarqube
2021.11.17 14:59:18 INFO ce[][o.s.c.c.CePluginRepository] Load plugins
2021.11.17 14:59:18 INFO ce[][o.s.c.p.PluginInfo] Plugin [l10nzh] defines 'l10nen' as base plugin. This metadata can be removed from manifest of l10n plugins since version 5.2.
2021.11.17 14:59:19 INFO ce[][o.s.c.c.ComputeEngineContainerImpl] Running Community edition
2021.11.17 14:59:19 INFO ce[][o.s.ce.app.CeServer] Compute Engine is operational
2021.11.17 14:59:19 INFO app[][o.s.a.SchedulerImpl] Process[ce] is up
2021.11.17 14:59:19 INFO app[][o.s.a.SchedulerImpl] SonarQube is up
使用 LDAP 用户登录 SonarQube 平台
注销登录,使用 admin 登录平台,查看用户权限
3.5 安装语言插件
SonarQube 在安装插件时经常出现长时间 hang 在下载阶段,此时你会看到 extensions/downloads/<plugin_name>-<version>.jar.tmp
临时下载文件,如果长时间没有大小变动的话,我建议你也别等了,直接手动下载,然后丢到 plugins
目录重启运行
具体手动下载步骤:
浏览器访问插件更新中心
打开网页搜索,输入插件名称(
如 sonar-java-plugin-6.3.2.22818.jar
)拿到 jar 包下载地址后,手动下载并上传服务器(或 服务器配置国外代理下载)
如果觉得手动下载麻烦的话,可以在 sonar.properties 里配置插件代理,如下所示:
# LDAP Config
# LDAP Admin user
sonar.security.realm=LDAP
ldap.url=ldap://47.115.121.119:389
ldap.bindDn=cn=admin,dc=lotusching,dc=com
ldap.bindPassword=admin_passwd_4_ldap
# LDAP user match
ldap.user.baseDn=ou=Beijing,dc=lotusching,dc=com
ldap.user.request=(&(objectClass=inetOrgPerson)(cn={login}))
ldap.user.realNameAttribute=cn
ldap.user.emailAttribute=mail
# Proxy Config
http.proxyHost=172.17.0.1
http.proxyPort=8786
https.proxyHost=172.17.0.1
https.proxyPort=8786
配置完成后,重启服务
$ docker restart sonarqube
$ docker logs -f --tail 10 sonarqube
# ...
2021.11.18 14:55:56 INFO app[][o.s.a.SchedulerImpl] Process[es] is up
2021.11.18 14:55:56 INFO app[][o.s.a.ProcessLauncherImpl] Launch process[[key='web', ipcIndex=2, logFilenamePrefix=web]] from [/opt/sonarqube]: /usr/local/openjdk-11/bin/java -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djava.io.tmpdir=/opt/sonarqube/temp --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED -Xmx512m -Xms128m -XX:+HeapDumpOnOutOfMemoryError -Djava.security.egd=file:/dev/./urandom -Dhttp.proxyHost=172.17.0.1 -Dhttp.proxyPort=8786 -Dhttp.nonProxyHosts=localhost|127.*|[::1] -Dhttps.proxyHost=172.17.0.1 -Dhttps.proxyPort=8786 -cp
# ...
2021.11.18 14:59:14 INFO app[][o.s.a.SchedulerImpl] Process[ce] is up
2021.11.18 14:59:14 INFO app[][o.s.a.SchedulerImpl] SonarQube is up
访问 WebUI 查看 SonarQube 系统配置
再次下载插件尝试
3.6 体验代码扫描
安装好相关语言插件后,我们开始尝试体验下 SonarQube 代码扫描,首先,创建一个 Project
随后,在项目仓库中,通过 mvn 命令执行代码扫描
☁ simple-java-maven-app [master]$ mvn sonar:sonar \
-Dsonar.projectKey=simple-java-maven-app \
-Dsonar.login=f1c0107b0ab4732ffc85208f27c591260673aebc
执行效果
[INFO] Scanning for projects...
[WARNING] The artifact org.codehaus.mojo:sonar-maven-plugin:jar:3.9.0.2155 has been relocated to org.sonarsource.scanner.maven:sonar-maven-plugin:jar:3.9.0.2155: SonarQube plugin was moved to SonarSource organisation
[INFO]
[INFO] ----------------------< com.mycompany.app:my-app >----------------------
[INFO] Building my-app 1.1-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- sonar-maven-plugin:3.9.0.2155:sonar (default-cli) @ my-app ---
[INFO] User cache: /root/.sonar/cache
[INFO] SonarQube version: 7.9.2
[INFO] Default locale: "en_US", source code encoding: "UTF-8" (analysis is platform dependent)
[INFO] Load global settings
[INFO] Load global settings (done) | time=181ms
[INFO] Server id: BF41A1F2-AX0uI2Tc7gRC9XoQ84ku
[INFO] User cache: /root/.sonar/cache
[INFO] Load/download plugins
[INFO] Load plugins index
[INFO] Load plugins index (done) | time=85ms
[INFO] Plugin [l10nzh] defines 'l10nen' as base plugin. This metadata can be removed from manifest of l10n plugins since version 5.2.
[INFO] Load/download plugins (done) | time=12458ms
[INFO] Process project properties
[INFO] Project key: simple-java-maven-app
[INFO] Base dir: /tmp/simple-java-maven-app
[INFO] Working dir: /tmp/simple-java-maven-app/target/sonar
[INFO] Load project settings for component key: 'simple-java-maven-app'
[INFO] Load project settings for component key: 'simple-java-maven-app' (done) | time=83ms
[INFO] Load quality profiles
[INFO] Load quality profiles (done) | time=110ms
[INFO] Load active rules
[INFO] Load active rules (done) | time=1434ms
[INFO] Indexing files...
[INFO] Project configuration:
[INFO] 3 files indexed
[INFO] 0 files ignored because of scm ignore settings
[INFO] Quality profile for java: Sonar way
[INFO] ------------- Run sensors on module my-app
[INFO] Load metrics repository
[INFO] Load metrics repository (done) | time=98ms
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by net.sf.cglib.core.ReflectUtils$1 (file:/root/.sonar/cache/13ad013e7b135beb25756efaa4e75337/sonar-javascript-plugin.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of net.sf.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
[INFO] Sensor JavaSquidSensor [java]
[INFO] Configured Java source version (sonar.java.source): 11
[INFO] JavaClasspath initialization
[WARNING] Bytecode of dependencies was not provided for analysis of source files, you might end up with less precise results. Bytecode can be provided using sonar.java.libraries property.
[INFO] JavaClasspath initialization (done) | time=3ms
[INFO] JavaTestClasspath initialization
[INFO] JavaTestClasspath initialization (done) | time=10ms
[INFO] Java Main Files AST scan
[INFO] 1 source files to be analyzed
[INFO] Load project repositories
[INFO] Load project repositories (done) | time=66ms
[INFO] 1/1 source files have been analyzed
[INFO] Java Main Files AST scan (done) | time=1139ms
[INFO] Java Test Files AST scan
[INFO] 1 source files to be analyzed
[INFO] Java Test Files AST scan (done) | time=79ms
[INFO] Java Generated Files AST scan
[INFO] 0 source files to be analyzed
[INFO] Java Generated Files AST scan (done) | time=0ms
[INFO] Sensor JavaSquidSensor [java] (done) | time=1436ms
[INFO] Sensor SurefireSensor [java]
[INFO] parsing [/tmp/simple-java-maven-app/target/surefire-reports]
[INFO] Sensor SurefireSensor [java] (done) | time=2ms
[INFO] Sensor JavaXmlSensor [java]
[INFO] 1 source files to be analyzed
[INFO] 1/1 source files have been analyzed
[INFO] 0/0 source files have been analyzed
[INFO] Sensor JavaXmlSensor [java] (done) | time=137ms
[INFO] 1/1 source files have been analyzed
[INFO] ------------- Run sensors on project
[INFO] Sensor Zero Coverage Sensor
[INFO] Sensor Zero Coverage Sensor (done) | time=6ms
[INFO] Sensor Java CPD Block Indexer
[INFO] Sensor Java CPD Block Indexer (done) | time=10ms
[INFO] SCM provider for this project is: git
[INFO] 3 files to be analyzed
[INFO] 3/3 files analyzed
[INFO] 1 file had no CPD blocks
[INFO] Calculating CPD for 0 files
[INFO] CPD calculation finished
[INFO] Analysis report generated in 77ms, dir size=43 KB
[INFO] Analysis report compressed in 13ms, zip size=10 KB
[INFO] Analysis report uploaded in 161ms
# 执行完成,访问地址查看结果:http://192.144.227.61:9000/dashboard?id=simple-java-maven-app
[INFO] ANALYSIS SUCCESSFUL, you can browse http://192.144.227.61:9000/dashboard?id=simple-java-maven-app
[INFO] Note that you will be able to access the updated dashboard once the server has processed the submitted analysis report
# 任务结果,JSON 对象:http://192.144.227.61:9000/api/ce/task?id=AX0zpjwUaiskEnYQepjB
[INFO] More about the report processing at http://192.144.227.61:9000/api/ce/task?id=AX0zpjwUaiskEnYQepjB
[INFO] Analysis total time: 5.561 s
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 19.915 s
[INFO] Finished at: 2021-11-18T23:24:23+08:00
[INFO] ------------------------------------------------------------------------
此时,回到浏览器会发现页面已自动刷新,我们可以随便点点看看,里面的很多名词目前还不了解,无所谓,后面用着用着就明白了
3.7 项目质量配置
SonarQube 内置了一些规则,名为 “Sonar way”,如图所示:
我们可以创建规则集,如下所示:
配置项目应用自定义的规则集
3.8 项目质量阈
同样的,SonarQube 也有内置的质量阈
也支持自定义项目质量阈
四、SonarQube 深入
4.1 Jenkins 集成
4.1.1 安装插件
安装 SonarQube 扫描器插件 SonarQube Scanner
4.1.2 配置 Maven
前面,我们在执行 mvn sonar:sonar
扫描时,显式声明 sonar 服务端的地址,这部分我们可以省略掉,只需要在 maven 中配置下即可,如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.2.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.2.0 https://maven.apache.org/xsd/settings-1.2.0.xsd">
<pluginGroups>
<!-- 添加 SonarQube Scanner 插件组 -->
<pluginGroup>org.sonarsource.scanner.maven</pluginGroup>
</pluginGroups>
<proxies>
</proxies>
<servers>
</servers>
<mirrors>
<!-- 阿里云 Maven 镜像库 -->
<mirror>
<id>alimaven</id>
<name>aliyun maven</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<mirrorOf>central</mirrorOf>
</mirror>
</mirrors>
<profiles>
<!-- 添加 SonarQube 服务端地址 -->
<profile>
<id>sonar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<sonar.host.url>http://192.144.227.61:9000</sonar.host.url>
</properties>
</profile>
</profiles>
</settings>
配置好以后,可以再通过 mvn clean package && mvn sonar:sonar
进行扫描测试,如果 WebUI 上能看到最新的测试数据,则代表配置成功,这里就不演示了
4.1.3 安装 扫描器
$ curl https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.6.2.2472-linux.zip -o sonar-scanner-cli-4.6.2.2472
解压
$ unzip sonar-scanner-cli-4.6.2.2472-linux.zip -d /usr/local
$ mv /usr/local/sonar-scanner-4.6.2.2472-linux /usr/local/sonar-scanner-4.6.2.2472
配置
$ vim /etc/profile
export SONAR_SCANNER_HOME=/usr/local/sonar-scanner-4.6.2.2472
export PATH=$PATH:$SONAR_SCANNER_HOME/bin
$ . /etc/profile
确认
$ sonar-scanner -v
INFO: Scanner configuration file: /usr/local/sonar-scanner-4.6.2.2472/conf/sonar-scanner.properties
INFO: Project root configuration file: NONE
INFO: SonarScanner 4.6.2.2472
INFO: Java 11.0.11 AdoptOpenJDK (64-bit)
INFO: Linux 3.10.0-1160.31.1.el7.x86_64 amd64
4.1.4 配置 扫描器
(1)配置文件
上面执行获取版本号时返回配置文件路径,我们可以在这个文件定义好参数,这样后面在 Jenkinsfile 中就不必重复声明了
#----- Default SonarQube server
sonar.host.url=http://192.144.227.61:9000
sonar.login=c858b53879dda7707d4581cd415f7ef3cbe44b74
sonar.ws.timeout=30
#----- Default source code encoding
sonar.sourceEncoding=UTF-8
sonar.java.binaries=target/classes
sonar.java.test.binaries=target/test-classes
sonar.java.surefire.report=target/surefire-reports
除此外,还要配置下 Jenkins 那边,Jenkins 那主要是三个工作
- 创建 Jenkins Credentials(Secret text 管理员 token)
- SonarQube servers
- 全局工具配置
(2)创建凭证
(3)配置 SonarQube Server
访问路径:配置中心 → configuration
#####(4)配置 全局工具
4.1.5 配置 支持多分支插件
默认 SonarQube 社区版不支持多分支,所以这里我们引用一个第三方的插件,**sonarqube-community-branch-plugin**,这里是插件下载地址
下表是版本对照表,按照 SonarQube 下载对应的 插件 Release
SonarQube Version | Plugin Version |
---|---|
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 |
下载完之后,除了放入 extensions/plugins/
以外,还要拷贝一份放到 lib/common
$ cp extensions/plugins/sonarqube-community-branch-plugin-1.3.2.jar lib/common/
$ ls -alh extensions/plugins/sonarqube-community-branch-plugin-1.3.2.jar
-rw-r--r-- 1 root root 4.9M Nov 19 03:44 extensions/plugins/sonarqube-community-branch-plugin-1.3.2.jar
$ ls -alh lib/common/sonarqube-community-branch-plugin-1.3.2.jar
-rw-r--r-- 1 sonarqube sonarqube 4.9M Nov 19 03:51 lib/common/sonarqube-community-branch-plugin-1.3.2.jar
重启服务
$ docker restart sonarqube
构建日志(流水线文件见后文)
WebUI
SonarQube 多分支分为 短期分支 与 长期分支,短期分支在 30 天之内没有分析,就会被永久删除,所以我们需要按照需求调整下
按照组织内 Git 分支策略编写对应的分支(正则)匹配规则即可
补充一个查看任务历史的页面
4.1.6 配置 Sonar 凭证
创建 Username/Password 类型的 Jenkins 凭证,主要供以 Jenkins 共享库中的 sonarapi 使用
4.1.7 增加 共享库模块
Sonar Scanner 命令行模块:src/org/devops/sonarqube.groovy
package org.devops
// 静态扫描功能函数
def SonarScan(sonar_server, project_name, source_path, branch_name){
// 定义 SonarQube 服务器列表,这里的名称要与 配置中心 -> SonarQube servers 配置项匹配
def servers = ["default":"SonarQube-Server"]
// 格式化获取当前时间,作为扫描结果的版本号
def now = new Date()
def current_dt = now.format("yyyyMMddHHmmss", TimeZone.getTimeZone('CST'))
// 根据 sonar_server 传递来的变量,选择使用列表中对应的 SonarQube 服务器
withSonarQubeEnv("${servers[sonar_server]}"){
scannerHome = tool 'SonarQube Scanner'
sh """
${scannerHome}/bin/sonar-scanner \
-Dsonar.projectKey=${project_name} \
-Dsonar.projectName=${project_name} \
-Dsonar.sources=${source_path} \
-Dsonar.projectVersion=${current_dt} \
-Dsonar.branch.name=${branch_name}
"""
}
// 下面是来自于官方文档示例,等待扫描结果回调并判断,不过在实验中会遇到 任务长时间处于 IN_PROGESS、PENDING 等状态,所以暂不使用该方案
// 目前方案是基于 SonarQube API 实现类似功能,API 文档:http://<sonar_server>:<port>/web_api
//def qg = waitForQualityGate()
//if (qg.status != 'OK') {
//error "Pipeline aborted due to quality gate failure: ${qg.status}"
//}
}
SonarQube API 模块:src/org/devops/sonarapi.groovy
package org.devops
// 封装 HTTP
// 原生 API JSON 格式,野生 API FORM 格式
def HttpReq(reqType, api_url, reqBody, content_type='APPLICATION_JSON'){
result = httpRequest authentication: 'sonar-admin-user',
httpMode: reqType,
contentType: "${content_type}",
consoleLogResponseBody: true,
ignoreSslErrors: true,
requestBody: reqBody,
url: "${api_url}"
// quiet: true
return result
}
// 配置分支模式
def ConfigProjectBranchMode(sonar_server, project_name, branch_match_regex){
apiUrl = "${sonar_server}/api/settings/set"
// branch_match_regex 需要注意下,最好对 url_encode 下,不然可能会有问题
data = "component=${project_name}&key=sonar.branch.longLivedBranches.regex&value=${branch_match_regex}"
response = HttpReq("POST", apiUrl, data, "APPLICATION_FORM")
println(response)
return response
}
// 获取 Sonar 质量阈状态
def GetProjectStatus(sonar_server, project_name, branch_name){
apiUrl = """${sonar_server}/api/qualitygates/project_status?projectKey=${project_name}&branch=${branch_name}"""
response = HttpReq("POST", apiUrl, '')
response = readJSON text: """${response.content}"""
result = response["projectStatus"]["status"]
// println(response)
return result
}
// 搜索Sonar项目
def SerarchProject(sonar_server, project_name){
apiUrl = """${sonar_server}/api/projects/search?projects=${project_name}"""
response = HttpReq("GET", apiUrl, '')
response = readJSON text: """${response.content}"""
match_project_num = response["paging"]["total"]
if(match_project_num.toString() == "0"){
// 未搜索到对应的 SonarQube 项目
return "false"
} else {
return "true"
}
}
// 创建 Sonar 项目
def CreateProject(sonar_server, project_name){
apiUrl = """${sonar_server}/api/projects/create?name=${project_name}&project=${project_name}"""
response = HttpReq("POST", apiUrl, '')
println(response)
}
//配置项目质量规则
def ConfigQualityProfiles(sonar_server, project_name, language, qpname){
apiUrl = """${sonar_server}/api/qualityprofiles/add_project?language=${language}&project=${project_name}&qualityProfile=${qpname}"""
response = HttpReq("POST", apiUrl, '')
println(response)
}
// 获取质量阈 ID
def GetQualtyGateId(sonar_server, gate_name){
apiUrl= """${sonar_server}/api/qualitygates/show?name=${gate_name}"""
response = HttpReq("GET", apiUrl, '')
response = readJSON text: """${response.content}"""
// 当质量阈不存在时主动抛出异常
if (response.containsKey('errors')){
throw new Exception("配置阈 ${gate_name} 不存在,请确认 SonarQube 中是否创建配置.")
}
result = response["id"]
return result
}
// 配置项目质量阈
def ConfigQualityGates(sonar_server, project_name, gate_name){
gateId = GetQualtyGateId(sonar_server, gate_name)
apiUrl = """${sonar_server}/api/qualitygates/select?gateId=${gateId}&projectKey=${project_name}"""
response = HttpReq("POST", apiUrl, '')
println(response)
}
4.1.8 配置 流水线
首先,添加一个 project_name
变量,提供给后面上传 sonar 扫描结果使用
算了,还是贴一个完整的截图吧
Jenkinsfile 流水线文件
@Library('shareLibraryDemo') _
def util_tools = new org.devops.utils()
def toemail = new org.devops.toemail()
def sonar = new org.devops.sonarqube()
def sonarapi = new org.devops.sonarapi()
String branch_name = "${branch}".split('/')[-1]
String sonarqube_server = "http://192.144.227.61:9000"
currentBuild.description = "Trigger by ${committer_name} ${branch_name}"
pipeline {
agent {
node {
label "bj-tencent-lhins-1"
}
}
tools {
jdk 'Openjdk 11.0.13'
maven 'M2'
}
options {
timestamps()
}
stages {
stage('拉取代码') {
steps {
// branches 只拉取特定仓库的分支
// branches: [[name: '*/master'],]:
// 当远程仓库 master 分支提交时,触发 pipeline 拉取 master 分支
// 当远程仓库 develop 分支提交时,仍然触发 pipeline 拉取 master 分支
checkout([$class: 'GitSCM', branches: [
[name: "${branch}"],
], extensions : [], userRemoteConfigs: [
[credentialsId: 'gitee', url: "${env.GIT_URL}"]
]])
}
}
stage("打包应用") {
steps {
script {
util_tools.PrintColorMsg("编译/打包最新代码.", "blue")
sh "mvn package"
util_tools.PrintColorMsg("编译/打包完成.", "green")
}
}
}
stage("代码扫描") {
steps {
script {
scannerHome = tool 'SonarQube Scanner'
// 1. 搜索 SonarQube 项目是否存在
util_tools.PrintColorMsg("搜索 SonarQube 项目", "blue")
search_project_result = sonarapi.SerarchProject("${sonarqube_server}", "${project_name}")
println(search_project_result)
if (search_project_result == "false"){
println("${project_name}---项目不存在,准备创建项目---> ${project_name}!")
util_tools.PrintColorMsg("执行创建 SonarQube 项目,项目名:${project_name}", "blue")
sonarapi.CreateProject("${sonarqube_server}", "${project_name}")
util_tools.PrintColorMsg("创建完毕", "green")
} else {
println("${project_name}---项目已存在!")
}
util_tools.PrintColorMsg("搜索完毕", "green")
// 2. 设置分支模式
util_tools.PrintColorMsg("配置分支模式", "blue")
sonarapi.ConfigProjectBranchMode("${sonarqube_server}", "${project_name}", ".*(release%7Cmaster%7Cdevelop).*")
util_tools.PrintColorMsg("配置分支模式完成", "green")
// 3. 配置项目质量规则
// 应用默认规则 Sonar way
util_tools.PrintColorMsg("配置项目质量规则", "blue")
quality_profile_language = "java" // java, py, js, ts
// quality_profile_name ="Sonar%20way" // Sonar way 默认质量配置,拼接到 URL 所以需要 encode 后的字符串
quality_profile_name = "quality-demo"
sonarapi.ConfigQualityProfiles("${sonarqube_server}", "${project_name}","${quality_profile_language}", "${quality_profile_name}")
util_tools.PrintColorMsg("配置项目质量规则完成", "green")
// 4. 配置项目质量阈
// 应用自定义项目质量阈
util_tools.PrintColorMsg("配置项目质量规则", "blue")
quality_gate_name = "quality-gates-demo"
util_tools.PrintColorMsg("配置质量阈", "blue")
sonarapi.ConfigQualityGates("${sonarqube_server}", "${project_name}", "${quality_gate_name}")
util_tools.PrintColorMsg("配置质量阈完成", "green")
// 5. 静态扫描分析
util_tools.PrintColorMsg("开始执行静态扫描分析", "blue")
sonar.SonarScan("default", "${project_name}", "src", "${branch}")
util_tools.PrintColorMsg("扫描完成", "green")
util_tools.PrintColorMsg("等待获取扫描结果", "blue")
sleep 10
result = sonarapi.GetProjectStatus("${sonarqube_server}", "${project_name}", "${branch}")
println("扫描结果:${result}")
util_tools.PrintColorMsg("获取扫描结果完成", "green")
// 6. 质量阈校验
util_tools.PrintColorMsg("开始执行项目质量阈校验", "blue")
if (result.toString() == "ERROR"){
util_tools.PrintColorMsg("质量阈校验失败!请联系相关人员及时修复!!!", "red")
// toemail.Email("质量阈校验失败", committer_email, branch_name)
throw new Exception("代码质量阈校验失败!请联系相关人员及时修复!")
} else {
util_tools.PrintColorMsg("质量阈校验通过!", "green")
}
}
}
}
}
post {
success {
script {
util_tools.PrintColorMsg("构建成功~", "green")
toemail.Email("流水线构建成功了!", committer_email, branch_name)
}
}
failure {
script {
util_tools.PrintColorMsg("构建失败!", "red")
toemail.Email("流水线执行失败了!", committer_email, branch_name)
}
}
aborted {
script {
util_tools.PrintColorMsg("构建取消!", "red")
toemail.Email("流水线被取消了!", committer_email, branch_name)
}
}
always {
script {
util_tools.PrintColorMsg("清理临时产物", "green")
sh "mvn clean"
}
}
}
}
4.1.9 创建 质量配置、质量阈
在 Jenkinsfile 流水线文件我们定义使用了 自定义的 质量配置(quality-profile-demo) 与 质量阈(quality-gates-demo),主要用来跑通流程的,相关配置如下:
quality-profile-demo
quality-gates-demo
4.1.10 联调 流水线
首先,master 分支提交代码
Jenkins Server 接收到 Webhook 请求
查看流水线仪表盘
访问 BlueOcean WebUI
- 拉取代码
- 编译打包
代码扫描