※最近のSonarQubeの場合はmavenなら<sonar.qualitygate.wait>true</sonar.qualitygate.wait>
が使えるみたいなので、そちらを使った方がシンプルになる点に注意。
SonarQubeのQuality Gateの結果をGitLabのPipelineで受け取ってデプロイ判定に利用するFeasibility検証した時のメモ。
SonarQubeにはQuality Gateと呼ばれる、コードの品質(≠バグの量)が一定以上かどうかを多角的な条件から判断する機能がある。Quality Gateによって一定品質に満たしていないと判断されたコードは仮にテストで問題が検出されなくてもデプロイしたくない場合がある。このようなニーズにおいて、Quality Gateの結果によってデプロイすべきかどうかをGitLabのパイプライン上で判断するための実機検証となる。
確認内容
確認したい内容は以下のフローが実装できるか、になる。ただし、SonarQubeの分析結果を受け取ったところから先はFeasibility確認は出来ているため、実際に確認するのは分析結果を受け取って、分析結果からイメージビルドを実行するかどうか、のところまでとなる。
前提
以下が利用できるものとする。
- GitLab
- GitLab Runner
- SonarQube
前準備
空のGitリポジトリを用意し、JUnit5のサンプルのmaven部分をコピーしておく。
git clone https://github.com/junit-team/junit5-samples.git
git clone https://mygitlab.info/myapp/junit5-jupiter-starter-maven.git
cp -a junit5-samples/junit5-jupiter-starter-maven/* junit5-jupiter-starter-maven/
cd junit5-jupiter-starter-maven
git add *
git commit -m "nocomment"
git push origin main
また、JaCoCoのカバー率を利用してQualityGateのSuccess/Failureを制御したいため、JaCoCoを利用するための修正を含めておく。
pom.xmlの修正内容は以下になる。
@@ -16,6 +16,11 @@
<dependencyManagement>
<dependencies>
+ <dependency>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>0.8.8</version>
+ </dependency>
<dependency>
<groupId>org.junit</groupId>
@@ -36,6 +41,11 @@
<build>
<plugins>
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>0.8.8</version>
+ </plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
次に、SonarQubeのProjectを作成し、一度スキャンは実施しておく。Project作成〜スキャン実施の詳細は割愛するが、最終的には以下のようなコマンドをソースコードのrootディレクトリ上で実行することになる。
mvn sonar:sonar \
-Dsonar.projectKey=maven-sample \
-Dsonar.host.url=https://mysonarqube.info \
-Dsonar.login=2577f240df389a5e4fbfaf6ff2fc5c52626aafc7
また今回のサンプルでQuality GatesでFailedを起こすための厳しいQuality Gatesを作成しておく。
SonarQubeのUI上の上部のQuality GatesからCreateを選択し、Line Coverageが80%以下だとFailedになるQuality Gatesを作成する。
作成後、ProjectのProject Settings -> Quality Gateから"Always use a specific Quality Gate"を選択し、作成したQuality Gateを選択してSaveする。
パイプラインの作成
以下の手順で構築する。
- GitLabがSonarQubeのWebhookを受け取るための設定
- SonarQubeのWebhook設定
- .gitlab-ci.ymlの作成
- テストラン
GitLabがSonarQubeのWebhookを受け取るための設定
GitLabのプロジェクトでSettings -> CI/CD -> Pipeline triggersを選択し、Tokenを作成する。
作成後、下の"Use webhook"に表示されているURLをコピーし、REF_NAME
をブランチ名、TOKEN
を先程取得したTokenに置き換える。
https://mygitlab.info/api/v4/projects/5/ref/main/trigger/pipeline?token=95218b76965deee8494d2bca7a5a37
SonarQubeのWebhook設定
SonarQubeのProjectを選択後、Project Settings -> Webhookから"Create"を選択する。
Nameは好みで、URLは先程GitLabで取得した"Use webhook"の値を設定する。Secretは最初から値が入っているが、そのままで問題ない。
.gitlab-ci.ymlの作成
GitLabのプロジェクトに戻り、CI/CD -> Editorを選択し、.gitlab-ci.ymlを作成する。
.gitlab-ci.ymlでは以下のステージを実装する。
- sonarqube-scan:SonarQubeの呼び出し
- build-image:Quality Gateの結果がFAILEDだと停止、SUCCESSならイメージビルド
.gitlab-ci.ymlの先頭に以下を記載する。
stages:
- sonarqube-scan
- build-image
sonarqube-scan
先程実行したコードスキャンをmavenイメージを使ってpush時のみ実行する。.gitlab-ci.ymlの記載は以下になる。
sonarqube-scan:
image: maven:3-jdk-11
rules:
- if: '$CI_PIPELINE_SOURCE == "push"'
stage: sonarqube-scan
script:
- |
set -x
mvn jacoco:prepare-agent test jacoco:report sonar:sonar -Dsonar.projectKey=maven-sample -Dsonar.host.url=https://mysonarqube.info -Dsonar.login=2577f240df389a5e4fbfaf6ff2fc5c52626aafc7
build-image
SonarQubeのwebhook設定により、スキャン完了後にwebhook契機で実行するステージとなる。
SonarQubeから飛んでくるPayloadはこちらに例が記載してある。
必要な箇所だけ抜き出すと、以下の部分になる。
{
"qualityGate": {
"conditions": [
:(省略)
]
"name": "SonarQube way",
"status": "OK"
}
}
これを利用したステージは以下となる。
build-image:
image: gcr.io/kaniko-project/executor:debug
rules:
- if: $TRIGGER_PAYLOAD
stage: build-image
before_script:
wget -O ./jq https://github.com/stedolan/jq/releases/download/jq-1.6/jq-linux64 && chmod +x ./jq
script:
- |
set -x
QUALITY_GATE_STATUS=`cat ${TRIGGER_PAYLOAD} | ./jq -r ".qualityGate.status"`
if [ "$QUALITY_GATE_STATUS" == "OK" ]; then
echo "Starting Build Image"
else
echo "Failed. Aborting."
exit 1
fi
webhook契機でPipelineが呼び出される場合はTRIGGER_PAYLOAD
環境変数が設定されるため、これがある時のみ動くようにする。
script:
内ではTRIGGER_PAYLOAD
をjqによりパースしてQualityGateのstatusを抽出している。今回イメージビルドするところは確認対象外なので、実装はif文による分岐までとしている。
テストラン
Quality GateでLine Coverageが80%以下ならFailedになるように設定しているため、Line Coverageを調整しながらパイプラインの実行結果を確認する。
実際にコードに修正してpushした結果はこちら。
1つ目のパイプライン(#67)でSonarQubeによるコードスキャンを実施し、2つ目のパイプライン(#68)でQuality Gateの結果を確認している。
サンプルの初期状態ではカバレッジは100%なので、build-imageのジョブの実行結果は以下のように成功した。
+ + ./jq -r .qualityGate.status
cat /builds/myapp/junit5-jupiter-starter-maven.tmp/TRIGGER_PAYLOAD
+ QUALITY_GATE_STATUS=OK
+ '[' OK '==' OK ]
+ echo 'Starting Build Image'
Starting Build Image
Cleaning up project directory and file based variables
00:00
Job succeeded
次にGitLabのUIからsrc/main/java/com/example/project/Calculator.java
をEditで開き、以下の修正を加える。
package com.example.project;
public class Calculator {
public int add(int a, int b) {
return a + b;
}
+ public int sub(int a, int b) {
+ return a - b;
+ }
}
これをCommit changeで保存すると、再度パイプラインが実行される。この時、JUnitのテストコードの不足によりカバレッジは66.67%と低下し、Quality Gateの80%の基準に未達となる。build-imageのジョブも以下のような結果となり、ジョブの実行は失敗する。
+ ./jq -r .qualityGate.status
+ cat /builds/myapp/junit5-jupiter-starter-maven.tmp/TRIGGER_PAYLOAD
+ QUALITY_GATE_STATUS=ERROR
+ '[' ERROR '==' OK ]
+ echo 'Failed. Aborting.'
Failed. Aborting.
+ exit 1
Cleaning up project directory and file based variables
00:00
ERROR: Job failed: command terminated with exit code 1
以上より、想定通りの動作確認が出来た。
まとめ
Quality Gateの結果をwebhookを使うことでGitLabのパイプラインで取得して、デプロイ可否の判断材料として利用できる見込みを得た。