Architectural Models

Templating & Data

How to use Tera, Jsonnet, and JMESPath in rescile models, compliance, and output files — covering data sources, global vs. iteration variables, and module parameters.

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.