Files
arista-evpn-vxlan-clab/infrahub/transforms/templates/bgp_yang.j2
Damien 6df4e5ab19 feat: Add Infrahub Jinja2 transform for BGP configuration (#23)
Single unified bgp_yang_transform covering the full BGP router stanza
(process config, peer groups, neighbors, address families) for all
10 fabric devices in a single query+template pass.

- Add GraphQL query bgp_intent.gql: queries InfraBGPRouterConfig by
  device name, traversing local_asn, peer_groups (with remote_asn),
  and sessions (with peer_group, remote_asn, vrf, peer_device);
  separately queries InfraBGPAddressFamily for the same device,
  including active_peer_groups, active_sessions, networks, and optional
  vrf relationship
- Add Jinja2 template bgp_yang.j2: renders a JSON object with bgp.global
  (ASN, router-id, distances, ECMP), bgp.peer_groups, bgp.neighbors,
  bgp.address_families for global sessions, plus bgp.vrf_neighbors and
  bgp.vrf_address_families for VRF-scoped entries (e.g. leaf7 VRF gold
  border peering); returns [] for devices with no BGP config; all
  optional relationships guarded with 'is defined and is not none' checks;
  send_community "none" normalised to null in output
- Update .infrahub.yml: register bgp_intent query and bgp_yang_transform
- Add unit test fixtures for three representative devices:
    leaf1  — 3 peer-groups, 5 global neighbors, 2 global AFs
    spine1 — 1 peer-group (evpn/next-hop-unchanged), 16 neighbors
             (8 direct underlay with remote_asn, 8 EVPN via peer-group),
             IPv4 AF activates individual sessions
    leaf7  — leaf1 pattern + VRF gold border session (AS 64999) and
             VRF-scoped IPv4 unicast AF

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 12:41:08 +01:00

195 lines
6.9 KiB
Django/Jinja

{#
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 -%}