mirror of https://github.com/ansible/ansible.git
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 fixedpull/67709/head
parent
74e948e6e4
commit
d283126c31
@ -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)
|
@ -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()
|
@ -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
|
@ -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…
Reference in New Issue