【Firebase】Firestore の自動バックアップを24行で実現する

なるべく firebase プロダクト内で完結する Firestore の自動バックアップを目指してみました
役者が揃ってきたこともあり、今ならだいぶ簡潔に実装できます

## 結論

ADC と functions の pubsub.schedule 機能を使うのがポイントです

```typescript
import { region } from 'firebase-functions'
import { credential } from 'firebase-admin'
import axios from 'axios' // optional

export const backupFirestoreToStorage = region('asia-northeast1')
  .pubsub.schedule('0 3 * * *')
  .timeZone('Asia/Tokyo')
  .onRun(async () => {
    try {
      const accessToken = await credential
        .applicationDefault()
        .getAccessToken()
        .then(result => result.access_token)
      const projectID = process.env.GCLOUD_PROJECT
      const response = await axios.post(
        `https://firestore.googleapis.com/v1/projects/${projectID}/databases/(default):exportDocuments`,
        { outputUriPrefix: `gs://${projectID}-firestore-backups` },
        { headers: { Authorization: `Bearer ${accessToken}` } }
      )
      console.log(response)
    } catch (err) {
      console.error(err)
    }
  })
```

## 手順

2019年6月時点での方法です

（どうしても GCP の操作が必要になる部分はありますが、その際は `gcloud` コマンドは使わずにコンソールで行うようにしています）

### 課金を有効にする

まずは Blaze プランにして課金を有効にします

### バックアップ用の Storage バケットを作成

GCP のコンソールから「Storage」を開き、一意のバケット名を指定して作成します
今回の例では `${projectId}-firestore-backup` としています
<img width="777" alt="スクリーンショット 2019-06-13 18.37.31.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199914/cbf6a4ba-6737-1947-85b0-465fd4012f30.png">

Firestore のリージョンと同じリージョンを指定しましょう

参考：https://speakerdeck.com/vexus2/alu-firestore-number-aru?slide=14

### サービスアカウントに役割を付与

GCP のコンソールから「IAM と管理」を開き、
`[project-id]@appspot.gserviceaccount.com` に以下の２つの役割を付与します

- ストレージのオブジェクト管理者
- Cloud Datastore インポート / エクスポート管理者

<img width="860" alt="スクリーンショット 2019-06-13 19.00.26.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199914/25f91240-a61e-f2f4-9cea-59d8ba4bf22c.png">


### functions をデプロイ

```bash
$ firebase deploy --only functions:backupFirestoreToStorage
```

これで終わりです
デプロイの際に Cloud Pub/Sub と Could Scheduler を自動で設定してくれます（便利）

今回は毎日午前3時にバックアップを実行する cron (Cloud Scheduler) を設定しました
動作確認をする場合は Cloud Scheduler を実行すれば OK です
<img width="1062" alt="スクリーンショット 2019-06-13 19.11.36.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/199914/3023211a-de42-29f2-b517-1870b156b13f.png">

## 解説

### ADC について

Cloud Firestore のバックアップは REST API 経由で実行しているため OAuth2 のアクセストークンが必要です
そのためにサービスアカウントキーの json ファイルを渡したり googleapis ライブラリを使ったりしてトークンを発行するサンプルコードがありましたが、実はそれらを使わなくても実現することができます

[サーバー間での本番環境アプリケーションの認証の設定](https://cloud.google.com/docs/authentication/production?hl=ja)

ADC (= Application Default Credentials) の詳しい説明は上記の公式ドキュメントに任せますが、
今回の流れで説明すると

- Cloud Functions に設定されているデフォルトのサービスアカウント(`[project-id]@appspot.gserviceaccount.com`)がある
- firebase-admin が ADC を使ってそのサービスアカウントを検出している  
  (今回のサンプルの `credential.applicationDefault()` に対応)
- ついでに OAuth2 のアクセストークンを取得 (`getAccessToken()` に対応)
- 検出したサービスアカウントに firestore バックアップ用の権限を持たせておいたので API が実行できる

となります

サービスアカウントキーを管理しなくて済みますし、追加のライブラリが必要ないのでよりセキュアな実装といえるのではないでしょうか

他にいい方法があればぜひ教えてくださいm(_ _)m

余談ですが、
`[project-id]@appspot.gserviceaccount.com` は IAM 上では App Engine default service account と説明されているのに Cloud Functions のデフォルトのサービスアカウントでもあるのややこしいですよね。。。