Templating & Data
Dynamic Values, External Data, and Multiple Templating Engines
rescile supports several templating and data-querying technologies that you can use inside your TOML
configuration files. This page explains each capability and how to choose between them.
| Technology | Where used | Purpose |
|---|---|---|
| Tera | models/, compliance/, output/, proxy.toml |
String interpolation, conditionals, loops, filters |
| Jsonnet | output/ (jsonnet key) |
Complex JSON generation with functions, imports, variables |
| JMESPath | Header json! pre-filter, jmespath Tera filter |
Querying and reshaping JSON data |
1. Tera Templating
All string values in name, properties, match_on, filename, and template fields are processed
by the Tera templating engine. Tera syntax uses {{ expression }}
for values and {% tag %} for control flow.
For the complete list of rescile-specific functions and filters, see Tera Function & Filter Reference.
The origin_resource Variable
The most common data source in Tera templates. Inside any Rule Block, origin_resource refers to the
specific resource being processed in the current loop iteration.
origin_resource = "application"
[[create_resource]]
resource_type = "server"
name = "server_for_{{ origin_resource.name }}"
[create_resource.properties]
hostname = "{{ origin_resource.name | upper }}"
tier = "{% if origin_resource.environment == 'prod' %}premium{% else %}standard{% endif %}"
When processing the billing-api application, {{ origin_resource.name }} renders as billing-api.
The value Variable (from create_from)
When using create_from to iterate over a list, origin_resource is not available. Instead, the
current list item is exposed as {{ value }}.
provider_list = ["aws", "azure", "gcp"]
[[create_resource]]
create_from = { list = "provider_list", as = "provider" }
name = "cloud-{{ value }}"
[create_resource.properties]
short_name = "{{ value | upper }}"
During the first iteration {{ value }} is aws, second is azure, and so on.
Inline TOML Data
Any top-level key in the file header that is not a reserved rescile directive becomes a template variable available to all Rule Blocks in that file.
origin_resource = "application"
server_specs = { standard_cpu = 4, premium_cpu = 8 }
[[create_resource]]
resource_type = "server"
name = "server_{{ origin_resource.name }}"
[create_resource.properties]
cpu_cores = "{% if origin_resource.environment == 'prod' %}{{ server_specs.premium_cpu }}{% else %}{{ server_specs.standard_cpu }}{% endif %}"
External JSON Files (json!)
Load data from external JSON files with the json! directive. The path is relative to the data/
directory.
origin_resource = "server"
ip_ranges = { "json!" = "shared/ip_ranges.json" }
[[create_resource]]
[create_resource.properties]
subnet = "{{ ip_ranges.subnets[origin_resource.region] }}"
Pre-filtering JSON with JMESPath
Add a jmespath key alongside json! to pre-filter the data before it enters the template context.
This is both more efficient and more readable than filtering inside every rule block.
# Select only the first critical-level policy from slas.json
critical_sla = { "json!" = "slas.json", jmespath = "policies[?level == 'critical'] | [0]" }
[[create_resource]]
[create_resource.properties]
backup_frequency = "{{ critical_sla.backup_frequency }}"
Importing Arbitrary JSON “As Is”
When JSON keys contain characters that violate GraphQL naming rules (e.g. netbox-2.10.0), rescile
exposes the value as a custom JSON scalar type, bypassing sanitization. Use json_encode | safe in
your template to pass the data through unchanged:
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
Header variables can be evaluated in two different contexts:
| Syntax | When evaluated | origin_resource is… |
|---|---|---|
"{{ ... }}" (plain string) |
Once globally, before the loop | A list of all matching resources |
{ "template!" = "..." } or { "function!" = "..." } |
Per iteration, inside the loop | The single resource being processed |
origin_resource = "network"
# Evaluated GLOBALLY — origin_resource is a list of ALL networks
total_network_count = "{{ origin_resource | length }}"
# 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 }}"
Module Parameters
Parameters passed via --module-params on the command line are available as global Tera variables
across all files in the module.
rescile-ce --module ./my-module --module-params "region=eu-central-1;env=prod" serve
# models/server.toml — inside the module
[create_resource.properties]
hostname = "srv-{{ region }}-{{ origin_resource.name }}"
For more on modules and parameters, see Using Modules.
2. Jsonnet
Jsonnet is a data templating language designed for generating structured JSON. It supports local variables, functions, conditionals, list comprehensions, and file imports, making it ideal for complex output generation where Tera’s string templating becomes unwieldy.
Jsonnet is only available in output/ files, as the value of the jsonnet key in an [[output]]
block. It is mutually exclusive with the template key.
For a complete reference on the output engine, see Output Engine.
Data Context in Jsonnet
Data is injected as external variables and accessed with std.extVar():
| Variable | How to access |
|---|---|
origin_resource |
std.extVar("origin_resource") |
| Any header variable | std.extVar("variable_name") |
Example — Inline Jsonnet Logic
origin_resource = "server"
[[output]]
resource_type = "k8s_manifest"
name = "svc-{{ origin_resource.name }}" # Name still uses Tera
filename = "svc-{{ origin_resource.name }}.json"
mimetype = "application/json"
jsonnet = """
local origin = std.extVar("origin_resource");
{
apiVersion: "v1",
kind: "Service",
metadata: {
name: origin.name,
labels: {
app: if std.length(origin.application) > 0
then origin.application[0].name
else "unknown"
}
},
spec: {
ports: [{ port: p } for p in origin.ports]
}
}
"""
Example — Modular Imports (.libsonnet)
rescile supports sibling imports: .libsonnet or .jsonnet files in the same directory as the
TOML file can be imported directly by filename.
data/output/k8s.libsonnet
{
service(name, ports):: {
apiVersion: "v1",
kind: "Service",
metadata: { name: name },
spec: { ports: [{ port: p } for p in ports] }
}
}
data/output/services.toml
origin_resource = "server"
[[output]]
resource_type = "k8s_manifest"
name = "svc-{{ origin_resource.name }}"
filename = "svc-{{ origin_resource.name }}.json"
mimetype = "application/json"
jsonnet = """
local k8s = import 'k8s.libsonnet';
local origin = std.extVar("origin_resource");
k8s.service(origin.name, origin.ports)
"""
Example — OSCAL Compliance
Jsonnet’s list comprehensions and helper functions make it well-suited for generating complex standards-based documents like OSCAL (Open Security Controls Assessment Language):
# data/output/audit_oscal.toml
origin_resource = "audit"
[[output]]
resource_type = "oscal_ssp"
name = "oscal-ssp-{{ origin_resource.name }}"
filename = "{{ origin_resource.name }}-ssp.json"
mimetype = "application/json"
jsonnet = '''
local origin = std.extVar("origin_resource");
{
"system-security-plan": {
metadata: {
title: "System Security Plan for " + origin.audit_name,
},
"control-implementation": {
"implemented-requirements": [
{
"control-id": ctrl.name,
description: std.get(ctrl, "evidence_summary", {}),
}
for ctrl in std.get(origin, "control", [])
],
},
},
}
'''
For a complete walkthrough of generating OSCAL and Markdown audit reports, see Generating Audit Artifacts.
3. JMESPath
JMESPath is a query language for JSON. In rescile it appears in two places:
| Usage | How |
|---|---|
| Header pre-filter | { "json!" = "file.json", jmespath = "..." } — filters JSON data once, before the loop |
| Tera filter | | jmespath(query="...") — queries a value inline inside any template |
Pre-filtering JSON at Load Time
Adding a jmespath key alongside json! selects or reshapes data before it is bound to a
variable. This is more efficient than filtering the full dataset inside every rule block.
# Only load the first SLA policy whose level is "critical"
critical_sla = { "json!" = "slas.json", jmespath = "policies[?level == 'critical'] | [0]" }
[[create_resource]]
[create_resource.properties]
backup_frequency = "{{ critical_sla.backup_frequency }}"
rto_hours = "{{ critical_sla.rto_hours }}"
Querying Inline with the jmespath Filter
The jmespath Tera filter lets you run a JMESPath query against any JSON-compatible value at
template evaluation time. It returns the query result, or an empty/falsy value when nothing matches.
[create_resource.properties]
# origin_resource.config = { "storage": { "class": "premium-ssd" } }
storage_class = "{{ origin_resource.config | jmespath(query='storage.class') }}"
# → "premium-ssd"
Using JMESPath in match_on Expressions
Combine the expression operator with the jmespath filter to match on deeply nested JSON
data that the standard operators cannot reach:
[[create_resource]]
match_on = [
{
expression = """
{%- set tags = origin_resource.config
| jmespath(query="tags[?key == 'env' && value == 'prod']") -%}
{% if tags %}true{% endif %}
"""
}
]
resource_type = "backup_policy"
name = "backup_for_{{ origin_resource.name }}"
The condition is true when the JMESPath query returns a non-empty, non-null result. For a dedicated guide to this pattern, see Matching on JSON with jmespath.
For the complete filter signature and more examples, see the
Tera Function & Filter Reference — jmespath
section of the Reference guide.
4. Choosing the Right Tool
| Scenario | Recommended tool |
|---|---|
| Simple string interpolation, property access, conditionals | Tera — {{ ... }} / {% ... %} |
| Arithmetic, loops, and array manipulations in property values | Tera filters (map, join, select, …) |
| Extracting a value from a nested JSON object | JMESPath filter inside Tera |
| Pre-selecting a slice of a large JSON file before the loop | JMESPath header pre-filter (jmespath =) |
| Generating a complete, structured JSON document (manifests, tfvars) | Jsonnet in [[output]] |
| Reusable JSON generation logic shared across multiple output files | Jsonnet with .libsonnet imports |
Quick Reference
Tera Variables Available in Rule Blocks
| Variable | Available when | Description |
|---|---|---|
origin_resource |
origin_resource is set in header |
The resource being processed in the current iteration |
value |
Inside a create_from block |
The current item from the iterated list or property |
origin_resource_counter |
origin_resource is set |
Zero-based counter, increments per rule block per iteration |
<header key> |
Always | Any top-level key defined in the file header |
<module param> |
Module is loaded with --module-params |
Parameters passed via CLI, available globally |
RESOURCE_ALIASES |
Dependency aliasing is configured | Map of original → aliased resource type names |
env.<VAR> |
proxy.toml only |
Environment variables (CE: system env; Enterprise: workspace secrets) |
request |
proxy.toml inject_headers only |
Incoming request object (method, path, body, timestamp, …) |
Header Variable Evaluation Modes
origin_resource = "application"
# ── Evaluated ONCE globally (origin_resource = list of ALL applications) ──
total = "{{ origin_resource | length }}"
loaded = { "json!" = "data/ranges.json" }
# ── Evaluated PER ITERATION (origin_resource = the current application) ──
per_app = { "template!" = "{{ origin_resource.name | upper }}" }
# or equivalently:
per_app2 = { "function!" = "{{ origin_resource.name | upper }}" }
Commonly Used Tera Filters
| Filter | Example | Result |
|---|---|---|
upper / lower |
{{ name | upper }} |
"BILLING-API" |
replace(from, to) |
{{ name | replace(from=".", to="-") }} |
"billing-api" |
default(value) |
{{ x | default(value="n/a") }} |
fallback when undefined |
split(pat) + first |
{{ v | split(pat=".") | first }} |
major version component |
join(sep) |
{{ arr | join(sep=",") }} |
comma-separated string |
map(attribute) |
{{ servers | map(attribute="ip") }} |
array of IPs |
json_encode | safe |
{{ obj | json_encode | safe }} |
inline JSON (no escaping) |
length |
{{ items | length }} |
item count |
jmespath(query) |
{{ obj | jmespath(query="a.b") }} |
nested value extraction |
select(from, match) |
{{ list | select(from="name", match="status=active") }} |
filtered property list |
cidr_nth_subnet(prefix, nth) |
{{ "10.0.0.0/16" | cidr_nth_subnet(prefix=24, nth=0) }} |
"10.0.0.0/24" |
For the complete list of rescile-specific functions and filters — including counter,
calculate_cidr, allocate_subnets, sha256, base64_encode, and hmac_sha256 — see the
Tera Function & Filter Reference.