terraform { required_version = ">= 1.0" required_providers { google = { source = "hashicorp/google" version = "~> 5.0" } } } provider "google" { project = var.project_id region = var.region } # Variables variable "project_id" { description = "Google Cloud Project ID" type = string } variable "region" { description = "Google Cloud Region" type = string default = "us-central1" } variable "environment" { description = "Environment (dev, staging, prod)" type = string default = "prod" } # Enable required APIs resource "google_project_service" "required_apis" { for_each = toset([ "cloudbuild.googleapis.com", "run.googleapis.com", "containerregistry.googleapis.com", "secretmanager.googleapis.com", "cloudtrace.googleapis.com", "monitoring.googleapis.com", "translate.googleapis.com", "texttospeech.googleapis.com", "storage.googleapis.com", "aiplatform.googleapis.com" ]) service = each.value disable_on_destroy = false } # Service Accounts resource "google_service_account" "api_service_account" { account_id = "accessible-video-api" display_name = "Accessible Video API Service Account" description = "Service account for the API server" } resource "google_service_account" "worker_service_account" { account_id = "accessible-video-worker" display_name = "Accessible Video Worker Service Account" description = "Service account for Celery workers" } # IAM bindings for API service account resource "google_project_iam_member" "api_secret_accessor" { project = var.project_id role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.api_service_account.email}" } resource "google_project_iam_member" "api_storage_admin" { project = var.project_id role = "roles/storage.objectAdmin" member = "serviceAccount:${google_service_account.api_service_account.email}" } resource "google_project_iam_member" "api_trace_agent" { project = var.project_id role = "roles/cloudtrace.agent" member = "serviceAccount:${google_service_account.api_service_account.email}" } resource "google_project_iam_member" "api_monitoring_writer" { project = var.project_id role = "roles/monitoring.metricWriter" member = "serviceAccount:${google_service_account.api_service_account.email}" } # IAM bindings for Worker service account resource "google_project_iam_member" "worker_secret_accessor" { project = var.project_id role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${google_service_account.worker_service_account.email}" } resource "google_project_iam_member" "worker_storage_admin" { project = var.project_id role = "roles/storage.objectAdmin" member = "serviceAccount:${google_service_account.worker_service_account.email}" } resource "google_project_iam_member" "worker_trace_agent" { project = var.project_id role = "roles/cloudtrace.agent" member = "serviceAccount:${google_service_account.worker_service_account.email}" } resource "google_project_iam_member" "worker_monitoring_writer" { project = var.project_id role = "roles/monitoring.metricWriter" member = "serviceAccount:${google_service_account.worker_service_account.email}" } resource "google_project_iam_member" "worker_ai_user" { project = var.project_id role = "roles/aiplatform.user" member = "serviceAccount:${google_service_account.worker_service_account.email}" } # GCS Bucket for video storage resource "google_storage_bucket" "video_storage" { name = "accessible-video-${var.project_id}" location = var.region storage_class = "STANDARD" uniform_bucket_level_access = true cors { origin = ["https://your-frontend-domain.com", "http://localhost:5173"] method = ["GET", "POST", "PUT", "DELETE", "OPTIONS"] response_header = ["Content-Type", "Authorization", "Range"] max_age_seconds = 3600 } lifecycle_rule { condition { age = 90 } action { type = "Delete" } } } # Cloud Run API Service resource "google_cloud_run_v2_service" "api_service" { name = "accessible-video-api" location = var.region depends_on = [google_project_service.required_apis] template { service_account = google_service_account.api_service_account.email scaling { min_instance_count = 1 max_instance_count = 10 } containers { image = "gcr.io/${var.project_id}/accessible-video-api:latest" ports { container_port = 8000 } resources { limits = { memory = "2Gi" cpu = "2000m" } cpu_idle = false } env { name = "APP_ENV" value = var.environment } env { name = "PYTHONPATH" value = "/app" } env { name = "PYTHONUNBUFFERED" value = "1" } env { name = "GCS_BUCKET_NAME" value = google_storage_bucket.video_storage.name } env { name = "GOOGLE_CLOUD_PROJECT" value = var.project_id } env { name = "OTEL_SERVICE_NAME" value = "accessible-video-api" } env { name = "OTEL_TRACES_EXPORTER" value = "gcp_trace" } env { name = "SENTRY_ENVIRONMENT" value = var.environment } # Secret environment variables env { name = "MONGODB_URL" value_source { secret_key_ref { secret = "mongodb-url" version = "latest" } } } env { name = "REDIS_URL" value_source { secret_key_ref { secret = "redis-url" version = "latest" } } } env { name = "JWT_SECRET_KEY" value_source { secret_key_ref { secret = "jwt-secret" version = "latest" } } } env { name = "GEMINI_API_KEY" value_source { secret_key_ref { secret = "gemini-api-key" version = "latest" } } } env { name = "SENDGRID_API_KEY" value_source { secret_key_ref { secret = "sendgrid-api-key" version = "latest" } } } env { name = "ELEVENLABS_API_KEY" value_source { secret_key_ref { secret = "elevenlabs-api-key" version = "latest" } } } env { name = "SENTRY_DSN" value_source { secret_key_ref { secret = "sentry-dsn" version = "latest" } } } liveness_probe { http_get { path = "/health" port = 8000 } initial_delay_seconds = 30 timeout_seconds = 10 period_seconds = 60 } startup_probe { http_get { path = "/health" port = 8000 } initial_delay_seconds = 10 timeout_seconds = 5 period_seconds = 30 } } } } # Cloud Run Worker Service resource "google_cloud_run_v2_service" "worker_service" { name = "accessible-video-worker" location = var.region depends_on = [google_project_service.required_apis] template { service_account = google_service_account.worker_service_account.email scaling { min_instance_count = 0 max_instance_count = 5 } containers { image = "gcr.io/${var.project_id}/accessible-video-worker:latest" resources { limits = { memory = "4Gi" cpu = "4000m" } cpu_idle = false } env { name = "APP_ENV" value = var.environment } env { name = "PYTHONPATH" value = "/app" } env { name = "PYTHONUNBUFFERED" value = "1" } env { name = "C_FORCE_ROOT" value = "1" } env { name = "GCS_BUCKET_NAME" value = google_storage_bucket.video_storage.name } env { name = "GOOGLE_CLOUD_PROJECT" value = var.project_id } env { name = "OTEL_SERVICE_NAME" value = "accessible-video-worker" } env { name = "OTEL_TRACES_EXPORTER" value = "gcp_trace" } env { name = "SENTRY_ENVIRONMENT" value = var.environment } # Secret environment variables env { name = "MONGODB_URL" value_source { secret_key_ref { secret = "mongodb-url" version = "latest" } } } env { name = "REDIS_URL" value_source { secret_key_ref { secret = "redis-url" version = "latest" } } } env { name = "CELERY_BROKER_URL" value_source { secret_key_ref { secret = "redis-url" version = "latest" } } } env { name = "CELERY_RESULT_BACKEND" value_source { secret_key_ref { secret = "redis-url" version = "latest" } } } env { name = "GEMINI_API_KEY" value_source { secret_key_ref { secret = "gemini-api-key" version = "latest" } } } env { name = "ELEVENLABS_API_KEY" value_source { secret_key_ref { secret = "elevenlabs-api-key" version = "latest" } } } env { name = "SENTRY_DSN" value_source { secret_key_ref { secret = "sentry-dsn" version = "latest" } } } } } } # IAM for public access to API resource "google_cloud_run_service_iam_binding" "api_public_access" { location = google_cloud_run_v2_service.api_service.location service = google_cloud_run_v2_service.api_service.name role = "roles/run.invoker" members = ["allUsers"] } # Outputs output "api_service_url" { description = "URL of the deployed API service" value = google_cloud_run_v2_service.api_service.uri } output "worker_service_url" { description = "URL of the deployed worker service" value = google_cloud_run_v2_service.worker_service.uri } output "storage_bucket_name" { description = "Name of the GCS bucket for video storage" value = google_storage_bucket.video_storage.name }