Terraform と Firebase を使ってみる

Firebase で Terraform のサポートを開始しました。特定のリソースがプロビジョニングされ、サービスが有効になっている Firebase プロジェクトの作成を自動化し、標準化したいチームの場合は、Firebase で Terraform を使用することをおすすめします。

Firebase で Terraform を使用するための基本的なワークフローは次のとおりです。

  • Terraform 構成ファイル(.tf ファイル)を作成してカスタマイズする。このファイルに、プロビジョニングしたいインフラストラクチャ(プロビジョニングするリソースと有効にするサービス)を指定します。

  • Terraform とやり取りする gcloud CLI コマンドを使用して、.tf ファイルに指定したインフラストラクチャをプロビジョニングします。

Terraform と Firebase でできること

このガイドで説明する一般的なワークフローの例は、Android アプリを含む新しい Firebase プロジェクトを作成するものですが、Terraform を使用すると次のようなこともできます。

  • Terraform を使用して、既存のインフラストラクチャの削除と変更を行う。

  • Terraform を使用して、次のようなプロダクト固有の構成とタスクを管理する。

    • Firebase Authentication ログイン プロバイダを有効にする。
    • Cloud Storage バケットまたはデータベース インスタンスを作成して、Firebase Security Rules をデプロイする。

これらのタスクはすべて、標準の Terraform 構成ファイルとコマンドで行うことができます。このような作業に役立つように、一般的なユースケースに対応したサンプルの Terraform 構成ファイルが用意されています。



Firebase で Terraform を使用するための一般的なワークフロー

前提条件

このガイドでは、Firebase で Terraform を使用する方法について説明します。そのため、Terraform に関する基本的なスキルがあることを前提としています。このワークフローを始める前に、次の前提条件を満たしていることを確認してください。

  • Terraform をインストールし、公式のチュートリアルで Terraform について学習している。

  • Google Cloud CLIをインストールgcloud CLI)します。ユーザー アカウントまたはサービス アカウントを使用してログインします。

    • ユーザー アカウントを使用する場合は、Firebase の利用規約(Firebase ToS)に同意する必要があります。Firebase コンソールに Firebase プロジェクトが表示されている場合は、Firebase ToS にすでに同意しています。
    • Terraform が特定のアクション(プロジェクトの作成など)を実行するには、次の条件を満たしている必要があります。
      • ユーザー アカウントまたはサービス アカウントには、これらのアクションに対応する IAM アクセス権が必要です。
      • ユーザー アカウントまたはサービス アカウントが Google Cloud 組織に属している場合、組織のポリシーで、これらのアクションの実行をアカウントに許可する必要があります。


ステップ 1: Terraform 構成ファイルを作成してカスタマイズする

Terraform 構成ファイルには次の 2 つのメイン セクションが必要です(このガイドの後半で詳しく説明します)。

provider を設定する

provider は、使用する Firebase プロダクトまたはサービスに関係なく設定する必要があります。

  1. ローカル ディレクトリに Terraform 構成ファイル(main.tf ファイルなど)を作成します。

    このガイドでは、この構成ファイルを使用して、provider の設定と、Terraform で作成するすべてのインフラストラクチャを指定します。ただし、別の方法でプロバイダの設定を追加することもできます。

    残りの Terraform 構成に provider の設定を追加するには、次の方法があります。

    • オプション 1: 1 つの Terraform .tf 構成ファイル(このガイドを参照)の先頭に追加します。

      • Terraform を初めて使用する場合や Firebase で Terraform を試す場合は、このオプションを使用します。
    • オプション 2: 作成するインフラストラクチャを指定する .tf ファイル(main.tf ファイルなど)とは別の .tf ファイル(provider.tf ファイルなど)に追加します。

      • 大規模なチームの一員として設定を標準化する必要がある場合は、このオプションを使用します。
      • Terraform コマンドを実行するときに、provider.tfmain.tf の両方のファイルが同じディレクトリに配置されている必要があります。

  2. main.tf ファイルの先頭に、次の provider 設定を記述します。

    これは Terraform で Firebase を使用する機能のベータ版であるため、google-beta プロバイダを使用する必要があります。本番環境で使用する場合は注意してください。

    # Terraform configuration to set up providers by version.
    terraform {
      required_providers {
        google-beta = {
          source  = "hashicorp/google-beta"
          version = "~> 5.0"
        }
      }
    }
    
    # Configures the provider to use the resource block's specified project for quota checks.
    provider "google-beta" {
      user_project_override = true
    }
    
    # Configures the provider to not use the resource block's specified project for quota checks.
    # This provider should only be used during project creation and initializing services.
    provider "google-beta" {
      alias = "no_user_project_override"
      user_project_override = false
    }

    Firebase で Terraform を使用する場合は、さまざまな種類のプロジェクト関連の属性(このガイドの「割り当てチェック用プロジェクト」も含む)をご覧ください。

  3. 次のセクションに進んで構成ファイルを作成し、作成するインフラストラクチャを指定します。

resource ブロックを使用して、作成するインフラストラクチャを指定する

