【Github Actions】Rails × PostgreSQL環境作ってHerokuにデプロイするCIとその解説

Github Actionsを使ってRails×PostgreSQL環境のCIを作りました。
Github Actionsは普段触らなかったので、作ったCIについてコードの解説をここに書いていきます。

まずはCIでやっていることと、そのコードの全体像を書いていきます。そのあとにCIのコードについて説明を書いていきます。

## 今回のCIでやっていること
1. Pull Requestが作られたタイミングで、RSpecによるテスト
2. Masterブランチにマージされたタイミングで、Herokuにデプロイ
3. テストが完了したら、Slackに通知する


## 書いたCI
```yml
name: Build And Test
on:
  pull_request:
  push:
    branches:
      - master
jobs:
  build_and_test:
    name: Exec RSpec rubocop brakeman
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:11.5
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
    env:
      RAILS_ENV: test
      BUNDLE_PATH: ./vendor/bundle
      DATABASE_NAME: covid19_line_bot_test
      DATABASE_USER: postgres
      DATABASE_PASSWORD: ""
      DATABASE_HOST: 127.0.0.1
      DATABASE_PORT: 5432
      TZ: Asia/Tokyo
      RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
    steps:
      - name: Check out code
        uses: actions/checkout@v2
      - name: Cache Gemfile.lock
        uses: actions/cache@v2
        with:
          path: ./vendor/bundle
          key: ${{ runner.os }}-rails-bundle-v1-${{ hashFiles('**/Gemfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-rails-bundle-v1-
      - name: Ruby 2.6.5のセットアップ
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6.5
      - name: pg gem の依存関係のインストール
        run: |
          sudo apt-get update; sudo apt-get install libpq-dev
      - name: node v10のセットアップ
        uses: actions/setup-node@v1
        with:
          node-version: '10.x'
      - name: yarn install
        run: |
          yarn install --check-files
      - name: gem install
        run: |
          gem install bundler -v 1.17.3
      - name: bundler install
        run: |
          bundle check || bundle install --path vendor/bundle --jobs 4 --retry 3
      - name: Setup Database
        run: |
          bundle exec rails db:create db:schema:load --trace
      - name: Exec RSpec
        run: |
          bundle exec rspec --format documentation --force-color --backtrace
      - name: Exec Brakeman
        run: |
          bundle exec brakeman -A
      - name: Exec rubocop
        run: |
          bundle exec rubocop --extra-details --display-style-guide --parallel --display-cop-names
  test_slack_notification:
    name: 【Test Result】Slack Notification by Github Actions
    runs-on: ubuntu-latest
    needs: build_and_test
    env:
      SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
      SLACK_USERNAME: Github Actions Result
    steps:
      - name: Slack Notification on Success
        uses: rtCamp/action-slack-notify@v2
        if: success()
        env:
          SLACK_MESSAGE: Pass rubocop, rspec, brakeman
          SLACK_TITLE: Test Complete!🎉✨
      - name: Slack Notification on Failure
        uses: rtCamp/action-slack-notify@v2
        if: failure()
        env:
          SLACK_MESSAGE: Fail rubocop, rspec, brakeman
          SLACK_TITLE: Test Failure!👾
          SLACK_COLOR: "#dc3545"
  deploy_production:
    name: Herokuにデプロイ
    needs: build_and_test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/master'
    steps:
      - name: Check out code
        uses: actions/checkout@v2
      - name: Deploy to Heroku Production
        uses: akhileshns/heroku-deploy@v3.6.8
        with:
          heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
          heroku_app_name: ${{ secrets.HEROKU_APP_NAME }}
          heroku_email: ${{ secrets.HEROKU_EMAIL }}
```


## 解説

### name

```yml
name: Build And Test
```
ワークフローの名前GitHub リポジトリの [Actions] タブに表示されるワークフローの名前
![スクリーンショット 2020-12-16 14.39.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107345/b239460b-0512-41e1-6e74-2b3ab07e5b6a.png)



### on
ワークフローを実行するタイミングを指定

```yml
on:
  pull_request:
  push:
    branches:
      - master
```

上記の例では、以下のタイミングでワークフローが走ります。

- Pull Requestが作られたとき
- MasterブランチにPushされたとき

他にもデプロイがされたとき、ブランチが作られたとき、プルリクにレビューが投げられたとき、など様々なタイミングを指定することが可能。

参考：[Events that trigger workflows
](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows)


### jobs
ワークフローは1つ以上のJobからなる。
複数のジョブはデフォルトでは、並行に実行される。

#### jobs.job_id
JobにはJobを一意に判別するIDが必要

