Skip to content

Instantly share code, notes, and snippets.

@zebreus
Last active March 27, 2024 11:00
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save zebreus/906b8870e49586adfe8bd7bbff43f0a8 to your computer and use it in GitHub Desktop.
Save zebreus/906b8870e49586adfe8bd7bbff43f0a8 to your computer and use it in GitHub Desktop.
Terraform configuration for creating a firebase project with firestore, functions and storage
# firebase.tf https://gist.githubusercontent.com/Zebreus/906b8870e49586adfe8bd7bbff43f0a8/raw/firebase.tf
# Terraform configuration for creating a firebase project with firestore, functions and storage
# Unfinished
terraform {
required_providers {
google-beta = {
source = "hashicorp/google-beta"
version = "4.11.0"
}
null = {
version = "~> 3.1.0"
}
time = {
source = "hashicorp/time"
version = "0.7.2"
}
}
}
variable "billing_account_id" {
type = string
description = "The id of the associated billing account"
nullable = false
}
variable "project_id" {
type = string
description = "The id of the created project"
nullable = false
}
variable "project_name" {
type = string
description = "The name of the created project"
nullable = false
}
variable "region" {
type = string
description = "The region to create the project in"
default = "europe-west1"
nullable = false
}
variable "zone" {
type = string
description = "The zone to create the project in"
default = "europe-west1-b"
nullable = false
}
variable "location" {
type = string
description = "The location to create the project in"
default = "europe-west"
nullable = false
}
locals {
bucket_location = "EUROPE-WEST1"
}
# Basic provider
provider "google-beta" {
alias = "gcloud-user"
region = var.region
zone = var.zone
}
data "google_billing_account" "account" {
provider = google-beta.gcloud-user
billing_account = var.billing_account_id
}
data "google_client_config" "gcloud-user" {
provider = google-beta.gcloud-user
# depends_on = [
# google_service_account.service_account
# ]
}
data "google_client_openid_userinfo" "gcloud-user" {
provider = google-beta.gcloud-user
}
// Create new google cloud project with service account
resource "google_project" "default" {
provider = google-beta.gcloud-user
project_id = var.project_id
name = var.project_name
billing_account = data.google_billing_account.account.id
}
resource "google_service_account" "service_account" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
account_id = "terraform"
display_name = "Terraform"
}
# Allow your user to create a access token
resource "google_service_account_iam_member" "grant-token-iam" {
provider = google-beta.gcloud-user
service_account_id = google_service_account.service_account.id
role = "roles/iam.serviceAccountTokenCreator"
member = "user:${data.google_client_openid_userinfo.gcloud-user.email}"
}
resource "time_sleep" "delay_token_creation" {
depends_on = [
google_service_account_iam_member.grant-token-iam,
google_service_account.service_account,
google_project_iam_member.firebase-admin-iam,
google_project_iam_member.service-usage-admin-iam,
google_project_iam_member.appengine-admin-iam,
google_project_iam_member.appengine-creator-iam,
google_project_iam_member.editor-iam
]
create_duration = "30s"
}
# Create access token
data "google_service_account_access_token" "default" {
provider = google-beta.gcloud-user
# project = google_project.default.project_id
target_service_account = google_service_account.service_account.email
scopes = ["userinfo-email", "cloud-platform"]
lifetime = "300s"
depends_on = [
google_service_account_iam_member.grant-token-iam,
google_service_account.service_account,
google_project_iam_member.firebase-admin-iam,
google_project_iam_member.service-usage-admin-iam,
google_project_iam_member.appengine-admin-iam,
google_project_iam_member.appengine-creator-iam,
google_project_iam_member.editor-iam,
time_sleep.delay_token_creation
]
}
# Give some roles to the service account
resource "google_project_iam_member" "firebase-admin-iam" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/firebase.admin"
member = "serviceAccount:${google_service_account.service_account.email}"
}
resource "google_project_iam_member" "service-usage-admin-iam" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/serviceusage.serviceUsageAdmin"
member = "serviceAccount:${google_service_account.service_account.email}"
}
resource "google_project_iam_member" "appengine-admin-iam" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/appengine.appAdmin"
member = "serviceAccount:${google_service_account.service_account.email}"
}
resource "google_project_iam_member" "appengine-creator-iam" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/appengine.appCreator"
member = "serviceAccount:${google_service_account.service_account.email}"
}
resource "google_project_iam_member" "editor-iam" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/editor"
member = "serviceAccount:${google_service_account.service_account.email}"
}
# Create provider with service account
resource "google_service_account_key" "mykey" {
provider = google-beta.gcloud-user
service_account_id = google_service_account.service_account.id
# Wait for the account being added to roles
depends_on = [
google_project_iam_member.firebase-admin-iam,
google_project_iam_member.service-usage-admin-iam,
]
}
provider "google-beta" {
alias = "service-account"
project = google_project.default.project_id
region = var.region
zone = var.zone
# impersonate_service_account = google_service_account.service_account.email
# credentials = base64decode(google_service_account_key.mykey.private_key)
access_token = data.google_service_account_access_token.default.access_token
}
# Activate all required apis
resource "google_project_service" "serviceusage" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
service = "serviceusage.googleapis.com"
disable_dependent_services = true
depends_on = [
]
}
resource "google_project_service" "firebase" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "firebase.googleapis.com"
disable_dependent_services = true
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "firestore" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "firestore.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "firebasestorage" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "firebasestorage.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "cloudresourcemanager" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "cloudresourcemanager.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "identitytoolkit" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "identitytoolkit.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "compute" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "compute.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "container_registry" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "containerregistry.googleapis.com"
disable_dependent_services = true
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "cloud_run" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "run.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
resource "google_project_service" "cloud_build" {
provider = google-beta.service-account
project = google_project.default.project_id
service = "cloudbuild.googleapis.com"
depends_on = [
google_project_service.serviceusage
]
}
# Create firebase project
resource "google_firebase_project" "default" {
provider = google-beta.service-account
project = google_project.default.project_id
depends_on = [
google_project_service.firebase
]
}
# Create firebase web app
resource "google_firebase_web_app" "basic" {
provider = google-beta.service-account
project = google_project.default.project_id
display_name = "${var.project_name} App"
depends_on = [
google_firebase_project.default
]
}
data "google_firebase_web_app_config" "basic" {
provider = google-beta.service-account
web_app_id = google_firebase_web_app.basic.app_id
}
# Create firestore database
resource "google_app_engine_application" "app" {
provider = google-beta.service-account
# provider = google-beta.gcloud-user
project = google_project.default.project_id
location_id = var.location
database_type = "CLOUD_FIRESTORE"
depends_on = [
google_project_iam_member.appengine-admin-iam,
google_project_iam_member.appengine-creator-iam,
google_project_service.firestore
]
}
# Create a bucket for backups
resource "google_storage_bucket" "backup" {
provider = google-beta.service-account
project = google_project.default.project_id
name = "${google_project.default.project_id}-backup"
location = local.bucket_location
}
# Create admin-sdk service account
resource "google_service_account" "admin_sdk" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
account_id = "firebase-adminsdk-ouwu6"
display_name = "firebase-adminsdk"
}
resource "google_project_iam_member" "admin-sdk-token-creator" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/iam.serviceAccountTokenCreator"
member = "serviceAccount:${google_service_account.admin_sdk.email}"
}
resource "google_project_iam_member" "admin-sdk-agent" {
provider = google-beta.gcloud-user
project = google_project.default.project_id
role = "roles/firebase.sdkAdminServiceAgent"
member = "serviceAccount:${google_service_account.admin_sdk.email}"
}
resource "google_service_account_key" "admin_sdk" {
provider = google-beta.gcloud-user
service_account_id = google_service_account.service_account.id
# Wait for the account being added to roles
depends_on = [
google_project_iam_member.admin-sdk-token-creator,
google_project_iam_member.admin-sdk-agent,
]
}
# Create firebase storage
resource "null_resource" "activate_storage" {
triggers = {
bucket = data.google_firebase_web_app_config.basic.storage_bucket
}
provisioner "local-exec" {
command = "curl -X POST -H 'Authorization: Bearer ${nonsensitive(data.google_service_account_access_token.default.access_token)}' -H 'Content-Type: application/json' 'https://firebasestorage.googleapis.com/v1beta/projects/${google_project.default.project_id}/buckets/${data.google_firebase_web_app_config.basic.storage_bucket}:addFirebase'"
interpreter = ["sh", "-c"]
}
depends_on = [
google_firebase_web_app.basic,
google_project_service.firebasestorage
]
}
# Enable authentication service
resource "google_identity_platform_config" "identity_platform_config" {
provider = google-beta.service-account
project = google_project.default.project_id
autodelete_anonymous_users = true
depends_on = [
google_firebase_web_app.basic,
google_project_service.identitytoolkit
]
}
resource "google_identity_platform_project_default_config" "identity_project_config" {
provider = google-beta.service-account
project = google_project.default.project_id
sign_in {
allow_duplicate_emails = false
email {
enabled = true
password_required = true
}
}
depends_on =[google_identity_platform_config.identity_platform_config]
}
# Write secrets to local file
resource "local_file" "firebase_config" {
content = jsonencode({
firebase = {
appId = google_firebase_web_app.basic.app_id
apiKey = data.google_firebase_web_app_config.basic.api_key
authDomain = data.google_firebase_web_app_config.basic.auth_domain
databaseURL = lookup(data.google_firebase_web_app_config.basic, "database_url", "")
storageBucket = lookup(data.google_firebase_web_app_config.basic, "storage_bucket", "")
messagingSenderId = lookup(data.google_firebase_web_app_config.basic, "messaging_sender_id", "")
measurementId = lookup(data.google_firebase_web_app_config.basic, "measurement_id", "")
}
})
filename = "${path.module}/firebase-config.json"
depends_on = [
google_firebase_web_app.basic
]
}
resource "local_file" "secrets_file" {
content = jsonencode({
private = {
serviceAccount = jsondecode(base64decode(google_service_account_key.admin_sdk.private_key))
firebase = {
backupBucket = google_storage_bucket.backup.name
}
}
public = {
firebase = {
projectId = google_project.default.project_id
appId = google_firebase_web_app.basic.app_id
apiKey = data.google_firebase_web_app_config.basic.api_key
authDomain = data.google_firebase_web_app_config.basic.auth_domain
databaseURL = lookup(data.google_firebase_web_app_config.basic, "database_url", "")
storageBucket = lookup(data.google_firebase_web_app_config.basic, "storage_bucket", "")
messagingSenderId = lookup(data.google_firebase_web_app_config.basic, "messaging_sender_id", "")
measurementId = lookup(data.google_firebase_web_app_config.basic, "measurement_id", "")
}
}
})
filename = "${path.module}/secrets.json"
depends_on = [
google_firebase_web_app.basic
]
}
resource "local_file" "firebaserc" {
content = jsonencode({
projects = {
development = google_project.default.project_id
production = google_project.default.project_id
}
})
filename = "${path.module}/.firebaserc"
depends_on = [
google_project.default
]
}
resource "local_file" "admin_config" {
content = base64decode(google_service_account_key.mykey.private_key)
filename = "${path.module}/admin-config.json"
depends_on = [
google_service_account_key.mykey,
google_firebase_web_app.basic
]
}
@zebreus
Copy link
Author

zebreus commented Feb 21, 2023

Neat solution! I added it to the gist.

I am curious to know how you figured out that the config was missing?

@s-kravtsov
Copy link

@zebreus I figured Identity Platform was one of the APIs independent from firebase, so you can open it in the GCP console and see what is missing. And you can find what can be configured in the terraform doc.

@afgallo
Copy link

afgallo commented May 15, 2023

Just wondering if anyone has run into this issue with the latest version of this gist:

╷
│ Error: Invalid resource type
│
│   on main.tf line 363, in resource "google_identity_platform_config" "identity_platform_config":
│  363: resource "google_identity_platform_config" "identity_platform_config" {
│
│ The provider hashicorp/google-beta does not support resource type "google_identity_platform_config".

@myktra
Copy link

myktra commented May 30, 2023

@afgallo

I pulled the google_identity_platform_config.identity_platform_config resource into my own script, dropped the provider = google-beta instruction (as of v4.66.0, you don't need the beta provider), and it worked fine for me.

@schowdhuri
Copy link

This gist is super helpful, thank you! FYI, setting authorized domains is now supported: https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/identity_platform_config#authorized_domains

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment