Architectural Models

Custom Functions and Filters

Reference for rescile's custom Tera functions and filters for networking, hashing, encoding, and arrays.

Custom Functions and Filters

rescile extends Tera with custom functions and filters, particularly for hybrid toplogy modeling. These can be used in any templated string value.

counter Function

A stateful counter that increments for each unique key provided. While rescile provides a basic origin_resource_counter variable for simple iteration, the counter function is much more powerful for scenarios where you need independent, keyed counters. For example, numbering resources within different groups (like subnets within different VPCs). The counters are reset for each importer run.

  • key: A string that uniquely identifies the counter. Each unique key will have its own independent, zero-based counter. This can be a static string or a dynamic value from a template.

Goal: For each department resource that belongs to a specific region, allocate a unique /24 subnet from the region’s larger /16 block. The numbering of subnets (nth) should restart from 0 for each region.

data/models/department_subnets.toml

origin_resource = "department"

[[create_resource]]
resource_type = "subnet"
relation_type = "HAS_SUBNET"
[create_resource.properties]
# The key for the counter is the region's CIDR block.
# For all departments in the '10.0.0.0/16' region, the counter will go 0, 1, 2...
# For all departments in the '10.1.0.0/16' region, it will also go 0, 1, 2...
subnet_cidr = "{{ origin_resource.region[0].cidr | cidr_nth_subnet(prefix=24, nth=counter(key=origin_resource.region[0].cidr)) }}"

calculate_cidr Function

Calculates the smallest possible CIDR block that contains a given list of IP addresses. This is useful for summarizing a set of related IP addresses into a single network range, for example, to create a firewall rule.

  • ips: An array of strings, where each string is a valid IPv4 or IPv6 address.

Goal: Given a list of server IP addresses, calculate the CIDR block that encompasses all of them to create a summary network object.

data/models/network_summary.toml

origin_resource = "application"

[[create_resource]]
resource_type = "network_summary"
name = "summary_for_{{ origin_resource.name }}"
[create_resource.properties]
# origin_resource.server is a relation to multiple server resources.
# The `map(attribute='ip_address')` filter extracts the 'ip_address' from each server.
# The `calculate_cidr` function then calculates the containing CIDR.
# Example result for ips=["10.1.5.10", "10.1.5.25"]: "10.1.5.0/27"
summary_cidr = "{{ calculate_cidr(ips=origin_resource.server | map(attribute='ip_address')) }}"

cidr_nth_subnet Filter

Calculates a subnet within a parent CIDR block. This is useful for partitioning a large network block into smaller, predictable subnets.

  • prefix: The additional prefix to add to the parent CIDR.
  • nth: The zero-based index of the subnet to select.

Goal: For each department resource, allocate a unique /24 subnet from a larger /16 block in a predictable, ordered fashion.

data/models/department_subnets.toml

origin_resource = "department"

[[create_resource]]
resource_type = "subnet"
relation_type = "HAS_SUBNET"
[create_resource.properties]
# For the first department, `origin_resource_counter` is 0, calculating 10.0.0.0/24.
# For the second, it's 1, calculating 10.0.1.0/24, and so on.
subnet_cidr = "{{ '10.0.0.0/16' | cidr_nth_subnet(prefix=24, nth=origin_resource_counter) }}"

cidr_split_n Filter

Splits a parent CIDR block into a specified number of equal-sized subnets. The number of subnets is rounded up to the next power of two for even division.

  • n: The desired number of subnets.

Goal: Divide a /16 network into 4 equal /18 subnets for regional distribution.

data/models/network_division.toml

origin_resource = "vpc" # Assuming a VPC resource with a cidr property like "10.10.0.0/16"

[[create_resource]]
resource_type = "network_layout"
name = "layout_for_{{origin_resource.name}}"
[create_resource.properties]
# The filter returns an array of strings.
# Example result: ["10.10.0.0/18", "10.10.64.0/18", "10.10.128.0/18", "10.10.192.0/18"]
regional_subnets = "{{ origin_resource.cidr | cidr_split_n(n=4) }}"

allocate_subnets Function

A powerful function for dynamic subnet planning. It takes a parent CIDR block and a map of subnet names to their required host counts. It then calculates the smallest possible subnet for each request and allocates them sequentially within the parent block.

  • cidr: The parent CIDR block from which to allocate subnets.
  • host_map: A dictionary where keys are the desired subnet names and values are the number of hosts required.

Goal: Automatically plan a network layout based on the needs of different environments.

data/models/network_layout.toml

origin_resource = "network"

# This is an inline TOML table, available in templates as 'required_subnets'.
required_subnets = { dmz = 10, app = 100, db = 50 }

