summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGomathiselviS <gomathiselvi@gmail.com>2020-02-24 12:27:11 -0500
committerGitHub <noreply@github.com>2020-02-24 12:27:11 -0500
commitd283126c31e8bc5be01af3ca93bba0517235ffeb (patch)
tree1812acc1549b0ae78eefacb3293eb83237b15be0
parent74e948e6e424cd1e58dab9563f8cbb4efc4f1d18 (diff)
downloadansible-d283126c31e8bc5be01af3ca93bba0517235ffeb.tar.gz
eos_acls : Add eos acls resource module (#66308)
* Adding files for RM static_routes * Added Integration tests * Added Unit testcases * Addressed review comments * corrected lint errors * corrected documentation errors * Lint errors * corrected test/sanity * corrected documentation for deprecation * corrected case sensitivity * Again Documentation eroor * Lint errors again * corrected deprecated module in ignoretxt * added new gethered,rendered,parsed state checks to unit test * New code broke the old flow-fixed * Lint errs * Added check for running_config * eos_acls resource module added * Corrected errors * corrected documentation errors * corrected typo * Testcases in progress * Integration tests in progress * Integration tests * Added Intergration tcs * Corrected pylint errors * Resolving issues due to rebase * Corrected Typo * more pylint errors * more pylint errors * more pylint errors * Documentation * Documentation * More lint errors * Fixed Indentation * Indentation issues - not getting fixed * Indentation issues - not getting fixed * Added rtt testcase * Corrected whitespaces * addressed review comments * moved integration tests to common - to support eapi tests * modification for merge update * indentation errors * added line key * Fixing shippable errors * fixing doc errors * fixing doc errors * fixing doc errors * fixing doc errors * fixing indentation * modified replaced operation * rebase issue fixed * Corrected typo * review comments and flake8 error fixed
-rw-r--r--lib/ansible/module_utils/network/eos/argspec/acls/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/eos/argspec/acls/acls.py468
-rw-r--r--lib/ansible/module_utils/network/eos/config/acls/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/eos/config/acls/acls.py483
-rw-r--r--lib/ansible/module_utils/network/eos/facts/acls/__init__.py0
-rw-r--r--lib/ansible/module_utils/network/eos/facts/acls/acls.py300
-rw-r--r--lib/ansible/module_utils/network/eos/facts/facts.py2
-rw-r--r--lib/ansible/modules/network/eos/eos_acls.py925
-rw-r--r--lib/ansible/modules/network/eos/eos_facts.py2
-rw-r--r--test/integration/targets/eos_acls/defaults/main.yaml3
-rw-r--r--test/integration/targets/eos_acls/meta/main.yaml2
-rw-r--r--test/integration/targets/eos_acls/tasks/cli.yaml18
-rw-r--r--test/integration/targets/eos_acls/tasks/eapi.yaml16
-rw-r--r--test/integration/targets/eos_acls/tasks/main.yaml3
-rw-r--r--test/integration/targets/eos_acls/tests/common/_parsed.cfg4
-rw-r--r--test/integration/targets/eos_acls/tests/common/_parsed_cfg.yaml11
-rw-r--r--test/integration/targets/eos_acls/tests/common/_populate.yaml49
-rw-r--r--test/integration/targets/eos_acls/tests/common/_remove_config.yaml8
-rw-r--r--test/integration/targets/eos_acls/tests/common/deleted.yaml168
-rw-r--r--test/integration/targets/eos_acls/tests/common/gathered.yaml37
-rw-r--r--test/integration/targets/eos_acls/tests/common/merged.yaml152
-rw-r--r--test/integration/targets/eos_acls/tests/common/overridden.yaml71
-rw-r--r--test/integration/targets/eos_acls/tests/common/parsed.yaml29
-rw-r--r--test/integration/targets/eos_acls/tests/common/rendered.yaml80
-rw-r--r--test/integration/targets/eos_acls/tests/common/replaced.yaml94
-rw-r--r--test/integration/targets/eos_acls/tests/common/rtt.yaml101
-rw-r--r--test/integration/targets/eos_acls/vars/main.yaml110
-rw-r--r--test/units/modules/network/eos/eos_module.py3
-rw-r--r--test/units/modules/network/eos/fixtures/eos_acls_config.cfg3
-rw-r--r--test/units/modules/network/eos/test_eos_acls.py278
30 files changed, 3417 insertions, 3 deletions
diff --git a/lib/ansible/module_utils/network/eos/argspec/acls/__init__.py b/lib/ansible/module_utils/network/eos/argspec/acls/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/argspec/acls/__init__.py
diff --git a/lib/ansible/module_utils/network/eos/argspec/acls/acls.py b/lib/ansible/module_utils/network/eos/argspec/acls/acls.py
new file mode 100644
index 0000000000..f998982404
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/argspec/acls/acls.py
@@ -0,0 +1,468 @@
+#
+# -*- 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 eos_acls module
+"""
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+class AclsArgs(object): # pylint: disable=R0903
+ """The arg spec for the eos_acls module
+ """
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ 'config': {
+ 'elements': 'dict',
+ 'options': {
+ 'acls': {
+ 'elements': 'dict',
+ 'options': {
+ 'aces': {
+ 'elements': 'dict',
+ 'options': {
+ 'destination': {
+ 'mutually_exclusive':
+ [['address', 'subnet_address', 'any', 'host'],
+ ['wildcard_bits', 'subnet_address', 'any', 'host']],
+ 'options': {
+ 'address': {
+ 'type': 'str'
+ },
+ 'any': {
+ 'type': 'bool'
+ },
+ 'host': {
+ 'type': 'str'
+ },
+ 'port_protocol': {
+ 'type': 'dict'
+ },
+ 'subnet_address': {
+ 'type': 'str'
+ },
+ 'wildcard_bits': {
+ 'type': 'str'
+ }
+ },
+ 'required_together':
+ [['address', 'wildcard_bits']],
+ 'type':
+ 'dict'
+ },
+ 'fragment_rules': {
+ 'type': 'bool'
+ },
+ 'fragments': {
+ 'type': 'bool'
+ },
+ 'grant': {
+ 'choices': ['permit', 'deny'],
+ 'type': 'str'
+ },
+ 'line': {
+ 'type': 'str',
+ 'aliases': ['ace']
+ },
+ 'hop_limit': {
+ 'type': 'dict'
+ },
+ 'log': {
+ 'type': 'bool'
+ },
+ 'protocol': {
+ 'type': 'str'
+ },
+ 'protocol_options': {
+ '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_num': {
+ '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'
+ },
+ 'icmpv6': {
+ 'options': {
+ 'address_unreachable': {
+ 'type': 'bool'
+ },
+ 'beyond_scope': {
+ 'type': 'bool'
+ },
+ 'echo_reply': {
+ 'type': 'bool'
+ },
+ 'echo_request': {
+ 'type': 'bool'
+ },
+ 'erroneous_header': {
+ 'type': 'bool'
+ },
+ 'fragment_reassembly_exceeded':
+ {
+ 'type': 'bool'
+ },
+ 'hop_limit_exceeded': {
+ 'type': 'bool'
+ },
+ 'neighbor_advertisement': {
+ 'type': 'bool'
+ },
+ 'neighbor_solicitation': {
+ 'type': 'bool'
+ },
+ 'no_admin': {
+ 'type': 'bool'
+ },
+ 'no_route': {
+ 'type': 'bool'
+ },
+ 'packet_too_big': {
+ 'type': 'bool'
+ },
+ 'parameter_problem': {
+ 'type': 'bool'
+ },
+ 'port_unreachable': {
+ 'type': 'bool'
+ },
+ 'redirect_message': {
+ 'type': 'bool'
+ },
+ 'reject_route': {
+ 'type': 'bool'
+ },
+ 'router_advertisement': {
+ 'type': 'bool'
+ },
+ 'router_solicitation': {
+ 'type': 'bool'
+ },
+ 'source_address_failed': {
+ 'type': 'bool'
+ },
+ 'source_routing_error': {
+ 'type': 'bool'
+ },
+ 'time_exceeded': {
+ 'type': 'bool'
+ },
+ 'unreachable': {
+ 'type': 'bool'
+ },
+ 'unrecognized_ipv6_option': {
+ 'type': 'bool'
+ },
+ 'unrecognized_next_header': {
+ 'type': 'bool'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'ip': {
+ 'options': {
+ 'nexthop_group': {
+ 'type': 'str'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'ipv6': {
+ 'options': {
+ 'nexthop_group': {
+ 'type': 'str'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'tcp': {
+ 'options': {
+ 'flags': {
+ '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'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'remark': {
+ 'type': 'str'
+ },
+ 'sequence': {
+ 'type': 'int'
+ },
+ 'source': {
+ 'mutually_exclusive':
+ [['address', 'subnet_address', 'any', 'host'],
+ ['wildcard_bits', 'subnet_address', 'any', 'host']],
+ 'options': {
+ 'address': {
+ 'type': 'str'
+ },
+ 'any': {
+ 'type': 'bool'
+ },
+ 'host': {
+ 'type': 'str'
+ },
+ 'port_protocol': {
+ 'type': 'dict'
+ },
+ 'subnet_address': {
+ 'type': 'str'
+ },
+ 'wildcard_bits': {
+ 'type': 'str'
+ }
+ },
+ 'required_together': [['address', 'wildcard_bits']],
+ 'type': 'dict'
+ },
+ 'tracked': {
+ 'type': 'bool'
+ },
+ 'ttl': {
+ 'options': {
+ 'eq': {
+ 'type': 'int'
+ },
+ 'gt': {
+ 'type': 'int'
+ },
+ 'lt': {
+ 'type': 'int'
+ },
+ 'neq': {
+ 'type': 'int'
+ }
+ },
+ 'type': 'dict'
+ },
+ 'vlan': {
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'name': {
+ 'required': True,
+ 'type': 'str'
+ },
+ 'standard': {
+ 'type': 'bool'
+ }
+ },
+ 'type': 'list'
+ },
+ 'afi': {
+ 'choices': ['ipv4', 'ipv6'],
+ 'required': True,
+ 'type': 'str'
+ }
+ },
+ 'type': 'list'
+ },
+ 'running_config': {
+ 'type': 'str'
+ },
+ 'state': {
+ 'choices': [
+ 'deleted', 'merged', 'overridden', 'replaced', 'gathered',
+ 'rendered', 'parsed'
+ ],
+ 'default':
+ 'merged',
+ 'type':
+ 'str'
+ }
+ } # pylint: disable=C0301
diff --git a/lib/ansible/module_utils/network/eos/config/acls/__init__.py b/lib/ansible/module_utils/network/eos/config/acls/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/config/acls/__init__.py
diff --git a/lib/ansible/module_utils/network/eos/config/acls/acls.py b/lib/ansible/module_utils/network/eos/config/acls/acls.py
new file mode 100644
index 0000000000..90fee80b04
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/config/acls/acls.py
@@ -0,0 +1,483 @@
+#
+# -*- 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 eos_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
+import itertools
+
+from ansible.module_utils.network.common.cfg.base import ConfigBase
+from ansible.module_utils.network.common.utils import to_list
+from ansible.module_utils.network.common.utils import remove_empties, dict_diff
+from ansible.module_utils.network.eos.facts.facts import Facts
+
+
+class Acls(ConfigBase):
+ """
+ The eos_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 execute_module(self):
+ """ Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {'changed': False}
+ warnings = list()
+ commands = list()
+ changed = False
+
+ if self.state in self.ACTION_STATES:
+ existing_acls_facts = self.get_acls_facts()
+ else:
+ existing_acls_facts = []
+ if self.state in self.ACTION_STATES or self.state == 'rendered':
+ commands.extend(self.set_config(existing_acls_facts))
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ self._connection.edit_config(commands)
+ changed = True
+ if changed:
+ result['changed'] = True
+ if self.state in self.ACTION_STATES:
+ result['commands'] = commands
+ if self.state in self.ACTION_STATES or self.state == 'gathered':
+ changed_acls_facts = self.get_acls_facts()
+ elif self.state == 'rendered':
+ commands = list(itertools.chain(*commands))
+ result['rendered'] = commands
+ elif self.state == 'parsed':
+ if not self._module.params['running_config']:
+ self._module.fail_json(msg="Value of running_config parameter must not be empty for state parsed")
+ result['parsed'] = self.get_acls_facts(data=self._module.params['running_config'])
+ else:
+ changed_acls_facts = []
+ if self.state in self.ACTION_STATES:
+ result['before'] = existing_acls_facts
+ if result['changed']:
+ result['after'] = changed_acls_facts
+ elif self.state == 'gathered':
+ result['gathered'] = 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.get('config')
+ want = []
+ onbox_configs = []
+ for h in existing_acls_facts:
+ have_configs = add_commands(remove_empties(h))
+ onbox_configs.append(have_configs)
+ if config:
+ for w in config:
+ want.append(remove_empties(w))
+ have = existing_acls_facts
+ resp = self.set_state(want, have)
+ if self.state == 'merged':
+ to_config = self.compare_configs(onbox_configs, to_list(resp))
+ else:
+ to_config = resp
+ return to_config
+
+ def compare_configs(self, have, want):
+ commands = []
+ want = list(itertools.chain(*want))
+ have = list(itertools.chain(*have))
+ h_index = 0
+ config = list(want)
+ for w in want:
+ access_list = re.findall(r'(ip.*) access-list (.*)', w)
+ if access_list:
+ if w in have:
+ h_index = have.index(w)
+ else:
+ for num, h in enumerate(have, start=h_index + 1):
+ if "access-list" not in h:
+ seq_num = re.search(r'(\d+) (.*)', w)
+ if seq_num:
+ have_seq_num = re.search(r'(\d+) (.*)', h)
+ if seq_num.group(1) == have_seq_num.group(1) and have_seq_num.group(2) != seq_num.group(2):
+ negate_cmd = "no " + seq_num.group(1)
+ config.insert(config.index(w), negate_cmd)
+ if w in h:
+ config.pop(config.index(w))
+ break
+ for c in config:
+ access_list = re.findall(r'(ip.*) access-list (.*)', c)
+ if access_list:
+ acl_index = config.index(c)
+ else:
+ if config[acl_index] not in commands:
+ commands.append(config[acl_index])
+ commands.append(c)
+ return commands
+
+ 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
+ """
+ commands = []
+ if self.state in ('merged', 'replaced', 'overridden', 'rendered') and not want:
+ self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(self.state))
+ state = self._module.params['state']
+ if state == 'overridden':
+ commands = self._state_overridden(want, have)
+ elif state == 'deleted':
+ commands = self._state_deleted(want, have)
+ elif state == 'merged' or self.state == 'rendered':
+ commands = self._state_merged(want, have)
+ elif state == 'replaced':
+ commands = self._state_replaced(want, have)
+ return commands
+
+ @staticmethod
+ def _state_replaced(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_commands = []
+ remove_cmds = []
+ diff = {}
+ present = False
+ diff_present = False
+ for w in want:
+ afi = "ipv6" if w["afi"] == "ipv6" else "ipv4"
+ for acl in w["acls"]:
+ name = acl["name"]
+ want_ace = acl["aces"]
+ for h in have:
+ if h["afi"] == afi:
+ for h_acl in h["acls"]:
+ if h_acl["name"] == name:
+ present = True
+ h = {"afi": afi, "acls": [{"name": name}]}
+ for h_ace in h_acl['aces']:
+ diff = get_ace_diff(h_ace, want_ace)
+ if diff:
+ diff_present = True
+ h = {"afi": afi, "acls": [{"name": name, "aces": [h_ace]}]}
+ remove_cmds.append(del_commands(h, have))
+ if diff_present or not present:
+ config_cmds = set_commands(want, have)
+ config_cmds = list(itertools.chain(*config_cmds))
+ for cmd in have:
+ have_configs = add_commands(cmd)
+ have_commands.append(have_configs)
+ have_commands = list(itertools.chain(*have_commands))
+ if remove_cmds:
+ remove_cmds = list(itertools.chain(*remove_cmds))
+ commands.append(remove_cmds)
+ commands.append(config_cmds)
+ commands = list(itertools.chain(*commands))
+ commandset = []
+ [commandset.append(cmd) for cmd in commands if cmd not in commandset]
+ return commandset
+
+ @staticmethod
+ def _state_overridden(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 = []
+ ace_diff = {}
+ h_afi_list = []
+ w_afi_list = []
+ diff = False
+ for h in have:
+ h_afi_list.append(h["afi"])
+ for w in want:
+ w_afi_list.append(w["afi"])
+ for hafi in h_afi_list:
+ if hafi not in w_afi_list:
+ h = {"afi": hafi}
+ remove_cmds = del_commands(h, have)
+ commands.append(remove_cmds)
+ for w in want:
+ w_names = []
+ for h in have:
+ h_names = []
+ if w["afi"] == h["afi"]:
+ for w_acl in w["acls"]:
+ w_names.append(w_acl["name"])
+ for h_acl in h["acls"]:
+ h_names.append(h_acl["name"])
+ if h_acl["name"] == w_acl["name"]:
+ for w_ace in w_acl['aces']:
+ ace_diff = get_ace_diff(w_ace, h_acl["aces"])
+ if ace_diff:
+ diff = True
+ h = {"afi": h["afi"], "acls": [{"name": h_acl["name"], "aces": h_acl["aces"]}]}
+ remove_cmds = del_commands(h, have)
+ commands.append(remove_cmds)
+ for hname in h_names:
+ if hname not in w_names:
+ h = {"afi": h["afi"], "acls": [{"name": hname}]}
+ remove_cmds = del_commands(h, have)
+ if remove_cmds not in commands:
+ commands.append(remove_cmds)
+
+ if diff:
+ config_cmds = set_commands(want, have)
+ config_cmds = list(itertools.chain(*config_cmds))
+ commands.append(config_cmds)
+ if commands:
+ commands = list(itertools.chain(*commands))
+ return commands
+
+ @staticmethod
+ def _state_merged(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 set_commands(want, have)
+
+ @staticmethod
+ def _state_deleted(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 not want:
+ for h in have:
+ return_command = add_commands(h)
+ for command in return_command:
+ command = "no " + command
+ commands.append(command)
+ else:
+ for w in want:
+ return_command = del_commands(w, have)
+ commands.append(return_command)
+ commands = list(itertools.chain(*commands))
+ return commands
+
+
+def set_commands(want, have):
+ commands = []
+ for w in want:
+ wace_updated = []
+ for h in have:
+ if w['afi'] == h['afi']:
+ for wacl in w["acls"]:
+ for hacl in h["acls"]:
+ if wacl['name'] == hacl['name']:
+ want_aces = wacl['aces']
+ for wace in wacl['aces']:
+ for hace in hacl['aces']:
+ if 'sequence' in wace.keys() and 'sequence' in hace.keys():
+ if wace['sequence'] == hace['sequence']:
+ wace_updated = get_updated_ace(wace, hace)
+ if wace_updated:
+ want_aces.pop(want_aces.index(wace))
+ want_aces.append(wace_updated)
+ return_command = add_commands(w)
+ commands.append(return_command)
+ return commands
+
+
+def get_updated_ace(w, h):
+ # gives the ace to be updated in case of merge update.
+ w_updated = w.copy()
+ for hkey in h.keys():
+ if hkey not in w.keys():
+ w_updated.update({hkey: h[hkey]})
+ else:
+ w_updated.update({hkey: w[hkey]})
+ return w_updated
+
+
+def add_commands(want):
+ commandset = []
+ protocol_name = {"51": "ahp", "47": "gre", "1": "icmp", "2": "igmp",
+ "4": "ip", "89": "ospf", "103": "pim", "6": "tcp",
+ "17": "udp", "112": "vrrp"}
+ if not want:
+ return commandset
+ command = ""
+ afi = "ip" if want["afi"] == "ipv4" else "ipv6"
+ for acl in want["acls"]:
+ if "standard" in acl.keys() and acl["standard"]:
+ command = afi + " access-list standard " + acl["name"]
+ else:
+ command = afi + " access-list " + acl["name"]
+ commandset.append(command)
+ if "aces" not in acl.keys():
+ continue
+ for ace in acl["aces"]:
+ command = ""
+ if "sequence" in ace.keys():
+ command = str(ace["sequence"])
+ if "remark" in ace.keys():
+ command = command + " remark " + ace["remark"]
+ if "fragment_rules" in ace.keys() and ace["fragment_rules"]:
+ command = command + " fragment-rules"
+ if "grant" in ace.keys():
+ command = command + " " + ace["grant"]
+ if "vlan" in ace.keys():
+ command = command + " vlan " + ace["vlan"]
+ if "protocol" in ace.keys():
+ protocol = ace["protocol"]
+ if protocol.isdigit():
+ if protocol in protocol_name.keys():
+ protocol = protocol_name[protocol]
+ command = command + " " + protocol
+ if "source" in ace.keys():
+ if "any" in ace["source"].keys():
+ command = command + " any"
+ elif "subnet_address" in ace["source"].keys():
+ command = command + " " + ace["source"]["subnet_address"]
+ elif "host" in ace["source"].keys():
+ command = command + " host " + ace["source"]["host"]
+ elif "address" in ace["source"].keys():
+ command = command + " " + ace["source"]["address"] + " " + ace["source"]["wildcard_bits"]
+ if "port_protocol" in ace["source"].keys():
+ for op, val in ace["source"]["port_protocol"].items():
+ if val.isdigit():
+ val = socket.getservbyport(int(val))
+ command = command + " " + op + " " + val
+ if "destination" in ace.keys():
+ if "any" in ace["destination"].keys():
+ command = command + " any"
+ elif "subnet_address" in ace["destination"].keys():
+ command = command + " " + ace["destination"]["subnet_address"]
+ elif "host" in ace["destination"].keys():
+ command = command + " host " + ace["destination"]["host"]
+ elif "address" in ace["destination"].keys():
+ command = command + " " + ace["destination"]["address"] + " " + ace["destination"]["wildcard_bits"]
+ if "port_protocol" in ace["destination"].keys():
+ for op in ace["destination"]["port_protocol"].keys():
+ command = command + " " + op + " " + ace["destination"]["port_protocol"][op]
+ if "protocol_options" in ace.keys():
+ for proto in ace["protocol_options"].keys():
+ if proto == "icmp" or proto == "icmpv6":
+ for icmp_msg in ace["protocol_options"][proto].keys():
+ command = command + " " + icmp_msg
+ elif proto == "ip" or proto == "ipv6":
+ command = command + " nexthop-group " + ace["protocol_options"][proto]["nexthop_group"]
+ elif proto == "tcp":
+ for flag, val in ace["prtocol_options"][proto]["flags"].items():
+ command = command + " " + val
+ if "hop_limit" in ace.keys():
+ for op, val in ace["hop_limit"].items():
+ command = command + " hop-limit " + op + " " + val
+ if "tracked" in ace.keys() and ace["tracked"]:
+ command = command + " tracked"
+ if "ttl" in ace.keys():
+ for op, val in ace["ttl"].items():
+ command = command + " ttl " + op + " " + str(val)
+ if "fragments" in ace.keys():
+ command = command + " fragments"
+ if "log" in ace.keys():
+ command = command + " log"
+ commandset.append(command.strip())
+ return commandset
+
+
+def del_commands(want, have):
+ commandset = []
+ command = ""
+ have_command = []
+ for h in have:
+ have_configs = add_commands(h)
+ have_command.append(have_configs)
+ have_command = list(itertools.chain(*have_command))
+ afi = "ip" if want["afi"] == "ipv4" else "ipv6"
+ if "acls" not in want.keys():
+ for have_cmd in have_command:
+ access_list = re.search(r'(ip.*)\s+access-list .*', have_cmd)
+ if access_list and access_list.group(1) == afi:
+ commandset.append("no " + have_cmd)
+ return commandset
+
+ for acl in want["acls"]:
+ ace_present = True
+ if "standard" in acl.keys() and acl["standard"]:
+ command = afi + " access-list standard " + acl["name"]
+ else:
+ command = afi + " access-list " + acl["name"]
+ if "aces" not in acl.keys():
+ ace_present = False
+ commandset.append("no " + command)
+ if ace_present:
+ return_command = add_commands(want)
+ for cmd in return_command:
+ if "access-list" in cmd:
+ commandset.append(cmd)
+ continue
+ seq = re.search(r'(\d+) (permit|deny|fragment-rules|remark) .*', cmd)
+ if seq:
+ commandset.append("no " + seq.group(1))
+ else:
+ commandset.append("no " + cmd)
+ return commandset
+
+
+def get_ace_diff(want_ace, have_ace):
+ # gives the diff of the aces passed.
+ for h_a in have_ace:
+ d = dict_diff(want_ace, h_a)
+ if not d:
+ break
+ return d
diff --git a/lib/ansible/module_utils/network/eos/facts/acls/__init__.py b/lib/ansible/module_utils/network/eos/facts/acls/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/facts/acls/__init__.py
diff --git a/lib/ansible/module_utils/network/eos/facts/acls/acls.py b/lib/ansible/module_utils/network/eos/facts/acls/acls.py
new file mode 100644
index 0000000000..edd7752c13
--- /dev/null
+++ b/lib/ansible/module_utils/network/eos/facts/acls/acls.py
@@ -0,0 +1,300 @@
+#
+# -*- 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 eos 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.eos.argspec.acls.acls import AclsArgs
+
+
+class AclsFacts(object):
+ """ The eos 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 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)
+
+ # split the config into instances of the resource
+ find_pattern = r'(?:^|\n)(?:ip|ipv6) access\-list.*?(?=(?:^|\n)(?:ip|ipv6) access\-list|$)'
+ resources = [p for p in re.findall(find_pattern,
+ data,
+ re.DOTALL)]
+
+ objs = []
+ ipv4list = []
+ ipv6list = []
+ for resource in resources:
+ if "ipv6" in resource:
+ ipv6list.append(resource)
+ else:
+ ipv4list.append(resource)
+ ipv4list = ["\n".join(ipv4list)]
+ ipv6list = ["\n".join(ipv6list)]
+ for resource in ipv4list:
+ if resource:
+ obj = self.render_config(self.generated_spec, resource)
+ if obj:
+ objs.append(obj)
+ for resource in ipv6list:
+ 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:
+ facts['acls'] = []
+ params = utils.validate_config(self.argument_spec, {'config': objs})
+ for cfg in params['config']:
+ facts['acls'].append(utils.remove_empties(cfg))
+
+ ansible_facts['ansible_network_resources'].update(facts)
+ return ansible_facts
+
+ 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)
+ afi_list = []
+ acls_list = []
+ name_dict = {}
+ standard = 0
+ operator = ['eq', 'lt', 'neq', 'range', 'gt']
+ flags = ['ack', 'established', 'fin', 'psh', 'rst', 'syn', 'urg']
+ others = ['hop_limit', 'log', 'ttl', 'fragments', 'tracked']
+ for dev_config in conf.split('\n'):
+ ace_dict = {}
+ if not dev_config:
+ continue
+ if dev_config == '!':
+ continue
+ dev_config = dev_config.strip()
+ matches = re.findall(r'(ip.*?) access-list (.*)', dev_config)
+ if matches:
+ afi = "ipv4" if matches[0][0] == "ip" else "ipv6"
+ ace_list = []
+ if bool(name_dict):
+ acls_list.append(name_dict.copy())
+ name_dict = {}
+ if afi not in afi_list:
+ afi_list.append(afi)
+ config.update({"afi": afi})
+ if "standard" in matches[0][1]:
+ standard = 1
+ name = matches[0][1].split()
+ name_dict.update({"name": name[1]})
+ name_dict.update({"standard": True})
+ else:
+ name_dict.update({"name": matches[0][1]})
+ else:
+ source_dict = {}
+ dest_dict = {}
+ dev_config = re.sub('-', '_', dev_config)
+ dev_config_remainder = dev_config.split()
+ if "fragment_rules" in dev_config:
+ ace_dict.update({"sequence": dev_config_remainder.pop(0)})
+ ace_dict.update({"fragment_rules": True})
+ if "remark" in dev_config:
+ ace_dict.update({"sequence": dev_config_remainder.pop(0)})
+ ace_dict.update({"remark": ' '.join(dev_config_remainder[1:])})
+ seq = re.search(r'\d+ (permit|deny) .*', dev_config)
+ if seq:
+ ace_dict.update({"sequence": dev_config_remainder.pop(0)})
+ ace_dict.update({"grant": dev_config_remainder.pop(0)})
+ if dev_config_remainder[0] == "vlan":
+ vlan_str = ""
+ dev_config_remainder.pop(0)
+ if dev_config_remainder[0] == "inner":
+ vlan_str = dev_config_remainder.pop(0) + " "
+ vlan_str = dev_config_remainder.pop(0) + " " + dev_config_remainder.pop(0)
+ ace_dict.update({"vlan": vlan_str})
+ if not standard:
+ protocol = dev_config_remainder[0]
+ ace_dict.update({"protocol": dev_config_remainder.pop(0)})
+ src_prefix = re.search(r'/', dev_config_remainder[0])
+ src_address = re.search(r'[a-z\d:\.]+', dev_config_remainder[0])
+ if dev_config_remainder[0] == "host":
+ source_dict.update({"host": dev_config_remainder.pop(1)})
+ dev_config_remainder.pop(0)
+ elif dev_config_remainder[0] == "any":
+ source_dict.update({"any": True})
+ dev_config_remainder.pop(0)
+ elif src_prefix:
+ source_dict.update({"subnet_address": dev_config_remainder.pop(0)})
+ elif src_address:
+ source_dict.update({"address": dev_config_remainder.pop(0)})
+ source_dict.update({"wildcard_bits": dev_config_remainder.pop(0)})
+ if dev_config_remainder:
+ if dev_config_remainder[0] in operator:
+ port_dict = {}
+ src_port = ""
+ src_opr = dev_config_remainder.pop(0)
+ portlist = dev_config_remainder[:]
+ for config_remainder in portlist:
+ addr = re.search(r'[\.\:]', config_remainder)
+ if config_remainder == "any" or config_remainder == "host" or addr:
+ break
+ else:
+ src_port = src_port + " " + config_remainder
+ dev_config_remainder.pop(0)
+ src_port = src_port.strip()
+ port_dict.update({src_opr: src_port})
+ source_dict.update({"port_protocol": port_dict})
+ ace_dict.update({"source": source_dict})
+ if not dev_config_remainder or standard:
+ if dev_config_remainder and "log" in dev_config_remainder:
+ ace_dict.update({"log": True})
+ if bool(ace_dict):
+ ace_list.append(ace_dict.copy())
+ if len(ace_list):
+ name_dict = name_dict.copy()
+ name_dict.update({"aces": ace_list[:]})
+ # acls_list.append(name_dict)
+ continue
+ dest_prefix = re.search(r'/', dev_config_remainder[0])
+ dest_address = re.search(r'[a-z\d:\.]+', dev_config_remainder[0])
+ if dev_config_remainder[0] == "host":
+ dest_dict.update({"host": dev_config_remainder.pop(1)})
+ dev_config_remainder.pop(0)
+ elif dev_config_remainder[0] == "any":
+ dest_dict.update({"any": True})
+ dev_config_remainder.pop(0)
+ elif dest_prefix:
+ dest_dict.update({"subnet_address": dev_config_remainder.pop(0)})
+ elif dest_address:
+ dest_dict.update({"address": dev_config_remainder.pop(0)})
+ dest_dict.update({"wildcard_bits": dev_config_remainder.pop(0)})
+ if dev_config_remainder:
+ if dev_config_remainder[0] in operator:
+ port_dict = {}
+ dest_port = ""
+ dest_opr = dev_config_remainder.pop(0)
+ portlist = dev_config_remainder[:]
+ for config_remainder in portlist:
+ if config_remainder in operator or config_remainder in others:
+ break
+ else:
+ dest_port = dest_port + " " + config_remainder
+ dev_config_remainder.pop(0)
+ dest_port = dest_port.strip()
+ port_dict.update({dest_opr: dest_port})
+ dest_dict.update({"port_protocol": port_dict})
+ ace_dict.update({"destination": dest_dict})
+ protocol_option_dict = {}
+ tcp_dict = {}
+ icmp_dict = {}
+ ip_dict = {}
+ if not dev_config_remainder:
+ if bool(ace_dict):
+ ace_list.append(ace_dict.copy())
+ if len(ace_list):
+ name_dict = name_dict.copy()
+ name_dict.update({"aces": ace_list[:]})
+ # acls_list.append(name_dict)
+ continue
+ if protocol == "tcp" or "6":
+ protocol = "tcp"
+ flags_dict = {}
+ if dev_config_remainder[0] in flags:
+ flaglist = dev_config_remainder.copy()
+ for config_remainder in flaglist:
+ if config_remainder not in flags:
+ break
+ else:
+ flags_dict.update({config_remainder: True})
+ dev_config_remainder.pop(0)
+ if bool(flags_dict):
+ tcp_dict.update({"flags": flags_dict})
+ if bool(tcp_dict):
+ protocol_option_dict.update({"tcp": tcp_dict})
+ if protocol == "icmp" or protocol == "icmpv6" \
+ or protocol == "1" or protocol == "58":
+ if protocol == "1":
+ protocol = "icmp"
+ elif protocol == "58":
+ protocol = "icmpv6"
+ if dev_config_remainder[0] not in others:
+ icmp_dict.update({dev_config_remainder[0]: True})
+ dev_config_remainder.pop(0)
+ if bool(icmp_dict):
+ protocol_option_dict.update({protocol: icmp_dict})
+ if protocol == "ip" or "ipv6":
+ if dev_config_remainder[0] == "nexthop_group":
+ dev_config_remainder.pop(0)
+ ip_dict.update({"nexthop_group": dev_config_remainder.pop(0)})
+ if bool(ip_dict):
+ protocol_option_dict.update({protocol: ip_dict})
+ if bool(protocol_option_dict):
+ ace_dict.update({"protocol_options": protocol_option_dict})
+ if dev_config_remainder[0] == "ttl":
+ dev_config_remainder.pop(0)
+ op = dev_config_remainder.pop(0)
+ ttl_dict = {op: dev_config_remainder.pop(0)}
+ ace_dict.update({"ttl": ttl_dict})
+ for config_remainder in dev_config_remainder:
+ if config_remainder in others:
+ if config_remainder == "hop_limit":
+ hop_index = dev_config_remainder.index(config_remainder)
+ hoplimit_dict = {dev_config_remainder[hop_index + 1]: dev_config_remainder[hop_index + 2]}
+ ace_dict.update({"hop_limit": hoplimit_dict})
+ dev_config_remainder.pop(0)
+ continue
+ ace_dict.update({config_remainder: True})
+ dev_config_remainder.pop(0)
+ if dev_config_remainder:
+ config.update({"line": dev_config})
+ return utils.remove_empties(config)
+ if bool(ace_dict):
+ ace_list.append(ace_dict.copy())
+ if len(ace_list):
+ name_dict = name_dict.copy()
+ name_dict.update({"aces": ace_list[:]})
+ acls_list.append(name_dict.copy())
+ config.update({"acls": acls_list})
+ return utils.remove_empties(config)
diff --git a/lib/ansible/module_utils/network/eos/facts/facts.py b/lib/ansible/module_utils/network/eos/facts/facts.py
index a40da912ed..c82997a132 100644
--- a/lib/ansible/module_utils/network/eos/facts/facts.py
+++ b/lib/ansible/module_utils/network/eos/facts/facts.py
@@ -22,6 +22,7 @@ from ansible.module_utils.network.eos.facts.lldp_interfaces.lldp_interfaces impo
from ansible.module_utils.network.eos.facts.vlans.vlans import VlansFacts
from ansible.module_utils.network.eos.facts.legacy.base import Default, Hardware, Config, Interfaces
from ansible.module_utils.network.eos.facts.acl_interfaces.acl_interfaces import Acl_interfacesFacts
+from ansible.module_utils.network.eos.facts.acls.acls import AclsFacts
FACT_LEGACY_SUBSETS = dict(
@@ -41,6 +42,7 @@ FACT_RESOURCE_SUBSETS = dict(
lldp_interfaces=Lldp_interfacesFacts,
vlans=VlansFacts,
acl_interfaces=Acl_interfacesFacts,
+ acls=AclsFacts,
)
diff --git a/lib/ansible/modules/network/eos/eos_acls.py b/lib/ansible/modules/network/eos/eos_acls.py
new file mode 100644
index 0000000000..0dffd2c6df
--- /dev/null
+++ b/lib/ansible/modules/network/eos/eos_acls.py
@@ -0,0 +1,925 @@
+#!/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 eos_acls
+"""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'network'}
+
+DOCUMENTATION = """
+---
+module: eos_acls
+version_added: '2.10'
+short_description: 'Manages IP access-list attributes of Arista EOS interfaces'
+description: This module manages the IP access-list attributes of Arista EOS interfaces.
+author: Gomathiselvi S (@GomathiselviS)
+notes:
+- Tested against Arista vEOS v4.20.10M
+options:
+ config:
+ description: A dictionary of IP access-list options
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - The Address Family Indicator (AFI) for the Access Control Lists (ACL).
+ type: str
+ required: true
+ choices: ['ipv4', 'ipv6']
+ acls:
+ description:
+ - A list of Access Control Lists (ACL).
+ type: list
+ elements: dict
+ suboptions:
+ standard:
+ description: standard access-list or not
+ type: bool
+ default: False
+ name:
+ description: Name of the acl-list
+ type: str
+ required: true
+ aces:
+ description: Filtering data
+ type: list
+ elements: dict
+ suboptions:
+ sequence:
+ description: sequence number for the ordered list of rules
+ type: int
+ remark:
+ description: Specify a comment
+ type: str
+ fragment_rules:
+ description: Add fragment rules
+ type: bool
+ grant:
+ description: Action to be applied on the rule
+ type: str
+ choices: ['permit', 'deny']
+ line:
+ description: For fact gathering, any ACE that is not fully parsed, while show up as a value of this attribute.
+ type: str
+ aliases: ['ace']
+ protocol:
+ description:
+ - Specify the protocol to match.
+ - Refer to vendor documentation for valid values.
+ type: str
+ vlan:
+ description: Vlan options
+ type: str
+ protocol_options:
+ description: All the possible sub options for the protocol chosen.
+ type: dict
+ suboptions:
+ tcp:
+ description: Options for tcp protocol.
+ type: dict
+ suboptions:
+ flags:
+ description: Match TCP packet 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
+ icmp:
+ description:
+ - Internet Control Message Protocol settings.
+ 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 exceededs
+ 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
+ message_num:
+ description: icmp msg type number.
+ type: int
+ icmpv6:
+ description: Options for icmpv6.
+ type: dict
+ suboptions:
+ address_unreachable:
+ description: address unreachable
+ type: bool
+ beyond_scope:
+ description: beyond_scope
+ type: bool
+ echo_reply:
+ description: echo_reply
+ type: bool
+ echo_request:
+ description: echo reques
+ type: bool
+ erroneous_header:
+ description: erroneous header
+ type: bool
+ fragment_reassembly_exceeded:
+ description: fragment_reassembly_exceeded
+ type: bool
+ hop_limit_exceeded:
+ description: hop limit exceeded
+ type: bool
+ neighbor_advertisement:
+ description: neighbor advertisement
+ type: bool
+ neighbor_solicitation:
+ description: neighbor_solicitation
+ type: bool
+ no_admin:
+ description: no admin
+ type: bool
+ no_route:
+ description: no route
+ type: bool
+ packet_too_big:
+ description: packet too big
+ type: bool
+ parameter_problem:
+ description: parameter problem
+ type: bool
+ port_unreachable:
+ description: port unreachable
+ type: bool
+ redirect_message:
+ description: redirect message
+ type: bool
+ reject_route:
+ description: reject route
+ type: bool
+ router_advertisement:
+ description: router_advertisement
+ type: bool
+ router_solicitation:
+ description: router_solicitation
+ type: bool
+ source_address_failed:
+ description: source_address_failed
+ type: bool
+ source_routing_error:
+ description: source_routing_error
+ type: bool
+ time_exceeded:
+ description: time_exceeded
+ type: bool
+ unreachable:
+ description: unreachable
+ type: bool
+ unrecognized_ipv6_option:
+ description: unrecognized_ipv6_option
+ type: bool
+ unrecognized_next_header:
+ description: unrecognized_next_header
+ type: bool
+ ip:
+ description : Internet Protocol.
+ type: dict
+ suboptions:
+ nexthop_group:
+ description: Nexthop-group name.
+ type: str
+ ipv6:
+ description : Internet V6 Protocol.
+ type: dict
+ suboptions:
+ nexthop_group:
+ description: Nexthop-group name.
+ type: str
+ source:
+ description: The packet's source address
+ type: dict
+ suboptions:
+ address:
+ description: dotted decimal notation of IP address
+ type: str
+ wildcard_bits:
+ description: Source wildcard bits
+ type: str
+ subnet_address:
+ description: A subnet address
+ type: str
+ host:
+ description: Host IP address
+ type: str
+ any:
+ description: Rule matches all source addresses
+ type: bool
+ port_protocol:
+ description: Specify source port/protocoli, along with operator. (comes with tcp/udp).
+ type: dict
+ destination:
+ description: The packet's destination address
+ type: dict
+ suboptions:
+ address:
+ description: dotted decimal notation of IP address
+ type: str
+ wildcard_bits:
+ description: Source wildcard bits
+ type: str
+ subnet_address:
+ description: A subnet address
+ type: str
+ host:
+ description: Host IP address
+ type: str
+ any:
+ description: Rule matches all source addresses
+ type: bool
+ port_protocol:
+ description: Specify dest port/protocol, along with operator . (comes with tcp/udp).
+ type: dict
+ ttl:
+ description: Compares the TTL (time-to-live) value in the packet to a specified value
+ type: dict
+ suboptions:
+ eq:
+ description: Match a single TTL value
+ type: int
+ lt:
+ description: Match TTL lesser than this number
+ type: int
+ gt:
+ description: Match TTL greater than this number
+ type: int
+ neq:
+ description: Match TTL not equal to this value
+ type: int
+ fragments:
+ description: Match non-head fragment packets
+ type: bool
+ log:
+ description: Log matches against this rule
+ type: bool
+ tracked:
+ description: Match packets in existing ICMP/UDP/TCP connections
+ type: bool
+ hop_limit:
+ description: Hop limit value.
+ type: dict
+ running_config:
+ description:
+ - The module, by default, will connect to the remote device and
+ retrieve the current running-config to use as a base for comparing
+ against the contents of source. There are times when it is not
+ desirable to have the task get the current running-config for
+ every task in a playbook. The I(running_config) argument allows the
+ implementer to pass in the configuration to use as the base
+ config for comparison. This value of this option should be the
+ output received from device by executing command
+ version_added: "2.10"
+ type: str
+
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ ['deleted', 'merged', 'overridden', 'replaced', 'gathered', 'rendered', 'parsed']
+ default:
+ merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 40 permit ip any any
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+- name: Merge provided configuration with device configuration
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "ospf"
+ source:
+ subnet_address: 20.0.0.0/8
+ destnation:
+ any: true
+ state: merged
+
+# After state:
+# ------------
+#
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 35 deny ospf 20.0.0.0/8 any
+# 40 permit ip any any
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+# Using merged
+
+# Before state:
+# -------------
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 40 permit ip any any
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+- name: Merge to update the given configuration with an existing ace
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ log : true
+ ttl:
+ eq: 33
+ state: merged
+
+# After state:
+# ------------
+#
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 35 deny ospf 20.0.0.0/8 any ttl eq 33 log
+# 40 permit ip any any
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+# Using replaced
+
+# Before state:
+# -------------
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 40 permit ip any any
+# !
+# ip access-list test3
+# 10 permit ip 35.33.0.0/16 any log
+# !
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+
+
+- name: Replace device configuration with provided configuration
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "permit"
+ protocol: "ospf"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ state: replaced
+
+# After state:
+# ------------
+#
+# show running-config | section access-list
+# ip access-list test1
+# 35 permit ospf 20.0.0.0/8 any
+# !
+# ip access-list test3
+# 10 permit ip 35.33.0.0/16 any log
+# !
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+
+# Using overridden
+
+# Before state:
+# -------------
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 40 permit ip any any
+# !
+# ip access-list test3
+# 10 permit ip 35.33.0.0/16 any log
+# !
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+
+
+- name: override device configuration with provided configuration
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ action: "permit"
+ protocol: "ospf"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ state: overridden
+
+# After state:
+# ------------
+#
+# show running-config | section access-list
+# ip access-list test1
+# 35 permit ospf 20.0.0.0/8 any
+# !
+
+
+# Using deleted
+
+# Before state:
+# -------------
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 40 permit ip any any
+# !
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+
+- name: Delete provided configuration
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 30
+ state: deleted
+
+# After state:
+# ------------
+#
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 40 permit ip any any
+# !
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+
+
+# Before state:
+# -------------
+# show running-config | section access-list
+# ip access-list test1
+# 10 permit ip 10.10.10.0/24 any ttl eq 200
+# 20 permit ip 10.30.10.0/24 host 10.20.10.1
+# 30 deny tcp host 10.10.20.1 eq finger www any syn log
+# 40 permit ip any any
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+# !
+
+- name: Delete provided configuration
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ state: deleted
+
+# After state:
+# ------------
+#
+# show running-config | section access-list
+
+# ipv6 access-list test2
+# 10 deny icmpv6 any any reject-route hop-limit eq 20
+
+
+# using gathered
+
+# ip access-list test1
+# 35 deny ospf 20.0.0.0/8 any
+# ip access-list test2
+# 40 permit vlan 55 0xE2 icmpv6 any any log
+
+- name: Gather the exisitng condiguration
+ eos_acls:
+ state: gathered
+
+# returns:
+
+
+# eos_acls:
+# config:
+# - afi: "ipv4"
+# acls:
+# - name: test1
+# aces:
+# - sequence: 35
+# grant: "deny"
+# protocol: "ospf"
+# source:
+# subnet_address: 20.0.0.0/8
+# destination:
+# any: true
+# - afi: "ipv6"
+# acls:
+# - name: test2
+# aces:
+# - sequence: 40
+# grant: "permit"
+# vlan: "55 0xE2"
+# protocol: "icmpv6"
+# log: true
+# source:
+# any: true
+# destination:
+# any: true
+
+
+# using rendered
+
+- name: Delete provided configuration
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "ospf"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ - afi: "ipv6"
+ acls:
+ - name: test2
+ aces:
+ - sequence: 40
+ grant: "permit"
+ vlan: "55 0xE2"
+ protocol: "icmpv6"
+ log: true
+ source:
+ any: true
+ destination:
+ any: true
+ state: rendered
+
+# returns:
+
+# ip access-list test1
+# 35 deny ospf 20.0.0.0/8 any
+# ip access-list test2
+# 40 permit vlan 55 0xE2 icmpv6 any any log
+
+
+# Using Parsed
+
+# parsed_acls.cfg
+
+# ipv6 access-list standard test2
+# 10 permit any log
+# !
+# ip access-list test1
+# 35 deny ospf 20.0.0.0/8 any
+# 45 remark Run by ansible
+# 55 permit tcp any any
+# !
+
+- name: parse configs
+ eos_acls:
+ running_config: "{{ lookup('file', './parsed_acls.cfg') }}"
+ state: parsed
+
+# returns
+# "parsed": [
+# {
+# "acls": [
+# {
+# "aces": [
+# {
+# "destination": {
+# "any": true
+# },
+# "grant": "deny",
+# "protocol": "ospf",
+# "sequence": 35,
+# "source": {
+# "subnet_address": "20.0.0.0/8"
+# }
+# },
+# {
+# "remark": "Run by ansible",
+# "sequence": 45
+# },
+# {
+# "destination": {
+# "any": true
+# },
+# "grant": "permit",
+# "protocol": "tcp",
+# "sequence": 55,
+# "source": {
+# "any": true
+# }
+# }
+# ],
+# "name": "test1"
+# }
+# ],
+# "afi": "ipv4"
+# },
+# {
+# "acls": [
+# {
+# "aces": [
+# {
+# "grant": "permit",
+# "log": true,
+# "sequence": 10,
+# "source": {
+# "any": true
+# }
+# }
+# ],
+# "name": "test2",
+# "standard": true
+# }
+# ],
+# "afi": "ipv6"
+# }
+# ]
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ 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: list
+ 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:
+ - ipv6 access-list standard test2
+ - 10 permit any log
+ - ip access-list test1
+ - 35 deny ospf 20.0.0.0/8 any
+ - 45 remark Run by ansible
+ - 55 permit tcp any any
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.network.eos.argspec.acls.acls import AclsArgs
+from ansible.module_utils.network.eos.config.acls.acls import Acls
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+
+ required_if = [('state', 'merged', ('config',)),
+ ('state', 'replaced', ('config',)),
+ ('state', 'overridden', ('config',)),
+ ('state', 'rendered', ('config',)),
+ ('state', 'parsed', ('running_config',))]
+ mutually_exclusive = [('config', 'running_config')]
+
+ module = AnsibleModule(argument_spec=AclsArgs.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ mutually_exclusive=mutually_exclusive)
+
+ result = Acls(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/lib/ansible/modules/network/eos/eos_facts.py b/lib/ansible/modules/network/eos/eos_facts.py
index 0515131908..a410c5640d 100644
--- a/lib/ansible/modules/network/eos/eos_facts.py
+++ b/lib/ansible/modules/network/eos/eos_facts.py
@@ -50,7 +50,7 @@ options:
not be collected.
Valid subsets are 'all', 'interfaces', 'l2_interfaces', 'l3_interfaces',
'lacp', 'lacp_interfaces', 'lag_interfaces', 'lldp_global', 'lldp_interfaces',
- 'vlans'.
+ 'vlans', 'acls'.
required: false
type: list
version_added: "2.9"
diff --git a/test/integration/targets/eos_acls/defaults/main.yaml b/test/integration/targets/eos_acls/defaults/main.yaml
new file mode 100644
index 0000000000..164afead28
--- /dev/null
+++ b/test/integration/targets/eos_acls/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "[^_].*"
+test_items: []
diff --git a/test/integration/targets/eos_acls/meta/main.yaml b/test/integration/targets/eos_acls/meta/main.yaml
new file mode 100644
index 0000000000..e5c8cd02f0
--- /dev/null
+++ b/test/integration/targets/eos_acls/meta/main.yaml
@@ -0,0 +1,2 @@
+dependencies:
+ - prepare_eos_tests
diff --git a/test/integration/targets/eos_acls/tasks/cli.yaml b/test/integration/targets/eos_acls/tasks/cli.yaml
new file mode 100644
index 0000000000..66941b1f49
--- /dev/null
+++ b/test/integration/targets/eos_acls/tasks/cli.yaml
@@ -0,0 +1,18 @@
+---
+- name: collect all cli test cases
+ find:
+ paths: "{{ role_path }}/tests/common"
+ patterns: "{{ testcase }}.yaml"
+ use_regex: true
+ register: test_cases
+ delegate_to: localhost
+
+- 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"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
+ tags: connection_network_cli
diff --git a/test/integration/targets/eos_acls/tasks/eapi.yaml b/test/integration/targets/eos_acls/tasks/eapi.yaml
new file mode 100644
index 0000000000..cb5f04d80c
--- /dev/null
+++ b/test/integration/targets/eos_acls/tasks/eapi.yaml
@@ -0,0 +1,16 @@
+---
+- name: collect all eapi test cases
+ find:
+ paths: "{{ role_path }}/tests/common"
+ patterns: "{{ testcase }}.yaml"
+ delegate_to: localhost
+ register: test_cases
+
+- name: set test_items
+ set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
+
+- name: run test cases (connection=httpapi)
+ include: "{{ test_case_to_run }} ansible_connection=httpapi"
+ with_items: "{{ test_items }}"
+ loop_control:
+ loop_var: test_case_to_run
diff --git a/test/integration/targets/eos_acls/tasks/main.yaml b/test/integration/targets/eos_acls/tasks/main.yaml
new file mode 100644
index 0000000000..970e74171e
--- /dev/null
+++ b/test/integration/targets/eos_acls/tasks/main.yaml
@@ -0,0 +1,3 @@
+---
+- { include: cli.yaml, tags: ['cli'] }
+- { include: eapi.yaml, tags: ['eapi'] }
diff --git a/test/integration/targets/eos_acls/tests/common/_parsed.cfg b/test/integration/targets/eos_acls/tests/common/_parsed.cfg
new file mode 100644
index 0000000000..11758ce538
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/_parsed.cfg
@@ -0,0 +1,4 @@
+ip access-list test1
+35 deny tcp 20.0.0.0/8 any log
+45 remark Run by ansible
+55 permit tcp any any
diff --git a/test/integration/targets/eos_acls/tests/common/_parsed_cfg.yaml b/test/integration/targets/eos_acls/tests/common/_parsed_cfg.yaml
new file mode 100644
index 0000000000..5a655ce534
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/_parsed_cfg.yaml
@@ -0,0 +1,11 @@
+---
+- name: Setup
+ cli_config:
+ config: "{{ lines }}"
+ become: yes
+ vars:
+ lines: |
+ ip access-list test1
+ 35 deny tcp 20.0.0.0/8 any log
+ 45 remark Run by ansible
+ 55 permit tcp any any
diff --git a/test/integration/targets/eos_acls/tests/common/_populate.yaml b/test/integration/targets/eos_acls/tests/common/_populate.yaml
new file mode 100644
index 0000000000..07ed5b9673
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/_populate.yaml
@@ -0,0 +1,49 @@
+---
+- name: Setup
+ eos_acls: &merged
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "tcp"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ log: true
+ - remark: "Run by ansible"
+ - grant: "permit"
+ protocol: "6"
+ source:
+ any: true
+ destination:
+ any: true
+ - name: test4
+ aces:
+ - grant: "permit"
+ source:
+ any: true
+ port_protocol:
+ eq: "25"
+ destination:
+ any: true
+ port_protocol:
+ eq: "www"
+ protocol: "tcp"
+ ttl:
+ eq: "55"
+ - afi: "ipv6"
+ acls:
+ - name: test2
+ standard: true
+ aces:
+ - grant: "permit"
+ log: "true"
+ source:
+ any: true
+ state: merged
+ become: yes
+ register: result
diff --git a/test/integration/targets/eos_acls/tests/common/_remove_config.yaml b/test/integration/targets/eos_acls/tests/common/_remove_config.yaml
new file mode 100644
index 0000000000..a8a351d80b
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/_remove_config.yaml
@@ -0,0 +1,8 @@
+---
+- name: Setup
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ - afi: "ipv6"
+ state: deleted
+ become: yes
diff --git a/test/integration/targets/eos_acls/tests/common/deleted.yaml b/test/integration/targets/eos_acls/tests/common/deleted.yaml
new file mode 100644
index 0000000000..750e214d49
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/deleted.yaml
@@ -0,0 +1,168 @@
+---
+- debug:
+ msg: "Start eos_acls deleted integration tests ansible_connection={{ ansible_connection }}"
+
+- include_tasks: _populate.yaml
+
+- set_fact:
+ config1:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 55
+ grant: "permit"
+ protocol: "tcp"
+ source:
+ any: true
+ destination:
+ any: true
+ - remark: "Run by ansible"
+ sequence: 45
+ - name: test4
+ aces:
+ - grant: "permit"
+ sequence: 10
+ source:
+ any: true
+ port_protocol:
+ eq: "smtp"
+ destination:
+ any: true
+ port_protocol:
+ eq: "www"
+ protocol: "tcp"
+ ttl:
+ eq: "55"
+ - afi: "ipv6"
+ acls:
+ - name: test2
+ standard: true
+ aces:
+ - grant: "permit"
+ sequence: 10
+ log: "true"
+ source:
+ any: true
+
+- set_fact:
+ config2:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "tcp"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ log: true
+ - remark: "Run by ansible"
+ sequence: 45
+ - name: test4
+ aces:
+ - grant: "permit"
+ sequence: 10
+ source:
+ any: true
+ port_protocol:
+ eq: "smtp"
+ destination:
+ any: true
+ port_protocol:
+ eq: "www"
+ protocol: "tcp"
+ ttl:
+ eq: "55"
+- set_fact:
+ config3:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "tcp"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ log: true
+ - remark: "Run by ansible"
+ sequence: 45
+
+- block:
+ - name: Delete attributes of given acls.
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "tcp"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ log: true
+ state: deleted
+ become: yes
+ register: result
+
+ - eos_facts:
+ gather_network_resources: acls
+ become: yes
+
+ - assert:
+ that:
+ - "result.commands|length == 2"
+ - "result.changed == true"
+ - "ansible_facts.network_resources.acls|symmetric_difference(result.after) == [] "
+ become: yes
+
+ - name: Delete afi of given acls.
+ eos_acls:
+ config:
+ - afi: "ipv6"
+ state: deleted
+ become: yes
+ register: result
+
+ - eos_facts:
+ gather_network_resources: acls
+ become: yes
+
+ - assert:
+ that:
+ - "result.commands|length == 1"
+ - "result.changed == true"
+ - "ansible_facts.network_resources.acls|symmetric_difference(result.after) == [] "
+ become: yes
+
+ - name: Delete attributes of given named acl.
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test4
+ state: deleted
+ become: yes
+ register: result
+
+ - eos_facts:
+ gather_network_resources: acls
+ become: yes
+
+ - assert:
+ that:
+ - "result.commands|length == 1"
+ - "result.changed == true"
+ - "ansible_facts.network_resources.acls|symmetric_difference(result.after) == [] "
+ become: yes
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/test/integration/targets/eos_acls/tests/common/gathered.yaml b/test/integration/targets/eos_acls/tests/common/gathered.yaml
new file mode 100644
index 0000000000..0baedb13af
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/gathered.yaml
@@ -0,0 +1,37 @@
+---
+- debug:
+ msg: "START eos_acls gathered integration tests on connection={{ ansible_connection }}"
+
+
+- include_tasks: _populate.yaml
+
+- block:
+ - name: Gathered the provided configuration with the exisiting running configuration
+ eos_acls: &gathered
+ config:
+ state: gathered
+ become: yes
+ register: result
+
+ - eos_facts:
+ gather_network_resources: acls
+ become: yes
+
+ - name: Assert
+ assert:
+ that:
+ - "ansible_facts.network_resources.acls | symmetric_difference(result.gathered) == []"
+
+
+ - name: Gather the existing running configuration (IDEMPOTENT)
+ eos_acls: *gathered
+ become: yes
+ register: result
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - "result['changed'] == false"
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/test/integration/targets/eos_acls/tests/common/merged.yaml b/test/integration/targets/eos_acls/tests/common/merged.yaml
new file mode 100644
index 0000000000..9bfd233805
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/merged.yaml
@@ -0,0 +1,152 @@
+---
+- debug:
+ msg: "Start eos_acls merged integration tests ansible_connection={{ ansible_connection }}"
+
+
+- set_fact:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "tcp"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ log: true
+ - remark: "Run by ansible"
+ sequence: 45
+ - grant: "permit"
+ sequence: 55
+ protocol: "tcp"
+ source:
+ any: true
+ destination:
+ any: true
+ - name: test4
+ aces:
+ - grant: "permit"
+ sequence: 10
+ source:
+ any: true
+ port_protocol:
+ eq: "smtp"
+ destination:
+ any: true
+ port_protocol:
+ eq: "www"
+ protocol: "tcp"
+ ttl:
+ eq: "55"
+ - afi: "ipv6"
+ acls:
+ - name: test2
+ standard: true
+ aces:
+ - grant: "permit"
+ sequence: 10
+ log: "true"
+ source:
+ any: true
+
+- block:
+ - name: merge attributes of given acls.
+ eos_acls: &merged
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "tcp"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ log: true
+ - remark: "Run by ansible"
+ - grant: "permit"
+ protocol: "6"
+ source:
+ any: true
+ destination:
+ any: true
+ - name: test4
+ aces:
+ - grant: "permit"
+ source:
+ any: true
+ port_protocol:
+ eq: "25"
+ destination:
+ any: true
+ port_protocol:
+ eq: "www"
+ protocol: "tcp"
+ ttl:
+ eq: "55"
+ - afi: "ipv6"
+ acls:
+ - name: test2
+ standard: true
+ aces:
+ - grant: "permit"
+ log: "true"
+ source:
+ any: true
+ state: merged
+ become: yes
+ register: result
+
+ - eos_facts:
+ gather_network_resources: acls
+ become: yes
+
+ - assert:
+ that:
+ - "result.commands|length == 8"
+ - "result.changed == true"
+ become: yes
+
+ - name: Idempotency check
+ eos_acls: *merged
+ become: yes
+ register: result
+
+ - assert:
+ that:
+ - "result.changed == false"
+ - "result.commands|length == 0"
+ - "ansible_facts.network_resources.acls|symmetric_difference(result.before) == []"
+
+ - name: merge attributes with an existing ace
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ log: true
+ ttl:
+ eq: 33
+ source:
+ any: true
+ state: merged
+ become: yes
+ register: result
+
+ - assert:
+ that:
+ - "result.changed == true"
+ - "result.commands|length == 3"
+ - "'no 35' in result.commands"
+ - "'35 deny tcp any any ttl eq 33 log' in result.commands"
+
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/test/integration/targets/eos_acls/tests/common/overridden.yaml b/test/integration/targets/eos_acls/tests/common/overridden.yaml
new file mode 100644
index 0000000000..4c8e51998e
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/overridden.yaml
@@ -0,0 +1,71 @@
+---
+- debug:
+ msg: "Start eos_acls merged integration tests ansible_connection={{ ansible_connection }}"
+
+- include_tasks: _populate.yaml
+
+- set_fact:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 10
+ grant: "permit"
+ protocol: "ospf"
+ source:
+ any: true
+ destination:
+ any: true
+ log: true
+
+- block:
+ - name: overriden attributes with given acls.
+ eos_acls: &overridden
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - grant: "permit"
+ sequence: 10
+ protocol: "ospf"
+ source:
+ any: true
+ destination:
+ any: true
+ log: true
+ state: overridden
+ become: yes
+ register: result
+
+ - eos_facts:
+ gather_network_resources: acls
+ become: yes
+
+ - assert:
+ that:
+ - "result.commands|length == 8"
+ - "result.changed == true"
+ - "'ip access-list test1' in result.commands"
+ - "'10 permit ospf any any log' in result.commands"
+ - "ansible_facts.network_resources.acls|symmetric_difference(result.after) == []"
+ become: yes
+
+ - name: Idempotency check
+ eos_acls: *overridden
+ become: yes
+ register: result
+
+ - eos_facts:
+ gather_network_resources: acls
+ become: yes
+
+ - assert:
+ that:
+ - "result.changed == false"
+ - "result.commands|length == 0"
+ - "ansible_facts.network_resources.acls|symmetric_difference(result.before) == []"
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/test/integration/targets/eos_acls/tests/common/parsed.yaml b/test/integration/targets/eos_acls/tests/common/parsed.yaml
new file mode 100644
index 0000000000..ffadce84ed
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/parsed.yaml
@@ -0,0 +1,29 @@
+---
+- debug:
+ msg: "START eos_acls parsed integration tests on connection={{ ansible_connection }}"
+
+- include_tasks: _parsed_cfg.yaml
+
+- name: Gather acls facts
+ eos_facts:
+ gather_subset:
+ - default
+ gather_network_resources:
+ - acls
+ become: yes
+ register: acls_facts
+
+- name: Provide the running configuration for parsing (config to be parsed)
+ eos_acls: &parsed
+ running_config:
+ "{{ lookup('file', '_parsed.cfg') }}"
+ state: parsed
+ become: yes
+ register: result
+
+- assert:
+ that:
+ - "result.changed == false"
+ - "ansible_facts.network_resources.acls|symmetric_difference(result.parsed) == []"
+
+- include_tasks: _remove_config.yaml
diff --git a/test/integration/targets/eos_acls/tests/common/rendered.yaml b/test/integration/targets/eos_acls/tests/common/rendered.yaml
new file mode 100644
index 0000000000..f447c52ac0
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/rendered.yaml
@@ -0,0 +1,80 @@
+---
+- debug:
+ msg: "START eos_acls rendered integration tests on connection={{ ansible_connection }}"
+
+
+- block:
+ - name: Structure provided configuration into device specific commands
+ eos_acls: &rendered
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "tcp"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ log: true
+ - remark: "Run by ansible"
+ - grant: "permit"
+ protocol: "6"
+ source:
+ any: true
+ destination:
+ any: true
+ - name: test4
+ aces:
+ - grant: "permit"
+ source:
+ any: true
+ port_protocol:
+ eq: "25"
+ destination:
+ any: true
+ port_protocol:
+ eq: "www"
+ protocol: "tcp"
+ ttl:
+ eq: "55"
+ - afi: "ipv6"
+ acls:
+ - name: test2
+ standard: true
+ aces:
+ - grant: "permit"
+ log: "true"
+ source:
+ any: true
+ state: rendered
+ become: yes
+ register: result
+
+
+ - name: Assert that correct set of commands were generated
+ vars:
+ lines:
+ - ip access-list test1
+ - 35 deny tcp 20.0.0.0/8 any log
+ - remark Run by ansible
+ - permit tcp any any
+ - ip access-list test4
+ - permit tcp any eq smtp any eq www ttl eq 55
+ - ipv6 access-list standard test2
+ - permit any log
+
+ assert:
+ that:
+ - "{{ lines | symmetric_difference(result['rendered']) |length == 0 }}"
+
+ - name: Structure provided configuration into device specific commands (IDEMPOTENT)
+ eos_acls: *rendered
+ register: result
+
+ - name: Assert that the previous task was idempotent
+ assert:
+ that:
+ - "result['changed'] == false"
diff --git a/test/integration/targets/eos_acls/tests/common/replaced.yaml b/test/integration/targets/eos_acls/tests/common/replaced.yaml
new file mode 100644
index 0000000000..068e177ef5
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/replaced.yaml
@@ -0,0 +1,94 @@
+---
+- debug:
+ msg: "Start eos_acls replaced integration tests ansible_connection={{ ansible_connection }}"
+
+- include_tasks: _populate.yaml
+
+- set_fact:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 10
+ grant: "permit"
+ protocol: "ospf"
+ source:
+ any: true
+ destination:
+ any: true
+ log: true
+ - name: test4
+ aces:
+ - grant: "permit"
+ sequence: 10
+ source:
+ any: true
+ port_protocol:
+ eq: "smtp"
+ destination:
+ any: true
+ port_protocol:
+ eq: "www"
+ protocol: "tcp"
+ ttl:
+ eq: "55"
+ - afi: "ipv6"
+ acls:
+ - name: test2
+ standard: true
+ aces:
+ - grant: "permit"
+ sequence: 10
+ log: "true"
+ source:
+ any: true
+
+- block:
+ - name: replace attributes with given acls.
+ eos_acls: &replaced
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - grant: "permit"
+ sequence: 10
+ protocol: "ospf"
+ source:
+ any: true
+ destination:
+ any: true
+ log: true
+ state: replaced
+ become: yes
+ register: result
+
+ - eos_facts:
+ gather_network_resources: acls
+ become: yes
+
+ - assert:
+ that:
+ - "result.commands|length == 5"
+ - "result.changed == true"
+ - "ansible_facts.network_resources.acls|symmetric_difference(result.after) == []"
+ become: yes
+
+ - name: Idempotency check
+ eos_acls: *replaced
+ become: yes
+ register: result
+
+ - eos_facts:
+ gather_network_resources: acls
+ become: yes
+
+ - assert:
+ that:
+ - "result.changed == false"
+ - "result.commands|length == 0"
+ - "ansible_facts.network_resources.acls|symmetric_difference(result.before) == []"
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/test/integration/targets/eos_acls/tests/common/rtt.yaml b/test/integration/targets/eos_acls/tests/common/rtt.yaml
new file mode 100644
index 0000000000..4b78a8e782
--- /dev/null
+++ b/test/integration/targets/eos_acls/tests/common/rtt.yaml
@@ -0,0 +1,101 @@
+---
+- debug:
+ msg: "Start eos_acls round trip integration tests ansible_connection={{ ansible_connection }}"
+
+
+- block:
+ - name: merge attributes of given acls(apply base config).
+ eos_acls: &merged
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "tcp"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ log: true
+ - remark: "Run by ansible"
+ - grant: "permit"
+ protocol: "6"
+ source:
+ any: true
+ destination:
+ any: true
+ - name: test4
+ aces:
+ - grant: "permit"
+ source:
+ any: true
+ port_protocol:
+ eq: "25"
+ destination:
+ any: true
+ port_protocol:
+ eq: "www"
+ protocol: "tcp"
+ ttl:
+ eq: "55"
+ - afi: "ipv6"
+ acls:
+ - name: test2
+ standard: true
+ aces:
+ - grant: "permit"
+ log: "true"
+ source:
+ any: true
+ state: merged
+ become: yes
+ register: base_config
+
+ - eos_facts:
+ gather_network_resources: acls
+ become: yes
+
+ - assert:
+ that:
+ - "base_config.commands|length == 8"
+ - "base_config.changed == true"
+ - "ansible_facts.network_resources.acls|symmetric_difference(base_config.after) == []"
+
+ - name: Apply the provided configuration (config to be reverted)
+ eos_acls:
+ config:
+ - afi: "ipv4"
+ acls:
+ - name: test3
+ aces:
+ - sequence: 100
+ grant: "permit"
+ protocol: "icmp"
+ source:
+ any: true
+ destination:
+ any: true
+ log: true
+ become: yes
+ register: result
+
+ - name: Assert that changes were applied
+ assert:
+ that:
+ - "{{ round_trip['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
+
+ - name: Revert back to base config using facts round trip
+ eos_acls:
+ config: "{{ ansible_facts['network_resources']['acls'] }}"
+ state: overridden
+ become: yes
+ register: revert
+
+ - name: Assert that config was reverted
+ assert:
+ that: "{{ base_config['after'] | symmetric_difference(revert['after']) |length == 0 }}"
+
+ always:
+ - include_tasks: _remove_config.yaml
diff --git a/test/integration/targets/eos_acls/vars/main.yaml b/test/integration/targets/eos_acls/vars/main.yaml
new file mode 100644
index 0000000000..3efbfb76fa
--- /dev/null
+++ b/test/integration/targets/eos_acls/vars/main.yaml
@@ -0,0 +1,110 @@
+round_trip:
+ after:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "tcp"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ log: true
+ - remark: "Run by ansible"
+ sequence: 45
+ - grant: "permit"
+ sequence: 55
+ protocol: "tcp"
+ source:
+ any: true
+ destination:
+ any: true
+ - name: test3
+ aces:
+ - sequence: 100
+ grant: "permit"
+ protocol: "icmp"
+ source:
+ any: true
+ destination:
+ any: true
+ log: true
+ - name: test4
+ aces:
+ - grant: "permit"
+ sequence: 10
+ source:
+ any: true
+ port_protocol:
+ eq: "smtp"
+ destination:
+ any: true
+ port_protocol:
+ eq: "www"
+ protocol: "tcp"
+ ttl:
+ eq: "55"
+ - afi: "ipv6"
+ acls:
+ - name: test2
+ standard: true
+ aces:
+ - grant: "permit"
+ sequence: 10
+ log: true
+ source:
+ any: true
+
+ commands:
+ - "ip access-list test3"
+ - "100 permit icmp any any log"
+
+base_config:
+ after:
+ - afi: "ipv4"
+ acls:
+ - name: test1
+ aces:
+ - sequence: 35
+ grant: "deny"
+ protocol: "tcp"
+ source:
+ subnet_address: 20.0.0.0/8
+ destination:
+ any: true
+ log: true
+ - remark: "Run by ansible"
+ sequence: 45
+ - grant: "permit"
+ sequence: 55
+ protocol: "tcp"
+ source:
+ any: true
+ destination:
+ any: true
+ - name: test4
+ aces:
+ - grant: "permit"
+ sequence: 10
+ source:
+ any: true
+ port_protocol:
+ eq: "smtp"
+ destination:
+ any: true
+ port_protocol:
+ eq: "www"
+ protocol: "tcp"
+ ttl:
+ eq: "55"
+ - afi: "ipv6"
+ acls:
+ - name: test2
+ standard: true
+ aces:
+ - grant: "permit"
+ log: "true"
+ source:
+ any: true
diff --git a/test/units/modules/network/eos/eos_module.py b/test/units/modules/network/eos/eos_module.py
index 12f84af41a..ddc92b0497 100644
--- a/test/units/modules/network/eos/eos_module.py
+++ b/test/units/modules/network/eos/eos_module.py
@@ -23,6 +23,7 @@ import json
import os
from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase
+from ansible.module_utils.network.common.utils import dict_diff, param_list_to_dict
fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
@@ -37,7 +38,6 @@ def load_fixture(name):
with open(path) as f:
data = f.read()
-
try:
data = json.loads(data)
except Exception:
@@ -62,7 +62,6 @@ class TestEosModule(ModuleTestCase):
else:
result = self.changed(changed)
self.assertEqual(result['changed'], changed, result)
-
if commands is not None:
if transport == 'eapi':
cmd = []
diff --git a/test/units/modules/network/eos/fixtures/eos_acls_config.cfg b/test/units/modules/network/eos/fixtures/eos_acls_config.cfg
new file mode 100644
index 0000000000..fa4e4aae57
--- /dev/null
+++ b/test/units/modules/network/eos/fixtures/eos_acls_config.cfg
@@ -0,0 +1,3 @@
+ip access-list test1
+ 35 deny tcp 20.0.0.0/8 any log
+ 45 permit tcp any any
diff --git a/test/units/modules/network/eos/test_eos_acls.py b/test/units/modules/network/eos/test_eos_acls.py
new file mode 100644
index 0000000000..6a0947d2d2
--- /dev/null
+++ b/test/units/modules/network/eos/test_eos_acls.py
@@ -0,0 +1,278 @@
+#
+# (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 units.compat.mock import patch
+from ansible.modules.network.eos import eos_acls
+from ansible.module_utils.network.eos.config.acls.acls import add_commands
+from units.modules.utils import set_module_args
+from .eos_module import TestEosModule, load_fixture
+import itertools
+
+
+class TestEosAclsModule(TestEosModule):
+ module = eos_acls
+
+ def setUp(self):
+ super(TestEosAclsModule, 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.eos.providers.providers.CliProvider.edit_config'
+ )
+ self.edit_config = self.mock_edit_config.start()
+
+ self.mock_execute_show_command = patch(
+ 'ansible.module_utils.network.eos.facts.acls.acls.AclsFacts.get_device_data'
+ )
+ self.execute_show_command = self.mock_execute_show_command.start()
+
+ def tearDown(self):
+ super(TestEosAclsModule, 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, transport='cli', filename=None):
+ if filename is None:
+ filename = 'eos_acls_config.cfg'
+
+ def load_from_file(*args, **kwargs):
+ output = load_fixture(filename)
+ return output
+
+ self.execute_show_command.side_effect = load_from_file
+
+ def test_eos_acls_merged(self):
+ set_module_args(
+ dict(config=[
+ dict(afi="ipv6",
+ acls=[
+ dict(name="test2",
+ standard="true",
+ aces=[
+ dict(sequence="10",
+ grant="permit",
+ protocol="ospf",
+ source=dict(subnet_address="30.2.0.0/8"),
+ destination=dict(any="true"),
+ log="true")
+ ])
+ ])
+ ], state="merged"))
+ commands = ['ipv6 access-list standard test2', '10 permit ospf 30.2.0.0/8 any log']
+ self.execute_module(changed=True, commands=commands)
+
+ def test_eos_acls_merged_idempotent(self):
+ set_module_args(
+ dict(config=[
+ dict(afi="ipv4",
+ acls=[
+ dict(name="test1",
+ aces=[
+ dict(sequence="35",
+ grant="deny",
+ protocol="tcp",
+ source=dict(subnet_address="20.0.0.0/8"),
+ destination=dict(any="true"),
+ log="true"),
+ dict(grant="permit",
+ source=dict(any="true"),
+ destination=dict(any="true"),
+ protocol=6)
+ ])
+ ])
+ ], state="merged"))
+ self.execute_module(changed=False, commands=[])
+
+ def test_eos_acls_replaced(self):
+ set_module_args(
+ dict(config=[
+ dict(afi="ipv4",
+ acls=[
+ dict(name="test1",
+ aces=[
+ dict(sequence="10",
+ grant="permit",
+ protocol="ospf",
+ source=dict(subnet_address="30.2.0.0/8"),
+ destination=dict(any="true"),
+ log="true")
+ ])
+ ])
+ ], state="replaced"))
+ commands = ['ip access-list test1', 'no 35', 'no 45', '10 permit ospf 30.2.0.0/8 any log']
+ self.execute_module(changed=True, commands=commands)
+
+ def test_eos_acls_replaced_idempotent(self):
+ set_module_args(
+ dict(config=[
+ dict(afi="ipv4",
+ acls=[
+ dict(name="test1",
+ aces=[
+ dict(sequence="35",
+ grant="deny",
+ protocol="tcp",
+ source=dict(subnet_address="20.0.0.0/8"),
+ destination=dict(any="true"),
+ log="true"),
+ dict(grant="permit",
+ source=dict(any="true"),
+ destination=dict(any="true"),
+ sequence="45",
+ protocol="tcp")
+ ])
+ ])
+ ], state="replaced"))
+ self.execute_module(changed=False, commands=[])
+
+ def test_eos_acls_overridden(self):
+ set_module_args(
+ dict(config=[
+ dict(afi="ipv4",
+ acls=[
+ dict(name="test1",
+ aces=[
+ dict(sequence="10",
+ grant="permit",
+ protocol="ospf",
+ source=dict(subnet_address="30.2.0.0/8"),
+ destination=dict(any="true"),
+ log="true")
+ ])
+ ])
+ ], state="overridden"))
+ commands = ['ip access-list test1', 'no 35', 'no 45', 'ip access-list test1', '10 permit ospf 30.2.0.0/8 any log']
+ self.execute_module(changed=True, commands=commands)
+
+ def test_eos_acls_overridden_idempotent(self):
+ set_module_args(
+ dict(config=[
+ dict(afi="ipv4",
+ acls=[
+ dict(name="test1",
+ aces=[
+ dict(sequence="35",
+ grant="deny",
+ protocol="tcp",
+ source=dict(subnet_address="20.0.0.0/8"),
+ destination=dict(any="true"),
+ log="true"),
+ dict(grant="permit",
+ source=dict(any="true"),
+ destination=dict(any="true"),
+ sequence="45",
+ protocol="tcp")
+ ])
+ ])
+ ], state="overridden"))
+ self.execute_module(changed=False, commands=[])
+
+ def test_eos_acls_deletedaces(self):
+ set_module_args(
+ dict(config=[
+ dict(afi="ipv4",
+ acls=[
+ dict(name="test1",
+ aces=[
+ dict(grant="permit",
+ sequence="45",
+ source=dict(any="true"),
+ destination=dict(any="true"),
+ protocol=6)
+ ])
+ ])
+ ], state="deleted"))
+ commands = ['ip access-list test1', 'no 45']
+ self.execute_module(changed=True, commands=commands)
+
+ def test_eos_acls_deletedacls(self):
+ set_module_args(
+ dict(config=[
+ dict(afi="ipv4",
+ acls=[
+ dict(name="test1")
+ ])
+ ], state="deleted"))
+ commands = ['no ip access-list test1']
+ self.execute_module(changed=True, commands=commands)
+
+ def test_eos_acls_deletedafis(self):
+ set_module_args(
+ dict(config=[
+ dict(afi="ipv4")
+ ], state="deleted"))
+ commands = ['no ip access-list test1']
+ self.execute_module(changed=True, commands=commands)
+
+ def test_eos_acls_gathered(self):
+ set_module_args(
+ dict(config=[],
+ state="gathered"))
+ result = self.execute_module(changed=False, filename='eos_acls_config.cfg')
+ commands = []
+ for gathered_cmds in result['gathered']:
+ cfg = add_commands(gathered_cmds)
+ commands.append(cfg)
+ commands = list(itertools.chain(*commands))
+ config_commands = ['ip access-list test1', '35 deny tcp 20.0.0.0/8 any log', '45 permit tcp any any']
+ self.assertEqual(sorted(config_commands), sorted(commands), result['gathered'])
+
+ def test_eos_acls_rendered(self):
+ set_module_args(
+ dict(config=[
+ dict(afi="ipv4",
+ acls=[
+ dict(name="test1",
+ aces=[
+ dict(grant="permit",
+ sequence="45",
+ source=dict(any="true"),
+ destination=dict(any="true"),
+ protocol=6)
+ ])
+ ])
+ ], state="rendered"))
+ commands = ['ip access-list test1', '45 permit tcp any any']
+ result = self.execute_module(changed=False)
+ self.assertEqual(sorted(result['rendered']), sorted(commands), result['rendered'])
+
+ def test_eos_acls_parsed(self):
+ set_module_args(
+ dict(running_config="ipv6 access-list test2\n 10 permit icmpv6 host 10.2.33.1 any ttl eq 25",
+ state="parsed"))
+ commands = ['ipv6 access-list test2', '10 permit icmpv6 host 10.2.33.1 any ttl eq 25']
+ result = self.execute_module(changed=False)
+ parsed_commands = []
+ for cmds in result['parsed']:
+ cfg = add_commands(cmds)
+ parsed_commands.append(cfg)
+ parsed_commands = list(itertools.chain(*parsed_commands))
+ self.assertEqual(sorted(parsed_commands), sorted(commands), result['parsed'])