😺

Ridgepoleが発行するDDLをGitHub PR上で確認できるようにした

2021/04/19に公開

Railsでバックエンドを書く時のお供としてRidgepoleというDBマイグレーションをいい感じにしてくれるGemをよく使ってます。
Ridgepoleはほんと便利でRailsデフォルトのマイグレーションにはもう戻れない体になってしまっているのですが、更新系操作を行うときにRidgepoleがどういうDDLを発行するのか把握しておかないと痛い目に遭います。本業である新しいアプリケーションを開発していた際、カラム名を変更をする時にSchemafileに記載していたカラム名を変更してしまった結果、元のカラムはDROPされ、変更したいカラム名で新しいカラムが作られてしまったため、DROPされたカラムに入っていたデータが欠損したことがあります...。幸い本番稼働してなかったので問題にはなりませんでしたが。

このようにするとnameカラムがDROPされ、display_nameカラムがADDされるのでnameカラムに入っていた情報が欠損します。

# Schemafile
create_table(:users) do |t|
-  t.string, :name, null: false
+  t.string, :display_name, null: false
end

nameをdisplay_nameにリネームしたい場合は下記のように renamed_from を使う必要があります。

# Schemafile
create_table(:users) do |t|
-  t.string, :name, null: false
+  t.string, :display_name, null: false, renamed_from: :name
end

Ridgepoleの使い方をちゃんと把握してれば起きないでしょ!って話ではあるのですが複数人で開発する場合は全員がRidgepoleに詳しいわけではないので作った差分によってどういうDDLが発行されるのかを各自が事前に確認できると安心して運用ができるのではと思い、変更によって発行されるDDLをGitHubのPRに自動でコメントとして貼り付けるようにしてみました。イメージしやすいように実際に貼り付けられたコメントを貼っておきます。

差分がある場合

差分がない場合

必要要件

設定した必要要件は下記です。

  • CIのワークフロー内でコミット毎にDDLを検出してPRにコメントとして貼り付ける
  • PRにコメントするためのGitHubトークンは管理したくないので、ワークフロー内に閉じたトークンを自動生成するGitHub Actionsを利用する
  • コミットが積まれる毎にPRにコメントがつくと鬱陶しいので一つのコメントをアップデートする形式でDDLを貼り付ける

GitHub Actionsがワークフロー内に閉じたトークンを自動生成する、というのは下記の公式ドキュメントに記載があります。日本語ドキュメントを貼りたかったのですがアンカーが壊れているため英語ドキュメントを貼っています。
https://docs.github.com/en/actions/reference/authentication-in-a-workflow#about-the-github_token-secret

用意したワークフローファイル

下記のyamlファイルを .github/workflows/ 配下に設置します。後は勝手にワークフローが実行されるので、PRを作成してコミットを積むたびにDDLがコメントとして貼り付けられます。

name: Notify MySQL DDL
on: pull_request
jobs:
  build-and-notify:
    name: build-and-notify
    runs-on: ubuntu-latest
    services:
      db:
        image: mysql:5.7
        options: --health-cmd "mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
        ports:
          - 3306:3306
        env:
          MYSQL_ALLOW_EMPTY_PASSWORD: yes
          MYSQL_ROOT_PASSWORD: ''

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Setup Ruby
        uses: ruby/setup-ruby@v1
        with:
          bundler-cache: true

      # .env で管理している環境変数をそのまま流用する(環境変数を定義する箇所を減らしたいため)
      - name: Attach environment variables from .env file
        run: grep -v '^#' .env | xargs -I@ echo "@" >> $GITHUB_ENV

      - name: Create database
        run: bin/rails db:create

      # 起点になったブランチの最新のコミットにcheckoutしてdbのschemaを再現する
      - name: Reproduce db schema on base branch
        run: |
          # base_refは起点になったブランチ名、head_refはプルリクのブランチ名
          git fetch origin ${{github.base_ref}}
          git fetch origin ${{github.head_ref}}

          last_commit_hash_on_base_branch=`git rev-list origin/${{github.base_ref}} -n 1`
          git checkout $last_commit_hash_on_base_branch
          
          # base_refとhead_refで必要なGemが異なることがありえるので依存解決をしておきます
          bundle config --local deployment true
          bundle config --local jobs 16
          bundle config --local with 'test'
          bundle check || bundle install

          bin/ridgepole --config config/database.yml --apply --env test

      # PRのブランチに切り替えて発行されうるDDLを出力する
      - name: Detect MySQL DDL
        id: mysql_ddl
        run: |
          git checkout ${{github.head_ref}}
          result=`bin/ridgepole --config config/database.yml --apply --env test --dry-run`
          # set-outputに複数行を渡す場合はエスケープが必要
          result="${result//$'\n'/'%0A'}"
          echo "::set-output name=result::$result"

      - name: Notify MySQL DDL
        uses: marocchino/sticky-pull-request-comment@v2
        with:
          message: |
            origin/${{github.base_ref}} ブランチのスキーマとの差分
            ```
            ${{ steps.mysql_ddl.outputs.result }}
            ```

コミットが積まれる毎にPRにコメントがつくと鬱陶しいので一つのコメントをアップデートする形式でDDLを貼り付ける、はSticky Pull Request Commentというプラグインがあったのでこれを利用しました。Sticky Pull Request Commentは今回の要件で必要な「一つのコメントを更新する」ことを少ない記述で実現してくれるのでとてもありがたいですね。毎回新しくコメントを入れたり、古いコメントを消す機能も提供していて色んなシーンで使えるんじゃないかなと思いました。

おわりに

本業ではアプリケーションの開発リーダーをしていることもあり、誰が運用しても一定の品質が担保できるように色々工夫してきました。今回DDLを自動検出しPRのコメントという開発者が見やすい場所で閲覧可能にしたことで、本番DBに対して意図しないDDLの発行が行われる可能性を下げるという部分に貢献できているかなと思っていて、個人的に気に入っています。後はPRレビュー時にSchemafileが変更されている場合は貼り付けられたDDLを見れば何が行われるのか明白になったのでレビュー時の気持ちが少し楽になりました。

Discussion