LAB33_GCP_Event-Driven: Eventarc, Cloud Storage, and Cloud Run

Event-Driven: Eventarc, Cloud Storage, and Cloud Run

☁ GCP 🔥 Intermediate Eventarc Cloud Run Pub/Sub Event-Driven

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.

Your Objectives
  • 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"