firebaseのバックアップを同一projectのstrage内の指定バケットに自動で取る＆一定期間経過したファイルは自動で削除する

自分用の備忘録・作業ログです。
荒くてすみません。mm

# 要件
<strong>
１．Firestore のバックアップをスケジューリングしたい
２．2週間経過したバックアップの削除もスケジューリングしたい

３．Firestore のバックアップ先は 同一プロジェクト内のStorage（に作成した「bk」フォルダ）

３．Firestore のバックアップ先は 同一プロジェクト内のStorage内に作成したバックアップ用のバケット
（※自動削除の際に、バックアップ専用のバケットをStorage内に作っておいた方が都合がいい。
GCSライフサイクル機能を使ってファイルを定期削除する際に、
バケット単位でしか自動削除のルールが設定できない為）
</strong>

参考リンク：

https://firebase.google.com/docs/firestore/manage-data/export-import?hl=ja

https://firebase.google.com/docs/firestore/solutions/schedule-export?hl=ja

https://firebase.google.com/docs/cli?hl=ja

#まずは手動でバックアップを取る

まずは下記ページから読んでいく

https://firebase.google.com/docs/firestore/manage-data/export-import?hl=ja&fbclid=IwAR3zi1_rPP1M5uNORGPA7s7GOjw9H707su5zBu4XYUdMZIpia8twZFnKXCw

`できたこと：
firebaseの同一プロジェクト内のStorageに
バックアップ用の専用フォルダ「bk」を追加作成し、そちらに手動でバックアップする`

<strong>【追記】
※自動削除を行う場合には、バックアップ専用のバケットをStorage内に作っておいた方が都合がいい。
なのでディレクトリとして「bk」を作成して保存することは推奨しない。</strong>

## ざっくりとした流れメモ：
1） Google Cloud プロジェクトに対する課金を有効に

2） Cloud Shell へ（プロジェクト選択）


3） Google Cloud SDK をインストールして初期化（ログイン＆プロジェクト選択）

4） Firebaseのプロジェクト内のstrageに、任意のバックアップ用フォルダを作成する（今回は「bk」フォルダを作成）
5） Cloud Firestore の [インポート / エクスポート] ページに移動し、作成したバックアップ用のフォルダを選択
6） エクスポートする

一旦まずはエクスポートはできた。
（※単純にエクスポートを手動でとるだけなら、2）と3）は飛ばして良さそう。）

次は
自動でバックアップを取ってもらうためにスケジューリングしたい。

# 自動でバックアップを指定した時間に取るように設定

functionsを使用して、ある時間（今回は毎日午前4時）に
自動でバックアップをとるようにする。

https://firebase.google.com/docs/firestore/solutions/schedule-export?hl=ja

上記のページを読んでいくぞ。

## ざっくりとした流れメモ：
### 1） Firebase CLI をインストール

```zsh
curl -sL https://firebase.tools | bash
```
「bash」って書いてあるので、「私zshですが大丈夫…？」って思ったんですが大丈夫でした。
インストールする時の設定ですかね。
（ちなみグローバルなものみたいなのでプロジェクト内ではなくインストール）

既に入っていたので最新版に更新

>通常、新機能やバグ修正が含まれているのは Firebase CLI の最新バージョンだけです。
>CLI の更新を頻繁に行うことをおすすめします。

```zsh
curl -sL firebase.tools | upgrade=true bash
```

`⚠️つまづいたところ：
後ほど記述する、cloud functionsのデプロイ時に
nodeのバージョンが「12.20.0」に対応しておらず詰まりました。
一旦、グローバルのnodeのバージョンを「12.20.0」にした上で
firebase toolsをインストールすることで解消しました。
`

```zsh
// プロジェクトのディレクトリにて下記実行
nodenv global 12.20.0

npm install -g firebase-tools

firebase -V // ちゃんと動くか確かめ

// その後、（functions作成した後、）functionsのディレクトリに移動してからデプロイ
firebase deploy --only functions:scheduledBackup
```


