Tutorial Part 4: Advanced Modeling

Propagate context, enrich with external data, and use templates.


Part 4: Advanced Modeling & Data Enrichment

Goal: In this section, we’ll make our blueprint “smarter” by using rescile’s advanced features. We will propagate important data across the graph, join it with external information, use templates for dynamic properties, and demonstrate a powerful risk propagation pattern.

Step 12: Propagating Context with subscribe_property

Our onprem_vm nodes don’t know which network zone they belong to. This context exists on the parent application, but we need it at the infrastructure level for network policy decisions. The subscribe_property directive is perfect for this.

Create a model file that tells rescile to copy the network_zone property from a connected (application) down to the (onprem_vm). We’ll add a match_on so this rule only applies to production VMs.

data/models/onprem_vm.toml

origin_resource = "onprem_vm"

[[subscribe_property]]
# Copy FROM the connected 'application'.
origin_resource_type = "application"
origin_property = "network_zone"
# Copy TO this model's resource type ('onprem_vm').
target_resource_type = "onprem_vm"
target_property = "network_zone"
# Only apply this rule to VMs that have a specific property.
# The 'os_provider' property was set on the VM in our 'hosting.toml' model.
match_on = [
  { property = "os_provider", value = "redhat" } # 'redhat' implies production in our model
]

Run rescile-ce serve.

Result: The (onprem_vm) node for our billing-legacy application now has a network_zone: 'backend' property, inherited from its parent. The dev_server for billing-legacy-dev does not, because it failed the match_on condition. This shows how to propagate context conditionally.

Verify with this query:

query CheckVmNetworkZone {
  prod_vm: onprem_vm(filter: {name: "vm-for-billing-legacy"}) {
    name
    network_zone
  }
  dev_server: dev_server(filter: {name: "dev-server-for-billing-legacy-dev"}) {
    name
    # The 'network_zone' field won't exist here
  }
}

Step 13: Enriching with External Data (enrich_property)

Our infrastructure nodes (cloud_region, onprem_datacenter) are abstract. Let’s enrich them with real-world data about their physical location and compliance jurisdictions using enrich_property.

First, create a new asset file to serve as our external data source.

data/assets/location.csv

name,city,country,jurisdiction
eu-central-1,Frankfurt,DE,EU
dc01-frankfurt,Frankfurt,DE,"EU,BAFIN"

Now, create a model that joins this data onto our infrastructure nodes. We’ll add rules for both datacenters and cloud regions.

data/models/location_enrichment.toml

# Rule 1: Enrich on-prem datacenters
origin_resource = "onprem_datacenter"

[[enrich_property]]
from_resource_type = "location"
# Join onprem_datacenter.name with location.name
origin_key = "name"
target_key = "name"
property_to_copy = "jurisdiction"

# Rule 2: Enrich cloud regions
origin_resource = "cloud_region"

[[enrich_property]]
from_resource_type = "location"
# Join cloud_region.name with location.name
origin_key = "name"
target_key = "name"
property_to_copy = "jurisdiction"

Run rescile-ce serve.

Result: Our hybrid infrastructure nodes are now enriched with crucial compliance and geographical data from a separate source. This demonstrates rescile’s ability to federate data from different domains into a unified blueprint.

Step 14: Dynamic Properties with Tera Templating and json!()

Properties don’t have to be static. rescile uses the powerful Tera templating engine to generate property values dynamically. Let’s use it to create a Fully Qualified Domain Name (FQDN) for our order-frontend microservices.

First, create a JSON file to map network zones to DNS domains.

data/assets/domain_map.json

{
  "edge": "svc.example.com",
  "backend": "internal.svc.local"
}

Now, create a model that reads this JSON file and uses a Tera expression to build the FQDN.

data/models/fqdn.toml

# Load the JSON file into a variable named 'domain_map' available to all templates in this file.
domain_map = json!("data/assets/domain_map.json")

origin_resource = "microservice"

