github actions でfirebaseに自動デプロイするための設定

サービス開発のときは、環境構築と同時にデプロイ自動化まで行うのが俺的セオリーです。
というわけで、firebaseを使ったアプリケーションのデプロイを自動化する方法を紹介します。

# 想定QA
**Q. なんで環境構築と同時にデプロイ自動化までするの？面倒くない？**

A. 下のような理由

* 実際に使うインフラを知っておいたほうが、開発環境をどう構築したらいいかがわかる。開発環境は本番環境にできる限り近づけて作るべきなので、本番環境を先に知っといたほうがいい。
* 開発終わってから、デプロイの仕方わからーんってなって、構成見直すといった手戻りを防げる。
* どうせいつかやるから最初やっても同じ
* 開発のときのテンションが上がる


**Q. Github Actions癖強くない？CircleCIとかで良くない？餅は餅屋理論**

A. Githubで完結するのは強い。Github Actionsのデプロイ設定が面倒くさいのは一時の苦痛だが、デプロイのためにCircleCI等使うサービス1つ増やすのは一生の苦痛。

# 目的
githubにpushしたら自動でデプロイされるように、github actionsを設定します。
nuxtアプリ(SPAモード)を題材に書きますが、ビルドコマンド等をよしなに変更したら、他の環境でも使えるはずです。

# 前提
firebaseのhosting, firestoreのrules/index, GCP Cloud Functionsの3つをデプロイします。

# 手順
1. ci用にサービスアカウントを作成
2. サービスアカウントの認証ファイルをbase64で変換
3. github secretsにSA keyの登録
4. [optional] firebaseの各種認証情報もついでに登録
5. github actionsの設定
    1. firebase hostingへのデプロイ
    2. firebase firestoreのデプロイ
    3. functionsのデプロイ

## ci用にサービスアカウントを作成
https://console.cloud.google.com/iam-admin/serviceaccounts よりci用のサービスアカウントを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240149/dd6cafa0-5e2a-df35-081f-1c94fc448a9a.png)
「サービスアカウントを作成」ボタンをおします。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240149/86d24f72-9245-d29a-765e-cab1079a7387.png)
IDを適当に設定し、作成を押します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240149/2a36b754-149d-b9d9-549b-b7db22b71a97.png)
「Firebase Develop 管理者」の権限を付与します。
後述するCloudFunctionsにもデプロイする場合は、「Cloud Functions開発者」「サービス アカウント ユーザー」のロールも付与します。

次の画面の「ユーザーにこのサービス アカウントへのアクセス権を付与」は設定せずに、そのまま作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240149/54561ef3-37e2-57d3-a5a6-ecc904cd29c4.png)
一覧画面に戻ったら、詳細メニューから鍵を作成のボタンを押し、jsonファイルをダウンロードします。

## サービスアカウントの認証ファイルをbase64で変換
jsonファイルをbase64で文字列に変換します。
macだとこんな感じで変換できます。
gcp-sa-key.jsonは先程ダウンロードしたファイルです。

```bash
> cd ~/Downloads
> cat gcp-sa-key.json | base64
NANKARANDOMPPOIMOZIRETSUGADETEKURU4LJ5HFROPO2pSTNgt1HhKU6ZIo1n2t
1YY5uiNoIziuZI5QwYrd0szqBu6vGQJ6UuZPqZfEP6gmKX8FFXUXldj0yKcEx+7C
DWgMir3m/Tz4PoXQBVx7tqG3NdMoM3gCm4k++HHWHRP/VX3Xf+UB8UQEl+VsSOgk
PP96iGr2pW3WAo7IJK/qB0nQ
# このままクリップボードにコピー(mac限定)
> cat gcp-sa-key.json | base64 | pbcopy
```

## github secretsにSA keyの登録
githubリポジトリに行き、settingsの中のsecretsから環境変数を設定します。
url的には`https://github.com/{your_account}/{your_repo_name}/settings/secrets`でアクセスできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240149/ab912d13-5fcb-99fc-b74d-21db762bbc48.png)

## [optional] firebaseの各種認証情報もついでに登録
本筋と外れますが、nuxtとfirebaseをつなぐ認証情報をここで色々設定しておきます。
最終的にはこんな感じになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/240149/ed371917-097b-2838-0ea8-536539b758e4.png)

## github actionsの設定
ここからが本題です。
今回用いるプロジェクト構成図は以下のようになっています。
(関係するファイルのみ抜き出しています。)

```
root
├── firestore // firestoreのrulesやindexesをまとめたディレクトリ
│   ├── firebase.json // firestoreの設定
│   ├── firestore.rules // firestoreの読み書きrules
│   ├── firestore.indexes.json // firestoreのindexの設定
│    ・・・
├── front // フロントのコードをまとめたディレクトリ nuxt create-app の成果物。
│   ├── firebase.json // hostingの設定が書かれている
│    ・・・
└── functions
    ├── func1
    └── func2
```

