前回の続き。
前回の記事ではSpotBugsやPMD/CPDによる解析結果をマージリクエスト(GitHubなどのプルリクエストに相当、以下MR)連携したが、業務ではSonarQube CEで静的解析をしている。
SonarQube CEの場合、v7.6まではプレビューモードで解析することで、各種連携を行うことができたが、v7.7で該当機能が廃止された。
無償のCEでは連携はできなくなったと思っており、各種プラグインでもバージョンをv7.6まで下げる必要があると思っていたが、SonarQubeの解析結果をjqで加工して、Violationsツールを用いて連携することができたのでメモ。
環境
SonarQube CE 8.3, jq 1.6, violation-comments-to-gitlab-gradle-plugin 1.14.3。
問題
SonarQubeのプルリクエスト連携機能は存在するが、有償のDeveloper Edition以上で利用できる機能のため、無償のCommunity Editionでは利用できない。
SonarQube CEとGitHubのPRやGitLabのMR連携の方法を調べると、sonar-githubやSonar GitLab Pluginが出てくるが、どちらもSonarQubeのバージョンがv7.7未満の必要がある。これは、解析結果の取得にプレビューモード(結果をパブリッシュせず、分析のみ実行する)を利用していたが、そのオプションがv7.7で廃止されたため。
現在利用しているSonarQubeは古めのv8.3だが、v7.6まで下げると対応するJavaバージョンが8まで下がってしまう。また、有償版にする予定はなし。
対応
SonarQubeのAPIで問題を取得し、v7.6の解析結果と同じフォーマットに整形してやれば、連携できるのではないかと考えた。
ちょうど前後してViolationツールを発見したが、対応する静的解析ツールにSonarQubeも含まれている。
READMEのSonarQubeのNotesを見ると、以下のような記載がある。
With
mvn sonar:sonar -Dsonar.analysis.mode=preview -Dsonar.report.export.path=sonar-report.json
. Removed in 7.7, see SONAR-11670 but can be retrieved with:curl --silent 'http://sonar-server/api/issues/search?componentKeys=unique-key&resolved=false' | jq -f sonar-report-builder.jq > sonar-report.json
.
これがまさにやろうとしていた、「SonarQubeのAPIで問題を取得し、v7.6の解析結果と同じフォーマットに整形」だった。
使用例
SonarQubeの解析結果を整形し、GitLab CIからViolation CommentsのGitLab Gradle PluginプラグインでGitLabのMRにコメントする手順を記載。
Gradle以外のプロジェクト管理ツールや、GitHub連携も、利用するプラグインを変更すれば同様の方法で可能と思われる。
SonarQubeの解析結果の整形
記載されていたコマンドは、SonarQubeのAPIでissue(解析結果)を取得し、それを「sonar-report-builder.jq」というファイル内のフォーマットで整形して、「sonar-report.json」として出力するもの。
この「sonar-report-builder.jq」だが、SonarQubeコミュニティのこのスレッドに添付されている「sonar-report-builder.jq.txt」だと思われる。
以下内容。
# Convert sonarqube's issues search api json to sonar-report.json { "version": "7.8", "users": [], "issues": [ .issues[] | { "creationDate": .creationDate, "isNew": true, "status": "OPEN", "rule": .rule, "severity": .severity, "key": .key, "component": .component, "line": .textRange | .startLine, "startLine": .textRange | .startLine, "startOffset": .textRange | .startOffset, "endLine": .textRange | .endLine, "endOffset": .textRange | .endOffset, "message": .message } ], "components": [ .components[] | if .qualifier == "TRK" then { "key": .key } else { "status": "CHANGED", "moduleKey": .key | split(":src")[0], "path": .path, "key": .key } end ] }
このファイルを「config/sonar/sonar-report-builder.jq.txt」に保存し、「build/sonar-report.json」としてフォーマット変更後のファイルを出力するコマンドは以下。
curl --silent "https://${SONAR_HOSTNAME}/api/issues/search?componentKeys=${SONAR_PROJECT_KEY}&resolved=false" | jq -f config/sonar/sonar-report-builder.jq.txt > build/sonar-report.json
Violation Commentsプラグインの設定
前回の設定で記述した、
build.gradleの violationCommentsToGitLab
タスクを変更する。
SonarQubeと解析内容が重複するSpotBugとPMD/CPDを除外し、SonarQubeの設定を追加。Checkstyleは、SonarQubeにプラグインを追加して、そちらに解析を任せる手もあるが、今回は別々に解析することとした。
task violationCommentsToGitLab(type: ViolationCommentsToGitLabTask) { // 前略 violations = [ ['SONAR', buildDir.absolutePath, '.*/sonar-report\\.json\$', 'SonarQube'], ['CHECKSTYLE', file("${buildDir}/reports/checkstyle").absolutePath, '.*/main\\.xml\$', 'Checkstyle'], ] // 後略 }
.gitlab-ci.yml設定
前回の設定に、 sonarqube
タスクおよびSonarQubeのAPI実行を追加すればいい。
script: - ./gradlew check sonarqube -x test - curl --silent "https://${SONAR_HOSTNAME}/api/issues/search?componentKeys=unique-key&resolved=false" | jq -f config/sonar/sonar-report-builder.jq.txt > build/sonar-report.json - |- ./gradlew violationCommentsToGitLab \ -PgitLabUrl=${CI_SERVER_URL} \ -PprojectId=${CI_PROJECT_ID} \ -PmergeRequestIid=${CI_MERGE_REQUEST_IID} \ -PgitLabApiToken=${VIOLATION_API_TOKEN}
これでSonarQubeの解析結果を、GitLabのMRにコメントすることができた。
注意点
今回利用したSonarQubeのissue検索APIでは、SonarQube上のすべてのissueが結果に含まれる。
MRのソースブランチとターゲットブランチ間の差分で、指摘としてコメントするかを判定しているため、ターゲットブランチにはmainなどの基底ブランチをマージしていないが、ソースブランチにはマージしている場合など、他ブランチで発生した差分がソースブランチに含まれる場合、それらにもコメントされる。
振り返り
何とかSonarQubeのバージョンを変えずにMR連携することができた。
SonarQubeのissue検索APIが廃止されることはないと思うので、しばらくはこの方法でCommunity EditionでもPR/MR連携ができそう。
APIも有償エディション限定になる可能性はあるかもしれないが。