# This rule updates the existing microservice nodes created in Part 2.
# We re-use the same name to target the existing nodes.
[[create_resource]]
resource_type = "microservice"
name = "{{ origin_resource.name }}"
relation_type = "_UPDATED_WITH_FQDN" # A temporary relation to trigger the update
[create_resource.properties]
# This Tera expression:
# 1. Accesses the microservice's name via `origin_resource.name`.
# 2. Looks up the domain in our loaded JSON file using the network_zone property.
# 3. Concatenates them into an FQDN.
fqdn = "{{ origin_resource.name }}.{{ domain_map[origin_resource.parent_application_network_zone] }}"

# We need to get the network zone from the parent application.
# Let's add a subscribe_property rule to the microservice.toml model first.

The `origin_resource` for the FQDN model is `microservice`. Microservices don't have a `network_zone` property themselves. We need to subscribe it from the parent `application` first. Update your `microservice.toml`.

data/models/microservice.toml (add this block)

[[subscribe_property]]
origin_resource_type = "application"
origin_property = "network_zone"
target_resource_type = "microservice"
# Let's name the new property clearly to avoid conflicts
target_property = "parent_application_network_zone"

Now run rescile-ce serve.

Result: The (microservice) nodes now have a new fqdn property (e.g., auth-service.svc.example.com) that was dynamically and consistently generated based on their context within the blueprint.

Step 15: Advanced Conditional Logic

Tera templates can include if/else logic, allowing you to embed business rules directly into the blueprint. Let’s define an SLA tier based on the environment.

Add this create_resource block to your application.toml model.

data/models/application.toml (add this block)

# Update the application node with a calculated SLA tier.
[[create_resource]]
resource_type = "application"
name = "{{ origin_resource.name }}"
relation_type = "_UPDATED_WITH_SLA"
[create_resource.properties]
sla_tier = "{% if origin_resource.environment == 'production' %}Gold{% else %}Silver{% endif %}"

The match_on directive also supports complex OR logic. For example, to create a high-availability node if an app is in production OR if it’s a large container cluster, the rule would look like this:

# Conceptual example of an 'or' block
[[create_resource]]
match_on = [
  { or = [
      [ { property = "environment", value = "production" } ],
      [ { property = "node_count", greater = 5 } ]
    ]
  }
]
resource_type = "high_availability_config"
# ...

Result: After running rescile-ce serve, all production (application) nodes will have sla_tier: 'Gold', while the development app will have sla_tier: 'Silver'. The blueprint now contains executable business logic.

Step 16: Propagating Supply Chain Risk

This is a powerful demonstration of how rescile can automate complex risk analysis by propagating data up a dependency chain. In Part 3, we enriched our log4j SBOM component with a severity: 'CRITICAL' property. Now, let’s make the parent application aware of this risk.

We’ll add a series of subscribe_property rules that act like a chain reaction.

Create a new model file for this logic.

data/models/risk_propagation.toml

# Step 1: Propagate CVE severity to the SBOM component that contains it.
origin_resource = "cve"
[[subscribe_property]]
origin_resource_type = "cve"
origin_property = "severity"
target_resource_type = "sbom_component"
target_property = "highest_cve_severity"

# Step 2: If an SBOM component has a critical CVE, flag its parent container image.
origin_resource = "sbom_component"
[[subscribe_property]]
# Only run this for components with a critical CVE.
match_on = [ { property = "highest_cve_severity", value = "CRITICAL" } ]
origin_resource_type = "sbom_component"
# We don't subscribe a property from the source, but a literal 'true' value.
origin_property_literal = true
target_resource_type = "container_image"
target_property = "has_critical_vulnerability"

# Step 3: If a container image is flagged, flag its parent application.
origin_resource = "container_image"
[[subscribe_property]]
match_on = [ { property = "has_critical_vulnerability", value = true } ]
origin_resource_type = "container_image"
origin_property_literal = true
target_resource_type = "application"
target_property = "has_critical_vulnerability"

Run rescile-ce serve.

Result: The order-frontend application node now automatically has the property has_critical_vulnerability: true. This happened because the risk was propagated upwards: (cve) -> (sbom_component) -> (container_image) -> (application). This demonstrates how rescile automates complex, multi-level risk assessment, turning a simple piece of vulnerability data into actionable intelligence at the application level.