もし、別のgoogleアカウントで既にログインしていたら
logoutしてlogin

```zsh
firebase logout
```

```zsh
firebase login
```

### ２）Cloud Datastore インポート / エクスポート管理者ロールを割り当て

参考URLにはコマンドで叩くように書いてあるが、
下記URLからGUIでも権限付与が可能。
https://console.cloud.google.com/iam-admin/iam
（`PROJECT_ID@appspot.gserviceaccount.com`に対して
`Cloud Datastore インポート / エクスポート管理者`ロールを割り当てる）


### 3）Cloud 関数と Cloud Scheduler ジョブを作成する
https://firebase.google.com/docs/firestore/solutions/schedule-export?hl=ja
ソースコードの例を見ながらこんな感じでCloud functionsを実装。

以下、`scheduledBackup.js`

```javascript
const functions = require('firebase-functions')
const firestore = require('@google-cloud/firestore')
const client = new firestore.v1.FirestoreAdminClient()

// Replace 【バケット名】：FirebaseのStorageのページに書いてあるリンク名を参照
const bucket = `gs://【バケット名】`
//例 const bucket = `gs://hogehoge-project-backup.appspot.com`

exports.scheduledFirestoreExport = functions
  .region('asia-northeast1')
  // .pubsub.schedule('*/2 * * * *')
  .pubsub.schedule('0 4 * * *')
  .timeZone('Asia/Tokyo')
  .onRun((context) => {
    const projectId = process.env.GCP_PROJECT || process.env.GCLOUD_PROJECT
    const databaseName = client.databasePath(projectId, '(default)')

    return client
      .exportDocuments({
        name: databaseName,
        outputUriPrefix: bucket,
        // Leave collectionIds empty to export all collections
        // or set to a list of collection IDs to export,
        // collectionIds: ['users', 'posts']
        collectionIds: []
      })
      .then((responses) => {
        const response = responses[0]
        console.log(`Operation Name: ${response.name}`)
      })
      .catch((err) => {
        console.error(err)
        throw new Error('Export operation failed')
      })
  })

```

おまけ：使わなかったコード（ディレクトリ指定＆日付指定）

```javascript
const functions = require('firebase-functions')
const firestore = require('@google-cloud/firestore')
const client = new firestore.v1.FirestoreAdminClient()
const moment = require('moment')

exports.scheduledFirestoreExport = functions
  .region('asia-northeast1')
  // 分 時 日 月 曜日 の順に指定。
  // テスト用に2分おきにバックアップを取る指定
  // .pubsub.schedule('*/2 * * * *')
  // 毎日深夜4時にバックアップを取る指定
  .pubsub.schedule('0 4 * * *')
  // この指定のタイムゾーンに則って実行
  .timeZone('Asia/Tokyo')
  .onRun((context) => {
    // timestampをとろうとすると、9時間ずれるので今回はmomentを使って調整。
    const timestamp = moment().add(9, 'hours').format('YYYYMMDD-HHmmss')

    // Replace 【projectname】：FirebaseのStorageのページに書いてあるリンク名を参照
    // 今回は「bk」というフォルダの下に「timestamp」ごとにフォルダを作って保存する
    const bucket = `gs://【projectname】.appspot.com/bk/${timestamp}`

    const projectId = process.env.GCP_PROJECT || process.env.GCLOUD_PROJECT
    const databaseName = client.databasePath(projectId, '(default)')

    return client
      .exportDocuments({
        name: databaseName,
        outputUriPrefix: bucket,
        collectionIds: []
      })
      .then((responses) => {
        const response = responses[0]
        console.log(`Operation Name: ${response.name}`)
      })
      .catch((err) => {
        console.error(err)
        throw new Error('Export operation failed')
      })
  })

