Templating and Data
Using Dynamic Data and Context
rescile provides a rich context of data that you can access within your templates ({{ ... }}). This allows for highly dynamic and data-driven configurations. The data is available from three primary sources:
The origin_resource
As explained in the core iteration pattern, the origin_resource variable is available and refers to the specific resource being processed in the loop. This is the most common data source. Whenever you use Tera templating (e.g., {{ ... }}), the data is always evaluated in the context of this resource.
The special origin_resource variable gives you access to the properties of that resource. This allows you to create dynamic values using expressions, filters, and conditional logic.
# In a model with origin_resource = "application"
[[create_resource]]
resource_type = "server"
# Accesses a property from the current 'application' resource
name = "server_for_{{ origin_resource.name }}"
[create_resource.properties]
# Access a property from the resource being processed and apply a filter
hostname = "{{ origin_resource.name | upper }}" # Renders as "BILLING-API"
# Use conditional logic
tier = "{% if origin_resource.environment == 'prod' %}premium{% else %}standard{% endif %}"
When rescile is processing the application named billing-api, the template {{ origin_resource.name }} will render as billing-api. This ensures that the rules you define are applied correctly and uniquely for every resource they operate on.
The Iteration Variable (from create_from)
When using the data-driven create_from directive to iterate over a list, the origin_resource is not available. Instead, the context is populated with a special value variable. This variable holds the current item from the data source being iterated over.
This is the primary way to access data when bootstrapping resources from abstract lists.
# In a model without `origin_resource`.
provider_list = ["aws", "azure", "gcp"]
[[create_resource]]
# Iterate over `provider_list` and put each item in the `value` variable.
create_from = { list = "provider_list", as = "provider" }
name = "cloud-{{ value }}"
[create_resource.properties]
# Access the iteration variable.
short_name = "{{ value | upper }}"
# The `origin_resource` variable is not available here.
During the first iteration, {{ value }} will render as aws; in the second, azure; and so on.
Inline TOML Data
You can define static data directly in the header of your model, compliance, or output files. Any top-level key that is not a reserved rescile directive (like origin_resource) is automatically made available to all templates within that file.
This is ideal for defining constants, configuration maps, or environment-specific values that you want to reuse across multiple rules.
# data/models/server.toml
origin_resource = "application"
# This is a custom TOML table, available as 'server_specs'.
server_specs = { standard_cpu = 4, premium_cpu = 8 }
[[create_resource]]
resource_type = "server"
name = "server_{{ origin_resource.name }}"
[create_resource.properties]
# Use the inline data in a template
cpu_cores = "{% if origin_resource.environment == 'prod' %}{{ server_specs.premium_cpu }}{% else %}{{ server_specs.standard_cpu }}{% endif %}"
External JSON Files
For larger or more complex datasets, you can load data from external JSON files using the special json! directive. This keeps your configuration files clean and allows you to manage data separately. The path to the JSON file is relative to the data directory.
# data/models/network.toml
origin_resource = "server"
# Load data from 'data/shared/ip_ranges.json'
# The data becomes available as 'ip_ranges'.
ip_ranges = { "json!" = "shared/ip_ranges.json" }
[[create_resource]]
# ...
[create_resource.properties]
subnet = "{{ ip_ranges.subnets[origin_resource.region] }}"
Pre-filtering with JMESPath
When loading JSON data, you can pre-filter it using a JMESPath query directly in the header. This is a powerful way to select or reshape complex JSON data before it’s used in your templates, improving both performance and readability.
To use it, add a jmespath key alongside the json! directive. The query is applied to the JSON data, and the result of the query becomes the value of the variable.
# data/models/database.toml
# Load 'slas.json' but only select the first policy where level is 'critical'.
critical_sla = { "json!" = "slas.json", jmespath = "policies[?level == 'critical'] | [0]" }
[[create_resource]]
# ...
[create_resource.properties]
# Use the pre-filtered data.
backup_frequency = "{{ critical_sla.backup_frequency }}" # Renders "hourly"
Importing Arbitrary JSON “As Is”
When importing complex JSON objects (such as those loaded via json!), GraphQL specifications normally enforce strict naming conventions for type and field names (/[_A-Za-z][_0-9A-Za-z]*/). If the schema encounters JSON object keys violating this (like netbox-2.10.0), it would typically sanitize them (e.g., - to _), mutating your payload against your intent.
To allow import JSON “as is” without sanitization while strictly aligning with the GraphQL spec, rescile introduces a wildcard JSON custom scalar. This instructs the GraphQL schema builder to accept and return the payload identically instead of recursively attempting to map the JSON structure to strict GraphQL Object Types.
By wrapping arbitrary JSON in a generic key during generation, the logic natively treats it as a JSON scalar because it detects the unsupported characters:
origin_resource = "provider"
bundle_data = { "json!" = "bundle.json" }
[[output]]
resource_type = "bundle_list"
name = "bundles"
filename = "bundles.json"
mimetype = "application/json"
template = '''
{ "data": {{ bundle_data | json_encode | safe }} }
'''
Global vs. Iteration Variables
Variables defined in the header can be evaluated in two different contexts: globally (once per file) or locally (once per origin_resource iteration).
-
Global Variables (Standard Strings): If a variable is defined as a standard string containing Tera tags (
{{ ... }}), it is evaluated once globally before the iteration begins. In this global context, theorigin_resourcevariable is a list of all matching resources in the graph. This is useful for aggregations.origin_resource = "network" # Evaluated GLOBALLY: 'origin_resource' is a list of ALL networks. total_network_count = "{{ origin_resource | length }}" -
Iteration Variables (
template!orfunction!): If a variable is defined using the{ "template!" = "..." }or{ "function!" = "..." }directive, it is evaluated locally during each loop iteration. In this context, theorigin_resourcevariable represents the single resource currently being processed. This allows for the creation of complex, derived variables that simplify your rule blocks.
origin_resource = "network"
# Evaluated LOCALLY per iteration: 'origin_resource' is the current network.
subnet_count = { "function!" = '''
{%- set subnets = origin_resource.subnet | default(value=[]) -%}
{{ subnets | length }}
''' }
[[create_resource]]
# ...
[create_resource.properties]
total_subnets = "{{ subnet_count }}" # Uses the calculated iteration variable
Module Parameters
When using a reusable module, you can pass in parameters from the command line using the --module-params argument. These parameters become globally available variables within all templates in the module.
This is the primary way to inject project-specific configuration into a generic, reusable module.
Command Line:
rescile-ce --module ./my-module --module-params "app_name=billing-api;region=eu-central-1" serve
Template within the module (models/server.toml):
[create_resource.properties]
# Access parameters passed from the command line.
hostname = "srv-{{ region }}-{{ app_name }}" # Renders as "srv-eu-central-1-billing-api"