Skip to content

Instantly share code, notes, and snippets.

@zebreus
Last active April 2, 2025 04:05
Show Gist options
  • 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
]
}
@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

@jaylong255
Copy link

@zebreus i was looking at your null resource and how you're authenticating to get a token and then curling the firebase api. i started digging around for ways to create custom resources. have you looked into creating a plugin to extend the google-beta provider. that way you could write more robust crud and state management and lifecycle logic with the go sdk for firebase admin. you should be able to inherit auth from the same instantiation as your parent provider call too.

are you still effing with this? it's cool stuff. it would be really powerful to be able to fully manage firebase projects from code and i don't feel like waiting around for a version that does. i'm going to start hacking on something. let me know if you have any advice or another repo started with something like this.

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