【Golang】自動生成漏れをCIで検知する【GitHub Actions】

はじめに

QualiArts Advent Calendar 2020 - Qiita、7日目担当の鈴木光です
今回はGo言語とGitHub Actionsについてのお話をさせていただきます

自動生成

Go言語は言語仕様上、自動生成によるコード実装が多い言語です
例えば

  • protobufから生成したコード
  • ORMのコード
  • テストで使うmockコード
  • 何かを元に生成したenumの実装
  • APIドキュメント
  • 単純なCRUD API
  • etc ...

自動生成したコードは普通にgit commitしないといけません
ただ、そのうち自動生成コマンド自体の数が増えてくると、コミットの中に漏れが発生することがあります
もちろん、これらが漏れていたからといってアプリケーションが必ずしも動作しないわけではありません
テスト対象によってはmockコードがなくても通る場合はありますし、そもそもORMなどは漏れること自体があまりないでしょう
しかし、自動生成を忘れた人以外の人がそれらを生成した際に、自分の変更範囲外のものまで生成されるとちょっとアレじゃないでしょうか?
今回はその漏れを見逃さないようにGitHub Actionsを用いてチェックする方法をご紹介します

実装

今回は次の3つについて、GitHub Actions上で自動生成が漏れていないかチェックする実装を行っていきます

  • ORMのコード&単純なCRUD API
  • テストで使うmockコード
  • APIドキュメント

また、基本的に自動生成が漏れていないかチェックするだけなら

  1. コードを生成
  2. git diff --quietでチェック(diffがある場合は終了コードが1になる)

だけで済みます

ORMのコード&単純なCRUD API

現在sqlboilerという既存DBから実装コードを生成するタイプのORMを利用しています
Go言語だとやはりgormをよく見かけますが、sqlboilerはタイプセーフなコードを生成できてパフォーマンスも良いのでお気に入りです
加えてマスターデータを操作する単純なCRUD APIを自動で生成しており、カラム追加時などにちょくちょく忘れてしまうのでチェックしたいと思います(むしろこちらがメイン)
また、DBマイグレーションのツールとしてdbmateを使っています。非常に使い勝手が良いのでぜひ使ってみてください

name: Check autogen db code
on:
  pull_request:
    branches:
      - master
      - 'feature/**'
    paths:
      - 'db/migrations/*' # マイグレーションファイルに変更があった時だけ実行
jobs:
  check-autogen-db:
    runs-on: ubuntu-latest
    services:
      mysql:
        image: mysql:5.7
        ports:
          - 3306:3306
        env:
          MYSQL_ROOT_PASSWORD: root
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Setup golang
        uses: actions/setup-go@v2
        with:
          go-version: 1.14.10
      - name: Cache Go Modules
        uses: actions/cache@v2.1.3
        id: cache
        with:
          path: ~/go/pkg/mod
          key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-
      - name: Download Modules
        if: steps.cache.outputs.cache-hit != 'true'
        run: go mod download
      - name: Migration database
        uses: docker://amacneil/dbmate:v1.10.0
        env:
          DATABASE_URL: mysql://root:root@mysql:3306/hoge
        with:
          args: --wait --no-dump-schema up
      - name: Setup tools
        run: GO111MODULE=off go get github.com/volatiletech/sqlboiler github.com/volatiletech/sqlboiler/drivers/sqlboiler-mysql
      - name: Generate entity
        run: sqlboiler mysql --wipe
      - name: Check diff
        run: git add . && git diff --cached --quiet
      - name: Show diff if failure
        if: ${{ failure() }}
        run: git diff --cached
      - name: Generate master api
        run: make gen-masterapi # マスターデータ操作APIを自動生成する独自実装
      - name: Check diff
        run: git add . && git diff --cached --quiet
      - name: Show diff if failure
        if: ${{ failure() }}
        run: git diff --cached

テストで使うmockコード

お次はmockコードの生成です
現在テストライブラリとしてアサーションtestify、mock生成にmockeryを利用しています
自動生成コードなど一部のファイルをテスト対象から外しているケースでそこへ変更があった際に生成を忘れがちです(そもそも生成対象からはずせよっていう話でもありますがw)

name: Check autogen mock code
on:
  pull_request:
    branches:
      - master
      - 'feature/**'
    paths:
      - 'pkg/**'
jobs:
  check-autogen-mock:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Generate mock
        run: rm -rf mock && docker run -v ${PWD}:/src -w /src vektra/mockery:latest --all --dir=./pkg --keeptree --outpkg=mock --output=mock --disable-version-string # dockerコマンドも使える
      - name: Check diff
        run: git add . && git diff --cached --quiet
      - name: Show diff if failure
        if: ${{ failure() }}
        run: git diff --cached

APIドキュメント

最後はAPIドキュメントです
前提として現在のプロジェクトではコードに埋め込んだアノテーションからSwagger Specを生成する手法をとっており、go-swaggerを利用しています
GitHubリポジトリにSwagger Specを置く運用をしているのですが、APIのリクエスト・レスポンスを更新した時に生成を忘れてしまうなどはよくあるユースケースです
そんな時はGitHub ActionsにSwaggerの生成とドキュメントのコミットまでやってもらいましょう
同一リポジトリ内にコミットするなら非常に簡単なのですが、今回はドキュメント専用の別リポジトリに対してコミットしたかったのでdeploy keysを利用します
こちらのリポジトリにはシークレット情報としてDOCS_REPO_DEPLOY_KEYという名前で秘密鍵を設定します。ドキュメント側のリポジトリには公開鍵を登録しておきましょう

name: Generate Swagger Spec
on:
  push:
    branches:
      - master
    paths:
      - 'pkg/presentation/http/router.go'
      - 'pkg/presentation/http/request/**'
      - 'pkg/presentation/http/response/**'
jobs:
  generate-swagger:
    runs-on: ubuntu-latest
    env:
      DOCKER_WORKDIR: /github/workspace
      SPEC_PATH: spec/swagger.yaml
    steps:
      - name: Checkout code
        uses: actions/checkout@v2
      - name: Swagger specを生成
        uses: docker://quay.io/goswagger/swagger:latest
        with:
          args: generate spec -m -o ${{env.DOCKER_WORKDIR}}/${{env.SPEC_PATH}}
      - name: deploy keyをセットアップ
        env:
          DOCS_REPO_DEPLOY_KEY: ${{secrets.DOCS_REPO_DEPLOY_KEY}}
        run: |
          echo "$DOCS_REPO_DEPLOY_KEY" > ~/deploy_key.pem
          chmod 600 ~/deploy_key.pem
      - name: diffがあればdocs用リポジトリにcommit&push
        env:
          GIT_SSH_COMMAND: ssh -i ~/deploy_key.pem -o StrictHostKeyChecking=no -F /dev/null
          DOCS_REPO: hoge-server-docs
        run: |
          git clone git@github.com:hikyaru-suzuki/$DOCS_REPO.git ../$DOCS_REPO
          cd ../$DOCS_REPO
          mkdir -p ./spec
          rm -f ./${{env.SPEC_PATH}}
          cp $GITHUB_WORKSPACE/${{env.SPEC_PATH}} ./${{env.SPEC_PATH}}
          git add ./${{env.SPEC_PATH}}
          result=0
          $(git diff --cached --quiet) || result=$?
          if [ $result -ne 0 ]; then
            git config --local user.email "action@github.com"
            git config --local user.name "GitHub Action"
            git commit -m "update: swagger spec"
            git push origin master
          fi

まとめ

小さなことですが今回のCI設定で楽になりました
個別の作業はそれぞれ1分もかからないものですが、自動生成の確認は毎回しないといけないので地味にストレスになっていたのかもしれません
自動生成自体はGo言語に限ったことではないですし、何かの参考になれば幸いです

参考