exit: support custom VRF imports

Ten minutes to set up and ten hours to convince Ansible to not be
quite so retarded. The list2dict filter seems to be the (or another)
missing piece. Now let’s rewrite everything else using it. Or not.
This commit is contained in:
Timotej Lazar 2024-07-15 11:28:33 +02:00
parent 99aef43574
commit 82b10e8133
4 changed files with 60 additions and 7 deletions

14
filter_plugins/util.py Normal file
View file

@ -0,0 +1,14 @@
#!/usr/bin/python
class FilterModule(object):
'''Helper filters to make Ansible less unpleasant'''
def filters(self):
return {
'list2dict': self.list2dict,
}
def list2dict(self, items, key):
'''
Like items2dict but keep entire dictionaries as values.
'''
return {item[key]: item for item in items}

View file

@ -5,7 +5,7 @@
- name: Install keepalived - name: Install keepalived
import_tasks: keepalived.yml import_tasks: keepalived.yml
- name: Configure relay for old DHCP server - name: Configure DHCP relays
template: template:
dest: '/etc/default/isc-dhcp-relay-{{ prefixes | selectattr("prefix", "==", item.0 | ipaddr("network/prefix")) | map(attribute="vrf.name") | first }}' dest: '/etc/default/isc-dhcp-relay-{{ prefixes | selectattr("prefix", "==", item.0 | ipaddr("network/prefix")) | map(attribute="vrf.name") | first }}'
src: isc-dhcp-relay.j2 src: isc-dhcp-relay.j2

View file

