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
pull/67709/head
GomathiselviS 5 years ago committed by GitHub
parent 74e948e6e4
commit d283126c31
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -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

@ -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)

@ -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.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.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.acl_interfaces.acl_interfaces import Acl_interfacesFacts
from ansible.module_utils.network.eos.facts.acls.acls import AclsFacts
FACT_LEGACY_SUBSETS = dict( FACT_LEGACY_SUBSETS = dict(
@ -41,6 +42,7 @@ FACT_RESOURCE_SUBSETS = dict(
lldp_interfaces=Lldp_interfacesFacts, lldp_interfaces=Lldp_interfacesFacts,
vlans=VlansFacts, vlans=VlansFacts,
acl_interfaces=Acl_interfacesFacts, acl_interfaces=Acl_interfacesFacts,
acls=AclsFacts,
) )

@ -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()

@ -50,7 +50,7 @@ options:
not be collected. not be collected.
Valid subsets are 'all', 'interfaces', 'l2_interfaces', 'l3_interfaces', Valid subsets are 'all', 'interfaces', 'l2_interfaces', 'l3_interfaces',
'lacp', 'lacp_interfaces', 'lag_interfaces', 'lldp_global', 'lldp_interfaces', 'lacp', 'lacp_interfaces', 'lag_interfaces', 'lldp_global', 'lldp_interfaces',
'vlans'. 'vlans', 'acls'.
required: false required: false
type: list type: list
version_added: "2.9" version_added: "2.9"

@ -0,0 +1,3 @@
---
testcase: "[^_].*"
test_items: []

@ -0,0 +1,2 @@
dependencies:
- prepare_eos_tests

@ -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

@ -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

@ -0,0 +1,3 @@
---
- { include: cli.yaml, tags: ['cli'] }
- { include: eapi.yaml, tags: ['eapi'] }

@ -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

@ -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

@ -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

@ -0,0 +1,8 @@
---
- name: Setup
eos_acls:
config:
- afi: "ipv4"
- afi: "ipv6"
state: deleted
become: yes

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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"

@ -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

@ -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

@ -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

@ -23,6 +23,7 @@ import json
import os import os
from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase 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') fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures')
@ -37,7 +38,6 @@ def load_fixture(name):
with open(path) as f: with open(path) as f:
data = f.read() data = f.read()
try: try:
data = json.loads(data) data = json.loads(data)
except Exception: except Exception:
@ -62,7 +62,6 @@ class TestEosModule(ModuleTestCase):
else: else:
result = self.changed(changed) result = self.changed(changed)
self.assertEqual(result['changed'], changed, result) self.assertEqual(result['changed'], changed, result)
if commands is not None: if commands is not None:
if transport == 'eapi': if transport == 'eapi':
cmd = [] cmd = []

@ -0,0 +1,3 @@
ip access-list test1
35 deny tcp 20.0.0.0/8 any log
45 permit tcp any any

@ -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'])
Loading…
Cancel
Save