mirror of https://github.com/ansible/ansible.git
NX-OS ACLs module (#67558)
* Added nxos_acls module * Adding tests * Added integration tests * Integration tests update * Updated documentation * Replaced state changes * Added warning detection * Added port-protocol mapping * Added change * Merge update changes * Completed integration tests, rtt * Added unit tests * Linting Added metaclass info * Changed port protocol to str * Fixed shippable errors, added examples * Fixed type error, updated examplespull/67926/head
parent
4ef7bd4c79
commit
7307339a7e
@ -0,0 +1,425 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat
|
||||
# GNU General Public License v3.0+
|
||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#############################################
|
||||
# WARNING #
|
||||
#############################################
|
||||
#
|
||||
# This file is auto generated by the resource
|
||||
# module builder playbook.
|
||||
#
|
||||
# Do not edit this file manually.
|
||||
#
|
||||
# Changes to this file will be over written
|
||||
# by the resource module builder.
|
||||
#
|
||||
# Changes should be made in the model used to
|
||||
# generate this file or in the resource module
|
||||
# builder template.
|
||||
#
|
||||
#############################################
|
||||
"""
|
||||
The arg spec for the nxos_acls module
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
class AclsArgs(object): # pylint: disable=R0903
|
||||
"""The arg spec for the nxos_acls module
|
||||
"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
pass
|
||||
|
||||
argument_spec = {
|
||||
'config': {
|
||||
'elements': 'dict',
|
||||
'options': {
|
||||
'acls': {
|
||||
'elements': 'dict',
|
||||
'options': {
|
||||
'aces': {
|
||||
'elements': 'dict',
|
||||
'mutually_exclusive': [['grant', 'remark']],
|
||||
'options': {
|
||||
'destination': {
|
||||
'mutually_exclusive':
|
||||
[['address', 'any', 'host', 'prefix'],
|
||||
[
|
||||
'wildcard_bits', 'any', 'host',
|
||||
'prefix'
|
||||
]],
|
||||
'options': {
|
||||
'address': {
|
||||
'type': 'str'
|
||||
},
|
||||
'any': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host': {
|
||||
'type': 'str'
|
||||
},
|
||||
'port_protocol': {
|
||||
'mutually_exclusive': [[
|
||||
'eq', 'lt', 'neq', 'gt',
|
||||
'range'
|
||||
]],
|
||||
'options': {
|
||||
'eq': {
|
||||
'type': 'str'
|
||||
},
|
||||
'gt': {
|
||||
'type': 'str'
|
||||
},
|
||||
'lt': {
|
||||
'type': 'str'
|
||||
},
|
||||
'neq': {
|
||||
'type': 'str'
|
||||
},
|
||||
'range': {
|
||||
'options': {
|
||||
'end': {
|
||||
'type': 'str'
|
||||
},
|
||||
'start': {
|
||||
'type': 'str'
|
||||
}
|
||||
},
|
||||
'required_together':
|
||||
[['start', 'end']],
|
||||
'type': 'dict'
|
||||
}
|
||||
},
|
||||
'type': 'dict'
|
||||
},
|
||||
'prefix': {
|
||||
'type': 'str'
|
||||
},
|
||||
'wildcard_bits': {
|
||||
'type': 'str'
|
||||
}
|
||||
},
|
||||
'required_together':
|
||||
[['address', 'wildcard_bits']],
|
||||
'type': 'dict'
|
||||
},
|
||||
'dscp': {
|
||||
'type': 'str'
|
||||
},
|
||||
'fragments': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'grant': {
|
||||
'choices': ['permit', 'deny'],
|
||||
'type': 'str'
|
||||
},
|
||||
'log': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'precedence': {
|
||||
'type': 'str'
|
||||
},
|
||||
'protocol': {
|
||||
'type': 'str'
|
||||
},
|
||||
'protocol_options': {
|
||||
'mutually_exclusive':
|
||||
[['icmp', 'igmp', 'tcp']],
|
||||
'options': {
|
||||
'icmp': {
|
||||
'options': {
|
||||
'administratively_prohibited':
|
||||
{
|
||||
'type': 'bool'
|
||||
},
|
||||
'alternate_address': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'conversion_error': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'dod_host_prohibited': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'dod_net_prohibited': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'echo': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'echo_reply': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'general_parameter_problem': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host_isolated': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host_precedence_unreachable':
|
||||
{
|
||||
'type': 'bool'
|
||||
},
|
||||
'host_redirect': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host_tos_redirect': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host_tos_unreachable': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host_unknown': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host_unreachable': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'information_reply': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'information_request': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'mask_reply': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'mask_request': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'message_code': {
|
||||
'type': 'int'
|
||||
},
|
||||
'message_type': {
|
||||
'type': 'int'
|
||||
},
|
||||
'mobile_redirect': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'net_redirect': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'net_tos_redirect': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'net_tos_unreachable': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'net_unreachable': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'network_unknown': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'no_room_for_option': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'option_missing': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'packet_too_big': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'parameter_problem': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'port_unreachable': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'precedence_unreachable': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'protocol_unreachable': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'reassembly_timeout': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'redirect': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'router_advertisement': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'router_solicitation': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'source_quench': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'source_route_failed': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'time_exceeded': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'timestamp_reply': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'timestamp_request': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'traceroute': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'ttl_exceeded': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'unreachable': {
|
||||
'type': 'bool'
|
||||
}
|
||||
},
|
||||
'type': 'dict'
|
||||
},
|
||||
'igmp': {
|
||||
'mutually_exclusive': [[
|
||||
'dvmrp', 'host_query',
|
||||
'host_report'
|
||||
]],
|
||||
'options': {
|
||||
'dvmrp': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host_query': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host_report': {
|
||||
'type': 'bool'
|
||||
}
|
||||
},
|
||||
'type':
|
||||
'dict'
|
||||
},
|
||||
'tcp': {
|
||||
'options': {
|
||||
'ack': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'established': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'fin': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'psh': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'rst': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'syn': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'urg': {
|
||||
'type': 'bool'
|
||||
}
|
||||
},
|
||||
'type': 'dict'
|
||||
}
|
||||
},
|
||||
'type': 'dict'
|
||||
},
|
||||
'remark': {
|
||||
'type': 'str'
|
||||
},
|
||||
'sequence': {
|
||||
'type': 'int'
|
||||
},
|
||||
'source': {
|
||||
'mutually_exclusive':
|
||||
[['address', 'any', 'host', 'prefix'],
|
||||
[
|
||||
'wildcard_bits', 'host', 'any',
|
||||
'prefix'
|
||||
]],
|
||||
'options': {
|
||||
'address': {
|
||||
'type': 'str'
|
||||
},
|
||||
'any': {
|
||||
'type': 'bool'
|
||||
},
|
||||
'host': {
|
||||
'type': 'str'
|
||||
},
|
||||
'port_protocol': {
|
||||
'mutually_exclusive':
|
||||
[['eq', 'lt', 'neq', 'range'],
|
||||
['eq', 'gt', 'neq', 'range']],
|
||||
'options': {
|
||||
'eq': {
|
||||
'type': 'str'
|
||||
},
|
||||
'gt': {
|
||||
'type': 'str'
|
||||
},
|
||||
'lt': {
|
||||
'type': 'str'
|
||||
},
|
||||
'neq': {
|
||||
'type': 'str'
|
||||
},
|
||||
'range': {
|
||||
'options': {
|
||||
'end': {
|
||||
'type': 'str'
|
||||
},
|
||||
'start': {
|
||||
'type': 'str'
|
||||
}
|
||||
},
|
||||
'type': 'dict'
|
||||
}
|
||||
},
|
||||
'type':
|
||||
'dict'
|
||||
},
|
||||
'prefix': {
|
||||
'type': 'str'
|
||||
},
|
||||
'wildcard_bits': {
|
||||
'type': 'str'
|
||||
}
|
||||
},
|
||||
'required_together':
|
||||
[['address', 'wildcard_bits']],
|
||||
'type':
|
||||
'dict'
|
||||
}
|
||||
},
|
||||
'type': 'list'
|
||||
},
|
||||
'name': {
|
||||
'required': True,
|
||||
'type': 'str'
|
||||
}
|
||||
},
|
||||
'type': 'list'
|
||||
},
|
||||
'afi': {
|
||||
'choices': ['ipv4', 'ipv6'],
|
||||
'required': True,
|
||||
'type': 'str'
|
||||
}
|
||||
},
|
||||
'type': 'list'
|
||||
},
|
||||
'running_config': {
|
||||
'type': 'str'
|
||||
},
|
||||
'state': {
|
||||
'choices': [
|
||||
'deleted', 'gathered', 'merged', 'overridden', 'rendered',
|
||||
'replaced', 'parsed'
|
||||
],
|
||||
'default':
|
||||
'merged',
|
||||
'type':
|
||||
'str'
|
||||
}
|
||||
} # pylint: disable=C0301
|
@ -0,0 +1,690 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat
|
||||
# GNU General Public License v3.0+
|
||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
The nxos_acls class
|
||||
It is in this file where the current configuration (as dict)
|
||||
is compared to the provided configuration (as dict) and the command set
|
||||
necessary to bring the current configuration to it's desired end-state is
|
||||
created
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import socket
|
||||
import re
|
||||
from copy import deepcopy
|
||||
from ansible.module_utils.network.common.cfg.base import ConfigBase
|
||||
from ansible.module_utils.network.common.utils import to_list, remove_empties, dict_diff
|
||||
from ansible.module_utils.network.nxos.facts.facts import Facts
|
||||
from ansible.module_utils.network.nxos.argspec.acls.acls import AclsArgs
|
||||
from ansible.module_utils.network.common import utils
|
||||
from ansible.module_utils.network.nxos.utils.utils import flatten_dict, search_obj_in_list, get_interface_type, normalize_interface
|
||||
|
||||
|
||||
class Acls(ConfigBase):
|
||||
"""
|
||||
The nxos_acls class
|
||||
"""
|
||||
|
||||
gather_subset = [
|
||||
'!all',
|
||||
'!min',
|
||||
]
|
||||
|
||||
gather_network_resources = [
|
||||
'acls',
|
||||
]
|
||||
|
||||
def __init__(self, module):
|
||||
super(Acls, self).__init__(module)
|
||||
|
||||
def get_acls_facts(self, data=None):
|
||||
""" Get the 'facts' (the current configuration)
|
||||
|
||||
:rtype: A dictionary
|
||||
:returns: The current configuration as a dictionary
|
||||
"""
|
||||
facts, _warnings = Facts(self._module).get_facts(
|
||||
self.gather_subset, self.gather_network_resources, data=data)
|
||||
acls_facts = facts['ansible_network_resources'].get('acls')
|
||||
if not acls_facts:
|
||||
return []
|
||||
return acls_facts
|
||||
|
||||
def edit_config(self, commands):
|
||||
"""Wrapper method for `_connection.edit_config()`
|
||||
This exists solely to allow the unit test framework to mock device connection calls.
|
||||
"""
|
||||
return self._connection.edit_config(commands)
|
||||
|
||||
def execute_module(self):
|
||||
""" Execute the module
|
||||
|
||||
:rtype: A dictionary
|
||||
:returns: The result from module execution
|
||||
"""
|
||||
result = {'changed': False}
|
||||
warnings = list()
|
||||
commands = list()
|
||||
state = self._module.params['state']
|
||||
action_states = ['merged', 'replaced', 'deleted', 'overridden']
|
||||
|
||||
if state == 'gathered':
|
||||
result['gathered'] = self.get_acls_facts()
|
||||
elif state == 'rendered':
|
||||
result['rendered'] = self.set_config({})
|
||||
elif state == 'parsed':
|
||||
result['parsed'] = self.set_config({})
|
||||
else:
|
||||
existing_acls_facts = self.get_acls_facts()
|
||||
commands.extend(self.set_config(existing_acls_facts))
|
||||
if commands and state in action_states:
|
||||
if not self._module.check_mode:
|
||||
self._connection.edit_config(commands)
|
||||
result['changed'] = True
|
||||
result['before'] = existing_acls_facts
|
||||
result['commands'] = commands
|
||||
|
||||
changed_acls_facts = self.get_acls_facts()
|
||||
if result['changed']:
|
||||
result['after'] = changed_acls_facts
|
||||
result['warnings'] = warnings
|
||||
return result
|
||||
|
||||
def set_config(self, existing_acls_facts):
|
||||
""" Collect the configuration from the args passed to the module,
|
||||
collect the current configuration (as a dict from facts)
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
config = self._module.params['config']
|
||||
want = []
|
||||
if config:
|
||||
for w in config:
|
||||
want.append(remove_empties(w))
|
||||
have = existing_acls_facts
|
||||
if want:
|
||||
want = self.convert_values(want)
|
||||
resp = self.set_state(want, have)
|
||||
return to_list(resp)
|
||||
|
||||
def convert_values(self, want):
|
||||
'''
|
||||
This method is used to map and convert the user given values with what will actually be present in the device configuation
|
||||
'''
|
||||
port_protocol = {
|
||||
515: 'lpd',
|
||||
517: 'talk',
|
||||
7: 'echo',
|
||||
9: 'discard',
|
||||
12: 'exec',
|
||||
13: 'login',
|
||||
14: 'cmd',
|
||||
109: 'pop2',
|
||||
19: 'chargen',
|
||||
20: 'ftp-data',
|
||||
21: 'ftp',
|
||||
23: 'telnet',
|
||||
25: 'smtp',
|
||||
540: 'uucp',
|
||||
543: 'klogin',
|
||||
544: 'kshell',
|
||||
37: 'time',
|
||||
43: 'whois',
|
||||
49: 'tacacs',
|
||||
179: 'bgp',
|
||||
53: 'domain',
|
||||
194: 'irc',
|
||||
70: 'gopher',
|
||||
79: 'finger',
|
||||
80: 'www',
|
||||
101: 'hostname',
|
||||
3949: 'drip',
|
||||
110: 'pop3',
|
||||
111: 'sunrpc',
|
||||
496: 'pim-auto-rp',
|
||||
113: 'ident',
|
||||
119: 'nntp'
|
||||
}
|
||||
protocol = {
|
||||
1: 'icmp',
|
||||
2: 'igmp',
|
||||
4: 'ip',
|
||||
6: 'tcp',
|
||||
103: 'pim',
|
||||
108: 'pcp',
|
||||
47: 'gre',
|
||||
17: 'udp',
|
||||
50: 'esp',
|
||||
51: 'ahp',
|
||||
88: 'eigrp',
|
||||
89: 'ospf',
|
||||
94: 'nos'
|
||||
}
|
||||
precedence = {
|
||||
0: 'routine',
|
||||
1: 'priority',
|
||||
2: 'immediate',
|
||||
3: 'flash',
|
||||
4: 'flash-override',
|
||||
5: 'critical',
|
||||
6: 'internet',
|
||||
7: 'network'
|
||||
}
|
||||
dscp = {
|
||||
10: 'AF11',
|
||||
12: 'AF12',
|
||||
14: 'AF13',
|
||||
18: 'AF21',
|
||||
20: 'AF22',
|
||||
22: 'AF23',
|
||||
26: 'AF31',
|
||||
28: 'AF32',
|
||||
30: 'AF33',
|
||||
34: 'AF41',
|
||||
36: 'AF42',
|
||||
38: 'AF43',
|
||||
8: 'CS1',
|
||||
16: 'CS2',
|
||||
24: 'CS3',
|
||||
32: 'CS4',
|
||||
40: 'CS5',
|
||||
48: 'CS6',
|
||||
56: 'CS7',
|
||||
0: 'Default',
|
||||
46: 'EF'
|
||||
}
|
||||
# port_pro_num = list(protocol.keys())
|
||||
for afi in want:
|
||||
if 'acls' in afi.keys():
|
||||
for acl in afi['acls']:
|
||||
if 'aces' in acl.keys():
|
||||
for ace in acl['aces']:
|
||||
if 'dscp' in ace.keys():
|
||||
if ace['dscp'].isdigit():
|
||||
ace['dscp'] = dscp[int(ace['dscp'])]
|
||||
ace['dscp'] = ace['dscp'].lower()
|
||||
if 'precedence' in ace.keys():
|
||||
if ace['precedence'].isdigit():
|
||||
ace['precedence'] = precedence[int(
|
||||
ace['precedence'])]
|
||||
if 'protocol' in ace.keys(
|
||||
) and ace['protocol'].isdigit() and int(
|
||||
ace['protocol']) in protocol.keys():
|
||||
ace['protocol'] = protocol[int(
|
||||
ace['protocol'])]
|
||||
# convert number to name
|
||||
if 'protocol' in ace.keys(
|
||||
) and ace['protocol'] in ['tcp', 'udp']:
|
||||
for end in ['source', 'destination']:
|
||||
if 'port_protocol' in ace[end].keys():
|
||||
key = list(ace[end]
|
||||
['port_protocol'].keys())[0]
|
||||
# key could be eq,gt,lt,neq or range
|
||||
if key != 'range':
|
||||
val = ace[end]['port_protocol'][
|
||||
key]
|
||||
if val.isdigit() and int(val) in port_protocol.keys(
|
||||
):
|
||||
ace[end]['port_protocol'][
|
||||
key] = port_protocol[int(
|
||||
val)]
|
||||
else:
|
||||
st = int(ace[end]['port_protocol']
|
||||
['range']['start'])
|
||||
|
||||
end = int(ace[end]['port_protocol']
|
||||
['range']['end'])
|
||||
|
||||
if st in port_protocol.keys():
|
||||
ace[end]['port_protocol'][
|
||||
'range'][
|
||||
'start'] = port_protocol[
|
||||
st]
|
||||
if end in port_protocol.keys():
|
||||
ace[end]['port_protocol'][
|
||||
'range'][
|
||||
'end'] = port_protocol[
|
||||
end]
|
||||
return want
|
||||
|
||||
def set_state(self, want, have):
|
||||
""" Select the appropriate function based on the state provided
|
||||
|
||||
:param want: the desired configuration as a dictionary
|
||||
:param have: the current configuration as a dictionary
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
state = self._module.params['state']
|
||||
commands = []
|
||||
if state == 'overridden':
|
||||
commands = (self._state_overridden(want, have))
|
||||
elif state == 'deleted':
|
||||
commands = (self._state_deleted(want, have))
|
||||
elif state == 'rendered':
|
||||
commands = self._state_rendered(want)
|
||||
elif state == 'parsed':
|
||||
want = self._module.params['running_config']
|
||||
commands = self._state_parsed(want)
|
||||
else:
|
||||
for w in want:
|
||||
if state == 'merged':
|
||||
commands.extend(self._state_merged(w, have))
|
||||
elif state == 'replaced':
|
||||
commands.extend(self._state_replaced(w, have))
|
||||
if state != 'parsed':
|
||||
commands = [c.strip() for c in commands]
|
||||
return commands
|
||||
|
||||
def _state_parsed(self, want):
|
||||
return self.get_acls_facts(want)
|
||||
|
||||
def _state_rendered(self, want):
|
||||
commands = []
|
||||
for w in want:
|
||||
commands.extend(self.set_commands(w, {}))
|
||||
return commands
|
||||
|
||||
def _state_replaced(self, want, have):
|
||||
""" The command generator when state is replaced
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
have_afi = search_obj_in_list(want['afi'], have, 'afi')
|
||||
del_dict = {'acls': []}
|
||||
want_names = []
|
||||
if have_afi != want:
|
||||
if have_afi:
|
||||
del_dict.update({'afi': have_afi['afi'], 'acls': []})
|
||||
if want.get('acls'):
|
||||
want_names = [w['name'] for w in want['acls']]
|
||||
have_names = [h['name'] for h in have_afi['acls']]
|
||||
want_acls = want.get('acls')
|
||||
for w in want_acls:
|
||||
acl_commands = []
|
||||
if w['name'] not in have_names:
|
||||
# creates new ACL in replaced state
|
||||
merge_dict = {'afi': want['afi'], 'acls': [w]}
|
||||
commands.extend(
|
||||
self._state_merged(merge_dict, have))
|
||||
else:
|
||||
# acl in want exists in have
|
||||
have_name = search_obj_in_list(
|
||||
w['name'], have_afi['acls'], 'name')
|
||||
have_aces = have_name.get('aces') if have_name.get(
|
||||
'aces') else []
|
||||
merge_aces = []
|
||||
del_aces = []
|
||||
w_aces = w.get('aces') if w.get('aces') else []
|
||||
|
||||
for ace in have_aces:
|
||||
if ace not in w_aces:
|
||||
del_aces.append(ace)
|
||||
for ace in w_aces:
|
||||
if ace not in have_aces:
|
||||
merge_aces.append(ace)
|
||||
merge_dict = {
|
||||
'afi': want['afi'],
|
||||
'acls': [{
|
||||
'name': w['name'],
|
||||
'aces': merge_aces
|
||||
}]
|
||||
}
|
||||
del_dict = {
|
||||
'afi': want['afi'],
|
||||
'acls': [{
|
||||
'name': w['name'],
|
||||
'aces': del_aces
|
||||
}]
|
||||
}
|
||||
if del_dict['acls']:
|
||||
acl_commands.extend(
|
||||
self._state_deleted([del_dict], have))
|
||||
acl_commands.extend(
|
||||
self._state_merged(merge_dict, have))
|
||||
|
||||
for i in range(1, len(acl_commands)):
|
||||
if acl_commands[i] == acl_commands[0]:
|
||||
acl_commands[i] = ''
|
||||
commands.extend(acl_commands)
|
||||
else:
|
||||
acls = []
|
||||
# no acls given in want, so delete all have acls
|
||||
for acl in have_afi['acls']:
|
||||
acls.append({'name': acl['name']})
|
||||
del_dict['acls'] = acls
|
||||
if del_dict['acls']:
|
||||
commands.extend(self._state_deleted([del_dict], have))
|
||||
|
||||
else:
|
||||
# want_afi is not present in have
|
||||
commands.extend(self._state_merged(want, have))
|
||||
|
||||
commands = list(filter(None, commands))
|
||||
return commands
|
||||
|
||||
def _state_overridden(self, want, have):
|
||||
""" The command generator when state is overridden
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to migrate the current configuration
|
||||
to the desired configuration
|
||||
"""
|
||||
commands = []
|
||||
want_afi = [w['afi'] for w in want]
|
||||
for h in have:
|
||||
if h['afi'] in want_afi:
|
||||
w = search_obj_in_list(h['afi'], want, 'afi')
|
||||
for h_acl in h['acls']:
|
||||
w_acl = search_obj_in_list(h_acl['name'], w['acls'],
|
||||
'name')
|
||||
if not w_acl:
|
||||
del_dict = {
|
||||
'afi': h['afi'],
|
||||
'acls': [{
|
||||
'name': h_acl['name']
|
||||
}]
|
||||
}
|
||||
commands.extend(self._state_deleted([del_dict], have))
|
||||
else:
|
||||
# if afi is not in want
|
||||
commands.extend(self._state_deleted([{'afi': h['afi']}], have))
|
||||
for w in want:
|
||||
commands.extend(self._state_replaced(w, have))
|
||||
return commands
|
||||
|
||||
def _state_merged(self, want, have):
|
||||
""" The command generator when state is merged
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to merge the provided into
|
||||
the current configuration
|
||||
"""
|
||||
return self.set_commands(want, have)
|
||||
|
||||
def _state_deleted(self, want, have):
|
||||
""" The command generator when state is deleted
|
||||
|
||||
:rtype: A list
|
||||
:returns: the commands necessary to remove the current configuration
|
||||
of the provided objects
|
||||
"""
|
||||
commands = []
|
||||
if want: # and have != want:
|
||||
for w in want:
|
||||
ip = 'ipv6' if w['afi'] == 'ipv6' else 'ip'
|
||||
acl_names = []
|
||||
have_afi = search_obj_in_list(w['afi'], have, 'afi')
|
||||
# if want['afi] not in have, ignore
|
||||
if have_afi:
|
||||
if w.get('acls'):
|
||||
for acl in w['acls']:
|
||||
if 'aces' in acl.keys():
|
||||
have_name = search_obj_in_list(
|
||||
acl['name'], have_afi['acls'], 'name')
|
||||
if have_name:
|
||||
ace_commands = []
|
||||
flag = 0
|
||||
for ace in acl['aces']:
|
||||
if list(ace.keys()) == ['sequence']:
|
||||
# only sequence number is specified to be deleted
|
||||
if 'aces' in have_name.keys():
|
||||
for h_ace in have_name['aces']:
|
||||
if h_ace[
|
||||
'sequence'] == ace[
|
||||
'sequence']:
|
||||
ace_commands.append(
|
||||
'no ' +
|
||||
str(ace['sequence']
|
||||
))
|
||||
flag = 1
|
||||
else:
|
||||
if 'aces' in have_name.keys():
|
||||
for h_ace in have_name['aces']:
|
||||
# when want['ace'] does not have seq number
|
||||
if 'sequence' not in ace.keys(
|
||||
):
|
||||
del h_ace['sequence']
|
||||
if ace == h_ace:
|
||||
ace_commands.append(
|
||||
'no ' +
|
||||
self.process_ace(
|
||||
ace))
|
||||
flag = 1
|
||||
if flag:
|
||||
ace_commands.insert(
|
||||
0,
|
||||
ip + ' access-list ' + acl['name'])
|
||||
commands.extend(ace_commands)
|
||||
else:
|
||||
# only name given
|
||||
for h in have_afi['acls']:
|
||||
if h['name'] == acl['name']:
|
||||
acl_names.append(acl['name'])
|
||||
for name in acl_names:
|
||||
commands.append('no ' + ip + ' access-list ' +
|
||||
name)
|
||||
|
||||
else:
|
||||
# 'only afi is given'
|
||||
if have_afi.get('acls'):
|
||||
for h in have_afi['acls']:
|
||||
acl_names.append(h['name'])
|
||||
for name in acl_names:
|
||||
commands.append('no ' + ip + ' access-list ' +
|
||||
name)
|
||||
else:
|
||||
v6 = []
|
||||
v4 = []
|
||||
v6_local = v4_local = None
|
||||
for h in have:
|
||||
if h['afi'] == 'ipv6':
|
||||
v6 = (acl['name'] for acl in h['acls'])
|
||||
if 'match_local_traffic' in h.keys():
|
||||
v6_local = True
|
||||
else:
|
||||
v4 = (acl['name'] for acl in h['acls'])
|
||||
if 'match_local_traffic' in h.keys():
|
||||
v4_local = True
|
||||
|
||||
self.no_commands(v4, commands, v4_local, 'ip')
|
||||
self.no_commands(v6, commands, v6_local, 'ipv6')
|
||||
|
||||
for name in v6:
|
||||
commands.append('no ipv6 access-list ' + name)
|
||||
if v4_local:
|
||||
commands.append('no ipv6 access-list match-local-traffic')
|
||||
|
||||
return commands
|
||||
|
||||
def no_commands(self, v_list, commands, match_local, ip):
|
||||
for name in v_list:
|
||||
commands.append('no ' + ip + ' access-list ' + name)
|
||||
if match_local:
|
||||
commands.append('no ' + ip + ' access-list match-local-traffic')
|
||||
|
||||
def set_commands(self, want, have):
|
||||
commands = []
|
||||
have_afi = search_obj_in_list(want['afi'], have, 'afi')
|
||||
ip = ''
|
||||
if 'v6' in want['afi']:
|
||||
ip = 'ipv6 '
|
||||
else:
|
||||
ip = 'ip '
|
||||
|
||||
if have_afi:
|
||||
if want.get('acls'):
|
||||
for w_acl in want['acls']:
|
||||
have_acl = search_obj_in_list(w_acl['name'],
|
||||
have_afi['acls'], 'name')
|
||||
name = w_acl['name']
|
||||
flag = 0
|
||||
ace_commands = []
|
||||
if have_acl != w_acl:
|
||||
if have_acl:
|
||||
ace_list = []
|
||||
if w_acl.get('aces') and have_acl.get('aces'):
|
||||
# case 1 --> sequence number not given in want --> new ace
|
||||
# case 2 --> new sequence number in want --> new ace
|
||||
# case 3 --> existing sequence number given --> update rule (only for merged state.
|
||||
# For replaced and overridden, rule is deleted in the state's config)
|
||||
|
||||
ace_list = [
|
||||
item for item in w_acl['aces']
|
||||
if 'sequence' not in item.keys()
|
||||
] # case 1
|
||||
|
||||
want_seq = [
|
||||
item['sequence'] for item in w_acl['aces']
|
||||
if 'sequence' in item.keys()
|
||||
]
|
||||
|
||||
have_seq = [
|
||||
item['sequence']
|
||||
for item in have_acl['aces']
|
||||
]
|
||||
|
||||
new_seq = list(set(want_seq) - set(have_seq))
|
||||
common_seq = list(
|
||||
set(want_seq).intersection(set(have_seq)))
|
||||
|
||||
temp_list = [
|
||||
item for item in w_acl['aces']
|
||||
if 'sequence' in item.keys()
|
||||
and item['sequence'] in new_seq
|
||||
] # case 2
|
||||
ace_list.extend(temp_list)
|
||||
for w in w_acl['aces']:
|
||||
self.argument_spec = AclsArgs.argument_spec
|
||||
params = utils.validate_config(
|
||||
self.argument_spec, {
|
||||
'config': [{
|
||||
'afi':
|
||||
want['afi'],
|
||||
'acls': [{
|
||||
'name': name,
|
||||
'aces': ace_list
|
||||
}]
|
||||
}]
|
||||
})
|
||||
if 'sequence' in w.keys(
|
||||
) and w['sequence'] in common_seq:
|
||||
temp_obj = search_obj_in_list(
|
||||
w['sequence'], have_acl['aces'],
|
||||
'sequence') # case 3
|
||||
if temp_obj != w:
|
||||
for key, val in w.items():
|
||||
temp_obj[key] = val
|
||||
ace_list.append(temp_obj)
|
||||
if self._module.params[
|
||||
'state'] == 'merged':
|
||||
ace_commands.append(
|
||||
'no ' + str(w['sequence']))
|
||||
# remove existing rule to update it
|
||||
elif w_acl.get('aces'):
|
||||
# 'have' has ACL defined without any ACE
|
||||
ace_list = [item for item in w_acl['aces']]
|
||||
for w_ace in ace_list:
|
||||
ace_commands.append(
|
||||
self.process_ace(w_ace).strip())
|
||||
flag = 1
|
||||
|
||||
if flag:
|
||||
ace_commands.insert(0,
|
||||
ip + 'access-list ' + name)
|
||||
|
||||
else:
|
||||
commands.append(ip + 'access-list ' + name)
|
||||
if 'aces' in w_acl.keys():
|
||||
for w_ace in w_acl['aces']:
|
||||
commands.append(
|
||||
self.process_ace(w_ace).strip())
|
||||
commands.extend(ace_commands)
|
||||
else:
|
||||
if want.get('acls'):
|
||||
for w_acl in want['acls']:
|
||||
name = w_acl['name']
|
||||
commands.append(ip + 'access-list ' + name)
|
||||
if 'aces' in w_acl.keys():
|
||||
for w_ace in w_acl['aces']:
|
||||
commands.append(self.process_ace(w_ace).strip())
|
||||
|
||||
return commands
|
||||
|
||||
def process_ace(self, w_ace):
|
||||
command = ''
|
||||
ace_keys = w_ace.keys()
|
||||
if 'remark' in ace_keys:
|
||||
command += 'remark ' + w_ace['remark'] + ' '
|
||||
else:
|
||||
command += w_ace['grant'] + ' '
|
||||
if 'protocol' in ace_keys:
|
||||
command += w_ace['protocol'] + ' '
|
||||
src = self.get_address(w_ace['source'], w_ace['protocol'])
|
||||
dest = self.get_address(w_ace['destination'],
|
||||
w_ace['protocol'])
|
||||
command += src + dest
|
||||
if 'protocol_options' in ace_keys:
|
||||
pro = list(w_ace['protocol_options'].keys())[0]
|
||||
if pro != w_ace['protocol']:
|
||||
self._module.fail_json(
|
||||
msg='protocol and protocol_options mismatch')
|
||||
flags = ''
|
||||
for k in w_ace['protocol_options'][pro].keys():
|
||||
k = re.sub('_', '-', k)
|
||||
flags += k + ' '
|
||||
command += flags
|
||||
if 'dscp' in ace_keys:
|
||||
command += 'dscp ' + w_ace['dscp'] + ' '
|
||||
if 'fragments' in ace_keys:
|
||||
command += 'fragments '
|
||||
if 'precedence' in ace_keys:
|
||||
command += 'precedence ' + w_ace['precedence'] + ' '
|
||||
if 'log' in ace_keys:
|
||||
command += 'log '
|
||||
if 'sequence' in ace_keys:
|
||||
command = str(w_ace['sequence']) + ' ' + command
|
||||
return command
|
||||
|
||||
def get_address(self, endpoint, pro=''):
|
||||
ret_addr = ''
|
||||
keys = list(endpoint.keys())
|
||||
if 'address' in keys:
|
||||
if 'wildcard_bits' not in keys:
|
||||
self._module.fail_json(
|
||||
msg='wildcard bits not specified for address')
|
||||
else:
|
||||
ret_addr = endpoint['address'] + \
|
||||
' ' + endpoint['wildcard_bits'] + ' '
|
||||
elif 'any' in keys:
|
||||
ret_addr = 'any '
|
||||
elif 'host' in keys:
|
||||
ret_addr = 'host ' + endpoint['host'] + ' '
|
||||
elif 'prefix' in keys:
|
||||
ret_addr = endpoint['prefix'] + ' '
|
||||
|
||||
if pro in ['tcp', 'udp']:
|
||||
if 'port_protocol' in keys:
|
||||
options = self.get_options(endpoint['port_protocol'])
|
||||
ret_addr += options
|
||||
return ret_addr
|
||||
|
||||
def get_options(self, item):
|
||||
com = ''
|
||||
subkey = list(item.keys())
|
||||
if 'range' in subkey:
|
||||
com = 'range ' + item['range']['start'] + \
|
||||
' ' + item['range']['end'] + ' '
|
||||
else:
|
||||
com = subkey[0] + ' ' + item[subkey[0]] + ' '
|
||||
return com
|
@ -0,0 +1,236 @@
|
||||
#
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat
|
||||
# GNU General Public License v3.0+
|
||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
"""
|
||||
The nxos acls fact class
|
||||
It is in this file the configuration is collected from the device
|
||||
for a given resource, parsed, and the facts tree is populated
|
||||
based on the configuration.
|
||||
"""
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
from copy import deepcopy
|
||||
|
||||
from ansible.module_utils.network.common import utils
|
||||
from ansible.module_utils.network.nxos.argspec.acls.acls import AclsArgs
|
||||
|
||||
|
||||
class AclsFacts(object):
|
||||
""" The nxos acls fact class
|
||||
"""
|
||||
|
||||
def __init__(self, module, subspec='config', options='options'):
|
||||
self._module = module
|
||||
self.argument_spec = AclsArgs.argument_spec
|
||||
spec = deepcopy(self.argument_spec)
|
||||
if subspec:
|
||||
if options:
|
||||
facts_argument_spec = spec[subspec][options]
|
||||
else:
|
||||
facts_argument_spec = spec[subspec]
|
||||
else:
|
||||
facts_argument_spec = spec
|
||||
self.generated_spec = utils.generate_dict(facts_argument_spec)
|
||||
|
||||
def get_device_data(self, connection):
|
||||
return connection.get(
|
||||
"show running-config | section 'ip(v6)* access-list'")
|
||||
|
||||
def populate_facts(self, connection, ansible_facts, data=None):
|
||||
""" Populate the facts for acls
|
||||
:param connection: the device connection
|
||||
:param ansible_facts: Facts dictionary
|
||||
:param data: previously collected conf
|
||||
:rtype: dictionary
|
||||
:returns: facts
|
||||
"""
|
||||
if not data:
|
||||
data = self.get_device_data(connection)
|
||||
data = re.split('\nip', data)
|
||||
v6 = []
|
||||
v4 = []
|
||||
|
||||
for i in range(len(data)):
|
||||
if str(data[i]):
|
||||
if 'v6' in str(data[i]).split()[0]:
|
||||
v6.append(data[i])
|
||||
else:
|
||||
v4.append(data[i])
|
||||
|
||||
resources = []
|
||||
resources.append(v6)
|
||||
resources.append(v4)
|
||||
objs = []
|
||||
for resource in resources:
|
||||
if resource:
|
||||
obj = self.render_config(self.generated_spec, resource)
|
||||
if obj:
|
||||
objs.append(obj)
|
||||
|
||||
ansible_facts['ansible_network_resources'].pop('acls', None)
|
||||
facts = {}
|
||||
if objs:
|
||||
params = utils.validate_config(self.argument_spec,
|
||||
{'config': objs})
|
||||
params = utils.remove_empties(params)
|
||||
facts['acls'] = params['config']
|
||||
|
||||
ansible_facts['ansible_network_resources'].update(facts)
|
||||
return ansible_facts
|
||||
|
||||
def get_endpoint(self, ace, pro):
|
||||
ret_dict = {}
|
||||
option = ace.split()[0]
|
||||
if option == 'any':
|
||||
ret_dict.update({'any': True})
|
||||
else:
|
||||
# it could be a.b.c.d or a.b.c.d/x or a.b.c.d/32
|
||||
if '/' in option: # or 'host' in option:
|
||||
ip = re.search(r'(.*)/(\d+)', option)
|
||||
if int(ip.group(2)) < 32 or 32 < int(ip.group(2)) < 128:
|
||||
ret_dict.update({'prefix': option})
|
||||
else:
|
||||
ret_dict.update({'host': ip.group(1)})
|
||||
else:
|
||||
ret_dict.update({'address': option})
|
||||
wb = ace.split()[1]
|
||||
ret_dict.update({'wildcard_bits': wb})
|
||||
ace = re.sub('{0}'.format(wb), '', ace, 1)
|
||||
ace = re.sub(option, '', ace, 1)
|
||||
if pro in ['tcp', 'udp']:
|
||||
keywords = ['eq', 'lt', 'gt', 'neq', 'range']
|
||||
if len(ace.split()) and ace.split()[0] in keywords:
|
||||
port_protocol = {}
|
||||
port_pro = re.search(r'(eq|lt|gt|neq) (\w*)', ace)
|
||||
if port_pro:
|
||||
port_protocol.update(
|
||||
{port_pro.group(1): port_pro.group(2)})
|
||||
ace = re.sub(port_pro.group(1), '', ace, 1)
|
||||
ace = re.sub(port_pro.group(2), '', ace, 1)
|
||||
else:
|
||||
limit = re.search(r'(range) (\w*) (\w*)', ace)
|
||||
if limit:
|
||||
port_protocol.update({
|
||||
'range': {
|
||||
'start': limit.group(2),
|
||||
'end': limit.group(3)
|
||||
}
|
||||
})
|
||||
ace = re.sub(limit.group(2), '', ace, 1)
|
||||
ace = re.sub(limit.group(3), '', ace, 1)
|
||||
if port_protocol:
|
||||
ret_dict.update({'port_protocol': port_protocol})
|
||||
return ace, ret_dict
|
||||
|
||||
def render_config(self, spec, conf):
|
||||
"""
|
||||
Render config as dictionary structure and delete keys
|
||||
from spec for null values
|
||||
|
||||
:param spec: The facts tree, generated from the argspec
|
||||
:param conf: The configuration
|
||||
:rtype: dictionary
|
||||
:returns: The generated config
|
||||
"""
|
||||
config = deepcopy(spec)
|
||||
protocol_options = {
|
||||
'tcp': ['fin', 'established', 'psh', 'rst', 'syn', 'urg', 'ack'],
|
||||
'icmp': [
|
||||
'administratively_prohibited', 'alternate_address',
|
||||
'conversion_error', 'dod_host_prohibited',
|
||||
'dod_net_prohibited', 'echo', 'echo_reply',
|
||||
'general_parameter_problem', 'host_isolated',
|
||||
'host_precedence_unreachable', 'host_redirect',
|
||||
'host_tos_redirect', 'host_tos_unreachable', 'host_unknown',
|
||||
'host_unreachable', 'information_reply', 'information_request',
|
||||
'mask_reply', 'mask_request', 'mobile_redirect',
|
||||
'net_redirect', 'net_tos_redirect', 'net_tos_unreachable',
|
||||
'net_unreachable', 'network_unknown', 'no_room_for_option',
|
||||
'option_missing', 'packet_too_big', 'parameter_problem',
|
||||
'port_unreachable', 'precedence_unreachable',
|
||||
'protocol_unreachable', 'reassembly_timeout', 'redirect',
|
||||
'router_advertisement', 'router_solicitation', 'source_quench',
|
||||
'source_route_failed', 'time_exceeded', 'timestamp_reply',
|
||||
'timestamp_request', 'traceroute', 'ttl_exceeded',
|
||||
'unreachable'
|
||||
],
|
||||
'igmp': ['dvmrp', 'host_query', 'host_report'],
|
||||
}
|
||||
if conf:
|
||||
if 'v6' in conf[0].split()[0]:
|
||||
config['afi'] = 'ipv6'
|
||||
else:
|
||||
config['afi'] = 'ipv4'
|
||||
config['acls'] = []
|
||||
for acl in conf:
|
||||
acls = {}
|
||||
if 'match-local-traffic' in acl:
|
||||
config['match_local_traffic'] = True
|
||||
continue
|
||||
acl = acl.split('\n')
|
||||
acl = [a.strip() for a in acl]
|
||||
acl = list(filter(None, acl))
|
||||
acls['name'] = re.match(r'(ip)?(v6)?\s?access-list (.*)',
|
||||
acl[0]).group(3)
|
||||
acls['aces'] = []
|
||||
for ace in list(filter(None, acl[1:])):
|
||||
if re.search(r'ip(.*)access-list.*', ace):
|
||||
break
|
||||
entry = {}
|
||||
ace = ace.strip()
|
||||
seq = re.match(r'(\d*)', ace).group(0)
|
||||
entry.update({'sequence': seq})
|
||||
ace = re.sub(seq, '', ace, 1)
|
||||
grant = ace.split()[0]
|
||||
rem = ''
|
||||
if grant != 'remark':
|
||||
entry.update({'grant': grant})
|
||||
else:
|
||||
rem = re.match('.*remark (.*)', ace).group(1)
|
||||
entry.update({'remark': rem})
|
||||
|
||||
if not rem:
|
||||
ace = re.sub(grant, '', ace, 1)
|
||||
pro = ace.split()[0]
|
||||
entry.update({'protocol': pro})
|
||||
ace = re.sub(pro, '', ace, 1)
|
||||
ace, source = self.get_endpoint(ace, pro)
|
||||
entry.update({'source': source})
|
||||
ace, dest = self.get_endpoint(ace, pro)
|
||||
entry.update({'destination': dest})
|
||||
|
||||
dscp = re.search(r'dscp (\w*)', ace)
|
||||
if dscp:
|
||||
entry.update({'dscp': dscp.group(1)})
|
||||
|
||||
frag = re.search(r'fragments', ace)
|
||||
if frag:
|
||||
entry.update({'fragments': True})
|
||||
|
||||
prec = re.search(r'precedence (\w*)', ace)
|
||||
if prec:
|
||||
entry.update({'precedence': prec.group(1)})
|
||||
|
||||
log = re.search('log', ace)
|
||||
if log:
|
||||
entry.update({'log': True})
|
||||
|
||||
if pro == 'tcp' or pro == 'icmp' or pro == 'igmp':
|
||||
pro_options = {}
|
||||
options = {}
|
||||
for option in protocol_options[pro]:
|
||||
option = re.sub('_', '-', option)
|
||||
if option in ace:
|
||||
option = re.sub('-', '_', option)
|
||||
options.update({option: True})
|
||||
if options:
|
||||
pro_options.update({pro: options})
|
||||
if pro_options:
|
||||
entry.update({'protocol_options': pro_options})
|
||||
acls['aces'].append(entry)
|
||||
config['acls'].append(acls)
|
||||
return utils.remove_empties(config)
|
@ -0,0 +1,825 @@
|
||||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
# Copyright 2019 Red Hat
|
||||
# GNU General Public License v3.0+
|
||||
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
#############################################
|
||||
# WARNING #
|
||||
#############################################
|
||||
#
|
||||
# This file is auto generated by the resource
|
||||
# module builder playbook.
|
||||
#
|
||||
# Do not edit this file manually.
|
||||
#
|
||||
# Changes to this file will be over written
|
||||
# by the resource module builder.
|
||||
#
|
||||
# Changes should be made in the model used to
|
||||
# generate this file or in the resource module
|
||||
# builder template.
|
||||
#
|
||||
#############################################
|
||||
"""
|
||||
The module file for nxos_acls
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
ANSIBLE_METADATA = {
|
||||
'metadata_version': '1.1',
|
||||
'status': ['preview'],
|
||||
'supported_by': 'network'
|
||||
}
|
||||
|
||||
DOCUMENTATION = """
|
||||
---
|
||||
module: nxos_acls
|
||||
version_added: '2.10'
|
||||
short_description: Manage named IP ACLs on the Cisco NX-OS platform
|
||||
description: Manage named IP ACLs on the Cisco NX-OS platform
|
||||
author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
|
||||
notes:
|
||||
- Tested against NX-OS 7.3.(0)D1(1) on VIRL
|
||||
- As NX-OS allows configuring a rule again with different sequence numbers,
|
||||
the user is expected to provide sequence numbers for the access control entries to preserve idempotency.
|
||||
If no sequence number is given, the rule will be added as a new rule by the device.
|
||||
- To parse configuration text, provide the output of show running-config | section access-list or a mocked up config
|
||||
options:
|
||||
running_config:
|
||||
description:
|
||||
- Parse given commands into structured format. Required if I(state=parsed).
|
||||
type: str
|
||||
config:
|
||||
description: A dictionary of ACL options.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
afi:
|
||||
description: The Address Family Indicator (AFI) for the ACL.
|
||||
type: str
|
||||
required: true
|
||||
choices: ['ipv4', 'ipv6']
|
||||
acls:
|
||||
description: A list of the ACLs.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
name:
|
||||
description: Name of the ACL.
|
||||
type: str
|
||||
required: true
|
||||
aces:
|
||||
description: The entries within the ACL.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
grant:
|
||||
description: Action to be applied on the rule.
|
||||
type: str
|
||||
choices: ['permit', 'deny']
|
||||
destination:
|
||||
description: Specify the packet destination.
|
||||
type: dict
|
||||
suboptions:
|
||||
address:
|
||||
description: Destination network address.
|
||||
type: str
|
||||
any:
|
||||
description: Any destination address.
|
||||
type: bool
|
||||
host:
|
||||
description: Host IP address.
|
||||
type: str
|
||||
port_protocol:
|
||||
description: Specify the destination port or protocol (only for TCP and UDP).
|
||||
type: dict
|
||||
suboptions:
|
||||
eq:
|
||||
description: Match only packets on a given port number.
|
||||
type: str
|
||||
gt:
|
||||
description: Match only packets with a greater port number.
|
||||
type: str
|
||||
lt:
|
||||
description: Match only packets with a lower port number.
|
||||
type: str
|
||||
neq:
|
||||
description: Match only packets not on a given port number.
|
||||
type: str
|
||||
range:
|
||||
description: Match only packets in the range of port numbers.
|
||||
type: dict
|
||||
suboptions:
|
||||
start:
|
||||
description: Specify the start of the port range.
|
||||
type: str
|
||||
end:
|
||||
description: Specify the end of the port range.
|
||||
type: str
|
||||
prefix:
|
||||
description: Destination network prefix. Only for prefixes of value less than 31 for ipv4 and 127 for ipv6.
|
||||
Prefixes of 32 (ipv4) and 128 (ipv6) should be given in the 'host' key.
|
||||
type: str
|
||||
wildcard_bits:
|
||||
description: Destination wildcard bits.
|
||||
type: str
|
||||
|
||||
dscp:
|
||||
description: Match packets with given DSCP value.
|
||||
type: str
|
||||
|
||||
fragments:
|
||||
description: Check non-initial fragments.
|
||||
type: bool
|
||||
|
||||
remark:
|
||||
description: Access list entry comment.
|
||||
type: str
|
||||
|
||||
sequence:
|
||||
description: Sequence number.
|
||||
type: int
|
||||
|
||||
source:
|
||||
description: Specify the packet source.
|
||||
type: dict
|
||||
suboptions:
|
||||
address:
|
||||
description: Source network address.
|
||||
type: str
|
||||
any:
|
||||
description: Any source address.
|
||||
type: bool
|
||||
host:
|
||||
description: Host IP address.
|
||||
type: str
|
||||
port_protocol:
|
||||
description: Specify the destination port or protocol (only for TCP and UDP).
|
||||
type: dict
|
||||
suboptions:
|
||||
eq:
|
||||
description: Match only packets on a given port number.
|
||||
type: str
|
||||
gt:
|
||||
description: Match only packets with a greater port number.
|
||||
type: str
|
||||
lt:
|
||||
description: Match only packets with a lower port number.
|
||||
type: str
|
||||
neq:
|
||||
description: Match only packets not on a given port number.
|
||||
type: str
|
||||
range:
|
||||
description: Match only packets in the range of port numbers.
|
||||
type: dict
|
||||
suboptions:
|
||||
start:
|
||||
description: Specify the start of the port range.
|
||||
type: str
|
||||
end:
|
||||
description: Specify the end of the port range.
|
||||
type: str
|
||||
prefix:
|
||||
description: Source network prefix. Only for prefixes of mask value less than 31 for ipv4 and 127 for ipv6.
|
||||
Prefixes of mask 32 (ipv4) and 128 (ipv6) should be given in the 'host' key.
|
||||
type: str
|
||||
wildcard_bits:
|
||||
description: Source wildcard bits.
|
||||
type: str
|
||||
|
||||
log:
|
||||
description: Log matches against this entry.
|
||||
type: bool
|
||||
|
||||
precedence:
|
||||
description: Match packets with given precedence value.
|
||||
type: str
|
||||
|
||||
protocol:
|
||||
description: Specify the protocol.
|
||||
type: str
|
||||
|
||||
protocol_options:
|
||||
description: All possible suboptions for the protocol chosen.
|
||||
type: dict
|
||||
suboptions:
|
||||
icmp:
|
||||
description: ICMP protocol options.
|
||||
type: dict
|
||||
suboptions:
|
||||
administratively_prohibited:
|
||||
description: Administratively prohibited
|
||||
type: bool
|
||||
alternate_address:
|
||||
description: Alternate address
|
||||
type: bool
|
||||
conversion_error:
|
||||
description: Datagram conversion
|
||||
type: bool
|
||||
dod_host_prohibited:
|
||||
description: Host prohibited
|
||||
type: bool
|
||||
dod_net_prohibited:
|
||||
description: Net prohibited
|
||||
type: bool
|
||||
echo:
|
||||
description: Echo (ping)
|
||||
type: bool
|
||||
echo_reply:
|
||||
description: Echo reply
|
||||
type: bool
|
||||
general_parameter_problem:
|
||||
description: Parameter problem
|
||||
type: bool
|
||||
host_isolated:
|
||||
description: Host isolated
|
||||
type: bool
|
||||
host_precedence_unreachable:
|
||||
description: Host unreachable for precedence
|
||||
type: bool
|
||||
host_redirect:
|
||||
description: Host redirect
|
||||
type: bool
|
||||
host_tos_redirect:
|
||||
description: Host redirect for TOS
|
||||
type: bool
|
||||
host_tos_unreachable:
|
||||
description: Host unreachable for TOS
|
||||
type: bool
|
||||
host_unknown:
|
||||
description: Host unknown
|
||||
type: bool
|
||||
host_unreachable:
|
||||
description: Host unreachable
|
||||
type: bool
|
||||
information_reply:
|
||||
description: Information replies
|
||||
type: bool
|
||||
information_request:
|
||||
description: Information requests
|
||||
type: bool
|
||||
mask_reply:
|
||||
description: Mask replies
|
||||
type: bool
|
||||
mask_request:
|
||||
description: Mask requests
|
||||
type: bool
|
||||
message_code:
|
||||
description: ICMP message code
|
||||
type: int
|
||||
message_type:
|
||||
description: ICMP message type
|
||||
type: int
|
||||
mobile_redirect:
|
||||
description: Mobile host redirect
|
||||
type: bool
|
||||
net_redirect:
|
||||
description: Network redirect
|
||||
type: bool
|
||||
net_tos_redirect:
|
||||
description: Net redirect for TOS
|
||||
type: bool
|
||||
net_tos_unreachable:
|
||||
description: Network unreachable for TOS
|
||||
type: bool
|
||||
net_unreachable:
|
||||
description: Net unreachable
|
||||
type: bool
|
||||
network_unknown:
|
||||
description: Network unknown
|
||||
type: bool
|
||||
no_room_for_option:
|
||||
description: Parameter required but no room
|
||||
type: bool
|
||||
option_missing:
|
||||
description: Parameter required but not present
|
||||
type: bool
|
||||
packet_too_big:
|
||||
description: Fragmentation needed and DF set
|
||||
type: bool
|
||||
parameter_problem:
|
||||
description: All parameter problems
|
||||
type: bool
|
||||
port_unreachable:
|
||||
description: Port unreachable
|
||||
type: bool
|
||||
precedence_unreachable:
|
||||
description: Precedence cutoff
|
||||
type: bool
|
||||
protocol_unreachable:
|
||||
description: Protocol unreachable
|
||||
type: bool
|
||||
reassembly_timeout:
|
||||
description: Reassembly timeout
|
||||
type: bool
|
||||
redirect:
|
||||
description: All redirects
|
||||
type: bool
|
||||
router_advertisement:
|
||||
description: Router discovery advertisements
|
||||
type: bool
|
||||
router_solicitation:
|
||||
description: Router discovery solicitations
|
||||
type: bool
|
||||
source_quench:
|
||||
description: Source quenches
|
||||
type: bool
|
||||
source_route_failed:
|
||||
description: Source route failed
|
||||
type: bool
|
||||
time_exceeded:
|
||||
description: All time exceeded.
|
||||
type: bool
|
||||
timestamp_reply:
|
||||
description: Timestamp replies
|
||||
type: bool
|
||||
timestamp_request:
|
||||
description: Timestamp requests
|
||||
type: bool
|
||||
traceroute:
|
||||
description: Traceroute
|
||||
type: bool
|
||||
ttl_exceeded:
|
||||
description: TTL exceeded
|
||||
type: bool
|
||||
unreachable:
|
||||
description: All unreachables
|
||||
type: bool
|
||||
tcp:
|
||||
description: TCP flags.
|
||||
type: dict
|
||||
suboptions:
|
||||
ack:
|
||||
description: Match on the ACK bit
|
||||
type: bool
|
||||
established:
|
||||
description: Match established connections
|
||||
type: bool
|
||||
fin:
|
||||
description: Match on the FIN bit
|
||||
type: bool
|
||||
psh:
|
||||
description: Match on the PSH bit
|
||||
type: bool
|
||||
rst:
|
||||
description: Match on the RST bit
|
||||
type: bool
|
||||
syn:
|
||||
description: Match on the SYN bit
|
||||
type: bool
|
||||
urg:
|
||||
description: Match on the URG bit
|
||||
type: bool
|
||||
igmp:
|
||||
description: IGMP protocol options.
|
||||
type: dict
|
||||
suboptions:
|
||||
dvmrp:
|
||||
description: Distance Vector Multicast Routing Protocol
|
||||
type: bool
|
||||
host_query:
|
||||
description: Host Query
|
||||
type: bool
|
||||
host_report:
|
||||
description: Host Report
|
||||
type: bool
|
||||
|
||||
state:
|
||||
description:
|
||||
- The state the configuration should be left in
|
||||
type: str
|
||||
choices:
|
||||
- deleted
|
||||
- gathered
|
||||
- merged
|
||||
- overridden
|
||||
- rendered
|
||||
- replaced
|
||||
- parsed
|
||||
default: merged
|
||||
"""
|
||||
EXAMPLES = """
|
||||
# Using merged
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
|
||||
- name: Merge new ACLs configuration
|
||||
nxos_acls:
|
||||
config:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: ACL1v4
|
||||
aces:
|
||||
- grant: deny
|
||||
destination:
|
||||
address: 192.0.2.64
|
||||
wildcard_bits: 0.0.0.255
|
||||
source:
|
||||
any: true
|
||||
port_protocol:
|
||||
lt: 55
|
||||
protocol: tcp
|
||||
protocol_options:
|
||||
tcp:
|
||||
ack: true
|
||||
fin: true
|
||||
sequence: 50
|
||||
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: ACL1v6
|
||||
aces:
|
||||
- grant: permit
|
||||
sequence: 10
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
prefix: 2001:db8:12::/32
|
||||
protocol: sctp
|
||||
state: merged
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# ip access-list ACL1v4
|
||||
# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
|
||||
# ipv6 access-list ACL1v6
|
||||
# 10 permit sctp any any
|
||||
|
||||
# Using replaced
|
||||
|
||||
# Before state:
|
||||
# ----------------
|
||||
#
|
||||
# ip access-list ACL1v4
|
||||
# 10 permit ip any any
|
||||
# 20 deny udp any any
|
||||
# ip access-list ACL2v4
|
||||
# 10 permit ahp 192.0.2.0 0.0.0.255 any
|
||||
# ip access-list ACL1v6
|
||||
# 10 permit sctp any any
|
||||
# 20 remark IPv6 ACL
|
||||
# ip access-list ACL2v6
|
||||
# 10 deny ipv6 any 2001:db8:3000::/36
|
||||
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
|
||||
|
||||
- name: Replace existing ACL configuration with provided configuration
|
||||
nxos_acls:
|
||||
config:
|
||||
- afi: ipv4
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: ACL1v6
|
||||
aces:
|
||||
- sequence: 20
|
||||
grant: permit
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
any: true
|
||||
protocol: pip
|
||||
|
||||
- remark: Replaced ACE
|
||||
|
||||
- name: ACL2v6
|
||||
state: replaced
|
||||
|
||||
# After state:
|
||||
# ---------------
|
||||
#
|
||||
# ipv6 access-list ACL1v6
|
||||
# 20 permit pip any any
|
||||
# 30 remark Replaced ACE
|
||||
# ipv6 access-list ACL2v6
|
||||
|
||||
# Using overridden
|
||||
|
||||
# Before state:
|
||||
# ----------------
|
||||
#
|
||||
# ip access-list ACL1v4
|
||||
# 10 permit ip any any
|
||||
# 20 deny udp any any
|
||||
# ip access-list ACL2v4
|
||||
# 10 permit ahp 192.0.2.0 0.0.0.255 any
|
||||
# ip access-list ACL1v6
|
||||
# 10 permit sctp any any
|
||||
# 20 remark IPv6 ACL
|
||||
# ip access-list ACL2v6
|
||||
# 10 deny ipv6 any 2001:db8:3000::/36
|
||||
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
|
||||
|
||||
- name: Override existing configuration with provided configuration
|
||||
nxos_acls:
|
||||
config:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: NewACL
|
||||
aces:
|
||||
- grant: deny
|
||||
source:
|
||||
address: 192.0.2.0
|
||||
wildcard_bits: 0.0.255.255
|
||||
destination:
|
||||
any: true
|
||||
protocol: eigrp
|
||||
- remark: Example for overridden state
|
||||
state: overridden
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# ip access-list NewACL
|
||||
# 10 deny eigrp 192.0.2.0 0.0.255.255 any
|
||||
# 20 remark Example for overridden state
|
||||
|
||||
# Using deleted:
|
||||
#
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# ip access-list ACL1v4
|
||||
# 10 permit ip any any
|
||||
# 20 deny udp any any
|
||||
# ip access-list ACL2v4
|
||||
# 10 permit ahp 192.0.2.0 0.0.0.255 any
|
||||
# ip access-list ACL1v6
|
||||
# 10 permit sctp any any
|
||||
# 20 remark IPv6 ACL
|
||||
# ip access-list ACL2v6
|
||||
# 10 deny ipv6 any 2001:db8:3000::/36
|
||||
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
|
||||
|
||||
- name: Delete all ACLs
|
||||
nxos_acls:
|
||||
config:
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# -----------
|
||||
#
|
||||
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# ip access-list ACL1v4
|
||||
# 10 permit ip any any
|
||||
# 20 deny udp any any
|
||||
# ip access-list ACL2v4
|
||||
# 10 permit ahp 192.0.2.0 0.0.0.255 any
|
||||
# ip access-list ACL1v6
|
||||
# 10 permit sctp any any
|
||||
# 20 remark IPv6 ACL
|
||||
# ip access-list ACL2v6
|
||||
# 10 deny ipv6 any 2001:db8:3000::/36
|
||||
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
|
||||
|
||||
- name: Delete all ACLs in given AFI
|
||||
nxos_acls:
|
||||
config:
|
||||
- afi: ipv4
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# ip access-list ACL1v6
|
||||
# 10 permit sctp any any
|
||||
# 20 remark IPv6 ACL
|
||||
# ip access-list ACL2v6
|
||||
# 10 deny ipv6 any 2001:db8:3000::/36
|
||||
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
|
||||
|
||||
|
||||
|
||||
# Before state:
|
||||
# -------------
|
||||
#
|
||||
# ip access-list ACL1v4
|
||||
# 10 permit ip any any
|
||||
# 20 deny udp any any
|
||||
# ip access-list ACL2v4
|
||||
# 10 permit ahp 192.0.2.0 0.0.0.255 any
|
||||
# ip access-list ACL1v6
|
||||
# 10 permit sctp any any
|
||||
# 20 remark IPv6 ACL
|
||||
# ip access-list ACL2v6
|
||||
# 10 deny ipv6 any 2001:db8:3000::/36
|
||||
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
|
||||
|
||||
- name: Delete specific ACLs
|
||||
nxos_acls:
|
||||
config:
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: ACL1v6
|
||||
aces:
|
||||
- grant: permit
|
||||
sequence: 10
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
any: true
|
||||
protocol: sctp
|
||||
|
||||
- sequence: 20
|
||||
state: deleted
|
||||
|
||||
# After state:
|
||||
# ------------
|
||||
#
|
||||
# ip access-list ACL1v4
|
||||
# 10 permit ip any any
|
||||
# 20 deny udp any any
|
||||
# ip access-list ACL2v4
|
||||
# 10 permit ahp 192.0.2.0 0.0.0.255 any
|
||||
# ip access-list ACl1v6
|
||||
# ip access-list ACL2v6
|
||||
# 10 deny ipv6 any 2001:db8:3000::/36
|
||||
# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
|
||||
|
||||
# Using parsed
|
||||
|
||||
- name: Parse given config to structured data
|
||||
nxos_acls:
|
||||
running_config: |
|
||||
ip access-list ACL1v4
|
||||
50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
|
||||
ipv6 access-list ACL1v6
|
||||
10 permit sctp any any
|
||||
state: parsed
|
||||
|
||||
# returns:
|
||||
# parsed:
|
||||
# - afi: ipv4
|
||||
# acls:
|
||||
# - name: ACL1v4
|
||||
# aces:
|
||||
# - grant: deny
|
||||
# destination:
|
||||
# address: 192.0.2.64
|
||||
# wildcard_bits: 0.0.0.255
|
||||
# source:
|
||||
# any: true
|
||||
# port_protocol:
|
||||
# lt: 55
|
||||
# protocol: tcp
|
||||
# protocol_options:
|
||||
# tcp:
|
||||
# ack: true
|
||||
# fin: true
|
||||
# sequence: 50
|
||||
#
|
||||
# - afi: ipv6
|
||||
# acls:
|
||||
# - name: ACL1v6
|
||||
# aces:
|
||||
# - grant: permit
|
||||
# sequence: 10
|
||||
# source:
|
||||
# any: true
|
||||
# destination:
|
||||
# prefix: 2001:db8:12::/32
|
||||
# protocol: sctp
|
||||
|
||||
|
||||
# Using gathered:
|
||||
|
||||
# Before state:
|
||||
# ------------
|
||||
#
|
||||
# ip access-list ACL1v4
|
||||
# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
|
||||
# ipv6 access-list ACL1v6
|
||||
# 10 permit sctp any any
|
||||
|
||||
- name: Gather existing configuration
|
||||
nxos_acls:
|
||||
state: gathered
|
||||
|
||||
# returns:
|
||||
# gathered:
|
||||
# - afi: ipv4
|
||||
# acls:
|
||||
# - name: ACL1v4
|
||||
# aces:
|
||||
# - grant: deny
|
||||
# destination:
|
||||
# address: 192.0.2.64
|
||||
# wildcard_bits: 0.0.0.255
|
||||
# source:
|
||||
# any: true
|
||||
# port_protocol:
|
||||
# lt: 55
|
||||
# protocol: tcp
|
||||
# protocol_options:
|
||||
# tcp:
|
||||
# ack: true
|
||||
# fin: true
|
||||
# sequence: 50
|
||||
|
||||
# - afi: ipv6
|
||||
# acls:
|
||||
# - name: ACL1v6
|
||||
# aces:
|
||||
# - grant: permit
|
||||
# sequence: 10
|
||||
# source:
|
||||
# any: true
|
||||
# destination:
|
||||
# prefix: 2001:db8:12::/32
|
||||
# protocol: sctp
|
||||
|
||||
|
||||
# Using rendered
|
||||
|
||||
- name: Render required configuration to be pushed to the device
|
||||
nxos_acls:
|
||||
config:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: ACL1v4
|
||||
aces:
|
||||
- grant: deny
|
||||
destination:
|
||||
address: 192.0.2.64
|
||||
wildcard_bits: 0.0.0.255
|
||||
source:
|
||||
any: true
|
||||
port_protocol:
|
||||
lt: 55
|
||||
protocol: tcp
|
||||
protocol_options:
|
||||
tcp:
|
||||
ack: true
|
||||
fin: true
|
||||
sequence: 50
|
||||
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: ACL1v6
|
||||
aces:
|
||||
- grant: permit
|
||||
sequence: 10
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
prefix: 2001:db8:12::/32
|
||||
protocol: sctp
|
||||
state: rendered
|
||||
|
||||
# returns:
|
||||
# rendered:
|
||||
# ip access-list ACL1v4
|
||||
# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
|
||||
# ipv6 access-list ACL1v6
|
||||
# 10 permit sctp any any
|
||||
"""
|
||||
RETURN = """
|
||||
before:
|
||||
description: The configuration prior to the model invocation.
|
||||
returned: always
|
||||
type: dict
|
||||
sample: >
|
||||
The configuration returned will always be in the same format
|
||||
of the parameters above.
|
||||
after:
|
||||
description: The resulting configuration model invocation.
|
||||
returned: when changed
|
||||
type: dict
|
||||
sample: >
|
||||
The configuration returned will always be in the same format
|
||||
of the parameters above.
|
||||
commands:
|
||||
description: The set of commands pushed to the remote device.
|
||||
returned: always
|
||||
type: list
|
||||
sample: ['ip access-list ACL1v4', '10 permit ip any any precedence critical log', '20 deny tcp any lt smtp host 192.0.2.64 ack fin']
|
||||
"""
|
||||
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
from ansible.module_utils.network.nxos.argspec.acls.acls import AclsArgs
|
||||
from ansible.module_utils.network.nxos.config.acls.acls import Acls
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main entry point for module execution
|
||||
|
||||
:returns: the result form module invocation
|
||||
"""
|
||||
module = AnsibleModule(argument_spec=AclsArgs.argument_spec,
|
||||
supports_check_mode=True)
|
||||
|
||||
result = Acls(module).execute_module()
|
||||
module.exit_json(**result)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,2 @@
|
||||
---
|
||||
testcase: "*"
|
@ -0,0 +1,2 @@
|
||||
dependencies:
|
||||
- prepare_nxos_tests
|
@ -0,0 +1,20 @@
|
||||
---
|
||||
- name: collect cli test cases
|
||||
find:
|
||||
paths: "{{ role_path }}/tests/cli"
|
||||
patterns: "{{ testcase }}.yml"
|
||||
connection: local
|
||||
register: test_cases
|
||||
|
||||
- set_fact:
|
||||
test_cases:
|
||||
files: "{{ test_cases.files }}"
|
||||
|
||||
- name: set test_items
|
||||
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
|
||||
|
||||
- name: run test cases (connection=network_cli)
|
||||
include: "{{ test_case_to_run }} ansible_connection=network_cli connection={{ cli }}"
|
||||
with_items: "{{ test_items }}"
|
||||
loop_control:
|
||||
loop_var: test_case_to_run
|
@ -0,0 +1,2 @@
|
||||
---
|
||||
- { include: cli.yaml, tags: ['cli'] }
|
@ -0,0 +1,69 @@
|
||||
---
|
||||
- debug:
|
||||
msg: Start nxos_acls deleted integration tests connection={{ansible_connection}}"
|
||||
|
||||
- include_tasks: populate_config.yaml
|
||||
|
||||
- block:
|
||||
- name: Deleted (All ACLs)
|
||||
nxos_acls:
|
||||
config:
|
||||
state: deleted
|
||||
|
||||
- name: Gather acls facts
|
||||
nxos_facts: &facts
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
gather_network_resources: acls
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "ansible_facts.network_resources == {}"
|
||||
|
||||
- include_tasks: populate_config.yaml
|
||||
|
||||
- name: Deleted
|
||||
nxos_acls: &deleted
|
||||
config:
|
||||
- afi: ipv4
|
||||
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: ACL1v6
|
||||
aces:
|
||||
- grant: permit
|
||||
sequence: 10
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
any: true
|
||||
protocol: sctp
|
||||
|
||||
- sequence: 20
|
||||
state: deleted
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed==True"
|
||||
- "'no ip access-list ACL1v4' in result.commands"
|
||||
- "'no ip access-list ACL2v4' in result.commands"
|
||||
- "'ipv6 access-list ACL1v6' in result.commands"
|
||||
- "'no 10 permit sctp any any' in result.commands"
|
||||
- "'no 20' in result.commands"
|
||||
- "result.commands | length == 5"
|
||||
|
||||
- name: Gather acls facts
|
||||
nxos_facts: *facts
|
||||
|
||||
- name: Idempotence - deleted
|
||||
nxos_acls: *deleted
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
always:
|
||||
- include_tasks: remove_config.yaml
|
@ -0,0 +1,34 @@
|
||||
---
|
||||
- debug:
|
||||
msg: Start nxos_acls gathered integration tests connection={{ansible_connection}}"
|
||||
|
||||
- include_tasks: populate_config.yaml
|
||||
|
||||
- block:
|
||||
- name: Gather acls facts
|
||||
nxos_facts: &facts
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
gather_network_resources: acls
|
||||
|
||||
- name: Gathered
|
||||
nxos_acls: &gathered
|
||||
state: gathered
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
- "ansible_facts.network_resources.acls == result.gathered"
|
||||
|
||||
- name: Idempotence - Gathered
|
||||
nxos_acls: *gathered
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
always:
|
||||
- include_tasks: remove_config.yaml
|
@ -0,0 +1,108 @@
|
||||
---
|
||||
- debug:
|
||||
msg: Start nxos_acls merged integration tests connection={{ansible_connection}}"
|
||||
|
||||
- include_tasks: remove_config.yaml
|
||||
|
||||
- block:
|
||||
- name: Merged
|
||||
nxos_acls: &merged
|
||||
config:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: ACL1v4
|
||||
aces:
|
||||
- grant: deny
|
||||
destination:
|
||||
address: 192.0.2.64
|
||||
wildcard_bits: 0.0.0.255
|
||||
source:
|
||||
any: true
|
||||
port_protocol:
|
||||
lt: 25
|
||||
protocol: tcp
|
||||
protocol_options:
|
||||
tcp:
|
||||
ack: true
|
||||
fin: true
|
||||
sequence: 50
|
||||
- grant: permit
|
||||
protocol: ip
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
any: true
|
||||
fragments: true
|
||||
log: true
|
||||
sequence: 20
|
||||
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: ACL1v6
|
||||
aces:
|
||||
- grant: permit
|
||||
sequence: 10
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
host: 2001:db8:12::128
|
||||
protocol: sctp
|
||||
state: merged
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == True"
|
||||
- "'ip access-list ACL1v4' in result.commands"
|
||||
- "'20 permit ip any any fragments log' in result.commands"
|
||||
- "'50 deny tcp any lt smtp 192.0.2.64 0.0.0.255 ack fin' in result.commands"
|
||||
- "'ipv6 access-list ACL1v6' in result.commands"
|
||||
- "'10 permit sctp any host 2001:db8:12::128' in result.commands"
|
||||
- "result.commands | length == 5 "
|
||||
|
||||
- name: Gather acls facts
|
||||
nxos_facts:
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
gather_network_resources: acls
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "ansible_facts.network_resources.acls == result.after"
|
||||
|
||||
- name: Idempotence - Merged
|
||||
nxos_acls: *merged
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
- name: Update one parameter of an ACE
|
||||
nxos_acls:
|
||||
config:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: ACL1v4
|
||||
aces:
|
||||
- grant: permit
|
||||
protocol: tcp
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
any: true
|
||||
sequence: 20
|
||||
precedence: 5
|
||||
state: merged
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == True"
|
||||
- "'ip access-list ACL1v4' in result.commands"
|
||||
- "'no 20' in result.commands"
|
||||
- "'20 permit tcp any any fragments precedence critical log' in result.commands"
|
||||
|
||||
always:
|
||||
- include_tasks: remove_config.yaml
|
@ -0,0 +1,99 @@
|
||||
---
|
||||
- debug:
|
||||
msg: Start nxos_acls overridden integration tests connection={{ansible_connection}}"
|
||||
|
||||
- include_tasks: populate_config.yaml
|
||||
|
||||
- block:
|
||||
- name: Overridden (first test)
|
||||
nxos_acls:
|
||||
config:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: NewACL
|
||||
aces:
|
||||
- grant: deny
|
||||
source:
|
||||
address: 192.0.2.0
|
||||
wildcard_bits: 0.0.255.255
|
||||
destination:
|
||||
any: true
|
||||
protocol: eigrp
|
||||
- remark: Example for overridden state
|
||||
state: overridden
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed==True"
|
||||
- "'no ip access-list ACL1v4' in result.commands"
|
||||
- "'no ip access-list ACL2v4' in result.commands"
|
||||
- "'no ipv6 access-list ACL1v6' in result.commands"
|
||||
- "'no ipv6 access-list ACL2v6' in result.commands"
|
||||
- "'ip access-list NewACL' in result.commands"
|
||||
- "'deny eigrp 192.0.2.0 0.0.255.255 any' in result.commands"
|
||||
- "'remark Example for overridden state' in result.commands"
|
||||
- "result.commands|length==7"
|
||||
|
||||
- name: Gather acls post facts
|
||||
nxos_facts: &facts
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
gather_network_resources: acls
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "ansible_facts.network_resources.acls == result.after"
|
||||
|
||||
- include_tasks: populate_config.yaml
|
||||
|
||||
- name: Overridden (second test)
|
||||
nxos_acls: &overridden
|
||||
config:
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: ACL1v6
|
||||
aces:
|
||||
- grant: deny
|
||||
protocol: udp
|
||||
destination:
|
||||
any: true
|
||||
source:
|
||||
host: 2001:db8:3431::12
|
||||
port_protocol:
|
||||
lt: 35
|
||||
sequence: 10
|
||||
state: overridden
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed==True"
|
||||
- "'no ip access-list ACL1v4' in result.commands"
|
||||
- "'no ip access-list ACL2v4' in result.commands"
|
||||
- "'no ipv6 access-list ACL2v6' in result.commands"
|
||||
- "'no ip access-list NewACL' in result.commands"
|
||||
- "'ipv6 access-list ACL1v6' in result.commands"
|
||||
- "'no 10 permit sctp any any' in result.commands"
|
||||
- "'no 20 remark IPv6 ACL' in result.commands"
|
||||
- "'10 deny udp host 2001:db8:3431::12 lt 35 any' in result.commands"
|
||||
- "result.commands|length==8"
|
||||
|
||||
- name: Gather acls post facts
|
||||
nxos_facts: *facts
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "ansible_facts.network_resources.acls == result.after"
|
||||
|
||||
- name: Idempotence - overridden
|
||||
nxos_acls: *overridden
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
always:
|
||||
- include_tasks: remove_config.yaml
|
@ -0,0 +1,45 @@
|
||||
---
|
||||
- debug:
|
||||
msg: Start nxos_acls gathered integration tests connection={{ansible_connection}}"
|
||||
|
||||
- include_tasks: populate_config.yaml
|
||||
|
||||
- block:
|
||||
- name: Gather acls facts
|
||||
nxos_facts: &facts
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
gather_network_resources: acls
|
||||
|
||||
- name: Parsed
|
||||
nxos_acls: &parsed
|
||||
running_config: |
|
||||
ip access-list ACL1v4
|
||||
10 permit ip any any
|
||||
20 deny udp any any
|
||||
ip access-list ACL2v4
|
||||
10 permit ahp 192.0.2.0 0.0.0.255 any
|
||||
ipv6 access-list ACL1v6
|
||||
10 permit sctp any any
|
||||
20 remark IPv6 ACL
|
||||
ipv6 access-list ACL2v6
|
||||
10 deny ipv6 any 2001:db8:3000::36/128
|
||||
20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
|
||||
state: parsed
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
- "ansible_facts.network_resources.acls == result.parsed"
|
||||
|
||||
- name: Idempotence - Parsed
|
||||
nxos_acls: *parsed
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that: "result.changed == false"
|
||||
|
||||
always:
|
||||
- include_tasks: remove_config.yaml
|
@ -0,0 +1,15 @@
|
||||
---
|
||||
- name: Add configuration
|
||||
cli_config:
|
||||
config: |
|
||||
ip access-list ACL1v4
|
||||
10 permit ip any any
|
||||
20 deny udp any any
|
||||
ip access-list ACL2v4
|
||||
10 permit ahp 192.0.2.0 0.0.0.255 any
|
||||
ipv6 access-list ACL1v6
|
||||
10 permit sctp any any
|
||||
20 remark IPv6 ACL
|
||||
ipv6 access-list ACL2v6
|
||||
10 deny ipv6 any host 2001:db8:3000::36
|
||||
20 permit tcp host 2001:db8:2000:2::2 host 2001:db8:2000:ab::2
|
@ -0,0 +1,9 @@
|
||||
---
|
||||
- name: Remove config
|
||||
cli_config:
|
||||
config: |
|
||||
no ip access-list ACL1v4
|
||||
no ip access-list ACL2v4
|
||||
no ipv6 access-list ACL1v6
|
||||
no ipv6 access-list ACL2v6
|
||||
no ip access-list NewACL
|
@ -0,0 +1,56 @@
|
||||
---
|
||||
- debug:
|
||||
msg: "Start nxos_acls rendered tests connection={{ ansible_connection }}"
|
||||
|
||||
- name: Rendered
|
||||
nxos_acls: &rendered
|
||||
config:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: ACL1v4
|
||||
aces:
|
||||
- grant: deny
|
||||
destination:
|
||||
address: 192.0.2.64
|
||||
wildcard_bits: 0.0.0.255
|
||||
source:
|
||||
any: true
|
||||
port_protocol:
|
||||
eq: 43
|
||||
protocol: tcp
|
||||
protocol_options:
|
||||
tcp:
|
||||
ack: true
|
||||
fin: true
|
||||
sequence: 50
|
||||
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: ACL1v6
|
||||
aces:
|
||||
- grant: permit
|
||||
sequence: 10
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
prefix: 2001:db8:12::/32
|
||||
protocol: sctp
|
||||
state: rendered
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
- "'ip access-list ACL1v4' in result.rendered"
|
||||
- "'50 deny tcp any eq whois 192.0.2.64 0.0.0.255 ack fin' in result.rendered"
|
||||
- "'ipv6 access-list ACL1v6' in result.rendered"
|
||||
- "'10 permit sctp any 2001:db8:12::/32' in result.rendered"
|
||||
- "result.rendered | length == 4"
|
||||
|
||||
- name: Idempotence - Rendered
|
||||
nxos_acls: *rendered
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
@ -0,0 +1,65 @@
|
||||
---
|
||||
- debug:
|
||||
msg: Start nxos_acls replaced integration tests connection={{ansible_connection}}"
|
||||
|
||||
- include_tasks: populate_config.yaml
|
||||
|
||||
- block:
|
||||
- name: Replaced
|
||||
nxos_acls: &replaced
|
||||
config:
|
||||
- afi: ipv4
|
||||
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: ACL1v6
|
||||
aces:
|
||||
- sequence: 30
|
||||
grant: permit
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
any: true
|
||||
protocol: pim
|
||||
|
||||
- sequence: 40
|
||||
remark: Replaced ACE
|
||||
- name: ACL2v6
|
||||
state: replaced
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "'no ip access-list ACL1v4' in result.commands"
|
||||
- "'no ip access-list ACL2v4' in result.commands"
|
||||
- "'ipv6 access-list ACL1v6' in result.commands"
|
||||
- "'no 10 permit sctp any any' in result.commands"
|
||||
- "'no 20 remark IPv6 ACL' in result.commands"
|
||||
- "'30 permit pim any any' in result.commands"
|
||||
- "'40 remark Replaced ACE' in result.commands"
|
||||
- "'ipv6 access-list ACL2v6' in result.commands"
|
||||
- "'no 10 deny ipv6 any host 2001:db8:3000::36' in result.commands"
|
||||
- "'no 20 permit tcp host 2001:db8:2000:2::2 host 2001:db8:2000:ab::2' in result.commands"
|
||||
- "result.commands|length == 10"
|
||||
|
||||
- name: Gather static_routes post facts
|
||||
nxos_facts:
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
gather_network_resources: acls
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "ansible_facts.network_resources.acls == result.after"
|
||||
|
||||
- name: Idempotence - Replaced
|
||||
nxos_acls: *replaced
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == false"
|
||||
|
||||
always:
|
||||
- include_tasks: remove_config.yaml
|
@ -0,0 +1,87 @@
|
||||
---
|
||||
- debug:
|
||||
msg: "Start nxos_acls round trip integration tests connection = {{ansible_connection}}"
|
||||
|
||||
- block:
|
||||
- name: RTT - Apply provided configuration
|
||||
nxos_acls:
|
||||
config:
|
||||
- afi: ipv4
|
||||
acls:
|
||||
- name: ACL1v4
|
||||
aces:
|
||||
- grant: deny
|
||||
destination:
|
||||
address: 192.0.2.64
|
||||
wildcard_bits: 0.0.0.255
|
||||
source:
|
||||
any: true
|
||||
port_protocol:
|
||||
lt: 25
|
||||
protocol: tcp
|
||||
protocol_options:
|
||||
tcp:
|
||||
ack: true
|
||||
fin: true
|
||||
sequence: 50
|
||||
|
||||
- grant: permit
|
||||
protocol: ip
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
any: true
|
||||
fragments: true
|
||||
log: true
|
||||
sequence: 20
|
||||
state: merged
|
||||
|
||||
- name: Gather interfaces facts
|
||||
nxos_facts:
|
||||
gather_subset:
|
||||
- "!all"
|
||||
- "!min"
|
||||
gather_network_resources:
|
||||
- acls
|
||||
|
||||
- name: Apply configuration to be reverted
|
||||
nxos_acls:
|
||||
config:
|
||||
- afi: ipv6
|
||||
acls:
|
||||
- name: ACL1v6
|
||||
aces:
|
||||
- grant: permit
|
||||
sequence: 10
|
||||
source:
|
||||
any: true
|
||||
destination:
|
||||
host: 2001:db8:12::128
|
||||
protocol: sctp
|
||||
state: overridden
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == True"
|
||||
- "'no ip access-list ACL1v4' in result.commands"
|
||||
- "'ipv6 access-list ACL1v6' in result.commands"
|
||||
- "'10 permit sctp any host 2001:db8:12::128' in result.commands"
|
||||
- "result.commands | length == 3 "
|
||||
|
||||
- name: Revert back to base configuration using facts round trip
|
||||
nxos_acls:
|
||||
config: "{{ ansible_facts['network_resources']['acls'] }}"
|
||||
state: overridden
|
||||
register: result
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- "result.changed == True"
|
||||
- "'ip access-list ACL1v4' in result.commands"
|
||||
- "'20 permit ip any any fragments log' in result.commands"
|
||||
- "'50 deny tcp any lt smtp 192.0.2.64 0.0.0.255 fin ack' in result.commands"
|
||||
- "'no ipv6 access-list ACL1v6' in result.commands"
|
||||
- "result.commands | length == 4 "
|
||||
always:
|
||||
- include_tasks: remove_config.yaml
|
@ -0,0 +1,370 @@
|
||||
#
|
||||
# (c) 2019, Ansible by Red Hat, inc
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
#
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from ansible.modules.network.nxos import nxos_acls
|
||||
from units.compat.mock import patch, MagicMock
|
||||
from units.modules.utils import set_module_args
|
||||
from .nxos_module import TestNxosModule, load_fixture
|
||||
|
||||
|
||||
class TestNxosAclsModule(TestNxosModule):
|
||||
|
||||
module = nxos_acls
|
||||
|
||||
def setUp(self):
|
||||
super(TestNxosAclsModule, self).setUp()
|
||||
|
||||
self.mock_get_config = patch(
|
||||
'ansible.module_utils.network.common.network.Config.get_config')
|
||||
self.get_config = self.mock_get_config.start()
|
||||
|
||||
self.mock_load_config = patch(
|
||||
'ansible.module_utils.network.common.network.Config.load_config')
|
||||
self.load_config = self.mock_load_config.start()
|
||||
|
||||
self.mock_get_resource_connection_config = patch(
|
||||
'ansible.module_utils.network.common.cfg.base.get_resource_connection'
|
||||
)
|
||||
self.get_resource_connection_config = self.mock_get_resource_connection_config.start(
|
||||
)
|
||||
|
||||
self.mock_get_resource_connection_facts = patch(
|
||||
'ansible.module_utils.network.common.facts.facts.get_resource_connection'
|
||||
)
|
||||
self.get_resource_connection_facts = self.mock_get_resource_connection_facts.start()
|
||||
|
||||
self.mock_edit_config = patch(
|
||||
'ansible.module_utils.network.nxos.config.acls.acls.Acls.edit_config'
|
||||
)
|
||||
self.edit_config = self.mock_edit_config.start()
|
||||
|
||||
self.mock_execute_show_command = patch(
|
||||
'ansible.module_utils.network.nxos.facts.acls.acls.AclsFacts.get_device_data'
|
||||
)
|
||||
self.execute_show_command = self.mock_execute_show_command.start()
|
||||
|
||||
def tearDown(self):
|
||||
super(TestNxosAclsModule, self).tearDown()
|
||||
self.mock_get_resource_connection_config.stop()
|
||||
self.mock_get_resource_connection_facts.stop()
|
||||
self.mock_edit_config.stop()
|
||||
self.mock_get_config.stop()
|
||||
self.mock_load_config.stop()
|
||||
self.mock_execute_show_command.stop()
|
||||
|
||||
def load_fixtures(self, commands=None, device=''):
|
||||
def load_from_file(*args, **kwargs):
|
||||
v4 = '''\nip access-list ACL1v4\n 10 permit ip any any\n 20 deny udp any any'''
|
||||
v6 = '''\nipv6 access-list ACL1v6\n 10 permit sctp any any'''
|
||||
return v4 + v6
|
||||
|
||||
self.execute_show_command.side_effect = load_from_file
|
||||
|
||||
def test_nxos_acls_merged(self):
|
||||
set_module_args(
|
||||
dict(config=[
|
||||
dict(afi="ipv4",
|
||||
acls=[
|
||||
dict(name="ACL2v4",
|
||||
aces=[
|
||||
dict(
|
||||
grant="deny",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
fragments=True,
|
||||
sequence=20,
|
||||
protocol="tcp",
|
||||
protocol_options=dict(
|
||||
tcp=dict(ack=True))
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
dict(afi="ipv6",
|
||||
acls=[
|
||||
dict(name="ACL2v6")
|
||||
])
|
||||
], state="merged"))
|
||||
commands = ['ip access-list ACL2v4',
|
||||
'20 deny tcp any any ack fragments',
|
||||
'ipv6 access-list ACL2v6']
|
||||
self.execute_module(changed=True, commands=commands)
|
||||
|
||||
def test_nxos_acls_merged_idempotent(self):
|
||||
set_module_args(
|
||||
dict(config=[
|
||||
dict(afi="ipv4",
|
||||
acls=[
|
||||
dict(name="ACL1v4",
|
||||
aces=[
|
||||
dict(
|
||||
grant="permit",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=10,
|
||||
protocol="ip"
|
||||
),
|
||||
dict(
|
||||
grant="deny",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=20,
|
||||
protocol="udp")
|
||||
]
|
||||
),
|
||||
]
|
||||
),
|
||||
dict(afi="ipv6",
|
||||
acls=[
|
||||
dict(name="ACL1v6",
|
||||
aces=[
|
||||
dict(
|
||||
grant="permit",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=10,
|
||||
protocol="sctp",
|
||||
)
|
||||
])
|
||||
])
|
||||
], state="merged"))
|
||||
self.execute_module(changed=False, commands=[])
|
||||
|
||||
def test_nxos_acls_replaced(self):
|
||||
set_module_args(
|
||||
dict(config=[
|
||||
dict(afi="ipv4",
|
||||
acls=[
|
||||
dict(name="ACL1v4",
|
||||
aces=[
|
||||
dict(
|
||||
grant="permit",
|
||||
destination=dict(host="192.0.2.28"),
|
||||
source=dict(any=True),
|
||||
log=True,
|
||||
sequence=50,
|
||||
protocol="icmp",
|
||||
protocol_options=dict(
|
||||
icmp=dict(administratively_prohibited=True))
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
], state="replaced"))
|
||||
commands = ['ip access-list ACL1v4', 'no 20 deny udp any any',
|
||||
'no 10 permit ip any any',
|
||||
'50 permit icmp any host 192.0.2.28 administratively-prohibited log']
|
||||
self.execute_module(changed=True, commands=commands)
|
||||
|
||||
def test_nxos_acls_replaced_idempotent(self):
|
||||
set_module_args(
|
||||
dict(config=[
|
||||
dict(afi="ipv4",
|
||||
acls=[
|
||||
dict(name="ACL1v4",
|
||||
aces=[
|
||||
dict(
|
||||
grant="permit",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=10,
|
||||
protocol="ip",
|
||||
),
|
||||
dict(
|
||||
grant="deny",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=20,
|
||||
protocol="udp")
|
||||
]
|
||||
),
|
||||
]
|
||||
),
|
||||
dict(afi="ipv6",
|
||||
acls=[
|
||||
dict(name="ACL1v6",
|
||||
aces=[
|
||||
dict(
|
||||
grant="permit",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=10,
|
||||
protocol="sctp",
|
||||
)
|
||||
])
|
||||
])
|
||||
], state="replaced"))
|
||||
self.execute_module(changed=False, commands=[])
|
||||
|
||||
def test_nxos_acls_overridden(self):
|
||||
set_module_args(
|
||||
dict(config=[
|
||||
dict(afi="ipv4",
|
||||
acls=[
|
||||
dict(name="ACL2v4",
|
||||
aces=[
|
||||
dict(
|
||||
grant="permit",
|
||||
destination=dict(host="192.0.2.28"),
|
||||
source=dict(any=True),
|
||||
log=True,
|
||||
sequence=50,
|
||||
protocol="icmp",
|
||||
protocol_options=dict(
|
||||
icmp=dict(administratively_prohibited=True))
|
||||
),
|
||||
dict(
|
||||
remark="Overridden ACL"
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
], state="overridden"))
|
||||
commands = ['no ip access-list ACL1v4', 'no ipv6 access-list ACL1v6', 'ip access-list ACL2v4',
|
||||
'50 permit icmp any host 192.0.2.28 administratively-prohibited log', 'remark Overridden ACL']
|
||||
self.execute_module(changed=True, commands=commands)
|
||||
|
||||
def test_nxos_acls_overridden_idempotent(self):
|
||||
set_module_args(
|
||||
dict(config=[
|
||||
dict(afi="ipv4",
|
||||
acls=[
|
||||
dict(name="ACL1v4",
|
||||
aces=[
|
||||
dict(
|
||||
grant="permit",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=10,
|
||||
protocol="ip",
|
||||
),
|
||||
dict(
|
||||
grant="deny",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=20,
|
||||
protocol="udp")
|
||||
]
|
||||
),
|
||||
]
|
||||
),
|
||||
dict(afi="ipv6",
|
||||
acls=[
|
||||
dict(name="ACL1v6",
|
||||
aces=[
|
||||
dict(
|
||||
grant="permit",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=10,
|
||||
protocol="sctp",
|
||||
)
|
||||
])
|
||||
])
|
||||
], state="overridden"))
|
||||
self.execute_module(changed=False, commands=[])
|
||||
|
||||
def test_nxos_acls_deletedafi(self):
|
||||
set_module_args(
|
||||
dict(config=[dict(afi="ipv4")], state="deleted"))
|
||||
commands = ['no ip access-list ACL1v4']
|
||||
self.execute_module(changed=True, commands=commands)
|
||||
|
||||
def test_nxos_acls_deletedace(self):
|
||||
set_module_args(
|
||||
dict(config=[dict(afi="ipv6",
|
||||
acls=[
|
||||
dict(name="ACL1v6",
|
||||
aces=[
|
||||
dict(
|
||||
grant="permit",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=10,
|
||||
protocol="sctp",
|
||||
)
|
||||
])
|
||||
])], state="deleted"))
|
||||
commands = ['ipv6 access-list ACL1v6', 'no 10 permit sctp any any']
|
||||
self.execute_module(changed=True, commands=commands)
|
||||
|
||||
def test_nxos_acls_deletedall(self):
|
||||
set_module_args(dict(config=[], state='deleted'))
|
||||
commands = ['no ipv6 access-list ACL1v6', 'no ip access-list ACL1v4']
|
||||
self.execute_module(changed=True, commands=commands)
|
||||
|
||||
def test_nxos_acls_rendered(self):
|
||||
set_module_args(
|
||||
dict(config=[
|
||||
dict(afi="ipv4",
|
||||
acls=[
|
||||
dict(name="ACL1v4",
|
||||
aces=[
|
||||
dict(
|
||||
grant="permit",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=10,
|
||||
protocol="ip",
|
||||
),
|
||||
dict(
|
||||
grant="deny",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=20,
|
||||
protocol="udp")
|
||||
]
|
||||
),
|
||||
]
|
||||
),
|
||||
dict(afi="ipv6",
|
||||
acls=[
|
||||
dict(name="ACL1v6",
|
||||
aces=[
|
||||
dict(
|
||||
grant="permit",
|
||||
destination=dict(any=True),
|
||||
source=dict(any=True),
|
||||
sequence=10,
|
||||
protocol="sctp",
|
||||
)
|
||||
])
|
||||
])
|
||||
], state="rendered"))
|
||||
commands = ['ip access-list ACL1v4', '10 permit ip any any', '20 deny udp any any',
|
||||
'ipv6 access-list ACL1v6', '10 permit sctp any any']
|
||||
result = self.execute_module(changed=False)
|
||||
self.assertEqual(sorted(result['rendered']), sorted(
|
||||
commands), result['rendered'])
|
||||
|
||||
def test_nxos_acls_parsed(self):
|
||||
set_module_args(dict(running_config='''\nip access-list ACL1v4\n 10 permit ip any any\n 20 deny udp any any dscp AF23 precedence critical''',
|
||||
state="parsed"))
|
||||
result = self.execute_module(changed=False)
|
||||
compare_list = [{'afi': 'ipv4', 'acls': [{'name': 'ACL1v4',
|
||||
'aces': [{'grant': 'permit', 'sequence': 10, 'protocol': 'ip', 'source': {'any': True},
|
||||
'destination': {'any': True}}, {'grant': 'deny', 'sequence': 20,
|
||||
'protocol': 'udp', 'source': {'any': True},
|
||||
'destination': {'any': True},
|
||||
'dscp': 'AF23', 'precedence': 'critical'}]}]}]
|
||||
self.assertEqual(result['parsed'], compare_list, result['parsed'])
|
||||
|
||||
def test_nxos_acls_gathered(self):
|
||||
set_module_args(dict(config=[], state="gathered"))
|
||||
result = self.execute_module(changed=False)
|
||||
compare_list = [{'acls': [{'aces': [{'destination': {'any': True}, 'sequence': 10, 'protocol': 'sctp', 'source': {'any': True}, 'grant': 'permit'}],
|
||||
'name': 'ACL1v6'}], 'afi': 'ipv6'}, {'acls': [{'aces': [{'destination': {'any': True}, 'sequence': 10, 'protocol': 'ip',
|
||||
'source': {'any': True}, 'grant': 'permit'},
|
||||
{'destination': {'any': True}, 'sequence': 20, 'protocol': 'udp',
|
||||
'source': {'any': True}, 'grant': 'deny'}], 'name': 'ACL1v4'}],
|
||||
'afi': 'ipv4'}]
|
||||
self.assertEqual(result['gathered'],
|
||||
compare_list, result['gathered'])
|
Loading…
Reference in New Issue