SonarQube 代码扫描平台集成


SonarQube 代码扫描平台

一、SonarQube 简介

SonarQube 是一个开源的代码质量分析平台,以源代码为输入源(IDE 或 SCM),基于输入和平台预定义的规则,检查代码质量是否能达到预期,并提供许多代码改进建议

1.1 SonarQube 产生背景

日常软件开发工作当中,随着项目时间变长,开发人员编写的代码量也会越来越多,长此以往,代码量越来越庞大,却无法横量整体代码质量?若是要优化,也不知道如何优化

针对上面图中的问题,出现了各种各样的工具,比如:

  • Java 语言:Findbugs,PMD,CheckStyle 等,帮助检测代码编写规范上存在的问题和漏洞

  • Python 语言:Pyflakes,Pylint,pep8 等

  • C# 语言:FxCop、StyleCop 等

通过这些工具扫描的结果分析后,根据结果来优化代码问题,以此提高代码质量

以上这些工具都是 代码的静态扫描分析 工具,所谓静态代码分析,就是针对开发人员编写的源代码,在不运行的情况下,仅通过分析或检查源程序的语法、结构、过程、接口等来检查程序的正确性,找出代码隐藏的错误和缺陷

在单独使用以上这些工具时,我们会面临:

  1. 需要一个平台,能够汇总呈现不同语言的项目、不同工具的扫描结果

  2. 需要一个平台,可以友好的呈现或者追溯,一段时间内每一次扫描的结果的差异

于是,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 目录重启运行

具体手动下载步骤:

  1. 浏览器访问插件更新中心

  2. 打开网页搜索,输入插件名称(如 sonar-java-plugin-6.3.2.22818.jar

  3. 拿到 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 那主要是三个工作

  1. 创建 Jenkins Credentials(Secret text 管理员 token)
  2. SonarQube servers
  3. 全局工具配置
(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

  • 拉取代码

  • 编译打包

  • 代码扫描


文章作者: Da
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Da !
  目录