Interpolation Reference

Interpolation is how an Action passes fleet-specific values into the external work it triggers. Every interpolation-aware field accepts {{ ... }} expressions: trigger URLs, headers, auth values, webhook payloads, PR titles and bodies, branch names, file contents, labels, reviewers, and the name/properties of cascade-created entities and relationships. The same syntax resolves identically across all six trigger types (webhook, pull-request, terraform-cloud, atmos-workflow, atlantis, noop) and inside cascade operations.

This is the lookup reference. There is one expression form per resolution root — Terrantula's interpolation is variable substitution, not a function language: there are no filters, pipes, conditionals, or calls. Each root below is documented man-page style: purpose → syntax → example → notes.

How resolution works

A trigger is resolved in two passes, in this order:

  1. Secrets pass. Every {{ secrets.<name> }} is replaced with the named Secret's decrypted value, loaded from the project's encrypted store at dispatch time. This runs first so secret values are in place before anything else looks at the field.
  2. Variable pass. Every remaining {{ ... }} expression — parameters, entity, recommendations, run, operation — is substituted from the run's context.

The expression grammar is intentionally small: a {{, optional whitespace, a dotted path (word characters, dots, and hyphens), optional whitespace, a }}. Anything else is left untouched.

INFO

An unresolved variable renders as its own literal, not an empty string. If a blueprint uses {{ entity.name }} but the run has no entity in context (a collection-scoped Action, say), the field keeps the literal text {{ entity.name }} rather than collapsing to "". This is deliberate: a missing value surfaces loudly in the PR or payload instead of silently vanishing. The one exception is {{ secrets.<name> }} — a missing or value-less secret fails the run (see secretsnotes).

NOTE

Path segments named __proto__, constructor, or prototype never resolve — they short-circuit to the unresolved literal. This prototype-pollution guard applies to every nested-path root (parameters, entity.properties, entity.labels, recommendations.*.properties).


secrets

Purpose. Inject a decrypted Secret value — a TFC API token, a GitHub token, an Atlantis API secret, a webhook HMAC secret — into a trigger's auth field, header, or payload. The catalog only ever names the credential; the value is set out-of-band and never lands in YAML.

Syntax.

{{ secrets.<name> }}

<name> is the Secret's name. Names may contain word characters, dots, hyphens, and colons.

Example.

trigger:
  type: webhook
  url: https://workflows.example.com/onboard
  auth:
    type: bearer
    token: "{{ secrets.onboarding-webhook-token }}"

Notes.

  • Resolved in the secrets pass, before all other expressions — so a secret value can itself contain text that later passes ignore.
  • Resolution is project-scoped: only Secrets belonging to the firing project are visible. Values are env-scoped — the same name can hold a different value per environment, and only the running environment's value is decrypted. See the Configuration Reference for the env model.
  • A missing secret, or one whose value was never set, throws and fails the run. This differs from every other root, which renders the literal on a miss. Set values with terrantula secrets set-value <name> --value ….
  • Secret values are decrypted into the trigger at dispatch and never logged or echoed back by the API. Reference credentials by name only — never paste a value into a blueprint, PR body, or log line.

parameters

Purpose. Read an input collected when the Action was triggered. Parameters are declared on the Action's parameters field and supplied per run (by the operator in the UI/CLI, or by a cascade rule).

Syntax.

{{ parameters.<path> }}

<path> may be nested (parameters.network.cidr) for object-valued parameters.

Example.

operation:
  type: create-entity
  entityType: Tenant
  properties:
    customer_id: "{{ parameters.customer_id }}"
trigger:
  type: pull-request
  repo: my-org/infrastructure
  reviewers:
    - "{{ parameters.approver }}"

Notes.

  • Always available when the run carries parameters. A reference to a parameter the run didn't supply renders the literal.
  • Nested paths are guarded against prototype pollution (see above).

entity

Purpose. Reference the entity the Action is firing against — its name, declared properties, and labels. Available on instance-scoped Actions (associatedWith.scope: instance) and on cascade operations, where it refers to the entity the cascade is acting on (for a create-entity operation, the just-created entity).

Syntax.

{{ entity.name }} {{ entity.properties.<path> }} {{ entity.labels.<path> }} {{ entity.id }} # deprecated — see notes

Example.

trigger:
  type: pull-request
  repo: my-org/infrastructure
  title: "Onboard {{ entity.properties.companyName }}"
  head: "terrantula/onboard-{{ entity.name }}"
  files:
    - path: "tenants/{{ entity.name }}.yaml"
      content: |
        apiVersion: v1
        kind: Tenant
        metadata:
          name: "{{ entity.properties.companyName }}"
          tier: "{{ entity.labels.tier }}"

Notes.

  • Only populated for instance-scoped Actions and cascade operations. On a collection-scoped Action there is no entity in context, so entity.* references render the literal.
  • {{ entity.id }} is deprecated: entities are now keyed by name, not a UUID. Prefer {{ entity.name }} for new blueprints.
  • entity.properties.* and entity.labels.* accept nested paths and are prototype-pollution-guarded.

