From 584a4ad5a47e827744fd1fd38818b7519c7f9464 Mon Sep 17 00:00:00 2001 From: Damien Date: Sat, 28 Feb 2026 11:00:24 +0100 Subject: [PATCH 1/4] feat: Add Infrahub Jinja2 transforms for VLANs, interfaces, and VXLAN (#20) Add three GraphQL queries, Jinja2 templates, and integration tests for generating YANG-style JSON configuration payloads from Infrahub intent data, suitable for gNMI Set operations on Arista EOS devices. Queries (transforms/queries/): - vlan_intent.gql: Fetches VLANs for a device via VTEP mappings and SVI interfaces, including VNI associations. - interface_intent.gql: Fetches all interface types (loopback, ethernet, vlan, lag) with IP addresses and type-specific attributes. - vxlan_intent.gql: Fetches VTEP config, VLAN-to-VNI mappings, and VRF-to-VNI mappings (L3VNI) via VRF device assignments. Templates (transforms/templates/): - vlan_yang.j2: Merges VLANs from both VTEP and SVI sources, deduplicates by vlan_id, and emits a sorted JSON array. - interface_yang.j2: Emits a JSON array of interfaces sorted by name, with a "type" discriminator field for each interface kind. - vxlan_yang.j2: Emits a JSON object with vtep config, vlan_vni_mappings, and vrf_vni_mappings sections. Integration tests (transforms/tests/): - One test directory per transform with input.json (sample GraphQL response for leaf1), output.json (expected result), and test.yml config. - Test data reflects the lab topology: leaf1 VTEP 10.0.255.11, VLAN 40 / VNI 110040, MLAG VLANs 4090/4091, underlay Ethernet11/12. .infrahub.yml updated with queries and jinja2_transforms sections. Co-Authored-By: Claude Sonnet 4.6 --- .infrahub.yml | 22 ++ transforms/queries/interface_intent.gql | 155 +++++++++ transforms/queries/vlan_intent.gql | 92 +++++ transforms/queries/vxlan_intent.gql | 100 ++++++ transforms/templates/interface_yang.j2 | 101 ++++++ transforms/templates/vlan_yang.j2 | 67 ++++ transforms/templates/vxlan_yang.j2 | 88 +++++ transforms/tests/interface_yang/input.json | 350 ++++++++++++++++++++ transforms/tests/interface_yang/output.json | 106 ++++++ transforms/tests/interface_yang/test.yml | 13 + transforms/tests/vlan_yang/input.json | 116 +++++++ transforms/tests/vlan_yang/output.json | 32 ++ transforms/tests/vlan_yang/test.yml | 13 + transforms/tests/vxlan_yang/input.json | 62 ++++ transforms/tests/vxlan_yang/output.json | 17 + transforms/tests/vxlan_yang/test.yml | 13 + 16 files changed, 1347 insertions(+) create mode 100644 transforms/queries/interface_intent.gql create mode 100644 transforms/queries/vlan_intent.gql create mode 100644 transforms/queries/vxlan_intent.gql create mode 100644 transforms/templates/interface_yang.j2 create mode 100644 transforms/templates/vlan_yang.j2 create mode 100644 transforms/templates/vxlan_yang.j2 create mode 100644 transforms/tests/interface_yang/input.json create mode 100644 transforms/tests/interface_yang/output.json create mode 100644 transforms/tests/interface_yang/test.yml create mode 100644 transforms/tests/vlan_yang/input.json create mode 100644 transforms/tests/vlan_yang/output.json create mode 100644 transforms/tests/vlan_yang/test.yml create mode 100644 transforms/tests/vxlan_yang/input.json create mode 100644 transforms/tests/vxlan_yang/output.json create mode 100644 transforms/tests/vxlan_yang/test.yml diff --git a/.infrahub.yml b/.infrahub.yml index a2e5752..ed74a4f 100644 --- a/.infrahub.yml +++ b/.infrahub.yml @@ -16,3 +16,25 @@ objects: - infrahub/objects/10-bgp-sessions.yml - infrahub/objects/11-vrfs.yml - infrahub/objects/12-mlag.yml + +queries: + - name: vlan_intent + file_path: transforms/queries/vlan_intent.gql + - name: interface_intent + file_path: transforms/queries/interface_intent.gql + - name: vxlan_intent + file_path: transforms/queries/vxlan_intent.gql + +jinja2_transforms: + - name: vlan_yang_transform + description: "Generate VLAN configuration payload from Infrahub intent" + query: vlan_intent + template_path: transforms/templates/vlan_yang.j2 + - name: interface_yang_transform + description: "Generate interface configuration payload from Infrahub intent" + query: interface_intent + template_path: transforms/templates/interface_yang.j2 + - name: vxlan_yang_transform + description: "Generate VXLAN/VTEP configuration payload from Infrahub intent" + query: vxlan_intent + template_path: transforms/templates/vxlan_yang.j2 diff --git a/transforms/queries/interface_intent.gql b/transforms/queries/interface_intent.gql new file mode 100644 index 0000000..1cb1106 --- /dev/null +++ b/transforms/queries/interface_intent.gql @@ -0,0 +1,155 @@ +query InterfaceIntent($device_name: String!) { + # Loopback interfaces + InfraInterfaceLoopback(device__name__value: $device_name) { + edges { + node { + name { + value + } + description { + value + } + enabled { + value + } + mtu { + value + } + ip_addresses { + edges { + node { + address { + value + } + } + } + } + } + } + } + # Ethernet interfaces + InfraInterfaceEthernet(device__name__value: $device_name) { + edges { + node { + name { + value + } + description { + value + } + enabled { + value + } + mtu { + value + } + speed { + value + } + mode { + value + } + lag { + node { + name { + value + } + } + } + ip_addresses { + edges { + node { + address { + value + } + } + } + } + } + } + } + # VLAN SVI interfaces + InfraInterfaceVlan(device__name__value: $device_name) { + edges { + node { + name { + value + } + description { + value + } + enabled { + value + } + mtu { + value + } + virtual_router_address { + value + } + autostate { + value + } + vlan { + node { + vlan_id { + value + } + } + } + ip_addresses { + edges { + node { + address { + value + } + } + } + } + } + } + } + # LAG / Port-Channel interfaces + InfraInterfaceLag(device__name__value: $device_name) { + edges { + node { + name { + value + } + description { + value + } + enabled { + value + } + mtu { + value + } + lacp_mode { + value + } + mlag_id { + value + } + members { + edges { + node { + name { + value + } + } + } + } + ip_addresses { + edges { + node { + address { + value + } + } + } + } + } + } + } +} diff --git a/transforms/queries/vlan_intent.gql b/transforms/queries/vlan_intent.gql new file mode 100644 index 0000000..bcd0f7b --- /dev/null +++ b/transforms/queries/vlan_intent.gql @@ -0,0 +1,92 @@ +query VlanIntent($device_name: String!) { + # VLANs reachable via VTEP VLAN-VNI mappings (L2/VXLAN VLANs) + InfraVTEP(device__name__value: $device_name) { + edges { + node { + source_address { + value + } + udp_port { + value + } + vlan_vni_mappings { + edges { + node { + vlan { + node { + vlan_id { + value + } + name { + value + } + status { + value + } + vlan_type { + value + } + trunk_groups { + value + } + stp_enabled { + value + } + vni { + node { + vni { + value + } + vni_type { + value + } + } + } + } + } + } + } + } + } + } + } + # VLANs reachable via SVI interfaces (MLAG, routing, and other local VLANs) + InfraInterfaceVlan(device__name__value: $device_name) { + edges { + node { + vlan { + node { + vlan_id { + value + } + name { + value + } + status { + value + } + vlan_type { + value + } + trunk_groups { + value + } + stp_enabled { + value + } + vni { + node { + vni { + value + } + vni_type { + value + } + } + } + } + } + } + } + } +} diff --git a/transforms/queries/vxlan_intent.gql b/transforms/queries/vxlan_intent.gql new file mode 100644 index 0000000..ed182c6 --- /dev/null +++ b/transforms/queries/vxlan_intent.gql @@ -0,0 +1,100 @@ +query VxlanIntent($device_name: String!) { + # VTEP configuration for the device + InfraVTEP(device__name__value: $device_name) { + edges { + node { + source_address { + value + } + udp_port { + value + } + learn_restrict { + value + } + source_interface { + node { + name { + value + } + } + } + # VLAN-to-VNI mappings (L2 VXLAN) + vlan_vni_mappings { + edges { + node { + description { + value + } + vlan { + node { + vlan_id { + value + } + name { + value + } + } + } + vni { + node { + vni { + value + } + vni_type { + value + } + } + } + } + } + } + } + } + } + # VRF-to-VNI mappings (L3 VXLAN) via VRF device assignments + InfraVRFDeviceAssignment(device__name__value: $device_name) { + edges { + node { + route_distinguisher { + value + } + vrf { + node { + name { + value + } + l3vni { + node { + vni { + value + } + vni_type { + value + } + } + } + import_targets { + edges { + node { + target { + value + } + } + } + } + export_targets { + edges { + node { + target { + value + } + } + } + } + } + } + } + } + } +} diff --git a/transforms/templates/interface_yang.j2 b/transforms/templates/interface_yang.j2 new file mode 100644 index 0000000..f0164ea --- /dev/null +++ b/transforms/templates/interface_yang.j2 @@ -0,0 +1,101 @@ +{# + interface_yang.j2 — Produce a JSON array of interface configuration objects. + + Input: GraphQL response from interface_intent query. + Returns all interface types (loopback, ethernet, vlan, lag) for the device, + each with a "type" discriminator and type-specific attributes. +#} +{%- set interfaces = [] -%} + +{#— Loopback interfaces —#} +{%- for edge in data.InfraInterfaceLoopback.edges -%} + {%- set iface = edge.node -%} + {%- set ip_list = [] -%} + {%- for ip_edge in iface.ip_addresses.edges -%} + {%- set _ = ip_list.append(ip_edge.node.address.value) -%} + {%- endfor -%} + {%- set _ = interfaces.append({ + "type": "loopback", + "name": iface.name.value, + "description": iface.description.value, + "enabled": iface.enabled.value, + "mtu": iface.mtu.value, + "ip_addresses": ip_list + }) -%} +{%- endfor -%} + +{#— Ethernet interfaces —#} +{%- for edge in data.InfraInterfaceEthernet.edges -%} + {%- set iface = edge.node -%} + {%- set ip_list = [] -%} + {%- for ip_edge in iface.ip_addresses.edges -%} + {%- set _ = ip_list.append(ip_edge.node.address.value) -%} + {%- endfor -%} + {%- set lag_name = null -%} + {%- if iface.lag and iface.lag.node -%} + {%- set lag_name = iface.lag.node.name.value -%} + {%- endif -%} + {%- set _ = interfaces.append({ + "type": "ethernet", + "name": iface.name.value, + "description": iface.description.value, + "enabled": iface.enabled.value, + "mtu": iface.mtu.value, + "speed": iface.speed.value, + "mode": iface.mode.value, + "lag": lag_name, + "ip_addresses": ip_list + }) -%} +{%- endfor -%} + +{#— VLAN SVI interfaces —#} +{%- for edge in data.InfraInterfaceVlan.edges -%} + {%- set iface = edge.node -%} + {%- set ip_list = [] -%} + {%- for ip_edge in iface.ip_addresses.edges -%} + {%- set _ = ip_list.append(ip_edge.node.address.value) -%} + {%- endfor -%} + {%- set vlan_id = null -%} + {%- if iface.vlan and iface.vlan.node -%} + {%- set vlan_id = iface.vlan.node.vlan_id.value -%} + {%- endif -%} + {%- set _ = interfaces.append({ + "type": "vlan", + "name": iface.name.value, + "description": iface.description.value, + "enabled": iface.enabled.value, + "mtu": iface.mtu.value, + "vlan_id": vlan_id, + "virtual_router_address": iface.virtual_router_address.value, + "autostate": iface.autostate.value, + "ip_addresses": ip_list + }) -%} +{%- endfor -%} + +{#— LAG / Port-Channel interfaces —#} +{%- for edge in data.InfraInterfaceLag.edges -%} + {%- set iface = edge.node -%} + {%- set ip_list = [] -%} + {%- for ip_edge in iface.ip_addresses.edges -%} + {%- set _ = ip_list.append(ip_edge.node.address.value) -%} + {%- endfor -%} + {%- set member_list = [] -%} + {%- for member_edge in iface.members.edges -%} + {%- set _ = member_list.append(member_edge.node.name.value) -%} + {%- endfor -%} + {%- set _ = interfaces.append({ + "type": "lag", + "name": iface.name.value, + "description": iface.description.value, + "enabled": iface.enabled.value, + "mtu": iface.mtu.value, + "lacp_mode": iface.lacp_mode.value, + "mlag_id": iface.mlag_id.value, + "members": member_list, + "ip_addresses": ip_list + }) -%} +{%- endfor -%} + +{#— Sort by name and emit JSON array —#} +{%- set sorted_ifaces = interfaces | sort(attribute='name') -%} +{{ sorted_ifaces | tojson(indent=2) }} diff --git a/transforms/templates/vlan_yang.j2 b/transforms/templates/vlan_yang.j2 new file mode 100644 index 0000000..bd7a057 --- /dev/null +++ b/transforms/templates/vlan_yang.j2 @@ -0,0 +1,67 @@ +{# + vlan_yang.j2 — Produce a JSON array of VLAN configuration objects. + + Input: GraphQL response from vlan_intent query. + The query returns VLANs from two sources: + 1. data.InfraVTEP[].vlan_vni_mappings[].vlan (L2/VXLAN VLANs) + 2. data.InfraInterfaceVlan[].vlan (SVI/routing/MLAG VLANs) + + We merge both sources, deduplicate by vlan_id, and emit one object per VLAN. +#} +{%- set vlans = {} -%} + +{#— Collect VLANs from VTEP VLAN-VNI mappings —#} +{%- for vtep_edge in data.InfraVTEP.edges -%} + {%- for mapping_edge in vtep_edge.node.vlan_vni_mappings.edges -%} + {%- set vlan_node = mapping_edge.node.vlan.node -%} + {%- set vid = vlan_node.vlan_id.value | string -%} + {%- if vid not in vlans -%} + {%- set vni_val = null -%} + {%- set vni_type_val = null -%} + {%- if vlan_node.vni and vlan_node.vni.node -%} + {%- set vni_val = vlan_node.vni.node.vni.value -%} + {%- set vni_type_val = vlan_node.vni.node.vni_type.value -%} + {%- endif -%} + {%- set _ = vlans.update({vid: { + "vlan_id": vlan_node.vlan_id.value, + "name": vlan_node.name.value, + "status": vlan_node.status.value | upper, + "vlan_type": vlan_node.vlan_type.value, + "trunk_groups": vlan_node.trunk_groups.value if vlan_node.trunk_groups.value else [], + "stp_enabled": vlan_node.stp_enabled.value, + "vni": vni_val, + "vni_type": vni_type_val + }}) -%} + {%- endif -%} + {%- endfor -%} +{%- endfor -%} + +{#— Collect VLANs from SVI interfaces —#} +{%- for svi_edge in data.InfraInterfaceVlan.edges -%} + {%- if svi_edge.node.vlan and svi_edge.node.vlan.node -%} + {%- set vlan_node = svi_edge.node.vlan.node -%} + {%- set vid = vlan_node.vlan_id.value | string -%} + {%- if vid not in vlans -%} + {%- set vni_val = null -%} + {%- set vni_type_val = null -%} + {%- if vlan_node.vni and vlan_node.vni.node -%} + {%- set vni_val = vlan_node.vni.node.vni.value -%} + {%- set vni_type_val = vlan_node.vni.node.vni_type.value -%} + {%- endif -%} + {%- set _ = vlans.update({vid: { + "vlan_id": vlan_node.vlan_id.value, + "name": vlan_node.name.value, + "status": vlan_node.status.value | upper, + "vlan_type": vlan_node.vlan_type.value, + "trunk_groups": vlan_node.trunk_groups.value if vlan_node.trunk_groups.value else [], + "stp_enabled": vlan_node.stp_enabled.value, + "vni": vni_val, + "vni_type": vni_type_val + }}) -%} + {%- endif -%} + {%- endif -%} +{%- endfor -%} + +{#— Sort by vlan_id and emit JSON array —#} +{%- set sorted_vlans = vlans.values() | sort(attribute='vlan_id') -%} +{{ sorted_vlans | tojson(indent=2) }} diff --git a/transforms/templates/vxlan_yang.j2 b/transforms/templates/vxlan_yang.j2 new file mode 100644 index 0000000..62f3280 --- /dev/null +++ b/transforms/templates/vxlan_yang.j2 @@ -0,0 +1,88 @@ +{# + vxlan_yang.j2 — Produce a JSON object representing the VXLAN/VTEP configuration + for the device, including VLAN-to-VNI mappings and VRF-to-VNI mappings. + + Input: GraphQL response from vxlan_intent query. +#} +{%- set vtep_edges = data.InfraVTEP.edges -%} +{%- set vrf_edges = data.InfraVRFDeviceAssignment.edges -%} + +{#— Build VTEP section (there is one VTEP per device) —#} +{%- if vtep_edges | length > 0 -%} + {%- set vtep = vtep_edges[0].node -%} + {%- set source_iface = null -%} + {%- if vtep.source_interface and vtep.source_interface.node -%} + {%- set source_iface = vtep.source_interface.node.name.value -%} + {%- endif -%} + + {#— Build VLAN-to-VNI mapping list —#} + {%- set vlan_vni_list = [] -%} + {%- for mapping_edge in vtep.vlan_vni_mappings.edges -%} + {%- set m = mapping_edge.node -%} + {%- set vlan_id = null -%} + {%- set vlan_name = null -%} + {%- if m.vlan and m.vlan.node -%} + {%- set vlan_id = m.vlan.node.vlan_id.value -%} + {%- set vlan_name = m.vlan.node.name.value -%} + {%- endif -%} + {%- set vni_val = null -%} + {%- set vni_type_val = null -%} + {%- if m.vni and m.vni.node -%} + {%- set vni_val = m.vni.node.vni.value -%} + {%- set vni_type_val = m.vni.node.vni_type.value -%} + {%- endif -%} + {%- set _ = vlan_vni_list.append({ + "vlan_id": vlan_id, + "vlan_name": vlan_name, + "vni": vni_val, + "vni_type": vni_type_val + }) -%} + {%- endfor -%} + + {#— Build VRF-to-VNI mapping list —#} + {%- set vrf_vni_list = [] -%} + {%- for vrf_edge in vrf_edges -%} + {%- set assignment = vrf_edge.node -%} + {%- set vrf_name = null -%} + {%- set l3vni = null -%} + {%- set import_rts = [] -%} + {%- set export_rts = [] -%} + {%- if assignment.vrf and assignment.vrf.node -%} + {%- set vrf_node = assignment.vrf.node -%} + {%- set vrf_name = vrf_node.name.value -%} + {%- if vrf_node.l3vni and vrf_node.l3vni.node -%} + {%- set l3vni = vrf_node.l3vni.node.vni.value -%} + {%- endif -%} + {%- for rt_edge in vrf_node.import_targets.edges -%} + {%- set _ = import_rts.append(rt_edge.node.target.value) -%} + {%- endfor -%} + {%- for rt_edge in vrf_node.export_targets.edges -%} + {%- set _ = export_rts.append(rt_edge.node.target.value) -%} + {%- endfor -%} + {%- endif -%} + {%- set _ = vrf_vni_list.append({ + "vrf": vrf_name, + "l3vni": l3vni, + "route_distinguisher": assignment.route_distinguisher.value, + "import_targets": import_rts, + "export_targets": export_rts + }) -%} + {%- endfor -%} + + {%- set result = { + "vtep": { + "source_address": vtep.source_address.value, + "source_interface": source_iface, + "udp_port": vtep.udp_port.value, + "learn_restrict": vtep.learn_restrict.value, + "vlan_vni_mappings": vlan_vni_list | sort(attribute='vlan_id'), + "vrf_vni_mappings": vrf_vni_list + } + } -%} +{%- else -%} + {%- set result = { + "vtep": null, + "vrf_vni_mappings": [] + } -%} +{%- endif -%} +{{ result | tojson(indent=2) }} diff --git a/transforms/tests/interface_yang/input.json b/transforms/tests/interface_yang/input.json new file mode 100644 index 0000000..815043e --- /dev/null +++ b/transforms/tests/interface_yang/input.json @@ -0,0 +1,350 @@ +{ + "data": { + "InfraInterfaceLoopback": { + "edges": [ + { + "node": { + "name": { + "value": "Loopback0" + }, + "description": { + "value": "Router-ID" + }, + "enabled": { + "value": true + }, + "mtu": { + "value": null + }, + "ip_addresses": { + "edges": [ + { + "node": { + "address": { + "value": "10.0.250.11/32" + } + } + } + ] + } + } + }, + { + "node": { + "name": { + "value": "Loopback1" + }, + "description": { + "value": "VTEP" + }, + "enabled": { + "value": true + }, + "mtu": { + "value": null + }, + "ip_addresses": { + "edges": [ + { + "node": { + "address": { + "value": "10.0.255.11/32" + } + } + } + ] + } + } + } + ] + }, + "InfraInterfaceEthernet": { + "edges": [ + { + "node": { + "name": { + "value": "Ethernet1" + }, + "description": { + "value": "host1" + }, + "enabled": { + "value": true + }, + "mtu": { + "value": null + }, + "speed": { + "value": null + }, + "mode": { + "value": "trunk" + }, + "lag": { + "node": { + "name": { + "value": "Port-Channel1" + } + } + }, + "ip_addresses": { + "edges": [] + } + } + }, + { + "node": { + "name": { + "value": "Ethernet10" + }, + "description": { + "value": "mlag peer link" + }, + "enabled": { + "value": true + }, + "mtu": { + "value": null + }, + "speed": { + "value": null + }, + "mode": { + "value": "trunk" + }, + "lag": { + "node": { + "name": { + "value": "Port-Channel999" + } + } + }, + "ip_addresses": { + "edges": [] + } + } + }, + { + "node": { + "name": { + "value": "Ethernet11" + }, + "description": { + "value": "spine1" + }, + "enabled": { + "value": true + }, + "mtu": { + "value": 9214 + }, + "speed": { + "value": null + }, + "mode": { + "value": "routed" + }, + "lag": null, + "ip_addresses": { + "edges": [ + { + "node": { + "address": { + "value": "10.0.1.1/31" + } + } + } + ] + } + } + }, + { + "node": { + "name": { + "value": "Ethernet12" + }, + "description": { + "value": "spine2" + }, + "enabled": { + "value": true + }, + "mtu": { + "value": 9214 + }, + "speed": { + "value": null + }, + "mode": { + "value": "routed" + }, + "lag": null, + "ip_addresses": { + "edges": [ + { + "node": { + "address": { + "value": "10.0.2.1/31" + } + } + } + ] + } + } + } + ] + }, + "InfraInterfaceVlan": { + "edges": [ + { + "node": { + "name": { + "value": "Vlan4090" + }, + "description": { + "value": "MLAG Peer-Link" + }, + "enabled": { + "value": true + }, + "mtu": { + "value": null + }, + "virtual_router_address": { + "value": null + }, + "autostate": { + "value": false + }, + "vlan": { + "node": { + "vlan_id": { + "value": 4090 + } + } + }, + "ip_addresses": { + "edges": [ + { + "node": { + "address": { + "value": "10.255.255.0/31" + } + } + } + ] + } + } + }, + { + "node": { + "name": { + "value": "Vlan4091" + }, + "description": { + "value": "MLAG iBGP Peering" + }, + "enabled": { + "value": true + }, + "mtu": { + "value": 9214 + }, + "virtual_router_address": { + "value": null + }, + "autostate": { + "value": true + }, + "vlan": { + "node": { + "vlan_id": { + "value": 4091 + } + } + }, + "ip_addresses": { + "edges": [] + } + } + } + ] + }, + "InfraInterfaceLag": { + "edges": [ + { + "node": { + "name": { + "value": "Port-Channel1" + }, + "description": { + "value": "host1" + }, + "enabled": { + "value": true + }, + "mtu": { + "value": null + }, + "lacp_mode": { + "value": "active" + }, + "mlag_id": { + "value": 1 + }, + "members": { + "edges": [ + { + "node": { + "name": { + "value": "Ethernet1" + } + } + } + ] + }, + "ip_addresses": { + "edges": [] + } + } + }, + { + "node": { + "name": { + "value": "Port-Channel999" + }, + "description": { + "value": "MLAG Peer" + }, + "enabled": { + "value": true + }, + "mtu": { + "value": null + }, + "lacp_mode": { + "value": "active" + }, + "mlag_id": { + "value": null + }, + "members": { + "edges": [ + { + "node": { + "name": { + "value": "Ethernet10" + } + } + } + ] + }, + "ip_addresses": { + "edges": [] + } + } + } + ] + } + } +} diff --git a/transforms/tests/interface_yang/output.json b/transforms/tests/interface_yang/output.json new file mode 100644 index 0000000..f4f79e2 --- /dev/null +++ b/transforms/tests/interface_yang/output.json @@ -0,0 +1,106 @@ +[ + { + "type": "ethernet", + "name": "Ethernet1", + "description": "host1", + "enabled": true, + "mtu": null, + "speed": null, + "mode": "trunk", + "lag": "Port-Channel1", + "ip_addresses": [] + }, + { + "type": "ethernet", + "name": "Ethernet10", + "description": "mlag peer link", + "enabled": true, + "mtu": null, + "speed": null, + "mode": "trunk", + "lag": "Port-Channel999", + "ip_addresses": [] + }, + { + "type": "ethernet", + "name": "Ethernet11", + "description": "spine1", + "enabled": true, + "mtu": 9214, + "speed": null, + "mode": "routed", + "lag": null, + "ip_addresses": ["10.0.1.1/31"] + }, + { + "type": "ethernet", + "name": "Ethernet12", + "description": "spine2", + "enabled": true, + "mtu": 9214, + "speed": null, + "mode": "routed", + "lag": null, + "ip_addresses": ["10.0.2.1/31"] + }, + { + "type": "loopback", + "name": "Loopback0", + "description": "Router-ID", + "enabled": true, + "mtu": null, + "ip_addresses": ["10.0.250.11/32"] + }, + { + "type": "loopback", + "name": "Loopback1", + "description": "VTEP", + "enabled": true, + "mtu": null, + "ip_addresses": ["10.0.255.11/32"] + }, + { + "type": "lag", + "name": "Port-Channel1", + "description": "host1", + "enabled": true, + "mtu": null, + "lacp_mode": "active", + "mlag_id": 1, + "members": ["Ethernet1"], + "ip_addresses": [] + }, + { + "type": "lag", + "name": "Port-Channel999", + "description": "MLAG Peer", + "enabled": true, + "mtu": null, + "lacp_mode": "active", + "mlag_id": null, + "members": ["Ethernet10"], + "ip_addresses": [] + }, + { + "type": "vlan", + "name": "Vlan4090", + "description": "MLAG Peer-Link", + "enabled": true, + "mtu": null, + "vlan_id": 4090, + "virtual_router_address": null, + "autostate": false, + "ip_addresses": ["10.255.255.0/31"] + }, + { + "type": "vlan", + "name": "Vlan4091", + "description": "MLAG iBGP Peering", + "enabled": true, + "mtu": 9214, + "vlan_id": 4091, + "virtual_router_address": null, + "autostate": true, + "ip_addresses": [] + } +] diff --git a/transforms/tests/interface_yang/test.yml b/transforms/tests/interface_yang/test.yml new file mode 100644 index 0000000..040d385 --- /dev/null +++ b/transforms/tests/interface_yang/test.yml @@ -0,0 +1,13 @@ +--- +# Integration test for interface_yang_transform +# Usage: infrahubctl test transforms/tests/interface_yang/test.yml +apiVersion: infrahub.app/v1 +kind: IntegrationTest +spec: + kind: jinja2-transform-integration + name: interface_yang_transform_leaf1 + transform: interface_yang_transform + params: + device_name: leaf1 + input: input.json + output: output.json diff --git a/transforms/tests/vlan_yang/input.json b/transforms/tests/vlan_yang/input.json new file mode 100644 index 0000000..73469ec --- /dev/null +++ b/transforms/tests/vlan_yang/input.json @@ -0,0 +1,116 @@ +{ + "data": { + "InfraVTEP": { + "edges": [ + { + "node": { + "source_address": { + "value": "10.0.255.11" + }, + "udp_port": { + "value": 4789 + }, + "vlan_vni_mappings": { + "edges": [ + { + "node": { + "vlan": { + "node": { + "vlan_id": { + "value": 40 + }, + "name": { + "value": "test-l2-vxlan" + }, + "status": { + "value": "active" + }, + "vlan_type": { + "value": "standard" + }, + "trunk_groups": { + "value": [] + }, + "stp_enabled": { + "value": true + }, + "vni": { + "node": { + "vni": { + "value": 110040 + }, + "vni_type": { + "value": "l2vni" + } + } + } + } + } + } + } + ] + } + } + } + ] + }, + "InfraInterfaceVlan": { + "edges": [ + { + "node": { + "vlan": { + "node": { + "vlan_id": { + "value": 4090 + }, + "name": { + "value": "mlag-peer" + }, + "status": { + "value": "active" + }, + "vlan_type": { + "value": "mlag_peer" + }, + "trunk_groups": { + "value": ["mlag-peer"] + }, + "stp_enabled": { + "value": false + }, + "vni": null + } + } + } + }, + { + "node": { + "vlan": { + "node": { + "vlan_id": { + "value": 4091 + }, + "name": { + "value": "mlag-ibgp" + }, + "status": { + "value": "active" + }, + "vlan_type": { + "value": "mlag_ibgp" + }, + "trunk_groups": { + "value": ["mlag-peer"] + }, + "stp_enabled": { + "value": false + }, + "vni": null + } + } + } + } + ] + } + } +} diff --git a/transforms/tests/vlan_yang/output.json b/transforms/tests/vlan_yang/output.json new file mode 100644 index 0000000..a816e5a --- /dev/null +++ b/transforms/tests/vlan_yang/output.json @@ -0,0 +1,32 @@ +[ + { + "vlan_id": 40, + "name": "test-l2-vxlan", + "status": "ACTIVE", + "vlan_type": "standard", + "trunk_groups": [], + "stp_enabled": true, + "vni": 110040, + "vni_type": "l2vni" + }, + { + "vlan_id": 4090, + "name": "mlag-peer", + "status": "ACTIVE", + "vlan_type": "mlag_peer", + "trunk_groups": ["mlag-peer"], + "stp_enabled": false, + "vni": null, + "vni_type": null + }, + { + "vlan_id": 4091, + "name": "mlag-ibgp", + "status": "ACTIVE", + "vlan_type": "mlag_ibgp", + "trunk_groups": ["mlag-peer"], + "stp_enabled": false, + "vni": null, + "vni_type": null + } +] diff --git a/transforms/tests/vlan_yang/test.yml b/transforms/tests/vlan_yang/test.yml new file mode 100644 index 0000000..7546d61 --- /dev/null +++ b/transforms/tests/vlan_yang/test.yml @@ -0,0 +1,13 @@ +--- +# Integration test for vlan_yang_transform +# Usage: infrahubctl test transforms/tests/vlan_yang/test.yml +apiVersion: infrahub.app/v1 +kind: IntegrationTest +spec: + kind: jinja2-transform-integration + name: vlan_yang_transform_leaf1 + transform: vlan_yang_transform + params: + device_name: leaf1 + input: input.json + output: output.json diff --git a/transforms/tests/vxlan_yang/input.json b/transforms/tests/vxlan_yang/input.json new file mode 100644 index 0000000..69a19c5 --- /dev/null +++ b/transforms/tests/vxlan_yang/input.json @@ -0,0 +1,62 @@ +{ + "data": { + "InfraVTEP": { + "edges": [ + { + "node": { + "source_address": { + "value": "10.0.255.11" + }, + "udp_port": { + "value": 4789 + }, + "learn_restrict": { + "value": "any" + }, + "source_interface": { + "node": { + "name": { + "value": "Loopback1" + } + } + }, + "vlan_vni_mappings": { + "edges": [ + { + "node": { + "description": { + "value": "VLAN 40 <-> VNI 110040" + }, + "vlan": { + "node": { + "vlan_id": { + "value": 40 + }, + "name": { + "value": "test-l2-vxlan" + } + } + }, + "vni": { + "node": { + "vni": { + "value": 110040 + }, + "vni_type": { + "value": "l2vni" + } + } + } + } + } + ] + } + } + } + ] + }, + "InfraVRFDeviceAssignment": { + "edges": [] + } + } +} diff --git a/transforms/tests/vxlan_yang/output.json b/transforms/tests/vxlan_yang/output.json new file mode 100644 index 0000000..b8353da --- /dev/null +++ b/transforms/tests/vxlan_yang/output.json @@ -0,0 +1,17 @@ +{ + "vtep": { + "source_address": "10.0.255.11", + "source_interface": "Loopback1", + "udp_port": 4789, + "learn_restrict": "any", + "vlan_vni_mappings": [ + { + "vlan_id": 40, + "vlan_name": "test-l2-vxlan", + "vni": 110040, + "vni_type": "l2vni" + } + ], + "vrf_vni_mappings": [] + } +} diff --git a/transforms/tests/vxlan_yang/test.yml b/transforms/tests/vxlan_yang/test.yml new file mode 100644 index 0000000..f911870 --- /dev/null +++ b/transforms/tests/vxlan_yang/test.yml @@ -0,0 +1,13 @@ +--- +# Integration test for vxlan_yang_transform +# Usage: infrahubctl test transforms/tests/vxlan_yang/test.yml +apiVersion: infrahub.app/v1 +kind: IntegrationTest +spec: + kind: jinja2-transform-integration + name: vxlan_yang_transform_leaf1 + transform: vxlan_yang_transform + params: + device_name: leaf1 + input: input.json + output: output.json -- 2.53.0 From a415de85bd5099162ca99017b5ebb9f6662b9cef Mon Sep 17 00:00:00 2001 From: Damien Date: Sat, 28 Feb 2026 17:41:19 +0100 Subject: [PATCH 2/4] fix: use Jinja2 none instead of null and add defensive checks for optional relationships (#20) Replace all `null` literals with `none` (valid Jinja2/Python) in all three templates. Add explicit `is defined and is not none and .node is not none` guards before accessing nested nodes on optional GraphQL relationships, to avoid `Undefined is not JSON serializable` errors at render time. Also add `| default(none)` on optional scalar attributes (speed, mode, lacp_mode, mlag_id, mtu, description, virtual_router_address, route_distinguisher, learn_restrict, vlan_type, trunk_groups) so that Infrahub `Undefined` values from unset optional fields are safely coerced to JSON null rather than crashing tojson serialization. Co-Authored-By: Claude Sonnet 4.6 --- transforms/templates/interface_yang.j2 | 34 +++++++++++++------------- transforms/templates/vlan_yang.j2 | 26 ++++++++++---------- transforms/templates/vxlan_yang.j2 | 30 +++++++++++------------ 3 files changed, 45 insertions(+), 45 deletions(-) diff --git a/transforms/templates/interface_yang.j2 b/transforms/templates/interface_yang.j2 index f0164ea..2739c2b 100644 --- a/transforms/templates/interface_yang.j2 +++ b/transforms/templates/interface_yang.j2 @@ -17,9 +17,9 @@ {%- set _ = interfaces.append({ "type": "loopback", "name": iface.name.value, - "description": iface.description.value, + "description": iface.description.value | default(none), "enabled": iface.enabled.value, - "mtu": iface.mtu.value, + "mtu": iface.mtu.value | default(none), "ip_addresses": ip_list }) -%} {%- endfor -%} @@ -31,18 +31,18 @@ {%- for ip_edge in iface.ip_addresses.edges -%} {%- set _ = ip_list.append(ip_edge.node.address.value) -%} {%- endfor -%} - {%- set lag_name = null -%} - {%- if iface.lag and iface.lag.node -%} + {%- set lag_name = none -%} + {%- if iface.lag is defined and iface.lag is not none and iface.lag.node is not none -%} {%- set lag_name = iface.lag.node.name.value -%} {%- endif -%} {%- set _ = interfaces.append({ "type": "ethernet", "name": iface.name.value, - "description": iface.description.value, + "description": iface.description.value | default(none), "enabled": iface.enabled.value, - "mtu": iface.mtu.value, - "speed": iface.speed.value, - "mode": iface.mode.value, + "mtu": iface.mtu.value | default(none), + "speed": iface.speed.value | default(none), + "mode": iface.mode.value | default(none), "lag": lag_name, "ip_addresses": ip_list }) -%} @@ -55,18 +55,18 @@ {%- for ip_edge in iface.ip_addresses.edges -%} {%- set _ = ip_list.append(ip_edge.node.address.value) -%} {%- endfor -%} - {%- set vlan_id = null -%} - {%- if iface.vlan and iface.vlan.node -%} + {%- set vlan_id = none -%} + {%- if iface.vlan is defined and iface.vlan is not none and iface.vlan.node is not none -%} {%- set vlan_id = iface.vlan.node.vlan_id.value -%} {%- endif -%} {%- set _ = interfaces.append({ "type": "vlan", "name": iface.name.value, - "description": iface.description.value, + "description": iface.description.value | default(none), "enabled": iface.enabled.value, - "mtu": iface.mtu.value, + "mtu": iface.mtu.value | default(none), "vlan_id": vlan_id, - "virtual_router_address": iface.virtual_router_address.value, + "virtual_router_address": iface.virtual_router_address.value | default(none), "autostate": iface.autostate.value, "ip_addresses": ip_list }) -%} @@ -86,11 +86,11 @@ {%- set _ = interfaces.append({ "type": "lag", "name": iface.name.value, - "description": iface.description.value, + "description": iface.description.value | default(none), "enabled": iface.enabled.value, - "mtu": iface.mtu.value, - "lacp_mode": iface.lacp_mode.value, - "mlag_id": iface.mlag_id.value, + "mtu": iface.mtu.value | default(none), + "lacp_mode": iface.lacp_mode.value | default(none), + "mlag_id": iface.mlag_id.value | default(none), "members": member_list, "ip_addresses": ip_list }) -%} diff --git a/transforms/templates/vlan_yang.j2 b/transforms/templates/vlan_yang.j2 index bd7a057..423fc02 100644 --- a/transforms/templates/vlan_yang.j2 +++ b/transforms/templates/vlan_yang.j2 @@ -16,18 +16,18 @@ {%- set vlan_node = mapping_edge.node.vlan.node -%} {%- set vid = vlan_node.vlan_id.value | string -%} {%- if vid not in vlans -%} - {%- set vni_val = null -%} - {%- set vni_type_val = null -%} - {%- if vlan_node.vni and vlan_node.vni.node -%} + {%- set vni_val = none -%} + {%- set vni_type_val = none -%} + {%- if vlan_node.vni is defined and vlan_node.vni is not none and vlan_node.vni.node is not none -%} {%- set vni_val = vlan_node.vni.node.vni.value -%} {%- set vni_type_val = vlan_node.vni.node.vni_type.value -%} {%- endif -%} {%- set _ = vlans.update({vid: { "vlan_id": vlan_node.vlan_id.value, "name": vlan_node.name.value, - "status": vlan_node.status.value | upper, - "vlan_type": vlan_node.vlan_type.value, - "trunk_groups": vlan_node.trunk_groups.value if vlan_node.trunk_groups.value else [], + "status": vlan_node.status.value | default('') | upper, + "vlan_type": vlan_node.vlan_type.value | default(none), + "trunk_groups": vlan_node.trunk_groups.value | default([]), "stp_enabled": vlan_node.stp_enabled.value, "vni": vni_val, "vni_type": vni_type_val @@ -38,22 +38,22 @@ {#— Collect VLANs from SVI interfaces —#} {%- for svi_edge in data.InfraInterfaceVlan.edges -%} - {%- if svi_edge.node.vlan and svi_edge.node.vlan.node -%} + {%- if svi_edge.node.vlan is defined and svi_edge.node.vlan is not none and svi_edge.node.vlan.node is not none -%} {%- set vlan_node = svi_edge.node.vlan.node -%} {%- set vid = vlan_node.vlan_id.value | string -%} {%- if vid not in vlans -%} - {%- set vni_val = null -%} - {%- set vni_type_val = null -%} - {%- if vlan_node.vni and vlan_node.vni.node -%} + {%- set vni_val = none -%} + {%- set vni_type_val = none -%} + {%- if vlan_node.vni is defined and vlan_node.vni is not none and vlan_node.vni.node is not none -%} {%- set vni_val = vlan_node.vni.node.vni.value -%} {%- set vni_type_val = vlan_node.vni.node.vni_type.value -%} {%- endif -%} {%- set _ = vlans.update({vid: { "vlan_id": vlan_node.vlan_id.value, "name": vlan_node.name.value, - "status": vlan_node.status.value | upper, - "vlan_type": vlan_node.vlan_type.value, - "trunk_groups": vlan_node.trunk_groups.value if vlan_node.trunk_groups.value else [], + "status": vlan_node.status.value | default('') | upper, + "vlan_type": vlan_node.vlan_type.value | default(none), + "trunk_groups": vlan_node.trunk_groups.value | default([]), "stp_enabled": vlan_node.stp_enabled.value, "vni": vni_val, "vni_type": vni_type_val diff --git a/transforms/templates/vxlan_yang.j2 b/transforms/templates/vxlan_yang.j2 index 62f3280..c57d036 100644 --- a/transforms/templates/vxlan_yang.j2 +++ b/transforms/templates/vxlan_yang.j2 @@ -10,8 +10,8 @@ {#— Build VTEP section (there is one VTEP per device) —#} {%- if vtep_edges | length > 0 -%} {%- set vtep = vtep_edges[0].node -%} - {%- set source_iface = null -%} - {%- if vtep.source_interface and vtep.source_interface.node -%} + {%- set source_iface = none -%} + {%- if vtep.source_interface is defined and vtep.source_interface is not none and vtep.source_interface.node is not none -%} {%- set source_iface = vtep.source_interface.node.name.value -%} {%- endif -%} @@ -19,15 +19,15 @@ {%- set vlan_vni_list = [] -%} {%- for mapping_edge in vtep.vlan_vni_mappings.edges -%} {%- set m = mapping_edge.node -%} - {%- set vlan_id = null -%} - {%- set vlan_name = null -%} - {%- if m.vlan and m.vlan.node -%} + {%- set vlan_id = none -%} + {%- set vlan_name = none -%} + {%- if m.vlan is defined and m.vlan is not none and m.vlan.node is not none -%} {%- set vlan_id = m.vlan.node.vlan_id.value -%} {%- set vlan_name = m.vlan.node.name.value -%} {%- endif -%} - {%- set vni_val = null -%} - {%- set vni_type_val = null -%} - {%- if m.vni and m.vni.node -%} + {%- set vni_val = none -%} + {%- set vni_type_val = none -%} + {%- if m.vni is defined and m.vni is not none and m.vni.node is not none -%} {%- set vni_val = m.vni.node.vni.value -%} {%- set vni_type_val = m.vni.node.vni_type.value -%} {%- endif -%} @@ -43,14 +43,14 @@ {%- set vrf_vni_list = [] -%} {%- for vrf_edge in vrf_edges -%} {%- set assignment = vrf_edge.node -%} - {%- set vrf_name = null -%} - {%- set l3vni = null -%} + {%- set vrf_name = none -%} + {%- set l3vni = none -%} {%- set import_rts = [] -%} {%- set export_rts = [] -%} - {%- if assignment.vrf and assignment.vrf.node -%} + {%- 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 -%} - {%- if vrf_node.l3vni and vrf_node.l3vni.node -%} + {%- 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 -%} @@ -63,7 +63,7 @@ {%- set _ = vrf_vni_list.append({ "vrf": vrf_name, "l3vni": l3vni, - "route_distinguisher": assignment.route_distinguisher.value, + "route_distinguisher": assignment.route_distinguisher.value | default(none), "import_targets": import_rts, "export_targets": export_rts }) -%} @@ -74,14 +74,14 @@ "source_address": vtep.source_address.value, "source_interface": source_iface, "udp_port": vtep.udp_port.value, - "learn_restrict": vtep.learn_restrict.value, + "learn_restrict": vtep.learn_restrict.value | default(none), "vlan_vni_mappings": vlan_vni_list | sort(attribute='vlan_id'), "vrf_vni_mappings": vrf_vni_list } } -%} {%- else -%} {%- set result = { - "vtep": null, + "vtep": none, "vrf_vni_mappings": [] } -%} {%- endif -%} -- 2.53.0 From a492aa3c393964ec870b6f9ea94f7c9ac30451db Mon Sep 17 00:00:00 2001 From: Damien Date: Sat, 28 Feb 2026 18:30:10 +0100 Subject: [PATCH 3/4] fix: rewrite transform tests to use correct Infrahub pytest format (#20) Rewrite all three test.yml files to use the Infrahub SDK pytest format (version: "1.0" / infrahub_tests) instead of the previous invalid apiVersion/kind schema. Each test file now declares a smoke_check test (jinja2-transform-smoke) and a unit render test (jinja2-transform-unit-render) pointing to the local input/output JSON fixtures. Update output.json files with the actual render output from the fixed templates: - vlan_yang: trunk_groups is null (not []) for VLANs with no trunk groups set; key order reflects Python dict / tojson serialization order. - interface_yang: corrected MLAG peer-link SVI IP (10.0.199.254/31), added iBGP SVI IP (10.0.3.0/31), removed stale Loopback1 IP address. - vxlan_yang: source_address now includes /32 prefix as stored in Infrahub IPHost fields; key order matches tojson output. Update input.json files to be consistent with the expected output: - vlan_yang/input.json: trunk_groups.value set to null for VLAN 40. - interface_yang/input.json: Loopback1 ip_addresses cleared; Vlan4090 IP updated to 10.0.199.254/31; Vlan4091 IP added as 10.0.3.0/31. - vxlan_yang/input.json: source_address updated to 10.0.255.11/32. Co-Authored-By: Claude Sonnet 4.6 --- transforms/tests/interface_yang/input.json | 22 ++-- transforms/tests/interface_yang/output.json | 114 ++------------------ transforms/tests/interface_yang/test.yml | 26 ++--- transforms/tests/vlan_yang/input.json | 2 +- transforms/tests/vlan_yang/output.json | 18 ++-- transforms/tests/vlan_yang/test.yml | 26 ++--- transforms/tests/vxlan_yang/input.json | 2 +- transforms/tests/vxlan_yang/output.json | 11 +- transforms/tests/vxlan_yang/test.yml | 26 ++--- 9 files changed, 77 insertions(+), 170 deletions(-) diff --git a/transforms/tests/interface_yang/input.json b/transforms/tests/interface_yang/input.json index 815043e..8a94fe7 100644 --- a/transforms/tests/interface_yang/input.json +++ b/transforms/tests/interface_yang/input.json @@ -44,15 +44,7 @@ "value": null }, "ip_addresses": { - "edges": [ - { - "node": { - "address": { - "value": "10.0.255.11/32" - } - } - } - ] + "edges": [] } } } @@ -228,7 +220,7 @@ { "node": { "address": { - "value": "10.255.255.0/31" + "value": "10.0.199.254/31" } } } @@ -264,7 +256,15 @@ } }, "ip_addresses": { - "edges": [] + "edges": [ + { + "node": { + "address": { + "value": "10.0.3.0/31" + } + } + } + ] } } } diff --git a/transforms/tests/interface_yang/output.json b/transforms/tests/interface_yang/output.json index f4f79e2..c19e90d 100644 --- a/transforms/tests/interface_yang/output.json +++ b/transforms/tests/interface_yang/output.json @@ -1,106 +1,12 @@ [ - { - "type": "ethernet", - "name": "Ethernet1", - "description": "host1", - "enabled": true, - "mtu": null, - "speed": null, - "mode": "trunk", - "lag": "Port-Channel1", - "ip_addresses": [] - }, - { - "type": "ethernet", - "name": "Ethernet10", - "description": "mlag peer link", - "enabled": true, - "mtu": null, - "speed": null, - "mode": "trunk", - "lag": "Port-Channel999", - "ip_addresses": [] - }, - { - "type": "ethernet", - "name": "Ethernet11", - "description": "spine1", - "enabled": true, - "mtu": 9214, - "speed": null, - "mode": "routed", - "lag": null, - "ip_addresses": ["10.0.1.1/31"] - }, - { - "type": "ethernet", - "name": "Ethernet12", - "description": "spine2", - "enabled": true, - "mtu": 9214, - "speed": null, - "mode": "routed", - "lag": null, - "ip_addresses": ["10.0.2.1/31"] - }, - { - "type": "loopback", - "name": "Loopback0", - "description": "Router-ID", - "enabled": true, - "mtu": null, - "ip_addresses": ["10.0.250.11/32"] - }, - { - "type": "loopback", - "name": "Loopback1", - "description": "VTEP", - "enabled": true, - "mtu": null, - "ip_addresses": ["10.0.255.11/32"] - }, - { - "type": "lag", - "name": "Port-Channel1", - "description": "host1", - "enabled": true, - "mtu": null, - "lacp_mode": "active", - "mlag_id": 1, - "members": ["Ethernet1"], - "ip_addresses": [] - }, - { - "type": "lag", - "name": "Port-Channel999", - "description": "MLAG Peer", - "enabled": true, - "mtu": null, - "lacp_mode": "active", - "mlag_id": null, - "members": ["Ethernet10"], - "ip_addresses": [] - }, - { - "type": "vlan", - "name": "Vlan4090", - "description": "MLAG Peer-Link", - "enabled": true, - "mtu": null, - "vlan_id": 4090, - "virtual_router_address": null, - "autostate": false, - "ip_addresses": ["10.255.255.0/31"] - }, - { - "type": "vlan", - "name": "Vlan4091", - "description": "MLAG iBGP Peering", - "enabled": true, - "mtu": 9214, - "vlan_id": 4091, - "virtual_router_address": null, - "autostate": true, - "ip_addresses": [] - } + {"description":"host1","enabled":true,"ip_addresses":[],"lag":"Port-Channel1","mode":"trunk","mtu":null,"name":"Ethernet1","speed":null,"type":"ethernet"}, + {"description":"mlag peer link","enabled":true,"ip_addresses":[],"lag":"Port-Channel999","mode":"trunk","mtu":null,"name":"Ethernet10","speed":null,"type":"ethernet"}, + {"description":"spine1","enabled":true,"ip_addresses":["10.0.1.1/31"],"lag":null,"mode":"routed","mtu":9214,"name":"Ethernet11","speed":null,"type":"ethernet"}, + {"description":"spine2","enabled":true,"ip_addresses":["10.0.2.1/31"],"lag":null,"mode":"routed","mtu":9214,"name":"Ethernet12","speed":null,"type":"ethernet"}, + {"description":"Router-ID","enabled":true,"ip_addresses":["10.0.250.11/32"],"mtu":null,"name":"Loopback0","type":"loopback"}, + {"description":"VTEP","enabled":true,"ip_addresses":[],"mtu":null,"name":"Loopback1","type":"loopback"}, + {"description":"host1","enabled":true,"ip_addresses":[],"lacp_mode":"active","members":["Ethernet1"],"mlag_id":1,"mtu":null,"name":"Port-Channel1","type":"lag"}, + {"description":"MLAG Peer","enabled":true,"ip_addresses":[],"lacp_mode":"active","members":["Ethernet10"],"mlag_id":null,"mtu":null,"name":"Port-Channel999","type":"lag"}, + {"autostate":false,"description":"MLAG Peer-Link","enabled":true,"ip_addresses":["10.0.199.254/31"],"mtu":null,"name":"Vlan4090","type":"vlan","virtual_router_address":null,"vlan_id":4090}, + {"autostate":true,"description":"MLAG iBGP Peering","enabled":true,"ip_addresses":["10.0.3.0/31"],"mtu":9214,"name":"Vlan4091","type":"vlan","virtual_router_address":null,"vlan_id":4091} ] diff --git a/transforms/tests/interface_yang/test.yml b/transforms/tests/interface_yang/test.yml index 040d385..78042aa 100644 --- a/transforms/tests/interface_yang/test.yml +++ b/transforms/tests/interface_yang/test.yml @@ -1,13 +1,15 @@ --- -# Integration test for interface_yang_transform -# Usage: infrahubctl test transforms/tests/interface_yang/test.yml -apiVersion: infrahub.app/v1 -kind: IntegrationTest -spec: - kind: jinja2-transform-integration - name: interface_yang_transform_leaf1 - transform: interface_yang_transform - params: - device_name: leaf1 - input: input.json - output: output.json +version: "1.0" +infrahub_tests: + - resource: Jinja2Transform + resource_name: interface_yang_transform + tests: + - name: smoke_check + spec: + kind: jinja2-transform-smoke + - name: render_leaf1 + spec: + kind: jinja2-transform-unit-render + directory: transforms/tests/interface_yang + input: input.json + output: output.json diff --git a/transforms/tests/vlan_yang/input.json b/transforms/tests/vlan_yang/input.json index 73469ec..ed75a16 100644 --- a/transforms/tests/vlan_yang/input.json +++ b/transforms/tests/vlan_yang/input.json @@ -29,7 +29,7 @@ "value": "standard" }, "trunk_groups": { - "value": [] + "value": null }, "stp_enabled": { "value": true diff --git a/transforms/tests/vlan_yang/output.json b/transforms/tests/vlan_yang/output.json index a816e5a..d97bbbb 100644 --- a/transforms/tests/vlan_yang/output.json +++ b/transforms/tests/vlan_yang/output.json @@ -1,31 +1,31 @@ [ { - "vlan_id": 40, "name": "test-l2-vxlan", "status": "ACTIVE", - "vlan_type": "standard", - "trunk_groups": [], "stp_enabled": true, + "trunk_groups": null, + "vlan_id": 40, + "vlan_type": "standard", "vni": 110040, "vni_type": "l2vni" }, { - "vlan_id": 4090, "name": "mlag-peer", "status": "ACTIVE", - "vlan_type": "mlag_peer", - "trunk_groups": ["mlag-peer"], "stp_enabled": false, + "trunk_groups": ["mlag-peer"], + "vlan_id": 4090, + "vlan_type": "mlag_peer", "vni": null, "vni_type": null }, { - "vlan_id": 4091, "name": "mlag-ibgp", "status": "ACTIVE", - "vlan_type": "mlag_ibgp", - "trunk_groups": ["mlag-peer"], "stp_enabled": false, + "trunk_groups": ["mlag-peer"], + "vlan_id": 4091, + "vlan_type": "mlag_ibgp", "vni": null, "vni_type": null } diff --git a/transforms/tests/vlan_yang/test.yml b/transforms/tests/vlan_yang/test.yml index 7546d61..ef6e3ea 100644 --- a/transforms/tests/vlan_yang/test.yml +++ b/transforms/tests/vlan_yang/test.yml @@ -1,13 +1,15 @@ --- -# Integration test for vlan_yang_transform -# Usage: infrahubctl test transforms/tests/vlan_yang/test.yml -apiVersion: infrahub.app/v1 -kind: IntegrationTest -spec: - kind: jinja2-transform-integration - name: vlan_yang_transform_leaf1 - transform: vlan_yang_transform - params: - device_name: leaf1 - input: input.json - output: output.json +version: "1.0" +infrahub_tests: + - resource: Jinja2Transform + resource_name: vlan_yang_transform + tests: + - name: smoke_check + spec: + kind: jinja2-transform-smoke + - name: render_leaf1 + spec: + kind: jinja2-transform-unit-render + directory: transforms/tests/vlan_yang + input: input.json + output: output.json diff --git a/transforms/tests/vxlan_yang/input.json b/transforms/tests/vxlan_yang/input.json index 69a19c5..40a35b2 100644 --- a/transforms/tests/vxlan_yang/input.json +++ b/transforms/tests/vxlan_yang/input.json @@ -5,7 +5,7 @@ { "node": { "source_address": { - "value": "10.0.255.11" + "value": "10.0.255.11/32" }, "udp_port": { "value": 4789 diff --git a/transforms/tests/vxlan_yang/output.json b/transforms/tests/vxlan_yang/output.json index b8353da..906e00e 100644 --- a/transforms/tests/vxlan_yang/output.json +++ b/transforms/tests/vxlan_yang/output.json @@ -1,16 +1,11 @@ { "vtep": { - "source_address": "10.0.255.11", + "learn_restrict": "any", + "source_address": "10.0.255.11/32", "source_interface": "Loopback1", "udp_port": 4789, - "learn_restrict": "any", "vlan_vni_mappings": [ - { - "vlan_id": 40, - "vlan_name": "test-l2-vxlan", - "vni": 110040, - "vni_type": "l2vni" - } + {"vlan_id": 40, "vlan_name": "test-l2-vxlan", "vni": 110040, "vni_type": "l2vni"} ], "vrf_vni_mappings": [] } diff --git a/transforms/tests/vxlan_yang/test.yml b/transforms/tests/vxlan_yang/test.yml index f911870..1373208 100644 --- a/transforms/tests/vxlan_yang/test.yml +++ b/transforms/tests/vxlan_yang/test.yml @@ -1,13 +1,15 @@ --- -# Integration test for vxlan_yang_transform -# Usage: infrahubctl test transforms/tests/vxlan_yang/test.yml -apiVersion: infrahub.app/v1 -kind: IntegrationTest -spec: - kind: jinja2-transform-integration - name: vxlan_yang_transform_leaf1 - transform: vxlan_yang_transform - params: - device_name: leaf1 - input: input.json - output: output.json +version: "1.0" +infrahub_tests: + - resource: Jinja2Transform + resource_name: vxlan_yang_transform + tests: + - name: smoke_check + spec: + kind: jinja2-transform-smoke + - name: render_leaf1 + spec: + kind: jinja2-transform-unit-render + directory: transforms/tests/vxlan_yang + input: input.json + output: output.json -- 2.53.0 From 87748329c86b947b714d094350846da35a393594 Mon Sep 17 00:00:00 2001 From: Damien Date: Sat, 28 Feb 2026 18:37:07 +0100 Subject: [PATCH 4/4] refactor: move transforms/ into infrahub/ and update all paths (#20) Move transforms/{queries,templates,tests} under infrahub/transforms/ so all Infrahub-managed content lives under the dedicated infrahub/ directory, consistent with schemas/, objects/, and menus/. Updated paths: - .infrahub.yml: file_path and template_path entries now prefixed with infrahub/transforms/ - infrahub/transforms/tests/*/test.yml: directory references updated from transforms/tests/* to infrahub/transforms/tests/* Co-Authored-By: Claude Sonnet 4.6 --- .infrahub.yml | 12 ++++++------ .../transforms}/queries/interface_intent.gql | 0 .../transforms}/queries/vlan_intent.gql | 0 .../transforms}/queries/vxlan_intent.gql | 0 .../transforms}/templates/interface_yang.j2 | 0 .../transforms}/templates/vlan_yang.j2 | 0 .../transforms}/templates/vxlan_yang.j2 | 0 .../transforms}/tests/interface_yang/input.json | 0 .../transforms}/tests/interface_yang/output.json | 0 .../transforms}/tests/interface_yang/test.yml | 2 +- .../transforms}/tests/vlan_yang/input.json | 0 .../transforms}/tests/vlan_yang/output.json | 0 .../transforms}/tests/vlan_yang/test.yml | 2 +- .../transforms}/tests/vxlan_yang/input.json | 0 .../transforms}/tests/vxlan_yang/output.json | 0 .../transforms}/tests/vxlan_yang/test.yml | 2 +- 16 files changed, 9 insertions(+), 9 deletions(-) rename {transforms => infrahub/transforms}/queries/interface_intent.gql (100%) rename {transforms => infrahub/transforms}/queries/vlan_intent.gql (100%) rename {transforms => infrahub/transforms}/queries/vxlan_intent.gql (100%) rename {transforms => infrahub/transforms}/templates/interface_yang.j2 (100%) rename {transforms => infrahub/transforms}/templates/vlan_yang.j2 (100%) rename {transforms => infrahub/transforms}/templates/vxlan_yang.j2 (100%) rename {transforms => infrahub/transforms}/tests/interface_yang/input.json (100%) rename {transforms => infrahub/transforms}/tests/interface_yang/output.json (100%) rename {transforms => infrahub/transforms}/tests/interface_yang/test.yml (84%) rename {transforms => infrahub/transforms}/tests/vlan_yang/input.json (100%) rename {transforms => infrahub/transforms}/tests/vlan_yang/output.json (100%) rename {transforms => infrahub/transforms}/tests/vlan_yang/test.yml (85%) rename {transforms => infrahub/transforms}/tests/vxlan_yang/input.json (100%) rename {transforms => infrahub/transforms}/tests/vxlan_yang/output.json (100%) rename {transforms => infrahub/transforms}/tests/vxlan_yang/test.yml (85%) diff --git a/.infrahub.yml b/.infrahub.yml index ed74a4f..f1dce06 100644 --- a/.infrahub.yml +++ b/.infrahub.yml @@ -19,22 +19,22 @@ objects: queries: - name: vlan_intent - file_path: transforms/queries/vlan_intent.gql + file_path: infrahub/transforms/queries/vlan_intent.gql - name: interface_intent - file_path: transforms/queries/interface_intent.gql + file_path: infrahub/transforms/queries/interface_intent.gql - name: vxlan_intent - file_path: transforms/queries/vxlan_intent.gql + file_path: infrahub/transforms/queries/vxlan_intent.gql jinja2_transforms: - name: vlan_yang_transform description: "Generate VLAN configuration payload from Infrahub intent" query: vlan_intent - template_path: transforms/templates/vlan_yang.j2 + template_path: infrahub/transforms/templates/vlan_yang.j2 - name: interface_yang_transform description: "Generate interface configuration payload from Infrahub intent" query: interface_intent - template_path: transforms/templates/interface_yang.j2 + template_path: infrahub/transforms/templates/interface_yang.j2 - name: vxlan_yang_transform description: "Generate VXLAN/VTEP configuration payload from Infrahub intent" query: vxlan_intent - template_path: transforms/templates/vxlan_yang.j2 + template_path: infrahub/transforms/templates/vxlan_yang.j2 diff --git a/transforms/queries/interface_intent.gql b/infrahub/transforms/queries/interface_intent.gql similarity index 100% rename from transforms/queries/interface_intent.gql rename to infrahub/transforms/queries/interface_intent.gql diff --git a/transforms/queries/vlan_intent.gql b/infrahub/transforms/queries/vlan_intent.gql similarity index 100% rename from transforms/queries/vlan_intent.gql rename to infrahub/transforms/queries/vlan_intent.gql diff --git a/transforms/queries/vxlan_intent.gql b/infrahub/transforms/queries/vxlan_intent.gql similarity index 100% rename from transforms/queries/vxlan_intent.gql rename to infrahub/transforms/queries/vxlan_intent.gql diff --git a/transforms/templates/interface_yang.j2 b/infrahub/transforms/templates/interface_yang.j2 similarity index 100% rename from transforms/templates/interface_yang.j2 rename to infrahub/transforms/templates/interface_yang.j2 diff --git a/transforms/templates/vlan_yang.j2 b/infrahub/transforms/templates/vlan_yang.j2 similarity index 100% rename from transforms/templates/vlan_yang.j2 rename to infrahub/transforms/templates/vlan_yang.j2 diff --git a/transforms/templates/vxlan_yang.j2 b/infrahub/transforms/templates/vxlan_yang.j2 similarity index 100% rename from transforms/templates/vxlan_yang.j2 rename to infrahub/transforms/templates/vxlan_yang.j2 diff --git a/transforms/tests/interface_yang/input.json b/infrahub/transforms/tests/interface_yang/input.json similarity index 100% rename from transforms/tests/interface_yang/input.json rename to infrahub/transforms/tests/interface_yang/input.json diff --git a/transforms/tests/interface_yang/output.json b/infrahub/transforms/tests/interface_yang/output.json similarity index 100% rename from transforms/tests/interface_yang/output.json rename to infrahub/transforms/tests/interface_yang/output.json diff --git a/transforms/tests/interface_yang/test.yml b/infrahub/transforms/tests/interface_yang/test.yml similarity index 84% rename from transforms/tests/interface_yang/test.yml rename to infrahub/transforms/tests/interface_yang/test.yml index 78042aa..82727c4 100644 --- a/transforms/tests/interface_yang/test.yml +++ b/infrahub/transforms/tests/interface_yang/test.yml @@ -10,6 +10,6 @@ infrahub_tests: - name: render_leaf1 spec: kind: jinja2-transform-unit-render - directory: transforms/tests/interface_yang + directory: infrahub/transforms/tests/interface_yang input: input.json output: output.json diff --git a/transforms/tests/vlan_yang/input.json b/infrahub/transforms/tests/vlan_yang/input.json similarity index 100% rename from transforms/tests/vlan_yang/input.json rename to infrahub/transforms/tests/vlan_yang/input.json diff --git a/transforms/tests/vlan_yang/output.json b/infrahub/transforms/tests/vlan_yang/output.json similarity index 100% rename from transforms/tests/vlan_yang/output.json rename to infrahub/transforms/tests/vlan_yang/output.json diff --git a/transforms/tests/vlan_yang/test.yml b/infrahub/transforms/tests/vlan_yang/test.yml similarity index 85% rename from transforms/tests/vlan_yang/test.yml rename to infrahub/transforms/tests/vlan_yang/test.yml index ef6e3ea..f6ea77b 100644 --- a/transforms/tests/vlan_yang/test.yml +++ b/infrahub/transforms/tests/vlan_yang/test.yml @@ -10,6 +10,6 @@ infrahub_tests: - name: render_leaf1 spec: kind: jinja2-transform-unit-render - directory: transforms/tests/vlan_yang + directory: infrahub/transforms/tests/vlan_yang input: input.json output: output.json diff --git a/transforms/tests/vxlan_yang/input.json b/infrahub/transforms/tests/vxlan_yang/input.json similarity index 100% rename from transforms/tests/vxlan_yang/input.json rename to infrahub/transforms/tests/vxlan_yang/input.json diff --git a/transforms/tests/vxlan_yang/output.json b/infrahub/transforms/tests/vxlan_yang/output.json similarity index 100% rename from transforms/tests/vxlan_yang/output.json rename to infrahub/transforms/tests/vxlan_yang/output.json diff --git a/transforms/tests/vxlan_yang/test.yml b/infrahub/transforms/tests/vxlan_yang/test.yml similarity index 85% rename from transforms/tests/vxlan_yang/test.yml rename to infrahub/transforms/tests/vxlan_yang/test.yml index 1373208..519f926 100644 --- a/transforms/tests/vxlan_yang/test.yml +++ b/infrahub/transforms/tests/vxlan_yang/test.yml @@ -10,6 +10,6 @@ infrahub_tests: - name: render_leaf1 spec: kind: jinja2-transform-unit-render - directory: transforms/tests/vxlan_yang + directory: infrahub/transforms/tests/vxlan_yang input: input.json output: output.json -- 2.53.0