You have the static model: entity types, a cell that decides placement, and a relationship that connects tenants to clusters. The last piece is the verb — a way to grow the fleet. That's an Action, and this is where Terrantula stops being a read-only catalog and starts doing things, always within the rules you set.
In this step you'll wire OnboardTenant: a single declaration that takes operator input, picks the least-loaded cluster, creates the tenant and its runs_on link, and — crucially — opens a pull request against your repository. Terrantula never runs terraform apply. Your CI does, on merge.
An Action bundles five decisions into one document:
parameters — the inputs the operator provides.recommendations — a placement query against a cell. This is where least-loaded gets invoked.operation — what changes in the graph, with explicit states for each phase of the run.trigger — how the change reaches your real infrastructure. This is the part that opens a PR.conditions and dependsOn — guards and ordering.Add this fifth document to your blueprint.yaml. It's the biggest one, because it ties everything together — read the comments as you go:
associatedWith + scope: collection — collection scope means this Action appears at the entity-type level ("onboard a new tenant"). instance scope would attach it to each existing tenant ("suspend this tenant").recommendations — cell: prod-clusters, sortBy: tenant-count, order: asc resolves to "the least-loaded cluster in the fleet." The chosen cluster is then available as {{ recommendations.target-cluster.* }} throughout the document. This is the placement decision Terraform never made.operation — declares the graph change and the entity's state at each phase. onTrigger: provisioning is where the tenant sits while the PR is open; onSuccess: active is where it lands once the change applies. The createRelationship block creates the runs_on link in the same operation, putting the per-tenant namespace on the relationship.{{ ... }} interpolation — values flow from parameters (operator input), recommendations (the placement result), and secrets (the credential) into both the operation and the trigger.trigger: pull-request — commits tenants/<customer_id>.tfvars.json to a branch and opens a GitHub PR. That's the whole change it makes to your infrastructure: a file and a PR.This is the most important property of the whole system, so it's worth stating plainly:
terraform apply
There is no terraform apply anywhere in this Action. The pull-request trigger commits files and opens a PR. When a reviewer merges it, your existing GitHub Actions workflow runs the apply. Terrantula validates parameters, evaluates conditions, enforces the per-cluster and cell constraints, and runs placement — all before the PR opens — then tracks the outcome and transitions the tenant to activewhen the change lands. Your runner and state never leave your control.
This is why the operation declares onTrigger, onSuccess, and onFailure states: the tenant is provisioning while the PR is open, becomes active when the merge applies cleanly, and goes to failed if the apply fails — an honest, recoverable lifecycle that mirrors what your CI actually did.
The Action references {{ secrets.github-token }} for the PR, so declare the secret (add it to blueprint.yaml) and set its value out-of-band:
The value is encrypted at rest and never appears in your catalog, version control, or logs — you only ever reference it by name.
Notice the operation, recommendations, and placement logic say nothing about how the change ships. Swap the trigger block for type: terraform-cloud and the same Action fires a TFC run instead; type: atlantis opens a PR your Atlantis instance picks up; type: atmos-workflow invokes an Atmos workflow on your runner. The trigger is the only thing that knows about your runner — Terrantula sits on top of whatever you already run. See the Triggers Referencefor each one.
OnboardTenant is a collection-scope Action, so it's available at the Tenant type level — operators run it from the dashboard's Action surface (or via the API / SDK). Each firing creates an ActionRun: a record of who fired what, with which parameters, and how it ended. That's your audit trail. The run opens the PR; when it merges and your CI applies, Terrantula completes the run and transitions the tenant to active.
OnboardTenant, the Action that grows your fleet: parameters → placement → graph change → PR.github-token secret the trigger needs, by reference only.That completes the fleet: you can now see it, place into it, connect it, and grow it through reviewable PRs. The last page points you at where to go next.
Prev: ← Step 3 — Add relationships · Next: Next Steps →