[[create_resource]]
match_on = [ { property = "name", value = "core_network" } ]
resource_type = "network_layout"
name = "layout_for_{{origin_resource.name}}"
[create_resource.properties]
# The function allocates subnets from the network's main CIDR block.
# The keys are automatically sorted for deterministic allocation (app, db, dmz).
allocated_cidrs = "{{ allocate_subnets(cidr=origin_resource.cidr, host_map=required_subnets) }}"

Assuming origin_resource.cidr is "10.0.0.0/16", the allocated_cidrs property would become a JSON object string like this:

{
  "app": "10.0.0.0/25",
  "db": "10.0.0.128/26",
  "dmz": "10.0.0.192/28"
}

This object can then be used in subsequent models to create the actual subnet resources.

jmespath Filter

Queries a JSON-compatible value using a JMESPath expression. This is extremely useful for extracting or transforming data from complex JSON objects or arrays directly within a template. The filter takes a single query argument.

Goal: Extract the storage class from a nested JSON property on a resource.

Template:

[create_resource.properties]
# Assume origin_resource.config is a JSON object like: { "storage": { "class": "premium-ssd" } }
# The filter extracts the nested value.
storage_class = "{{ origin_resource.config | jmespath(query='storage.class') }}"

The jmespath filter can be applied to any variable that holds a JSON-like structure, including data loaded from files or properties on resources.

Array Filters

rescile provides custom filters for working with arrays of objects.

select Filter

Selects a property from objects in an array that match a specific condition. It can also operate on a single object. This is useful for extracting a list of names, IDs, or other attributes from a list of related resources.

  • from: The name of the property to extract from the matching object(s).
  • match: A string in the format "key=value" defining the condition. The filter will check if the object’s key property is equal to value.

Goal: From an application resource with multiple related server objects, create a comma-separated list of hostnames for all active servers.

Template:

[create_resource.properties]
# origin_resource.server is an array of server objects: [{name: "srv1", status: "active"}, {name: "srv2", status: "inactive"}]
# 1. `select` filters the array and extracts the 'name' property from matching objects. Result: ["srv1"]
# 2. `join` converts the array into a string. Result: "srv1"
active_servers = "{{ origin_resource.server | select(from='name', match='status=active') | join(sep=',') }}"

Behavior on a Single Object:

If applied to a single object, select returns the value of the from property if the condition matches, otherwise it returns null.

# If origin_resource.primary_db is { name: "db1", role: "primary" }
# This renders "db1".
primary_db_name = "{{ origin_resource.primary_db | select(from='name', match='role=primary') }}"
intersect Filter

Returns a new array containing only the elements that exist in both the input array and a second array provided via the with argument. The result contains no duplicates.

  • with: The second array to compare against.

Goal: Find the common tags between an application and its server.

Template:

[create_resource.properties]
# application.tags = ["frontend", "web", "prod"]
# server.tags = ["web", "api", "prod"]
# Result: ["web", "prod"]
common_tags = "{{ origin_resource.tags | intersect(with=origin_resource.server[0].tags) }}"

sha256 Filter

Computes the SHA-256 hash of a value. If the input is a string, it hashes the string directly. If the input is a complex type (like an array or object), it first serializes the value to a JSON string and then hashes it. The result is a lowercase hexadecimal string.

Goal: Create a unique, deterministic ID or name based on a combination of properties to avoid collisions.

Template:

[create_resource.properties]
# Create a unique hash based on the combination of multiple fields
unique_id = "{{ [origin_resource.address, origin_resource.port] | json_encode | sha256 }}"

base64_encode Filter

Encodes a value using Base64. Similar to the sha256 filter, it encodes strings directly, and serializes complex types to JSON strings before encoding.

Goal: Encode a script or configuration snippet to pass into a cloud-init user-data property or similar attributes requiring base64 encoding.

Template:

[create_resource.properties]
# Encode a shell script for a server provisioning payload
user_data = "{{ origin_resource.startup_script | base64_encode }}"

hmac_sha256 Filter

Computes the HMAC-SHA256 authentication code of a value using a specified key. The result is returned as a lowercase hexadecimal string by default, but can optionally be encoded in Base64.

  • key: The secret key used for the HMAC computation.
  • encoding: (Optional) The output encoding format. Can be "hex" (default) or "base64".

Goal: Create a signed payload for API authentication (e.g., generating an Exoscale API signature).

Template:

[create_resource.properties]
# Compute the HMAC-SHA256 hash and encode the result in base64
signature = "{{ origin_resource.payload | hmac_sha256(key=env.API_SECRET, encoding=\"base64\") }}"