```yml
jobs:
  build_and_test:
    name: Exec RSpec rubocop brakeman
    runs-on: ubuntu-latest
    services: ~~~
    [省略]
  test_slack_notification:
    name: 【Test Result】Slack Notification by Github Actions
    runs-on: ubuntu-latest
    needs: build_and_test
    env:
      SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}
      SLACK_USERNAME: Github Actions Result
    steps: ~~
    [省略]
  deploy_production:
    name: Herokuにデプロイ
    needs: build_and_test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/master'
    [省略]
````

上記のコードではこれらがJobのIDのキーに該当します。

- build_and_test
- test_slack_notification
- deploy_production



#### jobs.job_name

Jobの名前

```yml
  build_and_test:
    name: Exec RSpec rubocop brakeman
  test_slack_notification:
    name: 【Test Result】Slack Notification by Github Actions
  deploy_production:
    name: Herokuにデプロイ
```

#### jobs.runs_on
Jobを実行するマシンを指定します。

```yml
  build_and_test:
    name: Exec RSpec rubocop brakeman
    runs-on: ubuntu-latest
```

2020年12月16日現在は、このような環境が用意されています

|Virtual environment  |YAML workflow label |
|---|---|
|Windows Server 2019  |windows-latest or windows-2019  |
|Ubuntu 20.04  |ubuntu-20.04 |
|Ubuntu 18.04  | ubuntu-latest or ubuntu-18.04 |
|Ubuntu 16.04  |ubuntu-16.04 |
|macOS Big Sur 11.0  |	macos-11.0 |
|macOS Catalina 10.15  |macos-latest or macos-10.15 |

参考：[Github Actions Supported runners and hardware resources](https://docs.github.com/en/free-pro-team@latest/actions/reference/specifications-for-github-hosted-runners#supported-runners-and-hardware-resources)

#### jobs.services
ジョブのためのサービスコンテナをホストするために使用

[サービスコンテナ]
Jobを実行するのに必要になるDBやRedisなどのサービスを提供できるDockerコンテナ

```yml
jobs:
  build_and_test:
    name: Exec RSpec rubocop brakeman
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:11.5
        ports:
          - 5432:5432
        # ↓postgresが起動するまで待つヘルスチェックの設定
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
```

#### jobs.env
Job中で使える環境変数を定義
Stepの中に書けば、そのStepでのみ利用可能な環境変数を定義できる

```yml
jobs:
  build_and_test:
    name: Exec RSpec rubocop brakeman
    [省略]
    env:
      RAILS_ENV: test
      BUNDLE_PATH: ./vendor/bundle
      DATABASE_NAME: covid19_line_bot_test
      DATABASE_USER: postgres
      DATABASE_PASSWORD: ""
      DATABASE_HOST: 127.0.0.1
      DATABASE_PORT: 5432
      TZ: Asia/Tokyo
      RAILS_MASTER_KEY: ${{ secrets.RAILS_MASTER_KEY }}
```

#### jobs.needs
ジョブの実行前に正常に完了する必要があるジョブを指定できる
以下の例だと、 `build_and_test`が正常に完了しないと、`test_slack_notification`も `deploy_production`も実行され

```yml
jobs:
  [省略]
  test_slack_notification:
    name: 【Test Result】Slack Notification by Github Actions
    runs-on: ubuntu-latest
    needs: build_and_test
  deploy_production:
    name: Herokuにデプロイ
    needs: build_and_test
```

#### jobs.if
ジョブを実行する条件を書くことができる

```yml
  deploy_production:
    name: Herokuにデプロイ
    needs: build_and_test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/master'
