resource "google_project_service" "apis" { for_each = toset([ "aiplatform.googleapis.com", "storage.googleapis.com", "cloudbilling.googleapis.com", "cloudresourcemanager.googleapis.com", "iam.googleapis.com", "run.googleapis.com", "artifactregistry.googleapis.com", "cloudbuild.googleapis.com", "cloudbuildv2.googleapis.com", "secretmanager.googleapis.com", "monitoring.googleapis.com", "logging.googleapis.com", "cloudtrace.googleapis.com", "pubsub.googleapis.com", "cloudfunctions.googleapis.com", ]) service = each.key disable_dependent_services = true project = var.project_id } resource "google_service_account" "agent_sa" { project = var.project_id account_id = "${var.customer_id}-agent-sa" display_name = "${var.customer_id} Agent Runner SA" } resource "google_project_iam_member" "agent_sa_roles" { for_each = toset([ "roles/aiplatform.user", "roles/storage.objectAdmin", "roles/logging.logWriter", "roles/cloudtrace.agent", "roles/monitoring.metricWriter", "roles/secretmanager.secretAccessor", "roles/run.invoker", ]) project = var.project_id role = each.key member = "serviceAccount:${google_service_account.agent_sa.email}" } resource "google_pubsub_topic" "billing_alerts" { project = var.project_id name = "${var.customer_id}-billing-alert-auto-teardown" } resource "google_storage_bucket" "agent_staging" { project = var.project_id name = "${var.project_id}-${var.customer_id}-agent-staging" location = var.region force_destroy = true lifecycle_rule { action { type = "Delete" } condition { age = 7 } } } resource "google_billing_budget" "prod_budget" { billing_account = var.billing_account_id display_name = "${var.customer_id}-Agent-Budget-500USD" amount { specified_amount { currency_code = "NOK" units = "2500" } } threshold_rules { threshold_percent = 0.5 } threshold_rules { threshold_percent = 0.8 } threshold_rules { threshold_percent = 1.0 } all_updates_rule { pubsub_topic = google_pubsub_topic.billing_alerts.id schema_version = "1.0" } } resource "google_billing_budget" "dev_budget" { billing_account = var.billing_account_id display_name = "${var.customer_id}-Dev-Budget-75USD" amount { specified_amount { currency_code = "NOK" units = "500" } } threshold_rules { threshold_percent = 0.7 } threshold_rules { threshold_percent = 0.9 } threshold_rules { threshold_percent = 1.0 } all_updates_rule { pubsub_topic = google_pubsub_topic.billing_alerts.id schema_version = "1.0" } } resource "google_artifact_registry_repository" "repo" { project = var.project_id location = var.region repository_id = "${var.customer_id}-agent-images" format = "DOCKER" } resource "google_cloud_run_v2_service" "agent_service" { project = var.project_id name = "${var.customer_id}-agent" location = var.region template { service_account = google_service_account.agent_sa.email scaling { min_instance_count = 1 max_instance_count = 10 } containers { image = var.container_image resources { limits = { cpu = "1" memory = "1Gi" } } env { name = "PROJECT_ID" value = var.project_id } env { name = "REGION" value = var.region } } timeout = "300s" max_instance_request_concurrency = 80 } labels = { env = "prod" team = "osvaucoe" customer = var.customer_id } depends_on = [google_project_service.apis] } resource "google_cloud_run_v2_service_iam_member" "invoker" { project = var.project_id location = google_cloud_run_v2_service.agent_service.location name = google_cloud_run_v2_service.agent_service.name role = "roles/run.invoker" member = "user:chris.christiansen@vauco.no" # This should probably be a variable } resource "google_bigquery_dataset" "logs" { project = var.project_id dataset_id = "${var.customer_id}_logs" friendly_name = "${var.customer_id} Logs" location = var.region default_table_expiration_ms = 7776000000 # 90 dager } resource "google_bigquery_table" "cloud_run_logs" { project = var.project_id dataset_id = google_bigquery_dataset.logs.dataset_id table_id = "cloud_run_logs" deletion_protection = false time_partitioning { type = "DAY" field = "timestamp" expiration_ms = 7776000000 } schema = jsonencode([ { name = "timestamp", type = "TIMESTAMP", mode = "REQUIRED" }, { name = "severity", type = "STRING", mode = "NULLABLE" }, { name = "service_name", type = "STRING", mode = "NULLABLE" }, { name = "revision", type = "STRING", mode = "NULLABLE" }, { name = "http_method", type = "STRING", mode = "NULLABLE" }, { name = "http_url", type = "STRING", mode = "NULLABLE" }, { name = "http_status", type = "INTEGER", mode = "NULLABLE" }, { name = "latency_ms", type = "FLOAT", mode = "NULLABLE" }, { name = "message", type = "STRING", mode = "NULLABLE" }, { name = "trace", type = "STRING", mode = "NULLABLE" }, { name = "labels", type = "JSON", mode = "NULLABLE" } ]) } resource "google_project_iam_member" "bq_log_writer" { project = var.project_id role = "roles/bigquery.dataEditor" member = "serviceAccount:${google_service_account.agent_sa.email}" } resource "google_logging_project_sink" "cloudrun_to_bq" { project = var.project_id name = "${var.customer_id}-cloudrun-sink" destination = "bigquery.googleapis.com/projects/${var.project_id}/datasets/${google_bigquery_dataset.logs.dataset_id}" filter = "resource.type=\"cloud_run_revision\" AND resource.labels.service_name=\"${var.customer_id}-agent\"" unique_writer_identity = true } resource "google_project_iam_member" "log_sink_bq_writer" { project = var.project_id role = "roles/bigquery.dataEditor" member = google_logging_project_sink.cloudrun_to_bq.writer_identity } resource "google_monitoring_notification_channel" "email" { project = var.project_id display_name = "${var.customer_id} Alert Email" type = "email" labels = { email_address = var.alert_email } } resource "google_logging_metric" "agent_error_count" { project = var.project_id name = "${var.customer_id}-agent-error-count" description = "Count of ERROR severity logs from agent" filter = "resource.type=\"cloud_run_revision\" AND resource.labels.service_name=\"${var.customer_id}-agent\" AND severity=ERROR" } resource "google_monitoring_uptime_check_config" "cloud_run_uptime" { project = var.project_id display_name = "${var.customer_id}-uptime" http_check { path = "/health" port = 443 use_ssl = true } monitored_resource { type = "uptime_url" labels = { host = replace(google_cloud_run_v2_service.agent_service.uri, "https://", "") } } timeout = "10s" period = "60s" } data "archive_file" "cost_guard_fn_zip" { type = "zip" source_dir = "${path.module}/../../terraform/cost-guard-fn" output_path = "${path.module}/cost-guard-fn.zip" } resource "google_storage_bucket_object" "cost_guard_fn_source" { name = "${var.customer_id}-cost-guard-fn-source.zip" bucket = google_storage_bucket.agent_staging.name source = data.archive_file.cost_guard_fn_zip.output_path } resource "google_cloudfunctions2_function" "billing_auto_teardown" { project = var.project_id name = "${var.customer_id}-billing-auto-teardown" location = var.region build_config { runtime = "python312" entry_point = "billing_alert" source { storage_source { bucket = google_storage_bucket.agent_staging.name object = google_storage_bucket_object.cost_guard_fn_source.name } } } service_config { service_account_email = google_service_account.agent_sa.email environment_variables = { PROJECT_ID = var.project_id CLOUD_RUN_SERVICE = "${var.customer_id}-agent" REGION = var.region } } event_trigger { trigger_region = "global" event_type = "google.cloud.pubsub.topic.v1.messagePublished" pubsub_topic = google_pubsub_topic.billing_alerts.id } } data "google_project" "project" { project_id = var.project_id } resource "google_project_iam_member" "function_sa_run_admin" { project = var.project_id role = "roles/run.admin" member = "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com" } resource "google_project_iam_member" "function_sa_secret_accessor" { project = var.project_id role = "roles/secretmanager.secretAccessor" member = "serviceAccount:${data.google_project.project.number}-compute@developer.gserviceaccount.com" } resource "google_billing_account_iam_member" "billing_viewers" { for_each = toset(var.billing_viewer_emails) billing_account_id = var.billing_account_id role = "roles/billing.viewer" member = "user:${each.key}" }