From 8fd6f3bff600c88cc39e94d1e3d86c5f3d9b3a35 Mon Sep 17 00:00:00 2001 From: Timotej Lazar Date: Fri, 1 Aug 2025 12:20:08 +0200 Subject: [PATCH] access: fix check/diff mode for FS switches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regardless of terminal width FS switches always split known VLAN ranges into multiple lines of at most twenty numbers when showing configuration. Do the same in our config template to avoid reporting changes when there are none. Allowed VLANs for tagged ports are displayed similarly but even worse, with the first line specifying `allowed vlan only` for the first twenty numbers and subsequent lines adding the remaining VLANs. Not sure if configuring a switch this way – as opposed to a single long `allowed vlan only` line – could disrupt traffic. Instead we simply allow all VLANs on uplink ports, marked in NetBox as 'tagged-all'. For downlink tagged ports the number of allowed VLANs is unlikely to exceed twenty. Ansible now reports no fictional changes for all existing access switches. The only remaining issue is removing known VLANs, which has to be done manually on each switch. --- filter_plugins/netbox.py | 27 ++++++++++++++++++++++----- roles/access/templates/config-fs.j2 | 7 ++++++- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/filter_plugins/netbox.py b/filter_plugins/netbox.py index 0932c41..02ad61a 100644 --- a/filter_plugins/netbox.py +++ b/filter_plugins/netbox.py @@ -14,19 +14,36 @@ class FilterModule(object): 'iface_vlans': self.iface_vlans } - def compact_numlist(self, nums, sort=True, delimiter=',', range_delimiter='-'): - '''Transform [1,2,3,5,7,8,9] into "1-3,5,7-9"''' + def compact_numlist(self, nums, sort=True, delimiter=',', range_delimiter='-', max_per_line=None): + '''Transform [1,2,3,5,7,8,9] into "1-3,5,7-9". + + If max_per_line is given, return a list of such strings where each string contains at most max_per_line+1 numbers. + This emulates how VLAN ranges are displayed by FS switches so we can make ansible check mode work correctly. + ''' if sort: nums = sorted(nums) i = 0 - spans = [] + lines = [] + line = [] + nums_in_line = 0 while i < len(nums): j = i + 1 while j < len(nums) and nums[j]-nums[i] == j-i: j += 1 - spans += [f'{nums[i]}{range_delimiter}{nums[j-1]}' if j > i+1 else f'{nums[i]}'] + if j > i+1: + line += [f'{nums[i]}{range_delimiter}{nums[j-1]}'] + nums_in_line += 2 + else: + line += [f'{nums[i]}'] + nums_in_line += 1 + if max_per_line and nums_in_line >= max_per_line: + lines += [delimiter.join(line)] + line = [] + nums_in_line = 0 i = j - return delimiter.join(spans) + if line: + lines += [delimiter.join(line)] + return lines if max_per_line else lines[0] def device_address(self, device): '''Return loopback IP addresses for an L3 attached device''' diff --git a/roles/access/templates/config-fs.j2 b/roles/access/templates/config-fs.j2 index 6715715..b127495 100644 --- a/roles/access/templates/config-fs.j2 +++ b/roles/access/templates/config-fs.j2 @@ -6,7 +6,9 @@ no enable service telnet-server no enable service web-server http no enable service web-server https -vlan range {{ vlans | map(attribute='vid') | union([1]) | compact_numlist }} +{% for vlan_range in vlans | map(attribute='vid') | union([1]) | compact_numlist(max_per_line=19) %} +vlan range {{ vlan_range }} +{% endfor %} {% for iface in interfaces %} interface {{ iface.name }} @@ -37,6 +39,9 @@ interface {{ iface.name }} {%- elif iface.mode and iface.mode.value == 'tagged' %} switchport mode trunk switchport trunk allowed vlan only {{ (iface.tagged_vlans or vlans) | map(attribute='vid') | compact_numlist }} +{%- elif iface.mode and iface.mode.value == 'tagged-all' %} + switchport mode uplink + switchport trunk allowed vlan only 2-4094 {% endif %} {% endif %}