```

##### github.refとは
> ワークフローの実行をトリガーしたブランチまたはタグ ref。 ブランチの場合、これはrefs/heads/<branch_name> の形式で、タグの場合は refs/tags/<tag_name> です

参考：[GitHub Actions のコンテキストおよび式の構文](https://docs.github.com/ja/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions)


### steps
Jobで実行するコマンドなどを書いていく

```yml
    steps:
      - name: Check out code
        uses: actions/checkout@v2
      - name: Cache Gemfile.lock
        uses: actions/cache@v2
        with:
          path: ./vendor/bundle
          key: ${{ runner.os }}-rails-bundle-v1-${{ hashFiles('**/Gemfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-rails-bundle-v1-
      - name: Ruby 2.6.5のセットアップ
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6.5
      - name: pg gem の依存関係のインストール
        run: |
          sudo apt-get update; sudo apt-get install libpq-dev
      - name: node v10のセットアップ
        uses: actions/setup-node@v1
        with:
          node-version: '10.x'
      - name: yarn install
        run: |
          yarn install --check-files
      - name: gem install
        run: |
          gem install bundler -v 1.17.3
      - name: bundler install
        run: |
          bundle check || bundle install --path vendor/bundle --jobs 4 --retry 3
      - name: Setup Database
        run: |
          bundle exec rails db:create db:schema:load --trace
      - name: Exec RSpec
        run: |
          bundle exec rspec --format documentation --force-color --backtrace
      - name: Exec Brakeman
        run: |
          bundle exec brakeman -A
      - name: Exec rubocop
        run: |
          bundle exec rubocop --extra-details --display-style-guide --parallel --display-cop-names
```

#### steps.name
GitHubで表示されるステップの名前

```yml
    steps:
      - name: Check out code
        uses: actions/checkout@v2
```

#### steps.uses
ステップとして実行するアクションを選択

```yml
    steps:
      - name: Check out code
        uses: actions/checkout@v2
      - name: Cache Gemfile.lock
        uses: actions/cache@v2
        with:
          path: ./vendor/bundle
          key: ${{ runner.os }}-rails-bundle-v1-${{ hashFiles('**/Gemfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-rails-bundle-v1-
      - name: Ruby 2.6.5のセットアップ
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6.5
```

#### 使っているusersの説明
##### actions/checkout@v2
リポジトリをチェックアウトする

```yml
      - name: Check out code
        uses: actions/checkout@v2
```
参考：https://github.com/actions/checkout

##### actions/cache@v2
キャッシュできる

```yml
      - name: Cache Gemfile.lock
        uses: actions/cache@v2
        with:
          path: ./vendor/bundle
          key: ${{ runner.os }}-rails-bundle-v1-${{ hashFiles('**/Gemfile.lock') }}
          restore-keys: |
            ${{ runner.os }}-rails-bundle-v1-
```
`path: ~~`はキャッシュする、復元するファイルやディレクトリを指定する
`key: ~~`はキャッシュを保存/復元するためのキー
`restore-keys: ~~`はキーのキャッシュヒットが発生しなかった場合にキャッシュを復元するために使用するキーの順序付きリスト

参考：https://github.com/actions/cache

##### ruby/setup-ruby@v1

```yml
      - name: Ruby 2.6.5のセットアップ
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6.5
```
`ruby-version:~~`で使用するRubyのバージョンを指定できる 
参考：https://github.com/ruby/setup-ruby


##### actions/setup-node@v1

```yml
      - name: node v10のセットアップ
        uses: actions/setup-node@v1
        with:
          node-version: '10.x'
```

`node-version:~~`で使用するNodeのバージョンを指定できる 
参考：https://github.com/actions/setup-node


##### rtCamp/action-slack-notify@v2
Slackに通知を送ることができる

```yml
      - name: Slack Notification on Success
        uses: rtCamp/action-slack-notify@v2
        if: success()
        env:
          SLACK_MESSAGE: Pass rubocop, rspec, brakeman
          SLACK_TITLE: Test Complete!🎉✨
```
`SLACK_WEBHOOK：$ {{secrets.SLACK_WEBHOOK}}` SlackのWebHockのURL設定は必須

参考：https://github.com/rtCamp/action-slack-notify


###### akhileshns/heroku-deploy@v3.6.8
Herokuにデプロイできる

```yml
      - name: Deploy to Heroku Production
        uses: akhileshns/heroku-deploy@v3.6.8
        with:
          heroku_api_key: ${{ secrets.HEROKU_API_KEY }}
          heroku_app_name: ${{ secrets.HEROKU_APP_NAME }}
          heroku_email: ${{ secrets.HEROKU_EMAIL }}
```
heroku_api_key:、heroku_app_name:、heroku_emailは必須

参考：https://github.com/AkhileshNS/heroku-deploy


#### steps.run
コマンドラインプログラムを実行する

```yml
      - name: yarn install
        run: |
          yarn install --check-files
      - name: gem install
        run: |
          gem install bundler -v 1.17.3
      - name: bundler install
        run: |
          bundle check || bundle install --path vendor/bundle --jobs 4 --retry 3
      - name: Exec RSpec
        run: |
          bundle exec rspec --format documentation --force-color --backtrace
```

runの中では、bundle installやyarn install、Rspecの実行などを行うようにしました。


## 終わりに

Rails×PostgreSQL環境を使うことが多くていままでCircleCIを利用していましたが今回GithubActionsを書いてみました。CIとしてそれほど大きな違いはなく感じたので、個人的には学習コストは低かったです。
[ドキュメント](https://docs.github.com/ja/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions)も非常に分かりやすく日本語で書かれていてよかったです。