-
-
Save zebreus/906b8870e49586adfe8bd7bbff43f0a8 to your computer and use it in GitHub Desktop.
# 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 | |
] | |
} |
@sheikhaafaq I don't really understand your question, can you maybe explain in more detail what you want to know?
Hey, @zebreus , good job ! I tried to use a similar setup, but I can't seem to figure out how to activate the authentication service. The google_project_service.identitytoolkit does not seem to do it. Did you have this issue ? Thanks !
@s-kravtsov Yes, I also had that issue. I did not find a good solution except manually enabling the authentication service in the firebase cloud console.
It has been a while since I looked into this, but I think it uses an internal google API to activate that service. If I remember correctly that API requires authentication with a real user account, so no service account. I created a few new firebase projects and tried to reverse engineer the API calls by looking at the network requests made when enabling authentication, but was not able to reproduce it if I was not on the website.
I would be happy to hear if you find a solution.
@zebreus After some trial and error, I did find a way to do this. Basically, it is not enough to just activate the identitytoolkit api, you also need to create a default config and then activate the sign in methods you want to enable. Here is the complete code :
resource "google_project_service" "identitytoolkit" {
provider = google-beta
project = google_project.default.project_id
service = "identitytoolkit.googleapis.com"
depends_on = [google_project.default]
}
resource "google_identity_platform_config" "identity_platform_config" {
project = google_project.default.project_id
autodelete_anonymous_users = true
}
resource "google_identity_platform_project_default_config" "identity_project_config" {
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]
}
Here I am enabling the email/password login.
This removed the need to manually enable the api. However, there are still some manual steps to do which are not available in terraform (at least I did not find a way) :
- Register the authorized domains (Authentication -> Settings -> Authorized domains)
- Set the default action link (Authentication -> Templates)
So, not perfect, but the provider is still in beta, so I guess we'll get there.
Neat solution! I added it to the gist.
I am curious to know how you figured out that the config was missing?
@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.
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".
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.
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
@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.
where you have created the firebase project if you are not using it anywhere.