Terraform で作成するすべてのインフラストラクチャ(プロビジョニングするすべてのリソースと有効にするすべてのサービス)を Terraform 構成ファイル(このガイドでは main.tf ファイル)に指定する必要があります。このガイドの Terraform をサポートする Firebase リソースを確認してください。

  1. main.tf ファイルを開きます。

  2. provider の設定で、次の resource ブロックの構成を追加します。

    この基本的な例では、新しい Firebase プロジェクトを作成し、そのプロジェクトに Firebase Android アプリを作成します。

    # Terraform configuration to set up providers by version.
    ...
    
    # Configures the provider to use the resource block's specified project for quota checks.
    ...
    
    # Configures the provider to not use the resource block's specified project for quota checks.
    ...
    
    # Creates a new Google Cloud project.
    resource "google_project" "default" {
      provider   = google-beta.no_user_project_override
    
      name       = "Project Display Name"
      project_id = "project-id-for-new-project"
      # Required for any service that requires the Blaze pricing plan
      # (like Firebase Authentication with GCIP)
      billing_account = "000000-000000-000000"
    
      # Required for the project to display in any list of Firebase projects.
      labels = {
        "firebase" = "enabled"
      }
    }
    
    # Enables required APIs.
    resource "google_project_service" "default" {
      provider = google-beta.no_user_project_override
      project  = google_project.default.project_id
      for_each = toset([
        "cloudbilling.googleapis.com",
        "cloudresourcemanager.googleapis.com",
        "firebase.googleapis.com",
        # Enabling the ServiceUsage API allows the new project to be quota checked from now on.
        "serviceusage.googleapis.com",
      ])
      service = each.key
    
      # Don't disable the service if the resource block is removed by accident.
      disable_on_destroy = false
    }
    
    # Enables Firebase services for the new project created above.
    resource "google_firebase_project" "default" {
      provider = google-beta
      project  = google_project.default.project_id
    
      # Waits for the required APIs to be enabled.
      depends_on = [
        google_project_service.default
      ]
    }
    
    # Creates a Firebase Android App in the new project created above.
    resource "google_firebase_android_app" "default" {
      provider = google-beta
    
      project      = google_project.default.project_id
      display_name = "My Awesome Android app"
      package_name = "awesome.package.name"
    
      # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
      depends_on = [
        google_firebase_project.default,
      ]
    }

プロジェクトのインフラストラクチャとリソースとしてのアプリに慣れていない場合は、次のドキュメントをご覧ください。

# Terraform configuration to set up providers by version.
...

# Configures the provider to use the resource block's specified project for quota checks.
...

# Configures the provider to not use the resource block's specified project for quota checks.
...

# Creates a new Google Cloud project.
resource "google_project" "default" {
  # Use the provider that enables the setup of quota checks for a new project
  provider   = google-beta.no_user_project_override

  name            = "Project Display Name"        // learn more about the project name
  project_id      = "project-id-for-new-project"  // learn more about the project ID
  # Required for any service that requires the Blaze pricing plan
  # (like Firebase Authentication with GCIP)
  billing_account = "000000-000000-000000"

  # Required for the project to display in any list of Firebase projects.
  labels = {
    "firebase" = "enabled"  // learn more about the Firebase-enabled label
  }
}

# Enables required APIs.
resource "google_project_service" "default" {
  # Use the provider without quota checks for enabling APIS
  provider = google-beta.no_user_project_override
  project  = google_project.default.project_id
  for_each = toset([
    "cloudbilling.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "firebase.googleapis.com",
    # Enabling the ServiceUsage API allows the new project to be quota checked from now on.
    "serviceusage.googleapis.com",
  ])
  service = each.key

  # Don't disable the service if the resource block is removed by accident.
  disable_on_destroy = false
}

# Enables Firebase services for the new project created above.
# This action essentially "creates a Firebase project" and allows the project to use
# Firebase services (like Firebase Authentication) and
# Firebase tooling (like the Firebase console).
# Learn more about the relationship between Firebase projects and Google Cloud.
resource "google_firebase_project" "default" {
  # Use the provider that performs quota checks from now on
  provider = google-beta

  project  = google_project.default.project_id

  # Waits for the required APIs to be enabled.
  depends_on = [
    google_project_service.default
  ]
}

# Creates a Firebase Android App in the new project created above.
# Learn more about the relationship between Firebase Apps and Firebase projects.
resource "google_firebase_android_app" "default" {
  provider = google-beta

  project      = google_project.default.project_id
  display_name = "My Awesome Android app"  # learn more about an app's display name
  package_name = "awesome.package.name"    # learn more about an app's package name

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.default,
  ]
}


ステップ 2: Terraform コマンドを実行して、指定したインフラストラクチャを作成する

main.tf ファイルで指定されたリソースをプロビジョニングしてサービスを有効にするには、main.tf ファイルと同じディレクトリから次のコマンドを実行します。これらのコマンドの詳細については、Terraform のドキュメントをご覧ください。

  1. このディレクトリで Terraform コマンドを初めて実行する場合は、構成ディレクトリを初期化し、Google Terraform プロバイダをインストールする必要があります。この作業を行うには、次のコマンドを実行します。

    terraform init
  2. 次のコマンドを実行して、main.tf ファイルに指定されているインフラストラクチャを作成します。

    terraform apply
  3. すべてが想定どおりプロビジョニングまたは有効化されていることを確認します。

    • オプション 1: 次のコマンドを実行して、ターミナルに出力された構成を確認します。

      terraform show
    • オプション 2: Firebase コンソールで Firebase プロジェクトを表示します。



Terraform をサポートする Firebase リソース

次の Firebase と Google のリソースは Terraform に対応しています。Google では随時リソースを追加しています。Terraform で管理したいリソースが表示されない場合は、しばらくしてからもう一度ご確認いただくか、GitHub リポジトリで問題を報告してください。


Firebase プロジェクトとアプリの管理

  • google_firebase_project - 既存の Google Cloud プロジェクトで Firebase サービスを有効にする

  • Firebase アプリ


Firebase Authentication

  • google_identity_platform_config - Google Cloud Identity Platform(GCIP)(Firebase Authentication のバックエンド)を有効にして、プロジェクト レベルの認証設定を行う

    • Terraform を使用して Firebase Authentication を構成する場合は GCIP を有効にする必要があります。Firebase Authentication の設定方法については、サンプル .tf ファイルを確認してください。

    • Terraform で GCIP や Firebase Authentication を有効にするプロジェクトには、Blaze お支払いプランが必要です(つまり、プロジェクトに関連付けられた Cloud Billing アカウントが必要です)。google_project リソースに billing_account 属性を設定すると、この作業をプログラムで行うことができます。

    • このリソースを使用すると、匿名、メールアドレスとパスワード、電話認証などのローカル ログイン方法、ブロッキング関数、承認済みドメインなど、より多くの構成も可能になります。

  • google_identity_platform_default_supported_idp_config - Google、Facebook、Apple などの一般的なフェデレーション ID プロバイダを構成する

  • identity_platform_oauth_idp_config - 任意の OAuth ID プロバイダ(IdP)ソースを構成する

  • google_identity_platform_inbound_saml_config - SAML 統合を構成する

