Wiring
Wiring is how outputs from one step flow into the inputs of the next, and how Orchard decides the order to execute steps in. You never write a graph by hand — you write references in expressions, and Orchard derives the graph from them.
References
Four reference namespaces are in scope inside expressions:
| Form | Resolves to | Available when |
|---|---|---|
var.<name> | A scenario or component variable. | Plan time — variables are resolved up front. |
action.<type>.<name>.<output> | An output from another action. | After that action completes. |
component.<type>.<name>.<output> | An output from a component instance. | After that component resolves. |
var.<name> (inside a component) | The component’s own variable. | As soon as the component runs. |
References can appear inside any attribute expression — in action bodies, provider configs (variables only), output values, or component inputs.
Implicit dependencies
When you reference action.postgres_query.create_merchant.id inside another action’s body,
Orchard:
- Parses the expression.
- Extracts the traversal — “this block depends on
action.postgres_query.create_merchant”. - Adds an edge to the dependency graph.
- Executes
create_merchantfirst, captures its output, and evaluates the dependent expression with the value in scope.
You never declare this edge explicitly. This is the primary way dependencies are expressed in Orchard: through data references. If you find yourself writing a lot of explicit ordering, consider whether your actions should be passing data instead.
Explicit dependencies
Sometimes two actions are ordered by side effect rather than data — an HTTP call
that must happen before a SQL insert, with nothing flowing between them. Use
depends_on:
action "http_request" "provision_tenant" {
url = "/provision"
method = "POST"
body = "..."
}
action "postgres_query" "seed_tenant_data" {
depends_on = [action.http_request.provision_tenant]
query = "INSERT INTO tenant_settings ..."
}depends_on takes a list of references to other nodes. It adds graph edges
without pulling any data into scope.
Repetition
count and for_each create several instances of an action or component from
one block.
action "postgres_query" "payment" {
count = 3
query = "INSERT INTO payments (status) VALUES ('failed') RETURNING id"
}
component "merchant" "site" {
source = "./components/merchant.hcl"
for_each = { eu = "Europe", us = "America" }
inputs = { name = "${each.key} (${each.value})" }
}countexposescount.index(0-based).for_eachtakes a map, object, or list/set of strings and exposeseach.keyandeach.value(for a list, both are the element).- Reference one instance by index or key:
action.postgres_query.payment[0].id,component.merchant.site["eu"].id. Use a splat for all of them:action.postgres_query.payment[*].id. - Anything that references the block waits for every instance.
countandfor_eachmust be known before anything runs, so they may only referencevar.*, literals, and functions — not action or component outputs.- An action can’t reference its own instances, and
count/for_eachcan’t be set on actions inside a component file. Repeat the component instead. count = 0creates nothing. Referencing a block with zero instances is an error.
Instances appear in plans and run records as payment[0], site["eu"].
Teardown blocks can use count.index / each.* too.
Dependency resolution
Orchard builds a directed acyclic graph of all actions and components in the
scenario and runs each node once everything it depends on has completed. By
default one action runs at a time, in a stable order. orchard run --parallelism N runs up to N independent actions at once; an action still
never starts before the actions it references or lists in depends_on. If an
action fails, nothing new starts, but actions already running finish and are
recorded, so orchard teardown can undo them. Actions that touch the same
data without referencing each other should use depends_on when run in
parallel; otherwise the order they change it in is not guaranteed.
Cycles are detected at plan time and reported as errors — you cannot have two actions that depend on each other.
Output capture
Each provider decides what the outputs of its action look like. For example:
- The
postgres_queryaction returns the first row of the result set as an object, keyed by column name. - The
http_requestaction returns an object withstatus,body, andheaders. - The
exec_runaction returns an object withstdout,stderr, andexit_code.
Downstream expressions access these as ordinary HCL attribute traversals:
action "postgres_query" "next" {
query = "SELECT * FROM users WHERE id = ${action.postgres_query.previous.user_id}"
}See the provider reference for each provider’s exact output shape.
Passing values into SQL
Interpolating a value into a query works for IDs and other trusted values,
but a string with a quote in it breaks the SQL. To pass any value safely,
bind it with params: the _query actions send each value separately
from the SQL, to the database’s own placeholders ($1, $2 on Postgres,
? on MySQL and SQLite):
action "postgres_query" "customer" {
query = "INSERT INTO customers (name, notes) VALUES ($1, $2) RETURNING id"
params = [fake("name"), var.notes] # null binds as NULL
}A query with params must be a single statement. For inserts, updates and
deletes, the typed actions bind
values for you and check them against the schema at plan time.
Advanced action attributes
Actions support a few attributes that are handled by the engine rather than passed to the provider:
provider = "name"— explicitly route the action to a named provider.depends_on = [ ... ]— add explicit dependency edges.count/for_each— create several instances. See Repetition.timeout = "30s"— wrap the provider’s execute call in a Go-duration timeout. Exceeding the timeout fails the action.retry { }— retry the action on failure with backoff. See HCL schema reference.