Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions terraform/apps/mysql-catalog.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# ─────────────────────────────────────────────────────────────
# mysql-app-catalog
# Single contract between firebase-cloud and personal-cloud.
# firebase-cloud writes a JSON map of apps that need OCI MySQL DBs;
# personal-cloud reads it and provisions databases + populates per-app
# secret versions. Adding a new app to this map (and adding a matching
# module "<app>_db" call) is the only edit needed to provision a DB.
# ─────────────────────────────────────────────────────────────

locals {
mysql_apps = {
shehryar = module.shehryar_db.app_entry
}
}

resource "google_secret_manager_secret" "mysql_app_catalog" {
project = var.project_id
secret_id = "mysql-app-catalog"
replication {
auto {}
}
}

resource "google_secret_manager_secret_version" "mysql_app_catalog" {
secret = google_secret_manager_secret.mysql_app_catalog.id
secret_data = jsonencode(local.mysql_apps)
}

# ─────────────────────────────────────────────────────────────
# Bootstrap IAM: grant personal-cloud's deploy SA reader on the
# secrets it needs to bring up its terraform stack (catalog + admin
# creds + tfstate creds). Managed here rather than in personal-cloud
# to avoid a chicken-and-egg (personal-cloud cannot self-grant the
# access it needs to apply).
# ─────────────────────────────────────────────────────────────

locals {
personal_cloud_bootstrap_secrets = toset([
"mysql-app-catalog",
"db-admin-user",
"db-admin-pass",
"oci-tf-aws-access-key-id",
"oci-tf-aws-secret-access-key",
])
}

resource "google_secret_manager_secret_iam_member" "personal_cloud_bootstrap_access" {
for_each = local.personal_cloud_bootstrap_secrets
project = var.project_id
secret_id = each.value
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${var.personal_cloud_deploy_sa}"

depends_on = [google_secret_manager_secret.mysql_app_catalog]
}
31 changes: 31 additions & 0 deletions terraform/apps/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -233,3 +233,34 @@ output "crea8ivedesign_required_dns" {
description = "DNS records required at the registrar to verify and serve the custom domain"
value = google_firebase_hosting_custom_domain.crea8ivedesign.required_dns_updates
}

# ── shehryar Outputs ────────────────────────────────────────
output "shehryar_wif_provider" {
description = "WIF_PROVIDER for shehryar repo GitHub secrets"
value = module.shehryar_identity.wif_provider
}

output "shehryar_gcp_sa_email" {
description = "GCP_SA_EMAIL for shehryar repo GitHub secrets"
value = module.shehryar_identity.ci_cd_sa_email
}

output "shehryar_hosting_url" {
description = "Firebase Hosting URL"
value = module.shehryar_hosting.site_url
}

output "shehryar_custom_domain" {
description = "Custom domain for shehryar"
value = google_firebase_hosting_custom_domain.shehryar.custom_domain
}

output "shehryar_required_dns" {
description = "DNS records required at the registrar to verify and serve the custom domain"
value = google_firebase_hosting_custom_domain.shehryar.required_dns_updates
}

output "shehryar_api_url" {
description = "Cloud Run URL for the shehryar chatapp backend"
value = google_cloud_run_v2_service.shehryar_api.uri
}
192 changes: 192 additions & 0 deletions terraform/apps/shehryar.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# ─────────────────────────────────────────────────────────────
# shehryar — Static site + PHP chatapp on Cloud Run + OCI HeatWave MySQL
# ─────────────────────────────────────────────────────────────
# Firebase Hosting serves the static site. /chatapp/** is rewritten
# to a Cloud Run service (shehryar-api) running the PHP backend.
# The backend talks to OCI HeatWave MySQL (free tier) over the public
# internet; the daily keep-alive (mysql-keepalive.tf) prevents Oracle
# from marking the instance INACTIVE after 7 days of silence.
# ─────────────────────────────────────────────────────────────

# ── Identity: SA + WIF Provider ─────────────────────────────
module "shehryar_identity" {
source = "../modules/app-identity"

project_id = var.project_id
app_name = "shehryar"
github_org = var.github_org
github_repo = "shehryar"
wif_pool_id = var.wif_pool_id
wif_pool_name = var.wif_pool_name

ci_cd_roles = [
"roles/firebasehosting.admin",
"roles/firebase.admin",
"roles/run.admin",
"roles/artifactregistry.writer",
"roles/iam.serviceAccountUser",
"roles/serviceusage.serviceUsageConsumer",
]

runtime_roles = [
"roles/secretmanager.secretAccessor",
"roles/logging.logWriter",
"roles/monitoring.metricWriter",
"roles/cloudtrace.agent",
]
}

# ── Firebase Hosting ────────────────────────────────────────
module "shehryar_hosting" {
source = "../modules/hosting"

project_id = var.project_id
site_id = "shehryar"
}

resource "google_firebase_hosting_custom_domain" "shehryar" {
provider = google-beta
project = var.project_id
site_id = module.shehryar_hosting.site_id
custom_domain = "shehryar.junaid.guru"

wait_dns_verification = false
}

