## Summary Implements Infrahub Jinja2 Transform for VRF/L3VNI configuration, generating structured JSON payloads from Infrahub intent data. Closes #21. ## What's included - **GraphQL query** (`transforms/queries/vrf_intent.gql`): Queries `InfraVRFDeviceAssignment` by device name, fetching VRF details, L3VNI, route targets, and device-specific route distinguisher - **Jinja2 transform** (`transforms/templates/vrf_yang.j2`): Renders VRF config payloads with defensive null handling (lessons learned from #20) - **`.infrahub.yml`** updated with the new query and transform definitions - **Tests** (`transforms/tests/vrf_yang/`) using correct Infrahub pytest format (`jinja2-transform-smoke` + `jinja2-transform-unit-render`) ## Validation All transforms render correctly: | Device | Expected | Result | |--------|----------|--------| | `leaf3` | VRF gold, RD `10.0.250.13:1`, L3VNI 100001 | ✅ | | `leaf7` | VRF gold, RD `10.0.250.17:1`, L3VNI 100001 | ✅ | | `leaf1` | Empty array (no VRF assignment) | ✅ | | `spine1` | Empty array (no VRF assignment) | ✅ | ## Usage ```bash # Local debugging infrahubctl render vrf_yang_transform device_name=leaf3 # API endpoint GET /api/transform/jinja2/vrf_yang_transform?device_name=leaf3 ``` ## Related - Depends on: #20 (base transforms infrastructure, already merged) - Reference: Arista EVPN Type-5 / L3VXLAN configuration from lab topology
This commit was merged in pull request #25.
This commit is contained in:
@@ -24,6 +24,8 @@ queries:
|
||||
file_path: infrahub/transforms/queries/interface_intent.gql
|
||||
- name: vxlan_intent
|
||||
file_path: infrahub/transforms/queries/vxlan_intent.gql
|
||||
- name: vrf_intent
|
||||
file_path: infrahub/transforms/queries/vrf_intent.gql
|
||||
|
||||
jinja2_transforms:
|
||||
- name: vlan_yang_transform
|
||||
@@ -38,3 +40,7 @@ jinja2_transforms:
|
||||
description: "Generate VXLAN/VTEP configuration payload from Infrahub intent"
|
||||
query: vxlan_intent
|
||||
template_path: infrahub/transforms/templates/vxlan_yang.j2
|
||||
- name: vrf_yang_transform
|
||||
description: "Generate VRF/L3VNI configuration payload from Infrahub intent"
|
||||
query: vrf_intent
|
||||
template_path: infrahub/transforms/templates/vrf_yang.j2
|
||||
|
||||
50
infrahub/transforms/queries/vrf_intent.gql
Normal file
50
infrahub/transforms/queries/vrf_intent.gql
Normal file
@@ -0,0 +1,50 @@
|
||||
query VrfIntent($device_name: String!) {
|
||||
InfraVRFDeviceAssignment(device__name__value: $device_name) {
|
||||
edges {
|
||||
node {
|
||||
route_distinguisher { value }
|
||||
vrf {
|
||||
node {
|
||||
name { value }
|
||||
description { value }
|
||||
route_distinguisher { value }
|
||||
l3vni {
|
||||
node {
|
||||
vni { value }
|
||||
vni_type { value }
|
||||
}
|
||||
}
|
||||
import_targets {
|
||||
edges {
|
||||
node {
|
||||
target { value }
|
||||
}
|
||||
}
|
||||
}
|
||||
export_targets {
|
||||
edges {
|
||||
node {
|
||||
target { value }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
import_targets {
|
||||
edges {
|
||||
node {
|
||||
target { value }
|
||||
}
|
||||
}
|
||||
}
|
||||
export_targets {
|
||||
edges {
|
||||
node {
|
||||
target { value }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
78
infrahub/transforms/templates/vrf_yang.j2
Normal file
78
infrahub/transforms/templates/vrf_yang.j2
Normal file
@@ -0,0 +1,78 @@
|
||||
{#
|
||||
vrf_yang.j2 — Produce a JSON array of VRF configuration objects.
|
||||
|
||||
Input: GraphQL response from vrf_intent query.
|
||||
For each VRFDeviceAssignment, produce one VRF config entry with:
|
||||
- device-specific RD (falls back to VRF-level RD if not set on assignment)
|
||||
- L3VNI from the VRF's l3vni relationship
|
||||
- import/export targets: device-level if present, otherwise VRF-level
|
||||
#}
|
||||
{%- set vrf_list = [] -%}
|
||||
|
||||
{%- for assignment_edge in data.InfraVRFDeviceAssignment.edges -%}
|
||||
{%- set assignment = assignment_edge.node -%}
|
||||
|
||||
{#— Resolve VRF node —#}
|
||||
{%- set vrf_name = none -%}
|
||||
{%- set vrf_description = none -%}
|
||||
{%- set l3vni = none -%}
|
||||
{%- set vrf_level_import_rts = [] -%}
|
||||
{%- set vrf_level_export_rts = [] -%}
|
||||
|
||||
{%- if assignment.vrf is defined and assignment.vrf is not none and assignment.vrf.node is not none -%}
|
||||
{%- set vrf_node = assignment.vrf.node -%}
|
||||
{%- set vrf_name = vrf_node.name.value -%}
|
||||
{%- set vrf_description = vrf_node.description.value | default(none) -%}
|
||||
|
||||
{%- if vrf_node.l3vni is defined and vrf_node.l3vni is not none and vrf_node.l3vni.node is not none -%}
|
||||
{%- set l3vni = vrf_node.l3vni.node.vni.value -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- for rt_edge in vrf_node.import_targets.edges -%}
|
||||
{%- set _ = vrf_level_import_rts.append(rt_edge.node.target.value) -%}
|
||||
{%- endfor -%}
|
||||
{%- for rt_edge in vrf_node.export_targets.edges -%}
|
||||
{%- set _ = vrf_level_export_rts.append(rt_edge.node.target.value) -%}
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
|
||||
{#— Resolve route_distinguisher: device-specific first, fall back to VRF-level —#}
|
||||
{%- set rd = none -%}
|
||||
{%- if assignment.route_distinguisher is defined and assignment.route_distinguisher is not none and assignment.route_distinguisher.value is not none and assignment.route_distinguisher.value != "" -%}
|
||||
{%- set rd = assignment.route_distinguisher.value -%}
|
||||
{%- elif assignment.vrf is defined and assignment.vrf is not none and assignment.vrf.node is not none and assignment.vrf.node.route_distinguisher is defined and assignment.vrf.node.route_distinguisher is not none -%}
|
||||
{%- set rd = assignment.vrf.node.route_distinguisher.value | default(none) -%}
|
||||
{%- endif -%}
|
||||
|
||||
{#— Resolve import targets: device-level if present, otherwise VRF-level —#}
|
||||
{%- set import_rts = [] -%}
|
||||
{%- if assignment.import_targets is defined and assignment.import_targets is not none and assignment.import_targets.edges | length > 0 -%}
|
||||
{%- for rt_edge in assignment.import_targets.edges -%}
|
||||
{%- set _ = import_rts.append(rt_edge.node.target.value) -%}
|
||||
{%- endfor -%}
|
||||
{%- else -%}
|
||||
{%- set import_rts = vrf_level_import_rts -%}
|
||||
{%- endif -%}
|
||||
|
||||
{#— Resolve export targets: device-level if present, otherwise VRF-level —#}
|
||||
{%- set export_rts = [] -%}
|
||||
{%- if assignment.export_targets is defined and assignment.export_targets is not none and assignment.export_targets.edges | length > 0 -%}
|
||||
{%- for rt_edge in assignment.export_targets.edges -%}
|
||||
{%- set _ = export_rts.append(rt_edge.node.target.value) -%}
|
||||
{%- endfor -%}
|
||||
{%- else -%}
|
||||
{%- set export_rts = vrf_level_export_rts -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- set _ = vrf_list.append({
|
||||
"vrf_name": vrf_name,
|
||||
"description": vrf_description,
|
||||
"route_distinguisher": rd,
|
||||
"l3vni": l3vni,
|
||||
"import_targets": import_rts,
|
||||
"export_targets": export_rts,
|
||||
"redistribute_connected": true
|
||||
}) -%}
|
||||
{%- endfor -%}
|
||||
|
||||
{{ vrf_list | tojson(indent=2) }}
|
||||
82
infrahub/transforms/tests/vrf_yang/input.json
Normal file
82
infrahub/transforms/tests/vrf_yang/input.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"data": {
|
||||
"InfraVRFDeviceAssignment": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"route_distinguisher": {
|
||||
"value": "10.0.250.13:1"
|
||||
},
|
||||
"vrf": {
|
||||
"node": {
|
||||
"name": {
|
||||
"value": "gold"
|
||||
},
|
||||
"description": {
|
||||
"value": "VRF gold - L3 VXLAN with symmetric IRB"
|
||||
},
|
||||
"route_distinguisher": {
|
||||
"value": null
|
||||
},
|
||||
"l3vni": {
|
||||
"node": {
|
||||
"vni": {
|
||||
"value": 100001
|
||||
},
|
||||
"vni_type": {
|
||||
"value": "l3vni"
|
||||
}
|
||||
}
|
||||
},
|
||||
"import_targets": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"target": {
|
||||
"value": "1:100001"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"export_targets": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"target": {
|
||||
"value": "1:100001"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"import_targets": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"target": {
|
||||
"value": "1:100001"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"export_targets": {
|
||||
"edges": [
|
||||
{
|
||||
"node": {
|
||||
"target": {
|
||||
"value": "1:100001"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
15
infrahub/transforms/tests/vrf_yang/output.json
Normal file
15
infrahub/transforms/tests/vrf_yang/output.json
Normal file
@@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"description": "VRF gold - L3 VXLAN with symmetric IRB",
|
||||
"export_targets": [
|
||||
"1:100001"
|
||||
],
|
||||
"import_targets": [
|
||||
"1:100001"
|
||||
],
|
||||
"l3vni": 100001,
|
||||
"redistribute_connected": true,
|
||||
"route_distinguisher": "10.0.250.13:1",
|
||||
"vrf_name": "gold"
|
||||
}
|
||||
]
|
||||
15
infrahub/transforms/tests/vrf_yang/test.yml
Normal file
15
infrahub/transforms/tests/vrf_yang/test.yml
Normal file
@@ -0,0 +1,15 @@
|
||||
---
|
||||
version: "1.0"
|
||||
infrahub_tests:
|
||||
- resource: Jinja2Transform
|
||||
resource_name: vrf_yang_transform
|
||||
tests:
|
||||
- name: smoke_check
|
||||
spec:
|
||||
kind: jinja2-transform-smoke
|
||||
- name: render_leaf3
|
||||
spec:
|
||||
kind: jinja2-transform-unit-render
|
||||
directory: infrahub/transforms/tests/vrf_yang
|
||||
input: input.json
|
||||
output: output.json
|
||||
Reference in New Issue
Block a user