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