## Summary Closes #23. Implements a single unified `bgp_yang_transform` covering the complete BGP router stanza for all 10 fabric devices. **Design decision:** One transform (one query + one template) rather than 4 separate transforms, because all BGP components (process config, peer groups, neighbors, AFs) live under a single `router bgp <ASN>` stanza and must be consistent. This avoids multiple API calls per device and keeps the data model coherent. | File | Description | |------|-------------| | `infrahub/transforms/queries/bgp_intent.gql` | Unified GraphQL query — `InfraBGPRouterConfig` (with peer_groups, sessions) + `InfraBGPAddressFamily` (with active_peer_groups, active_sessions, networks, optional vrf) | | `infrahub/transforms/templates/bgp_yang.j2` | Jinja2 template — renders `bgp.global`, `bgp.peer_groups`, `bgp.neighbors`, `bgp.address_families`, `bgp.vrf_neighbors`, `bgp.vrf_address_families`; returns `[]` for devices with no BGP config | | `infrahub/transforms/tests/bgp_yang/test.yml` | Smoke check + unit render tests for leaf1, spine1, leaf7 | | `infrahub/transforms/tests/bgp_yang/leaf1/` | 3 peer-groups, 5 global neighbors, 2 global AFs | | `infrahub/transforms/tests/bgp_yang/spine1/` | 1 peer-group (evpn/next-hop-unchanged), 16 neighbors (8 direct underlay + 8 EVPN), IPv4 AF activates individual sessions | | `infrahub/transforms/tests/bgp_yang/leaf7/` | leaf1 pattern + VRF gold border session (AS 64999) + VRF-scoped IPv4 unicast AF | | `.infrahub.yml` | Registers `bgp_intent` query and `bgp_yang_transform` | ## Validation | Device | Expected output | |--------|----------------| | `leaf1` | 3 peer-groups, 5 global neighbors (underlay×2, iBGP×1, EVPN×2), 2 AFs, empty VRF sections | | `spine1` | 1 peer-group (evpn, next-hop-unchanged), 16 neighbors (8 direct with `remote_asn`, 8 EVPN via peer-group), IPv4 AF activates individual sessions | | `leaf7` | Same as leaf1 (AS 65004) + `vrf_neighbors: [{10.90.90.1, AS 64999, VRF gold}]` + `vrf_address_families: [{ipv4, VRF gold, active_sessions: [10.90.90.1]}]` | ```bash infrahubctl render bgp_yang_transform device_name=leaf1 infrahubctl render bgp_yang_transform device_name=spine1 infrahubctl render bgp_yang_transform device_name=leaf7 ``` ## Design notes - Follows identical conventions to existing transforms (#20–#22) - All optional relationships (`remote_asn`, `peer_group`, `vrf`, `peer_device`, `update_source`, etc.) wrapped in `is defined and is not none` guards - `send_community` value `"none"` (schema default) is normalised to `null` in the output — keeps the rendered JSON clean for downstream consumers - VRF-scoped sessions and AFs are separated into `vrf_neighbors` / `vrf_address_families` arrays, each entry carrying a `"vrf"` key, so the template consumer can trivially iterate per-VRF without filtering
This commit was merged in pull request #27.
This commit is contained in:
194
infrahub/transforms/templates/bgp_yang.j2
Normal file
194
infrahub/transforms/templates/bgp_yang.j2
Normal file
@@ -0,0 +1,194 @@
|
||||
{#
|
||||
bgp_yang.j2 — Produce a JSON object with the complete BGP configuration
|
||||
for a single device.
|
||||
|
||||
Input: GraphQL response from bgp_intent query.
|
||||
Returns [] if the device has no BGP router config.
|
||||
|
||||
Output structure:
|
||||
{
|
||||
"bgp": {
|
||||
"global": { asn, router_id, flags, distance, ecmp },
|
||||
"peer_groups": [ ... ],
|
||||
"neighbors": [ ... global-VRF sessions ... ],
|
||||
"address_families": [ ... global-VRF AFs ... ],
|
||||
"vrf_neighbors": [ ... VRF-scoped sessions ... ],
|
||||
"vrf_address_families": [ ... VRF-scoped AFs ... ]
|
||||
}
|
||||
}
|
||||
#}
|
||||
{%- set router_configs = data.InfraBGPRouterConfig.edges -%}
|
||||
{%- set af_edges = data.InfraBGPAddressFamily.edges -%}
|
||||
|
||||
{%- if router_configs | length == 0 -%}
|
||||
[]
|
||||
{%- else -%}
|
||||
{%- set rc = router_configs[0].node -%}
|
||||
|
||||
{#— Global section —#}
|
||||
{%- set asn = none -%}
|
||||
{%- if rc.local_asn is defined and rc.local_asn is not none and rc.local_asn.node is not none -%}
|
||||
{%- set asn = rc.local_asn.node.asn.value -%}
|
||||
{%- endif -%}
|
||||
|
||||
{#— Build peer_groups list —#}
|
||||
{%- set peer_groups = [] -%}
|
||||
{%- for pg_edge in rc.peer_groups.edges -%}
|
||||
{%- set pg = pg_edge.node -%}
|
||||
{%- set pg_remote_asn = none -%}
|
||||
{%- if pg.remote_asn is defined and pg.remote_asn is not none and pg.remote_asn.node is not none -%}
|
||||
{%- set pg_remote_asn = pg.remote_asn.node.asn.value -%}
|
||||
{%- endif -%}
|
||||
{%- set pg_send_community = none -%}
|
||||
{%- if pg.send_community is defined and pg.send_community is not none and pg.send_community.value is not none and pg.send_community.value != "none" -%}
|
||||
{%- set pg_send_community = pg.send_community.value -%}
|
||||
{%- endif -%}
|
||||
{%- set pg_update_source = none -%}
|
||||
{%- if pg.update_source is defined and pg.update_source is not none and pg.update_source.value is not none -%}
|
||||
{%- set pg_update_source = pg.update_source.value -%}
|
||||
{%- endif -%}
|
||||
{%- set pg_ebgp_multihop = none -%}
|
||||
{%- if pg.ebgp_multihop is defined and pg.ebgp_multihop is not none and pg.ebgp_multihop.value is not none -%}
|
||||
{%- set pg_ebgp_multihop = pg.ebgp_multihop.value -%}
|
||||
{%- endif -%}
|
||||
{%- set pg_max_routes = none -%}
|
||||
{%- if pg.maximum_routes is defined and pg.maximum_routes is not none and pg.maximum_routes.value is not none -%}
|
||||
{%- set pg_max_routes = pg.maximum_routes.value -%}
|
||||
{%- endif -%}
|
||||
{%- set _ = peer_groups.append({
|
||||
"name": pg.name.value,
|
||||
"type": pg.peer_group_type.value,
|
||||
"remote_asn": pg_remote_asn,
|
||||
"update_source": pg_update_source,
|
||||
"ebgp_multihop": pg_ebgp_multihop,
|
||||
"send_community": pg_send_community,
|
||||
"next_hop_self": pg.next_hop_self.value,
|
||||
"next_hop_unchanged": pg.next_hop_unchanged.value,
|
||||
"maximum_routes": pg_max_routes,
|
||||
"maximum_routes_warning_only": pg.maximum_routes_warning_only.value
|
||||
}) -%}
|
||||
{%- endfor -%}
|
||||
|
||||
{#— Split sessions into global and VRF-scoped —#}
|
||||
{%- set neighbors = [] -%}
|
||||
{%- set vrf_neighbors = [] -%}
|
||||
{%- for sess_edge in rc.sessions.edges -%}
|
||||
{%- set sess = sess_edge.node -%}
|
||||
|
||||
{%- set sess_peer_group = none -%}
|
||||
{%- if sess.peer_group is defined and sess.peer_group is not none and sess.peer_group.node is not none -%}
|
||||
{%- set sess_peer_group = sess.peer_group.node.name.value -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- set sess_remote_asn = none -%}
|
||||
{%- if sess.remote_asn is defined and sess.remote_asn is not none and sess.remote_asn.node is not none -%}
|
||||
{%- set sess_remote_asn = sess.remote_asn.node.asn.value -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- set sess_vrf = none -%}
|
||||
{%- if sess.vrf is defined and sess.vrf is not none and sess.vrf.node is not none -%}
|
||||
{%- set sess_vrf = sess.vrf.node.name.value -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- set sess_obj = {
|
||||
"peer_address": sess.peer_address.value,
|
||||
"description": sess.description.value | default(none),
|
||||
"enabled": sess.enabled.value,
|
||||
"peer_group": sess_peer_group,
|
||||
"remote_asn": sess_remote_asn
|
||||
} -%}
|
||||
|
||||
{%- if sess_vrf is not none -%}
|
||||
{%- set vrf_sess_obj = {
|
||||
"peer_address": sess.peer_address.value,
|
||||
"description": sess.description.value | default(none),
|
||||
"enabled": sess.enabled.value,
|
||||
"peer_group": sess_peer_group,
|
||||
"remote_asn": sess_remote_asn,
|
||||
"vrf": sess_vrf
|
||||
} -%}
|
||||
{%- set _ = vrf_neighbors.append(vrf_sess_obj) -%}
|
||||
{%- else -%}
|
||||
{%- set _ = neighbors.append(sess_obj) -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
|
||||
{#— Split address families into global and VRF-scoped —#}
|
||||
{%- set address_families = [] -%}
|
||||
{%- set vrf_address_families = [] -%}
|
||||
{%- for af_edge in af_edges -%}
|
||||
{%- set af = af_edge.node -%}
|
||||
|
||||
{%- set af_vrf = none -%}
|
||||
{%- if af.vrf is defined and af.vrf is not none and af.vrf.node is not none -%}
|
||||
{%- set af_vrf = af.vrf.node.name.value -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- set af_active_pgs = [] -%}
|
||||
{%- for pg_edge in af.active_peer_groups.edges -%}
|
||||
{%- set _ = af_active_pgs.append(pg_edge.node.name.value) -%}
|
||||
{%- endfor -%}
|
||||
|
||||
{%- set af_active_sess = [] -%}
|
||||
{%- if af.active_sessions is defined and af.active_sessions is not none -%}
|
||||
{%- for s_edge in af.active_sessions.edges -%}
|
||||
{%- set _ = af_active_sess.append(s_edge.node.peer_address.value) -%}
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- set af_networks = [] -%}
|
||||
{%- if af.networks is defined and af.networks is not none -%}
|
||||
{%- for net_edge in af.networks.edges -%}
|
||||
{%- set _ = af_networks.append(net_edge.node.address.value) -%}
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- set af_obj = {
|
||||
"afi": af.afi.value,
|
||||
"safi": af.safi.value,
|
||||
"active_peer_groups": af_active_pgs,
|
||||
"active_sessions": af_active_sess,
|
||||
"networks": af_networks
|
||||
} -%}
|
||||
|
||||
{%- if af_vrf is not none -%}
|
||||
{%- set vrf_af_obj = {
|
||||
"afi": af.afi.value,
|
||||
"safi": af.safi.value,
|
||||
"vrf": af_vrf,
|
||||
"active_peer_groups": af_active_pgs,
|
||||
"active_sessions": af_active_sess,
|
||||
"networks": af_networks
|
||||
} -%}
|
||||
{%- set _ = vrf_address_families.append(vrf_af_obj) -%}
|
||||
{%- else -%}
|
||||
{%- set _ = address_families.append(af_obj) -%}
|
||||
{%- endif -%}
|
||||
{%- endfor -%}
|
||||
|
||||
{%- set result = {
|
||||
"bgp": {
|
||||
"global": {
|
||||
"asn": asn,
|
||||
"router_id": rc.router_id.value,
|
||||
"default_ipv4_unicast": rc.default_ipv4_unicast.value,
|
||||
"log_neighbor_changes": rc.log_neighbor_changes.value,
|
||||
"distance": {
|
||||
"ebgp": rc.ebgp_distance.value,
|
||||
"ibgp": rc.ibgp_distance.value,
|
||||
"local": rc.local_distance.value
|
||||
},
|
||||
"ecmp": {
|
||||
"max_paths": rc.ecmp_max_paths.value,
|
||||
"max_ecmp": rc.ecmp_max_ecmp.value
|
||||
}
|
||||
},
|
||||
"peer_groups": peer_groups,
|
||||
"neighbors": neighbors,
|
||||
"address_families": address_families,
|
||||
"vrf_neighbors": vrf_neighbors,
|
||||
"vrf_address_families": vrf_address_families
|
||||
}
|
||||
} -%}
|
||||
{{ result | tojson(indent=2) }}
|
||||
{%- endif -%}
|
||||
Reference in New Issue
Block a user