Cloud Functions GoでFirestoreの定期バックアップ

Cloud Functions Goを使ってみるの第二弾です。(前回は[こちら](https://qiita.com/iwata@github/items/76545282ec136a4f271d))

## tl;dr

- GoでFirestoreをバックアップするには[google.golang.org/api/firestore/v1](https://godoc.org/google.golang.org/api/firestore/v1)を使う必要がある
- Cloud FunctionsでもApplication Default Credentialsが使えるので簡単にAPIを呼ぶことができる

## まえがき

去年(2018年)の8月のアナウンスでFirestoreのimport/exportができるようになりました。
[The Firebase Blog: More Cloud Firestore Improvements\!](https://firebase.googleblog.com/2018/08/more-cloud-firestore-improvements.html)

gcloudコマンドを使うか、以下のREST APIでExportできます。
[Method: projects\.databases\.exportDocuments  \|  Cloud Firestore  \|  Google Cloud](https://cloud.google.com/firestore/docs/reference/rest/v1/projects.databases/exportDocuments)

## Cloud Scheduler経由でFirestoreを定期バックアップ

詳しくは以下のGincoさんのブログで紹介されています。
[Cloud Scheduler で Firestore を定期的にバックアップする \- Ginco Tech Blog](https://tech.ginco.io/post/cloud_scheduler/)

Cloud Scheduler経由でPub/SubトピックをPublishし、SubscribeしたCloud Functions内で前述のREST APIを呼んでいます。
ExportするCloud Functionsのサンプルは大概Node.jsのものが多いので、今回Goのサンプルを作ってみました。
コードはGitHubにもあげてあります。
[go\-cloud\-functions\-examples/backup\-firestore at master · iwata/go\-cloud\-functions\-examples](https://github.com/iwata/go-cloud-functions-examples/tree/master/backup-firestore)

## Cloud Functions

REST APIなので素朴に`http.Client`とかで実装してもよいですが、今回はGoogleのGoライブラリを使いました。
Export APIに対応しているのは[google.golang.org/api/firestore/v1](https://godoc.org/google.golang.org/api/firestore/v1)になります。

> This package is DEPRECATED. Use package cloud.google.com/go/firestore instead.

と書かれていますが、その[cloud.google.com/go/firestore](https://godoc.org/cloud.google.com/go/firestore)にはいまのところExport用のメソッドが存在しないため、`google.golang.org/api/firestore/v1`を使います。

```go
package function

import (
	"context"
	"fmt"
	"os"

	"github.com/pkg/errors"
	"google.golang.org/api/firestore/v1"
	"google.golang.org/api/option"
)

var projectID string

// PubSubMessage is a body data from Pub/Sub payload
type PubSubMessage struct {
	Data string `json:"data"`
}

func init() {
	projectID = os.Getenv("GCP_PROJECT")
}

// BackupFirestore is triggered by Cloud Functions
func BackupFirestore(ctx context.Context, m PubSubMessage) error {
	svc, err := firestore.NewService(ctx, option.WithScopes(firestore.DatastoreScope, firestore.CloudPlatformScope))
	if err != nil {
		return errors.Wrap(err, "Failed to create Firestore service")
	}

	req := &firestore.GoogleFirestoreAdminV1ExportDocumentsRequest{
		OutputUriPrefix: fmt.Sprintf("gs://%s-backup-firestore", projectID),
	}
	_, err = firestore.NewProjectsDatabasesService(svc).ExportDocuments(
		fmt.Sprintf("projects/%s/databases/(default)", projectID), req,
	).Context(ctx).Do()
	if err != nil {
		return errors.Wrap(err, "Failed to export Firestore")
	}

	fmt.Println("Backup Successfully")

	return nil
}
```

~~OAuthクライアントには[google.DefaultClient](https://godoc.org/golang.org/x/oauth2/google#DefaultClient)を使います。
これを使うことでCloud Functionsを起動しているService AccountのApplication Default Credentialsで認証可能となり、アクセストークンの取得を省略できます。(便利!)~~
`google.golang.org/api` v0.3.0から`google.DefaultClient`不要になりました。(さらに便利!)
詳細は以下のドキュメントを参照してください。
https://godoc.org/google.golang.org/api/firestore/v1#hdr-Creating_a_client

Cloud Functionsは特に指定なしだとGAEのデフォルトService Accountを使用しますので、特に設定していない場合はそのサービスアカウントの以下のRoleを付与します。

- Cloud Datastore Import Export Admin
- Storage Admin

`OutputUriPrefix`には保存先のCloud Storageを指定します。
こちらは任意で書き換えてください。

認証周りを気にしなくてよいのですこぶるシンプルに書けますね!