Problem
modules/service-repo/environments.tf couples two orthogonal concerns: deploy ordering (deploy_order) and required-reviewer protection rules. Any environment with deploy_order > 0 gets a reviewers block automatically, which:
- Fails with HTTP 422 on private repos that aren't on GitHub Enterprise Cloud — required-reviewer environment protection is EC-only for private repos.
- Conflates intent — sequencing and approval are independent decisions, not the same decision.
Separately, there's no slot in the CD pipeline for verifying a sandbox deploy actually works before production deploy starts.
Proposed changes
1. Decouple require_approval from deploy_order
modules/service-repo/variables.tf:
variable "environments" {
type = map(object({
deploy_order = number
require_approval = optional(bool, false)
}))
}
variable "approval_team_slug" {
type = string
default = null
description = <<-EOT
GitHub team slug whose members can approve deployments to environments
where require_approval = true. Required only if any environment opts in.
Required reviewers on private repos need GitHub Enterprise Cloud.
EOT
}
modules/service-repo/environments.tf:
data "github_team" "approval" {
count = anytrue([for env in var.environments : env.require_approval]) ? 1 : 0
slug = var.approval_team_slug
}
resource "github_repository_environment" "cd" {
for_each = var.environments
environment = "live-${each.key}"
repository = github_repository.this.name
dynamic "reviewers" {
for_each = each.value.require_approval ? [1] : []
content {
teams = [data.github_team.approval[0].id]
}
}
}
Add a validation: if any env sets require_approval = true and approval_team_slug is null, fail with a clear message.
2. Add make smoke contract
modules/service-repo/templates/makefile.mk:
.PHONY: smoke
smoke:
@if [ -x scripts/smoke.sh ]; then \
scripts/smoke.sh "$(ENV)"; \
else \
echo "smoke[$(ENV)]: no scripts/smoke.sh, skipping"; \
fi
CD workflow template — insert after the "Remove Plan" step, inside the env loop:
- name: "Smoke test"
run: |
make smoke ENV=${{ matrix.env }}
Fail-open: missing script logs one line and exits 0. Smoke runs after every env, including production, so post-deploy health regressions surface as workflow failures.
3. Documentation
Module README updates:
- New
require_approval and approval_team_slug inputs with the EC-on-private-repos caveat called out.
scripts/smoke.sh contract: input $1 = env name, exit 0 = healthy, non-zero = halt pipeline.
- Copy-pasteable stub script in the README, not scaffolded into customer repos by Terraform.
Acceptance criteria
Migration
No external customers yet — no migration shim needed. Bump module to next major version: default behavior changes from "implicit reviewers when deploy_order > 0" to "no reviewers unless require_approval = true."
Reference
Original failure on aws-service-infrahouse-app:
PUT https://api.github.com/repos/infrahouse/aws-service-infrahouse-app/environments/live-production: 422
Failed to create the environment protection rule. Please ensure the billing plan
supports the required reviewers protection rule.
GitHub feature gate: deployment protection rules with required reviewers are Enterprise Cloud only for private repos. Pro/Team plans get environments but not required reviewers for private repos.
Problem
modules/service-repo/environments.tfcouples two orthogonal concerns: deploy ordering (deploy_order) and required-reviewer protection rules. Any environment withdeploy_order > 0gets areviewersblock automatically, which:Separately, there's no slot in the CD pipeline for verifying a sandbox deploy actually works before production deploy starts.
Proposed changes
1. Decouple
require_approvalfromdeploy_ordermodules/service-repo/variables.tf:modules/service-repo/environments.tf:Add a validation: if any env sets
require_approval = trueandapproval_team_slugis null, fail with a clear message.2. Add
make smokecontractmodules/service-repo/templates/makefile.mk:CD workflow template — insert after the "Remove Plan" step, inside the env loop:
Fail-open: missing script logs one line and exits 0. Smoke runs after every env, including production, so post-deploy health regressions surface as workflow failures.
3. Documentation
Module README updates:
require_approvalandapproval_team_sluginputs with the EC-on-private-repos caveat called out.scripts/smoke.shcontract: input$1= env name, exit 0 = healthy, non-zero = halt pipeline.Acceptance criteria
require_approvaldefaults to false; envs withdeploy_order > 0no longer get reviewers implicitly.require_approval = trueworks on EC orgs for opted-in envs.require_approval = trueis set withoutapproval_team_slug.make smoke ENV=<env>runsscripts/smoke.sh <env>if present, no-ops with a log line otherwise.make smokeafter each env's apply.live-sandboxhalts the job —live-productiondeploy does not run.aws-service-infrahouse-app) reapplies cleanly withrequire_approval = falseon production, noapproval_team_slugset, no 422.scripts/smoke.shcontract.Migration
No external customers yet — no migration shim needed. Bump module to next major version: default behavior changes from "implicit reviewers when
deploy_order > 0" to "no reviewers unlessrequire_approval = true."Reference
Original failure on
aws-service-infrahouse-app:GitHub feature gate: deployment protection rules with required reviewers are Enterprise Cloud only for private repos. Pro/Team plans get environments but not required reviewers for private repos.