```

今回は下記のような構成なので
`index.js` でrequireする。

```javascript
📁functions
∟📄index.js // ←ここでrequire
∟📁.cert
　∟📄【project名】-firebase-adminsdk-xxxxxxxxxxx.json // ←デプロイに必要
∟📁funcs
　∟📁scheduled
　　∟📄scheduledBackup.js　// ←これを
∟📄.node-version // 12.20.0
```

以下、`index.js`

```javascript
const admin = require('firebase-admin')
const serviceAccount = require('./.cert/【project名】-firebase-adminsdk-xxxxxxxxxxx.json')

/**
 * initialize admin
 */
admin.initializeApp({
  credential: admin.credential.cert(serviceAccount)
})

// 他で使ってるやつ
exports.scheduledBackup = require('./funcs/hogehoge.js')
exports.scheduledBackup = require('./funcs/hugahuga.js')

// 今回作ったやつ
exports.scheduledBackup = require('./funcs/scheduled/scheduledBackup.js')
```

### 4）デプロイ
デプロイするときには `.cert` 内に `【project名】-firebase-adminsdk-xxxxxxxxxxx.json` が必要。
また、全てのfunctionsをデプロイするととても重くなるので
該当のものだけ単体でデプロイするのがいい。時間がかかるので。

```zsh
firebase deploy --only functions // これだと全部デプロイされる
```

```zsh
firebase deploy --only functions:scheduledBackup // これだと指定したfunctionのみデプロイされる
```

### 設定されたかどうか確認したい時は
こちらの「cloudscheduler」のページでできる。
https://console.cloud.google.com/cloudscheduler

### functionsのコンソールを確認したい時は
firebase管理画面の
https://console.firebase.google.com/u/0/project/【プロジェクト名】/functions/list
のページの、右側「…」から見ることができる。

# 一定期間過ぎたバックアップファイルの削除

こちらの記事を参考にさせていただきました↓🙏
<b>GCSライフサイクル機能を使ってファイルを定期削除する</b>

https://qiita.com/Sekky0905/items/65c60da94fe945a454fa

要点は以下。
・GCSのライフサイクル機能を使う
・バケット単位で「作成から一定時間が経ったデータ」を削除することが可能
（今回はバックアップ専用のバケットを作って、そちらに保存する。
当初はバケット内に専用のディレクトリを作成してその中に保存しようとしていたが、路線変更した。）
（※バケット単位でルールを指定することができる。）
・gsutilツールのコマンドで、設定用のjsonファイルをGCSに設定する
・反映まで24時間くらいかかることもある

他のgoogleアカウントでログインしている場合はログアウトしてからログインし直す。
コマンドは

###ログイン
```
$ gcloud auth login
```

###ログアウト
```
$ gcloud auth revoke
```

###プロジェクト一覧
```
$ gcloud projects list
```

その他コマンドは

https://qiita.com/komiya_____/items/4cef3b2d47ed42a7caf5


---

以下、
https://qiita.com/Sekky0905/items/65c60da94fe945a454fa
から引用させていただきます。🙏
（もし記事が削除された時用のメモです。）

#### 設定用jsonファイル

14日後に削除する場合

```json
{
    "lifecycle": {
        "rule": [
            {
                "action": {
                    "type": "Delete"
                },
                "condition": {
                    "age": 14,
                    "isLive": true
                }
            }
        ]
    }
}
```

`"type": "Delete"`
ライブ オブジェクトまたはアーカイブされたオブジェクトを削除する

`"age": 14`
日数指定条件、ageで指定した日数が経過したら条件が満たされたとして、actionが発動する
１年後であれば `"age": 365`


#### gsutilツールのコマンドでjsonの設定をGCSに設定する

```
gsutil lifecycle set [jsonのファイル名] gs://[設定対象のGCSのバケット名]
```

設定の確認コマンド

```
gsutil lifecycle get gs://[設定対象のGCSのバケット名]
```

これが返ってきたら設定うまくいってる

```
{"rule": [{"action": {"type": "Delete"}, "condition": {"age": 14, "isLive": true}}]}
```

以下のURLからも確認できる
https://console.cloud.google.com/storage/browser/【バケット名】;tab=lifecycle

リファレンス
https://cloud.google.com/storage/docs/managing-lifecycles