Golang + Sonarqube + Codebuild


https://qiita.com/fake-deli-ca/items/e01a42a9d04cf0bab8c4
の後にJenkinsじゃなくてCodeBuildも対応してみたんだけど、異様にわかりにくくて、時間かかった…。

結論

最初に結論から。
関係する箇所のみ抜粋してある。

buildspec.yml
version: 0.2

env:
  variables:
   GO_VERSION      : ""  
   PROJECT_VERSION : ""  
   # 本来はParameter-Storeにすべき
   SONARQUBE_TOKEN  : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"


phases:
  install:
    runtime-versions:
      golang: ${GO_VERSION}
    commands:
      - type go && go version
      - chmod +x ./gradlew
      - export REGOPATH=`echo $GOPATH | cut -c 5-`
      - export PATH=${GOPATH}/bin:${PATH} && echo ${PATH}

  pre_build:
    commands:
      - echo 'setup'
      - make setup

  build:
    commands:
      - echo 'make & test'
      - make
      - make test
      - ./gradlew sonarqube -Dsonar.projectBaseDir=${REGOPATH}/src -Dsonar.login=${SONARQUBE_TOKEN} -Dsonar.projectVersion=${PROJECT_VERSION} --info

下記のMakefileはBuildの記載がないので、このままでは動作しない。

.PHONY: all
all: build

.PHONY: setup
setup:
    go get -u github.com/ory/go-acc

.PHONY: test
test:
    go test ./... -json > reports/test.json
    go-acc ./...  -o reports/coverage.out
build.gradle
plugins {
    id "org.sonarqube" version "3.0"
}

sonarqube {
    properties {
        //FIXME 
        property "sonar.host.url","https://sonarqube.foo.jp"

        property "sonar.projectKey", "hogehoge"
        property "sonar.projectName", "hogehoge Project"

        property "sonar.go.coverage.reportPaths", "(gitのpass)/reports/coverage.out"
        property "sonar.go.tests.reportPaths", "(gitのpass)/reports/test.json"
        //FIXME

        property "sonar.sources", "."

        property "sonar.exclusions", "**/*_test.go"
        property "sonar.tests", "."
        property "sonar.test.inclusions", "**/*_test.go"
        property "sonar.test.exclusions", "**/vendor/**"
        property "sonar.sourceEncoding", "UTF-8"
    }
}

確認すべき点

is not included in the project, ignoring coverage

Sensor Go Cover sensor for Go coverage [go]
Load coverage report from '/codebuild/output/src532592822/src/(gitのpass)/coverage.txt'
File '/codebuild/output/src532592822/src/(gitのpass)/AAA/BBB.go' is not included in the project, ignoring coverage

とにかく、is not included in the project, ignoring coverageにやられた。どう設定していいのか全く分からない。
https://github.com/SonarSource/sonar-go/issues/218
を読むとGOPATHの設定が悪い、と書いてあるが、どうすれば治るのかは書いてない。
酷い…。

結論を言うと、sonar.projectBaseDirの設定が悪い。ところが、皆様ご存じの通り、Codebuildは/codebuild/output/srcXXXXXXXXXX/srcの部分が毎回異なるため、何らかの利用できる変数で設定しなければならない。
普通に考えるとsonar.projectBaseDir=$GOPATH/srcとすればよいのだが、ところがである。Codebuildの$GOPATH/go:/codebuild/output/src532592822となっており、/go: の部分が心底余計なのである。
上記のように設定すると、

* What went wrong:
Execution failed for task ':sonarqube'.
> The folder '/go:/codebuild/output/src179970974/src' does not exist for 'xxxxx' (base directory = /codebuild/output/src179970974/src/(gitのpass))

みたいなエラーになるのである。まあ当然だが。
というわけで強引にcutで削除して再設定した。本当にこれでいいのかどうかは知らない。
また、本来はbuild.gradleに全部書きたかったのだが、当然環境変数と展開できないので、-Dsonar.projectBaseDir=${REGOPATH}/srcという設定とした。

reportPathsの変更

上記projectBaseDirの変更に伴う対応である。
普通、この辺のPathは$CODEBUILD_SRC_DIRの直下に置かれるので、gitのpassは本来不要なのであるが、projectBaseDirを変更した関係上、ここも変更が必要である。

        property "sonar.go.coverage.reportPaths", "(gitのpass)/reports/coverage.out"
        property "sonar.go.tests.reportPaths", "(gitのpass)/reports/test.json"

Gradleの利用

sonarqubeだけgradle使っててなんかおかしな感じではあるのだが、最初こんな感じでやってたらunzipのインストールが必要だったり、sonar-scannerのインストール位置が気持ち悪かったり、なんだか訳がわからなくなりそうだったので、あきらめて素直にgradle使うことにした。別に使わなくてもいいとは思う。ただ↓の設定でうまくいくところまでは試してない。少なくともGOPATHはこのままでは駄目だと思う。
また、gogradleで全部書き直すことも考えたが、なんかそこまでして綺麗にする理由もなかったのでやめ。

buildspec.yml
phases:
  install:
    runtime-versions:
      golang: ${GO_VERSION}
    commands:
      - type go && go version
      # sonar-scanner install
      - yum -y install unzip
      - wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-4.4.0.2170-linux.zip
      - unzip ./sonar-scanner-cli-4.4.0.2170-linux.zip
  pre_build:
    commands:
      - echo 'setup'
      - export PATH=${GOPATH}/bin:./sonar-scanner-4.4.0.2170-linux/bin:${PATH} && echo ${PATH}
      - make setup

  build:
    commands:
      - echo 'make & test'
      - make
      - make test
      - sonar-scanner -X -Dsonar.login=${SONARQUBE_TOKEN}

go-accの利用

ここまで実施してみて、どうもCoverage率が異様に低いことに気が付く。
これも結論だけをいうと、go test -coverのデフォルト挙動に問題があるということである。なので、go-accを利用することとした。

The reason for this was that go test -cover is per default only recording code coverage for the package that is currently tested, not for all the packages from the project. If you have a lot of integration tests, your code coverage might be higher than you think!

…と結論だけなら簡単だが、ここに辿り着くまでに大層時間かかった。

I found a brutal hack of a workaround. I'm trying to report coverage on a project with a single golang module, and all of the module's files are below a single subdirectory in the git repo. We use go-acc to measure coverage, it writes file coverage.txt. I added a step in the build script to rewrite the prefix on the paths in that file, change the module name to the directory name, kinda like this:
sed -i -e 's/^mymodule/ModuleSubdir/' coverage.txt
Then the sonar scanner is happy. I don't know if the coverage numbers are correct, but they ain't zero no more

https://jira.sonarsource.com/browse/SONARSLANG-450
大本は、is not included in the project, ignoring coverageを解決すべく探していた際に見つけた上記であるが、結果的にソースコードのCoverage登録そのものとは関係なく、別の問題を解決することになった。

うーん。

なんかもうちょっと綺麗にしたい気分ではあるんだけど…。
というのと、情報が少なくてきついですね。