diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-13 12:04:41 +0000 |
commit | 975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch) | |
tree | 89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/cisco/asa/plugins/module_utils | |
parent | Initial commit. (diff) | |
download | ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip |
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/asa/plugins/module_utils')
30 files changed, 3434 insertions, 0 deletions
diff --git a/ansible_collections/cisco/asa/plugins/module_utils/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/acls.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/acls.py new file mode 100644 index 000000000..f27708e8a --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/acls/acls.py @@ -0,0 +1,293 @@ +# -*- 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 asa_acls module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class AclsArgs(object): + """The arg spec for the asa_acls module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "type": "dict", + "options": { + "acls": { + "elements": "dict", + "type": "list", + "options": { + "name": {"required": True, "type": "str"}, + "acl_type": { + "choices": ["extended", "standard"], + "type": "str", + }, + "rename": {"type": "str"}, + "aces": { + "elements": "dict", + "type": "list", + "options": { + "grant": { + "choices": ["permit", "deny"], + "type": "str", + }, + "line": {"type": "int"}, + "remark": {"type": "str"}, + "source": { + "type": "dict", + "options": { + "address": {"type": "str"}, + "netmask": {"type": "str"}, + "any": {"type": "bool"}, + "any4": {"type": "bool"}, + "any6": {"type": "bool"}, + "host": {"type": "str"}, + "interface": {"type": "str"}, + "object_group": {"type": "str"}, + "port_protocol": { + "type": "dict", + "options": { + "eq": {"type": "str"}, + "gt": {"type": "str"}, + "lt": {"type": "str"}, + "neq": {"type": "str"}, + "range": { + "type": "dict", + "options": { + "start": { + "type": "int", + }, + "end": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "destination": { + "type": "dict", + "options": { + "address": {"type": "str"}, + "netmask": {"type": "str"}, + "any": {"type": "bool"}, + "any4": {"type": "bool"}, + "any6": {"type": "bool"}, + "host": {"type": "str"}, + "interface": {"type": "str"}, + "object_group": {"type": "str"}, + "service_object_group": { + "type": "str", + }, + "port_protocol": { + "type": "dict", + "options": { + "eq": {"type": "str"}, + "gt": {"type": "str"}, + "lt": {"type": "str"}, + "neq": {"type": "str"}, + "range": { + "type": "dict", + "options": { + "start": { + "type": "int", + }, + "end": {"type": "int"}, + }, + }, + }, + }, + }, + }, + "protocol": {"type": "str"}, + "protocol_options": { + "type": "dict", + "options": { + "protocol_number": {"type": "int"}, + "ahp": {"type": "bool"}, + "eigrp": {"type": "bool"}, + "esp": {"type": "bool"}, + "gre": {"type": "bool"}, + "icmp": { + "type": "dict", + "options": { + "alternate_address": { + "type": "bool", + }, + "conversion_error": { + "type": "bool", + }, + "echo": {"type": "bool"}, + "echo_reply": {"type": "bool"}, + "information_reply": { + "type": "bool", + }, + "information_request": { + "type": "bool", + }, + "mask_reply": {"type": "bool"}, + "mask_request": { + "type": "bool", + }, + "mobile_redirect": { + "type": "bool", + }, + "parameter_problem": { + "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"}, + "unreachable": { + "type": "bool", + }, + }, + }, + "icmp6": { + "type": "dict", + "options": { + "echo": {"type": "bool"}, + "echo_reply": {"type": "bool"}, + "membership_query": { + "type": "bool", + }, + "membership_reduction": { + "type": "bool", + }, + "membership_report": { + "type": "bool", + }, + "neighbor_advertisement": { + "type": "bool", + }, + "neighbor_redirect": { + "type": "bool", + }, + "neighbor_solicitation": { + "type": "bool", + }, + "packet_too_big": { + "type": "bool", + }, + "parameter_problem": { + "type": "bool", + }, + "router_advertisement": { + "type": "bool", + }, + "router_renumbering": { + "type": "bool", + }, + "router_solicitation": { + "type": "bool", + }, + "time_exceeded": { + "type": "bool", + }, + "unreachable": { + "type": "bool", + }, + }, + }, + "igmp": {"type": "bool"}, + "igrp": {"type": "bool"}, + "ip": {"type": "bool"}, + "ipinip": {"type": "bool"}, + "ipsec": {"type": "bool"}, + "nos": {"type": "bool"}, + "ospf": {"type": "bool"}, + "pcp": {"type": "bool"}, + "pim": {"type": "bool"}, + "pptp": {"type": "bool"}, + "sctp": {"type": "bool"}, + "snp": {"type": "bool"}, + "tcp": {"type": "bool"}, + "udp": {"type": "bool"}, + }, + }, + "inactive": {"type": "bool"}, + "log": { + "type": "str", + "choices": [ + "default", + "alerts", + "critical", + "debugging", + "disable", + "emergencies", + "errors", + "informational", + "interval", + "notifications", + "warnings", + ], + }, + "time_range": {"type": "str"}, + }, + }, + }, + }, + }, + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/facts.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/facts.py new file mode 100644 index 000000000..af6d008dd --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/facts/facts.py @@ -0,0 +1,29 @@ +# +# -*- 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 arg spec for the asa facts module. +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class FactsArgs(object): + """The arg spec for the asa facts module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "gather_subset": dict( + default=["!config"], + type="list", + elements="str", + ), + "gather_network_resources": dict(type="list", elements="str"), + } diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/ogs.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/ogs.py new file mode 100644 index 000000000..65c9daccb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/argspec/ogs/ogs.py @@ -0,0 +1,261 @@ +# -*- 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 asa_ogs module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class OGsArgs(object): + """The arg spec for the asa_ogs module""" + + argument_spec = { + "config": { + "elements": "dict", + "type": "list", + "options": { + "object_type": { + "type": "str", + "required": True, + "choices": [ + "icmp-type", + "network", + "protocol", + "security", + "service", + "user", + ], + }, + "object_groups": { + "elements": "dict", + "type": "list", + "options": { + "name": {"required": True, "type": "str"}, + "description": {"type": "str"}, + "icmp_type": { + "type": "dict", + "options": { + "icmp_object": { + "type": "list", + "elements": "str", + "choices": [ + "alternate-address", + "conversion-error", + "echo", + "echo-reply", + "information-reply", + "information-request", + "mask-reply", + "mask-request", + "mobile-redirect", + "parameter-problem", + "redirect", + "router-advertisement", + "router-solicitation", + "source-quench", + "time-exceeded", + "timestamp-reply", + "timestamp-request", + "traceroute", + "unreachable", + ], + }, + }, + }, + "network_object": { + "type": "dict", + "options": { + "host": {"type": "list", "elements": "str"}, + "address": {"type": "list", "elements": "str"}, + "ipv6_address": { + "type": "list", + "elements": "str", + }, + "object": {"type": "list", "elements": "str"}, + }, + }, + "protocol_object": { + "type": "dict", + "options": { + "protocol": {"type": "list", "elements": "str"}, + }, + }, + "security_group": { + "type": "dict", + "options": { + "sec_name": { + "type": "list", + "elements": "str", + }, + "tag": {"type": "list", "elements": "str"}, + }, + }, + "services_object": { + "type": "list", + "elements": "dict", + "options": { + "protocol": {"type": "str"}, + "object": {"type": "str"}, + "source_port": { + "type": "dict", + "options": { + "eq": {"type": "str"}, + "gt": {"type": "str"}, + "lt": {"type": "str"}, + "neq": {"type": "str"}, + "range": { + "type": "dict", + "options": { + "start": {"type": "str"}, + "end": {"type": "str"}, + }, + }, + }, + }, + "destination_port": { + "type": "dict", + "options": { + "eq": {"type": "str"}, + "gt": {"type": "str"}, + "lt": {"type": "str"}, + "neq": {"type": "str"}, + "range": { + "type": "dict", + "options": { + "start": {"type": "str"}, + "end": {"type": "str"}, + }, + }, + }, + }, + }, + }, + "protocol": { + "type": "str", + "choices": ["tcp", "tcp-udp", "udp"], + }, + "port_object": { + "type": "list", + "elements": "dict", + "options": { + "eq": {"type": "str"}, + "range": { + "type": "dict", + "options": { + "start": {"type": "str"}, + "end": {"type": "str"}, + }, + }, + }, + }, + "service_object": { + "type": "dict", + "options": { + "protocol": { + "type": "list", + "elements": "str", + "choices": [ + "ah", + "eigrp", + "esp", + "gre", + "icmp", + "icmp6", + "igmp", + "igrp", + "ip", + "ipinip", + "ipsec", + "nos", + "ospf", + "pcp", + "pim", + "pptp", + "sctp", + "snp", + "tcp", + "tcp-udp", + "udp", + ], + }, + "object": {"type": "str"}, + }, + }, + "user_object": { + "type": "dict", + "options": { + "user": { + "elements": "dict", + "type": "list", + "options": { + "name": { + "required": True, + "type": "str", + }, + "domain": { + "required": True, + "type": "str", + }, + }, + }, + "user_group": { + "elements": "dict", + "type": "list", + "options": { + "name": { + "required": True, + "type": "str", + }, + "domain": { + "required": True, + "type": "str", + }, + }, + }, + }, + }, + "group_object": {"type": "list", "elements": "str"}, + }, + }, + }, + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/asa.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/asa.py new file mode 100644 index 000000000..7a19c7d30 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/asa.py @@ -0,0 +1,156 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2016 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type +import json + +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import Connection, ConnectionError, exec_command +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + EntityCollection, +) + + +_DEVICE_CONFIGS = {} +_CONNECTION = None + +command_spec = {"command": dict(key=True), "prompt": dict(), "answer": dict()} + +asa_argument_spec = { + "context": dict(type="str"), + "passwords": dict(type="bool"), +} + + +def check_args(module): + pass + + +def get_connection(module): + if hasattr(module, "_asa_connection"): + return module._asa_connection + + # Not all modules include the 'context' key. + context = module.params.get("context") + connection_proxy = Connection(module._socket_path) + cap = json.loads(connection_proxy.get_capabilities()) + if cap["network_api"] == "cliconf": + module._asa_connection = Connection(module._socket_path) + + if context: + if context == "system": + command = "changeto system" + else: + command = "changeto context %s" % context + module._asa_connection.get(command) + + return module._asa_connection + + +def get_capabilities(module): + if hasattr(module, "_asa_capabilities"): + return module._asa_capabilities + try: + capabilities = Connection(module._socket_path).get_capabilities() + except ConnectionError as exc: + module.fail_json(msg=to_text(exc, errors="surrogate_then_replace")) + module._asa_capabilities = json.loads(capabilities) + + return module._asa_capabilities + + +def to_commands(module, commands): + if not isinstance(commands, list): + raise AssertionError("argument must be of type <list>") + + transform = EntityCollection(module, command_spec) + commands = transform(commands) + + for index, item in enumerate(commands): + if module.check_mode and not item["command"].startswith("show"): + module.warn( + "only show commands are supported when using check " + "mode, not executing `%s`" % item["command"], + ) + + return commands + + +def run_commands(module, commands, check_rc=True): + connection = get_connection(module) + try: + return connection.run_commands(commands=commands, check_rc=check_rc) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def get_config(module, flags=None): + flags = [] if flags is None else flags + + # Not all modules include the 'passwords' key. + passwords = module.params.get("passwords", False) + if passwords: + cmd = "more system:running-config" + else: + cmd = "show running-config " + cmd += " ".join(flags) + cmd = cmd.strip() + + try: + return _DEVICE_CONFIGS[cmd] + except KeyError: + conn = get_connection(module) + out = conn.get(cmd) + cfg = to_text(out, errors="surrogate_then_replace").strip() + _DEVICE_CONFIGS[cmd] = cfg + return cfg + + +def load_config(module, config): + try: + conn = get_connection(module) + conn.edit_config(config) + except ConnectionError as exc: + module.fail_json(msg=to_text(exc)) + + +def get_defaults_flag(module): + rc, out, err = exec_command(module, "show running-config ?") + out = to_text(out, errors="surrogate_then_replace") + + commands = set() + for line in out.splitlines(): + if line: + commands.add(line.strip().split()[0]) + + if "all" in commands: + return "all" + else: + return "full" diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/acls.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/acls.py new file mode 100644 index 000000000..67c6ee9aa --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/acls/acls.py @@ -0,0 +1,237 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The asa_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 copy + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) + +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.facts import Facts +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.rm_templates.acls import ( + AclsTemplate, +) + + +class Acls(ResourceModule): + """ + The asa_acls class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["acls"] + + def __init__(self, module): + super(Acls, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="acls", + tmplt=AclsTemplate(), + ) + + def execute_module(self): + """Execute the module + :rtype: A dictionary + :returns: The result from module execution + """ + self.gen_config() + self.run_commands() + return self.result + + def gen_config(self): + """Select the appropriate function based on the state provided + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + if self.want: + temp = {} + for entry in self.want["acls"]: + temp.update({(entry["name"]): entry}) + wantd = temp + else: + wantd = {} + if self.have: + temp = {} + for entry in self.have["acls"]: + temp.update({(entry["name"]): entry}) + haved = temp + else: + haved = {} + + for k, want in iteritems(wantd): + h_want = haved.get(k, {}) + if want.get("aces"): + for each in want["aces"]: + if h_want.get("aces"): + for e_have in h_want.get("aces"): + if e_have.get("source") == each.get("source") and e_have.get( + "destination", + ) == each.get( + "destination", + ): + if ( + "protocol" in e_have + and "protocol" not in each + and each.get("protocol_options") + == e_have.get("protocol_options") + ): + del e_have["protocol"] + break + # if state is merged, merge want onto have and then compare + if self.state == "merged": + # to append line number from have to want + # if want ace config mateches have ace config + temp_have = copy.deepcopy(haved) + for k, v in iteritems(wantd): + h_item = temp_have.pop(k, {}) + if not h_item: + continue + if v.get("aces"): + for each in v["aces"]: + if "line" in each: + continue + else: + for each_have in h_item["aces"]: + have_line = each_have.pop("line") + if each == each_have: + each.update({"line": have_line}) + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + temp = {} + for k, v in iteritems(haved): + if k in wantd or not wantd: + temp.update({k: v}) + haved = temp + wantd = {} + + # remove superfluous config for overridden and deleted + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + self._compare(want={}, have=have) + + temp = [] + for k, want in iteritems(wantd): + if want.get("rename") and want.get("rename") not in temp: + self.commands.extend( + ["access-list {name} rename {rename}".format(**want)], + ) + elif k in haved: + temp.append(k) + self._compare(want=want, have=haved.pop(k, {})) + if self.state in ["replaced", "overridden", "deleted"]: + config_cmd = [cmd for cmd in self.commands if "no" in cmd][::-1] + config_cmd.extend( + [cmd for cmd in self.commands if "no" not in cmd], + ) + self.commands = config_cmd + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Ospf_interfaces network resource. + """ + parsers = ["aces"] + + if want.get("aces"): + for each in want["aces"]: + set_want = True + if have.get("aces"): + temp = 0 + for e_have in have.get("aces"): + if e_have.get("source") == each.get("source") and e_have.get( + "destination", + ) == each.get( + "destination", + ): + set_want = False + if each.get("protocol") == e_have.get("protocol"): + if not each.get( + "protocol_options", + ) and e_have.get("protocol_options"): + del e_have["protocol_options"] + if each == e_have: + del have.get("aces")[temp] + break + each.update( + { + "name": want.get("name"), + "acl_type": want.get("acl_type"), + }, + ) + e_have.update( + { + "name": have.get("name"), + "acl_type": have.get("acl_type"), + }, + ) + self.compare( + parsers=parsers, + want={"aces": each}, + have={"aces": e_have}, + ) + break + temp += 1 + else: + each.update( + { + "name": want.get("name"), + "acl_type": want.get("acl_type"), + }, + ) + self.compare( + parsers=parsers, + want={"aces": each}, + have=dict(), + ) + set_want = False + if set_want: + each.update( + { + "name": want.get("name"), + "acl_type": want.get("acl_type"), + }, + ) + self.compare( + parsers=parsers, + want={"aces": each}, + have=dict(), + ) + if self.state in ["overridden", "deleted", "replaced"]: + if have.get("aces"): + for each in have["aces"]: + each.update( + { + "name": have.get("name"), + "acl_type": have.get("acl_type"), + }, + ) + self.compare( + parsers=parsers, + want=dict(), + have={"aces": each}, + ) diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/ogs.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/ogs.py new file mode 100644 index 000000000..b414aee08 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/config/ogs/ogs.py @@ -0,0 +1,694 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2019 Red Hat Inc. +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +""" +The asa_ogs 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 copy + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) + +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.facts import Facts +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.rm_templates.ogs import ( + OGsTemplate, +) + + +class OGs(ResourceModule): + """ + The asa_ogs class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["ogs"] + + def __init__(self, module): + super(OGs, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="ogs", + tmplt=OGsTemplate(), + ) + + def execute_module(self): + """Execute the module + :rtype: A dictionary + :returns: The result from module execution + """ + self.gen_config() + self.run_commands() + return self.result + + def gen_config(self): + """Select the appropriate function based on the state provided + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + if self.want: + temp = {} + for entry in self.want: + temp.update({(entry["object_type"]): entry}) + wantd = temp + else: + wantd = {} + if self.have: + temp = {} + for entry in self.have: + temp.update({(entry["object_type"]): entry}) + haved = temp + else: + haved = {} + + obj_gp = {} + for k, v in wantd.items(): + temp = {} + for each in v.get("object_groups"): + temp[each.get("name")] = each + temp["object_type"] = k + obj_gp[k] = temp + if obj_gp: + wantd = obj_gp + obj_gp = {} + for k, v in haved.items(): + temp = {} + for each in v.get("object_groups"): + temp[each.get("name")] = each + temp["object_type"] = k + obj_gp[k] = temp + if obj_gp: + haved = obj_gp + + # if state is merged, merge want onto have + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + temp = {} + for k, v in iteritems(haved): + temp_have = {} + if k in wantd or not wantd: + for key, val in iteritems(v): + if not wantd or key in wantd[k]: + temp_have.update({key: val}) + temp.update({k: temp_have}) + haved = temp + wantd = {} + + # delete processes first so we do run into "more than one" errors + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + for each_key, each_val in iteritems(have): + if each_key != "object_type": + each_val.update( + {"object_type": have.get("object_type")}, + ) + self.addcmd(each_val, "og_name", True) + + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {})) + + def _compare(self, want, have): + if want != have: + for k, v in iteritems(want): + if k != "object_type": + v.update({"object_type": want.get("object_type")}) + if have: + for k, v in iteritems(have): + if k != "object_type": + v.update({"object_type": want.get("object_type")}) + + object_type = want.get("object_type") + if object_type == "icmp-type": + self._icmp_object_compare(want, have) + if object_type == "network": + self._network_object_compare(want, have) + elif object_type == "protocol": + self._protocol_object_compare(want, have) + elif object_type == "security": + self._security_object_compare(want, have) + elif object_type == "service": + self._service_object_compare(want, have) + elif object_type == "user": + self._user_object_compare(want, have) + + def get_list_diff(self, want, have, object, param): + diff = [item for item in want[object][param] if item not in have[object][param]] + return diff + + def check_for_have_and_overidden(self, have): + if have and self.state == "overridden": + for name, entry in iteritems(have): + if name != "object_type": + self.addcmd(entry, "og_name", True) + + def _icmp_object_compare(self, want, have): + icmp_obj = "icmp_type" + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if entry != h_item and name != "object_type" and entry[icmp_obj].get("icmp_object"): + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, + h_item, + icmp_obj, + ["icmp_type"], + ) + else: + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if self.state in ("overridden", "replaced") and h_item: + self.compare(["icmp_type"], {}, h_item) + if h_item and h_item[icmp_obj].get("icmp_object"): + li_diff = self.get_list_diff( + entry, + h_item, + icmp_obj, + "icmp_object", + ) + else: + li_diff = entry[icmp_obj].get("icmp_object") + entry[icmp_obj]["icmp_object"] = li_diff + self.addcmd(entry, "icmp_type", False) + self.check_for_have_and_overidden(have) + + def _network_object_compare(self, want, have): + network_obj = "network_object" + parsers = [ + "network_object.host", + "network_object.address", + "network_object.ipv6_address", + "network_object.object", + ] + add_obj_cmd = False + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if entry != h_item and name != "object_type": + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, + h_item, + network_obj, + ["address", "host", "ipv6_address", "object"], + ) + else: + add_obj_cmd = True + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if entry[network_obj].get("address"): + self._compare_object_diff( + entry, + h_item, + network_obj, + "address", + parsers, + "network_object.address", + ) + elif h_item and h_item.get(network_obj) and h_item[network_obj].get("address"): + h_item[network_obj] = { + "address": h_item[network_obj].get("address"), + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + if entry[network_obj].get("host"): + self._compare_object_diff( + entry, + h_item, + network_obj, + "host", + parsers, + "network_object.host", + ) + elif h_item and h_item[network_obj].get("host"): + h_item[network_obj] = { + "host": h_item[network_obj].get("host"), + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + if entry[network_obj].get("ipv6_address"): + self._compare_object_diff( + entry, + h_item, + network_obj, + "ipv6_address", + parsers, + "network_object.ipv6_address", + ) + elif h_item and h_item.get(network_obj) and h_item[network_obj].get("ipv6_address"): + h_item[network_obj] = { + "ipv6_address": h_item[network_obj].get("ipv6_address"), + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + if entry[network_obj].get("object"): + self._compare_object_diff( + entry, + h_item, + network_obj, + "object", + parsers, + "network_object.object", + ) + elif h_item and h_item.get(network_obj) and h_item[network_obj].get("object"): + h_item[network_obj] = { + "object": h_item[network_obj].get("object"), + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + self.check_for_have_and_overidden(have) + + def _protocol_object_compare(self, want, have): + protocol_obj = "protocol_object" + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if entry != h_item and name != "object_type": + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, + h_item, + protocol_obj, + ["protocol"], + ) + else: + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if entry[protocol_obj].get("protocol"): + self._compare_object_diff( + entry, + h_item, + protocol_obj, + "protocol", + [protocol_obj], + protocol_obj, + ) + self.check_for_have_and_overidden(have) + + def _security_object_compare(self, want, have): + security_obj = "security_group" + parsers = ["security_group.sec_name", "security_group.tag"] + add_obj_cmd = False + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if entry != h_item and name != "object_type": + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, + h_item, + security_obj, + ["sec_name", "tag"], + ) + else: + add_obj_cmd = True + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if entry[security_obj].get("sec_name"): + self._compare_object_diff( + entry, + h_item, + security_obj, + "sec_name", + parsers, + "security_group.sec_name", + ) + elif h_item and h_item[security_obj].get("sec_name"): + h_item[security_obj] = { + "sec_name": h_item[security_obj].get("sec_name"), + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + if entry[security_obj].get("tag"): + self._compare_object_diff( + entry, + h_item, + security_obj, + "tag", + parsers, + "security_group.tag", + ) + elif h_item and h_item[security_obj].get("tag"): + h_item[security_obj] = { + "tag": h_item[security_obj].get("tag"), + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + self.check_for_have_and_overidden(have) + + def _service_object_compare(self, want, have): + service_obj = "service_object" + services_obj = "services_object" + port_obj = "port_object" + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if entry != h_item and name != "object_type": + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, + h_item, + service_obj, + ["protocol"], + ) + else: + protocol = entry.get("protocol") + if protocol: + entry["name"] = "{0} {1}".format(name, protocol) + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if entry.get(service_obj): + if entry[service_obj].get("protocol"): + self._compare_object_diff( + entry, + h_item, + service_obj, + "protocol", + ["service_object"], + service_obj, + ) + elif entry.get(services_obj): + if h_item: + h_item = self.convert_list_to_dict( + val=h_item, + source="source_port", + destination="destination_port", + ) + entry = self.convert_list_to_dict( + val=entry, + source="source_port", + destination="destination_port", + ) + command_len = len(self.commands) + for k, v in iteritems(entry): + if h_item: + h_service_item = h_item.pop(k, {}) + if h_service_item != v: + self.compare( + [services_obj], + want={services_obj: v}, + have={services_obj: h_service_item}, + ) + else: + temp_want = {"name": name, services_obj: v} + self.addcmd(temp_want, "og_name", True) + + self.compare( + [services_obj], + want=temp_want, + have={}, + ) + if h_item and self.state in ["overridden", "replaced"]: + for k, v in iteritems(h_item): + temp_have = {"name": name, services_obj: v} + self.compare( + [services_obj], + want={}, + have=temp_have, + ) + if command_len < len(self.commands): + cmd = "object-group service {0}".format(name) + if cmd not in self.commands: + self.commands.insert(command_len, cmd) + elif entry.get(port_obj): + protocol = entry.get("protocol") + if h_item: + h_item = self.convert_list_to_dict( + val=h_item, + source="source_port", + destination="destination_port", + ) + entry = self.convert_list_to_dict( + val=entry, + source="source_port", + destination="destination_port", + ) + command_len = len(self.commands) + for k, v in iteritems(entry): + h_port_item = h_item.pop(k, {}) + if "http" in k and "_" in k: + # This condition is to TC of device behaviour, where if user tries to + # configure http it gets converted to www. + temp = k.split("_")[0] + h_port_item = {temp: "http"} + if h_port_item != v: + self.compare( + [port_obj], + want={port_obj: v}, + have={port_obj: h_port_item}, + ) + elif not h_port_item: + temp_want = {"name": name, port_obj: v} + self.compare([port_obj], want=temp_want, have={}) + if h_item and self.state in ["overridden", "replaced"]: + for k, v in iteritems(h_item): + temp_have = {"name": name, port_obj: v} + self.compare([port_obj], want={}, have=temp_have) + self.check_for_have_and_overidden(have) + + def convert_list_to_dict(self, *args, **kwargs): + temp = {} + if kwargs["val"].get("services_object"): + for every in kwargs["val"]["services_object"]: + temp_key = every["protocol"] + if "source_port" in every: + if "range" in every["source_port"]: + temp_key = ( + "range" + + "_" + + str(every["source_port"]["range"]["start"]) + + "_" + + str(every["source_port"]["range"]["end"]) + ) + else: + source_key = list(every["source_port"])[0] + temp_key = ( + temp_key + "_" + source_key + "_" + every["source_port"][source_key] + ) + if "destination_port" in every: + if "range" in every["destination_port"]: + temp_key = ( + "range" + + "_" + + str(every["destination_port"]["range"]["start"]) + + "_" + + str(every["destination_port"]["range"]["end"]) + ) + else: + destination_key = list(every["destination_port"])[0] + temp_key = ( + temp_key + + "_" + + destination_key + + "_" + + every["destination_port"][destination_key] + ) + temp.update({temp_key: every}) + return temp + elif kwargs["val"].get("port_object"): + for every in kwargs["val"]["port_object"]: + if "range" in every: + temp_key = ( + "start" + + "_" + + every["range"]["start"] + + "_" + + "end" + + "_" + + every["range"]["end"] + ) + else: + every_key = list(every)[0] + temp_key = every_key + "_" + every[every_key] + temp.update({temp_key: every}) + return temp + + def _user_object_compare(self, want, have): + user_obj = "user_object" + parsers = ["user_object.user", "user_object.user_gp"] + add_obj_cmd = False + for name, entry in iteritems(want): + h_item = have.pop(name, {}) + if entry != h_item and name != "object_type": + if h_item and entry.get("group_object"): + self.addcmd(entry, "og_name", False) + self._add_group_object_cmd(entry, h_item) + continue + if h_item: + self._add_object_cmd( + entry, + h_item, + user_obj, + ["user", "user_group"], + ) + else: + add_obj_cmd = True + self.addcmd(entry, "og_name", False) + self.compare(["description"], entry, h_item) + if entry.get("group_object"): + self._add_group_object_cmd(entry, h_item) + continue + if entry[user_obj].get("user"): + self._compare_object_diff( + entry, + h_item, + user_obj, + "user", + ["user_object.user"], + "user_object.user", + ) + elif h_item and h_item[user_obj].get("user"): + h_item[user_obj] = {"user": h_item[user_obj].get("user")} + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + if entry[user_obj].get("user_group"): + self._compare_object_diff( + entry, + h_item, + user_obj, + "user_group", + ["user_object.user_group"], + "user_object.user_gp", + ) + elif h_item and h_item[user_obj].get("user_group"): + h_item[user_obj] = { + "user_group": h_item[user_obj].get("user_group"), + } + if not add_obj_cmd: + self.addcmd(entry, "og_name", False) + self.compare(parsers, {}, h_item) + self.check_for_have_and_overidden(have) + + def _add_object_cmd(self, want, have, object, object_elements): + obj_cmd_added = False + for each in object_elements: + want_element = want[object].get(each) if want.get(object) else want + have_element = have[object].get(each) if have.get(object) else have + if ( + want_element + and isinstance(want_element, list) + and isinstance(want_element[0], dict) + ): + if want_element and have_element and want_element != have_element: + if not obj_cmd_added: + self.addcmd(want, "og_name", False) + self.compare(["description"], want, have) + obj_cmd_added = True + else: + if want_element and have_element and set(want_element) != set(have_element): + if not obj_cmd_added: + self.addcmd(want, "og_name", False) + self.compare(["description"], want, have) + obj_cmd_added = True + + def _add_group_object_cmd(self, want, have): + if have and have.get("group_object"): + want["group_object"] = list( + set(want.get("group_object")) - set(have.get("group_object")), + ) + have["group_object"] = list( + set(have.get("group_object")) - set(want.get("group_object")), + ) + for each in want["group_object"]: + self.compare(["group_object"], {"group_object": each}, dict()) + if ( + (self.state == "replaced" or self.state == "overridden") + and have + and have.get("group_object") + ): + for each in have["group_object"]: + self.compare(["group_object"], dict(), {"group_object": each}) + + def _compare_object_diff( + self, + want, + have, + object, + object_type, + parsers, + val, + ): + temp_have = copy.copy(have) + temp_want = copy.copy(want) + if temp_have and temp_have.get(object) and temp_have[object].get(object_type): + want_diff = self.get_list_diff( + temp_want, + temp_have, + object, + object_type, + ) + have_diff = [ + each + for each in temp_have[object][object_type] + if each not in temp_want[object][object_type] + ] + if have_diff: + temp_have[object].pop(object_type) + else: + have_diff = [] + want_diff = temp_want[object].get(object_type) + temp_want[object][object_type] = want_diff + if have_diff or temp_have.get(object) and self.state in ("overridden", "replaced"): + if have_diff: + temp_have[object] = {object_type: have_diff} + self.compare(parsers, {}, temp_have) + self.addcmd(temp_want, val, False) diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/acls.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/acls.py new file mode 100644 index 000000000..b624a055b --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/acls/acls.py @@ -0,0 +1,107 @@ +# +# -*- 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 asa_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 + + +from copy import deepcopy + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.argspec.acls.acls import ( + AclsArgs, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.rm_templates.acls import ( + AclsTemplate, +) + + +class AclsFacts(object): + """The asa_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_acls_config(self, connection): + return connection.get("sh 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_acls_config(connection) + + rmmod = NetworkTemplate(lines=data.splitlines(), tmplt=AclsTemplate()) + current = rmmod.parse() + acls = list() + if current.get("acls"): + for key, val in iteritems(current.get("acls")): + if val.get("name") == "cached": + continue + for each in val.get("aces"): + if "protocol_number" in each: + each["protocol_options"] = { + "protocol_number": each["protocol_number"], + } + del each["protocol_number"] + if "icmp_icmp6_protocol" in each and each.get("protocol"): + each["protocol_options"] = { + each.get("protocol"): { + each["icmp_icmp6_protocol"].replace( + "-", + "_", + ): True, + }, + } + del each["icmp_icmp6_protocol"] + elif ( + each.get("protocol") + and each.get("protocol") != "icmp" + and each.get("protocol") != "icmp6" + ): + each["protocol_options"] = {each.get("protocol"): True} + acls.append(val) + facts = {} + params = {} + if acls: + params = utils.validate_config( + self.argument_spec, + {"config": {"acls": acls}}, + ) + params = utils.remove_empties(params) + facts["acls"] = params["config"] + + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/facts.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/facts.py new file mode 100644 index 000000000..59533d327 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/facts.py @@ -0,0 +1,70 @@ +# -*- 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 facts class for asa +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import ( + FactsBase, +) + +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.acls.acls import AclsFacts +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.legacy.base import ( + Config, + Default, + Hardware, +) +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.ogs.ogs import OGsFacts + + +FACT_LEGACY_SUBSETS = dict(default=Default, hardware=Hardware, config=Config) + +FACT_RESOURCE_SUBSETS = dict(acls=AclsFacts, ogs=OGsFacts) + + +class Facts(FactsBase): + """The fact class for asa""" + + VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) + VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys()) + + def __init__(self, module): + super(Facts, self).__init__(module) + + def get_facts( + self, + legacy_facts_type=None, + resource_facts_type=None, + data=None, + ): + """Collect the facts for asa + :param legacy_facts_type: List of legacy facts types + :param resource_facts_type: List of resource fact types + :param data: previously collected conf + :rtype: dict + :return: the facts gathered + """ + if self.VALID_RESOURCE_SUBSETS: + self.get_network_resources_facts( + FACT_RESOURCE_SUBSETS, + resource_facts_type, + data, + ) + + if self.VALID_LEGACY_GATHER_SUBSETS: + self.get_network_legacy_facts( + FACT_LEGACY_SUBSETS, + legacy_facts_type, + ) + + return self.ansible_facts, self._warnings diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/base.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/base.py new file mode 100644 index 000000000..d256229bc --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/legacy/base.py @@ -0,0 +1,185 @@ +# -*- 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 asa legacy 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 platform +import re + +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import ( + get_capabilities, + run_commands, +) + + +class FactsBase(object): + COMMANDS = list() + + def __init__(self, module): + self.module = module + self.facts = dict() + self.warnings = list() + self.responses = None + + def populate(self): + self.responses = run_commands( + self.module, + commands=self.COMMANDS, + check_rc=False, + ) + + def run(self, cmd): + return run_commands(self.module, commands=cmd, check_rc=False) + + +class Default(FactsBase): + COMMANDS = ["show version"] + + def populate(self): + super(Default, self).populate() + self.facts.update(self.platform_facts()) + data = self.responses[0] + if data: + self.facts["asatype"] = self.parse_asatype(data) + self.facts["serialnum"] = self.parse_serialnum(data) + self.parse_stacks(data) + + def parse_asatype(self, data): + match = re.search(r"Hardware:(\s+)ASA", data) + if match: + return "ASA" + + def parse_serialnum(self, data): + match = re.search(r"Serial Number: (\S+)", data) + if match: + return match.group(1) + + def parse_stacks(self, data): + match = re.findall(r"^Model [Nn]umber\s+: (\S+)", data, re.M) + if match: + self.facts["stacked_models"] = match + + match = re.findall( + r"^System [Ss]erial [Nn]umber\s+: (\S+)", + data, + re.M, + ) + if match: + self.facts["stacked_serialnums"] = match + + def platform_facts(self): + platform_facts = {} + + resp = get_capabilities(self.module) + device_info = resp["device_info"] + platform_facts["system"] = device_info["network_os"] + + for item in ( + "model", + "image", + "version", + "platform", + "hostname", + "firepower_version", + "device_mgr_version", + ): + val = device_info.get("network_os_%s" % item) + if val: + platform_facts[item] = val + + platform_facts["api"] = resp["network_api"] + platform_facts["python_version"] = platform.python_version() + + return platform_facts + + +class Hardware(FactsBase): + COMMANDS = ["dir", "show memory"] + + def populate(self): + warnings = list() + super(Hardware, self).populate() + data = self.responses[0] + if data: + self.facts["filesystems"] = self.parse_filesystems(data) + self.facts["filesystems_info"] = self.parse_filesystems_info(data) + + data = self.responses[1] + if data: + if "Invalid input detected" in data: + warnings.append("Unable to gather memory statistics") + else: + mem_list = [l for l in data.splitlines() if "memory" in l] + for each in mem_list: + if "Free memory" in each: + match = re.search( + r"Free memory.+ (\d+) .+(\d\d)", + each, + ) + if match: + self.facts["memfree_mb"] = int(match.group(1)) // 1024 + elif "Used memory" in each: + match = re.search( + r"Used memory.+ (\d+) .+(\d\d)", + each, + ) + if match: + self.facts["memused_mb"] = int(match.group(1)) // 1024 + elif "Total memory" in each: + match = re.search( + r"Total memory.+ (\d+) .+(\d\d)", + each, + ) + if match: + self.facts["memtotal_mb"] = int(match.group(1)) // 1024 + + def parse_filesystems(self, data): + return re.findall(r"^Directory of (\S+)/", data, re.M) + + def parse_filesystems_info(self, data): + facts = dict() + fs = "" + for line in data.split("\n"): + match = re.match(r"^Directory of (\S+)/", line) + if match: + fs = match.group(1) + facts[fs] = dict() + continue + match = re.match( + r"^(\d+) bytes total \((\d+) bytes free\/(\d+)% free\)", + line, + ) + if match: + facts[fs]["spacetotal_kb"] = int(match.group(1)) / 1024 + facts[fs]["spacefree_kb"] = int(match.group(2)) / 1024 + + return facts + + +class Config(FactsBase): + COMMANDS = ["show running-config"] + + def populate(self): + super(Config, self).populate() + data = self.responses[0] + if data: + data = re.sub( + r"^Building configuration...\s+Current configuration : \d+ bytes\n", + "", + data, + flags=re.MULTILINE, + ) + self.facts["config"] = data diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/ogs.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/ogs.py new file mode 100644 index 000000000..41821d4c1 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/facts/ogs/ogs.py @@ -0,0 +1,98 @@ +# -*- 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 asa_og 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 + + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.argspec.ogs.ogs import OGsArgs +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.rm_templates.ogs import ( + OGsTemplate, +) + + +class OGsFacts(object): + """The asa_ogs fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = OGsArgs.argument_spec + + def get_og_data(self, connection): + return connection.get("sh running-config object-group") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for OGs + :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_og_data(connection) + rmmod = NetworkTemplate(lines=data.splitlines(), tmplt=OGsTemplate()) + current = rmmod.parse() + ogs = [] + object_groups = { + "icmp-type": "icmp_type", + "network": "network_object", + "protocol": "protocol_object", + "security": "security_group", + "service": "service_object", + "user": "user_object", + } + if current.get("ogs"): + for k, v in iteritems(current.get("ogs")): + obj_gp = {} + config_dict = {} + config_dict["object_type"] = k + config_dict["object_groups"] = [] + for each in iteritems(v): + obj_gp["name"] = each[1].pop("name") + each[1].pop("object_type") + if each[1].get("description"): + obj_gp["description"] = each[1].pop("description") + if each[1].get("group_object"): + obj_gp["group_object"] = each[1].pop("group_object") + if k == "service": + if "services_object" in each[1]: + obj_gp["services_object"] = each[1]["services_object"] + elif "port_object" in each[1]: + obj_gp["port_object"] = each[1]["port_object"] + obj_gp["protocol"] = each[1]["protocol"] + else: + obj_gp[object_groups.get(k)] = each[1] + config_dict["object_groups"].append(obj_gp) + obj_gp = {} + config_dict["object_groups"] = sorted( + config_dict["object_groups"], + key=lambda k, sk="name": str(k[sk]), + ) + ogs.append(config_dict) + # sort the object group list of dict by object_type + ogs = sorted(ogs, key=lambda i: i["object_type"]) + facts = {} + params = utils.remove_empties( + utils.validate_config(self.argument_spec, {"config": ogs}), + ) + facts["ogs"] = params.get("config") + + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/module.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/module.py new file mode 100644 index 000000000..ca76966f5 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/module.py @@ -0,0 +1,71 @@ +# +# (c) 2019, Ansible by Red Hat, inc +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +# +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection + +from ansible_collections.cisco.asa.plugins.module_utils.network.asa.providers import providers + + +class NetworkModule(AnsibleModule): + fail_on_missing_provider = True + + def __init__(self, connection=None, *args, **kwargs): + super(NetworkModule, self).__init__(*args, **kwargs) + + if connection is None: + connection = Connection(self._socket_path) + + self.connection = connection + + @property + def provider(self): + if not hasattr(self, "_provider"): + capabilities = self.from_json(self.connection.get_capabilities()) + + network_os = capabilities["device_info"]["network_os"] + network_api = capabilities["network_api"] + + if network_api == "cliconf": + connection_type = "network_cli" + + cls = providers.get( + network_os, + self._name.split(".")[-1], + connection_type, + ) + + if not cls: + msg = "unable to find suitable provider for network os %s" % network_os + if self.fail_on_missing_provider: + self.fail_json(msg=msg) + else: + self.warn(msg) + + obj = cls(self.params, self.connection, self.check_mode) + + setattr(self, "_provider", obj) + + return getattr(self, "_provider") + + def get_facts(self, subset=None): + try: + self.provider.get_facts(subset) + except Exception as exc: + self.fail_json(msg=to_text(exc)) + + def edit_config(self, config_filter=None): + current_config = self.connection.get_config(flags=config_filter) + try: + commands = self.provider.edit_config(current_config) + changed = bool(commands) + return {"commands": commands, "changed": changed} + except Exception as exc: + self.fail_json(msg=to_text(exc)) diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/providers.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/providers.py new file mode 100644 index 000000000..ad956ea64 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/providers/providers.py @@ -0,0 +1,126 @@ +# +# (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 + +import json + +from threading import RLock + +from ansible.module_utils.six import itervalues +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import ( + NetworkConfig, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list + + +_registered_providers = {} +_provider_lock = RLock() + + +def register_provider(network_os, module_name): + def wrapper(cls): + _provider_lock.acquire() + try: + if network_os not in _registered_providers: + _registered_providers[network_os] = {} + for ct in cls.supported_connections: + if ct not in _registered_providers[network_os]: + _registered_providers[network_os][ct] = {} + for item in to_list(module_name): + for entry in itervalues(_registered_providers[network_os]): + entry[item] = cls + finally: + _provider_lock.release() + return cls + + return wrapper + + +def get(network_os, module_name, connection_type): + network_os_providers = _registered_providers.get(network_os) + if network_os_providers is None: + raise ValueError("unable to find a suitable provider for this module") + if connection_type not in network_os_providers: + raise ValueError("provider does not support this connection type") + elif module_name not in network_os_providers[connection_type]: + raise ValueError("could not find a suitable provider for this module") + return network_os_providers[connection_type][module_name] + + +class ProviderBase(object): + supported_connections = () + + def __init__(self, params, connection=None, check_mode=False): + self.params = params + self.connection = connection + self.check_mode = check_mode + + @property + def capabilities(self): + if not hasattr(self, "_capabilities"): + resp = self.from_json(self.connection.get_capabilities()) + setattr(self, "_capabilities", resp) + return getattr(self, "_capabilities") + + def get_value(self, path): + params = self.params.copy() + for key in path.split("."): + params = params[key] + return params + + def get_facts(self, subset=None): + raise NotImplementedError(self.__class__.__name__) + + def edit_config(self): + raise NotImplementedError(self.__class__.__name__) + + +class CliProvider(ProviderBase): + supported_connections = ("network_cli",) + + @property + def capabilities(self): + if not hasattr(self, "_capabilities"): + resp = self.from_json(self.connection.get_capabilities()) + setattr(self, "_capabilities", resp) + return getattr(self, "_capabilities") + + def get_config_context(self, config, path, indent=1): + if config is not None: + netcfg = NetworkConfig(indent=indent, contents=config) + try: + config = netcfg.get_block_config(to_list(path)) + except ValueError: + config = None + return config + + def render(self, config=None): + raise NotImplementedError(self.__class__.__name__) + + def cli(self, command): + try: + if not hasattr(self, "_command_output"): + setattr(self, "_command_output", {}) + return self._command_output[command] + except KeyError: + out = self.connection.get(command) + try: + out = json.loads(out) + except ValueError: + pass + self._command_output[command] = out + return out + + def get_facts(self, subset=None): + return self.populate() + + def edit_config(self, config=None): + commands = self.render(config) + if commands and self.check_mode is False: + self.connection.edit_config(commands) + return commands diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/acls.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/acls.py new file mode 100644 index 000000000..b97151027 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/acls.py @@ -0,0 +1,249 @@ +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _tmplt_access_list_name(config_data): + command = "access-list {acls_name} ".format(**config_data) + return command + + +def _tmplt_access_list_entries(config_data): + if "aces" in config_data: + command = [] + + def source_destination_common_config(config_data, cmd, type): + if config_data["aces"][type].get("any"): + cmd += " any" + elif config_data["aces"][type].get("any4"): + cmd += " any4" + elif config_data["aces"][type].get("any6"): + cmd += " any6" + elif config_data["aces"][type].get("address"): + cmd += " {address}".format(**config_data["aces"][type]) + if config_data["aces"][type].get("netmask"): + cmd += " {netmask}".format(**config_data["aces"][type]) + elif config_data["aces"][type].get("host"): + cmd += " host {host}".format(**config_data["aces"][type]) + elif config_data["aces"][type].get("interface"): + cmd += " interface {interface}".format(**config_data["aces"][type]) + elif config_data["aces"][type].get("object_group"): + cmd += " object-group {object_group}".format(**config_data["aces"][type]) + if type == "destination" and config_data["aces"][type].get( + "service_object_group", + ): + cmd += " object-group {service_object_group}".format(**config_data["aces"][type]) + if config_data["aces"].get("protocol_options"): + protocol_option_key = list( + config_data["aces"]["protocol_options"], + )[0] + if ( + isinstance( + config_data["aces"]["protocol_options"][protocol_option_key], + dict, + ) + and type == "destination" + ): + val = list( + config_data["aces"]["protocol_options"][protocol_option_key], + )[0] + cmd += " {0}".format(val.replace("_", "-")) + if config_data["aces"][type].get("port_protocol"): + if config_data["aces"][type].get("port_protocol").get("range"): + start = config_data["aces"][type].get("port_protocol")["range"]["start"] + end = config_data["aces"][type].get("port_protocol")["range"]["end"] + cmd += " range {0} {1}".format(start, end) + else: + port_protocol = list( + config_data["aces"][type]["port_protocol"], + )[0] + cmd += ( + " " + + port_protocol + + " " + + config_data["aces"][type]["port_protocol"][port_protocol] + ) + return cmd + + cmd = "" + if config_data["aces"].get("remark"): + command.append( + "access-list {name} line {line} remark {remark}".format(**config_data["aces"]), + ) + if len(config_data["aces"]) > 4: + try: + cmd = "access-list {name} line {line}".format(**config_data["aces"]) + except KeyError: + cmd = "access-list {name}".format(**config_data["aces"]) + if ( + config_data["aces"].get("acl_type") + and config_data["aces"].get("acl_type") != "standard" + ): + cmd += " {acl_type}".format(**config_data["aces"]) + if config_data["aces"].get("grant"): + cmd += " {grant}".format(**config_data["aces"]) + if config_data["aces"].get("protocol_options"): + if "protocol_number" in config_data["aces"]["protocol_options"]: + cmd += " {protocol_number}".format(**config_data["aces"]["protocol_options"]) + else: + cmd += " {0}".format( + list(config_data["aces"]["protocol_options"])[0], + ) + elif config_data["aces"].get("protocol"): + cmd += " {protocol}".format(**config_data["aces"]) + if config_data["aces"].get("source"): + cmd = source_destination_common_config( + config_data, + cmd, + "source", + ) + if config_data["aces"].get("destination"): + cmd = source_destination_common_config( + config_data, + cmd, + "destination", + ) + if config_data["aces"].get("log"): + cmd += " log {log}".format(**config_data["aces"]) + if config_data["aces"].get("inactive"): + cmd += " inactive" + if config_data["aces"].get("time_range"): + cmd += " time-range {time_range}".format(**config_data["aces"]) + if cmd: + command.append(cmd) + return command + + +class AclsTemplate(NetworkTemplate): + def __init__(self, lines=None): + super(AclsTemplate, self).__init__(lines=lines, tmplt=self) + + PARSERS = [ + { + "name": "acls_name", + "getval": re.compile( + r"""^access-list* + \s*(?P<acl_name>\S+); + \s*\S+\s*elements; + """, + re.VERBOSE, + ), + "setval": _tmplt_access_list_name, + "compval": "name", + "result": {"acls": {"{{ acl_name }}": {"name": "{{ acl_name }}"}}}, + "shared": True, + }, + { + "name": "aces", + "getval": re.compile( + r"""^access-list* + \s*(?P<acl_name>\S+)* + \s*(?P<line>line\s\d+)* + \s*(?P<remark>remark\s\S.*)* + \s*(?P<ethertype>ethertype)* + \s*(?P<webtype>webtype)* + \s*(?P<acl_type>extended|standard)* + \s*(?P<grant>deny|permit)* + \s*(?P<ethertype_params>(dsap\s\S+)|bpdu|eii-ipx|ipx|mpls-unicast|mpls-multicast|isis|any\s)* + \s*(?P<std_dest>(host\s\S+)|any4|(?:[0-9]{1,3}\.){3}[0-9]{1,3}\s(?:[0-9]{1,3}\.){3}[0-9]{1,3})* + \s*(?P<protocol>ah|eigrp|esp|gre|icmp|icmp6|igmp|igrp|ip|ipinip|ipsec|nos|ospf|pcp|pim|pptp|sctp|snp|tcp|udp)* + \s*(?P<protocol_num>\d+\s)* + \s*(?P<source>any4|any6|any|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\s([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})|(([a-f0-9:]+:+)+[a-f0-9]+\S+|host\s(([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})|(([a-f0-9:]+:+)+[a-f0-9]+)\S+)|interface\s\S+|object-group\s\S+))* + \s*(?P<source_port_protocol>(eq|gts|lt|neq)\s(\S+|\d+)|range\s\S+\s\S+)* + \s*(?P<destination>any4|any6|any|([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})\s([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})|(([a-f0-9:]+:+)+[a-f0-9]+\S+|host\s(([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})|(([a-f0-9:]+:+)+[a-f0-9]+)\S+)|interface\s\S+|object-group\s\S+))* + \s*(?P<dest_svc_object_group>object-group\s\S+)* + \s*(?P<dest_port_protocol>(eq|gts|lt|neq)\s(\S+|\d+)|range\s\S+\s\S+)* + \s*(?P<icmp_icmp6_protocol>alternate-address|conversion-error|echo|echo-reply|information-reply|information-request|mask-reply|mask-request|membership-query|membership-reduction|membership-report|mobile-redirect|neighbor-advertisement|neighbor-redirect|neighbor-solicitation|parameter-problem|packet-too-big|redirect|router-advertisement|router-renumbering|router-solicitation|source-quench|source-route-failed|time-exceeded|timestamp-reply|timestamp-request|traceroute|unreachable)* + \s*(?P<log>log\s\S+)* + \s*(?P<time_range>time-range\s\S+)* + \s*(?P<inactive>inactive)* + """, + re.VERBOSE, + ), + "setval": _tmplt_access_list_entries, + "result": { + "acls": { + "{{ acl_name }}": { + "name": "{{ acl_name }}", + "acl_type": "{{ acl_type if acl_type is defined }}", + "aces": [ + { + "grant": "{{ grant }}", + "line": "{{ line.split(' ')[1] if line is defined }}", + "remark": "{{ remark.split('remark ')[1] if remark is defined }}", + "protocol": "{{ protocol if protocol is defined else None }}", + "protocol_number": "{{ protocol_num if protocol_num is defined }}", + "icmp_icmp6_protocol": "{{ icmp_icmp6_protocol if icmp_icmp6_protocol is defined else None }}", + "source": { + "address": "{% if source is defined and '.' in source and 'host'\ + not in source and 'object-group' not in source %}{{ source.split(' ')[0] }}{% elif source is defined and\ + '::' in source and 'host' not in source %}{{ source }}{% endif %}", + "netmask": "{{ source.split(' ')[1] if source\ + is defined and '.' in source and 'host' not in source else None and 'object-group' not in source }}", + "any4": "{{ True if source is defined and source == 'any4' else None }}", + "any6": "{{ True if source is defined and source == 'any6' else None }}", + "any": "{{ True if source is defined and source == 'any' else None }}", + "host": "{{ source.split(' ')[1] if source is defined and 'host' in source else None }}", + "interface": "{{ source.split(' ')[1] if source is defined and 'interface' in source else None }}", + "object_group": "{{ source.split(' ')[1] if source is defined and 'object-group' in source else None }}", + "port_protocol": { + "{{ source_port_protocol.split(' ')[0] if source_port_protocol\ + is defined and 'range' not in source_port_protocol else None }}": "{{ source_port_protocol.split(' ')[1]\ + if source_port_protocol is defined and 'range' not in source_port_protocol else None }}", + "{{ 'range' }}": { + "start": "{{ source_port_protocol.split(' ')[1] if source_port_protocol is defined and\ + 'range' in source_port_protocol else None }}", + "end": "{{ source_port_protocol.split(' ')[2] if source_port_protocol is defined and\ + 'range' in source_port_protocol else None }}", + }, + }, + }, + "destination": { + "address": "{% if destination is defined and 'host' not in destination and\ + '.' in destination and\ + 'object-group' not in destination %}{{ destination.split(' ')[0] }}{% elif std_dest is defined and\ + '.' in std_dest and 'host' not in std_dest %}{{ std_dest.split(' ')[0] }}{% elif destination is defined and\ + '::' in destination %}{{ destination }}{% endif %}", + "netmask": "{% if destination is defined and 'host' not in destination and\ + '.' in destination and\ + 'object-group' not in destination %}{{ destination.split(' ')[1] }}{% elif std_dest is defined and\ + '.' in std_dest and 'host' not in std_dest %}{{ std_dest.split(' ')[1] }}{% endif %}", + "any4": "{% if destination is defined and\ + destination == 'any4' %}{{ True }}{% elif std_dest is defined and std_dest == 'any4' %}{{ True }}{% endif %}", + "any6": "{{ True if destination is defined and destination == 'any6' else None }}", + "any": "{{ True if destination is defined and destination == 'any' else None }}", + "host": "{% if destination is defined and\ + 'host' in destination %}{{ destination.split(' ')[1] }}{% elif std_dest is defined and\ + 'host' in std_dest %}{{ std_dest.split(' ')[1] }}{% endif %}", + "interface": "{{ destination.split(' ')[1] if destination is defined and 'interface' in destination else None }}", + "object_group": "{{ destination.split(' ')[1] if destination is defined and 'object-group' in destination else None }}", + "service_object_group": "{{ dest_svc_object_group.split('object-group ')[1] if dest_svc_object_group is defined }}", + "port_protocol": { + "{{ dest_port_protocol.split(' ')[0] if dest_port_protocol\ + is defined and 'range' not in dest_port_protocol else None }}": "{{ dest_port_protocol.split(' ')[1]\ + if dest_port_protocol is defined and 'range' not in dest_port_protocol else None }}", + "{{ 'range' }}": { + "start": "{{ dest_port_protocol.split(' ')[1] if dest_port_protocol is defined and\ + 'range' in dest_port_protocol }}", + "end": "{{ dest_port_protocol.split(' ')[2] if dest_port_protocol is defined and\ + 'range' in dest_port_protocol }}", + }, + }, + }, + "inactive": "{{ True if inactive is defined }}", + "log": "{{ log.split('log ')[1] if log is defined }}", + "time_range": "{{ time_range if time_range is defined }}", + }, + ], + }, + }, + }, + }, + ] diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/ogs.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/ogs.py new file mode 100644 index 000000000..26a032478 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/rm_templates/ogs.py @@ -0,0 +1,538 @@ +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( + NetworkTemplate, +) + + +def _tmplt_object_group(config_data): + command = "object-group {object_type} {name}".format(**config_data) + return command + + +def _tmplt_icmp_object(config_data): + commands = [] + if config_data.get("icmp_type").get("icmp_object"): + for each in config_data.get("icmp_type").get("icmp_object"): + commands.append("icmp-object {0}".format(each)) + return commands + + +def _tmplt_network_object(config_data): + commands = [] + if config_data.get("network_object").get("host"): + for each in config_data.get("network_object").get("host"): + commands.append("network-object host {0}".format(each)) + return commands + + +def _tmplt_network_object_address(config_data): + commands = [] + if config_data.get("network_object").get("address"): + for each in config_data.get("network_object").get("address"): + commands.append("network-object {0}".format(each)) + return commands + + +def _tmplt_network_object_ipv6(config_data): + commands = [] + if config_data.get("network_object").get("ipv6_address"): + for each in config_data.get("network_object").get("ipv6_address"): + commands.append("network-object {0}".format(each)) + return commands + + +def _tmplt_network_object_object(config_data): + commands = [] + if config_data.get("network_object").get("object"): + for each in config_data.get("network_object").get("object"): + commands.append("network-object object {0}".format(each)) + return commands + + +def _tmplt_protocol_object(config_data): + commands = [] + if config_data.get("protocol_object").get("protocol"): + for each in config_data.get("protocol_object").get("protocol"): + commands.append("protocol {0}".format(each)) + return commands + + +def _tmplt_sec_group_name(config_data): + commands = [] + if config_data.get("security_group").get("sec_name"): + for each in config_data.get("security_group").get("sec_name"): + commands.append("security-group name {0}".format(each)) + return commands + + +def _tmplt_sec_group_tag(config_data): + commands = [] + if config_data.get("security_group").get("tag"): + for each in config_data.get("security_group").get("tag"): + commands.append("security-group tag {0}".format(each)) + return commands + + +def _tmplt_service_object(config_data): + if config_data.get("service_object").get("protocol"): + commands = [] + for each in config_data.get("service_object").get("protocol"): + commands.append("service-object {0}".format(each)) + return commands + + +def _tmplt_services_object(config_data): + if config_data.get("services_object"): + cmd = "service-object {protocol}".format(**config_data["services_object"]) + if config_data["services_object"].get("source_port"): + if config_data["services_object"]["source_port"].get("range"): + cmd += " source range {start} {end}".format( + **config_data["services_object"]["source_port"]["range"] + ) + else: + key = list(config_data["services_object"]["source_port"])[0] + cmd += " source {0} {1}".format( + key, + config_data["services_object"]["source_port"][key], + ) + if config_data["services_object"].get("destination_port"): + if config_data["services_object"]["destination_port"].get("range"): + cmd += " destination range {start} {end}".format( + **config_data["services_object"]["destination_port"]["range"] + ) + else: + key = list(config_data["services_object"]["destination_port"])[0] + cmd += " destination {0} {1}".format( + key, + config_data["services_object"]["destination_port"][key], + ) + return cmd + + +def _tmplt_port_object(config_data): + if config_data.get("port_object"): + cmd = "port-object" + if config_data["port_object"].get("range"): + cmd += " range {start} {end}".format(**config_data["port_object"]["range"]) + else: + key = list(config_data["port_object"])[0] + cmd += " {0} {1}".format(key, config_data["port_object"][key]) + return cmd + + +def _tmplt_user_object_user(config_data): + commands = [] + if config_data.get("user_object").get("user"): + for each in config_data.get("user_object").get("user"): + commands.append("user {domain}\\{name}".format(**each)) + return commands + + +def _tmplt_user_object_user_gp(config_data): + commands = [] + if config_data.get("user_object").get("user_group"): + for each in config_data.get("user_object").get("user_group"): + commands.append(r"user-group {domain}\\{name}".format(**each)) + return commands + + +def _tmplt_group_object(config_data): + command = "group-object {group_object}".format(**config_data) + return command + + +class OGsTemplate(NetworkTemplate): + def __init__(self, lines=None): + super(OGsTemplate, self).__init__(lines=lines, tmplt=self) + + PARSERS = [ + { + "name": "og_name", + "getval": re.compile( + r""" + ^object-group* + \s*(?P<obj_type>\S+)* + \s*(?P<obj_name>\S+)* + \s*(?P<protocol>\S+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_object_group, + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": { + "object_type": "{{ obj_type }}", + "name": "{{ obj_name }}", + "protocol": "{{ protocol }}", + }, + }, + }, + }, + "shared": True, + }, + { + "name": "description", + "getval": re.compile( + r"""\s+description:* + \s*(?P<description>.+) + *$""", + re.VERBOSE, + ), + "setval": "description {{ description }}", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"description": "{{ description }}"}, + }, + }, + }, + }, + { + "name": "icmp_type", + "getval": re.compile( + r"""\s+icmp-object* + \s*(?P<object>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_icmp_object, + "compval": "icmp_type", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"icmp_object": ["{{ object }}"]}, + }, + }, + }, + }, + { + "name": "network_object.address", + "getval": re.compile( + r"""\s+network-object* + \s*(?P<address>\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\s\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_network_object_address, + "compval": "network_object.address", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"address": ["{{ address }}"]}, + }, + }, + }, + }, + { + "name": "network_object.ipv6_address", + "getval": re.compile( + r"""\s+network-object* + \s*(?P<ipv6>\S+::/\d+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_network_object_ipv6, + "compval": "network_object.ipv6_address", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"ipv6_address": ["{{ ipv6 }}"]}, + }, + }, + }, + }, + { + "name": "network_object.host", + "getval": re.compile( + r"""\s+network-object* + \s*(?P<host_obj>host)* + \s*(?P<host_address>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_network_object, + "compval": "network_object.host", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"host": ["{{ host_address }}"]}, + }, + }, + }, + }, + { + "name": "network_object.object", + "getval": re.compile( + r"""\s+network-object\s + object* + \s*(?P<object>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_network_object_object, + "compval": "network_object.object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"object": ["{{ object }}"]}, + }, + }, + }, + }, + { + "name": "protocol_object", + "getval": re.compile( + r"""\s+protocol-object* + \s*(?P<protocol>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_protocol_object, + "compval": "protocol_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"protocol": ["{{ protocol }}"]}, + }, + }, + }, + }, + { + "name": "security_group.sec_name", + "getval": re.compile( + r"""\s+security-group\s + name* + \s*(?P<name>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_sec_group_name, + "compval": "security_group.sec_name", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"sec_name": ["{{ name }}"]}, + }, + }, + }, + }, + { + "name": "security_group.tag", + "getval": re.compile( + r"""\s+security-group\s + tag* + \s*(?P<tag>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_sec_group_tag, + "compval": "security_group.tag", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"tag": ["{{ tag }}"]}, + }, + }, + }, + }, + { + "name": "port_object", + "getval": re.compile( + r"""\s+port-object* + \s*(?P<eq>eq\s\S+)* + \s*(?P<range>range\s(\S+|\d+)\s(\S+|\d+)) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_port_object, + "compval": "port_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": { + "port_object": [ + { + "eq": "{{ eq.split(' ')[1] if eq is defined }}", + "range": { + "start": "{{ range.split('range ')[1].split(' ')[0] if range is defined else None }}", + "end": "{{ range.split('range ')[1].split(' ')[1] if range is defined else None }}", + }, + }, + ], + }, + }, + }, + }, + }, + { + "name": "services_object", + "getval": re.compile( + r"""\s+service-object* + \s*(?P<protocol>\S+)* + \s*(?P<source_port>source\s((eq|gts|lt|neq)\s(\S+|\d+)|(range\s(\S+|\S+)\s(\S+|\S+))))* + \s*(?P<destination_port>destination\s((eq|gt|lt|neq)\s(\S+|\d+)|(range\s(\S+|\S+)\s(\S+|\S+)))) + *""", + re.VERBOSE, + ), + "setval": _tmplt_services_object, + "compval": "services_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": { + "services_object": [ + { + "protocol": "{{ protocol }}", + "source_port": { + "eq": "{{ source_port.split(' ')[2] if source_port is defined and\ + 'eq' in source_port and 'range' not in source_port }}", + "gt": "{{ source_port.split(' ')[2] if source_port is defined and\ + 'gt' in source_port and 'range' not in source_port }}", + "lt": "{{ source_port.split(' ')[2] if source_port is defined and\ + 'lt' in source_port and 'range' not in source_port }}", + "neq": "{{ source_port.split(' ')[2] if source_port is defined and\ + 'neq' in source_port and 'range' not in source_port }}", + "range": { + "start": "{{ source_port.split('range ')[1].split(' ')[0] if source_port is defined and\ + 'range' in source_port else None }}", + "end": "{{ source_port.split('range ')[1].split(' ')[1] if source_port is defined and\ + 'range' in source_port else None }}", + }, + }, + "destination_port": { + "eq": "{{ destination_port.split(' ')[2] if destination_port is defined and\ + 'eq' in destination_port and 'range' not in destination_port }}", + "gt": "{{ destination_port.split(' ')[2] if destination_port is defined and\ + 'gt' in destination_port and 'range' not in destination_port }}", + "lt": "{{ destination_port.split(' ')[2] if destination_port is defined and\ + 'lt' in destination_port and 'range' not in destination_port }}", + "neq": "{{ destination_port.split(' ')[2] if destination_port is defined and\ + 'neq' in destination_port and 'range' not in destination_port }}", + "range": { + "start": "{{ destination_port.split('range ')[1].split(' ')[0] if destination_port is defined and\ + 'range' in destination_port else None }}", + "end": "{{ destination_port.split('range ')[1].split(' ')[1] if destination_port is defined and\ + 'range' in destination_port else None }}", + }, + }, + }, + ], + }, + }, + }, + }, + }, + { + "name": "service_object.object", + "getval": re.compile( + r"""\s+service-object\s + object* + \s*(?P<object>\S+) + *$""", + re.VERBOSE, + ), + "setval": "service-object object {{ object }}", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"object": "{{ object }}"}, + }, + }, + }, + }, + { + "name": "service_object", + "getval": re.compile( + r"""\s+service-object* + \s*(?P<protocol>\S+)*\s + *$""", + re.VERBOSE, + ), + "setval": _tmplt_service_object, + "compval": "service_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"protocol": ["{{ protocol }}"]}, + }, + }, + }, + }, + { + "name": "user_object.user", + "getval": re.compile( + r"""\s+user* + \s*(?P<domain>\S+)\\ + (?P<user_name>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_user_object_user, + "compval": "user_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": { + "user": [ + { + "name": "{{ user_name }}", + "domain": "{{ domain }}", + }, + ], + }, + }, + }, + }, + }, + { + "name": "user_object.user_gp", + "getval": re.compile( + r"""\s+user-group* + \s*(?P<domain>\S+\\) + (?P<user_gp>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_user_object_user_gp, + "compval": "user_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": { + "user_group": [ + { + "name": "{{ user_gp }}", + "domain": r"{{ domain.split('\\')[0] }}", + }, + ], + }, + }, + }, + }, + }, + { + "name": "group_object", + "getval": re.compile( + r"""\s+group-object* + \s*(?P<gp_obj>\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_group_object, + "compval": "group_object", + "result": { + "ogs": { + "{{ obj_type }}": { + "{{ obj_name }}": {"group_object": ["{{ gp_obj }}"]}, + }, + }, + }, + }, + ] diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/__init__.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/__init__.py new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/__init__.py diff --git a/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/utils.py b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/utils.py new file mode 100644 index 000000000..c5399db00 --- /dev/null +++ b/ansible_collections/cisco/asa/plugins/module_utils/network/asa/utils/utils.py @@ -0,0 +1,320 @@ +# +# -*- 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) + +# utils + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +import socket + +from ansible.module_utils.common.network import is_masklen, to_netmask +from ansible.module_utils.six import iteritems + + +def remove_duplicate_cmd(cmd, commands): + # Remove duplicate interface from commands + set_cmd = [] + for each in commands: + if cmd in each: + if each not in set_cmd: + set_cmd.append(each) + else: + set_cmd.append(each) + + return set_cmd + + +def remove_command_from_config_list(interface, cmd, commands): + # To delete the passed config + if interface not in commands: + commands.insert(0, interface) + commands.append("no %s" % cmd) + return commands + + +def add_command_to_config_list(interface, cmd, commands): + # To set the passed config + if interface not in commands: + commands.insert(0, interface) + commands.append(cmd) + + +def check_n_return_valid_ipv6_addr(module, input_list, filtered_ipv6_list): + # To verify the valid ipv6 address + try: + for each in input_list: + if "::" in each: + if "/" in each: + each = each.split("/")[0] + if socket.inet_pton(socket.AF_INET6, each): + filtered_ipv6_list.append(each) + return filtered_ipv6_list + except socket.error: + module.fail_json(msg="Incorrect IPV6 address!") + + +def new_dict_to_set(input_dict, temp_list, test_set, count=0): + # recursive function to convert input dict to set for comparision + test_dict = dict() + if isinstance(input_dict, dict): + input_dict_len = len(input_dict) + for k, v in sorted(iteritems(input_dict)): + count += 1 + if isinstance(v, list): + temp_list.append(k) + for each in v: + if isinstance(each, dict): + if [True for i in each.values() if type(i) == list]: + new_dict_to_set(each, temp_list, test_set, count) + else: + new_dict_to_set(each, temp_list, test_set, 0) + else: + if v is not None: + test_dict.update({k: v}) + try: + if tuple(iteritems(test_dict)) not in test_set and count == input_dict_len: + test_set.add(tuple(iteritems(test_dict))) + count = 0 + except TypeError: + temp_dict = {} + + def expand_dict(dict_to_expand): + temp = dict() + for k, v in iteritems(dict_to_expand): + if isinstance(v, dict): + expand_dict(v) + else: + if v is not None: + temp.update({k: v}) + temp_dict.update(tuple(iteritems(temp))) + + new_dict = {k: v} + expand_dict(new_dict) + if tuple(iteritems(temp_dict)) not in test_set: + test_set.add(tuple(iteritems(temp_dict))) + + +def dict_to_set(sample_dict): + # Generate a set with passed dictionary for comparison + test_dict = dict() + if isinstance(sample_dict, dict): + for k, v in iteritems(sample_dict): + if v is not None: + if isinstance(v, list): + if isinstance(v[0], dict): + li = [] + for each in v: + for key, value in iteritems(each): + if isinstance(value, list): + each[key] = tuple(value) + li.append(tuple(iteritems(each))) + v = tuple(li) + else: + v = tuple(v) + elif isinstance(v, dict): + li = [] + for key, value in iteritems(v): + if isinstance(value, list): + v[key] = tuple(value) + li.extend(tuple(iteritems(v))) + v = tuple(li) + test_dict.update({k: v}) + return_set = set(tuple(iteritems(test_dict))) + else: + return_set = set(sample_dict) + return return_set + + +def filter_dict_having_none_value(want, have): + # Generate dict with have dict value which is None in want dict + test_dict = dict() + test_key_dict = dict() + name = want.get("name") + if name: + test_dict["name"] = name + diff_ip = False + want_ip = "" + for k, v in iteritems(want): + if isinstance(v, dict): + for key, value in iteritems(v): + if value is None: + dict_val = have.get(k).get(key) + test_key_dict.update({key: dict_val}) + test_dict.update({k: test_key_dict}) + if isinstance(v, list): + for key, value in iteritems(v[0]): + if value is None: + dict_val = have.get(k).get(key) + test_key_dict.update({key: dict_val}) + test_dict.update({k: test_key_dict}) + # below conditions checks are added to check if + # secondary IP is configured, if yes then delete + # the already configured IP if want and have IP + # is different else if it's same no need to delete + for each in v: + if each.get("secondary"): + want_ip = each.get("address").split("/") + have_ip = have.get("ipv4") + if len(want_ip) > 1 and have_ip and have_ip[0].get("secondary"): + have_ip = have_ip[0]["address"].split(" ")[0] + if have_ip != want_ip[0]: + diff_ip = True + if each.get("secondary") and diff_ip is True: + test_key_dict.update({"secondary": True}) + test_dict.update({"ipv4": test_key_dict}) + if v is None: + val = have.get(k) + test_dict.update({k: val}) + return test_dict + + +def remove_duplicate_interface(commands): + # Remove duplicate interface from commands + set_cmd = [] + for each in commands: + if "interface" in each: + if each not in set_cmd: + set_cmd.append(each) + else: + set_cmd.append(each) + + return set_cmd + + +def validate_ipv4(value, module): + if value: + address = value.split("/") + if len(address) != 2: + module.fail_json( + msg="address format is <ipv4 address>/<mask>, got invalid format {0}".format( + value, + ), + ) + + if not is_masklen(address[1]): + module.fail_json( + msg="invalid value for mask: {0}, mask should be in range 0-32".format( + address[1], + ), + ) + + +def validate_ipv6(value, module): + if value: + address = value.split("/") + if len(address) != 2: + module.fail_json( + msg="address format is <ipv6 address>/<mask>, got invalid format {0}".format( + value, + ), + ) + else: + if not 0 <= int(address[1]) <= 128: + module.fail_json( + msg="invalid value for mask: {0}, mask should be in range 0-128".format( + address[1], + ), + ) + + +def validate_n_expand_ipv4(module, want): + # Check if input IPV4 is valid IP and expand IPV4 with its subnet mask + ip_addr_want = want.get("address") + if len(ip_addr_want.split(" ")) > 1: + return ip_addr_want + validate_ipv4(ip_addr_want, module) + ip = ip_addr_want.split("/") + if len(ip) == 2: + ip_addr_want = "{0} {1}".format(ip[0], to_netmask(ip[1])) + + return ip_addr_want + + +def normalize_interface(name): + """Return the normalized interface name""" + if not name: + return + + def _get_number(name): + digits = "" + for char in name: + if char.isdigit() or char in "/.": + digits += char + return digits + + if name.lower().startswith("gi"): + if_type = "GigabitEthernet" + elif name.lower().startswith("te"): + if_type = "TenGigabitEthernet" + elif name.lower().startswith("fa"): + if_type = "FastEthernet" + elif name.lower().startswith("fo"): + if_type = "FortyGigabitEthernet" + elif name.lower().startswith("long"): + if_type = "LongReachEthernet" + elif name.lower().startswith("et"): + if_type = "Ethernet" + elif name.lower().startswith("vl"): + if_type = "Vlan" + elif name.lower().startswith("lo"): + if_type = "loopback" + elif name.lower().startswith("po"): + if_type = "Port-channel" + elif name.lower().startswith("nv"): + if_type = "nve" + elif name.lower().startswith("twe"): + if_type = "TwentyFiveGigE" + elif name.lower().startswith("hu"): + if_type = "HundredGigE" + else: + if_type = None + + number_list = name.split(" ") + if len(number_list) == 2: + number = number_list[-1].strip() + else: + number = _get_number(name) + + if if_type: + proper_interface = if_type + number + else: + proper_interface = name + + return proper_interface + + +def get_interface_type(interface): + """Gets the type of interface""" + + if interface.upper().startswith("GI"): + return "GigabitEthernet" + elif interface.upper().startswith("TE"): + return "TenGigabitEthernet" + elif interface.upper().startswith("FA"): + return "FastEthernet" + elif interface.upper().startswith("FO"): + return "FortyGigabitEthernet" + elif interface.upper().startswith("LON"): + return "LongReachEthernet" + elif interface.upper().startswith("ET"): + return "Ethernet" + elif interface.upper().startswith("VL"): + return "Vlan" + elif interface.upper().startswith("LO"): + return "loopback" + elif interface.upper().startswith("PO"): + return "Port-channel" + elif interface.upper().startswith("NV"): + return "nve" + elif interface.upper().startswith("TWE"): + return "TwentyFiveGigE" + elif interface.upper().startswith("HU"): + return "HundredGigE" + else: + return "unknown" |