The Iteration Pattern
The
origin_resourceFor-Each Loop and Anatomy of a Configuration File
The most important concept for writing model files is rescile’s implicit iteration pattern. Think of each
origin_resource block as a declarative loop.
The Golden Rule: A block beginning with
origin_resource = "application"is afor-eachloop. It tellsrescileto run the following rules once for everyapplicationin your graph, until another column-zeroorigin_resourcedeclaration starts a new block.
You don’t write imperative loops — you declare a set of rules that operate on a collection of resources. rescile
handles the iteration automatically.
How rescile parses model TOML
Model files support more than one origin block. Before TOML deserialization, rescile scans the raw file and splits
it at every line matching ^origin_resource\s*=. Each chunk is then parsed independently as a model definition.
origin_resource = "application"
[[create_resource]]
resource_type = "server"
relation_type = "RUNS_ON"
name = "server-{{ origin_resource.name }}"
[create_resource.properties]
environment = "{{ origin_resource.environment }}"
origin_resource = "server"
[[link_resources]]
with = "subscription"
join = { local = "subscription_name", remote = "name" }
create_relation = { type = "BELONGS_TO" }
This file produces two model definitions: the first runs with application; the second runs with server.
A generic TOML parser applied to the complete file gives a different and misleading structure. Under standard
whole-file TOML table rules, the second declaration appears below create_resource[0].properties. Rescile does not
execute that whole-file parse tree because it splits the file first.
Requirements:
- Every intended model
origin_resourcedeclaration must begin at column zero. - Do not indent the declaration. Leading spaces or tabs prevent rescile from recognizing a new chunk.
- Do not infer execution scope from a generic whole-file TOML parse tree.
- Use
rescile-ce validate --strict --build, debug-build output, and saved-graph assertions to verify actual origins and relations.
Using one origin per file remains a reasonable readability convention, but it is not required for correct execution. Split files when clearer ownership is useful, not because multiple recognized origin blocks are invalid.
Declarative vs. Imperative
The declarative model file below is equivalent to the imperative pseudo-code that follows it.
# data/models/server.toml
origin_resource = "application"
[[create_resource]]
resource_type = "server"
relation_type = "HOSTED_ON"
name = "{{ origin_resource.name }}_server"
[[create_resource]]
match_on = [{ property = "environment", value = "prod" }]
resource_type = "monitoring_agent"
name = "agent_for_{{ origin_resource.name }}"
all_applications = graph.find_resources(type="application")
for current_application in all_applications:
create_server_for(current_application)
if current_application.environment == "prod":
create_monitoring_agent_for(current_application)
Data-Driven Iteration with create_from
As an alternative to iterating over graph resources, rescile supports a data-driven iteration model using the
create_from directive. This lets you generate resources from an abstract list defined in the file header.
# data/models/providers.toml
# No origin_resource is defined.
providers = { "json!" = "shared/providers.json" }
provider_list = { "template!" = '''{{ providers | map(attribute="name") | safe }}''' }
[[create_resource]]
create_from = { list = "provider_list", as = "provider" }
name = "ext-{{ value }}"
With a globally resolved header list, origin_resource is unavailable and the special value variable holds the
current list item. A templated json! list that transitively depends on origin_resource instead creates a nested
loop where both variables are available. See Templating and Data Manipulation.
Anatomy of a Model
A single-origin model commonly has a Header and one or more Rule Blocks. A multi-origin model repeats the origin header and rule blocks for each chunk. File-scoped data can be shared across those chunks.
# ── HEADER ──────────────────────────────────────────────────────────────
origin_resource = "application" # The for-each subject
# File-scoped data available to all Rule Blocks
server_specs = { standard_cpu = 4, premium_cpu = 8 }
ip_ranges = { "json!" = "shared/ip_ranges.json" }
# ── RULE BLOCK 1 ─────────────────────────────────────────────────────────
[[create_resource]]
resource_type = "server"
relation_type = "HOSTS"
name = "server_{{ origin_resource.name }}"
[create_resource.properties]
cpu_cores = "{{ server_specs.standard_cpu }}"
status = "provisioning"
# ── RULE BLOCK 2 ─────────────────────────────────────────────────────────
[[create_resource]]
match_on = [{ property = "environment", value = "prod" }]
resource_type = "monitoring_agent"
name = "agent_{{ origin_resource.name }}"
Header and origin scope
| Key | Description |
|---|---|
origin_resource |
Optional. The resource type this chunk iterates over. A new column-zero declaration starts another chunk. If omitted, rules run once in a global context (useful for singletons or create_from generation). |
| Any other key | File-scoped data available as template variables. May be an inline TOML table, a { "json!" = "..." } reference, or a { "template!" = "..." } or { "function!" = "..." } snippet. |
When
origin_resourceis omitted, rules are processed exactly once unlesscreate_fromwith alistis used. Theorigin_resourcevariable is not available in templates.
Rule Blocks and Directives
A Rule Block is a TOML array-of-tables entry such as [[create_resource]] or [[link_resources]]. Think of
each block as a single executable function that rescile evaluates for every iteration of the loop.
Inside a Rule Block, Directives are the key-value pairs that configure the function — for example
resource_type, name, relation_type, and match_on.
Global Context: Module Parameters
In addition to the file-scoped data defined in the header, any module parameters passed via --module-params
on the command line are available as global variables in every template across all files in the module.
rescile-ce --module ./my-module --module-params "region=eu-central-1;env=prod" serve
Inside any template: {{ region }} renders "eu-central-1" and {{ env }} renders "prod".
For the complete reference on all available template data and functions, see Tera Essentials.