### firebase hostingへのデプロイ
説明はyml内に書きます。

```deploy.yml
name: Deploy

# masterにpushするたびに、github actionsを走らせる。
on:
  push:
    branches: [ master ]

# jobを複数定義できる。
jobs:

  # frontのデプロイ
  front:
    runs-on: ubuntu-latest
    # デフォルトで走らせるディレクトリを指定
    defaults:
      run:
        working-directory: ./front
    steps:
    - uses: actions/checkout@v2
    
    # node & yarn を入れる
    - name: setup node
      uses: actions/setup-node@v1
      with:
        node-version: '12.x'
    - name: install
      run: yarn
      env:
        NODE_ENV: development

    # yarn generateコマンドでbuildする。
    # nuxtに注入する環境変数を明示的に指定する。
    - name: build
      run: yarn generate
      env:
        APP_ENV: production
        NUXT_ENV_APIKEY: ${{ secrets.NUXT_ENV_APIKEY }}
        NUXT_ENV_AUTH_DOMAIN: ${{ secrets.NUXT_ENV_AUTH_DOMAIN }}
        NUXT_ENV_DATABASE_URL: ${{ secrets.NUXT_ENV_DATABASE_URL }}
        NUXT_ENV_PROJECT_ID: ${{ secrets.NUXT_ENV_PROJECT_ID }}
        NUXT_ENV_STORAGE_BUCKET: ${{ secrets.NUXT_ENV_STORAGE_BUCKET }}
        NUXT_ENV_MESSAGING_SENDER_ID: ${{ secrets.NUXT_ENV_MESSAGING_SENDER_ID }}
        NUXT_ENV_APP_ID: ${{ secrets.NUXT_ENV_APP_ID }}

    # build結果をfirebase hosgint にデプロイ
    # 先ほど取得したSAのkeyを環境変数として渡す。
    # PROJECT_ID は firebaseのプロジェクトID(uniqueな方)
    - name: Deploy Firebase Hosting
      uses: w9jds/firebase-action@master
      with:
        args: deploy --only hosting
      env:
        PROJECT_ID: your-pj-id
        GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
        PROJECT_PATH: ./front
```

hostingだけしたい人はここまでで大丈夫です。

### firebase firestoreのデプロイ
先ほどの`deploy.yml`に追記します。

```deploy.yml
name: Deploy

# masterにpushするたびに、github actionsを走らせる。
on:
  push:
    branches: [ master ]

# jobを複数定義できる。
jobs:
  front:
    # ((省略))

  firestore:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2

    # front同様、SAのkeyを環境変数として渡す。
    # firebaseのdeployコマンドのoptionが異なる。
    - name: Deploy Firestore rules and index
      uses: w9jds/firebase-action@master
      with:
        args: deploy --only firestore
      env:
        PROJECT_ID: your-pj-id
        GCP_SA_KEY: ${{ secrets.GCP_SA_KEY }}
        PROJECT_PATH: ./firestore
```

### functionsのデプロイ
**注意: この章ではfirebaseのfunctionsではなくGCPのCloud Functionsへのデプロイの仕方を紹介します。**(pythonを使いたかったため)

最後にfunctionsへのデプロイの仕方です。
functionsは別jobに並列化することで実行時間を減らすことができます。
functionのdeployにわたすoptionは公式ドキュメントから確認できます。

参考
- https://github.com/GoogleCloudPlatform/github-actions
- https://cloud.google.com/functions/docs/deploying/filesystem?hl=ja

```deploy.yml
name: Deploy

# masterにpushするたびに、github actionsを走らせる。
on:
  push:
    branches: [ master ]

# jobを複数定義できる。
jobs:
  front:
    # ((省略))

  firestore:
    # ((省略))

  functions_func1:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v2
    - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
      with:
        version: '290.0.1'
        service_account_key: ${{ secrets.GCP_SA_KEY }}
        project_id: your-pj-id
    - name: Deploy Functions
      run: |
        gcloud functions deploy a --allow-unauthenticated --source ./functions/func1 \
        --runtime python37 --trigger-http --region=asia-northeast1 \
        --set-env-vars ENV=production

  functions_func2:
    runs-on: ubuntu-latest
    steps:
    - name: Checkout
      uses: actions/checkout@v2
    - uses: GoogleCloudPlatform/github-actions/setup-gcloud@master
      with:
        version: '290.0.1'
        service_account_key: ${{ secrets.GCP_SA_KEY }}
        project_id: your-pj-id
    - name: Deploy Functions
      run: |
        gcloud functions deploy a --allow-unauthenticated --source ./functions/func2 \
        --runtime python37 --trigger-http --region=asia-northeast1 \
        --set-env-vars ENV=production
```

# まとめ
みなさんも快適な自動デプロイライフを！