tbls と GitLab CI でデータベースドキュメントを自動生成する


データベースドキュメントは、フォーマットを決めたり、ER図を作ったり、スキーマの変更に合わせて更新が必要だったり面倒なことが多い。そこで tbls と GitLab CI を使って、最新のドキュメントを自動生成できる仕組みを作ってみた。

## tblsとは

データベースに接続してMarkdownでスキーマのドキュメントを生成するツール。データベース全体のER図と各テーブルごとのER図も生成してくれる。テンプレートのカスタマイズもできるのでかなり柔軟に対応できる。[サンプル](https://github.com/k1LoW/tbls/blob/main/sample/postgres/README.md)


tblsはGo言語で作られたシングルバイナリなのでCI環境でも動かしやすく、ER図の生成は内蔵のGraphvizで行われるため、別途インストールが必要ないのも良い（Go言語の[go-graphviz](https://github.com/goccy/go-graphviz)が使われているらしい）。

tblsはデータベースのリンターにもなり、
- テーブルやカラムにコメントがついているか
- １テーブルあたりのカラム数
- `created_at`のようなカラムを必須に設定
- 重複したリレーションがないか
- カバレッジの算出

などほかではあまり見ない機能もある。


https://github.com/k1LoW/tbls

## GitLab CI のジョブを作成する

GitLab CIの説明は割愛。次の処理を行うジョブを作成します。

- データベースを立ち上げる
- マイグレーションを実行
- tlbs でデータベースからドキュメントを生成
- 生成されたドキュメントをコミット・プッシュ
- ジョブはマイグレーションファイルの変更がプッシュされた時のみ実行

作成したジョブは以下の通り。GitLab RunnerはDocker executorを使用しています。

```yaml
tbls:
  stage: document
  services:
    - mysql:8.0
  variables:
    MYSQL_DATABASE: database
    MYSQL_USER: user
    MYSQL_PASSWORD: password
    MYSQL_ROOT_PASSWORD: root
  script:
    - composer install
    - cp .env.example .env
    - DB_HOST=mysql DB_PASSWORD=root php artisan migrate
    - source <(curl https://raw.githubusercontent.com/k1LoW/tbls/main/use)
    - tbls doc --rm-dist mysql://root:root@mysql:3306/database
    - git config user.name tbls
    - git config user.email project***_bot@noreply.example.com
    - git remote set-url --push origin https://$CI_PROJECT_NAME:$GITLAB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git
    - git add -N .
    - git diff --quiet || git commit -am "Update database documents"
    - git push origin HEAD:$CI_COMMIT_BRANCH
  rules:
    - if: $CI_COMMIT_BEFORE_SHA != '0000000000000000000000000000000000000000'
      changes:
        - .tbls.yml
        - database/migrations/*
```

今回はLaravel/MySQLのプロジェクトでしたが、`composer install`～`artisan migrate`あたりのコマンドを
各種フレームワークに置き換えて考えてもらえれば問題ないはず。
ジョブを構成するにあたって重要なポイントを簡単に解説しておきます。

#### GitLab CI で データベースを使う
`services`でDockerイメージを指定しておくことで、ジョブの実行中に別のコンテナを立ち上げておくことができます。コンテナの立ち上げ時に必要な環境変数は`variables`で指定します。
```yaml
  services:
    - mysql:8.0
  variables:
    MYSQL_DATABASE: database
    MYSQL_USER: user
    MYSQL_PASSWORD: password
    MYSQL_ROOT_PASSWORD: root
```

https://docs.gitlab.com/ee/ci/services/mysql.html

#### アクセストークンでプッシュ
そのままではリポジトリにPushする権限がないので、Project Access Tokensを準備しました。
ロールは`Developer`以上、スコープは`read_repository`と`write_repository`にチェックを入れてトークンを作成し、CI変数に設定しておきます。（今回は`GITLAB_TOKEN`という名前で設定しています）

設定したトークンを使ってリモートURLを`git remote set-url`コマンドで認証付きのものに変更します。
```yaml
    - git config user.name tbls
    - git config user.email project***_bot@noreply.example.com
    - git remote set-url --push origin https://$CI_PROJECT_NAME:$GITLAB_TOKEN@$CI_SERVER_HOST/$CI_PROJECT_PATH.git
```
https://docs.gitlab.com/ee/user/project/settings/project_access_tokens.html


#### 差分があるときだけコミット
基本的にはスキーマに変更があるときにしかジョブは実行されませんが、生成されたドキュメントに変更がなかった場合の対策をしておきます。

`git diff`で新規ファイルを含んだ差分を表示するため、事前に`git add -N .`を実行します。
`git diff`は`--quiet`をつけておくことでexit codeで差分の有無を表してくれるので、差分がある場合のみ`git commit -am`を実行します。

```yaml
    - git add -N .
    - git diff --quiet || git commit -am "Update database documents"
    - git push origin HEAD:$CI_COMMIT_BRANCH
```

#### マイグレーションファイルが変更された場合のみジョブを実行する

ファイルの変更を検出し実行するには、`rules:changes`にファイルパスを記載します。
スケジュールパイプラインや手動パイプラインなどでは`rules:changes`は常に`true`と評価されるので、`if`の指定も必要になります。（今回は新規ブランチをプッシュした際にジョブが実行されてしまうのが嫌だったので`$CI_COMMIT_BEFORE_SHA`を条件に設定してます）

```yaml
  rules:
    - if: $CI_COMMIT_BEFORE_SHA != '0000000000000000000000000000000000000000'
      changes:
        - .tbls.yml
        - database/migrations/*
```

https://docs.gitlab.com/ee/ci/yaml/#ruleschanges