PlatformEngr® Terraform course

Cheat notes · Module 09 · Safe change

Capstone: onboard team-payments on your own

A five-minute review for when you have done this module and want it back in your head. Open the full module for the steps and the lab, or the full text to print.

The idea

Onboarding a team that needs something new means extending the module with an optional input whose default keeps today's behaviour, proving it with a test and an empty plan for every existing team, then adding the calls dev first. Retiring a team is forgetting in Terraform, then deleting by hand.

Remember

  • extra_viewers = map(string), default {}: existing callers change nothing.
  • for_each = local.viewers with static keys (team, audit); the team's own binding keeps the name team-view.
  • moved { from = kubernetes_role_binding_v1.view to = kubernetes_role_binding_v1.view["team"] } in the module, so every caller gets it.
  • Order: module and test, empty plans, the calls, the import, then prod, in one pull request.
  • removed { from = module.reporting lifecycle { destroy = false } }: out of state, still running; then kubectl delete namespace as a deliberate step.

Pitfalls

  • Adding for_each without moved: every team's binding is destroyed and recreated, prod too.
  • Deleting a team's module block to retire it: prevent_destroy leaves with the block, and the plan deletes the namespace with no error. Use removed with destroy = false.
  • Removing prevent_destroy from the module to retire one team: every team loses the guard.
  • Making the new input required: every caller must change, and the change is no longer additive.

Commands

terraform testFive runs, all pass
kubectl auth can-i list pods -n payments-prod --as=x --as-group=team-finance-audityes; for checkout-prod, no
terraform state listNo module.reporting left

modules/team-namespace/rbac.tf (the view binding)

resource "kubernetes_role_binding_v1" "view" {
  for_each = local.viewers

  metadata {
    name      = each.key == "team" ? "team-view" : "view-${each.key}"
    namespace = kubernetes_namespace_v1.this.metadata[0].name
  }
  role_ref {
    api_group = "rbac.authorization.k8s.io"
    kind      = "ClusterRole"
    name      = "view"
  }
  subject {
    api_group = "rbac.authorization.k8s.io"
    kind      = "Group"
    name      = each.value
  }
}

Test yourself

Answer in your head first, then open the answer.

What is the bar for a module change?

An empty plan (or only has moved to) for every existing caller.

Why are the for_each keys team and audit, not the group names?

Keys are part of the address; names you write stay stable and known at plan time.

What does removed with destroy = false do on apply?

Takes the objects out of state and leaves them running.

Where module 09 leaves you: payments-dev and payments-prod have the full kit plus a second read-only binding for team-finance-audit. No other team's objects were recreated. The module has an extra_viewers input, a moved block and five tests. docs/onboarding.md says how to do it again. team-reporting is out of Terraform's state and its namespace is gone.