Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions iac/cloudrun-redirect/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Local terraform working dir + provider binaries
.terraform/
# State (may contain secrets) — use remote backend instead
*.tfstate
*.tfstate.*
# Local var overrides
terraform.tfvars
*.auto.tfvars
crash.log
22 changes: 22 additions & 0 deletions iac/cloudrun-redirect/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

58 changes: 58 additions & 0 deletions iac/cloudrun-redirect/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Cloud Run redirect service

Terraform for a Cloud Run service that 301-redirects **every** request to a
canonical host (default `https://metermanager.pvacd.com`), preserving path and
query string. Uses the prebuilt public image `schmunk42/nginx-redirect` — no
image to build or maintain.

This module creates the **service only**. It does not map a custom domain; point
DNS or a Cloud Run domain mapping at the emitted `service_url` yourself.

## Files

| File | Purpose |
|------|---------|
| `versions.tf` | Terraform + google provider constraints |
| `variables.tf` | Inputs (project, region, redirect target, scaling) |
| `main.tf` | `google_cloud_run_v2_service` + public invoker IAM |
| `outputs.tf` | Service name + `run.app` URL |
| `terraform.tfvars.example` | Copy to `terraform.tfvars` |

## Deploy

```sh
gcloud auth application-default login # or set GOOGLE_APPLICATION_CREDENTIALS
gcloud services enable run.googleapis.com --project YOUR_PROJECT

cp terraform.tfvars.example terraform.tfvars # edit project_id
terraform init
terraform apply
```

## Verify

```sh
URL=$(terraform output -raw service_url)
curl -sI "$URL/some/path?q=1"
# expect: HTTP/2 301
# location: https://metermanager.pvacd.com/some/path?q=1
```

## Pointing the old domain at it

`terraform apply` gives a `*.run.app` URL. To send
`pvacd.newmexicowaterdata.org` through it, add a
[Cloud Run domain mapping](https://cloud.google.com/run/docs/mapping-custom-domains)
(needs the domain verified in the project) or front it with an external HTTPS
load balancer. Domain mapping was intentionally left out of this module — add it
here later if you want it managed in Terraform.

## Notes

- Public access: `allow_unauthenticated = true` grants `allUsers` the
`roles/run.invoker` role so browsers can reach the redirect. An org policy
(`iam.allowedPolicyMemberDomains`) may block `allUsers`; if apply fails on the
IAM member, that policy is why.
- Swapping the image: set `image` and pass matching env var names via `env`.
- Pin the image: replace `:latest` in `variables.tf` with a specific tag or
`@sha256:...` digest for reproducible deploys.
52 changes: 52 additions & 0 deletions iac/cloudrun-redirect/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
locals {
# Env vars for schmunk42/nginx-redirect. Override with var.env for a different image.
default_env = {
SERVER_REDIRECT = var.redirect_host
SERVER_REDIRECT_SCHEME = var.redirect_scheme
SERVER_REDIRECT_CODE = tostring(var.redirect_code)
}

container_env = length(var.env) > 0 ? var.env : local.default_env
}

resource "google_cloud_run_v2_service" "redirect" {
name = var.service_name
location = var.region

# Public HTTP endpoint; browsers hit it directly.
ingress = "INGRESS_TRAFFIC_ALL"

template {
scaling {
min_instance_count = var.min_instances
max_instance_count = var.max_instances
}

containers {
image = var.image

ports {
container_port = var.container_port
}

dynamic "env" {
for_each = local.container_env
content {
name = env.key
value = env.value
}
}
}
}
}

# Allow public (unauthenticated) access so the redirect is reachable.
resource "google_cloud_run_v2_service_iam_member" "public" {
count = var.allow_unauthenticated ? 1 : 0

project = google_cloud_run_v2_service.redirect.project
location = google_cloud_run_v2_service.redirect.location
name = google_cloud_run_v2_service.redirect.name
role = "roles/run.invoker"
member = "allUsers"
}
9 changes: 9 additions & 0 deletions iac/cloudrun-redirect/outputs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
output "service_name" {
description = "Cloud Run service name."
value = google_cloud_run_v2_service.redirect.name
}

output "service_url" {
description = "Auto-assigned run.app URL of the redirect service. Point DNS (CNAME / domain mapping) at this, or verify redirect here directly."
value = google_cloud_run_v2_service.redirect.uri
}
8 changes: 8 additions & 0 deletions iac/cloudrun-redirect/terraform.tfvars.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Copy to terraform.tfvars and fill in.
project_id = "your-gcp-project-id"
region = "us-central1"

# Everything hitting this service 301s here, path + query preserved.
redirect_host = "metermanager.pvacd.com"
redirect_scheme = "https"
redirect_code = 301
79 changes: 79 additions & 0 deletions iac/cloudrun-redirect/variables.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
variable "project_id" {
type = string
description = "GCP project ID to deploy the redirect service into."
}

variable "region" {
type = string
description = "Cloud Run region."
default = "us-central1"
}

variable "service_name" {
type = string
description = "Name of the Cloud Run service."
default = "metermanager-redirect"
}

variable "image" {
type = string
description = <<-EOT
Prebuilt redirect container image. Cloud Run supports images from
Artifact Registry, Container Registry, and public Docker Hub.
Default is schmunk42/nginx-redirect, which 301s $scheme://$host$request_uri
(path + query preserved) and listens on port 80.
EOT
default = "docker.io/schmunk42/nginx-redirect:latest"
}

variable "container_port" {
type = number
description = "Port the redirect image listens on (schmunk42/nginx-redirect uses 80)."
default = 80
}

variable "redirect_host" {
type = string
description = "Canonical host to redirect every request to."
default = "metermanager.pvacd.com"
}

variable "redirect_scheme" {
type = string
description = "Scheme of the redirect target."
default = "https"
}

variable "redirect_code" {
type = number
description = "HTTP redirect status code (301 permanent / 302 temporary)."
default = 301
}

variable "env" {
type = map(string)
description = <<-EOT
Optional override of container env vars. When empty, env vars are derived
from redirect_host/redirect_scheme/redirect_code for schmunk42/nginx-redirect.
Set this if you swap `image` for one expecting different env var names.
EOT
default = {}
}

variable "allow_unauthenticated" {
type = bool
description = "Grant allUsers roles/run.invoker so browsers can reach the redirect. Required for a public redirect."
default = true
}

variable "min_instances" {
type = number
description = "Minimum Cloud Run instances (0 = scale to zero)."
default = 0
}

variable "max_instances" {
type = number
description = "Maximum Cloud Run instances."
default = 2
}
15 changes: 15 additions & 0 deletions iac/cloudrun-redirect/versions.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
terraform {
required_version = ">= 1.5"

required_providers {
google = {
source = "hashicorp/google"
version = ">= 5.0, < 7.0"
}
}
}

provider "google" {
project = var.project_id
region = var.region
}
Loading