🎢

GitHub Actionsでrails db:migrate/rollbackを実行しdb/schema.rbの差分をチェックする

2023/12/24に公開

これはなに?

PRを出したときにrails db:migraterails db:rollbackを実行しその実行がエラーにならないことと、それぞれの実行後にdb/schema.rbに差分がでないことを確認するGitHub Actionsのworkflowです。

コード

本題に関係ない処理は省いています。

.github/workflows/migrate_test.yml
name: Migrate Test

on:
  pull_request:
    branches:
      - develop
    paths:
      - 'db/migrate/*.rb'
      - 'db/schema.rb'
      - '.github/workflows/migrate_test.yml'
jobs:
  migrate-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.2.2
          bundler-cache: true

      - name : Exec Migrate
        run: bundle exec rails db:migrate

      # migrate実行後にdb/schema.rbに差分がでていないかチェック
      - name: Check schema diff after migrate
        run: |
          DIFF="$(git diff HEAD db/schema.rb)"
          if [[ $DIFF != '' ]]; then
            echo 'db:migrateを実行するとdb/schema.rbに差分が発生します。差分が出ないように修正してください'
            git diff HEAD
            exit 1
          fi

      # 追加されたdb/migrate配下のファイルの数を取得
      - name: Count new migration files
        id: count-migrations
        run: |
          git fetch origin ${{ github.base_ref }}
          count=$(git diff --name-only --diff-filter=A origin/${{ github.base_ref }} HEAD | grep db/migrate/ | wc -l)
          echo "Number of new migrations: $count"
          echo "count=$count" >> $GITHUB_OUTPUT

      # 追加されたmigrateファイルの数をSTEPに指定してrollback
      - name: Rollback database
        if: steps.count-migrations.outputs.count > 0
        working-directory: ./lc-local-dev
        run: |
          bundle exec rails db:rollback STEP=${{ steps.count-migrations.outputs.count }}

      # rollback後にPRのターゲットブランチと比較して差分がでていないかチェック
      - name: Check schema diff after rollback
        if: steps.count-migrations.outputs.count > 0
        run: |
          git fetch origin ${{ github.base_ref }}
          ROLLBACK_DIFF="$(git diff origin/${{ github.base_ref }} db/schema.rb)"
          if [[ $ROLLBACK_DIFF != '' ]]; then
            echo 'db:rollbackを実行するとdb/schema.rbに差分が発生します。差分が出ないように修正してください'
            echo $ROLLBACK_DIFF
            exit 1
          fi

モチベーション

副業で複数のRailsアプリケーションが1つのDBを利用する構成のシステムに関わる機会がありました。
共通で参照しているテーブルもあり、当然ですがDBになにかあった場合は複数のアプリケーションに影響します。
このような状況下でdb:migrateはそこそこ頻度が多いにもかかわらず割とリスクが伴う操作となっています。
特にいざ切り戻しが必要となった場合にdb:rollbackが失敗したり、テーブル定義がdb:migrateをする前と異なってしまうというのは二次障害につながるため避けたい事態でした。
このような背景からmigrationファイル追加をするPRを出したときにdb:migratedb:rollbackができることをGitHub Actionsでチェックするようにしました。

補足

  • 複数人が同時にmigration追加のPRを作成し、それらのうちタイムスタンプが新しい日付のものが先にマージされた場合、古い方のタイムスタンプのPRはrollbackの対象ファイルが追加したファイルにならないため必ずdb/schema.rbに差分が発生し失敗します。
    • ただしこの場合db/schema.rbにコンフリクトが発生していると思います。
    • また、railsのmigrateがタイムスタンプ順に実行される仕様から、PRを作成したときと前提とするDBの状態が変わっている可能性があるため、タイムスタンプを新しくつけ直すのが無難だと考えています。
  • rubocop-railsRails/ReversibleMigrationでmigrationファイルのコードがrollbackでmigrate実行前の状態に戻せるものになっているかチェックすることができます。ある程度はこれで検出できると思いますが、実際にrollbackを実行してその結果を確認するほうが確実だと考えています。

参考

https://product.st.inc/entry/2021/08/18/124533

https://sue445.hatenablog.com/entry/2017/01/16/120405

Discussion