ステージング環境のマイグレーションでDB定義が競合しにくい仕組みにしてみた - その2

オズビジョンのSREチームリーダの卜部です。


前回 [ステージング環境のマイグレーションでDB定義が競合しにくい仕組みにしてみた - その1](https://qiita.com/oz-urabe/items/7c51f7e759ac3fab1cb8) の記事でマイグレーションが起こりにくい仕組みを作成した内容の投稿をしました。

![ブランチイメージ-案1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/218682/829d5cd0-5024-b3d9-74db-cc0fdec60a07.png)


しかしながら、上記の対応では migrate up と対になる migrate down をエンジニアが正しく実装してくれた前提での話となります。



弊社では CI に [Scrutinizer CI](https://scrutinizer-ci.com/) を使い、コードに問題があるかを気づけるような仕組みになっています。今回、ここを使い、migrate up と対になる migrate down が実装されているか、確認が行える仕組みを検討します。

```yaml
build:
  root_path: ./
  environment:
    timezone: "Asia/Tokyo"
    php: 7.2
    mysql: 5.6 
    postgresql: false
    redis: false
  tests:
    override:
      - command: stty cols 80; ./vendor/bin/phpunit --coverage-clover=.coverage.xml
        coverage:
          file: ".coverage.xml"
          format: "clover"
        idle_timeout: 1200
```

ここにコマンドを一つ追加します

```yaml
build:
  root_path: ./
  environment:
    timezone: "Asia/Tokyo"
    php: 7.2
    mysql: 5.6 
    postgresql: false
    redis: false
  tests:
    override:
      - command: stty cols 80; ./vendor/bin/phpunit --coverage-clover=.coverage.xml
        coverage:
          file: ".coverage.xml"
          format: "clover"
        idle_timeout: 1200
      - command: chmod 777 ./tests/migration-test.sh && ./tests/migration-test.sh
```

./tests/migration-test.sh というテストスクリプトを追記しました。
この中身は以下のようになっています

```bash
#!/bin/bash

SCRIPT_DIR=$(cd $(dirname $0); pwd)
# use https://github.com/lehmannro/assert.sh
. $SCRIPT_DIR/assert.sh

STATUS=0
NOMAL_OUTPUT=/dev/stdout
ERROR_OUTPUT=/dev/stderr

# migration を初期バージョンに戻してからテスト
while true;
do
    CURRENT=$($SCRIPT_DIR/bin/console doctrine:migrations:status --em=migration|grep 'Current Version:'|cut -d: -f2- |xargs)
    echo migration down. current: $CURRENT
    if [ "$CURRENT" == "0" ]; then
        echo OK. Already at first version.
        break;
    fi

    $SCRIPT_DIR/bin/console  doctrine:migrations:migrate prev --em=migration --no-interaction > /dev/null 2>&1
done

# バージョン毎の migrate up, down の差分比較
for target in $(ls $SCRIPT_DIR/app/DoctrineMigrations/*.php|sort -n)
do
    version=$(basename $target|perl -pe 's/Version([0-9]{14})\.php/\1/ig')
    echo $version migration test
    mysqldump -uroot migration_ut -d --compact --ignore-table=ut_db.migration_versions | perl -pe 's/AUTO_INCREMENT=[0-9]+ //s' > /tmp/before.$version.txt 2> /dev/null
    # migrate up 前の DB 定義を /tmp に保存

    $SCRIPT_DIR/bin/console  doctrine:migrations:migrate $version --em=migration --no-interaction > $NOMAL_OUTPUT 2> $ERROR_OUTPUT
    RETURN=$?
    [ "$RETURN" != "0" ] && STATUS=1
    # migrate up 自体の正常実行チェック
    assert "echo $RETURN" "0"

    $SCRIPT_DIR/bin/console  doctrine:migrations:migrate prev --em=migration --no-interaction > $NOMAL_OUTPUT 2> $ERROR_OUTPUT
    RETURN=$?
    [ "$RETURN" != "0" ] && STATUS=1
    # migrate down 自体の正常実行チェック
    assert "echo $RETURN" "0"

    mysqldump -uroot migration_ut -d --compact --ignore-table=ut_db.migration_versions | perl -pe 's/AUTO_INCREMENT=[0-9]+ //s' > /tmp/after.$version.txt 2> /dev/null
    # migrate down 後の DB 定義を /tmp に保存

    diff /tmp/before.$version.txt /tmp/after.$version.txt > $NOMAL_OUTPUT 2> $ERROR_OUTPUT
    # 差分がないことをチェック
    RETURN=$?
    [ "$RETURN" != "0" ] && STATUS=1
    assert "echo $RETURN" "0"

    $SCRIPT_DIR/bin/console  doctrine:migrations:migrate $version --em=migration --no-interaction > /dev/null 2>&1
    # 次のマイグレーションのテストを行うため、次のバージョンに migrate up
done

assert_end php-migration

exit $STATUS

```

簡単に説明すると以下のようなチェックが実施されます

1. migration 用の DB の状態を初期のマイグレーションバージョンに戻す
2. migration ファイルのバージョン一覧を古い順に取り出し、以下のチェックを行う
    - a. DB のテーブル定義を保存する
    - b. migrate up 自体が正常に行えることを確認 （assert1）
    - c. migrate down 自体が正常に行えることを確認 （assert2）
    - d. DB のテーブル定義を保存
    - e. a. と d. のテーブル定義で差分がないかをチェック  （assert3）

単純なUTではありますが、こうすることで、migrate up, down が正しく作られているかのチェックが行え、
その1の記事の ステージング環境でブランチを切り替えても マイグレーションを一度過去に戻して、必要なマイグレーションのみ実行されるようにするという仕組みの完成版になります。

なお、今回は DDL に限った課題解決となります。マイグレーションに DML が含まれる場合に対しては今回の仕組みでは対処できません。DML を含む場合は、別途テストをしっかり実施していただく必要があるかと思います。