現時点でサポートされていない機能:

  • Terraform を使用した多要素認証(MFA)の構成

Firebase Realtime Database

現時点でサポートされていない機能:

  • Terraform を介して Firebase Realtime Database Security Rules をデプロイする(プログラムで行う方法など、別の手段でこれらの Rules をデプロイする方法をご覧ください)

Cloud Firestore

  • google_firestore_database Cloud Firestore インスタンスを作成します。

  • google_firestore_index - Cloud Firestore の効率的なクエリを有効にする

  • google_firestore_document - コレクション内の特定のドキュメントを含む Cloud Firestore インスタンスをシードする

    重要: このシード ドキュメントでは、実際のエンドユーザーのデータや本番環境のデータを使用しないでください。


Cloud Storage for Firebase

  • google_firebase_storage_bucket - Firebase SDK、認証、Firebase Security Rules で既存の Cloud Storage バケットにアクセスできるようにする

  • google_storage_bucket_object - Cloud Storage バケットにオブジェクトを追加する

    重要: このファイルでは、エンドユーザーや本番環境の実際のデータを使用しないでください。


Firebase Security RulesCloud FirestoreCloud Storage

Firebase Realtime Database では、Firebase Security Rules に別のプロビジョニング システムが使用されています。

  • google_firebaserules_ruleset - Cloud Firestore インスタンスまたは Cloud Storage バケットに適用される Firebase Security Rules を定義する

  • google_firebaserules_release - 特定のルールセットを Cloud Firestore インスタンスまたは Cloud Storage バケットにデプロイする


Firebase App Check


Firebase Extensions



一般的なユースケースの Terraform 構成ファイルの例

この構成ファイルでは、新しい Google Cloud プロジェクトを作成し、プロジェクトを Cloud Billing アカウントに関連付けます(GCIP を使用した Firebase Authentication には Blaze お支払いプランが必要です)。プロジェクトで Firebase サービスを有効にし、GCIP を使用して Firebase Authentication を設定し、プロジェクトに 3 つの異なるアプリの種類を登録します。

Terraform で Firebase Authentication を設定するには、GCIP を有効にする必要があります。

# Creates a new Google Cloud project.
resource "google_project" "auth" {
  provider  = google-beta.no_user_project_override
  folder_id = "folder-id-for-new-project"
  name            = "Project Display Name"
  project_id      = "project-id-for-new-project"

  # Associates the project with a Cloud Billing account
  # (required for Firebase Authentication with GCIP).
  billing_account = "000000-000000-000000"

  # Required for the project to display in a list of Firebase projects.
  labels = {
    "firebase" = "enabled"
  }
}

# Enables required APIs.
resource "google_project_service" "auth" {
  provider = google-beta.no_user_project_override
  project  = google_project.auth.project_id
  for_each = toset([
    "cloudbilling.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "serviceusage.googleapis.com",
    "identitytoolkit.googleapis.com",
  ])
  service = each.key

  # Don't disable the service if the resource block is removed by accident.
  disable_on_destroy = false
}

# Enables Firebase services for the new project created above.
resource "google_firebase_project" "auth" {
  provider = google-beta
  project  = google_project.auth.project_id

  depends_on = [
    google_project_service.auth,
  ]
}

# Creates an Identity Platform config.
# Also enables Firebase Authentication with Identity Platform in the project if not.
resource "google_identity_platform_config" "auth" {
  provider = google-beta
  project  = google_project.auth.project_id

  # Auto-deletes anonymous users
  autodelete_anonymous_users = true

  # Configures local sign-in methods, like anonymous, email/password, and phone authentication.
  sign_in {
    allow_duplicate_emails = true

    anonymous {
      enabled = true
    }

    email {
      enabled = true
      password_required = false
    }

    phone_number {
      enabled = true
      test_phone_numbers = {
        "+11231231234" = "000000"
      }
    }
  }

  # Sets an SMS region policy.
  sms_region_config {
    allowlist_only {
      allowed_regions = [
        "US",
        "CA",
      ]
    }
  }

  # Configures blocking functions.
  blocking_functions {
    triggers {
      event_type = "beforeSignIn"
      function_uri = "https://us-east1-${google_project.auth.project_id}.cloudfunctions.net/before-sign-in"
    }
    forward_inbound_credentials {
      refresh_token = true
      access_token = true
      id_token = true
    }
  }

  # Configures a temporary quota for new signups for anonymous, email/password, and phone number.
  quota {
    sign_up_quota_config {
      quota = 1000
      start_time = ""
      quota_duration = "7200s"
    }
  }

  # Configures authorized domains.
  authorized_domains = [
    "localhost",
    "${google_project.auth.project_id}.firebaseapp.com",
    "${google_project.auth.project_id}.web.app",
  ]

  # Wait for identitytoolkit.googleapis.com to be enabled before initializing Authentication.
  depends_on = [
    google_project_service.auth,
  ]
}

# Creates a Firebase Android App in the new project created above.
resource "google_firebase_android_app" "auth" {
  provider     = google-beta
  project      = google_project.auth.project_id
  display_name = "My Android app"
  package_name = "android.package.name"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.auth,
  ]
}

# Creates a Firebase Apple-platforms App in the new project created above.
resource "google_firebase_apple_app" "auth" {
  provider     = google-beta
  project      = google_project.auth.project_id
  display_name = "My Apple app"
  bundle_id    = "apple.app.12345"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.auth,
  ]
}

