My External Storage

May 1, 2020 - 4 minute read - Comments - Go GitHub GCP

GitHub Actions上でGCP Datastoreエミュレータを使ったテストを実行する

GCP Datastoreを扱うコードをGitHub Actions上でテストする方法をまとめた。

TL;DR

jobs:
  job-name:
    runs-on: ubuntu-latest
    services:
      datastore:
        image: singularities/datastore-emulator
        env:
          DATASTORE_LISTEN_ADDRESS: 0.0.0.0:18001
          DATASTORE_PROJECT_ID: budougumi0617
        ports:
          - 18001:18001
    env:
      DATASTORE_EMULATOR_HOST: localhost:18001

なお、引用しているGitHub Actions、GCPの説明は2020/05/01時点の説明である。

GitHub Actionsの設定

さっそく、エミュレータについて確認し、Actinosの設定YAMLを書いていく。

GCP Datastoreエミュレータ

GCP Datastoreはエミュレータが提供されている。

JREとgcloudコマンドを用意するのは骨が折れるので、Docker Hubで公開されている次のGCP Datastoreエミュレータコンテナを利用する。

ActionsでDockerコンテナを起動する

GitHub Actions上で任意のDockerコンテナを実行するには次の手順が用意されている。

jobs.<job_id>.containerはそのジョブ自体を実行する環境を指定するオプションのため、jobs.<job_id>.servicesのほうを利用する。 jobs.<job_id>.servicesの説明自体にも、以下のような助言が記載されている。

ワークフロー中のジョブのためのサービスコンテナをホストするために使われます。 サービスコンテナは、データベースやRedisのようなキャッシュサービスの作成に役立ちます。 ランナーは自動的にDockerネットワークを作成し、サービスコンテナのライフサイクルを管理します。

サービスコンテナへの接続方法

サービスコンテナ自体への接続は次のページに記載されている。

今回のActionはランナーマシン上でジョブを実行するため、次の記載に記載されている接続方法になる。

ジョブをランナーマシン上で直接実行する場合、サービスコンテナにはlocalhost:もしくは127.0.0.1:を使ってアクセスできます。 GitHubは、サービスコンテナからDockerホストへの通信を可能にするよう、コンテナネットワークを設定します。

設定したYAML

jobs.<job_id>.servicesを使い、PRに対してGoのテストを実行するYAMLの設定は次のとおりになる。

# .github/workflows/test.yml
on: [pull_request]
name: test
jobs:
  test:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    services:
      datastore:
        image: singularities/datastore-emulator
        env:
          DATASTORE_LISTEN_ADDRESS: 0.0.0.0:18001
          DATASTORE_PROJECT_ID: budougumi0617
        ports:
          - 18001:18001
    env:
      DATASTORE_EMULATOR_HOST: localhost:18001
    steps:
    - name: Install Go
      if: success()
      uses: actions/setup-go@v1
      with:
        go-version: 1.13.x # GAE/Go 1.13環境を想定して
    - name: Checkout code
      uses: actions/checkout@v1
    - name: Use Cache
      uses: actions/cache@v1
      with:
        path: ~/go/pkg/mod
        key: ubuntu-latest-go-${{ hashFiles('**/go.sum') }}
        restore-keys: |
                    ubuntu-latest-go-
    - name: Download Modules
      if: steps.cache.outputs.cache-hit != 'true'
      run: go mod download
    - name: Run tests
      run: go test -v -race -covermode=atomic

テストコードの作成

GitHub Actions上でDatastoreエミュレータを起動する準備はできた。 実際にエミュレータを使ったテストを書く。

エミュレータへの接続設定

google-cloud-goライブラリからエミュレータへ接続するようにするには、環境変数を指定するだけで良い。

google-cloud-goライブラリからのdatastoreクライアントの初期化処理を確認すると次のようになっている。

func NewClient(ctx context.Context, projectID string, opts ...option.ClientOption) (*Client, error) {
  var o []option.ClientOption
  if addr := os.Getenv("DATASTORE_EMULATOR_HOST"); addr != "" {
    o = []option.ClientOption{
      option.WithEndpoint(addr),
      option.WithoutAuthentication(),
      option.WithGRPCDialOption(grpc.WithInsecure()),
    }
  } else {
        // ...

よって、ActionsのYAMLファイル中で宣言したDATASTORE_EMULATOR_HOST: localhost:18001という環境変数設定があるだけでエミュレータへ接続してテストが実施される。

テストコードは簡単に以下のとおり。

package gaebot

import (
  "context"
  "testing"

  "cloud.google.com/go/datastore"
  "go.mercari.io/datastore/clouddatastore"
)


type CloudDatastoreStruct struct {
	Test string
}

// Actionsの動作確認。
func TestCloudDatastore_Put(t *testing.T) {
  ctx := context.Background()
  cli, _ := datastore.NewClient(ctx, "budougumi0617")
  client, err := clouddatastore.FromClient(ctx, cli)
  if err != nil {
    t.Fatal(err.Error())
  }
  defer client.Close()

  key := client.IncompleteKey("CloudDatastoreStruct", nil)
  if _, err = client.Put(ctx, key, &CloudDatastoreStruct{"Hi!"}); err != nil {
    t.Fatal(err.Error())
  }
}

終わりに

google-cloud-goライブラリのDatastore以外のクライアントも少し確認したところ、 Cloud Storage用のクライアントも環境変数を設定する方法でエミュレータへ接続するようになっていた。 外部接続先を変更するようなテストを書く機会は度々あるので、クライアントライブラリがこのような設計になっているのは開発者としても使いやすいなと思った。

また、Actionsの設定にはtimeout-minutes: 10という設定も追加している。これはジョブの実行時間を分単位で制限する設定だ。 タイムアウトを設定していないまま、間違った設定でエミュレータへ接続を試みるとジョブが終わらない。 Actionsの無料実行時間制限に達しないように、このような設定をしておくほうが安心だろう。

参考

関連記事