Firebase Hosting & GitHub連携によって構築されるCI/CD環境の仕組み

Firebase CLI を使って Firebase Hosting と GitHub を連携させることで、
ほとんど自動的に次のようなCI/CD環境を構築できます。
* プルリクエストを作成すると自動的にプレビューチャンネルをデプロイする
* プルリクエストをマージすると自動的に本番環境にデプロイする

今回はこのCI/CD環境の仕組みについて、
1. GitHub Actions の仕組み
2. Firebase CLI によって作成されるワークフロー

の2点を解説しながら掘り下げます。

構築方法についてはこちらの記事が参考になりました。
・[FirebaseとGitHubを連携しCI/CD環境を構築する](https://btj0.com/blog/react/firebase-github-ci/)

## GitHub Actions の仕組み

GitHub Actions とは、Github が提供している機能であり、Github 上のイベントに応じてタスクを自動化することができます。
Firebase Hosting への自動デプロイは、この GitHub Actions を用いることで実現されています。

GitHub Actions の仕組みを把握するために、GitHub Actions を構成する6つの要素について説明します。


### ワークフロー

ワークフローとは、自動化する一連の処理とその処理を実行する条件を定義したものです。

ワークフローは`YAML`形式で定義され、リポジトリの`.github/workflows`ディレクトリに保存することで実行できるようになります。

```yaml:example.yaml
name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
  build_and_preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build
      - uses: FirebaseExtended/action-hosting-deploy@v0
```


### イベント

イベントは、プルリクエストやプッシュなどのアクティビティです。
ワークフローはこのイベントをトリガーとして実行されます。

`YAML`ファイルには`'on'`というディレクティブがあり、そこでトリガーとなるイベントを指定することができます。


### アクション

アクションは、ワークフローを構成する最小単位の処理です。

`run`でコマンドを実行することや、`use`でGithubやサードパーティの公開アクションを利用することができます。


### ステップ

アクションを実行することをステップと呼びます。

### ジョブ

ジョブは複数のステップで構成された処理です。ジョブ同士は並列処理されます。


### ランナー

ジョブはランナーと呼ばれるサーバー上で実行されます。ランナーは GitHub でホストされています。


GitHub Actions は以上の6つの要素から構成されています。


```yaml:example.yaml
name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
  build_and_preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build
      - uses: FirebaseExtended/action-hosting-deploy@v0
```
例えば上のワークフローは

* ワークフローはプルリクエストによってトリガーされ、一連のステップでジョブを実行する。
* まずnpmから依存関係をインストールし、次にビルドスクリプトを実行し、最後にプレビューチャンネルにデプロイする。
* すべてのプロセスは Ubuntu ランナー上で行われる。

といったことを表しています。


## Firebase CLI が作成するワークフロー

Firebase CLI によって作成されるワークフローの特徴的な点について解説します。

### プルリクエスト用

```yaml:firebase-hosting-pull-request.yaml
name: Deploy to Firebase Hosting on PR
'on': pull_request
jobs:
  build_and_preview:
    if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
    runs-on: ubuntu-latest
        steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'
          projectId: project-id
```
・プルリクエストを作成するとプレビューチャンネルをデプロイする
・デプロイしたURLをプルリクエストにコメントする
といった処理を行うワークフローです。

細かく見ていきます。
<br>
```yaml
if: '${{ github.event.pull_request.head.repo.full_name == github.repository }}'
```
fork したリポジトリから元リポジトリへプルリクエストを送る場合に、
GitHub Actions を実行しないよう実行条件を指定しています。
<br>
```yaml
uses: actions/checkout@v2
```
ワークフローがリポジトリにアクセスできるようにチェックアウトします。
<br>
```yaml
run: npm ci && npm run build
```
package-lock.json から依存関係をインストールし、ビルドを行います。
<br>
```yaml
uses: FirebaseExtended/action-hosting-deploy@v0
```
以下の処理を行います。
・プレビューチャンネルをデプロイする
・ビルドログからプレビューの URLを抽出する
・プルリクエストが初めてオープンされた場合だけプレビューのURLをコメントする
・すでにプレビューのURLがコメントされていた場合はそれを見つける
<br>
```yaml
with:
      repoToken: '${{ secrets.GITHUB_TOKEN }}'
      firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'
      projectId: project-id
```
`FirebaseExtended/action-hosting-deploy@v0`に必要な変数を定義しています。

**repoToken**
GitHub 側で自動設定される GitHub のトークンです。
ビルドログからプレビューの URL を見つけるために必要になります。

**firebaseServiceAccount**
Firebase Hosting 用にのみスコープが設定されたGoogle Cloud Service Account です。
GitHub Action にデプロイを代行する権限を付与するために必要になります。

**projectId**
デプロイ先の Firebase プロジェクトのプロジェクト IDです。


### マージ用

```yaml:firebase-hosting-merge
name: Deploy to Firebase Hosting on merge
'on':
  push:
    branches:
      - main
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
        steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'
          channelId: live
          projectId: project-id

```
プルリクエストをマージすると自動的に本番環境にデプロイするワークフローです。

プルリクエスト時のワークフローとの違いは`channelId: live`の有無です。
`channelId:live`を指定した場合は本番環境へデプロイされ、
空白の場合はブランチや プルリクエスト ごとにプレビューチャンネルとその ID が自動生成されます。

---
この記事は以下の情報を参考にして執筆しました。
* [FirebaseExtended/action-hosting-deploy - GitHub](https://github.com/FirebaseExtended/action-hosting-deploy)
* [The comprehensive guide to GitHub Actions and Firebase Hosting](https://medium.com/firebase-developers/the-comprehensive-guide-to-github-actions-and-firebase-hosting-818502d86c31)
* [Understanding GitHub Actions](https://docs.github.com/ja/actions/learn-github-actions/understanding-github-actions)