# Creates a Firebase Web App in the new project created above.
resource "google_firebase_web_app" "auth" {
  provider     = google-beta
  project      = google_project.auth.project_id
  display_name = "My Web app"

  # The other App types (Android and Apple) use "DELETE" by default.
  # Web apps don't use "DELETE" by default due to backward-compatibility.
  deletion_policy = "DELETE"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.auth,
  ]
}

この構成ファイルでは、新しい Google Cloud プロジェクトを作成し、プロジェクトで Firebase サービスを有効にします。プロジェクトのデフォルトの Realtime Database インスタンスをプロビジョニングして、プロジェクトに 3 つの異なるアプリの種類を登録します。

# Creates a new Google Cloud project.
resource "google_project" "rtdb" {
  provider   = google-beta.no_user_project_override
  folder_id  = "folder-id-for-new-project"
  name       = "Project Display Name"
  project_id = "project-id-for-new-project"

  # Required for the project to display in a list of Firebase projects.
  labels = {
    "firebase" = "enabled"
  }
}

# Enables required APIs.
resource "google_project_service" "rtdb" {
  provider = google-beta.no_user_project_override
  project  = google_project.rtdb.project_id
  for_each = toset([
    "serviceusage.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "firebasedatabase.googleapis.com",
  ])
  service = each.key

  # Don't disable the service if the resource block is removed by accident.
  disable_on_destroy = false
}

# Enables Firebase services for the new project created above.
resource "google_firebase_project" "rtdb" {
  provider = google-beta
  project  = google_project.rtdb.project_id
}

# Provisions the default Realtime Database default instance.
resource "google_firebase_database_instance" "database" {
  provider    = google-beta
  project     = google_project.rtdb.project_id
  # See available locations: https://firebase.google.com/docs/database/locations
  region      = "name-of-region"
  # This value will become the first segment of the database's URL.
  instance_id = "${google_project.rtdb.project_id}-default-rtdb"
  type        = "DEFAULT_DATABASE"

  # Wait for Firebase to be enabled in the Google Cloud project before initializing Realtime Database.
  depends_on = [
    google_firebase_project.rtdb,
  ]
}

# Creates a Firebase Android App in the new project created above.
resource "google_firebase_android_app" "rtdb" {
  provider     = google-beta
  project      = google_project.rtdb.project_id
  display_name = "My Android app"
  package_name = "android.package.name"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.rtdb,
  ]
}

# Creates a Firebase Apple-platforms App in the new project created above.
resource "google_firebase_apple_app" "rtdb" {
  provider     = google-beta
  project      = google_project.rtdb.project_id
  display_name = "My Apple app"
  bundle_id    = "apple.app.12345"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.rtdb,
  ]
}

# Creates a Firebase Web App in the new project created above.
resource "google_firebase_web_app" "rtdb" {
  provider     = google-beta
  project      = google_project.rtdb.project_id
  display_name = "My Web app"

  # The other App types (Android and Apple) use "DELETE" by default.
  # Web apps don't use "DELETE" by default due to backward-compatibility.
  deletion_policy = "DELETE"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.rtdb,
  ]
}

この構成ファイルでは、新しい Google Cloud プロジェクトを作成し、プロジェクトを Cloud Billing アカウントに関連付けます(複数の Realtime Database インスタンスには Blaze お支払いプランが必要です)。プロジェクトで Firebase サービスを有効にして、複数の Realtime Database インスタンス(プロジェクトのデフォルトの Realtime Database インスタンスを含む)をプロビジョニングし、プロジェクトに 3 つの異なるアプリの種類を登録します。

# Creates a new Google Cloud project.
resource "google_project" "rtdb-multi" {
  provider   = google-beta.no_user_project_override
  folder_id  = "folder-id-for-new-project"
  name       = "Project Display Name"
  project_id = "project-id-for-new-project"

  # Associate the project with a Cloud Billing account
  # (required for multiple Realtime Database instances).
  billing_account = "000000-000000-000000"

  # Required for the project to display in a list of Firebase projects.
  labels = {
    "firebase" = "enabled"
  }
}

# Enables required APIs.
resource "google_project_service" "rtdb-multi" {
  provider = google-beta.no_user_project_override
  project  = google_project.rtdb-multi.project_id
  for_each = toset([
    "cloudbilling.googleapis.com",
    "serviceusage.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "firebasedatabase.googleapis.com",
  ])
  service = each.key

  # Don't disable the service if the resource block is removed by accident.
  disable_on_destroy = false
}

# Enables Firebase services for the new project created above.
resource "google_firebase_project" "rtdb-multi" {
  provider = google-beta
  project  = google_project.rtdb-multi.project_id
}

# Provisions the default Realtime Database default instance.
resource "google_firebase_database_instance" "database-default" {
  provider    = google-beta
  project     = google_project.rtdb-multi.project_id
  # See available locations: https://firebase.google.com/docs/database/locations
  region      = "name-of-region"
  # This value will become the first segment of the database's URL.
  instance_id = "${google_project.rtdb-multi.project_id}-default-rtdb"
  type        = "DEFAULT_DATABASE"

  # Wait for Firebase to be enabled in the Google Cloud project before initializing Realtime Database.
  depends_on = [
    google_firebase_project.rtdb-multi,
  ]
}

# Provisions an additional Realtime Database instance.
resource "google_firebase_database_instance" "database-additional" {
  provider    = google-beta
  project     = google_project.rtdb-multi.project_id
  # See available locations: https://firebase.google.com/docs/projects/locations#rtdb-locations
  # This location doesn't need to be the same as the default database instance.
  region      = "name-of-region"
  # This value will become the first segment of the database's URL.
  instance_id = "name-of-additional-database-instance"
  type        = "USER_DATABASE"

  # Wait for Firebase to be enabled in the Google Cloud project before initializing Realtime Database.
  depends_on = [
    google_firebase_project.rtdb-multi,
  ]
}

