GitHub Actionsで使い捨てのDBを利用する

# はじめに
GitHub Actionsでテストを行う際に、毎回使い捨てのDBを立てて、そのDBを使ってテストをする方法について調べました。


# 環境
記事を書くにあたって作成したサンプルコードはGitHubの[KentarouSuzuki/actions-service-container](https://github.com/KentarouSuzuki/actions-service-container)にアップロードしてあるので、必要に応じてこちらを参照してください。
今回はGoで実装をしました。本来、Goでテストコードを書く際には `hoge_test.go` というファイル名にしますが、今回はコードを `cmd/sample/main.go` に書きました。

|             |                                                             |
| :---------: | :---------------------------------------------------------: |
| データベース      | PostgreSQL                                                  |
| 言語          | Go 1.14                                                     |
| マイグレーションツール | [rubenv/sql-migrate](https://github.com/rubenv/sql-migrate) |

## テストの順序
次のような順序でテストを進めていきます。  
1. GitHubからソースコードをチェックアウトしてくる。
2. DBマイグレーションを行う。
   - Accountテーブルの作成
3. テストコードを実行する。
    1. データを追加
    2. 追加したデータを全て取得して、標準出力に表示
    3. データを全件削除する


# サービスコンテナ
GitHub ActionsのワークフローでDBに接続してテストを行うには、GitHub Actionsの[サービスコンテナ](https://docs.github.com/ja/actions/guides/about-service-containers)を使います。このサービスコンテナの正体はDockerコンテナでDocker Hubのイメージを指定し、作成します。この記事ではPostgreSQLを使っていますが、これをMySQLといった他のRDBやRedis、nginxを使うこともできます。
例えば、ジョブを実行するにあたってPostgreSQLのサービスコンテナを用意するには以下のように書きます。

```yaml
jobs:
  my-job:
    runs-on: ubuntu-latest
    services:
      postgres:
        # Docker Hubのイメージ
        image: postgres
        # 動かすコンテナで使う環境変数
        # この場合はpostgresqlのパスワードを指定している。
        env:
          POSTGRES_PASSWORD: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
```

各ジョブの直下にあるservicesに動かしたいサービスコンテナのラベルを書き(この例ではpostgresですが、dbとかでも大丈夫です)、その下に動かしたいDockerイメージや環境変数、ヘルスチェックの設定を指定します。

# サービスコンテナと通信する
ワークフロー中で作成したサービスコンテナと通信する方法はジョブを実行している環境によって変わってきます。そこで、まずはジョブを実行する環境について説明します。その後、それぞれの方法でサービスコンテナへ通信する方法について説明をします。

## GitHub Actionsでジョブを実行する環境
GitHub Actionsでは以下の2つの環境でジョブを実行することができます。
1. ランナーマシン上でジョブを直接実行する
2. コンテナ内でジョブを実行する

### ランナーマシン上でジョブを直接実行する
あくまで私が公式ドキュメントやQiitaなどの記事で見た限りですが、この方法で実行している場合が多いです。公式ドキュメントの[クイックスタート](https://docs.github.com/ja/actions/quickstart#creating-your-first-workflow)もこの方法で実行しています。  
この方法では以下のように各ジョブの `runs-on` で指定されているマシンの種類でジョブが実行されます。GitHub ActionsではデフォルトでWindows、macOS、Ubuntuのマシンが用意されています。ジョブの中に定義されている `steps` は `runs-on` で指定されたマシンで実行されます。この記事のようにサービスコンテナを実行するにはGitHub Actionの仕様でLinuxのランナーを指定しなければならないようです。[^1]

```yaml
jobs:
  my-job:
    runs-on: ubuntu-latest
    steps:
      - name: Hello, world
        run: |
          echo "Hello, world"
```

### コンテナ内でジョブを実行する
こちらの方法はジョブ直下の `runs-on` でマシンの種類を、 `steps` で実行するジョブの内容を設定するところは同じです。しかし、それらに加えて `container` を設定します。この項目ではジョブが実際に実行するコンテナの設定を指定しています。この例の場合は、 `go version` はubuntu上に動いている `golang:1.14` のコンテナ内で実行されます。  
この方法でも先ほどのランナーマシン上でジョブを実行する時と同様にGitHub Actionsの仕様でLinuxのランナーを指定しなければならないようです。[^1]

```yaml
jobs:
  my-job:
    runs-on: ubuntu-latest
    container:
      # Docker Hubのイメージ
      image: golang:1.14
    steps:
      - name: Print Go version
        run: |
          go version
```

## サービスコンテナとの通信方法
### ランナーマシン上で動いているジョブ
まずは、ランナーマシン上で動いているジョブからサービスコンテナに通信する方法について説明します。  
ジョブをランナーマシン上で動かしている場合、サービスコンテナはランナーマシンに対してデフォルトではポートを公開していません。したがって、GitHub Actionsでサービスコンテナを定義する際は、サービスコンテナとランナーマシンのポートをマップする必要があります。例えば、この記事を作成するにあたって実装したサンプルではPostgreSQLをサービスコンテナとしたので、ランナーマシンの5432番ポートとサービスコンテナの5432番ポートをマップします。実際の設定ファイルは以下のようになります。  

```yaml
jobs:
  runner-job:
    runs-on: ubuntu-latest
    env:
      ENV: runner-job
    services:
      postgres:
        image: postgres
        env:
          POSTGRES_USER: sue
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432
```

そして、サービスコンテナのホストは `local` または `127.0.0.1` に設定されています。したがって、 `localhost:5432` に対してアクセスすればPostgreSQLと通信ができるようになります。実際に、サンプルでもランナーマシン上でジョブを実行している場合のsql-migrateは以下のように設定しており、 `localhost:5432` に対してアクセスするようになっています。

 ```yaml
 runner-job:
    dialect: postgres
    datasource: host=localhost dbname=postgres user=sue password=postgres sslmode=disable
    dir: migrations
    table: migrations
 ```


### コンテナ内で動いているジョブ
続いて、コンテナ内で動いているジョブからサービスコンテナに通信する方法について説明します。  
コンテナ内でジョブを実行している時には、ランナーマシン上でジョブを実行している時よりもシンプルにサービスコンテナと通信することができます。この時にはDockerのユーザー定義ブリッジネットワークを使っています。そのため、任意のホスト名をつけられるので、GitHub Actionsではサービスコンテナのラベル名を使ってアクセスすることができます。また、コンテナはお互いに全てのポートを公開しあっているために、ポートのマッピングをする必要がありません。  
なので、サンプルでもサービスコンテナを定義する際には以下のような設定をして、  

```yaml
  container-job:
    runs-on: ubuntu-latest
    container:
      image: golang:1.14
    env:
      ENV: container-job
    services:
      postgres:
        image: postgres
        env:
          POSTGRES_USER: sue
          POSTGRES_PASSWORD: postgres
          POSTGRES_DB: postgres
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
```

sql-migrateでDBのマイグレーションを行う際の設定も

```yaml
container-job:
    dialect: postgres
    datasource: host=postgres dbname=postgres user=sue password=postgres sslmode=disable
    dir: migrations
    table: migrations
```

のように設定し、 `postgres:5432` にアクセスできます。

[^1]: 公式ドキュメントの[こちらの部分](https://docs.github.com/ja/actions/guides/about-service-containers#about-service-containers)で言及されています。