TerraformでFCM Webプッシュ通知の環境を構築する

## はじめに

FCM（[FirebaseCloudMessaging](https://firebase.google.com/docs/cloud-messaging?hl=ja)）でWebプッシュ通知を送信する環境をTerraformで構築しました。

- 通知受取側：PWA（Nuxt3)
- 通知送信側：AWS Lambda(Python)

Pythonから送信するので、[FirebaseAdminSDK](https://firebase.google.com/docs/reference/admin?hl=ja)も使えるようにします。
バックエンドは私の場合はLambdaですが、他の場合でもFCMの環境作成自体は同じです。

コンソールから構築する記事はたくさんあったのですが、Terraformでの実装例が全然ないのと、普段AWSを主に使っているのでGCPの理解が足りず割と苦労しました。

FirebaseとTerraformの公式ドキュメントを参考にしています。

https://firebase.google.com/docs/projects/terraform/get-started?hl=ja

https://registry.terraform.io/providers/hashicorp/google/latest

コンソールから作る場合の参考

https://firebase.google.com/docs/cloud-messaging/js/client?hl=ja

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

## 実行環境

- macOS Ventura `13.2.1`
- Terraform `v1.5.4`
- Google Cloud SDK `464.0.0`
- bq `2.0.101`
- core `2024.02.09`
- gcloud-crc32c `1.0.0`
- gsutil `5.27`

Google Cloud SDK以下は[gcloud-cli](https://cloud.google.com/sdk/gcloud?hl=ja)をインストールしたら一緒に入ってきたツール達です。

## gcloud-cliの認証

Terraformからリソースを作成するにあたり、作成対象のGoogleアカウントの認証をgcloud-cliで行う必要があります。

ここで初めて知ったのですが、GCPを扱う上ではユーザの種類がユーザアカウントとサービスアカウントの2種類あるようです。
今回のようにCLIから使う場合は、サービスアカウントで認証する方がTerraform Workspaceでディレクトリごとに違う環境にデプロイする時などに良いんだろうなあと思います。

が、今回はWorkspaceを使ってないのでユーザアカウントで認証します。

作成対象のアカウントが1つしかない場合は`gcloud auth login`コマンドで対話形式で設定していくのが良いと思います。
私はaws-cliのプロファイルのように実行時にプロファイルを切り替えたかったので、以下のコマンドで設定しました。

```bash
gcloud config configurations create <config名> #configの作成
gcloud config configurations activate <config名> #configの切り替え
gcloud config set core/account <Googleアカウントのアドレス> #対象のアカウントをセット
gcloud config set core/disable_usage_reporting False #とりあえずFalse
gcloud auth login # ブラウザが開くのでログインする
```

他にも設定値があるのでドキュメントを見てください。

## コード

Terraformの公式ドキュメントとにらめっこしながら作成したコードがこちらです。
ドキュメントそのままの部分が多いですが、コンソールから作成したリソースとTerraformのリソース名がどう対応するのかわからなかったんですよね。

`local`と`var`でローカル変数を参照しているところは適宜任意の名前に書き換えてください。

### main.tf

```tf
terraform {
  required_providers {
    google-beta = {
      source  = "hashicorp/google-beta"
      version = "5.16.0"
    }
  }
}

# 新規でプロジェクトを作成するときはuser_project_overrideをfalseにする
provider "google-beta" {
  alias                 = "no_user_project_override"
  user_project_override = false
}

```

どうでもいいですがbetaがついていることに驚きです。

### GCPプロジェクトとFirebaseプロジェクトの作成

```tf
resource "google_project" "project" {
  provider = google-beta.no_user_project_override

  name        = local.project_name
  project_id  = local.project_id
  skip_delete = true

  labels = {
    "firebase" = "enabled"
  }
}

resource "google_firebase_project" "firebase_project" {
  provider = google-beta.no_user_project_override
  project  = google_project.project.project_id
}

```

### AdminSDK操作用のサービスアカウントの作成

PythonからAdminSDKを操作するためのサービスアカウントと、アクセス用のキーペアを作成します。

```tf
resource "google_service_account" "fb_admin_sdk" {
  provider     = google-beta.no_user_project_override
  project      = google_project.project.project_id
  account_id   = local.fb_admin_sdk_account_id
  display_name = local.fb_admin_sdk_account_id
}

resource "google_project_iam_binding" "fb_admin_sdk_service_agent" {
  provider = google-beta.no_user_project_override
  project  = google_project.project.project_id
  role     = "roles/firebase.sdkAdminServiceAgent"

  members = [
    "serviceAccount:${google_service_account.fb_admin_sdk.email}",
  ]
}

resource "google_project_iam_binding" "fb_admin_sdk_token_creator" {
  provider = google-beta.no_user_project_override
  project  = google_project.project.project_id
  role     = "roles/iam.serviceAccountTokenCreator"

  members = [
    "serviceAccount:${google_service_account.fb_admin_sdk.email}",
  ]
}

resource "google_service_account_key" "firebase_admin_sdk_key" {
  provider           = google-beta.no_user_project_override
  service_account_id = google_service_account.fb_admin_sdk.name
  private_key_type   = "TYPE_GOOGLE_CREDENTIALS_FILE"
}

locals {
  decoded_private_key = jsondecode(base64decode(google_service_account_key.firebase_admin_sdk_key.private_key))
}


```

`google_service_account_key`で作成したキーの内容を別moduleで作成しているLambdaの環境変数として渡したかったので、`bese64decode`→`jsondecode`でlocalsブロックで読み込んでいます。
参照するときは`local.decoded_private_key.private_key_id`の形式でjsonのキーごとに参照できます。
コンソールから作成すると下の形式のjsonファイルでダウンロードされる内容です。

```json
{
  "type": "service_account",
  "project_id": *****",
  "private_key_id": "*****",
  "private_key": "*****",
  "client_email": "*****",
  "client_id": "*****",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "*****",
  "universe_domain": "googleapis.com"
}
```

この作成した鍵の内容の読み取り方がわからず苦労しました。

`google_project_iam_binding`でサービスアカウントにロールをアタッチしています。
firebaseのプロジェクト作成時に自動でAdminSDK用のサービスアカウントが作成されるのですが、それと同じロールにしています。

### WEBプッシュ用アプリの作成

今回はWEBプッシュ通知がしたいので、WEBアプリを作成します。

```tf
resource "google_firebase_web_app" "fb_web_app" {
  provider     = google-beta.no_user_project_override
  project      = google_project.project.project_id
  display_name = local.fb_web_app_display_name
}

data "google_firebase_web_app_config" "fb_web_app_config" {
  provider   = google-beta.no_user_project_override
  project    = google_project.project.project_id
  web_app_id = google_firebase_web_app.fb_web_app.app_id
}
```

通知受取側のコードで`apiKey`等のfirebaseの設定情報が必要になります。
値を参照するときは`data.google_firebase_web_app_config.fb_web_app_config.api_key`のようにdataリソースから参照します。

### ウェブプッシュ証明書の作成

これだけTerraformで対応してなかった（見つからなかった）ので、GUIから作成します。

https://firebase.google.com/docs/cloud-messaging/js/client?hl=ja#configure_web_credentials_with

作成された公開鍵は通知受け取り側（PWA）の方で使います。

以上でリソースの作成は終了です。

通知送信側、受取側の詳しいコードははじめに記載した公式ドキュメントに載っているので、そちらを参考にしてください。

## 最後に

IaC大好きなので、なんでもコード化したくなります。
TerraformでFCM環境を作りたい方の参考になれば幸いです。