# Creates a Firebase Android App in the new project created above.
resource "google_firebase_android_app" "rtdb-multi" {
  provider     = google-beta
  project      = google_project.rtdb-multi.project_id
  display_name = "My Android app"
  package_name = "android.package.name"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.rtdb-multi,
  ]
}

# Creates a Firebase Apple-platforms App in the new project created above.
resource "google_firebase_apple_app" "rtdb-multi" {
  provider     = google-beta
  project      = google_project.rtdb-multi.project_id
  display_name = "My Apple app"
  bundle_id    = "apple.app.12345"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.rtdb-multi,
  ]
}

# Creates a Firebase Web App in the new project created above.
resource "google_firebase_web_app" "rtdb-multi" {
  provider     = google-beta
  project      = google_project.rtdb-multi.project_id
  display_name = "My Web app"

  # The other App types (Android and Apple) use "DELETE" by default.
  # Web apps don't use "DELETE" by default due to backward-compatibility.
  deletion_policy = "DELETE"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.rtdb-multi,
  ]
}

この構成ファイルでは、新しい Google Cloud プロジェクトを作成し、プロジェクトで Firebase サービスを有効にします。プロジェクトのデフォルトの Cloud Firestore インスタンスをプロビジョニングして、プロジェクトに 3 つの異なるアプリの種類を登録します。

また、デフォルトの Cloud Firestore インスタンスで Firebase Security Rules をプロビジョニングし、Cloud Firestore インデックスを作成して、シードデータを含む Cloud Firestore ドキュメントを追加します。

# Creates a new Google Cloud project.
resource "google_project" "firestore" {
  provider   = google-beta.no_user_project_override
  folder_id  = "folder-id-for-new-project"
  name       = "Project Display Name"
  project_id = "project-id-for-new-project"

  # Required for the project to display in a list of Firebase projects.
  labels = {
    "firebase" = "enabled"
  }
}

# Enables required APIs.
resource "google_project_service" "firestore" {
  provider = google-beta.no_user_project_override
  project  = google_project.firestore.project_id
  for_each = toset([
    "cloudresourcemanager.googleapis.com",
    "serviceusage.googleapis.com",
    "firestore.googleapis.com",
    "firebaserules.googleapis.com",
  ])
  service = each.key

  # Don't disable the service if the resource block is removed by accident.
  disable_on_destroy = false
}

# Enables Firebase services for the new project created above.
resource "google_firebase_project" "firestore" {
  provider = google-beta
  project  = google_project.firestore.project_id
}

# Provisions the Firestore database instance.
resource "google_firestore_database" "firestore" {
  provider                    = google-beta
  project                     = google_project.firestore.project_id
  name                        = "(default)"
  # See available locations: https://firebase.google.com/docs/firestore/locations
  location_id                 = "name-of-region"
  # "FIRESTORE_NATIVE" is required to use Firestore with Firebase SDKs, authentication, and Firebase Security Rules.
  type                        = "FIRESTORE_NATIVE"
  concurrency_mode            = "OPTIMISTIC"

  # Wait for Firebase to be enabled in the Google Cloud project before initializing Firestore.
  depends_on = [
    google_firebase_project.firestore,
  ]
}

# Creates a ruleset of Firestore Security Rules from a local file.
resource "google_firebaserules_ruleset" "firestore" {
  provider = google-beta
  project  = google_project.firestore.project_id
  source {
    files {
      name = "firestore.rules"
      # Write security rules in a local file named "firestore.rules".
      # Learn more: https://firebase.google.com/docs/firestore/security/get-started
      content = file("firestore.rules")
    }
  }

  # Wait for Firestore to be provisioned before creating this ruleset.
  depends_on = [
    google_firestore_database.firestore,
  ]
}

# Releases the ruleset for the Firestore instance.
resource "google_firebaserules_release" "firestore" {
  provider     = google-beta
  name         = "cloud.firestore"  # must be cloud.firestore
  ruleset_name = google_firebaserules_ruleset.firestore.name
  project      = google_project.firestore.project_id

  # Wait for Firestore to be provisioned before releasing the ruleset.
  depends_on = [
    google_firestore_database.firestore,
  ]
}

# Adds a new Firestore index.
resource "google_firestore_index" "indexes" {
  provider = google-beta
  project  = google_project.firestore.project_id

  collection  = "quiz"
  query_scope = "COLLECTION"

  fields {
    field_path = "question"
    order      = "ASCENDING"
  }

  fields {
    field_path = "answer"
    order      = "ASCENDING"
  }

  # Wait for Firestore to be provisioned before adding this index.
  depends_on = [
    google_firestore_database.firestore,
  ]
}

# Adds a new Firestore document with seed data.
# Don't use real end-user or production data in this seed document.
resource "google_firestore_document" "doc" {
  provider    = google-beta
  project     = google_project.firestore.project_id
  collection  = "quiz"
  document_id = "question-1"
  fields      = "{\"question\":{\"stringValue\":\"Favorite Database\"},\"answer\":{\"stringValue\":\"Firestore\"}}"

  # Wait for Firestore to be provisioned before adding this document.
  depends_on = [
    google_firestore_database.firestore,
  ]
}

# Creates a Firebase Android App in the new project created above.
resource "google_firebase_android_app" "firestore" {
  provider     = google-beta
  project      = google_project.firestore.project_id
  display_name = "My Android app"
  package_name = "android.package.name"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.firestore,
  ]
}

# Creates a Firebase Apple-platforms App in the new project created above.
resource "google_firebase_apple_app" "firestore" {
  provider     = google-beta
  project      = google_project.firestore.project_id
  display_name = "My Apple app"
  bundle_id    = "apple.app.12345"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.firestore,
  ]
}

