Skip to main content

Template language

Templates and checks are written in Jinja. MiniPAM adds a read-only data object for your inventory, a few helpers, and the require and warn tags.

Everything is scoped to the active site. Data reference in the header of the Templates and Asset integrity pages lists exactly what the version you run supports, with field descriptions.

The data object​

NameContains
data.siteThe site: id, name, location, timezone, tags
data.generated_atWhen the render started, as an ISO 8601 timestamp
data.<collection>A record set, one per collection below

Collections​

Each collection lists its main fields. A name in brackets is a shorter alias for the field before it, so network.cidr and network.network_cidr are the same.

  • data.devices (loop name device): name, hostname, description, platform_identifier, os_info, parent_id. Linked records: device.device_type.
  • data.interfaces (loop name interface): interface_name (name), mac_address, mac_vendor (vendor), ipv4_method, ipv6_method. Linked records: interface.device.
  • data.device_types (loop name device_type): name, category, device_type (type), device_subtype (subtype), manufacturer.
  • data.networks (loop name network): network_name (name), network_cidr (cidr), gateway_ip (gateway), vlan_id, vlan_name, family.
  • data.address_pools (loop name pool): pool_name (name), ip_start (start), ip_end (end), pool_type (type). Linked records: pool.network.
  • data.ip_addresses (loop name address): ip_address (address), usage_status (status), family, description, last_seen_at. Linked records: address.network.
  • data.applications (loop name application): app_name (name), port, port_protocol (transport), app_protocol (protocol), description, bind_ip, bind_scope, bind_ip_family, run_user, doc_url, source_url, netdevice_id (device_id), ip_address_id. Linked records: application.device, application.ip_address.
  • data.services (loop name service): name, category, protocol_family, preferred_access_mode (access_mode), overview, description, doc_url, site_id.
  • data.gateways (loop name gateway): name, overview, description, site_id.
  • data.gateway_routes (loop name route): fqdn, path_prefix, route_priority (priority), service_id, service_gateway_id (gateway_id). Linked records: route.service, route.gateway.
  • data.dns_records (loop name dns_record): record_name (name), record_type (type), record_value (value), ttl_seconds (ttl).

Every record also has id, created_at, and updated_at. All but gateways and gateway routes have tags.

Services and gateways include global ones, which have no site_id. data.applications.of(service) returns every provider of the service as a plain application: which one is primary, and each provider's role and priority, are not available in templates. data.ip_addresses.of(application) returns the application's IP address, or all of its device's interface addresses; it ignores bind_ip, so read that field yourself when it matters.

Record set methods​

  • filter('key:op:value'): Matching records. Same syntax as structured filters, up to 5 criteria joined with and.
  • of(record_or_set): Records related to a record or set, such as data.ip_addresses.of(device)
  • sort('field'): Records sorted ascending by a field
  • limit(n): At most n records (1 to 2,000)
  • first(): The first record, or none
  • one(): The only record. Fails if there are none or several.
  • exists(): true if there is at least one record
  • count(): The number of records

Record sets can be looped over with {% for %} and chained:

{% set servers = data.devices.filter('tag.role:eq:server').sort('name').limit(50) %}
{% for device in servers %}
{{ device.name }}: {% for address in data.ip_addresses.of(device) %}{{ address.address }} {% endfor %}
{% endfor %}

of() follows these relationships, in both directions: devices and interfaces, interfaces and IP addresses, devices and IP addresses, IP addresses and networks, pools and networks, applications and devices, IP addresses, or services, gateways and devices, routes and gateways or services, device types and devices.

Filter operators​

eq, ne, co (contains), nco, sw (starts with), ew (ends with), gt, gte, lt, lte, and for IP fields in_cidr and not_in_cidr:

{% set lan = data.ip_addresses.filter('address:in_cidr:192.168.1.0/24') %}
{% set routers = data.devices.filter('tag.role:eq:router and name:sw:edge-') %}

Helpers​

HelperReturns
ip_in_cidr(value, cidr)Whether an address is inside a CIDR
cidr_contains(container, candidate)Whether one CIDR contains another
cidr_overlaps(left, right)Whether two CIDRs overlap
normalize_ip(value)An address in canonical form
normalize_cidr(value)A CIDR in canonical form
ip_version(value)4 or 6
has_tag(record, key, value=None)Whether a record has a tag, optionally with a given value
is_unique(records, field)Whether no two records share a value for a field
duplicate_values(records, field)The values that appear more than once
all_present(records, field)Whether every record has a value for a field

Assertions: require and warn​

Available in templates and in checks:

{% require condition,
code='short_code',
message='What is wrong and what to fix',
expected='what should be true',
actual=what_was_found %}

{% warn condition, code='short_code', message='…' %}
  • require blocks: a failing require fails the check, and blocks a template's output from being published.
  • warn only reports.
  • code, message, expected, and actual are optional, but a clear message saves the next person time.
  • A check must contain at least one require.
  • Assertions produce no text in the output.

In an output check, artifact describes the generated file: artifact.content, artifact.filename, artifact.media_type, and artifact.size (in bytes).

Jinja features​

Standard Jinja statements and expressions work: set, if, for, macro, comments, whitespace control ({%-, -%}), and the ~ operator for joining strings.

Available filters: default, join, lower, upper, replace, trim, indent, length, sort, unique, int, float, round, tojson, capitalize, title.

Templates are self-contained: include, import, and extends are not available.

Limits​

Each render runs in a sandbox with these limits:

LimitDefaultConfigurable
Render time3 secondsNo
Output size2 MiBNo
Records returned by one query2,000Yes
Records loaded in one render10,000Yes
Queries in one render500Yes

Admins can change the configurable limits, globally or per site, under Administration → System → Settings (category Templates). See Settings.

When a render hits a limit, the error says which one. Store a query with {% set %} instead of repeating it inside a loop, and narrow large sets with filter() and limit().

How queries are counted​

  • Looping over a set runs one query, however many records it has.
  • of(record) inside a loop is batched: the first iteration runs its own query, and the second loads the lookups for every remaining record at once. data.ip_addresses.of(device) in a loop over 1,000 devices costs 3 queries, not 1,000. The same applies to count(), first(), and exists() on that lookup.
  • Narrowing the lookup, as in of(device).filter(...), .sort(...), or .limit(...), still runs one query per record.
  • count() counts without loading the records, and a stored set remembers its count. If you also loop over the set, use devices | length instead: it loads the records once and reuses them for the loop.