diff --git a/lib/ansible/plugins/filter/network.py b/lib/ansible/plugins/filter/network.py index 07d6ecbd9d0..14d3920cc0e 100644 --- a/lib/ansible/plugins/filter/network.py +++ b/lib/ansible/plugins/filter/network.py @@ -399,6 +399,79 @@ def comp_type5(unencrypted_password, encrypted_password, return_original=False): return False +def vlan_parser(vlan_list, first_line_len=48, other_line_len=44): + + ''' + Input: Unsorted list of vlan integers + Output: Sorted string list of integers according to IOS-like vlan list rules + + 1. Vlans are listed in ascending order + 2. Runs of 3 or more consecutive vlans are listed with a dash + 3. The first line of the list can be first_line_len characters long + 4. Subsequent list lines can be other_line_len characters + ''' + + # Sort and remove duplicates + sorted_list = sorted(set(vlan_list)) + + if sorted_list[0] < 1 or sorted_list[-1] > 4094: + raise AnsibleFilterError('Valid VLAN range is 1-4094') + + parse_list = [] + idx = 0 + while idx < len(sorted_list): + start = idx + end = start + while end < len(sorted_list) - 1: + if sorted_list[end + 1] - sorted_list[end] == 1: + end += 1 + else: + break + + if start == end: + # Single VLAN + parse_list.append(str(sorted_list[idx])) + elif start + 1 == end: + # Run of 2 VLANs + parse_list.append(str(sorted_list[start])) + parse_list.append(str(sorted_list[end])) + else: + # Run of 3 or more VLANs + parse_list.append(str(sorted_list[start]) + '-' + str(sorted_list[end])) + idx = end + 1 + + line_count = 0 + result = [''] + for vlans in parse_list: + # First line (" switchport trunk allowed vlan ") + if line_count == 0: + if len(result[line_count] + vlans) > first_line_len: + result.append('') + line_count += 1 + result[line_count] += vlans + ',' + else: + result[line_count] += vlans + ',' + + # Subsequent lines (" switchport trunk allowed vlan add ") + else: + if len(result[line_count] + vlans) > other_line_len: + result.append('') + line_count += 1 + result[line_count] += vlans + ',' + else: + result[line_count] += vlans + ',' + + # Remove trailing orphan commas + for idx in range(0, len(result)): + result[idx] = result[idx].rstrip(',') + + # Sometimes text wraps to next line, but there are no remaining VLANs + if '' in result: + result.remove('') + + return result + + class FilterModule(object): """Filters for working with output from network devices""" @@ -408,7 +481,8 @@ class FilterModule(object): 'parse_xml': parse_xml, 'type5_pw': type5_pw, 'hash_salt': hash_salt, - 'comp_type5': comp_type5 + 'comp_type5': comp_type5, + 'vlan_parser': vlan_parser } def filters(self): diff --git a/test/units/plugins/filter/test_network.py b/test/units/plugins/filter/test_network.py index 3174b867563..508174ca0c9 100644 --- a/test/units/plugins/filter/test_network.py +++ b/test/units/plugins/filter/test_network.py @@ -23,7 +23,8 @@ import sys import pytest from units.compat import unittest -from ansible.plugins.filter.network import parse_xml, type5_pw, hash_salt, comp_type5 +from ansible.plugins.filter.network import parse_xml, type5_pw, hash_salt, comp_type5, vlan_parser + from ansible.errors import AnsibleFilterError fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures', 'network') @@ -165,3 +166,21 @@ class TestCompareType5(unittest.TestCase): encrypted_password = '$1$nTc1$Z28sUTcWfXlvVe2x.3XAa.' parsed = comp_type5(unencrypted_password, encrypted_password) self.assertEqual(parsed, False) + + +class TestVlanParser(unittest.TestCase): + + def test_compression(self): + raw_list = [1, 2, 3] + parsed_list = ['1-3'] + self.assertEqual(vlan_parser(raw_list), parsed_list) + + def test_single_line(self): + raw_list = [100, 1688, 3002, 3003, 3004, 3005, 3102, 3103, 3104, 3105, 3802, 3900, 3998, 3999] + parsed_list = ['100,1688,3002-3005,3102-3105,3802,3900,3998,3999'] + self.assertEqual(vlan_parser(raw_list), parsed_list) + + def test_multi_line(self): + raw_list = [100, 1688, 3002, 3004, 3005, 3050, 3102, 3104, 3105, 3151, 3802, 3900, 3998, 3999] + parsed_list = ['100,1688,3002,3004,3005,3050,3102,3104,3105,3151', '3802,3900,3998,3999'] + self.assertEqual(vlan_parser(raw_list), parsed_list)