工作中靠人去做 CodeReview 是很困难的,也容易漏掉问题,所以公司往往需要自动化的代码扫描来卡点,SonarQube 就是这样一个业界成熟使用的服务。
# Docker 方式安装
注意修改下面 docker-compose.yml 里的 SONAR_JDBC_PASSWORD
和 POSTGRES_PASSWORD
version: "3"
services:
sonarqube:
image: mc1arke/sonarqube-with-community-branch-plugin:8.9-community
depends_on:
- db
environment:
TZ: Asia/Shanghai
SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
SONAR_JDBC_USERNAME: sonar
SONAR_JDBC_PASSWORD: password
SONAR_WEB_JAVAOPTS: -Xmx1G -Xms512M
SONAR_SEARCH_JAVAADDITIONALOPTS: -Xmx2G -Xms2G
SONAR_CE_JAVAOPTS: -Xmx2G -Xms1G
SONAR_WEB_SSO_ENABLE: true
SONAR_WEB_SSO_LOGINHEADER: X-Forwarded-DingTalk-Login
SONAR_WEB_SSO_NAMEHEADER: X-Forwarded-DingTalk-Name
SONAR_WEB_SSO_EMAILHEADER: X-Forwarded-DingTalk-Email
SONAR_WEB_SSO_GROUPSHEADER: X-Forwarded-DingTalk-Groups
SONAR_WEB_SSO_REFRESHINTERVALINMINUTES: 30
volumes:
- sonarqube_data:/opt/sonarqube/data
- sonarqube_extensions:/opt/sonarqube/extensions
- sonarqube_logs:/opt/sonarqube/logs
ports:
- "9000:9000"
db:
image: postgres:13
environment:
TZ: Asia/Shanghai
POSTGRES_USER: sonar
POSTGRES_PASSWORD: password
volumes:
- sonarqube_postgresql:/var/lib/postgresql
- sonarqube_postgresql_data:/var/lib/postgresql/data
- /etc/localtime:/etc/localtime:ro
volumes:
sonarqube_data:
sonarqube_extensions:
sonarqube_logs:
sonarqube_postgresql:
sonarqube_postgresql_data:
更多 Docker 环境变量参数参考 environment-variables
上面调整了 SonarQube 的内存参数(参考论坛) 和 SSO 认证(下面会讲这个是为了免密登录)
最后直接 docker compose up -d 启动整个服务
# 插件规则
SonarQube 里自带了 SonarWay 规则,我们也可以在插件市场里安装 FindBugs 规则,然后通过继承的方式创建自定义规则集合。需要注意的是,规则越多,扫描耗时越长。
另外不是所有的规则都是非常合理的,我们可以手动禁用不想要的规则。
可能我们还想集成阿里的 p3c 规则,但是 SonarQube 插件市场里没有现成的插件,网上的插件也很久没有更新了,其实 p3c 本质上是用的 PMD, 我们可以 Fork sonar-pmd 插件,然后自己集成 p3c-pmd 规则 进去。
新增
PmdP3CRulesDefinition.java
配置类
修改PmdPlugin.java
引入PmdP3CRulesDefinition.class
运行sonar-pmd-plugin/src/test/java/org/sonar/plugins/pmd/p3c/P3cXmlBuilder.java
测试类生成 p3c 的配置文件
单独跑sonar-pmd-plugin mvn install
打包
将打好的 Jar 包手动拷贝到 SonarQube 容器的插件目录下,然后重启
# 项目集成
在 pom.xml 中添加 sonar-maven-plugin 的插件
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.11.0.3922</version>
</plugin>
由于 sonar 支持自定义参数 analysis-parameters, 所以我们可以上报 git commit 等信息,后续通过 SonarQube 的 webhook 发送扫描报告结果就可以带上这些信息,生成一个内容丰富的卡片了。
这里我写了一个简单的脚本来生成 maven 的 sonar 编译参数,其中为了防止同一个 commit 重复扫描,会调用一个内部服务判断该 commit 是否已扫描,扫描结果通过 webhook 来维护。
#!/bin/bash
# 获取脚本传入的环境参数
while getopts ":e:" opt; do
case ${opt} in
e )
env=$OPTARG
;;
\? )
echo "Usage: scriptname [-e env]"
exit 1
;;
esac
done
shift $((OPTIND -1))
if [ -z $env ]; then
env=${SONAR_ENV:-}
fi
# 判断 commit 是否已经被扫描过了
check_exist_commit_id() {
local commit_id="$1"
local body_response
body_response=$(curl -m 5 --connect-timeout 3 -s "http://x.x.x.x/devops/sonarqube/exist?commitId=$commit_id")
if [[ "$body_response" == "true" ]]; then
echo "true"
else
echo "false"
fi
}
# 是否开启 sonar 扫描开关
sonar_switch=$NO_SONAR
sonar_host=${SONAR_HOST_URL:-}
sonar_token=${SONAR_LOGIN:-}
if [ "$sonar_switch" = "1" ] || [ -z "$sonar_host" ] || [ -z "$sonar_token" ]; then
exit
fi
commit_id=$(git rev-parse HEAD)
if [ -z $commit_id ]; then
exit
fi
exists_commit_id=$(check_exist_commit_id $commit_id)
if [[ "$exists_commit_id" == "true" ]]; then
exit
fi
# 将项目文件夹名称作为 key
key=$(basename "$(pwd)")
branch=$(git rev-parse --abbrev-ref HEAD)
if [ -z "$branch" ]; then
exit
fi
# 提交时间
time=$(git show -s --format='%ct')
author=$(git show -s --format=%an)
message=$(git show -s --format=%s | base64 -w 0)
analysis_id=$1
if [ -z "$uuid" ]; then
analysis_id=$(uuidgen | sed 's/-//g')
fi
ip=$(hostname -i | awk '{if (NF > 1) print $NF; else print $1}')
analysis_properties="-Dsonar.analysis.author=$author -Dsonar.analysis.time=$time -Dsonar.analysis.message=$message -Dsonar.analysis.env=$env -Dsonar.analysis.ip=$ip"
echo " sonar:sonar -Dsonar.host.url=$sonar_host -Dsonar.projectKey=$key -Dsonar.login=$sonar_token -Dsonar.branch.name=$branch -Dsonar.buildString=$analysis_id $analysis_properties "
# WebHook
通过 webhook 我们可以拿到之前上报的信息,以及你配置的质量阈的扫描结果。
# 免密登录
每次收到机器人发送的扫描报告,我们点进去都需要登录 SonarQube 的账号(你也可以纯内网访问,放开 SonarQube 的安全认证,但是不太安全啊)
查看文档发现,SonarQube 的认证方式有很多种 authentication
尤其是它支持 HTTP Header 的方式代理认证,这就是我们上文在 docker-compose.yml 里配置的几个 SSO 参数。
那我们要怎么实现点击链接,代理 Header 进去认证呢,肯定是需要一层 Nginx 代理的。为了方便,这里我直接起了一个 OpenResty 的 Docker 服务。
链接上带上签名参数,在 OpenResty 中校验签名,校验通过则添加相关 Header.
这里也许可以接入钉钉的认证,这种方式更好。