# ── Per-app DB + secret shells ──────────────────────────────
# Owned by the app-with-mysql module: creates shehryar-db-{user,pass,name}
# shells, grants runtime SA reader on them and on db-host, and emits an
# app_entry consumed by mysql-catalog.tf. Versions are populated by
# personal-cloud terraform on its next apply.
module "shehryar_db" {
source = "../modules/app-with-mysql"

project_id = var.project_id
app_name = "shehryar"
database_name = "rn_chatapp"
runtime_sa_email = module.shehryar_identity.runtime_sa_email
}

# ── Cloud Run (inlined to wire Secret Manager refs) ─────────
resource "google_cloud_run_v2_service" "shehryar_api" {
provider = google-beta
project = var.project_id
name = "shehryar-api"
location = var.region

deletion_protection = false
ingress = "INGRESS_TRAFFIC_ALL"

template {
service_account = module.shehryar_identity.runtime_sa_email

scaling {
min_instance_count = 0
max_instance_count = 1
}

containers {
image = "${var.region}-docker.pkg.dev/${var.project_id}/firebase-cloud/shehryar-api:latest"

resources {
limits = {
cpu = "1"
memory = "512Mi"
}
cpu_idle = true
startup_cpu_boost = true
}

env {
name = "APP_ENV"
value = "production"
}

env {
name = "DB_HOST"
value_source {
secret_key_ref {
secret = "db-host"
version = "latest"
}
}
}

env {
name = "DB_USER"
value_source {
secret_key_ref {
secret = "shehryar-db-user"
version = "latest"
}
}
}

env {
name = "DB_PASS"
value_source {
secret_key_ref {
secret = "shehryar-db-pass"
version = "latest"
}
}
}

env {
name = "DB_NAME"
value_source {
secret_key_ref {
secret = "shehryar-db-name"
version = "latest"
}
}
}

startup_probe {
http_get {
path = "/chatapp/health.php"
}
initial_delay_seconds = 5
period_seconds = 10
failure_threshold = 3
timeout_seconds = 3
}

liveness_probe {
http_get {
path = "/"
}
period_seconds = 30
failure_threshold = 3
timeout_seconds = 3
}
}

timeout = "300s"
execution_environment = "EXECUTION_ENVIRONMENT_GEN2"
}

traffic {
type = "TRAFFIC_TARGET_ALLOCATION_TYPE_LATEST"
percent = 100
}

lifecycle {
ignore_changes = [
template[0].containers[0].image,
traffic,
]
}

depends_on = [
module.shehryar_db,
]
}

resource "google_cloud_run_v2_service_iam_member" "shehryar_api_public" {
project = var.project_id
location = var.region
name = google_cloud_run_v2_service.shehryar_api.name
role = "roles/run.invoker"
member = "allUsers"
}
6 changes: 6 additions & 0 deletions terraform/apps/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,9 @@ variable "openguessr_google_maps_api_key" {
type = string
sensitive = true
}

# ── Cross-stack: personal-cloud (OCI MySQL provisioning) ────
variable "personal_cloud_deploy_sa" {
description = "personal-cloud workflow's deploy SA email; granted reader on bootstrap GSM secrets so it can authenticate to OCI tfstate and read MySQL admin creds"
type = string
}
2 changes: 2 additions & 0 deletions terraform/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,7 @@ module "apps" {

openguessr_google_maps_api_key = var.openguessr_google_maps_api_key

personal_cloud_deploy_sa = var.personal_cloud_deploy_sa

depends_on = [module.project_setup]
}
36 changes: 36 additions & 0 deletions terraform/modules/app-with-mysql/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
locals {
shell_kinds = ["user", "pass", "name"]
}

# Per-app secret shells. Versions are populated by personal-cloud terraform.
resource "google_secret_manager_secret" "shells" {
for_each = toset(local.shell_kinds)
project = var.project_id
secret_id = "${var.app_name}-db-${each.value}"
replication {
auto {}
}
}

# Grant the runtime SA reader on each per-app shell.
resource "google_secret_manager_secret_iam_member" "app_shells" {
for_each = google_secret_manager_secret.shells
project = var.project_id
secret_id = each.value.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${var.runtime_sa_email}"
}

# The shared db-host secret is owned out-of-module by mysql-keepalive.tf.
# We just look it up and grant the runtime SA reader on it.
data "google_secret_manager_secret" "db_host" {
project = var.project_id
secret_id = "db-host"
}

resource "google_secret_manager_secret_iam_member" "db_host" {
project = var.project_id
secret_id = data.google_secret_manager_secret.db_host.secret_id
role = "roles/secretmanager.secretAccessor"
member = "serviceAccount:${var.runtime_sa_email}"
}
7 changes: 7 additions & 0 deletions terraform/modules/app-with-mysql/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
output "app_entry" {
description = "Entry to add to the mysql-app-catalog locals; consumed by personal-cloud terraform"
value = {
database = var.database_name
sa_email = var.runtime_sa_email
}
}
19 changes: 19 additions & 0 deletions terraform/modules/app-with-mysql/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
variable "app_name" {
type = string
description = "App name; used as prefix for GSM secret IDs (e.g. shehryar-db-user) and as the catalog map key"
}

variable "database_name" {
type = string
description = "MySQL database name to provision for this app on the OCI MySQL HeatWave cluster"
}

variable "runtime_sa_email" {
type = string
description = "Email of the runtime service account that will read the per-app DB secrets at runtime"
}

variable "project_id" {
type = string
description = "GCP project ID hosting the secrets"
}
Loading
Loading