summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdharsh Srivats R <adharshsrivats@gmail.com>2020-03-02 12:19:28 +0100
committerGitHub <noreply@github.com>2020-03-02 12:19:28 +0100
commit7307339a7e6ae26baae0348ecf5ab6672ace364b (patch)
treec4bdab5ec878cecce0933e853ecc03b46c88c3f8
parentovirt: update botmeta deprecated (#67826) (diff)
downloadansible-7307339a7e6ae26baae0348ecf5ab6672ace364b.tar.xz
ansible-7307339a7e6ae26baae0348ecf5ab6672ace364b.zip
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 examples
-rw-r--r--lib/ansible/module_utils/network/nxos/argspec/acls/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/nxos/argspec/acls/acls.py425
-rw-r--r--lib/ansible/module_utils/network/nxos/config/acls/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/nxos/config/acls/acls.py690
-rw-r--r--lib/ansible/module_utils/network/nxos/facts/acls/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/nxos/facts/acls/acls.py236
-rw-r--r--lib/ansible/module_utils/network/nxos/facts/facts.py2
-rw-r--r--lib/ansible/modules/network/nxos/nxos_acls.py825
-rw-r--r--test/integration/targets/nxos_acls/defaults/main.yaml2
-rw-r--r--test/integration/targets/nxos_acls/meta/main.yml2
-rw-r--r--test/integration/targets/nxos_acls/tasks/cli.yaml20
-rw-r--r--test/integration/targets/nxos_acls/tasks/main.yaml2
-rw-r--r--test/integration/targets/nxos_acls/tests/cli/deleted.yml69
-rw-r--r--test/integration/targets/nxos_acls/tests/cli/gathered.yml34
-rw-r--r--test/integration/targets/nxos_acls/tests/cli/merged.yml108
-rw-r--r--test/integration/targets/nxos_acls/tests/cli/overridden.yml99
-rw-r--r--test/integration/targets/nxos_acls/tests/cli/parsed.yml45
-rw-r--r--test/integration/targets/nxos_acls/tests/cli/populate_config.yaml15
-rw-r--r--test/integration/targets/nxos_acls/tests/cli/remove_config.yaml9
-rw-r--r--test/integration/targets/nxos_acls/tests/cli/rendered.yml56
-rw-r--r--test/integration/targets/nxos_acls/tests/cli/replaced.yml65
-rw-r--r--test/integration/targets/nxos_acls/tests/cli/rtt.yml87
-rw-r--r--test/units/modules/network/nxos/test_nxos_acls.py370
23 files changed, 3161 insertions, 0 deletions
diff --git a/lib/ansible/module_utils/network/nxos/argspec/acls/__init__.py b/lib/ansible/module_utils/network/nxos/argspec/acls/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/nxos/argspec/acls/__init__.py
diff --git a/lib/ansible/module_utils/network/nxos/argspec/acls/acls.py b/lib/ansible/module_utils/network/nxos/argspec/acls/acls.py
new file mode 100644
index 0000000000..01e13f1893
--- /dev/null
+++ b/lib/ansible/module_utils/network/nxos/argspec/acls/acls.py
@@ -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
diff --git a/lib/ansible/module_utils/network/nxos/config/acls/__init__.py b/lib/ansible/module_utils/network/nxos/config/acls/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/nxos/config/acls/__init__.py
diff --git a/lib/ansible/module_utils/network/nxos/config/acls/acls.py b/lib/ansible/module_utils/network/nxos/config/acls/acls.py
new file mode 100644
index 0000000000..37c8b3d23a
--- /dev/null
+++ b/lib/ansible/module_utils/network/nxos/config/acls/acls.py
@@ -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
diff --git a/lib/ansible/module_utils/network/nxos/facts/acls/__init__.py b/lib/ansible/module_utils/network/nxos/facts/acls/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/nxos/facts/acls/__init__.py
diff --git a/lib/ansible/module_utils/network/nxos/facts/acls/acls.py b/lib/ansible/module_utils/network/nxos/facts/acls/acls.py
new file mode 100644
index 0000000000..e9b97b2456
--- /dev/null
+++ b/lib/ansible/module_utils/network/nxos/facts/acls/acls.py
@@ -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)
diff --git a/lib/ansible/module_utils/network/nxos/facts/facts.py b/lib/ansible/module_utils/network/nxos/facts/facts.py
index 9fab13c9ee..301652b385 100644
--- a/lib/ansible/module_utils/network/nxos/facts/facts.py
+++ b/lib/ansible/module_utils/network/nxos/facts/facts.py
@@ -24,6 +24,7 @@ from ansible.module_utils.network.nxos.facts.lacp_interfaces.lacp_interfaces imp
from ansible.module_utils.network.nxos.facts.lldp_global.lldp_global import Lldp_globalFacts
from ansible.module_utils.network.nxos.facts.lldp_interfaces.lldp_interfaces import Lldp_interfacesFacts
from ansible.module_utils.network.nxos.facts.acl_interfaces.acl_interfaces import Acl_interfacesFacts
+from ansible.module_utils.network.nxos.facts.acls.acls import AclsFacts
FACT_LEGACY_SUBSETS = dict(
@@ -48,6 +49,7 @@ FACT_RESOURCE_SUBSETS = dict(
l2_interfaces=L2_interfacesFacts,
lldp_interfaces=Lldp_interfacesFacts,
acl_interfaces=Acl_interfacesFacts,
+ acls=AclsFacts,
)
diff --git a/lib/ansible/modules/network/nxos/nxos_acls.py b/lib/ansible/modules/network/nxos/nxos_acls.py
new file mode 100644
index 0000000000..82d25b96a3
--- /dev/null
+++ b/lib/ansible/modules/network/nxos/nxos_acls.py
@@ -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()
diff --git a/test/integration/targets/nxos_acls/defaults/main.yaml b/test/integration/targets/nxos_acls/defaults/main.yaml
new file mode 100644
index 0000000000..5f709c5aac
--- /dev/null
+++ b/test/integration/targets/nxos_acls/defaults/main.yaml
@@ -0,0 +1,2 @@
+---
+testcase: "*"
diff --git a/test/integration/targets/nxos_acls/meta/main.yml b/test/integration/targets/nxos_acls/meta/main.yml
new file mode 100644
index 0000000000..ae741cbdc7
--- /dev/null
+++ b/test/integration/targets/nxos_acls/meta/main.yml
@@ -0,0 +1,2 @@
+dependencies:
+ - prepare_nxos_tests
diff --git a/test/integration/targets/nxos_acls/tasks/cli.yaml b/test/integration/targets/nxos_acls/tasks/cli.yaml
new file mode 100644
index 0000000000..f1c20c1b78
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tasks/cli.yaml
@@ -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
diff --git a/test/integration/targets/nxos_acls/tasks/main.yaml b/test/integration/targets/nxos_acls/tasks/main.yaml
new file mode 100644
index 0000000000..415c99d8b1
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tasks/main.yaml
@@ -0,0 +1,2 @@
+---
+- { include: cli.yaml, tags: ['cli'] }
diff --git a/test/integration/targets/nxos_acls/tests/cli/deleted.yml b/test/integration/targets/nxos_acls/tests/cli/deleted.yml
new file mode 100644
index 0000000000..d85b125034
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tests/cli/deleted.yml
@@ -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
diff --git a/test/integration/targets/nxos_acls/tests/cli/gathered.yml b/test/integration/targets/nxos_acls/tests/cli/gathered.yml
new file mode 100644
index 0000000000..c2ff83c9c5
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tests/cli/gathered.yml
@@ -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
diff --git a/test/integration/targets/nxos_acls/tests/cli/merged.yml b/test/integration/targets/nxos_acls/tests/cli/merged.yml
new file mode 100644
index 0000000000..b22b568adf
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tests/cli/merged.yml
@@ -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
diff --git a/test/integration/targets/nxos_acls/tests/cli/overridden.yml b/test/integration/targets/nxos_acls/tests/cli/overridden.yml
new file mode 100644
index 0000000000..27cca65d8b
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tests/cli/overridden.yml
@@ -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
diff --git a/test/integration/targets/nxos_acls/tests/cli/parsed.yml b/test/integration/targets/nxos_acls/tests/cli/parsed.yml
new file mode 100644
index 0000000000..8a2efd5270
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tests/cli/parsed.yml
@@ -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
diff --git a/test/integration/targets/nxos_acls/tests/cli/populate_config.yaml b/test/integration/targets/nxos_acls/tests/cli/populate_config.yaml
new file mode 100644
index 0000000000..81b72e11c0
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tests/cli/populate_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
diff --git a/test/integration/targets/nxos_acls/tests/cli/remove_config.yaml b/test/integration/targets/nxos_acls/tests/cli/remove_config.yaml
new file mode 100644
index 0000000000..a6f3320051
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tests/cli/remove_config.yaml
@@ -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
diff --git a/test/integration/targets/nxos_acls/tests/cli/rendered.yml b/test/integration/targets/nxos_acls/tests/cli/rendered.yml
new file mode 100644
index 0000000000..5419a7086b
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tests/cli/rendered.yml
@@ -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"
diff --git a/test/integration/targets/nxos_acls/tests/cli/replaced.yml b/test/integration/targets/nxos_acls/tests/cli/replaced.yml
new file mode 100644
index 0000000000..2451347491
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tests/cli/replaced.yml
@@ -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
diff --git a/test/integration/targets/nxos_acls/tests/cli/rtt.yml b/test/integration/targets/nxos_acls/tests/cli/rtt.yml
new file mode 100644
index 0000000000..d3878ec149
--- /dev/null
+++ b/test/integration/targets/nxos_acls/tests/cli/rtt.yml
@@ -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
diff --git a/test/units/modules/network/nxos/test_nxos_acls.py b/test/units/modules/network/nxos/test_nxos_acls.py
new file mode 100644
index 0000000000..32cfbdc168
--- /dev/null
+++ b/test/units/modules/network/nxos/test_nxos_acls.py
@@ -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'])