現在、サーバーサイド Kotlin を使ったプロジェクトの実装をしており、CIに CircleCI を使っています。
CircleCIでは、各JobをDockerコンテナを使って動かせるので、そこでGroovyを使ってみてもいいかなと思って実践してみました。
なぜGroovy?
例えば、ビルド前に以下のようなチェックを実施したいとします。
- Flyway のマイグレーションファイルのバージョン番号が重複していないかチェックしたい
複数人で並行して開発していると、マージするタイミングによって、Flywayのマイグレーションファイルのバージョン番号が重複するかもしれません。 (本当はちゃんとマージされる前に気づいてbumpしろって話ですが難しい)
もし間違って重複したままマージされても、デプロイされなければ(リポジトリは汚れるけど)動作の実害は起きないので、CIでビルド前・デプロイ前にチェックしたいです。
おそらく、どんなCIツールにしてもシェルスクリプトは使えるはずですが、シェルスクリプトで書くのはちょっとしんどい…。
そして、Kotlinを使ってるので、Java的言語ならみんな読めるはずだろう。
だけど、JavaやKotlinで書くと、ビルドしてクラスファイルを作って java
コマンドで…とするほど大きな処理ではない。
そこでGroovyです。
例
実際に書いてみた例がこちらです。
Flywayのマイグレーションファイルは、以下のフォーマットです。
V1.2.3.4__description.sql
前提条件
- Flywayのマイグレーションファイルは、
application/src/main/resources/db/migration
に保存されている - チェック用のGroovyスクリプトは
devops
フォルダに保存されている - JobでのGroovyの実行環境は、オフィシャルのDockerコンテナを使う
ソースコード
// マイグレーションファイルのパターン
def regexFileName = "^V([\\d+.])*\\d__.+\\.sql\$"
// ファイルが保存されているパスを指定
def directory = new File("${System.getProperty("user.dir")}/application/src/main/resources/db/migration")
// ファイル一覧を取得
def fileList = directory.listFiles()
// マイグレーションファイルのうち、バージョン番号でグループ化してカウント
def duplicatedVersions = fileList
.findAll { it.name.matches(regexFileName) }
.countBy { getVersion(it.name) }
.findAll { it.value > 1 }
// もしリストが空でないならエラー
if (duplicatedVersions.size() > 0) {
println("Error: Duplicated flyway migration found:")
duplicatedVersions.keySet()
.sort { sortByVersion(it) }
.each {
println(" $it")
fileList.findAll { f -> f.name.startsWith(it) }
.collect { it.name }
.sort()
.each {println(" $it")}
}
// 終了コードを0以外にして、CIでエラー扱いとする
System.exit(1)
} else {
println("All flyway migration files have valid name:")
fileList.findAll { it.name.matches(regexFileName) }
.sort { sortByVersion(getVersion(it.name)) }
.each { println(" ${it.name}") }
}
private static sortByVersion(String fileName) {
def versionNumbers = fileName.replaceFirst("V", "")
if (versionNumbers.length() == 0) {
return []
}
versionNumbers.split("\\.").collect { Integer.parseInt(it) }
}
private static String getVersion(String fileName) {
fileName.split("__", 2).first()
}
version: 2.1
jobs:
check-flyway-migration:
docker:
- image: groovy:4.0-alpine
steps:
- checkout
- run: groovy devops/check_duplicated_flyway_migration.groovy
test-build:
steps:
- checkout
- echo テストしてビルドする
deploy:
steps:
- echo デプロイする
workflows:
version: 2
build-deploy:
jobs:
- check-flyway-migration
- test-build
- deploy:
requires:
- check-flyway-migration
- test-build
実際にCircleCIで動かしてみる
重複している場合
#!/bin/sh -eo pipefail
groovy devops/check_duplicated_flyway_migration.groovy
WARNING: Using incubator modules: jdk.incubator.vector, jdk.incubator.foreign
Error: Duplicated flyway migration found:
V3.0.0.0
V3.0.0.0__test1.sql
V3.0.0.0__test2.sql
V3.2.1.0
V3.2.1.0__test5.sql
V3.2.1.0__test6.sql
V3.10.0.0
V3.10.0.0__test3.sql
V3.10.0.0__test4.sql
Exited with code exit status 1
CircleCI received exit code 1
これで、 deploy
ジョブは動きません。
OKの場合
#!/bin/sh -eo pipefail
groovy devops/check_duplicated_flyway_migration.groovy
WARNING: Using incubator modules: jdk.incubator.foreign, jdk.incubator.vector
All flyway migration files have valid name:
ここにファイル名
CircleCI received exit code 0
これで、 deploy
ジョブが動きます( test-build
も成功している場合)。
このように、シェルスクリプトで書くのは難しい、PythonとかNodeとか知らない、PythonやNodeだとvenvとかpackage.jsonとか色々…という場合、Groovyを使うこともアリかなと感じました。