All jobs

Review Terraform Pull Requests for a GKE Module

Yes. An agent runs terraform fmt, validate and plan on your GKE module's examples and comments on each resource the change would replace, destroy or expose.

Run this on RushIn a Project that has your repository.

What the agent does

The agent checks out the pull request's branch in its own sandbox and maps the change to the configurations that use it. In a module like terraform-google-modules/terraform-google-kubernetes-engine, the root module is split across cluster.tf, networks.tf, firewall.tf, sa.tf, dns.tf, masq.tf and moved.tf, modules/ holds 20 submodules such as private-cluster, beta-private-cluster-update-variant, safer-cluster and gke-node-pool, and examples/ holds 48 configurations from simple_regional to shared_vpc. versions.tf requires Terraform >=1.3 and the google provider >= 7.39.0, < 9, and the agent installs a Terraform release that satisfies it.

Before planning, it checks the repository's own rule about where changes belong. CONTRIBUTING.md says the root module and most submodules are generated from autogen/ by make build, and autogen/main/ holds cluster.tf.tmpl, main.tf.tmpl and the rest. A pull request that edits modules/private-cluster/cluster.tf by hand without the matching template change will be overwritten by the next make build, so the agent regenerates the files with make build, which the Makefile runs through its docker_generate_modules and docker_generate_docs targets, and comments on any file whose generated output differs from what the pull request committed.

Then it runs the checks. terraform fmt -check -recursive over the whole tree, then terraform init -backend=false and terraform validate in every example that uses a changed file. The examples point at the published module, for instance examples/simple_regional/main.tf has source = "terraform-google-modules/kubernetes-engine/google" with version = "~> 45.0", so in its sandbox copy the agent repoints source to the local path, never committing that edit, to plan the pull request's code rather than the registry's. terraform plan needs read-only Google credentials because the module reads google_compute_zones, google_container_engine_versions and google_compute_subnetwork; with them it plans the base commit and the branch and compares the two.

With both plans in hand, it lists every resource whose action is replace or delete. On a GKE cluster that matters more than anywhere: a change to a field like the cluster's location, its private cluster config or a node pool's machine type can replace the cluster or the pool. It checks that a renamed resource came with a moved block, which moved.tf already uses for renames such as kube-dns to kube_dns, that deletion_protection (default true in variables.tf) is not turned off for users, and that a new firewall rule or master_authorized_networks entry does not open 0.0.0.0/0 for ingress. It reads the action as well as the range, because the module's gke-shadow-* rules in firewall.tf use 0.0.0.0/0 for deny rules, which narrow access.

Start it with one API call

POST https://api.prix.dev/o/{org}/sessions with this body:

{
  "agent": "claude",
  "input": "Review the open pull request. Run terraform fmt -check -recursive, then make build and report any generated file that differs from what the pull request committed. For every example under examples/ that uses the changed files, point its module source at the local path in the sandbox only, run terraform init -backend=false and terraform validate, and run terraform plan with a local backend on the base commit and on this branch using the read-only credentials in GOOGLE_CREDENTIALS. Comment with each resource that would be replaced or destroyed, each rename without a moved block, any change to deletion_protection, and any new public exposure such as an ingress rule or authorized network open to 0.0.0.0/0. Do not push commits and never run terraform apply.",
  "resources": [
    {
      "url": "https://github.com/your-org/terraform-google-kubernetes-engine/pull/123"
    }
  ]
}
  1. Get an API key. In the console, open Settings, then API keys, and create a key for your org. Send it in the Authorization header as a Bearer token.
  2. Send the request. POST the body in request.json to https://api.prix.dev/o/{org}/sessions, with your repository as the resources URL. The run starts in its own cloud sandbox.
  3. Read what came back. Follow the run on its live stream or wait for the run.completed webhook, then read the result and its trajectory.

What comes back

What comes back is a review on the pull request. Each comment sits on the changed line in a .tf file or autogen/ template and names the resource address the plan shows (module.gke.google_container_cluster.primary, module.gke.google_container_node_pool.pools["default-node-pool"]), the action (update in place, replace, destroy), the attribute that forces it, and which example's plan it came from. Renames without a moved block and generated files out of step with autogen/ get their own comments with the fix.

The session keeps the fmt, make build, validate and both plan outputs per example, so a reviewer can read the exact plan lines a replacement comment quotes. If no credentials were set, the review says plan did not run and reports only what fmt and validate found, rather than implying the change is safe.

Before merging, a maintainer decides whether each replacement is acceptable for a module that many clusters depend on, which usually means a major version and an upgrade note in CHANGELOG.md, and checks that any new exposure is opt-in through a variable that defaults to closed.

Run it on every Terraform pull request

An automation with a GitHub trigger on pull_request.opened and pull_request.synchronize runs this review on each push. Start it in the Project that holds the GOOGLE_CREDENTIALS secret with POST /o/{org}/p/{project}/sessions, so every review gets the read-only service account without the key ever appearing in a request body.

A module with dozens of examples can take a while to plan, so set a webhook URL on the API key and wait for run.completed rather than holding the event stream open. If the author pushes a fix, a follow-up message to the same session re-plans only the examples that change touched.

The use case this job follows, with its automation and trigger: Review every pull request.

Why run it on Rush?

A Terraform review that does not run plan misses the one thing reviewers most need to know: whether the change replaces a cluster. Planning several examples on two commits takes a clean working directory per example and Google credentials on the reviewer's machine. On Rush each review gets its own sandbox and its own .terraform directories, with only the read-only service account from the Project secret, a local backend, and a prompt that forbids apply.

The comments quote the plan, and the session keeps every command and its output, so a maintainer can check a replacement claim without re-running the plan. Reviews for several pull requests run at the same time without sharing state, and none of them can touch your real state files because they never see the backend.

Frequently asked questions

What credentials does the agent need?

Only for terraform plan, because the module reads data sources such as the available zones and GKE versions. Save a read-only service account key as a Project secret named GOOGLE_CREDENTIALS and start the session in that Project; the agent reads it as an environment variable. Without it, the review runs fmt and validate and says plan was skipped.

Can it change our Terraform state or apply anything?

It uses a local backend with -backend=false for init, so it never reads or writes your remote state, and the prompt forbids terraform apply. A read-only service account makes that a hard limit rather than an instruction.

Why does it run make build?

Because the GKE module generates most of its files from autogen/ templates. A hand edit to a generated file passes validate and then disappears the next time someone runs make build, so the agent regenerates and comments on any difference.

Does this work for our own Terraform, not a public module?

Yes. Point it at your root configurations instead of examples/, and name the backend and variable files in the prompt. The replacement and exposure checks are the same.