# Creates a Firebase Web App in the new project created above.
resource "google_firebase_web_app" "firestore" {
  provider     = google-beta
  project      = google_project.firestore.project_id
  display_name = "My Web app"

  # The other App types (Android and Apple) use "DELETE" by default.
  # Web apps don't use "DELETE" by default due to backward-compatibility.
  deletion_policy = "DELETE"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.firestore,
  ]
}

これは Cloud Firestore Security Rules のルールセットで、firestore.rules という名前のローカル ファイルに存在する必要があります。

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    match /some_collection/{document} {
      allow read, create, update: if request.auth != null;
    }
  }
}

この構成ファイルでは、新しい Google Cloud プロジェクトを作成し、プロジェクトを Cloud Billing アカウントに関連付けます(追加のバケットには Blaze お支払いプランが必要です)。プロジェクトで Firebase サービスを有効にし、デフォルト以外の追加の Cloud Storage バケットをプロビジョニングして、プロジェクトに 3 つの異なるアプリの種類を登録します。

また、Cloud Storage バケットごとに Firebase Security Rules をプロビジョニングし、いずれかの Cloud Storage バケットにファイルをアップロードします。

# Creates a new Google Cloud project.
resource "google_project" "storage-multi" {
  provider  = google-beta.no_user_project_override
  folder_id = "folder-id-for-new-project"
  name            = "Project Display Name"
  project_id      = "project-id-for-new-project"

  # Associates the project with a Cloud Billing account
  # (required for multiple Cloud Storage buckets).
  billing_account = "000000-000000-000000"

  # Required for the project to display in a list of Firebase projects.
  labels = {
    "firebase" = "enabled"
  }
}

# Enables required APIs.
resource "google_project_service" "storage-multi" {
  provider = google-beta.no_user_project_override
  project  = google_project.storage-multi.project_id
  for_each = toset([
    "cloudbilling.googleapis.com",
    "serviceusage.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "firebaserules.googleapis.com",
    "firebasestorage.googleapis.com",
    "storage.googleapis.com",
  ])
  service = each.key

  # Don't disable the service if the resource block is removed by accident.
  disable_on_destroy = false
}

# Enables Firebase services for the new project created above.
resource "google_firebase_project" "storage-multi" {
  provider = google-beta
  project  = google_project.storage-multi.project_id
}

# Provisions a Cloud Storage bucket.
resource "google_storage_bucket" "bucket-1" {
  provider = google-beta
  project  = google_project.storage-multi.project_id
  name     = "name-of-storage-bucket"
  # See available locations: https://cloud.google.com/storage/docs/locations#available-locations
  location = "name-of-region-for-bucket"
}

# Provisions an additional Cloud Storage bucket.
resource "google_storage_bucket" "bucket-2" {
  provider = google-beta
  project  = google_project.storage-multi.project_id
  name     = "name-of-additional-storage-bucket"
  # See available locations: https://cloud.google.com/storage/docs/locations#available-locations
  # This location does not need to be the same as the existing Storage bucket.
  location = "name-of-region-for-additional-bucket"
}

# Makes the first Storage bucket accessible for Firebase SDKs, authentication, and Firebase Security Rules.
resource "google_firebase_storage_bucket" "bucket-1" {
  provider  = google-beta
  project   = google_project.storage-multi.project_id
  bucket_id = google_storage_bucket.bucket-1.name
}

# Makes the additional Storage bucket accessible for Firebase SDKs, authentication, and Firebase Security Rules.
resource "google_firebase_storage_bucket" "bucket-2" {
  provider  = google-beta
  project   = google_project.storage-multi.project_id
  bucket_id = google_storage_bucket.bucket-2.name
}

# Creates a ruleset of Firebase Security Rules from a local file.
resource "google_firebaserules_ruleset" "storage-multi" {
  provider = google-beta
  project  = google_project.storage-multi.project_id
  source {
    files {
      # Write security rules in a local file named "storage.rules"
      # Learn more: https://firebase.google.com/docs/storage/security/get-started
      name    = "storage.rules"
      content = file("storage.rules")
    }
  }

  # Wait for the Storage buckets to be provisioned before creating this ruleset.
  depends_on = [
    google_firebase_project.storage-multi,
  ]
}

# Releases the ruleset to the first Storage bucket.
resource "google_firebaserules_release" "bucket-1" {
  provider     = google-beta
  name         = "firebase.storage/${google_storage_bucket.bucket-1.name}"
  ruleset_name = "projects/${google_project.storage-multi.project_id}/rulesets/${google_firebaserules_ruleset.storage-multi.name}"
  project      = google_project.storage-multi.project_id
}

# Releases the ruleset to the additional Storage bucket.
resource "google_firebaserules_release" "bucket-2" {
  provider     = google-beta
  name         = "firebase.storage/${google_storage_bucket.bucket-2.name}"
  ruleset_name = "projects/${google_project.storage-multi.project_id}/rulesets/${google_firebaserules_ruleset.storage-multi.name}"
  project      = google_project.storage-multi.project_id
}

# Uploads a new file to the first Storage bucket.
# Do not use real end-user or production data in this file.
resource "google_storage_bucket_object" "cat-picture-multi" {
  provider = google-beta
  name     = "cat.png"
  source   = "path/to/cat.png"
  bucket   = google_storage_bucket.bucket-1.name
}

# Creates a Firebase Android App in the new project created above.
resource "google_firebase_android_app" "storage-multi" {
  provider     = google-beta
  project      = google_project.storage-multi.project_id
  display_name = "My Android app"
  package_name = "android.package.name"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.storage-multi,
  ]
}

# Creates a Firebase Apple-platforms App in the new project created above.
resource "google_firebase_apple_app" "storage-multi" {
  provider     = google-beta
  project      = google_project.storage-multi.project_id
  display_name = "My Apple app"
  bundle_id    = "apple.app.12345"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.storage-multi,
  ]
}

