Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,56 @@
#
# (c) 2016 Red Hat Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.ansible.netcommon.plugins.action.network import (
ActionModule as ActionNetworkModule,
)
from ansible.utils.display import Display
display = Display()
class ActionModule(ActionNetworkModule):
def run(self, tmp=None, task_vars=None):
del tmp # tmp no longer has any effect
module_name = self._task.action.split(".")[-1]
self._config_module = (
True if module_name in ["asa_config", "config"] else False
)
persistent_connection = self._play_context.connection.split(".")[-1]
warnings = []
if persistent_connection not in ("network_cli"):
return {
"failed": True,
"msg": "Connection type %s is not valid for this module"
% self._play_context.connection,
}
result = super(ActionModule, self).run(task_vars=task_vars)
if warnings:
if "warnings" in result:
result["warnings"].extend(warnings)
else:
result["warnings"] = warnings
return result

View File

@@ -0,0 +1,168 @@
#
# (c) 2017 Red Hat Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
---
author: Ansible Security Team (@ansible-security)
name: asa
short_description: Use asa cliconf to run command on Cisco ASA platform
description:
- This asa plugin provides low level abstraction apis for sending and receiving CLI
commands from Cisco ASA network devices.
version_added: 1.0.0
options:
config_commands:
description:
- Specifies a list of commands that can make configuration changes
to the target device.
- When `ansible_network_single_user_mode` is enabled, if a command sent
to the device is present in this list, the existing cache is invalidated.
version_added: 2.0.0
type: list
elements: str
default: []
vars:
- name: ansible_asa_config_commands
"""
import re
import json
from itertools import chain
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text
from ansible.module_utils.common._collections_compat import Mapping
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
to_list,
)
from ansible_collections.ansible.netcommon.plugins.plugin_utils.cliconf_base import (
CliconfBase,
enable_mode,
)
class Cliconf(CliconfBase):
def __init__(self, *args, **kwargs):
super(Cliconf, self).__init__(*args, **kwargs)
self._device_info = {}
def get_device_info(self):
if not self._device_info:
device_info = {}
device_info["network_os"] = "asa"
reply = self.get("show version")
data = to_text(reply, errors="surrogate_or_strict").strip()
match = re.search(r"Version (\S+)", data)
if match:
device_info["network_os_version"] = match.group(1)
match = re.search(r"Firepower .+ Version (\S+)", data)
if match:
device_info["network_os_firepower_version"] = match.group(1)
match = re.search(r"Device .+ Version (\S+)", data)
if match:
device_info["network_os_device_mgr_version"] = match.group(1)
match = re.search(r"^Model Id:\s+(.+) \(revision", data, re.M)
if match:
device_info["network_os_model"] = match.group(1)
match = re.search(r"^(.+) up", data, re.M)
if match:
device_info["network_os_hostname"] = match.group(1)
match = re.search(r'image file is "(.+)"', data)
if match:
device_info["network_os_image"] = match.group(1)
self._device_info = device_info
return self._device_info
@enable_mode
def get_config(self, source="running", flags=None, format="text"):
if source not in ("running", "startup"):
return self.invalid_params(
"fetching configuration from %s is not supported" % source
)
if source == "running":
cmd = "show running-config all"
else:
cmd = "show startup-config"
return self.send_command(cmd)
@enable_mode
def edit_config(self, command):
for cmd in chain(["configure terminal"], to_list(command), ["end"]):
self.send_command(cmd)
def get(
self,
command,
prompt=None,
answer=None,
sendonly=False,
newline=True,
check_all=False,
):
return self.send_command(
command=command,
prompt=prompt,
answer=answer,
sendonly=sendonly,
newline=newline,
check_all=check_all,
)
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
return json.dumps(result)
def run_commands(self, commands=None, check_rc=True):
if commands is None:
raise ValueError("'commands' value is required")
responses = list()
for cmd in to_list(commands):
if not isinstance(cmd, Mapping):
cmd = {"command": cmd}
output = cmd.pop("output", None)
if output:
raise ValueError(
"'output' value %s is not supported for run_commands"
% output
)
try:
out = self.send_command(**cmd)
except AnsibleConnectionFailure as e:
if check_rc:
raise
out = getattr(e, "err", to_text(e))
responses.append(out)
return responses

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
__metaclass__ = type
# Copyright: (c) 2016, Peter Sprygada <psprygada@ansible.com>
# Copyright: (c) 2016, Patrick Ogenstad <@ogenstad>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
class ModuleDocFragment(object):
# Standard files documentation fragment
DOCUMENTATION = r"""options:
context:
description:
- Specifies which context to target if you are running in the ASA in multiple
context mode. Defaults to the current context you login to.
type: str
passwords:
description:
- Saves running-config passwords in clear-text when set to True.
Defaults to False
type: bool
notes:
- For more information on using Ansible to manage network devices see the :ref:`Ansible
Network Guide <network_guide>`
"""

View File

@@ -0,0 +1,292 @@
# -*- 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",
},
}

View File

@@ -0,0 +1,26 @@
#
# -*- 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"),
}

View File

@@ -0,0 +1,260 @@
# -*- 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",
},
}

View File

@@ -0,0 +1,155 @@
# 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_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
EntityCollection,
)
from ansible.module_utils.connection import exec_command
from ansible.module_utils.connection import Connection, ConnectionError
_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"

View File

@@ -0,0 +1,230 @@
#
# -*- 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.cisco.asa.plugins.module_utils.network.asa.facts.facts import (
Facts,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.cisco.asa.plugins.module_utils.network.asa.rm_templates.acls import (
AclsTemplate,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.resource_module import (
ResourceModule,
)
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}
)

View File

@@ -0,0 +1,702 @@
#
# -*- 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.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,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.resource_module import (
ResourceModule,
)
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)

View File

@@ -0,0 +1,105 @@
#
# -*- 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.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,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network_template import (
NetworkTemplate,
)
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

View File

@@ -0,0 +1,66 @@
# -*- 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.ogs.ogs import (
OGsFacts,
)
from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.legacy.base import (
Default,
Hardware,
Config,
)
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

View File

@@ -0,0 +1,186 @@
# -*- 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 (
run_commands,
get_capabilities,
)
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

View File

@@ -0,0 +1,103 @@
# -*- 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.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,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network_template import (
NetworkTemplate,
)
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

View File

@@ -0,0 +1,73 @@
#
# (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.basic import AnsibleModule
from ansible.module_utils.connection import Connection
from ansible_collections.cisco.asa.plugins.module_utils.network.asa.providers import (
providers,
)
from ansible.module_utils._text import to_text
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))

View File

@@ -0,0 +1,129 @@
#
# (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.utils import (
to_list,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
NetworkConfig,
)
_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

View File

@@ -0,0 +1,268 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.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 }}",
}
],
}
}
},
},
]

View File

@@ -0,0 +1,543 @@
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.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 }}"]}
}
}
},
},
]

View File

@@ -0,0 +1,328 @@
#
# -*- 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.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
is_masklen,
to_netmask,
)
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"

View File

@@ -0,0 +1,197 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# 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
DOCUMENTATION = """
module: asa_command
author: Peter Sprygada (@privateip), Patrick Ogenstad (@ogenstad)
short_description: Run arbitrary commands on Cisco ASA devices
description:
- Sends arbitrary commands to an ASA node and returns the results read from the device.
The C(asa_command) module includes an argument that will cause the module to wait
for a specific condition before returning or timing out if the condition is not
met.
version_added: 1.0.0
extends_documentation_fragment:
- cisco.asa.asa
options:
commands:
description:
- List of commands to send to the remote device over the configured provider.
The resulting output from the command is returned. If the I(wait_for) argument
is provided, the module is not returned until the condition is satisfied or
the number of retires as expired.
required: true
type: list
elements: str
wait_for:
description:
- List of conditions to evaluate against the output of the command. The task will
wait for each condition to be true before moving forward. If the conditional
is not true within the configured number of retries, the task fails. See examples.
aliases:
- waitfor
type: list
elements: str
match:
description:
- The I(match) argument is used in conjunction with the I(wait_for) argument to
specify the match policy. Valid values are C(all) or C(any). If the value
is set to C(all) then all conditionals in the wait_for must be satisfied. If
the value is set to C(any) then only one of the values must be satisfied.
default: all
choices:
- any
- all
type: str
retries:
description:
- Specifies the number of retries a command should by tried before it is considered
failed. The command is run on the target device every retry and evaluated against
the I(wait_for) conditions.
default: 10
type: int
interval:
description:
- Configures the interval in seconds to wait between retries of the command. If
the command does not pass the specified conditions, the interval indicates how
long to wait before trying the command again.
default: 1
type: int
notes:
- When processing wait_for, each commands' output is stored as an element of the I(result)
array. The allowed operators for conditional evaluation are I(eq), I(==), I(neq),
I(ne), I(!=), I(gt), I(>), I(ge), I(>=), I(lt), I(<), I(le), I(<=), I(contains),
I(matches). Operators can be prefaced by I(not) to negate their meaning. The I(contains)
operator searches for a substring match (like the Python I(in) operator). The I(matches)
operator searches using a regex search operation.
"""
EXAMPLES = """
- name: Show the ASA version
cisco.asa.asa_command:
commands:
- show version
- name: Show ASA drops and memory
cisco.asa.asa_command:
commands:
- show asp drop
- show memory
- name: Send repeat pings and wait for the result to pass 100%
cisco.asa.asa_command:
commands:
- ping 8.8.8.8 repeat 20 size 350
wait_for:
- result[0] contains 100
retries: 2
"""
RETURN = """
stdout:
description: the set of responses from the commands
returned: always
type: list
sample: ['...', '...']
stdout_lines:
description: The value of stdout split into a list
returned: always
type: list
sample: [['...', '...'], ['...'], ['...']]
failed_conditions:
description: the conditionals that failed
returned: failed
type: list
sample: ['...', '...']
"""
import time
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import (
asa_argument_spec,
check_args,
)
from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import (
run_commands,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import (
Conditional,
)
from ansible.module_utils.six import string_types
def to_lines(stdout):
for item in stdout:
if isinstance(item, string_types):
item = str(item).split("\n")
yield item
def main():
spec = dict(
# { command: <str>, prompt: <str>, response: <str> }
commands=dict(type="list", required=True, elements="str"),
wait_for=dict(type="list", aliases=["waitfor"], elements="str"),
match=dict(default="all", choices=["all", "any"], type="str"),
retries=dict(default=10, type="int"),
interval=dict(default=1, type="int"),
)
spec.update(asa_argument_spec)
module = AnsibleModule(argument_spec=spec, supports_check_mode=True)
check_args(module)
result = {"changed": False}
wait_for = module.params["wait_for"] or list()
conditionals = [Conditional(c) for c in wait_for]
commands = module.params["commands"]
retries = module.params["retries"]
interval = module.params["interval"]
match = module.params["match"]
while retries > 0:
responses = run_commands(module, commands)
for item in list(conditionals):
if item(responses):
if match == "any":
conditionals = list()
break
conditionals.remove(item)
if not conditionals:
break
time.sleep(interval)
retries -= 1
if conditionals:
failed_conditions = [item.raw for item in conditionals]
msg = "One or more conditional statements have not be satisfied"
module.fail_json(msg=msg, failed_conditions=failed_conditions)
result.update(
{
"changed": False,
"stdout": responses,
"stdout_lines": list(to_lines(responses)),
}
)
module.exit_json(**result)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,406 @@
#!/usr/bin/python
#
# Copyright: Ansible Project
# 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
DOCUMENTATION = """
module: asa_config
author: Peter Sprygada (@privateip), Patrick Ogenstad (@ogenstad)
short_description: Manage configuration sections on Cisco ASA devices
description:
- Cisco ASA configurations use a simple block indent file syntax for segmenting configuration
into sections. This module provides an implementation for working with ASA configuration
sections in a deterministic way.
version_added: 1.0.0
extends_documentation_fragment:
- cisco.asa.asa
options:
lines:
description:
- The ordered set of commands that should be configured in the section. The commands
must be the exact same commands as found in the device running-config. Be sure
to note the configuration command syntax as some commands are automatically
modified by the device config parser.
aliases:
- commands
type: list
elements: str
parents:
description:
- The ordered set of parents that uniquely identify the section or hierarchy the
commands should be checked against. If the parents argument is omitted, the
commands are checked against the set of top level or global commands.
type: list
elements: str
src:
description:
- Specifies the source path to the file that contains the configuration or configuration
template to load. The path to the source file can either be the full path on
the Ansible control host or a relative path from the playbook or role root directory. This
argument is mutually exclusive with I(lines), I(parents).
type: path
before:
description:
- The ordered set of commands to push on to the command stack if a change needs
to be made. This allows the playbook designer the opportunity to perform configuration
commands prior to pushing any changes without affecting how the set of commands
are matched against the system.
type: list
elements: str
after:
description:
- The ordered set of commands to append to the end of the command stack if a change
needs to be made. Just like with I(before) this allows the playbook designer
to append a set of commands to be executed after the command set.
type: list
elements: str
match:
description:
- Instructs the module on the way to perform the matching of the set of commands
against the current device config. If match is set to I(line), commands are
matched line by line. If match is set to I(strict), command lines are matched
with respect to position. If match is set to I(exact), command lines must be
an equal match. Finally, if match is set to I(none), the module will not attempt
to compare the source configuration with the running configuration on the remote
device.
default: line
choices:
- line
- strict
- exact
- none
type: str
replace:
description:
- Instructs the module on the way to perform the configuration on the device. If
the replace argument is set to I(line) then the modified lines are pushed to
the device in configuration mode. If the replace argument is set to I(block)
then the entire command block is pushed to the device in configuration mode
if any line is not correct
default: line
choices:
- line
- block
type: str
backup:
description:
- This argument will cause the module to create a full backup of the current C(running-config)
from the remote device before any changes are made. If the C(backup_options)
value is not given, the backup file is written to the C(backup) folder in the
playbook root directory. If the directory does not exist, it is created.
type: bool
default: no
config:
description:
- The C(config) argument allows the playbook designer to supply the base configuration
to be used to validate configuration changes necessary. If this argument is
provided, the module will not download the running-config from the remote node.
type: str
defaults:
description:
- This argument specifies whether or not to collect all defaults when getting
the remote device running config. When enabled, the module will get the current
config by issuing the command C(show running-config all).
type: bool
default: no
passwords:
description:
- This argument specifies to include passwords in the config when retrieving the
running-config from the remote device. This includes passwords related to VPN
endpoints. This argument is mutually exclusive with I(defaults).
type: bool
save:
description:
- The C(save) argument instructs the module to save the running- config to the
startup-config at the conclusion of the module running. If check mode is specified,
this argument is ignored.
type: bool
default: no
backup_options:
description:
- This is a dict object containing configurable options related to backup file
path. The value of this option is read only when C(backup) is set to I(yes),
if C(backup) is set to I(no) this option will be silently ignored.
suboptions:
filename:
description:
- The filename to be used to store the backup configuration. If the filename
is not given it will be generated based on the hostname, current time and
date in format defined by <hostname>_config.<current-date>@<current-time>
type: str
dir_path:
description:
- This option provides the path ending with directory name in which the backup
configuration file will be stored. If the directory does not exist it will
be first created and the filename is either the value of C(filename) or
default filename as described in C(filename) options description. If the
path value is not given in that case a I(backup) directory will be created
in the current working directory and backup configuration will be copied
in C(filename) within I(backup) directory.
type: path
type: dict
save_when:
description:
- When changes are made to the device running-configuration, the changes are not
copied to non-volatile storage by default. Using this argument will change
that before. If the argument is set to I(always), then the running-config will
always be copied to the startup-config and the I(modified) flag will always
be set to True. If the argument is set to I(modified), then the running-config
will only be copied to the startup-config if it has changed since the last save
to startup-config. If the argument is set to I(never), the running-config will
never be copied to the startup-config. If the argument is set to I(changed),
then the running-config will only be copied to the startup-config if the task
has made a change. I(changed) was added in Ansible 2.5.
default: never
version_added: 1.1.0
choices:
- always
- never
- modified
- changed
type: str
"""
EXAMPLES = """
- cisco.asa.asa_config:
lines:
- network-object host 10.80.30.18
- network-object host 10.80.30.19
- network-object host 10.80.30.20
parents: [object-group network OG-MONITORED-SERVERS]
- cisco.asa.asa_config:
host: '{{ inventory_hostname }}'
lines:
- message-length maximum client auto
- message-length maximum 512
match: line
parents: [policy-map type inspect dns PM-DNS, parameters]
authorize: yes
auth_pass: cisco
username: admin
password: cisco
context: ansible
- cisco.asa.asa_config:
lines:
- ikev1 pre-shared-key MyS3cretVPNK3y
parents: tunnel-group 1.1.1.1 ipsec-attributes
passwords: yes
- name: attach ASA acl on interface vlan13/nameif cloud13
cisco.asa.asa_config:
lines:
- access-group cloud-acl_access_in in interface cloud13
- name: configure ASA (>=9.2) default BGP
cisco.asa.asa_config:
lines:
- bgp log-neighbor-changes
- bgp bestpath compare-routerid
parents:
- router bgp 65002
register: bgp
when: bgp_default_config is defined
- name: configure ASA (>=9.2) BGP neighbor in default/single context mode
cisco.asa.asa_config:
lines:
- bgp router-id {{ bgp_router_id }}
- neighbor {{ bgp_neighbor_ip }} remote-as {{ bgp_neighbor_as }}
- neighbor {{ bgp_neighbor_ip }} description {{ bgp_neighbor_name }}
parents:
- router bgp 65002
- address-family ipv4 unicast
register: bgp
when: bgp_neighbor_as is defined
- name: configure ASA interface with standby
cisco.asa.asa_config:
lines:
- description my cloud interface
- nameif cloud13
- security-level 50
- ip address 192.168.13.1 255.255.255.0 standby 192.168.13.2
parents: [interface Vlan13]
register: interface
- name: Show changes to interface from task above
ansible.builtin.debug:
var: interface
- name: configurable backup path
cisco.asa.asa_config:
lines:
- access-group cloud-acl_access_in in interface cloud13
backup: yes
backup_options:
filename: backup.cfg
dir_path: /home/user
- name: save running to startup when modified
cisco.asa.asa_config:
save_when: modified
"""
RETURN = """
updates:
description: The set of commands that will be pushed to the remote device
returned: always
type: list
sample: ['...', '...']
backup_path:
description: The full path to the backup file
returned: when backup is yes
type: str
sample: /playbooks/ansible/backup/asa_config.2016-07-16@22:28:34
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import (
asa_argument_spec,
check_args,
)
from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import (
get_config,
load_config,
run_commands,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
NetworkConfig,
dumps,
)
def get_candidate(module):
candidate = NetworkConfig(indent=1)
if module.params["src"]:
candidate.load(module.params["src"])
elif module.params["lines"]:
parents = module.params["parents"] or list()
candidate.add(module.params["lines"], parents=parents)
return candidate
def save_config(module, result):
result["changed"] = True
if not module.check_mode:
run_commands(module, "write mem")
def run(module, result):
match = module.params["match"]
replace = module.params["replace"]
path = module.params["parents"]
candidate = get_candidate(module)
if match != "none":
contents = module.params["config"]
if not contents:
contents = get_config(module)
config = NetworkConfig(indent=1, contents=contents)
configobjs = candidate.difference(
config, path=path, match=match, replace=replace
)
else:
configobjs = candidate.items
if configobjs:
commands = dumps(configobjs, "commands").split("\n")
if module.params["lines"]:
if module.params["before"]:
commands[:0] = module.params["before"]
if module.params["after"]:
commands.extend(module.params["after"])
result["updates"] = commands
# send the configuration commands to the device and merge
# them with the current running config
if not module.check_mode:
load_config(module, commands)
result["changed"] = True
if module.params["save"]:
module.warn(
"module param save is deprecated, please use newer and updated param save_when instead which is released with more functionality!"
)
save_config(module, result)
if module.params["save_when"] == "always":
save_config(module, result)
elif module.params["save_when"] == "modified":
running_config_checksum = run_commands(
module, "show running-config | include checksum:"
)
startup_config_checksum = run_commands(
module, "show startup-config | include checksum:"
)
if running_config_checksum != startup_config_checksum:
save_config(module, result)
elif module.params["save_when"] == "changed" and result["changed"]:
save_config(module, result)
def main():
"""main entry point for module execution"""
backup_spec = dict(filename=dict(), dir_path=dict(type="path"))
argument_spec = dict(
src=dict(type="path"),
lines=dict(aliases=["commands"], type="list", elements="str"),
parents=dict(type="list", elements="str"),
before=dict(type="list", elements="str"),
after=dict(type="list", elements="str"),
match=dict(
default="line", choices=["line", "strict", "exact", "none"]
),
replace=dict(default="line", choices=["line", "block"]),
backup_options=dict(type="dict", options=backup_spec),
config=dict(),
defaults=dict(type="bool", default=False),
passwords=dict(type="bool", default=False),
backup=dict(type="bool", default=False),
save=dict(type="bool", default=False),
save_when=dict(
choices=["always", "never", "modified", "changed"], default="never"
),
)
argument_spec.update(asa_argument_spec)
mutually_exclusive = [
("lines", "src"),
("parents", "src"),
("defaults", "passwords"),
]
required_if = [
("match", "strict", ["lines"]),
("match", "exact", ["lines"]),
("replace", "block", ["lines"]),
]
module = AnsibleModule(
argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
required_if=required_if,
supports_check_mode=True,
)
result = {"changed": False}
check_args(module)
if module.params["backup"]:
result["__backup__"] = get_config(module)
run(module, result)
module.exit_json(**result)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,210 @@
#!/usr/bin/python
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: asa_facts
author:
- Sumit Jaiswal (@justjais)
short_description: Collect facts from remote devices running Cisco ASA
description:
- Collects a base set of device facts from a remote device that is running ASA. This
module prepends all of the base network fact keys with C(ansible_net_<fact>). The
facts module will always collect a base set of facts from the device and can enable
or disable collection of additional facts.
- Note, to collects facts from ASA device properly user should elevate the privilege
to become.
version_added: 1.0.0
extends_documentation_fragment:
- cisco.asa.asa
notes:
- Tested against asa 9.10(1)11
options:
gather_subset:
description:
- When supplied, this argument restricts the facts collected to a given subset.
- Possible values for this argument include C(all), C(min), C(hardware), C(config).
- Specify a list of values to include a larger subset.
- Use a value with an initial C(!) to collect all facts except that subset.
required: false
type: list
elements: str
default: '!config'
gather_network_resources:
description:
- When supplied, this argument will restrict the facts collected to a given subset.
Possible values for this argument include all and the resources like interfaces,
vlans etc. Can specify a list of values to include a larger subset. Values can
also be used with an initial C(!) to specify that a specific subset should
not be collected. Values can also be used with an initial C(!) to specify
that a specific subset should not be collected. Valid subsets are 'all', 'acls', 'ogs'.
required: false
type: list
elements: str
"""
EXAMPLES = """
- name: Gather all legacy facts
cisco.asa.asa_facts:
gather_subset: all
- name: Gather only the config and default facts
cisco.asa.asa_facts:
gather_subset:
- config
- name: Do not gather hardware facts
cisco.asa.asa_facts:
gather_subset:
- '!hardware'
- name: Gather legacy and resource facts
cisco.asa.asa_facts:
gather_subset: all
"""
RETURN = """
ansible_net_gather_subset:
description: The list of fact subsets collected from the device
returned: always
type: list
# default
ansible_net_model:
description: The model name returned from the device
returned: always
type: str
ansible_net_serialnum:
description: The serial number of the remote device
returned: always
type: str
ansible_net_version:
description: The operating system version running on the remote device
returned: always
type: str
ansible_net_firepower_version:
description: The Firepower operating system version running on the remote device.
returned: always
type: str
ansible_net_device_mgr_version:
description: The Device manager version running on the remote device.
returned: always
type: str
ansible_net_asatype:
description: The operating system type (Cisco ASA) running on the remote device.
returned: always
type: str
ansible_net_hostname:
description: The configured hostname of the device
returned: always
type: str
ansible_net_image:
description: The image file the device is running
returned: always
type: str
ansible_net_stacked_models:
description: The model names of each device in the stack
returned: when multiple devices are configured in a stack
type: list
ansible_net_stacked_serialnums:
description: The serial numbers of each device in the stack
returned: when multiple devices are configured in a stack
type: list
ansible_net_api:
description: The name of the transport
returned: always
type: str
ansible_net_python_version:
description: The Python version Ansible controller is using
returned: always
type: str
# hardware
ansible_net_filesystems:
description: All file system names available on the device
returned: when hardware is configured
type: list
ansible_net_filesystems_info:
description: A hash of all file systems containing info about each file system (e.g. free and total space)
returned: when hardware is configured
type: dict
ansible_net_memfree_mb:
description: The available free memory on the remote device in Mb
returned: when hardware is configured
type: int
ansible_net_memused_mb:
description: The used memory on the remote device in Mb
returned: when hardware is configured
type: int
ansible_net_memtotal_mb:
description: The total memory on the remote device in Mb
returned: when hardware is configured
type: int
# config
ansible_net_config:
description: The current active config from the device
returned: when config is configured
type: str
"""
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.cisco.asa.plugins.module_utils.network.asa.argspec.facts.facts import (
FactsArgs,
)
from ansible_collections.cisco.asa.plugins.module_utils.network.asa.facts.facts import (
Facts,
)
from ansible_collections.cisco.asa.plugins.module_utils.network.asa.asa import (
asa_argument_spec,
)
def main():
"""
Main entry point for module execution
:returns: ansible_facts
"""
argument_spec = FactsArgs.argument_spec
argument_spec.update(asa_argument_spec)
module = AnsibleModule(
argument_spec=argument_spec, supports_check_mode=True
)
warnings = []
if module.params["gather_subset"] == "!config":
warnings.append(
"default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards"
)
result = Facts(module).get_facts()
ansible_facts, additional_warnings = result
warnings.extend(additional_warnings)
module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,80 @@
#
# (c) 2016 Red Hat Inc.
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
import json
from ansible.errors import AnsibleConnectionFailure
from ansible.module_utils._text import to_text, to_bytes
from ansible_collections.ansible.netcommon.plugins.plugin_utils.terminal_base import (
TerminalBase,
)
class TerminalModule(TerminalBase):
terminal_stdout_re = [
re.compile(rb"[\r\n]?[\w+\-\.:\/\[\]]+(?:\([^\)]+\)){,3}(?:>|#) ?$"),
re.compile(rb"\[\w+\@[\w\-\.]+(?: [^\]])\] ?[>#\$] ?$"),
]
terminal_stderr_re = [
re.compile(rb"error:", re.I),
re.compile(rb"Removing.* not allowed, it is being used"),
re.compile(rb"^Command authorization failed\r?$", re.MULTILINE),
]
terminal_config_prompt = re.compile(r"^.+\(config(-.*)?\)#$")
def on_open_shell(self):
if self._get_prompt().strip().endswith(b"#"):
self.disable_pager()
def disable_pager(self):
try:
self._exec_cli_command("no terminal pager")
except AnsibleConnectionFailure:
raise AnsibleConnectionFailure("unable to disable terminal pager")
def on_become(self, passwd=None):
if self._get_prompt().strip().endswith(b"#"):
return
cmd = {"command": "enable"}
if passwd:
# Note: python-3.5 cannot combine u"" and r"" together. Thus make
# an r string and use to_text to ensure it's text on both py2 and py3.
cmd["prompt"] = to_text(
r"[\r\n]?[Pp]assword: $", errors="surrogate_or_strict"
)
cmd["answer"] = passwd
try:
self._exec_cli_command(
to_bytes(json.dumps(cmd), errors="surrogate_or_strict")
)
except AnsibleConnectionFailure:
raise AnsibleConnectionFailure(
"unable to elevate privilege to enable mode"
)
self.disable_pager()