SonarQube CE v7.7以降の解析結果をViolationsツールでGitHubのPRやGitLabのMRに連携する

前回の続き。

前回の記事では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-githubSonar 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も有償エディション限定になる可能性はあるかもしれないが。