Skip to content

Instantly share code, notes, and snippets.

@cidrblock
Last active March 11, 2021 00:04
Show Gist options
  • Save cidrblock/575966b578f204adc15ab05f14eaf3fd to your computer and use it in GitHub Desktop.
Save cidrblock/575966b578f204adc15ab05f14eaf3fd to your computer and use it in GitHub Desktop.
Add or remove vlans from a trunk port as needed
{# ############################################## fail if add, no longer supported #}
{% if 'add' in interface['value']['switchport']['trunk']['allowed_vlans'] %}
{{ you_cant_use_the_add_statement_anymore }}
{% endif %}
{# ############################################## remediate the trunk list #}
{% set trunk_vlan_changes = configuration_prechange['matches']['sections'][0]['section_content']|trunk_vlan_remediate(interface['value']['switchport']['trunk']['allowed_vlans']['vlans'],20) %}
{% if 'vlans' in trunk_vlan_changes and trunk_vlan_changes['vlans'] %}
{% set _ = desired.append("switchport trunk allowed vlan %s" % (trunk_vlan_changes['vlans'])) %}
{% endif %}{# vlans #}
{% for entry in trunk_vlan_changes['vlans_add'] %}
{% set _ = desired.append("switchport trunk allowed vlan add %s" % (entry)) %}
{% endfor %}{# vlans_add #}
{% for entry in trunk_vlan_changes['vlans_remove'] %}
{% set _ = desired.append("switchport trunk allowed vlan remove %s" % (entry)) %}
{% endfor %}{# vlans_remove #}
{% endif %}{# allowed-vlans #}
{% endif %}{# switchport trunk #}
##########################################
- name: "TEST 1> Test just parsing existing"
set_fact:
prefix: 'TEST 1>'
- name: "{{ prefix }} Set working configuration"
set_fact:
current_config:
- interface port-channel3
- description sealb1srt002-channel-po3
- switchport
- switchport mode trunk
- switchport trunk native vlan 5
- switchport trunk allowed vlan 99-1000,2100-2105,2195-2196,2200-2205
- switchport trunk allowed vlan add 2295-2296,2300-2305,2395-2396,2400-2405
- switchport trunk allowed vlan add 2495-2496,2500-2505,2595-2596,2600-2605
- spanning-tree port type network
- vpc peer-link
- name: "{{ prefix }} Set expected results"
set_fact:
result: "{{ current_config|trunk_vlan_list }}"
expected_list: "99-1000,2100-2105,2195-2196,2200-2205,2295-2296,2300-2305,2395-2396,2400-2405,2495-2496,2500-2505,2595-2596,2600-2605"
- name: "{{ prefix }} Assert results as expected"
assert:
that: result == expected_list
##########################################
- name: "TEST 2> Test both the addition and removal of vlans to an exisiting port"
set_fact:
prefix: 'TEST 2>'
- name: "{{ prefix }} Set working configuration"
set_fact:
vlans: 1,2,3,99-1000,2100-2105,2195-2196,2200-2205,2295-2296
current_config:
- interface port-channel3
- description sealb1srt002-channel-po3
- switchport
- switchport mode trunk
- switchport trunk native vlan 5
- switchport trunk allowed vlan 99-1000,2100-2105,2195-2196,2200-2205
- switchport trunk allowed vlan add 2295-2296,2300-2305,2395-2396,2400-2405
- switchport trunk allowed vlan add 2495-2496,2500-2505,2595-2596,2600-2605
- spanning-tree port type network
- vpc peer-link
- name: "{{ prefix }} Set expected results"
set_fact:
result: "{{ current_config|trunk_vlan_remediate(vlans,20) }}"
expected_vlans: null
expected_vlans_add:
- "1-3"
expected_vlans_remove:
- "2300-2305,2395-2396"
- "2400-2405,2495-2496"
- "2500-2505,2595-2596"
- "2600-2605"
- name: "{{ prefix }} Assert results as expected"
assert:
that: result['vlans'] == expected_vlans and
result['vlans_add'] == expected_vlans_add and
result['vlans_remove'] == expected_vlans_remove
##########################################
- name: "TEST 3> Test total removal of vlans from an exisiting port"
set_fact:
prefix: 'TEST 3>'
- name: "{{ prefix }} Set working configuration"
set_fact:
vlans:
current_config:
- interface port-channel3
- description sealb1srt002-channel-po3
- switchport
- switchport mode trunk
- switchport trunk native vlan 5
- switchport trunk allowed vlan 99-1000,2100-2105,2195-2196,2200-2205
- switchport trunk allowed vlan add 2295-2296,2300-2305,2395-2396,2400-2405
- switchport trunk allowed vlan add 2495-2496,2500-2505,2595-2596,2600-2605
- spanning-tree port type network
- vpc peer-link
- name: "{{ prefix }} Set expected results"
set_fact:
result: "{{ current_config|trunk_vlan_remediate(vlans,20) }}"
expected_vlans: null
expected_vlans_add: []
expected_vlans_remove:
- "99-1000,2100-2105"
- "2195-2196,2200-2205"
- "2295-2296,2300-2305"
- "2395-2396,2400-2405"
- "2495-2496,2500-2505"
- "2595-2596,2600-2605"
- name: "{{ prefix }} Assert results as expected"
assert:
that: result['vlans'] == expected_vlans and
result['vlans_add'] == expected_vlans_add and
result['vlans_remove'] == expected_vlans_remove
##########################################
- name: "TEST 4> Test adding all vlans to a new port"
set_fact:
prefix: 'TEST 4>'
- name: "{{ prefix }} Set working configuration"
set_fact:
vlans: 1,2,3,99-1000,2100-2105,2195-2196,2200-2205,2295-2296
current_config:
- interface port-channel3
- description sealb1srt002-channel-po3
- switchport
- switchport mode trunk
- switchport trunk native vlan 5
- name: "{{ prefix }} Set expected results"
set_fact:
result: "{{ current_config|trunk_vlan_remediate(vlans,20) }}"
expected_vlans: "1-3,99-1000"
expected_vlans_add:
- "2100-2105,2195-2196"
- "2200-2205,2295-2296"
expected_vlans_remove: []
- name: "{{ prefix }} Assert results as expected"
assert:
that: result['vlans'] == expected_vlans and
result['vlans_add'] == expected_vlans_add and
result['vlans_remove'] == expected_vlans_remove
import re
import itertools
def flatten(string):
""" Given a string of vlans, return a list of ints
Args:
string (str): A string of vlans
Returns:
list: A list of ints
"""
new_list = []
if isinstance(string, basestring):
if "," in string:
vlans = string.split(',')
else:
vlans = [string]
for entry in vlans:
if '-' in entry:
parts = entry.split('-')
flat_range = list(range(int(parts[0]), int(parts[1]) + 1))
new_list.extend(flat_range)
else:
try:
new_list.append(int(entry))
except ValueError:
return False
elif type(string) is int:
try:
new_list.append(string)
except ValueError:
return False
else:
return []
return new_list
def ranges(i):
""" Build ranges of consecutive numbers
Args:
i (list): a list of integers
Returns:
list: a list of tuples
"""
for a, b in itertools.groupby(enumerate(sorted(set(i))), lambda (x, y): y - x):
b = list(b)
yield b[0][1], b[-1][1]
def string_from_ranges(ranged):
""" Build a vlan list string from a list of tuples
Args:
ranged (list): A list of tuples
Returns:
str: A string of comma delimited vlans
"""
new_list = []
for entry in ranged:
if entry[0] == entry[1]:
new_list.append(str(entry[0]))
else:
new_list.append("%s-%s" % (entry[0], entry[1]))
return (",").join(new_list)
def splitter(text, length):
""" Split a comma delimited string into chunks
Args:
text (str): A comma delimited string
length (int): The maximum length of any line
Return:
list: A list of line, each at or smaller than the maximum
"""
words = iter(text.split(','))
lines, current = [], next(words)
for word in words:
if len(current) + 1 + len(word) > length:
lines.append(current)
current = word
else:
current += "," + word
lines.append(current)
return lines
def trunk_vlan_list(current_config):
""" Returns a list of vlans from a config
Args:
current_config (list): A list of lines making up the configuration
Returns:
str: A comma delimited list of the vlans
"""
match_lines = []
regex = re.compile(r'^switchport trunk allowed vlan (?:add\s)?(?P<vlans>[\d\-,]+)$')
for line in current_config:
result = re.match(regex, line)
if result:
match_lines.append(result.groupdict()['vlans'])
if match_lines:
current_vlan_list = ",".join(match_lines)
else:
current_vlan_list = ""
return current_vlan_list
def trunk_vlan_remediate(current_config, vlan_list, chunk_size):
""" Propose remediation to right a configuration to the data
Args:
current_config (list): The current configuration
vlan_list (str): The comma delimited list of vlans from the data
chunk_size (int): The maximum size of each line
Returns:
dict:
vlans: A string to be applied with the
switchport trunk allowed vlan command
vlans_add: A list of strings to be applied with the
switchport trunk allowed vlan add command
vlans_remove: A list of strings to be applied with the
switchport trunk allowed vlan remove command
"""
current_vlan_list = trunk_vlan_list(current_config)
if current_vlan_list:
current_vlan_list_flat = flatten(current_vlan_list)
else:
current_vlan_list_flat = []
if vlan_list:
desired_vlan_list_flat = flatten(vlan_list)
else:
desired_vlan_list_flat = []
add_vlans = [x for x in desired_vlan_list_flat if x not in current_vlan_list_flat]
if add_vlans:
add_vlans_with_ranges = string_from_ranges(ranges(add_vlans))
add_vlan_strings = splitter(add_vlans_with_ranges, chunk_size)
else:
add_vlan_strings = []
remove_vlans = [x for x in current_vlan_list_flat if x not in desired_vlan_list_flat]
if remove_vlans:
remove_vlans_with_ranges = string_from_ranges(ranges(remove_vlans))
remove_vlan_strings = splitter(remove_vlans_with_ranges, chunk_size)
else:
remove_vlan_strings = []
response = {}
if current_vlan_list:
response['vlans'] = None
response['vlans_add'] = add_vlan_strings
response['vlans_remove'] = remove_vlan_strings
else:
response['vlans'] = add_vlan_strings[0]
response['vlans_add'] = add_vlan_strings[1:]
response['vlans_remove'] = remove_vlan_strings
return response
class FilterModule(object):
def filters(self):
return {
'trunk_vlan_remediate': trunk_vlan_remediate,
'trunk_vlan_list': trunk_vlan_list
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment