## Summary Closes #22. Implements Infrahub Jinja2 transforms for MLAG configuration, generating Arista-native YANG-compatible JSON payloads from Infrahub intent data. | File | Description | |------|-------------| | `infrahub/transforms/queries/mlag_intent.gql` | GraphQL query — fetches `InfraMlagPeerConfig` by `$device_name`, including MLAG domain attributes, peer/iBGP VLANs, local interface SVI, and peer-link LAG | | `infrahub/transforms/templates/mlag_yang.j2` | Jinja2 template — renders a JSON object targeting `/arista-mlag-augments:mlag/config`; returns `[]` for devices with no MLAG (spines) | | `infrahub/transforms/tests/mlag_yang/test.yml` | Test spec: smoke check + unit render tests for `leaf1` and `spine1` | | `infrahub/transforms/tests/mlag_yang/leaf1/` | Mock input + expected output for leaf1 (full MLAG config, domain `leafs-1-2`) | | `infrahub/transforms/tests/mlag_yang/spine1/` | Mock input (empty edges) + expected output `[]` for spine1 | | `.infrahub.yml` | Registers `mlag_intent` query and `mlag_yang_transform` jinja2 transform | ## Output shape (leaf1) ```json { "mlag": { "config": { "domain-id": "leafs-1-2", "peer-link": "Port-Channel999", "local-interface": "Vlan4090", "peer-address": "10.0.199.255", "shutdown": false }, "dual_primary_detection": { "enabled": true, "delay": 10, "action": "errdisable", "heartbeat_peer_ip": "172.16.0.50", "heartbeat_vrf": "mgmt" }, "virtual_mac": "c001.cafe.babe", "peer_vlan_id": 4090, "ibgp_vlan_id": 4091, "local_interface_ip": "10.0.199.254/31" } } ``` ## Validation ```bash infrahubctl render mlag_yang_transform device_name=leaf1 # → MLAG config JSON infrahubctl render mlag_yang_transform device_name=spine1 # → [] ``` ## Design notes - Follows identical conventions to existing transforms (#20 VLAN/interface/VXLAN, #21 VRF/L3VNI) - All optional relationship accesses wrapped in `is defined and is not none` guards - Spine/non-MLAG devices return `[]` (empty array) — consistent with the "no data → empty collection" pattern used in vxlan_yang for devices without VTEP
This commit was merged in pull request #26.
This commit is contained in:
@@ -26,6 +26,8 @@ queries:
|
|||||||
file_path: infrahub/transforms/queries/vxlan_intent.gql
|
file_path: infrahub/transforms/queries/vxlan_intent.gql
|
||||||
- name: vrf_intent
|
- name: vrf_intent
|
||||||
file_path: infrahub/transforms/queries/vrf_intent.gql
|
file_path: infrahub/transforms/queries/vrf_intent.gql
|
||||||
|
- name: mlag_intent
|
||||||
|
file_path: infrahub/transforms/queries/mlag_intent.gql
|
||||||
|
|
||||||
jinja2_transforms:
|
jinja2_transforms:
|
||||||
- name: vlan_yang_transform
|
- name: vlan_yang_transform
|
||||||
@@ -44,3 +46,7 @@ jinja2_transforms:
|
|||||||
description: "Generate VRF/L3VNI configuration payload from Infrahub intent"
|
description: "Generate VRF/L3VNI configuration payload from Infrahub intent"
|
||||||
query: vrf_intent
|
query: vrf_intent
|
||||||
template_path: infrahub/transforms/templates/vrf_yang.j2
|
template_path: infrahub/transforms/templates/vrf_yang.j2
|
||||||
|
- name: mlag_yang_transform
|
||||||
|
description: "Generate MLAG configuration payload from Infrahub intent"
|
||||||
|
query: mlag_intent
|
||||||
|
template_path: infrahub/transforms/templates/mlag_yang.j2
|
||||||
|
|||||||
53
infrahub/transforms/queries/mlag_intent.gql
Normal file
53
infrahub/transforms/queries/mlag_intent.gql
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
query MlagIntent($device_name: String!) {
|
||||||
|
InfraMlagPeerConfig(device__name__value: $device_name) {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
local_interface_ip { value }
|
||||||
|
peer_address { value }
|
||||||
|
heartbeat_peer_ip { value }
|
||||||
|
device {
|
||||||
|
node {
|
||||||
|
name { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mlag_domain {
|
||||||
|
node {
|
||||||
|
domain_id { value }
|
||||||
|
virtual_mac { value }
|
||||||
|
heartbeat_vrf { value }
|
||||||
|
dual_primary_detection { value }
|
||||||
|
dual_primary_delay { value }
|
||||||
|
dual_primary_action { value }
|
||||||
|
devices {
|
||||||
|
edges {
|
||||||
|
node {
|
||||||
|
name { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peer_vlan {
|
||||||
|
node {
|
||||||
|
vlan_id { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ibgp_vlan {
|
||||||
|
node {
|
||||||
|
vlan_id { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
local_interface {
|
||||||
|
node {
|
||||||
|
name { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peer_link {
|
||||||
|
node {
|
||||||
|
name { value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
90
infrahub/transforms/templates/mlag_yang.j2
Normal file
90
infrahub/transforms/templates/mlag_yang.j2
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
{#
|
||||||
|
mlag_yang.j2 — Produce a JSON object with the MLAG configuration payload
|
||||||
|
for a single device, targeting Arista-native YANG paths.
|
||||||
|
|
||||||
|
Input: GraphQL response from mlag_intent query.
|
||||||
|
Returns an empty array [] if the device has no MLAG peer config (e.g. spines).
|
||||||
|
|
||||||
|
Output structure:
|
||||||
|
{
|
||||||
|
"mlag": {
|
||||||
|
"config": { ... }, # /arista-mlag-augments:mlag/config
|
||||||
|
"dual_primary_detection": { ... },
|
||||||
|
"virtual_mac": "...",
|
||||||
|
"peer_vlan_id": ...,
|
||||||
|
"ibgp_vlan_id": ...,
|
||||||
|
"local_interface_ip": "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#}
|
||||||
|
{%- set peer_configs = data.InfraMlagPeerConfig.edges -%}
|
||||||
|
|
||||||
|
{%- if peer_configs | length == 0 -%}
|
||||||
|
[]
|
||||||
|
{%- else -%}
|
||||||
|
{%- set cfg = peer_configs[0].node -%}
|
||||||
|
|
||||||
|
{#— Resolve local interface name —#}
|
||||||
|
{%- set local_iface_name = none -%}
|
||||||
|
{%- if cfg.local_interface is defined and cfg.local_interface is not none and cfg.local_interface.node is not none -%}
|
||||||
|
{%- set local_iface_name = cfg.local_interface.node.name.value -%}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{#— Resolve peer-link name —#}
|
||||||
|
{%- set peer_link_name = none -%}
|
||||||
|
{%- if cfg.peer_link is defined and cfg.peer_link is not none and cfg.peer_link.node is not none -%}
|
||||||
|
{%- set peer_link_name = cfg.peer_link.node.name.value -%}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{#— Resolve MLAG domain attributes —#}
|
||||||
|
{%- set domain_id = none -%}
|
||||||
|
{%- set virtual_mac = none -%}
|
||||||
|
{%- set heartbeat_vrf = none -%}
|
||||||
|
{%- set dual_primary_detection = false -%}
|
||||||
|
{%- set dual_primary_delay = none -%}
|
||||||
|
{%- set dual_primary_action = none -%}
|
||||||
|
{%- set peer_vlan_id = none -%}
|
||||||
|
{%- set ibgp_vlan_id = none -%}
|
||||||
|
|
||||||
|
{%- if cfg.mlag_domain is defined and cfg.mlag_domain is not none and cfg.mlag_domain.node is not none -%}
|
||||||
|
{%- set domain = cfg.mlag_domain.node -%}
|
||||||
|
{%- set domain_id = domain.domain_id.value -%}
|
||||||
|
{%- set virtual_mac = domain.virtual_mac.value | default(none) -%}
|
||||||
|
{%- set heartbeat_vrf = domain.heartbeat_vrf.value | default("mgmt") -%}
|
||||||
|
{%- set dual_primary_detection = domain.dual_primary_detection.value | default(false) -%}
|
||||||
|
{%- set dual_primary_delay = domain.dual_primary_delay.value | default(none) -%}
|
||||||
|
{%- set dual_primary_action = domain.dual_primary_action.value | default(none) -%}
|
||||||
|
|
||||||
|
{%- if domain.peer_vlan is defined and domain.peer_vlan is not none and domain.peer_vlan.node is not none -%}
|
||||||
|
{%- set peer_vlan_id = domain.peer_vlan.node.vlan_id.value -%}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{%- if domain.ibgp_vlan is defined and domain.ibgp_vlan is not none and domain.ibgp_vlan.node is not none -%}
|
||||||
|
{%- set ibgp_vlan_id = domain.ibgp_vlan.node.vlan_id.value -%}
|
||||||
|
{%- endif -%}
|
||||||
|
{%- endif -%}
|
||||||
|
|
||||||
|
{%- set result = {
|
||||||
|
"mlag": {
|
||||||
|
"config": {
|
||||||
|
"domain-id": domain_id,
|
||||||
|
"peer-link": peer_link_name,
|
||||||
|
"local-interface": local_iface_name,
|
||||||
|
"peer-address": cfg.peer_address.value,
|
||||||
|
"shutdown": false
|
||||||
|
},
|
||||||
|
"dual_primary_detection": {
|
||||||
|
"enabled": dual_primary_detection,
|
||||||
|
"delay": dual_primary_delay,
|
||||||
|
"action": dual_primary_action,
|
||||||
|
"heartbeat_peer_ip": cfg.heartbeat_peer_ip.value,
|
||||||
|
"heartbeat_vrf": heartbeat_vrf
|
||||||
|
},
|
||||||
|
"virtual_mac": virtual_mac,
|
||||||
|
"peer_vlan_id": peer_vlan_id,
|
||||||
|
"ibgp_vlan_id": ibgp_vlan_id,
|
||||||
|
"local_interface_ip": cfg.local_interface_ip.value
|
||||||
|
}
|
||||||
|
} -%}
|
||||||
|
{{ result | tojson(indent=2) }}
|
||||||
|
{%- endif -%}
|
||||||
96
infrahub/transforms/tests/mlag_yang/leaf1/input.json
Normal file
96
infrahub/transforms/tests/mlag_yang/leaf1/input.json
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"InfraMlagPeerConfig": {
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"local_interface_ip": {
|
||||||
|
"value": "10.0.199.254/31"
|
||||||
|
},
|
||||||
|
"peer_address": {
|
||||||
|
"value": "10.0.199.255"
|
||||||
|
},
|
||||||
|
"heartbeat_peer_ip": {
|
||||||
|
"value": "172.16.0.50"
|
||||||
|
},
|
||||||
|
"device": {
|
||||||
|
"node": {
|
||||||
|
"name": {
|
||||||
|
"value": "leaf1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mlag_domain": {
|
||||||
|
"node": {
|
||||||
|
"domain_id": {
|
||||||
|
"value": "leafs-1-2"
|
||||||
|
},
|
||||||
|
"virtual_mac": {
|
||||||
|
"value": "c001.cafe.babe"
|
||||||
|
},
|
||||||
|
"heartbeat_vrf": {
|
||||||
|
"value": "mgmt"
|
||||||
|
},
|
||||||
|
"dual_primary_detection": {
|
||||||
|
"value": true
|
||||||
|
},
|
||||||
|
"dual_primary_delay": {
|
||||||
|
"value": 10
|
||||||
|
},
|
||||||
|
"dual_primary_action": {
|
||||||
|
"value": "errdisable"
|
||||||
|
},
|
||||||
|
"devices": {
|
||||||
|
"edges": [
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"name": {
|
||||||
|
"value": "leaf1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"node": {
|
||||||
|
"name": {
|
||||||
|
"value": "leaf2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"peer_vlan": {
|
||||||
|
"node": {
|
||||||
|
"vlan_id": {
|
||||||
|
"value": 4090
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ibgp_vlan": {
|
||||||
|
"node": {
|
||||||
|
"vlan_id": {
|
||||||
|
"value": 4091
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"local_interface": {
|
||||||
|
"node": {
|
||||||
|
"name": {
|
||||||
|
"value": "Vlan4090"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"peer_link": {
|
||||||
|
"node": {
|
||||||
|
"name": {
|
||||||
|
"value": "Port-Channel999"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
infrahub/transforms/tests/mlag_yang/leaf1/output.json
Normal file
22
infrahub/transforms/tests/mlag_yang/leaf1/output.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"mlag": {
|
||||||
|
"config": {
|
||||||
|
"domain-id": "leafs-1-2",
|
||||||
|
"peer-link": "Port-Channel999",
|
||||||
|
"local-interface": "Vlan4090",
|
||||||
|
"peer-address": "10.0.199.255",
|
||||||
|
"shutdown": false
|
||||||
|
},
|
||||||
|
"dual_primary_detection": {
|
||||||
|
"enabled": true,
|
||||||
|
"delay": 10,
|
||||||
|
"action": "errdisable",
|
||||||
|
"heartbeat_peer_ip": "172.16.0.50",
|
||||||
|
"heartbeat_vrf": "mgmt"
|
||||||
|
},
|
||||||
|
"virtual_mac": "c001.cafe.babe",
|
||||||
|
"peer_vlan_id": 4090,
|
||||||
|
"ibgp_vlan_id": 4091,
|
||||||
|
"local_interface_ip": "10.0.199.254/31"
|
||||||
|
}
|
||||||
|
}
|
||||||
7
infrahub/transforms/tests/mlag_yang/spine1/input.json
Normal file
7
infrahub/transforms/tests/mlag_yang/spine1/input.json
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"data": {
|
||||||
|
"InfraMlagPeerConfig": {
|
||||||
|
"edges": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
infrahub/transforms/tests/mlag_yang/spine1/output.json
Normal file
1
infrahub/transforms/tests/mlag_yang/spine1/output.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
[]
|
||||||
21
infrahub/transforms/tests/mlag_yang/test.yml
Normal file
21
infrahub/transforms/tests/mlag_yang/test.yml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
version: "1.0"
|
||||||
|
infrahub_tests:
|
||||||
|
- resource: Jinja2Transform
|
||||||
|
resource_name: mlag_yang_transform
|
||||||
|
tests:
|
||||||
|
- name: smoke_check
|
||||||
|
spec:
|
||||||
|
kind: jinja2-transform-smoke
|
||||||
|
- name: render_leaf1
|
||||||
|
spec:
|
||||||
|
kind: jinja2-transform-unit-render
|
||||||
|
directory: infrahub/transforms/tests/mlag_yang/leaf1
|
||||||
|
input: input.json
|
||||||
|
output: output.json
|
||||||
|
- name: render_spine1
|
||||||
|
spec:
|
||||||
|
kind: jinja2-transform-unit-render
|
||||||
|
directory: infrahub/transforms/tests/mlag_yang/spine1
|
||||||
|
input: input.json
|
||||||
|
output: output.json
|
||||||
Reference in New Issue
Block a user