recommendations

Purpose. Read a cell-ranked placement suggestion surfaced at trigger time. When an Action declares recommendations, Terrantula ranks candidate Cells and exposes the chosen target so the trigger can wire the entity to the right pen — the core of the tenant-placement cattle workflow.

Syntax.

{{ recommendations.<name>.name }} {{ recommendations.<name>.properties.<path> }} {{ recommendations.<name>.id }} # deprecated — see notes

<name> is the recommendation's declared name.

Example.

trigger:
  type: webhook
  url: https://workflows.example.com/onboard
  payload:
    targetCluster: "{{ recommendations.targetCluster.name }}"
    region: "{{ recommendations.targetCluster.properties.region }}"

Notes.

  • Available only when the Action declares recommendations and the run resolved one for <name>. An unresolved recommendation renders the literal.
  • {{ recommendations.<name>.id }} is deprecated for the same reason as entity.id; prefer .name.
  • .properties.* accepts nested paths and is prototype-pollution-guarded.

run

Purpose. Reference the firing ActionRun itself — its identifier and the callback channel an external workflow uses to report completion back to Terrantula.

Syntax.

{{ run.id }} {{ run.callbackUrl }} {{ run.callbackToken }}

Example.

trigger:
  type: webhook
  url: https://workflows.example.com/onboard
  payload:
    callbackUrl: "{{ run.callbackUrl }}"
    callbackToken: "{{ run.callbackToken }}"
  body: |
    Triggered by Terrantula ActionRun {{ run.id }}.

Notes.

  • {{ run.id }} — the ActionRun UUID. Always safe to embed in PR bodies, commit messages, or logs: it identifies a run for human reference but cannot authorize anything on its own.
  • {{ run.callbackUrl }} — the endpoint your external workflow POSTs to when it finishes, so Terrantula can mark the run succeeded or failed.
  • {{ run.callbackToken }} — the bearer token your workflow presents to that endpoint. Format: {runId}:{expiresAtMs}:{hmac}. The HMAC-SHA256 signature is computed with a key derived from the server encryption key and binds the token to this run and this expiry (the Action's timeout, default 60 minutes). The callback endpoint verifies it in constant time and guards on run status, so a token issued for one run cannot be replayed against another. Because the binding — not the string's secrecy — is what protects it, the token is safe to embed in a PR body or payload.

operation

Purpose. Inside a cascade createRelationship on a create-entity operation, reference the entity the parent operation just created. This lets one Action create an entity and wire its relationship atomically, without a second call.

Syntax.

{{ operation.entity.id }}

Example.

operation:
  type: create-entity
  entityType: Tenant
  properties:
    customer_id: "{{ parameters.customer_id }}"
  createRelationship:
    relationshipType: runs_on
    to: "{{ recommendations.target-cluster.name }}"
    properties:
      namespace: "tenant-{{ operation.entity.id }}"

Notes.

  • Valid only inside a createRelationship block on a create-entity operation. Anywhere else it renders the literal.
  • It resolves to the id assigned to the entity the parent operation just created, so createRelationship can point at it before the entity exists by any other reference.

Quick reference

ExpressionAvailable whenPassMiss behavior
{{ secrets.<name> }}alwayssecretsfails the run
{{ parameters.<path> }}run supplied the parametervariablerenders literal
{{ entity.name }} / .properties.<p> / .labels.<p>instance scope or cascade operationvariablerenders literal
{{ entity.id }}(deprecated — use entity.name)variablerenders literal
{{ recommendations.<name>.name }} / .properties.<p>Action declares recommendationsvariablerenders literal
{{ recommendations.<name>.id }}(deprecated — use .name)variablerenders literal
{{ run.id }} / {{ run.callbackUrl }} / {{ run.callbackToken }}alwaysvariablerenders literal
{{ operation.entity.id }}createRelationship on a create-entity opvariablerenders literal

Worked example: a pull-request trigger

A single instance-scoped Action wiring every root together. Terrantula opens this PR; your CI applies it — Terrantula never runs terraform apply itself.

trigger:
  type: pull-request
  repo: my-org/infrastructure
  auth:
    type: token
    token: "{{ secrets.github-token }}"
  title: "Onboard {{ entity.properties.companyName }} → {{ recommendations.targetCluster.name }}"
  body: |
    Triggered by Terrantula ActionRun {{ run.id }}.

    Tenant: {{ entity.properties.companyName }}
    Target cluster: {{ recommendations.targetCluster.name }}
    Region: {{ recommendations.targetCluster.properties.region }}
  head: "terrantula/onboard-{{ entity.name }}"
  base: main
  reviewers:
    - "{{ parameters.approver }}"
  files:
    - path: "tenants/{{ entity.name }}.yaml"
      content: |
        apiVersion: v1
        kind: Tenant
        metadata:
          name: "{{ entity.properties.companyName }}"
          cluster: "{{ recommendations.targetCluster.name }}"
  webhookSecret: "{{ secrets.github-webhook-secret }}"