# Creates a Firebase Web App in the new project created above.
resource "google_firebase_web_app" "storage-multi" {
  provider     = google-beta
  project      = google_project.storage-multi.project_id
  display_name = "My Web app"

  # Wait for Firebase to be enabled in the Google Cloud project before creating this App.
  depends_on = [
    google_firebase_project.storage-multi,
  ]
}

これは Cloud Storage Security Rules のルールセットで、storage.rules という名前のローカル ファイルに存在する必要があります。

rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /some_folder/{fileName} {
      allow read, write: if request.auth != null;
    }
  }
}

この構成ファイルでは、新しい Google Cloud プロジェクトを作成し、プロジェクトで Firebase サービスを有効にし、Cloud FirestoreFirebase App Check の適用を設定して有効にし、Android アプリからのみアクセスできるようにします。

# Creates a new Google Cloud project.
resource "google_project" "appcheck" {
  provider   = google-beta.no_user_project_override
  folder_id  = "folder-id-for-new-project"
  name       = "Project Display Name"
  project_id = "project-id-for-new-project"

  # Required for the project to display in a list of Firebase projects.
  labels = {
    "firebase" = "enabled"
  }
}

# Enables required APIs.
resource "google_project_service" "services" {
  provider = google-beta.no_user_project_override
  project  = google_project.appcheck.project_id
  for_each = toset([
    "cloudresourcemanager.googleapis.com",
    "firebase.googleapis.com",
    "firebaseappcheck.googleapis.com",
    "firestore.googleapis.com",
    "serviceusage.googleapis.com",
  ])
  service = each.key

  # Don't disable the service if the resource block is removed by accident.
  disable_on_destroy = false
}

# Enables Firebase services for the new project created earlier.
resource "google_firebase_project" "appcheck" {
  provider = google-beta
  project  = google_project.appcheck.project_id

  depends_on = [google_project_service.services]
}

# Provisions the Firestore database instance.
resource "google_firestore_database" "database" {
  provider = google-beta
  project  = google_firebase_project.appcheck.project
  name     = "(default)"
  # See available locations: https://firebase.google.com/docs/projects/locations#default-cloud-location
  location_id = "name-of-region"
  # "FIRESTORE_NATIVE" is required to use Firestore with Firebase SDKs, authentication, and Firebase Security Rules.
  type             = "FIRESTORE_NATIVE"
  concurrency_mode = "OPTIMISTIC"

  # Wait for Firebase to be enabled in the Google Cloud project before initializing Firestore.
  depends_on = [
    google_firebase_project.appcheck,
  ]
}

# Creates a Firebase Android App in the new project created earlier.
resource "google_firebase_android_app" "appcheck" {
  provider     = google-beta
  project      = google_firebase_project.appcheck.project
  display_name = "Play Integrity app"
  package_name = "package.name.playintegrity"
  sha256_hashes = [
    # TODO: insert your Android app's SHA256 certificate
  ]
}

# It takes a while for App Check to recognize the new app
# If your app already exists, you don't have to wait 30 seconds.
resource "time_sleep" "wait_30s" {
  depends_on      = [google_firebase_android_app.appcheck]
  create_duration = "30s"
}

# Register the Android app with the Play Integrity provider
resource "google_firebase_app_check_play_integrity_config" "appcheck" {
  provider = google-beta
  project  = google_firebase_project.appcheck.project
  app_id   = google_firebase_android_app.appcheck.app_id

  depends_on = [time_sleep.wait_30s, google_firestore_database.database]

  lifecycle {
    precondition {
      condition     = length(google_firebase_android_app.appcheck.sha256_hashes) > 0
      error_message = "Provide a SHA-256 certificate on the Android App to use App Check"
    }
  }
}

# Enable enforcement of App Check for Firestore
resource "google_firebase_app_check_service_config" "firestore" {
  provider = google-beta

  project    = google_firebase_project.appcheck.project
  service_id = "firestore.googleapis.com"

  depends_on = [google_project_service.services]
}

この構成ファイルでは、新しい Google Cloud プロジェクトを作成し、プロジェクトで Firebase サービスを有効にし、プロジェクトに Firebase Extension の新しいインスタンスをインストールします。インスタンスがすでに存在する場合、そのパラメータは構成で指定された値に基づいて更新されます。

# Creates a new Google Cloud project.
resource "google_project" "extensions" {
  provider   = google-beta.no_user_project_override
  folder_id  = "folder-id-for-new-project"
  name       = "Project Display Name"
  project_id = "project-id-for-new-project"

  # Associates the project with a Cloud Billing account
  # (required to use Firebase Extensions).
  billing_account = "000000-000000-000000"

  # Required for the project to display in a list of Firebase projects.
  labels = {
    "firebase" = "enabled"
  }
}

# Enables required APIs.
resource "google_project_service" "extensions" {
  provider = google-beta.no_user_project_override
  project  = google_project.extensions.project_id
  for_each = toset([
    "cloudbilling.googleapis.com",
    "cloudresourcemanager.googleapis.com",
    "serviceusage.googleapis.com",
    "firebase.googleapis.com",
    "firebaseextensions.googleapis.com",
  ])
  service = each.key

  # Don't disable the service if the resource block is removed by accident.
  disable_on_destroy = false
}

# Enables Firebase services for the new project created above.
resource "google_firebase_project" "extensions" {
  provider = google-beta
  project  = google_project.extensions.project_id

  depends_on = [
    google_project_service.extensions,
  ]
}