@ -3,7 +3,7 @@
| selectattr('role') | selectattr('role.value', '==', 'loopback') | selectattr('role') | selectattr('role.value', '==', 'loopback')
| map(attribute='address') %} | map(attribute='address') %}
{% set inside_vrfs = interfaces | selectattr('parent') | selectattr('parent.name', '==', 'bridge') {% set inside_vrfs = interfaces | selectattr('parent') | selectattr('parent.name', '==', 'bridge')
| selectattr('vrf') | map(attribute='vrf') | rejectattr('name', '==', 'outside') -%} | selectattr('vrf') | map(attribute='vrf.name') | reject('==', 'outside') | sort %}
frr defaults datacenter frr defaults datacenter
log syslog informational log syslog informational
@ -158,7 +158,7 @@ router bgp {{ asn.asn }} vrf inside
redistribute connected route-map loopback-inside redistribute connected route-map loopback-inside
{% for vrf in inside_vrfs %} {% for vrf in inside_vrfs %}
import vrf {{ vrf.name }} import vrf {{ vrf }}
{% endfor %} {% endfor %}
import vrf default import vrf default
import vrf route-map inside-import import vrf route-map inside-import
@ -180,14 +180,14 @@ router bgp {{ asn.asn }} vrf inside
redistribute connected route-map loopback-inside redistribute connected route-map loopback-inside
{% for vrf in inside_vrfs %} {% for vrf in inside_vrfs %}
import vrf {{ vrf.name }} import vrf {{ vrf }}
{% endfor %} {% endfor %}
import vrf default import vrf default
import vrf route-map inside-import import vrf route-map inside-import
exit-address-family exit-address-family
{% for vrf in inside_vrfs %} {% for vrf in vrfs.values() | selectattr('name', 'in', inside_vrfs) %}
# VRF for L2 network {{ vrf.name }}. Imports gateway from inside VRF. # VRF for L2 network {{ vrf.name }}. Imports gateway from inside VRF.
router bgp {{ asn.asn }} vrf {{ vrf.name }} router bgp {{ asn.asn }} vrf {{ vrf.name }}
bgp bestpath as-path multipath-relax bgp bestpath as-path multipath-relax
@ -195,13 +195,27 @@ router bgp {{ asn.asn }} vrf {{ vrf.name }}
address-family ipv4 unicast address-family ipv4 unicast
redistribute connected redistribute connected
import vrf inside import vrf inside
{% if vrf.custom_fields.imports %}
{% for import in vrf.custom_fields.imports %}
import vrf {{ import.name }}
{% endfor %}
import vrf route-map {{ vrf.name }}-import
{% else %}
import vrf route-map office-import import vrf route-map office-import
{% endif %}
exit-address-family exit-address-family
address-family ipv6 unicast address-family ipv6 unicast
redistribute connected redistribute connected
import vrf inside import vrf inside
{% if vrf.custom_fields.imports %}
{% for import in vrf.custom_fields.imports %}
import vrf {{ import.name }}
{% endfor %}
import vrf route-map {{ vrf.name }}-import
{% else %}
import vrf route-map office-import import vrf route-map office-import
{% endif %}
exit-address-family exit-address-family
{% endfor %} {% endfor %}
@ -213,8 +227,8 @@ ipv6 prefix-list default permit ::/0
ip prefix-list fabric permit 10.34.0.0/24 ge 32 ip prefix-list fabric permit 10.34.0.0/24 ge 32
ipv6 prefix-list fabric permit 2001:1470:fffd:3400::/64 ge 128 ipv6 prefix-list fabric permit 2001:1470:fffd:3400::/64 ge 128
{% for prefix in vrf_prefixes # common prefix list for all inside networks
| selectattr('vrf.id', 'in', inside_vrfs|map(attribute='id')) {% for prefix in vrf_prefixes | selectattr('vrf.name', 'in', inside_vrfs)
| sort(attribute='family.value') | sort(attribute='vlan.vid') %} | sort(attribute='family.value') | sort(attribute='vlan.vid') %}
{% if prefix.family.value == 4 %} {% if prefix.family.value == 4 %}
ip prefix-list office permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }} ip prefix-list office permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }}
@ -223,6 +237,16 @@ ipv6 prefix-list office permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr(
{% endif %} {% endif %}
{% endfor %} {% endfor %}
# individual prefix lists for each inside network
{% for prefix in vrf_prefixes | selectattr('vrf.name', 'in', inside_vrfs)
| sort(attribute='family.value') | sort(attribute='vlan.vid') %}
{% if prefix.family.value == 4 %}
ip prefix-list {{ prefix.vrf.name }} permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }}
{% else %}
ipv6 prefix-list {{ prefix.vrf.name }} permit {{ prefix.prefix }} ge {{ prefix.prefix | ipaddr('prefix') }}
{% endif %}
{% endfor %}
{% if wg_net is defined %} {% if wg_net is defined %}
ip prefix-list vpn permit {{ wg_net | ipaddr('subnet') }} ip prefix-list vpn permit {{ wg_net | ipaddr('subnet') }}
{% endif %} {% endif %}
@ -278,6 +302,20 @@ route-map inside-import permit 20
route-map inside-import permit 21 route-map inside-import permit 21
match ipv6 address prefix-list office match ipv6 address prefix-list office
{% for vrf in vrfs.values() | selectattr('custom_fields.imports') %}
route-map {{ vrf.name }}-import permit 10
match ip address prefix-list default
route-map {{ vrf.name }}-import permit 11
match ipv6 address prefix-list default
{% for import in vrf.custom_fields.imports %}
route-map {{ vrf.name }}-import permit {{ 100 + 10*loop.index0 }}
match ip address prefix-list {{ import.name }}
route-map {{ vrf.name }}-import permit {{ 101 + 10*loop.index0 }}
match ipv6 address prefix-list {{ import.name }}
{% endfor %}
{% endfor %}
# Route maps for advertised and received routes. # Route maps for advertised and received routes.
# Default VRF ↔ fabric. # Default VRF ↔ fabric.
route-map default->fabric permit 10 route-map default->fabric permit 10

View file

@ -3,6 +3,7 @@
set_fact: set_fact:
vlans: '{{ query("netbox.netbox.nb_lookup", "vlans", api_filter="group=new-net", raw_data=true) vlans: '{{ query("netbox.netbox.nb_lookup", "vlans", api_filter="group=new-net", raw_data=true)
| sort(attribute="vid") }}' | sort(attribute="vid") }}'
vrfs: '{{ query("netbox.netbox.nb_lookup", "vrfs", raw_data=true) | list2dict("name") }}'
prefixes: '{{ query("netbox.netbox.nb_lookup", "prefixes", raw_data=true) prefixes: '{{ query("netbox.netbox.nb_lookup", "prefixes", raw_data=true)
| sort(attribute="prefix") | sort(attribute="family.value") }}' | sort(attribute="prefix") | sort(attribute="family.value") }}'