Event-Driven: Eventarc, Cloud Storage, and Cloud Run
Scenario
When a user uploads an image to a Cloud Storage Bucket, you want an asynchronous background process to immediately resize the image. Instead of polling the bucket, you will use Eventarc. Eventarc will capture the Cloud Storage "Object Finalized" events via Cloud Audit Logs/PubSub, and route an HTTP POST request to a Cloud Run service payload containing the bucket and object details.
- Deploy a backend google_cloud_run_v2_service meant to process the images. Ensure it requires IAM authentication (do not make it `allUsers` public).
- Deploy the incoming google_storage_bucket.
- Create a google_service_account for the Eventarc trigger to run as, granting it roles/run.invoker and roles/eventarc.eventReceiver.
- Create a google_eventarc_trigger tracking the google.cloud.storage.object.v1.finalized event on the bucket, sending destination data to Cloud Run natively.
Additional Context
Eventarc vs Legacy Cloud Functions Trigger: In older GCP, you attached Cloud Storage triggers directly inside the `google_cloudfunctions_function` resource. Eventarc is the modern, decoupled approach. Eventarc unifies all GCP events into CloudEvents standards, and can trigger Cloud Functions v2, Cloud Run, or GKE services identically. It relies heavily on hidden Pub/Sub topics under the hood.
IAM Trickiness: Eventarc must create a trigger. To do this, the Service Account creating the trigger (or running Terraform) must have the `roles/pubsub.publisher` role, and the default Compute Engine Service Account must have `roles/pubsub.publisher` because Cloud Storage uses it natively to publish the audit logs.
main.tf
provider "google" {
project = var.project_id
region = "us-central1"
}
# 1. Cloud Storage Bucket (The Event Source)
resource "random_id" "bkt" { byte_length = 4 }
resource "google_storage_bucket" "uploads" {
name = "image-uploads-${random_id.bkt.hex}"
location = "us-central1"
uniform_bucket_level_access = true
}
# 2. Cloud Run Service (The Event Destination)
resource "google_cloud_run_v2_service" "image_processor" {
name = "event-image-processor"
location = "us-central1"
template {
containers {
# Use a dummy container for tracking the POST requests
image = "us-docker.pkg.dev/cloudrun/container/hello"
}
}
}
# 3. Security: The Identity Eventarc uses to Invoke Cloud Run
resource "google_service_account" "eventarc_trigger_sa" {
account_id = "eventarc-invoker-sa"
display_name = "Eventarc Trigger SA"
}
# Allow Eventarc to invoke the private Cloud Run service
resource "google_cloud_run_service_iam_member" "run_invoker" {
location = google_cloud_run_v2_service.image_processor.location
project = google_cloud_run_v2_service.image_processor.project
service = google_cloud_run_v2_service.image_processor.name
role = "roles/run.invoker"
member = "serviceAccount:${google_service_account.eventarc_trigger_sa.email}"
}
# Note: In a fresh project, you may need to grant pubsub.publisher to the Google-managed
# Cloud Storage service agent (service-[PROJECT_NUM]@gs-project-accounts.iam.gserviceaccount.com)
# for Eventarc triggers to successfully deploy without error.
# 4. The Eventarc Trigger
resource "google_eventarc_trigger" "storage_trigger" {
name = "storage-upload-trigger"
location = "us-central1"
# Specify the Service Account acting as the trigger
service_account = google_service_account.eventarc_trigger_sa.email
# Step A: Listen for exactly this type of event
matching_criteria {
attribute = "type"
value = "google.cloud.storage.object.v1.finalized"
}
# Step B: Filter the event to only a specific bucket
matching_criteria {
attribute = "bucket"
value = google_storage_bucket.uploads.name
}
# Step C: Send the HTTP CloudEvent payload to Cloud Run
destination {
cloud_run_service {
service = google_cloud_run_v2_service.image_processor.name
region = google_cloud_run_v2_service.image_processor.location
}
}
depends_on = [google_cloud_run_service_iam_member.run_invoker]
}Workflow Commands
terraform init
terraform apply -var="project_id=my-gcp-project-123"
# Upload an file to trigger the event
gcloud storage cp dummy.jpg gs://$(terraform output -raw bucket_name)/
# The Cloud Run logs will automatically show an invocation within 5 seconds!
terraform destroy -var="project_id=my-gcp-project-123"
