From 76a31fee9a2e98aeb1eab95ac67fbbb34427c26f Mon Sep 17 00:00:00 2001 From: Jacky-Pham Date: Tue, 25 Aug 2026 13:58:27 -0700 Subject: [PATCH 1/3] feat: manage STRR dev infrastructure with Terraform --- .github/workflows/strr-terraform.yaml | 71 +++++++++++++++++ .gitignore | 3 + terraform/.terraform.lock.hcl | 22 ++++++ terraform/README.md | 37 +++++++++ terraform/data.tf | 7 ++ terraform/dev.tfvars | 6 ++ terraform/eventarc.tf | 31 ++++++++ terraform/pubsub.tf | 109 ++++++++++++++++++++++++++ terraform/variables.tf | 35 +++++++++ terraform/versions.tf | 20 +++++ 10 files changed, 341 insertions(+) create mode 100644 .github/workflows/strr-terraform.yaml create mode 100644 terraform/.terraform.lock.hcl create mode 100644 terraform/README.md create mode 100644 terraform/data.tf create mode 100644 terraform/dev.tfvars create mode 100644 terraform/eventarc.tf create mode 100644 terraform/pubsub.tf create mode 100644 terraform/variables.tf create mode 100644 terraform/versions.tf diff --git a/.github/workflows/strr-terraform.yaml b/.github/workflows/strr-terraform.yaml new file mode 100644 index 000000000..3d4e98946 --- /dev/null +++ b/.github/workflows/strr-terraform.yaml @@ -0,0 +1,71 @@ +name: STRR Terraform + +on: + workflow_dispatch: + inputs: + action: + description: Terraform action for the dev environment + required: true + type: choice + options: + - plan + - apply + default: plan + +permissions: + contents: read + id-token: write + +concurrency: + group: strr-terraform-dev + cancel-in-progress: false + +jobs: + terraform: + name: Terraform dev - ${{ inputs.action }} + runs-on: ubuntu-latest + environment: dev + timeout-minutes: 30 + + defaults: + run: + shell: bash + working-directory: terraform + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v3 + with: + workload_identity_provider: projects/331250273634/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider + service_account: sa-strr-infra@bcrbk9-tools.iam.gserviceaccount.com + + - name: Set up Terraform + uses: hashicorp/setup-terraform@v3 + with: + terraform_version: 1.10.5 + terraform_wrapper: false + + - name: Terraform init + run: terraform init -input=false -lockfile=readonly + + - name: Terraform format check + run: terraform fmt -check -diff -recursive + + - name: Terraform validate + run: terraform validate + + - name: Require main for apply + if: inputs.action == 'apply' && github.ref != 'refs/heads/main' + run: exit 1 + + - name: Terraform plan + run: terraform plan -input=false -lock-timeout=5m -var-file=dev.tfvars -out=tfplan + + - name: Terraform apply + if: inputs.action == 'apply' + run: terraform apply -input=false -auto-approve tfplan diff --git a/.gitignore b/.gitignore index e422c5cce..fcde8b32f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ .DS_Store +# Terraform working directories +.terraform/ + # Logs logs *.log diff --git a/terraform/.terraform.lock.hcl b/terraform/.terraform.lock.hcl new file mode 100644 index 000000000..78b8795f9 --- /dev/null +++ b/terraform/.terraform.lock.hcl @@ -0,0 +1,22 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/google" { + version = "6.50.0" + constraints = "~> 6.0" + hashes = [ + "h1:79CwMTsp3Ud1nOl5hFS5mxQHyT0fGVye7pqpU0PPlHI=", + "zh:1f3513fcfcbf7ca53d667a168c5067a4dd91a4d4cccd19743e248ff31065503c", + "zh:3da7db8fc2c51a77dd958ea8baaa05c29cd7f829bd8941c26e2ea9cb3aadc1e5", + "zh:3e09ac3f6ca8111cbb659d38c251771829f4347ab159a12db195e211c76068bb", + "zh:7bb9e41c568df15ccf1a8946037355eefb4dfb4e35e3b190808bb7c4abae547d", + "zh:81e5d78bdec7778e6d67b5c3544777505db40a826b6eb5abe9b86d4ba396866b", + "zh:8d309d020fb321525883f5c4ea864df3d5942b6087f6656d6d8b3a1377f340fc", + "zh:93e112559655ab95a523193158f4a4ac0f2bfed7eeaa712010b85ebb551d5071", + "zh:d3efe589ffd625b300cef5917c4629513f77e3a7b111c9df65075f76a46a63c7", + "zh:d4a4d672bbef756a870d8f32b35925f8ce2ef4f6bbd5b71a3cb764f1b6c85421", + "zh:e13a86bca299ba8a118e80d5f84fbdd708fe600ecdceea1a13d4919c068379fe", + "zh:f569b65999264a9416862bca5cd2a6177d94ccb0424f3a4ef424428912b9cb3c", + "zh:fec30c095647b583a246c39d557704947195a1b7d41f81e369ba377d997faef6", + ] +} diff --git a/terraform/README.md b/terraform/README.md new file mode 100644 index 000000000..82a4db359 --- /dev/null +++ b/terraform/README.md @@ -0,0 +1,37 @@ +# STRR Terraform + +This configuration is intentionally limited to the `bcrbk9-dev` project for the initial transfer of STRR infrastructure ownership. + +It adopts the existing STRR email Pub/Sub resources and bulk-validation Eventarc trigger into Terraform stored with the application. Eventarc's generated transport topic and subscription remain managed by Eventarc. The application bucket and Cloud Run services are referenced but are not managed by this configuration. + +Terraform state is stored in the platform-managed `strr-tools-terraform-state` GCS bucket with the `strr/dev` prefix. The service account, its IAM, and the state bucket remain managed outside this configuration by `bcgov/bcregistry-sre`. + +## Local verification + +```bash +terraform init +terraform fmt -check -diff -recursive +terraform validate +terraform plan -var-file=dev.tfvars +``` + +The import blocks are for the initial adoption of existing dev resources. The first reviewed plan must contain only the expected imports, with no resources added, changed, or destroyed. + +## GitHub Actions authentication + +The workflow uses keyless Workload Identity Federation with: + +- Provider: `projects/331250273634/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider` +- Service account: `sa-strr-infra@bcrbk9-tools.iam.gserviceaccount.com` +- GitHub environment: `dev` + +These resource identifiers are not secrets. SRE-managed IAM restricts the GitHub `dev` environment identity that can impersonate the service account. The service account can write Terraform state and act as the existing Eventarc and Pub/Sub runtime service accounts. + +## Initial adoption + +1. Run the workflow with `action: plan`. +2. Confirm that the plan contains only the expected imports and reports `0 to add, 0 to change, 0 to destroy`. +3. Run the workflow from `main` with `action: apply` to record the imports in Terraform state. +4. Run `action: plan` again and confirm that Terraform reports no changes. + +Changes that add missing environment-parity resources should be reviewed separately from this no-change adoption. diff --git a/terraform/data.tf b/terraform/data.tf new file mode 100644 index 000000000..6c11c0e5d --- /dev/null +++ b/terraform/data.tf @@ -0,0 +1,7 @@ +data "google_project" "current" { + project_id = var.project_id +} + +locals { + pubsub_service_agent = "service-${data.google_project.current.number}@gcp-sa-pubsub.iam.gserviceaccount.com" +} diff --git a/terraform/dev.tfvars b/terraform/dev.tfvars new file mode 100644 index 000000000..7273e6f49 --- /dev/null +++ b/terraform/dev.tfvars @@ -0,0 +1,6 @@ +project_id = "bcrbk9-dev" +region = "northamerica-northeast1" +bulk_validation_requests_bucket = "strr_bulk_validation_requests_dev" +bulk_validation_listener_service = "batch-permit-listener-dev" +eventarc_service_account = "sa-eventarc@bcrbk9-dev.iam.gserviceaccount.com" +email_push_service_account = "sa-pubsub@bcrbk9-dev.iam.gserviceaccount.com" diff --git a/terraform/eventarc.tf b/terraform/eventarc.tf new file mode 100644 index 000000000..a615da5b6 --- /dev/null +++ b/terraform/eventarc.tf @@ -0,0 +1,31 @@ +resource "google_eventarc_trigger" "bulk_permit_validation" { + name = "bulk-permit-validation-trigger" + location = var.region + project = var.project_id + + event_data_content_type = "application/json" + service_account = var.eventarc_service_account + + matching_criteria { + attribute = "type" + value = "google.cloud.storage.object.v1.finalized" + } + + matching_criteria { + attribute = "bucket" + value = var.bulk_validation_requests_bucket + } + + destination { + cloud_run_service { + service = var.bulk_validation_listener_service + region = var.region + path = "/" + } + } +} + +import { + to = google_eventarc_trigger.bulk_permit_validation + id = "projects/bcrbk9-dev/locations/northamerica-northeast1/triggers/bulk-permit-validation-trigger" +} diff --git a/terraform/pubsub.tf b/terraform/pubsub.tf new file mode 100644 index 000000000..7e892c4ea --- /dev/null +++ b/terraform/pubsub.tf @@ -0,0 +1,109 @@ +resource "google_pubsub_topic" "emailer" { + name = "strr-emailer-dev" + project = var.project_id +} + +resource "google_pubsub_topic" "emailer_dlq" { + name = "strr-emailer-dlq-dev" + project = var.project_id +} + +resource "google_pubsub_topic" "bulk_validation_response" { + name = "strr-bulk-validation-response-dev" + project = var.project_id +} + +resource "google_pubsub_subscription" "emailer" { + name = "strr-emailer-sub-dev" + project = var.project_id + topic = google_pubsub_topic.emailer.id + + ack_deadline_seconds = 10 + message_retention_duration = "604800s" + retain_acked_messages = false + + expiration_policy { + ttl = "" + } + + dead_letter_policy { + dead_letter_topic = google_pubsub_topic.emailer_dlq.id + max_delivery_attempts = 5 + } + + push_config { + push_endpoint = "https://strr-email-dev-i2rbretwta-nn.a.run.app" + + oidc_token { + service_account_email = var.email_push_service_account + } + } + + retry_policy { + minimum_backoff = "10s" + maximum_backoff = "600s" + } +} + +resource "google_pubsub_subscription" "emailer_dlq" { + name = "strr-emailer-dlq-sub-dev" + project = var.project_id + topic = google_pubsub_topic.emailer_dlq.id + + ack_deadline_seconds = 60 + message_retention_duration = "604800s" + retain_acked_messages = false + + expiration_policy { + ttl = "" + } +} + +# Pub/Sub needs these permissions for dead-letter forwarding and tracking. +resource "google_pubsub_topic_iam_member" "emailer_dlq_publisher" { + project = var.project_id + topic = google_pubsub_topic.emailer_dlq.name + role = "roles/pubsub.publisher" + member = "serviceAccount:${local.pubsub_service_agent}" +} + +resource "google_pubsub_subscription_iam_member" "emailer_subscriber" { + subscription = google_pubsub_subscription.emailer.id + role = "roles/pubsub.subscriber" + member = "serviceAccount:${local.pubsub_service_agent}" +} + +import { + to = google_pubsub_topic.emailer + id = "projects/bcrbk9-dev/topics/strr-emailer-dev" +} + +import { + to = google_pubsub_topic.emailer_dlq + id = "projects/bcrbk9-dev/topics/strr-emailer-dlq-dev" +} + +import { + to = google_pubsub_topic.bulk_validation_response + id = "projects/bcrbk9-dev/topics/strr-bulk-validation-response-dev" +} + +import { + to = google_pubsub_subscription.emailer + id = "projects/bcrbk9-dev/subscriptions/strr-emailer-sub-dev" +} + +import { + to = google_pubsub_subscription.emailer_dlq + id = "projects/bcrbk9-dev/subscriptions/strr-emailer-dlq-sub-dev" +} + +import { + to = google_pubsub_topic_iam_member.emailer_dlq_publisher + id = "projects/bcrbk9-dev/topics/strr-emailer-dlq-dev roles/pubsub.publisher serviceAccount:service-382361722867@gcp-sa-pubsub.iam.gserviceaccount.com" +} + +import { + to = google_pubsub_subscription_iam_member.emailer_subscriber + id = "projects/bcrbk9-dev/subscriptions/strr-emailer-sub-dev roles/pubsub.subscriber serviceAccount:service-382361722867@gcp-sa-pubsub.iam.gserviceaccount.com" +} diff --git a/terraform/variables.tf b/terraform/variables.tf new file mode 100644 index 000000000..999d01a3f --- /dev/null +++ b/terraform/variables.tf @@ -0,0 +1,35 @@ +variable "project_id" { + description = "The single GCP project managed by this initial dev-only configuration." + type = string + + validation { + condition = var.project_id == "bcrbk9-dev" + error_message = "This initial configuration is intentionally limited to bcrbk9-dev." + } +} + +variable "region" { + description = "Regional location for STRR Eventarc resources." + type = string + default = "northamerica-northeast1" +} + +variable "bulk_validation_requests_bucket" { + description = "GCS bucket that emits finalized-object events for bulk validation." + type = string +} + +variable "bulk_validation_listener_service" { + description = "Cloud Run service receiving the bulk validation Eventarc trigger." + type = string +} + +variable "eventarc_service_account" { + description = "Existing STRR service account used for Eventarc delivery." + type = string +} + +variable "email_push_service_account" { + description = "Existing STRR service account used for authenticated email delivery." + type = string +} diff --git a/terraform/versions.tf b/terraform/versions.tf new file mode 100644 index 000000000..ba3c7804d --- /dev/null +++ b/terraform/versions.tf @@ -0,0 +1,20 @@ +terraform { + required_version = "~> 1.10.5" + + backend "gcs" { + bucket = "strr-tools-terraform-state" + prefix = "strr/dev" + } + + required_providers { + google = { + source = "hashicorp/google" + version = "~> 6.0" + } + } +} + +provider "google" { + project = var.project_id + region = var.region +} From 36300899245284942b0aef5b0680b71c006bc70c Mon Sep 17 00:00:00 2001 From: Jacky-Pham Date: Tue, 25 Aug 2026 14:00:03 -0700 Subject: [PATCH 2/3] chore: pin Terraform workflow actions --- .github/workflows/strr-terraform.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/strr-terraform.yaml b/.github/workflows/strr-terraform.yaml index 3d4e98946..108d842c2 100644 --- a/.github/workflows/strr-terraform.yaml +++ b/.github/workflows/strr-terraform.yaml @@ -34,18 +34,18 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: persist-credentials: false - name: Authenticate to Google Cloud - uses: google-github-actions/auth@v3 + uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3 with: workload_identity_provider: projects/331250273634/locations/global/workloadIdentityPools/github-actions-pool/providers/github-actions-provider service_account: sa-strr-infra@bcrbk9-tools.iam.gserviceaccount.com - name: Set up Terraform - uses: hashicorp/setup-terraform@v3 + uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3 with: terraform_version: 1.10.5 terraform_wrapper: false From 909f655d88e56da4918c67a7291c9611ffae8d1e Mon Sep 17 00:00:00 2001 From: Jacky-Pham Date: Wed, 26 Aug 2026 10:14:45 -0700 Subject: [PATCH 3/3] chore: use setup-terraform v4 --- .github/workflows/strr-terraform.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/strr-terraform.yaml b/.github/workflows/strr-terraform.yaml index 108d842c2..ebd7cfe27 100644 --- a/.github/workflows/strr-terraform.yaml +++ b/.github/workflows/strr-terraform.yaml @@ -45,7 +45,7 @@ jobs: service_account: sa-strr-infra@bcrbk9-tools.iam.gserviceaccount.com - name: Set up Terraform - uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd # v3 + uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4 with: terraform_version: 1.10.5 terraform_wrapper: false