# Installs an instance of the "Translate Text in Firestore" extension.
# Or updates the extension if the specified instance already exists.
resource "google_firebase_extensions_instance" "translation" {
  provider = google-beta
  project = google_project.extensions.project_id

  instance_id = "translate-text-in-firestore"
  config {
    extension_ref = "firebase/firestore-translate-text"

    params = {
      COLLECTION_PATH      = "posts/comments/translations"
      DO_BACKFILL          = true
      LANGUAGES            = "ar,en,es,de,fr"
      INPUT_FIELD_NAME     = "input"
      LANGUAGES_FIELD_NAME = "languages"
      OUTPUT_FIELD_NAME    = "translated"
    }

    system_params = {
      "firebaseextensions.v1beta.function/location"                   = "us-central1"
      "firebaseextensions.v1beta.function/memory"                     = "256"
      "firebaseextensions.v1beta.function/minInstances"               = "0"
      "firebaseextensions.v1beta.function/vpcConnectorEgressSettings" = "VPC_CONNECTOR_EGRESS_SETTINGS_UNSPECIFIED"
    }
  }
}



トラブルシューティングとよくある質問

このガイドでは、プロジェクトを操作する際に次の Terraform 属性を使用します。

resource ブロック内の project

推奨: 可能な限り、各 resource ブロック内に project 属性を含めます。

プロジェクト属性を含めると、Terraform は、リソース ブロック内で指定されたインフラストラクチャを、指定されたプロジェクトに作成します。このガイドとすべてのサンプル構成ファイルでは、この手法を使用しています。

project については、Terraform の公式ドキュメントをご覧ください。

provider ブロック内の user_project_override

ほとんどのリソースのプロビジョニングで user_project_override = true の使用を推奨します。これを使用すると、Firebase プロジェクトの割り当てがチェックされます。ただし、新しいプロジェクトをセットアップする際に割り当てチェックが行われるようにするには、最初に user_project_override = false を使用する必要があります。

user_project_override については、Terraform の公式ドキュメントをご覧ください。

gcloud CLI コマンドの実行に使用するユーザー アカウントが Firebase の利用規約(Firebase ToS)に同意していることを確認します。

  • この確認を行うには、ユーザー アカウントにログインしているブラウザで Firebase コンソールを開き、既存の Firebase プロジェクトを表示してみます。既存の Firebase プロジェクトが表示された場合、ユーザー アカウントは Firebase ToS に同意しています。

  • 既存の Firebase プロジェクトが表示されない場合、ユーザー アカウントは Firebase ToS に同意していない可能性があります。この問題を解決するには、Firebase コンソールで新しい Firebase プロジェクトを作成し、プロジェクトの作成時に Firebase ToS に同意します。このプロジェクトは、コンソールの [プロジェクト設定] ですぐに削除できます。

数分待ってから、terraform apply を再度実行してみてください。

この問題は、システム間の伝播の遅延が原因で発生している可能性があります。この問題を解決するには、terraform import を実行して、Terraform の状態にリソースをインポートします。その後、terraform apply を再度実行してみてください。

各リソースをインポートする方法については、Terraform のドキュメントでインポートに関するセクションをご覧ください(Cloud Firestore の「インポート」ドキュメントなど)。

エラーが示しているように、Terraform が複数のインデックスのプロビジョニングとドキュメントの作成を同時に実行し、同時実行エラーが発生している可能性があります。もう一度 terraform apply を実行してみてください。

このエラーは、Terraform が割り当てを確認するプロジェクトを把握していないことを意味します。トラブルシューティングを行うには、resource ブロック内の次の点を確認してください。

  • project 属性の値が指定されていることを確認してください。
  • プロバイダで user_project_override = true が使用されていること(エイリアスが使用されていないこと)を確認してください。Firebase サンプルでは google-beta です。

プロジェクト ID がすでに存在している理由としては、次のようなものが考えられます。

  • この ID に関連付けられているプロジェクトを別のユーザーが所有している。

    • 解決策: 別のプロジェクト ID を選択します。
  • その ID に関連付けられたプロジェクトが最近削除された(復元可能な削除状態になっている)。

    • 解決策: ID に関連付けられたプロジェクトが自身の所有するプロジェクトであると思われる場合は、projects.get REST API を使用して、プロジェクトの状態を確認します。
  • ID に関連付けられたプロジェクトが現在のユーザーの下に存在している。エラーの原因としては、以前の terraform apply が中断されたことが考えられます。

    • 解決策: 次のコマンドを順番に実行します。
      terraform import google_project.default PROJECT_ID
      terraform import google_firebase_project.default PROJECT_ID

デフォルトの Cloud Firestore インスタンスのプロビジョニングを試みる前に、デフォルトの Cloud Storage バケットを(google_app_engine_application を介して)プロビジョニングした場合、デフォルトの Cloud Firestore インスタンスはすでにプロビジョニングされています。プロビジョニングされたデータベース インスタンスは Datastore モードです。つまり、Firebase SDK、認証、Firebase Security Rules にはアクセスできません。これらの Firebase サービスで Cloud Firestore を使用するには、データベースを空にしてから、Google Cloud コンソールでデータベースのタイプを変更する必要があります。

プロジェクトのデフォルトの Cloud Storage バケットを(google_app_engine_application を介して)プロビジョニングするときに、プロジェクトにデフォルトの Cloud Firestore インスタンスが存在しないと、google_app_engine_application はプロジェクトのデフォルトの Cloud Firestore インスタンスを自動的にプロビジョニングします。

そのため、プロジェクトのデフォルトの Cloud Firestore インスタンスはすでにプロビジョニングされているため、そのデフォルト インスタンスを明示的に再度プロビジョニングしようとすると、google_firestore_database でエラーが発生します。

プロジェクトのデフォルトの Cloud Firestore インスタンスをプロビジョニングした後に、そのインスタンスを「再プロビジョニング」したり、ロケーションの変更を行うことはできません。プロビジョニングされたデータベース インスタンスは Datastore モードです。つまり、Firebase SDK、認証、Firebase Security Rules にはアクセスできません。これらの Firebase サービスで Cloud Firestore を使用するには、データベースを空にしてから、Google Cloud コンソールでデータベースのタイプを変更する必要があります。