diff --git a/plugins/action/vyos.py b/plugins/action/vyos.py
index 148d7c64..df8e7127 100644
--- a/plugins/action/vyos.py
+++ b/plugins/action/vyos.py
@@ -1,55 +1,53 @@
#
# (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 .
#
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
from ansible.utils.display import Display
from ansible_collections.ansible.netcommon.plugins.action.network import (
ActionModule as ActionNetworkModule,
)
-
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 ["vyos_config", "config"] else False
persistent_connection = self._play_context.connection.split(".")[-1]
warnings = []
if persistent_connection != "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
diff --git a/plugins/cliconf/vyos.py b/plugins/cliconf/vyos.py
index 17fcdf24..96c24c15 100644
--- a/plugins/cliconf/vyos.py
+++ b/plugins/cliconf/vyos.py
@@ -1,357 +1,356 @@
# (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 .
#
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
DOCUMENTATION = """
author: Ansible Networking Team (@ansible-network)
name: vyos
short_description: Use vyos cliconf to run command on VyOS platform
description:
- This vyos plugin provides low level abstraction apis for sending and receiving CLI
commands from VyOS 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_vyos_config_commands
"""
import json
import re
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.config import (
NetworkConfig,
)
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
class Cliconf(CliconfBase):
__rpc__ = CliconfBase.__rpc__ + [
"commit",
"discard_changes",
"get_diff",
"run_commands",
]
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"] = "vyos"
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)
if device_info["network_os_version"]:
match = re.search(r"VyOS\s*(\d+\.\d+)", device_info["network_os_version"])
if match:
device_info["network_os_major_version"] = match.group(1)
match = re.search(r"(?:HW|Hardware) model:\s*(\S+)", data)
if match:
device_info["network_os_model"] = match.group(1)
reply = self.get("show host name")
device_info["network_os_hostname"] = to_text(
reply,
errors="surrogate_or_strict",
).strip()
self._device_info = device_info
return self._device_info
def get_config(self, flags=None, format=None):
if format:
option_values = self.get_option_values()
if format not in option_values["format"]:
raise ValueError(
"'format' value %s is invalid. Valid values of format are %s"
% (format, ", ".join(option_values["format"])),
)
if not flags:
flags = []
if format == "text":
command = "show configuration"
else:
command = "show configuration commands"
command += " ".join(to_list(flags))
command = command.strip()
out = self.send_command(command)
return out
def edit_config(
self, candidate=None, commit=True, replace=None, diff=False, comment=None, confirm=None
):
resp = {}
operations = self.get_device_operations()
self.check_edit_config_capability(operations, candidate, commit, replace, comment)
results = []
requests = []
self.send_command("configure")
for cmd in to_list(candidate):
if not isinstance(cmd, Mapping):
cmd = {"command": cmd}
results.append(self.send_command(**cmd))
requests.append(cmd["command"])
out = self.get("compare")
out = to_text(out, errors="surrogate_or_strict")
diff_config = out if not out.startswith("No changes") else None
if diff_config:
if commit:
try:
self.commit(comment, confirm)
except AnsibleConnectionFailure as e:
msg = "commit failed: %s" % e.message
self.discard_changes()
raise AnsibleConnectionFailure(msg)
else:
self.send_command("exit")
else:
self.discard_changes()
else:
self.send_command("exit")
if (
to_text(self._connection.get_prompt(), errors="surrogate_or_strict")
.strip()
.endswith("#")
):
self.discard_changes()
if diff_config:
resp["diff"] = diff_config
resp["response"] = results
resp["request"] = requests
return resp
def get(
self,
command=None,
prompt=None,
answer=None,
sendonly=False,
newline=True,
output=None,
check_all=False,
):
if not command:
raise ValueError("must provide value of command to execute")
if output:
raise ValueError("'output' value %s is not supported for get" % output)
return self.send_command(
command=command,
prompt=prompt,
answer=answer,
sendonly=sendonly,
newline=newline,
check_all=check_all,
)
def commit(self, comment=None, confirm=None):
if confirm:
if comment:
command = 'commit-confirm {0} comment "{1}"'.format(confirm, comment)
else:
- command = 'commit-confirm {0}'.format(confirm)
+ command = "commit-confirm {0}".format(confirm)
self.send_command(command, "Proceed?", "\n")
else:
if comment:
command = 'commit comment "{0}"'.format(comment)
else:
command = "commit"
self.send_command(command)
def discard_changes(self):
self.send_command("exit discard")
def get_diff(
self,
candidate=None,
running=None,
diff_match="line",
diff_ignore_lines=None,
path=None,
diff_replace=None,
):
diff = {}
device_operations = self.get_device_operations()
option_values = self.get_option_values()
if candidate is None and device_operations["supports_generate_diff"]:
raise ValueError("candidate configuration is required to generate diff")
if diff_match not in option_values["diff_match"]:
raise ValueError(
"'match' value %s in invalid, valid values are %s"
% (diff_match, ", ".join(option_values["diff_match"])),
)
if diff_replace:
raise ValueError("'replace' in diff is not supported")
if diff_ignore_lines:
raise ValueError("'diff_ignore_lines' in diff is not supported")
if path:
raise ValueError("'path' in diff is not supported")
set_format = candidate.startswith("set") or candidate.startswith("delete")
candidate_obj = NetworkConfig(indent=4, contents=candidate)
if not set_format:
config = [c.line for c in candidate_obj.items]
commands = list()
# this filters out less specific lines
for item in config:
for index, entry in enumerate(commands):
if item.startswith(entry):
del commands[index]
break
commands.append(item)
candidate_commands = ["set %s" % cmd.replace(" {", "") for cmd in commands]
else:
candidate_commands = str(candidate).strip().split("\n")
if diff_match == "none":
diff["config_diff"] = list(candidate_commands)
return diff
running_commands = [str(c).replace("'", "") for c in running.splitlines()]
updates = list()
visited = set()
for line in candidate_commands:
item = str(line).replace("'", "")
if not item.startswith("set") and not item.startswith("delete"):
raise ValueError("line must start with either `set` or `delete`")
elif item.startswith("set") and item not in running_commands:
updates.append(line)
elif item.startswith("delete"):
if not running_commands:
updates.append(line)
else:
item = re.sub(r"delete", "set", item)
for entry in running_commands:
if re.match(rf"^{re.escape(item)}\b", entry) and line not in visited:
updates.append(line)
visited.add(line)
diff["config_diff"] = list(updates)
return diff
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", e)
responses.append(out)
return responses
def get_device_operations(self):
return {
"supports_diff_replace": False,
"supports_commit": True,
"supports_rollback": False,
"supports_defaults": False,
"supports_onbox_diff": True,
"supports_commit_comment": True,
"supports_multiline_delimiter": False,
"supports_diff_match": True,
"supports_diff_ignore_lines": False,
"supports_generate_diff": False,
"supports_replace": False,
}
def get_option_values(self):
return {
"format": ["text", "set"],
"diff_match": ["line", "none"],
"diff_replace": [],
"output": [],
}
def get_capabilities(self):
result = super(Cliconf, self).get_capabilities()
result["device_operations"] = self.get_device_operations()
result.update(self.get_option_values())
return json.dumps(result)
def set_cli_prompt_context(self):
"""
Make sure we are in the operational cli mode
:return: None
"""
if self._connection.connected:
self._update_cli_prompt_context(config_context="#", exit_command="exit discard")
diff --git a/plugins/doc_fragments/vyos.py b/plugins/doc_fragments/vyos.py
index aaa7bf71..698d0f5f 100644
--- a/plugins/doc_fragments/vyos.py
+++ b/plugins/doc_fragments/vyos.py
@@ -1,17 +1,16 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
# Copyright: (c) 2015, Peter Sprygada
# 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: {}
notes:
- For more information on using Ansible to manage network devices see the :ref:`Ansible
Network Guide `
"""
diff --git a/plugins/module_utils/network/vyos/argspec/bgp_address_family/bgp_address_family.py b/plugins/module_utils/network/vyos/argspec/bgp_address_family/bgp_address_family.py
index 13f9fab0..946f6861 100644
--- a/plugins/module_utils/network/vyos/argspec/bgp_address_family/bgp_address_family.py
+++ b/plugins/module_utils/network/vyos/argspec/bgp_address_family/bgp_address_family.py
@@ -1,195 +1,194 @@
# -*- coding: utf-8 -*-
# Copyright 2024 Red Hat
# 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
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the
# cli_rm_builder.
#
# Manually editing this file is not advised.
#
# To update the argspec make the desired changes
# in the module docstring and re-run
# cli_rm_builder.
#
#############################################
"""
The arg spec for the vyos_bgp_address_family module
"""
class Bgp_address_familyArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_bgp_address_family module"""
argument_spec = {
"config": {
"type": "dict",
"options": {
"as_number": {"type": "int"},
"address_family": {
"type": "list",
"elements": "dict",
"options": {
"afi": {"type": "str", "choices": ["ipv4", "ipv6"]},
"aggregate_address": {
"type": "list",
"elements": "dict",
"options": {
"prefix": {"type": "str"},
"as_set": {"type": "bool"},
"summary_only": {"type": "bool"},
},
},
"networks": {
"type": "list",
"elements": "dict",
"options": {
"prefix": {"type": "str"},
"path_limit": {"type": "int"},
"backdoor": {"type": "bool"},
"route_map": {"type": "str"},
},
},
"redistribute": {
"type": "list",
"elements": "dict",
"options": {
"protocol": {
"type": "str",
"choices": [
"connected",
"kernel",
"ospf",
"ospfv3",
"rip",
"ripng",
"static",
],
},
"table": {"type": "str"},
"route_map": {"type": "str"},
"metric": {"type": "int"},
},
},
},
},
"neighbors": {
"type": "list",
"elements": "dict",
"options": {
"neighbor_address": {"type": "str"},
"address_family": {
"type": "list",
"elements": "dict",
"options": {
"afi": {
"type": "str",
"choices": ["ipv4", "ipv6"],
},
"allowas_in": {"type": "int"},
"as_override": {"type": "bool"},
"attribute_unchanged": {
"type": "dict",
"options": {
"as_path": {"type": "bool"},
"med": {"type": "bool"},
"next_hop": {"type": "bool"},
},
},
"capability": {
"type": "dict",
"options": {
"dynamic": {"type": "bool"},
"orf": {
"type": "str",
"choices": ["send", "receive"],
},
},
},
"default_originate": {"type": "str"},
"distribute_list": {
"type": "list",
"elements": "dict",
"options": {
"action": {
"type": "str",
"choices": ["export", "import"],
},
"acl": {"type": "int"},
},
},
"filter_list": {
"type": "list",
"elements": "dict",
"options": {
"action": {
"type": "str",
"choices": ["export", "import"],
},
"path_list": {"type": "str"},
},
},
"maximum_prefix": {"type": "int"},
"nexthop_local": {"type": "bool"},
"nexthop_self": {"type": "bool"},
"peer_group": {"type": "str"},
"prefix_list": {
"type": "list",
"elements": "dict",
"options": {
"action": {
"type": "str",
"choices": ["export", "import"],
},
"prefix_list": {"type": "str"},
},
},
"remove_private_as": {"type": "bool"},
"route_map": {
"type": "list",
"elements": "dict",
"options": {
"action": {
"type": "str",
"choices": ["export", "import"],
},
"route_map": {"type": "str"},
},
},
"route_reflector_client": {"type": "bool"},
"route_server_client": {"type": "bool"},
"soft_reconfiguration": {"type": "bool"},
"unsupress_map": {"type": "str"},
"weight": {"type": "int"},
},
},
},
},
},
},
"running_config": {"type": "str"},
"state": {
"type": "str",
"choices": [
"merged",
"replaced",
"deleted",
"gathered",
"parsed",
"rendered",
"purged",
"overridden",
],
"default": "merged",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/bgp_global/bgp_global.py b/plugins/module_utils/network/vyos/argspec/bgp_global/bgp_global.py
index 42fb5abf..13cca54d 100644
--- a/plugins/module_utils/network/vyos/argspec/bgp_global/bgp_global.py
+++ b/plugins/module_utils/network/vyos/argspec/bgp_global/bgp_global.py
@@ -1,173 +1,172 @@
# -*- coding: utf-8 -*-
# Copyright 2024 Red Hat
# 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
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the
# cli_rm_builder.
#
# Manually editing this file is not advised.
#
# To update the argspec make the desired changes
# in the module docstring and re-run
# cli_rm_builder.
#
#############################################
"""
The arg spec for the vyos_bgp_global module
"""
class Bgp_globalArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_bgp_global module"""
argument_spec = {
"config": {
"type": "dict",
"options": {
"as_number": {"type": "int"},
"neighbor": {
"type": "list",
"elements": "dict",
"options": {
"address": {"type": "str"},
"advertisement_interval": {"type": "int"},
"capability": {
"type": "dict",
"options": {
"dynamic": {"type": "bool"},
"extended_nexthop": {"type": "bool"},
},
},
"default_originate": {"type": "str"},
"description": {"type": "str"},
"disable_capability_negotiation": {"type": "bool"},
"disable_connected_check": {"type": "bool"},
"disable_send_community": {
"type": "str",
"choices": ["extended", "standard"],
},
"ebgp_multihop": {"type": "int"},
"local_as": {"type": "int"},
"override_capability": {"type": "bool"},
"passive": {"type": "bool"},
"password": {"type": "str", "no_log": True},
"peer_group_name": {"type": "str"},
"peer_group": {"type": "bool"},
"port": {"type": "int"},
"remote_as": {"type": "int"},
"solo": {"type": "bool"},
"shutdown": {"type": "bool"},
"strict_capability_match": {"type": "bool"},
"timers": {
"type": "dict",
"options": {
"connect": {"type": "int"},
"holdtime": {"type": "int"},
"keepalive": {"type": "int"},
},
},
"ttl_security": {"type": "int"},
"update_source": {"type": "str"},
},
},
"timers": {
"type": "dict",
"options": {
"keepalive": {"type": "int"},
"holdtime": {"type": "int"},
},
},
"bgp_params": {
"type": "dict",
"options": {
"always_compare_med": {"type": "bool"},
"bestpath": {
"type": "dict",
"options": {
"as_path": {
"type": "str",
"choices": ["confed", "ignore"],
},
"compare_routerid": {"type": "bool"},
"med": {
"type": "str",
"choices": ["confed", "missing-as-worst"],
},
},
},
"cluster_id": {"type": "str"},
"confederation": {
"type": "list",
"elements": "dict",
"options": {
"identifier": {"type": "int"},
"peers": {"type": "int"},
},
},
"dampening": {
"type": "dict",
"options": {
"half_life": {"type": "int"},
"max_suppress_time": {"type": "int"},
"re_use": {"type": "int"},
"start_suppress_time": {"type": "int"},
},
},
"default": {
"type": "dict",
"options": {
"local_pref": {"type": "int"},
"no_ipv4_unicast": {"type": "bool"},
},
},
"deterministic_med": {"type": "bool"},
"disable_network_import_check": {"type": "bool"},
"distance": {
"type": "list",
"elements": "dict",
"options": {
"type": {
"type": "str",
"choices": ["external", "internal", "local"],
},
"value": {"type": "int"},
"prefix": {"type": "int"},
},
},
"enforce_first_as": {"type": "bool"},
"graceful_restart": {"type": "int"},
"log_neighbor_changes": {"type": "bool"},
"no_client_to_client_reflection": {"type": "bool"},
"no_fast_external_failover": {"type": "bool"},
"router_id": {"type": "str"},
"scan_time": {"type": "int"},
},
},
},
},
"state": {
"type": "str",
"choices": [
"deleted",
"merged",
"purged",
"replaced",
"gathered",
"rendered",
"parsed",
],
"default": "merged",
},
"running_config": {"type": "str"},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/facts/facts.py b/plugins/module_utils/network/vyos/argspec/facts/facts.py
index b274c507..ddcd8d14 100644
--- a/plugins/module_utils/network/vyos/argspec/facts/facts.py
+++ b/plugins/module_utils/network/vyos/argspec/facts/facts.py
@@ -1,23 +1,23 @@
# 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 vyos facts module.
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
class FactsArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos facts module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"gather_subset": dict(default=["min"], type="list", elements="str"),
"gather_network_resources": dict(type="list", elements="str"),
"available_network_resources": {"type": "bool", "default": False},
}
diff --git a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
index 8421b6de..aeef42bc 100644
--- a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
@@ -1,206 +1,204 @@
#
# -*- 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 vyos_firewall_global module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
class Firewall_globalArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_firewall_global module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"config": {
"options": {
"config_trap": {"type": "bool"},
"group": {
"options": {
"address_group": {
"elements": "dict",
"options": {
"afi": {
"choices": [
"ipv4",
"ipv6",
],
"default": "ipv4",
"type": "str",
},
"description": {"type": "str"},
"members": {
"elements": "dict",
"options": {"address": {"type": "str"}},
"type": "list",
},
"name": {
"required": True,
"type": "str",
},
},
"type": "list",
},
"network_group": {
"elements": "dict",
"options": {
"afi": {
"choices": [
"ipv4",
"ipv6",
],
"default": "ipv4",
"type": "str",
},
"description": {"type": "str"},
"members": {
"elements": "dict",
"options": {"address": {"type": "str"}},
"type": "list",
},
"name": {
"required": True,
"type": "str",
},
},
"type": "list",
},
"port_group": {
"elements": "dict",
"options": {
"description": {"type": "str"},
"members": {
"elements": "dict",
"options": {"port": {"type": "str"}},
"type": "list",
},
"name": {
"required": True,
"type": "str",
},
},
"type": "list",
},
},
"type": "dict",
},
"log_martians": {"type": "bool"},
"ping": {
"options": {
"all": {"type": "bool"},
"broadcast": {"type": "bool"},
},
"type": "dict",
},
"route_redirects": {
"elements": "dict",
"options": {
"afi": {
"choices": [
"ipv4",
"ipv6",
],
"required": True,
"type": "str",
},
"icmp_redirects": {
"options": {
"receive": {"type": "bool"},
"send": {"type": "bool"},
},
"type": "dict",
},
"ip_src_route": {"type": "bool"},
},
"type": "list",
},
"state_policy": {
"elements": "dict",
"options": {
"action": {
"choices": [
"accept",
"drop",
"reject",
],
"type": "str",
},
"connection_type": {
"choices": [
"established",
"invalid",
"related",
],
"type": "str",
},
"log": {"type": "bool"},
"log_level": {
"choices": [
"emerg",
"alert",
"crit",
"err",
"warn",
"notice",
"info",
"debug",
],
"type": "str",
},
},
"type": "list",
},
"syn_cookies": {"type": "bool"},
"twa_hazards_protection": {"type": "bool"},
"validation": {
"choices": [
"strict",
"loose",
"disable",
],
"type": "str",
},
},
"type": "dict",
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"deleted",
"gathered",
"rendered",
"parsed",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py b/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py
index 93c898e8..d925a7f4 100644
--- a/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py
@@ -1,93 +1,91 @@
#
# -*- 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 vyos_firewall_interfaces module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
class Firewall_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_firewall_interfaces module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"config": {
"elements": "dict",
"options": {
"access_rules": {
"elements": "dict",
"options": {
"afi": {
"choices": [
"ipv4",
"ipv6",
],
"required": True,
"type": "str",
},
"rules": {
"elements": "dict",
"options": {
"direction": {
"choices": [
"in",
"local",
"out",
],
"required": True,
"type": "str",
},
"name": {"type": "str"},
},
"type": "list",
},
},
"type": "list",
},
"name": {"required": True, "type": "str"},
},
"type": "list",
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"parsed",
"rendered",
"gathered",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py
index d1cf4fe8..b847d2b0 100644
--- a/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py
+++ b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py
@@ -1,351 +1,349 @@
#
# -*- 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 vyos_firewall_rules module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
class Firewall_rulesArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_firewall_rules module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"config": {
"elements": "dict",
"options": {
"afi": {
"choices": ["ipv4", "ipv6"],
"required": True,
"type": "str",
},
"rule_sets": {
"elements": "dict",
"options": {
"default_action": {
"choices": [
"drop",
"reject",
"accept",
"jump",
],
"type": "str",
},
"default_jump_target": {"type": "str"},
"description": {"type": "str"},
"enable_default_log": {"type": "bool"},
"filter": {
"choices": [
"input",
"output",
"forward",
],
"type": "str",
},
"name": {"type": "str"},
"rules": {
"elements": "dict",
"options": {
"action": {
"choices": [
"drop",
"reject",
"accept",
"inspect",
"continue",
"return",
"jump",
"offload",
"queue",
"synproxy",
],
"type": "str",
},
"description": {"type": "str"},
"destination": {
"options": {
"address": {"type": "str"},
"group": {
"options": {
"address_group": {"type": "str"},
"network_group": {"type": "str"},
"port_group": {"type": "str"},
},
"type": "dict",
},
"port": {"type": "str"},
},
"type": "dict",
},
"disable": {
"aliases": ["disabled"],
"type": "bool",
},
"fragment": {
"choices": [
"match-frag",
"match-non-frag",
],
"type": "str",
},
"icmp": {
"options": {
"code": {"type": "int"},
"type": {"type": "int"},
"type_name": {
"choices": [
"any",
"echo-reply",
"destination-unreachable",
"network-unreachable",
"host-unreachable",
"protocol-unreachable",
"port-unreachable",
"fragmentation-needed",
"source-route-failed",
"network-unknown",
"host-unknown",
"network-prohibited",
"host-prohibited",
"TOS-network-unreachable",
"TOS-host-unreachable",
"communication-prohibited",
"host-precedence-violation",
"precedence-cutoff",
"source-quench",
"redirect",
"network-redirect",
"host-redirect",
"TOS-network-redirect",
"TOS-host-redirect",
"echo-request",
"router-advertisement",
"router-solicitation",
"time-exceeded",
"ttl-zero-during-transit",
"ttl-zero-during-reassembly",
"parameter-problem",
"ip-header-bad",
"required-option-missing",
"timestamp-request",
"timestamp-reply",
"address-mask-request",
"address-mask-reply",
"ping",
"pong",
"ttl-exceeded",
],
"type": "str",
},
},
"type": "dict",
},
"inbound_interface": {
"options": {
"group": {"type": "str"},
"name": {"type": "str"},
},
"type": "dict",
},
"ipsec": {
"choices": [
"match-ipsec",
"match-none",
"match-ipsec-in",
"match-ipsec-out",
"match-none-in",
"match-none-out",
],
"type": "str",
},
"jump_target": {"type": "str"},
"limit": {
"options": {
"burst": {"type": "int"},
"rate": {
"options": {
"number": {"type": "int"},
"unit": {"type": "str"},
},
"type": "dict",
},
},
"type": "dict",
},
"log": {
"choices": [
"disable",
"enable",
],
"type": "str",
},
"number": {
"required": True,
"type": "int",
},
"offload_target": {"type": "str"},
"outbound_interface": {
"options": {
"group": {"type": "str"},
"name": {"type": "str"},
},
"type": "dict",
},
"packet_length": {
"elements": "dict",
"options": {"length": {"type": "str"}},
"type": "list",
},
"packet_length_exclude": {
"elements": "dict",
"options": {"length": {"type": "str"}},
"type": "list",
},
"packet_type": {
"choices": [
"broadcast",
"multicast",
"host",
"other",
],
"type": "str",
},
"protocol": {"type": "str"},
"queue": {"type": "str"},
"queue_options": {
"choices": [
"bypass",
"fanout",
],
"type": "str",
},
"recent": {
"options": {
"count": {"type": "int"},
"time": {"type": "str"},
},
"type": "dict",
},
"source": {
"options": {
"address": {"type": "str"},
"fqdn": {"type": "str"},
"group": {
"options": {
"address_group": {"type": "str"},
"network_group": {"type": "str"},
"port_group": {"type": "str"},
},
"type": "dict",
},
"mac_address": {"type": "str"},
"port": {"type": "str"},
},
"type": "dict",
},
"state": {
"options": {
"established": {"type": "bool"},
"invalid": {"type": "bool"},
"new": {"type": "bool"},
"related": {"type": "bool"},
},
"type": "dict",
},
"synproxy": {
"options": {
"mss": {"type": "int"},
"window_scale": {"type": "int"},
},
"type": "dict",
},
"tcp": {
"options": {
"flags": {
"elements": "dict",
"options": {
"flag": {
"choices": [
"ack",
"cwr",
"ecn",
"fin",
"psh",
"rst",
"syn",
"urg",
"all",
],
"type": "str",
},
"invert": {"type": "bool"},
},
"type": "list",
},
},
"type": "dict",
},
"time": {
"options": {
"monthdays": {"type": "str"},
"startdate": {"type": "str"},
"starttime": {"type": "str"},
"stopdate": {"type": "str"},
"stoptime": {"type": "str"},
"utc": {"type": "bool"},
"weekdays": {"type": "str"},
},
"type": "dict",
},
},
"type": "list",
},
},
"type": "list",
},
},
"type": "list",
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"gathered",
"rendered",
"parsed",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/hostname/hostname.py b/plugins/module_utils/network/vyos/argspec/hostname/hostname.py
index 12864e66..b1af7b9c 100644
--- a/plugins/module_utils/network/vyos/argspec/hostname/hostname.py
+++ b/plugins/module_utils/network/vyos/argspec/hostname/hostname.py
@@ -1,50 +1,49 @@
# -*- coding: utf-8 -*-
# Copyright 2024 Red Hat
# 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
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the
# cli_rm_builder.
#
# Manually editing this file is not advised.
#
# To update the argspec make the desired changes
# in the module docstring and re-run
# cli_rm_builder.
#
#############################################
"""
The arg spec for the vyos_hostname module
"""
class HostnameArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_hostname module"""
argument_spec = {
"config": {"type": "dict", "options": {"hostname": {"type": "str"}}},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"gathered",
"parsed",
"rendered",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py b/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py
index 14b67c28..66e046bf 100644
--- a/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py
@@ -1,99 +1,97 @@
#
# -*- 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 vyos_interfaces module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
class InterfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_interfaces module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"config": {
"elements": "dict",
"options": {
"description": {"type": "str"},
"duplex": {
"choices": ["full", "half", "auto"],
"type": "str",
},
"enabled": {
"aliases": ["enable"],
"default": True,
"type": "bool",
},
"mtu": {"type": "int"},
"name": {"required": True, "type": "str"},
"speed": {
"choices": [
"auto",
"10",
"100",
"1000",
"2500",
"10000",
],
"type": "str",
},
"vifs": {
"elements": "dict",
"options": {
"description": {"type": "str"},
"enabled": {
"aliases": ["enable"],
"default": True,
"type": "bool",
},
"mtu": {"type": "int"},
"vlan_id": {"type": "int"},
},
"type": "list",
},
},
"type": "list",
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"rendered",
"gathered",
"parsed",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py
index 4dee518e..d6d9e298 100644
--- a/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py
@@ -1,90 +1,88 @@
#
# -*- 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 vyos_l3_interfaces module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
class L3_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_l3_interfaces module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"config": {
"elements": "dict",
"options": {
"ipv4": {
"elements": "dict",
"options": {"address": {"type": "str"}},
"type": "list",
},
"ipv6": {
"elements": "dict",
"options": {"address": {"type": "str"}},
"type": "list",
},
"name": {"required": True, "type": "str"},
"vifs": {
"elements": "dict",
"options": {
"ipv4": {
"elements": "dict",
"options": {"address": {"type": "str"}},
"type": "list",
},
"ipv6": {
"elements": "dict",
"options": {"address": {"type": "str"}},
"type": "list",
},
"vlan_id": {"type": "int"},
},
"type": "list",
},
},
"type": "list",
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"rendered",
"gathered",
"parsed",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py
index 956c4385..7744eea0 100644
--- a/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py
@@ -1,99 +1,97 @@
#
# -*- 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 vyos_lag_interfaces module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
class Lag_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_lag_interfaces module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"config": {
"elements": "dict",
"options": {
"arp_monitor": {
"options": {
"interval": {"type": "int"},
"target": {
"elements": "str",
"type": "list",
},
},
"type": "dict",
},
"hash_policy": {
"choices": [
"layer2",
"layer2+3",
"layer3+4",
],
"type": "str",
},
"members": {
"elements": "dict",
"options": {"member": {"type": "str"}},
"type": "list",
},
"mode": {
"choices": [
"802.3ad",
"active-backup",
"broadcast",
"round-robin",
"transmit-load-balance",
"adaptive-load-balance",
"xor-hash",
],
"type": "str",
},
"name": {"required": True, "type": "str"},
"primary": {"type": "str"},
},
"type": "list",
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"parsed",
"gathered",
"rendered",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py b/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py
index d79de617..6d1129e8 100644
--- a/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py
+++ b/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py
@@ -1,78 +1,76 @@
#
# -*- 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 vyos_lldp_global module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
class Lldp_globalArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_lldp_global module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"config": {
"options": {
"address": {"type": "str"},
"addresses": {
"elements": "str",
"type": "list",
},
"enable": {"type": "bool"},
"legacy_protocols": {
"choices": [
"cdp",
"edp",
"fdp",
"sonmp",
],
"elements": "str",
"type": "list",
},
"snmp": {"type": "str"},
},
"type": "dict",
"mutually_exclusive": [["addresses", "address"]],
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"deleted",
"gathered",
"rendered",
"parsed",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py
index fd6c6271..4bb742f5 100644
--- a/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py
@@ -1,92 +1,90 @@
#
# -*- 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 vyos_lldp_interfaces module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
class Lldp_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_lldp_interfaces module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"config": {
"elements": "dict",
"options": {
"enable": {"default": True, "type": "bool"},
"location": {
"options": {
"coordinate_based": {
"options": {
"altitude": {"type": "int"},
"datum": {
"choices": [
"WGS84",
"NAD83",
"MLLW",
],
"type": "str",
},
"latitude": {
"required": True,
"type": "str",
},
"longitude": {
"required": True,
"type": "str",
},
},
"type": "dict",
},
"elin": {"type": "str"},
},
"type": "dict",
},
"name": {"required": True, "type": "str"},
},
"type": "list",
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"rendered",
"parsed",
"gathered",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/logging_global/logging_global.py b/plugins/module_utils/network/vyos/argspec/logging_global/logging_global.py
index 734d190e..f6800e9b 100644
--- a/plugins/module_utils/network/vyos/argspec/logging_global/logging_global.py
+++ b/plugins/module_utils/network/vyos/argspec/logging_global/logging_global.py
@@ -1,379 +1,378 @@
# -*- coding: utf-8 -*-
# Copyright 2024 Red Hat
# 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
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the
# cli_rm_builder.
#
# Manually editing this file is not advised.
#
# To update the argspec make the desired changes
# in the module docstring and re-run
# cli_rm_builder.
#
#############################################
"""
The arg spec for the vyos_logging_global module
"""
class Logging_globalArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_logging_global module"""
argument_spec = {
"config": {
"type": "dict",
"options": {
"console": {
"type": "dict",
"options": {
"state": {
"type": "str",
"choices": ["enabled", "disabled"],
},
"facilities": {
"type": "list",
"elements": "dict",
"options": {
"facility": {
"type": "str",
"choices": [
"all",
"auth",
"authpriv",
"cron",
"daemon",
"kern",
"lpr",
"mail",
"mark",
"news",
"protocols",
"security",
"syslog",
"user",
"uucp",
"local0",
"local1",
"local2",
"local3",
"local4",
"local5",
"local6",
"local7",
],
},
"severity": {
"type": "str",
"choices": [
"emerg",
"alert",
"crit",
"err",
"warning",
"notice",
"info",
"debug",
"all",
],
},
},
},
},
},
"files": {
"type": "list",
"elements": "dict",
"options": {
"path": {"type": "str"},
"archive": {
"type": "dict",
"options": {
"state": {
"type": "str",
"choices": ["enabled", "disabled"],
},
"file_num": {"type": "int"},
"size": {"type": "int"},
},
},
"facilities": {
"type": "list",
"elements": "dict",
"options": {
"facility": {
"type": "str",
"choices": [
"all",
"auth",
"authpriv",
"cron",
"daemon",
"kern",
"lpr",
"mail",
"mark",
"news",
"protocols",
"security",
"syslog",
"user",
"uucp",
"local0",
"local1",
"local2",
"local3",
"local4",
"local5",
"local6",
"local7",
],
},
"severity": {
"type": "str",
"choices": [
"emerg",
"alert",
"crit",
"err",
"warning",
"notice",
"info",
"debug",
"all",
],
},
},
},
},
},
"global_params": {
"type": "dict",
"options": {
"state": {
"type": "str",
"choices": ["enabled", "disabled"],
},
"archive": {
"type": "dict",
"options": {
"state": {
"type": "str",
"choices": ["enabled", "disabled"],
},
"file_num": {"type": "int"},
"size": {"type": "int"},
},
"removed_in_version": "7.0.0",
"removed_from_collection": "vyos.vyos",
},
"facilities": {
"type": "list",
"elements": "dict",
"options": {
"facility": {
"type": "str",
"choices": [
"all",
"auth",
"authpriv",
"cron",
"daemon",
"kern",
"lpr",
"mail",
"mark",
"news",
"protocols",
"security",
"syslog",
"user",
"uucp",
"local0",
"local1",
"local2",
"local3",
"local4",
"local5",
"local6",
"local7",
],
},
"severity": {
"type": "str",
"choices": [
"emerg",
"alert",
"crit",
"err",
"warning",
"notice",
"info",
"debug",
"all",
],
},
},
},
"marker_interval": {"type": "int"},
"preserve_fqdn": {"type": "bool"},
},
},
"hosts": {
"type": "list",
"elements": "dict",
"options": {
"port": {"type": "int"},
"facilities": {
"type": "list",
"elements": "dict",
"options": {
"facility": {
"type": "str",
"choices": [
"all",
"auth",
"authpriv",
"cron",
"daemon",
"kern",
"lpr",
"mail",
"mark",
"news",
"protocols",
"security",
"syslog",
"user",
"uucp",
"local0",
"local1",
"local2",
"local3",
"local4",
"local5",
"local6",
"local7",
],
},
"severity": {
"type": "str",
"choices": [
"emerg",
"alert",
"crit",
"err",
"warning",
"notice",
"info",
"debug",
"all",
],
},
"protocol": {
"type": "str",
"choices": ["udp", "tcp"],
"removed_in_version": "7.0.0",
"removed_from_collection": "vyos.vyos",
},
},
},
"hostname": {"type": "str"},
"protocol": {
"type": "str",
"choices": ["udp", "tcp"],
},
},
},
"syslog": {
"type": "dict",
"options": {
"state": {
"type": "str",
"choices": ["enabled", "disabled"],
},
},
},
"users": {
"type": "list",
"elements": "dict",
"options": {
"username": {"type": "str"},
"facilities": {
"type": "list",
"elements": "dict",
"options": {
"facility": {
"type": "str",
"choices": [
"all",
"auth",
"authpriv",
"cron",
"daemon",
"kern",
"lpr",
"mail",
"mark",
"news",
"protocols",
"security",
"syslog",
"user",
"uucp",
"local0",
"local1",
"local2",
"local3",
"local4",
"local5",
"local6",
"local7",
],
},
"severity": {
"type": "str",
"choices": [
"emerg",
"alert",
"crit",
"err",
"warning",
"notice",
"info",
"debug",
"all",
],
},
},
},
},
},
},
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"gathered",
"parsed",
"rendered",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/ntp_global/ntp_global.py b/plugins/module_utils/network/vyos/argspec/ntp_global/ntp_global.py
index 6940fb7e..5e10d05b 100644
--- a/plugins/module_utils/network/vyos/argspec/ntp_global/ntp_global.py
+++ b/plugins/module_utils/network/vyos/argspec/ntp_global/ntp_global.py
@@ -1,77 +1,76 @@
# -*- coding: utf-8 -*-
# Copyright 2024 Red Hat
# 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
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the
# cli_rm_builder.
#
# Manually editing this file is not advised.
#
# To update the argspec make the desired changes
# in the module docstring and re-run
# cli_rm_builder.
#
#############################################
"""
The arg spec for the vyos_ntp_global module
"""
class Ntp_globalArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_ntp_global module"""
argument_spec = {
"config": {
"type": "dict",
"options": {
"allow_clients": {"type": "list", "elements": "str"},
"listen_addresses": {"type": "list", "elements": "str"},
"servers": {
"type": "list",
"elements": "dict",
"options": {
"server": {"type": "str"},
"options": {
"type": "list",
"elements": "str",
"choices": [
"dynamic",
"noselect",
"pool",
"preempt",
"prefer",
"nts",
"ptp",
"interleave",
],
},
},
},
},
},
"running_config": {"type": "str"},
"state": {
"type": "str",
"choices": [
"deleted",
"merged",
"overridden",
"replaced",
"gathered",
"rendered",
"parsed",
],
"default": "merged",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/argspec/ospf_interfaces/ospf_interfaces.py
index 0b5814be..ced9f98e 100644
--- a/plugins/module_utils/network/vyos/argspec/ospf_interfaces/ospf_interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/ospf_interfaces/ospf_interfaces.py
@@ -1,96 +1,95 @@
# -*- coding: utf-8 -*-
# Copyright 2024 Red Hat
# 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
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the
# cli_rm_builder.
#
# Manually editing this file is not advised.
#
# To update the argspec make the desired changes
# in the module docstring and re-run
# cli_rm_builder.
#
#############################################
"""
The arg spec for the vyos_ospf_interfaces module
"""
class Ospf_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_ospf_interfaces module"""
argument_spec = {
"config": {
"type": "list",
"elements": "dict",
"options": {
"name": {"type": "str"},
"address_family": {
"type": "list",
"elements": "dict",
"options": {
"afi": {
"type": "str",
"choices": ["ipv4", "ipv6"],
"required": True,
},
"authentication": {
"type": "dict",
"options": {
"plaintext_password": {
"type": "str",
"no_log": True,
},
"md5_key": {
"type": "dict",
"no_log": False,
"options": {
"key_id": {"type": "int"},
"key": {"type": "str", "no_log": True},
},
},
},
},
"bandwidth": {"type": "int"},
"cost": {"type": "int"},
"dead_interval": {"type": "int"},
"hello_interval": {"type": "int"},
"mtu_ignore": {"type": "bool"},
"network": {"type": "str"},
"priority": {"type": "int"},
"retransmit_interval": {"type": "int"},
"transmit_delay": {"type": "int"},
"ifmtu": {"type": "int"},
"instance": {"type": "str"},
"passive": {"type": "bool"},
},
},
},
},
"running_config": {"type": "str"},
"state": {
"type": "str",
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"gathered",
"parsed",
"rendered",
],
"default": "merged",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py
index 0a422e89..a939652b 100644
--- a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py
+++ b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py
@@ -1,296 +1,294 @@
#
# -*- 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 vyos_ospfv2 module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
class Ospfv2Args(object): # pylint: disable=R0903
"""The arg spec for the vyos_ospfv2 module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"config": {
"options": {
"areas": {
"elements": "dict",
"options": {
"area_id": {"type": "str"},
"area_type": {
"options": {
"normal": {"type": "bool"},
"nssa": {
"options": {
"default_cost": {"type": "int"},
"no_summary": {"type": "bool"},
"set": {"type": "bool"},
"translate": {
"choices": [
"always",
"candidate",
"never",
],
"type": "str",
},
},
"type": "dict",
},
"stub": {
"options": {
"default_cost": {"type": "int"},
"no_summary": {"type": "bool"},
"set": {"type": "bool"},
},
"type": "dict",
},
},
"type": "dict",
},
"authentication": {
"choices": [
"plaintext-password",
"md5",
],
"type": "str",
},
"network": {
"elements": "dict",
"options": {
"address": {
"required": True,
"type": "str",
},
},
"type": "list",
},
"range": {
"elements": "dict",
"options": {
"address": {"type": "str"},
"cost": {"type": "int"},
"not_advertise": {"type": "bool"},
"substitute": {"type": "str"},
},
"type": "list",
},
"shortcut": {
"choices": [
"default",
"disable",
"enable",
],
"type": "str",
},
"virtual_link": {
"elements": "dict",
"options": {
"address": {"type": "str"},
"authentication": {
"options": {
"md5": {
"elements": "dict",
"options": {
"key_id": {"type": "int"},
"md5_key": {
"no_log": True,
"type": "str",
},
},
"type": "list",
},
"plaintext_password": {
"no_log": True,
"type": "str",
},
},
"type": "dict",
},
"dead_interval": {"type": "int"},
"hello_interval": {"type": "int"},
"retransmit_interval": {"type": "int"},
"transmit_delay": {"type": "int"},
},
"type": "list",
},
},
"type": "list",
},
"auto_cost": {
"options": {"reference_bandwidth": {"type": "int"}},
"type": "dict",
},
"default_information": {
"options": {
"originate": {
"options": {
"always": {"type": "bool"},
"metric": {"type": "int"},
"metric_type": {"type": "int"},
"route_map": {"type": "str"},
},
"type": "dict",
},
},
"type": "dict",
},
"default_metric": {"type": "int"},
"distance": {
"options": {
"global": {"type": "int"},
"ospf": {
"options": {
"external": {"type": "int"},
"inter_area": {"type": "int"},
"intra_area": {"type": "int"},
},
"type": "dict",
},
},
"type": "dict",
},
"log_adjacency_changes": {
"choices": ["detail"],
"type": "str",
},
"max_metric": {
"options": {
"router_lsa": {
"options": {
"administrative": {"type": "bool"},
"on_shutdown": {"type": "int"},
"on_startup": {"type": "int"},
},
"type": "dict",
},
},
"type": "dict",
},
"mpls_te": {
"options": {
"enabled": {"type": "bool"},
"router_address": {"type": "str"},
},
"type": "dict",
},
"neighbor": {
"elements": "dict",
"options": {
"neighbor_id": {"type": "str"},
"poll_interval": {"type": "int"},
"priority": {"type": "int"},
},
"type": "list",
},
"parameters": {
"options": {
"abr_type": {
"choices": [
"cisco",
"ibm",
"shortcut",
"standard",
],
"type": "str",
},
"opaque_lsa": {"type": "bool"},
"rfc1583_compatibility": {"type": "bool"},
"router_id": {"type": "str"},
},
"type": "dict",
},
"passive_interface": {
"elements": "str",
"type": "list",
},
"passive_interface_exclude": {
"elements": "str",
"type": "list",
},
"redistribute": {
"elements": "dict",
"options": {
"metric": {"type": "int"},
"metric_type": {"type": "int"},
"route_map": {"type": "str"},
"route_type": {
"choices": [
"bgp",
"connected",
"kernel",
"rip",
"static",
],
"type": "str",
},
},
"type": "list",
},
"route_map": {"elements": "str", "type": "list"},
"timers": {
"options": {
"refresh": {
"options": {"timers": {"type": "int"}},
"type": "dict",
},
"throttle": {
"options": {
"spf": {
"options": {
"delay": {"type": "int"},
"initial_holdtime": {"type": "int"},
"max_holdtime": {"type": "int"},
},
"type": "dict",
},
},
"type": "dict",
},
},
"type": "dict",
},
},
"type": "dict",
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"deleted",
"parsed",
"gathered",
"rendered",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py
index 61704345..77a17d79 100644
--- a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py
+++ b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py
@@ -1,105 +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)
#############################################
# 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 vyos_ospfv3 module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
class Ospfv3Args(object): # pylint: disable=R0903
"""The arg spec for the vyos_ospfv3 module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"config": {
"options": {
"areas": {
"elements": "dict",
"options": {
"area_id": {"type": "str"},
"export_list": {"type": "str"},
"import_list": {"type": "str"},
"interface": {
"aliases": ["interfaces"],
"type": "list",
"elements": "dict",
"options": {"name": {"type": "str"}},
},
"range": {
"elements": "dict",
"options": {
"address": {"type": "str"},
"advertise": {"type": "bool"},
"not_advertise": {"type": "bool"},
},
"type": "list",
},
},
"type": "list",
},
"parameters": {
"options": {"router_id": {"type": "str"}},
"type": "dict",
},
"redistribute": {
"elements": "dict",
"options": {
"route_map": {"type": "str"},
"route_type": {
"choices": [
"bgp",
"connected",
"kernel",
"ripng",
"static",
],
"type": "str",
},
},
"type": "list",
},
},
"type": "dict",
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"deleted",
"parsed",
"gathered",
"rendered",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py b/plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py
index b01a3e82..c74400ba 100644
--- a/plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py
+++ b/plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py
@@ -1,83 +1,82 @@
# -*- coding: utf-8 -*-
# Copyright 2024 Red Hat
# 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
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the
# cli_rm_builder.
#
# Manually editing this file is not advised.
#
# To update the argspec make the desired changes
# in the module docstring and re-run
# cli_rm_builder.
#
#############################################
"""
The arg spec for the vyos_prefix_lists module
"""
class Prefix_listsArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_prefix_lists module"""
argument_spec = {
"config": {
"type": "list",
"elements": "dict",
"options": {
"afi": {
"type": "str",
"choices": ["ipv4", "ipv6"],
"required": True,
},
"prefix_lists": {
"type": "list",
"elements": "dict",
"options": {
"name": {"type": "str", "required": True},
"description": {"type": "str"},
"entries": {
"type": "list",
"elements": "dict",
"options": {
"sequence": {"type": "int", "required": True},
"description": {"type": "str"},
"action": {
"type": "str",
"choices": ["permit", "deny"],
},
"ge": {"type": "int"},
"le": {"type": "int"},
"prefix": {"type": "str"},
},
},
},
},
},
},
"running_config": {"type": "str"},
"state": {
"type": "str",
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"gathered",
"rendered",
"parsed",
],
"default": "merged",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py b/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py
index 5b6d404d..58ffa3e5 100644
--- a/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py
+++ b/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py
@@ -1,241 +1,240 @@
# -*- coding: utf-8 -*-
# Copyright 2024 Red Hat
# 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
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the
# cli_rm_builder.
#
# Manually editing this file is not advised.
#
# To update the argspec make the desired changes
# in the module docstring and re-run
# cli_rm_builder.
#
#############################################
"""
The arg spec for the vyos_route_maps module
"""
class Route_mapsArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_route_maps module"""
argument_spec = {
"config": {
"type": "list",
"elements": "dict",
"options": {
"route_map": {"type": "str"},
"entries": {
"aliases": ["rules"],
"type": "list",
"elements": "dict",
"options": {
"sequence": {"type": "int"},
"call": {"type": "str"},
"description": {"type": "str"},
"action": {"type": "str", "choices": ["deny", "permit"]},
"continue_sequence": {"type": "int"},
"set": {
"type": "dict",
"options": {
"aggregator": {
"type": "dict",
"options": {
"ip": {"type": "str"},
"as": {"type": "str"},
},
},
"as_path_exclude": {"type": "str"},
"as_path_prepend": {"type": "str"},
"atomic_aggregate": {"type": "bool"},
"bgp_extcommunity_rt": {"type": "str"},
"comm_list": {
"type": "dict",
"options": {
"comm_list": {"type": "str"},
"delete": {"type": "bool"},
},
},
"community": {
"type": "dict",
"options": {"value": {"type": "str"}},
},
"extcommunity_rt": {"type": "str"},
"extcommunity_soo": {"type": "str"},
"extcommunity_bandwidth": {"type": "str"},
"extcommunity_bandwidth_non_transitive": {"type": "bool"},
"ip_next_hop": {"type": "str"},
"ipv6_next_hop": {
"type": "dict",
"options": {
"ip_type": {
"type": "str",
"choices": ["global", "local"],
},
"value": {"type": "str"},
},
},
"large_community": {"type": "str"},
"local_preference": {"type": "str"},
"metric": {"type": "str"},
"metric_type": {
"type": "str",
"choices": ["type-1", "type-2"],
},
"origin": {
"type": "str",
"choices": ["egp", "igp", "incomplete"],
},
"originator_id": {"type": "str"},
"src": {"type": "str"},
"tag": {"type": "str"},
"weight": {"type": "str"},
"table": {"type": "str"},
},
},
"match": {
"type": "dict",
"options": {
"as_path": {"type": "str"},
"community": {
"type": "dict",
"options": {
"community_list": {"type": "str"},
"exact_match": {"type": "bool"},
},
},
"extcommunity": {"type": "str"},
"interface": {"type": "str"},
"ip": {
"type": "dict",
"options": {
"address": {
"type": "dict",
"options": {
"list_type": {
"type": "str",
"choices": [
"access-list",
"prefix-list",
],
},
"value": {"type": "str"},
},
},
"next_hop": {
"type": "dict",
"options": {
"list_type": {
"type": "str",
"choices": [
"access-list",
"prefix-list",
],
},
"value": {"type": "str"},
},
},
"route_source": {
"type": "dict",
"options": {
"list_type": {
"type": "str",
"choices": [
"access-list",
"prefix-list",
],
},
"value": {"type": "str"},
},
},
},
},
"ipv6": {
"type": "dict",
"options": {
"address": {
"type": "dict",
"options": {
"list_type": {
"type": "str",
"choices": [
"access-list",
"prefix-list",
],
},
"value": {"type": "str"},
},
},
"next_hop": {"type": "str"},
},
},
"protocol": {
"type": "str",
"choices": [
"babel",
"bgp",
"connected",
"isis",
"kernel",
"ospf",
"ospfv3",
"rip",
"ripng",
"static",
"table",
"vnc",
],
},
"large_community_large_community_list": {
"type": "str",
},
"metric": {"type": "int"},
"origin": {
"type": "str",
"choices": ["ebgp", "ibgp", "incomplete"],
},
"peer": {"type": "str"},
"rpki": {
"type": "str",
"choices": ["notfound", "invalid", "valid"],
},
},
},
"on_match": {
"type": "dict",
"options": {
"next": {"type": "bool"},
"goto": {"type": "int"},
},
},
},
},
},
},
"running_config": {"type": "str"},
"state": {
"type": "str",
"choices": [
"deleted",
"merged",
"overridden",
"replaced",
"gathered",
"rendered",
"parsed",
],
"default": "merged",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/snmp_server/snmp_server.py b/plugins/module_utils/network/vyos/argspec/snmp_server/snmp_server.py
index b94c2639..53516c4f 100644
--- a/plugins/module_utils/network/vyos/argspec/snmp_server/snmp_server.py
+++ b/plugins/module_utils/network/vyos/argspec/snmp_server/snmp_server.py
@@ -1,211 +1,210 @@
# -*- coding: utf-8 -*-
# Copyright 2024 Red Hat
# 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
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the
# cli_rm_builder.
#
# Manually editing this file is not advised.
#
# To update the argspec make the desired changes
# in the module docstring and re-run
# cli_rm_builder.
#
#############################################
"""
The arg spec for the vyos_snmp_server module
"""
class Snmp_serverArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_snmp_server module"""
argument_spec = {
"config": {
"type": "dict",
"options": {
"communities": {
"type": "list",
"elements": "dict",
"options": {
"name": {"type": "str"},
"clients": {"type": "list", "elements": "str"},
"networks": {"type": "list", "elements": "str"},
"authorization_type": {
"type": "str",
"choices": ["ro", "rw"],
},
},
},
"contact": {"type": "str"},
"description": {"type": "str"},
"listen_addresses": {
"type": "list",
"elements": "dict",
"options": {
"address": {"type": "str"},
"port": {"type": "int"},
},
},
"location": {"type": "str"},
"smux_peer": {"type": "str"},
"trap_source": {"type": "str"},
"trap_target": {
"type": "dict",
"options": {
"address": {"type": "str"},
"community": {"type": "str"},
"port": {"type": "int"},
},
},
"snmp_v3": {
"type": "dict",
"options": {
"engine_id": {"type": "str"},
"groups": {
"type": "list",
"elements": "dict",
"options": {
"group": {"type": "str"},
"mode": {"type": "str", "choices": ["ro", "rw"]},
"seclevel": {
"type": "str",
"choices": ["auth", "priv"],
},
"view": {"type": "str"},
},
},
"trap_targets": {
"type": "list",
"elements": "dict",
"options": {
"address": {"type": "str"},
"authentication": {
"type": "dict",
"options": {
"type": {
"type": "str",
"choices": ["md5", "sha"],
},
"encrypted_key": {
"type": "str",
"no_log": True,
},
"plaintext_key": {
"type": "str",
"no_log": True,
},
},
},
"port": {"type": "int"},
"privacy": {
"type": "dict",
"options": {
"type": {
"type": "str",
"choices": ["des", "aes"],
},
"encrypted_key": {
"type": "str",
"no_log": True,
},
"plaintext_key": {
"type": "str",
"no_log": True,
},
},
},
"protocol": {
"type": "str",
"choices": ["tcp", "udp"],
},
"type": {
"type": "str",
"choices": ["inform", "trap"],
},
},
},
"users": {
"type": "list",
"elements": "dict",
"options": {
"user": {"type": "str"},
"authentication": {
"type": "dict",
"options": {
"type": {
"type": "str",
"choices": ["md5", "sha"],
},
"encrypted_key": {
"type": "str",
"no_log": True,
},
"plaintext_key": {
"type": "str",
"no_log": True,
},
},
},
"group": {"type": "str"},
"mode": {"type": "str", "choices": ["ro", "rw"]},
"privacy": {
"type": "dict",
"options": {
"type": {
"type": "str",
"choices": ["des", "aes"],
},
"encrypted_key": {
"type": "str",
"no_log": True,
},
"plaintext_key": {
"type": "str",
"no_log": True,
},
},
},
"tsm_key": {"type": "str", "no_log": True},
},
},
"views": {
"type": "list",
"elements": "dict",
"options": {
"view": {"type": "str"},
"oid": {"type": "str"},
"exclude": {"type": "str"},
"mask": {"type": "str"},
},
},
},
},
},
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"gathered",
"parsed",
"rendered",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py b/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py
index 365df48a..d8a4f11a 100644
--- a/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py
+++ b/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py
@@ -1,105 +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)
#############################################
# 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 vyos_static_routes module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
class Static_routesArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_static_routes module"""
def __init__(self, **kwargs):
pass
argument_spec = {
"config": {
"elements": "dict",
"options": {
"address_families": {
"elements": "dict",
"options": {
"afi": {
"choices": [
"ipv4",
"ipv6",
],
"required": True,
"type": "str",
},
"routes": {
"elements": "dict",
"options": {
"blackhole_config": {
"options": {
"distance": {"type": "int"},
"type": {"type": "str"},
},
"type": "dict",
},
"dest": {
"required": True,
"type": "str",
},
"next_hops": {
"elements": "dict",
"options": {
"admin_distance": {"type": "int"},
"enabled": {"type": "bool"},
"forward_router_address": {
"type": "str",
},
"interface": {"type": "str"},
},
"type": "list",
},
},
"type": "list",
},
},
"type": "list",
},
},
"type": "list",
},
"running_config": {"type": "str"},
"state": {
"choices": [
"merged",
"replaced",
"overridden",
"deleted",
"gathered",
"rendered",
"parsed",
],
"default": "merged",
"type": "str",
},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/vrf/vrf.py b/plugins/module_utils/network/vyos/argspec/vrf/vrf.py
index e24fd537..20947f9d 100644
--- a/plugins/module_utils/network/vyos/argspec/vrf/vrf.py
+++ b/plugins/module_utils/network/vyos/argspec/vrf/vrf.py
@@ -1,131 +1,130 @@
# -*- coding: utf-8 -*-
# Copyright 2024 Red Hat
# 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
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the
# cli_rm_builder.
#
# Manually editing this file is not advised.
#
# To update the argspec make the desired changes
# in the module docstring and re-run
# cli_rm_builder.
#
#############################################
"""
The arg spec for the vyos_vrf module
"""
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.bgp_global.bgp_global import (
Bgp_globalArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv2.ospfv2 import (
Ospfv2Args,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv3.ospfv3 import (
Ospfv3Args,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.static_routes.static_routes import (
Static_routesArgs,
)
class VrfArgs(object): # pylint: disable=R0903
"""The arg spec for the vyos_vrf module"""
bgp_argument_spec = Bgp_globalArgs.argument_spec["config"]
static_routes_argument_spec = Static_routesArgs.argument_spec["config"]
ospfv2_argument_spec = Ospfv2Args.argument_spec["config"]
ospfv3_argument_spec = Ospfv3Args.argument_spec["config"]
argument_spec = {
"config": {
"type": "dict",
"options": {
"bind_to_all": {"type": "bool", "default": False},
"instances": {
"type": "list",
"elements": "dict",
"options": {
"name": {"required": True, "type": "str"},
"description": {"type": "str"},
"disable": {
"aliases": ["disabled"],
"default": False,
"type": "bool",
},
"table_id": {"type": "int"},
"vni": {"type": "int"},
"address_family": {
"type": "list",
"elements": "dict",
"options": {
"afi": {
"type": "str",
"choices": ["ipv4", "ipv6"],
},
"disable_forwarding": {"type": "bool", "default": False},
"nht_no_resolve_via_default": {"type": "bool", "default": False},
"route_maps": {
"type": "list",
"elements": "dict",
"options": {
"rm_name": {"type": "str", "required": True},
"protocol": {
"type": "str",
"choices": [
"any",
"babel",
"bgp",
"eigrp",
"isis",
"ospf",
"rip",
"static",
],
},
},
},
},
},
"protocols": {
# "type": "list", # sanity
# "elements": "dict",
"type": "dict",
"options": {
"bgp": bgp_argument_spec,
"ospf": ospfv2_argument_spec,
"ospfv3": ospfv3_argument_spec,
"static": static_routes_argument_spec,
},
},
},
},
},
},
"state": {
"type": "str",
"choices": [
"deleted",
"merged",
"replaced",
"overridden",
"gathered",
"rendered",
"parsed",
],
"default": "merged",
},
"running_config": {"type": "str"},
} # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py b/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py
index 2d6481c2..f1494698 100644
--- a/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py
+++ b/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py
@@ -1,405 +1,404 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos_bgp_address_family config file.
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 its desired end-state is
created.
"""
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_address_family import (
Bgp_address_familyTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_address_family_14 import (
Bgp_address_familyTemplate14,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Bgp_address_family(ResourceModule):
"""
The vyos_bgp_address_family config class
"""
def __init__(self, module):
super(Bgp_address_family, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="bgp_address_family",
tmplt=Bgp_address_familyTemplate(),
)
self.parsers = []
def _validate_template(self):
version = get_os_version(self._module)
if LooseVersion(version) >= LooseVersion("1.4"):
self._tmplt = Bgp_address_familyTemplate14()
else:
self._tmplt = Bgp_address_familyTemplate()
def parse(self):
"""override parse to check template"""
self._validate_template()
return super().parse()
def get_parser(self, name):
"""get_parsers"""
self._validate_template()
return super().get_parser(name)
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
self._validate_template()
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = {}
haved = {}
if (
self.want.get("as_number") == self.have.get("as_number")
or not self.have
or LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
):
if self.want:
wantd = {self.want["as_number"]: self.want}
if self.have:
haved = {self.have["as_number"]: self.have}
else:
self._module.fail_json(msg="Only one bgp instance is allowed per device")
# turn all lists of dicts into dicts prior to merge
for entry in wantd, haved:
self._bgp_af_list_to_dict(entry)
# if state is merged, merge want onto have and then compare
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":
for k, have in haved.items():
self._delete_af(wantd, have)
wantd = {}
if self.state == "overridden":
for k, have in haved.items():
if k not in wantd:
self._compare(want={}, have=have)
for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
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 Bgp_address_family network resource.
"""
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
self._compare_asn(want, have)
self._compare_af(want, have)
self._compare_neighbors(want, have)
# Do the negation first
command_set = []
for cmd in self.commands:
if cmd not in command_set:
if "delete" in cmd:
command_set.insert(0, cmd)
else:
command_set.append(cmd)
self.commands = command_set
def _compare_af(self, want, have):
waf = want.get("address_family", {})
haf = have.get("address_family", {})
for name, entry in waf.items():
self._compare_lists(
entry,
have=haf.get(name, {}),
as_number=want["as_number"],
afi=name,
)
for name, entry in haf.items():
if name not in waf.keys() and self.state == "replaced":
continue
self._compare_lists({}, entry, as_number=have["as_number"], afi=name)
def _delete_af(self, want, have):
for as_num, entry in want.items():
for afi, af_entry in entry.get("address_family", {}).items():
if have.get("address_family"):
for hafi, hentry in have["address_family"].items():
if hafi == afi:
self.commands.append(
self._tmplt.render(
{
"as_number": as_num,
"address_family": {"afi": afi},
},
"address_family",
True,
),
)
for neigh, neigh_entry in entry.get("neighbors", {}).items():
if have.get("neighbors"):
for hneigh, hnentry in have["neighbors"].items():
if hneigh == neigh:
if not neigh_entry.get("address_family"):
self.commands.append(
self._tmplt.render(
{
"as_number": as_num,
"neighbors": {"neighbor_address": neigh},
},
"neighbors",
True,
),
)
else:
for k in neigh_entry["address_family"].keys():
if (
hnentry.get("address_family")
and k in hnentry["address_family"].keys()
):
self.commands.append(
self._tmplt.render(
{
"as_number": as_num,
"neighbors": {
"neighbor_address": neigh,
"address_family": {"afi": k},
},
},
"neighbors.address_family",
True,
),
)
def _compare_neighbors(self, want, have):
parsers = [
"neighbors.allowas_in",
"neighbors.as_override",
"neighbors.attribute_unchanged.as_path",
"neighbors.attribute_unchanged.med",
"neighbors.attribute_unchanged.next_hop",
"neighbors.capability_dynamic",
"neighbors.capability_orf",
"neighbors.default_originate",
"neighbors.distribute_list",
"neighbors.prefix_list",
"neighbors.filter_list",
"neighbors.maximum_prefix",
"neighbors.nexthop_local",
"neighbors.nexthop_self",
"neighbors.peer_group",
"neighbors.remove_private_as",
"neighbors.route_map",
"neighbors.route_reflector_client",
"neighbors.route_server_client",
"neighbors.soft_reconfiguration",
"neighbors.unsuppress_map",
"neighbors.weight",
]
wneigh = want.get("neighbors", {})
hneigh = have.get("neighbors", {})
for name, entry in wneigh.items():
for afi, af_entry in entry.get("address_family").items():
for k, val in af_entry.items():
w = {
"as_number": want["as_number"],
"neighbors": {
"neighbor_address": name,
"address_family": {"afi": afi, k: val},
},
}
h = {}
if hneigh.get(name):
if hneigh[name]["address_family"].get(afi):
if hneigh[name]["address_family"][afi].get(k):
h = {
"as_number": want["as_number"],
"neighbors": {
"neighbor_address": name,
"address_family": {
"afi": afi,
k: hneigh[name]["address_family"][afi].pop(k, {}),
},
},
}
self.compare(
parsers=parsers,
want=w,
have=h,
)
for name, entry in hneigh.items():
if name not in wneigh.keys():
# remove surplus config for overridden and replaced
if self.state != "replaced":
self.commands.append(
self._tmplt.render(
{
"as_number": have["as_number"],
"neighbors": {"neighbor_address": name},
},
"neighbors",
True,
),
)
continue
for hafi, haf_entry in entry.get("address_family").items():
# remove surplus configs for given neighbor - replace and overridden
for k, val in haf_entry.items():
h = {
"as_number": have["as_number"],
"neighbors": {
"neighbor_address": name,
"address_family": {"afi": hafi, k: val},
},
}
self.compare(parsers=parsers, want={}, have=h)
def _compare_lists(self, want, have, as_number, afi):
parsers = [
"aggregate_address",
"network",
"network.backdoor",
"network.path_limit",
"network.route_map",
"redistribute",
"redistribute.metric",
"redistribute.route_map",
"redistribute.table",
]
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
delete_asn = ""
else:
delete_asn = " " + str(as_number)
for attrib in ["redistribute", "networks", "aggregate_address"]:
wdict = want.pop(attrib, {})
hdict = have.pop(attrib, {})
for key, entry in wdict.items():
if entry != hdict.get(key, {}):
self.compare(
parsers=parsers,
want={
"as_number": as_number,
"address_family": {"afi": afi, attrib: entry},
},
have={
"as_number": as_number,
"address_family": {
"afi": afi,
attrib: hdict.pop(key, {}),
},
},
)
hdict.pop(key, {})
# remove remaining items in have for replaced
if not wdict and hdict:
attrib = re.sub("_", "-", attrib)
attrib = re.sub("networks", "network", attrib)
self.commands.append(
"delete protocols bgp"
+ delete_asn
+ " "
+ "address-family "
+ afi
+ " "
+ attrib,
)
hdict = {}
for key, entry in hdict.items():
self.compare(
parsers=parsers,
want={},
have={
"as_number": as_number,
"address_family": {"afi": afi, attrib: entry},
},
)
# de-duplicate child commands if parent command is present
for val in self.commands:
for val2 in self.commands:
if val != val2 and val2.startswith(val):
self.commands.remove(val2)
def _compare_asn(self, want, have):
if want.get("as_number") and not have.get("as_number"):
self.commands.append(
"set protocols bgp " + "system-as " + str(want.get("as_number")),
)
def _bgp_af_list_to_dict(self, entry):
for name, proc in entry.items():
if "address_family" in proc:
af_dict = {}
for entry in proc.get("address_family"):
if "networks" in entry:
network_dict = {}
for n_entry in entry.get("networks", []):
network_dict.update({n_entry["prefix"]: n_entry})
entry["networks"] = network_dict
if "aggregate_address" in entry:
agg_dict = {}
for a_entry in entry.get("aggregate_address", []):
agg_dict.update({a_entry["prefix"]: a_entry})
entry["aggregate_address"] = agg_dict
if "redistribute" in entry:
redis_dict = {}
for r_entry in entry.get("redistribute", []):
proto_key = r_entry.get("protocol", "table")
redis_dict.update({proto_key: r_entry})
entry["redistribute"] = redis_dict
for af in proc.get("address_family"):
af_dict.update({af["afi"]: af})
proc["address_family"] = af_dict
if "neighbors" in proc:
neigh_dict = {}
for entry in proc.get("neighbors", []):
neigh_dict.update({entry["neighbor_address"]: entry})
proc["neighbors"] = neigh_dict
self._bgp_af_list_to_dict(proc["neighbors"])
diff --git a/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py b/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py
index 075c8fd2..2d7d3e63 100644
--- a/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py
+++ b/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py
@@ -1,416 +1,415 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos_bgp_global config file.
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 its desired end-state is
created.
"""
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_global import (
Bgp_globalTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_global_14 import (
Bgp_globalTemplate14,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Bgp_global(ResourceModule):
"""
The vyos_bgp_global config class
"""
def __init__(self, module):
super(Bgp_global, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="bgp_global",
tmplt=Bgp_globalTemplate(),
)
self.parsers = []
def _validate_template(self):
version = get_os_version(self._module)
if LooseVersion(version) >= LooseVersion("1.4"):
self._tmplt = Bgp_globalTemplate14()
else:
self._tmplt = Bgp_globalTemplate()
def parse(self):
"""override parse to check template"""
self._validate_template()
return super().parse()
def get_parser(self, name):
"""get_parsers"""
self._validate_template()
return super().get_parser(name)
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
version = get_os_version(self._module)
if LooseVersion(version) >= LooseVersion("1.4"):
self._asn_mod = ""
else:
self._asn_mod = " " + str(self.have.get("as_number"))
self._validate_template()
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = {}
haved = {}
if (
self.want.get("as_number") == self.have.get("as_number")
or not self.have
or LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
):
if self.want:
wantd = {self.want["as_number"]: self.want}
if self.have:
haved = {self.have["as_number"]: self.have}
else:
self._module.fail_json(msg="Only one bgp instance is allowed per device")
# turn all lists of dicts into dicts prior to merge
for entry in wantd, haved:
self._bgp_global_list_to_dict(entry)
# if state is merged, merge want onto have and then compare
if self.state == "merged":
wantd = dict_merge(haved, wantd)
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "purged":
h_del = {}
for k, v in haved.items():
if k in wantd or not wantd:
h_del.update({k: v})
for num, entry in h_del.items():
self.commands.append(self._tmplt.render({"as_number": num}, "router", True))
wantd = {}
if self.state == "deleted":
self._compare(want={}, have=self.have)
wantd = {}
for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
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 Bgp_global network resource.
"""
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
self._compare_asn(want, have)
parsers = ["maximum_paths", "timers"]
self._compare_neighbor(want, have)
self._compare_bgp_params(want, have)
for name, entry in want.items():
if name != "as_number":
self.compare(
parsers=parsers,
want={"as_number": want["as_number"], name: entry},
have={
"as_number": want["as_number"],
name: have.pop(name, {}),
},
)
for name, entry in have.items():
if name != "as_number":
self.compare(
parsers=parsers,
want={},
have={"as_number": have["as_number"], name: entry},
)
# Do the negation first
command_set = []
for cmd in self.commands:
if cmd not in command_set:
if "delete" in cmd:
command_set.insert(0, cmd)
else:
command_set.append(cmd)
self.commands = command_set
def _compare_neighbor(self, want, have):
parsers = [
"neighbor.advertisement_interval",
"neighbor.allowas_in",
"neighbor.as_override",
"neighbor.attribute_unchanged.as_path",
"neighbor.attribute_unchanged.med",
"neighbor.attribute_unchanged.next_hop",
"neighbor.capability_dynamic",
"neighbor.capability_orf",
"neighbor.default_originate",
"neighbor.description",
"neighbor.disable_capability_negotiation",
"neighbor.disable_connected_check",
"neighbor.disable_send_community",
"neighbor.distribute_list",
"neighbor.ebgp_multihop",
"neighbor.filter_list",
"neighbor.local_as",
"neighbor.maximum_prefix",
"neighbor.nexthop_self",
"neighbor.override_capability",
"neighbor.passive",
"neighbor.password",
"neighbor.peer_group_name",
"neighbor.port",
"neighbor.prefix_list",
"neighbor.remote_as",
"neighbor.remove_private_as",
"neighbor.route_map",
"neighbor.route_reflector_client",
"neighbor.route_server_client",
"neighbor.shutdown",
"neighbor.soft_reconfiguration",
"neighbor.strict_capability_match",
"neighbor.unsuppress_map",
"neighbor.update_source",
"neighbor.weight",
"neighbor.ttl_security",
"neighbor.timers",
]
wneigh = want.pop("neighbor", {})
hneigh = have.pop("neighbor", {})
self._compare_neigh_lists(wneigh, hneigh)
for name, entry in wneigh.items():
for k, v in entry.items():
if k == "address":
continue
if hneigh.get(name):
h = {"address": name, k: hneigh[name].pop(k, {})}
else:
h = {}
self.compare(
parsers=parsers,
want={
"as_number": want["as_number"],
"neighbor": {"address": name, k: v},
},
have={"as_number": want["as_number"], "neighbor": h},
)
for name, entry in hneigh.items():
if name not in wneigh.keys():
if self._check_af(name):
msg = "Use the _bgp_address_family module to delete the address_family under neighbor {0}, before replacing/deleting the neighbor.".format(
name,
)
self._module.fail_json(msg=msg)
else:
self.commands.append(
"delete protocols bgp" + self._asn_mod + " neighbor " + name,
)
continue
for k, v in entry.items():
self.compare(
parsers=parsers,
want={},
have={
"as_number": have["as_number"],
"neighbor": {"address": name, k: v},
},
)
def _compare_bgp_params(self, want, have):
parsers = [
"bgp_params.always_compare_med",
"bgp_params.bestpath.as_path",
"bgp_params.bestpath.compare_routerid",
"bgp_params.bestpath.med",
"bgp_params.cluster_id",
"bgp_params.confederation",
"bgp_params.dampening_half_life",
"bgp_params.dampening_max_suppress_time",
"bgp_params.dampening_re_use",
"bgp_params.dampening_start_suppress_time",
"bgp_params.default",
"bgp_params.deterministic_med",
"bgp_params.disbale_network_import_check",
"bgp_params.enforce_first_as",
"bgp_params.graceful_restart",
"bgp_params.log_neighbor_changes",
"bgp_params.no_client_to_client_reflection",
"bgp_params.no_fast_external_failover",
"bgp_params.routerid",
"bgp_params.scan_time",
]
wbgp = want.pop("bgp_params", {})
hbgp = have.pop("bgp_params", {})
for name, entry in wbgp.items():
if name == "confederation":
if entry != hbgp.pop(name, {}):
self.addcmd(
{
"as_number": want["as_number"],
"bgp_params": {name: entry},
},
"bgp_params.confederation",
False,
)
elif name == "distance":
if entry != hbgp.pop(name, {}):
distance_parsers = [
"bgp_params.distance.global",
"bgp_params.distance.prefix",
]
for distance_type in entry:
self.compare(
parsers=distance_parsers,
want={
"as_number": want["as_number"],
"bgp_params": {name: distance_type},
},
have={
"as_number": want["as_number"],
"bgp_params": {name: hbgp.pop(name, {})},
},
)
else:
self.compare(
parsers=parsers,
want={
"as_number": want["as_number"],
"bgp_params": {name: entry},
},
have={
"as_number": want["as_number"],
"bgp_params": {name: hbgp.pop(name, {})},
},
)
if not wbgp and hbgp:
self.commands.append("delete protocols bgp" + self._asn_mod + " parameters")
hbgp = {}
for name, entry in hbgp.items():
if name == "confederation":
self.commands.append(
"delete protocols bgp" + self._asn_mod + " parameters confederation",
)
elif name == "distance":
distance_parsers = [
"bgp_params.distance.global",
"bgp_params.distance.prefix",
]
self.compare(
parsers=distance_parsers,
want={},
have={
"as_number": have["as_number"],
"bgp_params": {name: entry[0]},
},
)
else:
self.compare(
parsers=parsers,
want={},
have={
"as_number": have["as_number"],
"bgp_params": {name: entry},
},
)
def _compare_neigh_lists(self, want, have):
for attrib in [
"distribute_list",
"filter_list",
"prefix_list",
"route_map",
]:
wdict = want.pop(attrib, {})
hdict = have.pop(attrib, {})
for key, entry in wdict.items():
if entry != hdict.pop(key, {}):
self.addcmd(entry, "neighbor.{0}".format(attrib), False)
# remove remaining items in have for replaced
for entry in hdict.values():
self.addcmd(entry, "neighbor.{0}".format(attrib), True)
def _bgp_global_list_to_dict(self, entry):
for name, proc in entry.items():
if "neighbor" in proc:
neigh_dict = {}
for entry in proc.get("neighbor", []):
neigh_dict.update({entry["address"]: entry})
proc["neighbor"] = neigh_dict
if "network" in proc:
network_dict = {}
for entry in proc.get("network", []):
network_dict.update({entry["address"]: entry})
proc["network"] = network_dict
if "aggregate_address" in proc:
agg_dict = {}
for entry in proc.get("aggregate_address", []):
agg_dict.update({entry["prefix"]: entry})
proc["aggregate_address"] = agg_dict
if "redistribute" in proc:
redis_dict = {}
for entry in proc.get("redistribute", []):
redis_dict.update({entry["protocol"]: entry})
proc["redistribute"] = redis_dict
def _compare_asn(self, want, have):
if want.get("as_number") and not have.get("as_number"):
self.commands.append(
"set protocols bgp " + "system-as" + " " + str(want.get("as_number")),
)
def _check_af(self, neighbor):
af_present = False
if self._connection:
config_lines = self._get_config(self._connection).splitlines()
for line in config_lines:
if neighbor in line:
if "address-family" in line:
af_present = True
return af_present
def _get_config(self, connection):
return connection.get(
'show configuration commands | match "set protocols bgp .*neighbor"',
)
diff --git a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
index d4e173c1..fcacb913 100644
--- a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
@@ -1,810 +1,812 @@
#
# -*- 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 vyos_firewall_global 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
remove_empties,
to_list,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
in_target_not_none,
list_diff_want_only,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import (
get_os_version,
load_config,
)
class Firewall_global(ConfigBase):
"""
The vyos_firewall_global class
"""
gather_subset = ["!all", "!min"]
gather_network_resources = ["firewall_global"]
def __init__(self, module):
super(Firewall_global, self).__init__(module)
def get_firewall_global_facts(self, data=None):
"""Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
)
firewall_global_facts = facts["ansible_network_resources"].get("firewall_global")
if not firewall_global_facts:
return []
return firewall_global_facts
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
try:
self._module.params["comment"]
except KeyError:
comment = []
else:
comment = self._module.params["comment"]
if self.state in self.ACTION_STATES:
existing_firewall_global_facts = self.get_firewall_global_facts()
else:
existing_firewall_global_facts = []
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_firewall_global_facts))
if commands and self._module._diff:
commit = not self._module.check_mode
diff = load_config(self._module, commands, commit=commit, comment=comment)
if diff:
result["diff"] = {"prepared": str(diff)}
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_firewall_global_facts = self.get_firewall_global_facts()
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
result["parsed"] = self.get_firewall_global_facts(data=running_config)
else:
changed_firewall_global_facts = []
if self.state in self.ACTION_STATES:
result["before"] = existing_firewall_global_facts
if result["changed"]:
result["after"] = changed_firewall_global_facts
elif self.state == "gathered":
result["gathered"] = changed_firewall_global_facts
result["warnings"] = warnings
return result
def set_config(self, existing_firewall_global_facts):
"""Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_firewall_global_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, w, h):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "rendered") and not w:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "deleted":
commands.extend(self._state_deleted(want=None, have=h))
elif w:
if self.state == "merged" or self.state == "rendered":
commands.extend(self._state_merged(w, h))
elif self.state == "replaced":
commands.extend(self._state_replaced(w, h))
return commands
def _state_replaced(self, w, h):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if h:
commands.extend(self._state_deleted(h, w))
commands.extend(self._state_merged(w, h))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
commands.extend(self._add_global_attr(want, have))
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
b_set = (
"config_trap",
"validation",
"log_martians",
"syn_cookies",
"twa_hazards_protection",
)
if want:
for key, val in want.items():
if val and key in b_set and not have:
commands.append(self._form_attr_cmd(attr=key, opr=False))
elif val and key in b_set and have and key in have and have[key] != val:
commands.append(self._form_attr_cmd(attr=key, opr=False))
else:
commands.extend(self._render_attr_config(want, have, key))
elif not want and have:
commands.append(self._compute_command(opr=False))
elif have:
for key, val in have.items():
if val and key in b_set:
commands.append(self._form_attr_cmd(attr=key, opr=False))
else:
commands.extend(self._render_attr_config(want, have, key))
return commands
def _render_attr_config(self, w, h, key, opr=False):
"""
This function invoke the function to extend commands
based on the key.
:param w: the desired configuration.
:param h: the current configuration.
:param key: attribute name
:param opr: operation
:return: list of commands
"""
commands = []
if key == "ping":
commands.extend(self._render_ping(key, w, h, opr=opr))
elif key == "group":
commands.extend(self._render_group(key, w, h, opr=opr))
elif key == "state_policy":
commands.extend(self._render_state_policy(key, w, h, opr=opr))
elif key == "route_redirects":
commands.extend(self._render_route_redirects(key, w, h, opr=opr))
return commands
def _add_global_attr(self, w, h, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for firewall_global attributes.
:param w: the desired config.
:param h: the target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
w_fg = deepcopy(remove_empties(w))
l_set = (
"config_trap",
"validation",
"log_martians",
"syn_cookies",
"twa_hazards_protection",
)
if w_fg:
for key, val in w_fg.items():
if opr and key in l_set and not (h and self._is_w_same(w_fg, h, key)):
commands.append(
self._form_attr_cmd(attr=key, val=self._bool_to_str(val), opr=opr),
)
elif not opr:
if key and self._is_del(l_set, h):
commands.append(
self._form_attr_cmd(attr=key, key=self._bool_to_str(val), opr=opr),
)
continue
if key in l_set and not self._in_target(h, key) and not self._is_del(l_set, h):
commands.append(
self._form_attr_cmd(attr=key, val=self._bool_to_str(val), opr=opr),
)
else:
commands.extend(self._render_attr_config(w_fg, h, key, opr))
return commands
def _render_ping(self, attr, w, h, opr):
"""
This function forms the commands for 'ping' attributes based on the 'opr'.
:param attr: attribute name.
:param w: the desired configuration.
:param h: the target config.
:param opr: True/False.
:return: generated list of commands.
"""
commands = []
h_ping = {}
l_set = ("all", "broadcast")
if h:
h_ping = h.get(attr) or {}
if self._is_root_del(w[attr], h_ping, attr):
for item, value in h[attr].items():
if not opr and item in l_set:
commands.append(self._form_attr_cmd(attr=item, opr=opr))
elif w[attr]:
if h and attr in h.keys():
h_ping = h.get(attr) or {}
for item, value in w[attr].items():
if (
opr
and item in l_set
and not (h_ping and self._is_w_same(w[attr], h_ping, item))
):
commands.append(
self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr),
)
elif (
not opr
and item in l_set
and not (h_ping and self._is_w_same(w[attr], h_ping, item))
):
commands.append(self._form_attr_cmd(attr=item, opr=opr))
return commands
def _render_group(self, attr, w, h, opr):
"""
This function forms the commands for 'group' attribute based on the 'opr'.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param opr: True/False.
:return: generated list of commands.
"""
commands = []
h_grp = {}
if not opr and self._is_root_del(h, w, attr):
commands.append(self._form_attr_cmd(attr=attr, opr=opr))
else:
if h:
h_grp = h.get("group") or {}
if w:
commands.extend(self._render_grp_mem("port_group", w["group"], h_grp, opr))
commands.extend(self._render_grp_mem("address_group", w["group"], h_grp, opr))
commands.extend(self._render_grp_mem("network_group", w["group"], h_grp, opr))
return commands
def _render_grp_mem(self, attr, w, h, opr):
"""
This function forms the commands for group list/members attributes based on the 'opr'.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param opr: True/False.
:return: generated list of commands.
"""
commands = []
h_grp = []
w_grp = []
l_set = ("name", "description")
if w:
w_grp = w.get(attr) or []
if h:
h_grp = h.get(attr) or []
if w_grp:
for want in w_grp:
h = self.search_attrib_in_have(h_grp, want, "name")
if "afi" in want and want["afi"] == "ipv6":
cmd = self._compute_command(key="group", attr="ipv6-" + attr, opr=opr)
else:
cmd = self._compute_command(key="group", attr=attr, opr=opr)
for key, val in want.items():
if val:
if opr and key in l_set and not (h and self._is_w_same(want, h, key)):
if key == "name":
commands.append(cmd + " " + str(val))
else:
commands.append(
cmd
+ " "
+ want["name"]
+ " "
+ key
+ " '"
+ str(want[key])
+ "'",
)
elif not opr and key in l_set:
if key == "name" and self._is_grp_del(h, want, key):
- if len(commands) > 0 and commands[-1] == cmd + " " + want["name"] + " " + self._grp_type(
+ if len(commands) > 0 and commands[-1] == cmd + " " + want[
+ "name"
+ ] + " " + self._grp_type(
attr,
):
commands.pop()
commands.append(cmd + " " + want["name"])
continue
if not (h and in_target_not_none(h, key)) and not self._is_grp_del(
h,
want,
"name",
):
commands.append(cmd + " " + want["name"] + " " + key)
elif key == "members":
commands.extend(
self._render_ports_addrs(
key,
want,
h,
opr,
cmd,
want["name"],
attr,
),
)
return commands
def _render_ports_addrs(self, attr, w, h, opr, cmd, name, type):
"""
This function forms the commands for port/address/network group members
based on the 'opr'.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param cmd: commands to be prepend.
:param name: name of group.
:param type: group type.
:return: generated list of commands.
"""
commands = []
have = []
if w:
want = w.get(attr) or []
if h:
have = h.get(attr) or []
if want:
if opr:
members = list_diff_want_only(want, have)
for member in members:
commands.append(
cmd
+ " "
+ name
+ " "
+ self._grp_type(type)
+ " "
+ member[self._get_mem_type(type)],
)
elif not opr and have:
members = list_diff_want_only(want, have)
for member in members:
commands.append(
cmd
+ " "
+ name
+ " "
+ self._grp_type(type)
+ " "
+ member[self._get_mem_type(type)],
)
elif not opr and not have:
commands.append(
cmd + " " + name + " " + self._grp_type(type),
)
return commands
def _get_mem_type(self, group):
"""
This function returns the member type
based on the type of group.
"""
return "port" if group == "port_group" else "address"
def _render_state_policy(self, attr, w, h, opr):
"""
This function forms the commands for 'state-policy' attributes
based on the 'opr'.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param opr: True/False.
:return: generated list of commands.
"""
commands = []
have = []
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
l_set = ("log", "action", "connection_type", "log_level")
else:
l_set = ("log", "action", "connection_type")
if not opr and self._is_root_del(h, w, attr):
commands.append(self._form_attr_cmd(attr=attr, opr=opr))
else:
w_sp = deepcopy(remove_empties(w))
want = w_sp.get(attr) or []
if h:
have = h.get(attr) or []
if want:
for w in want:
h = self.search_attrib_in_have(have, w, "connection_type")
for key, val in w.items():
if val and key != "connection_type":
if opr and key in l_set and not (h and self._is_w_same(w, h, key)):
if key == "log" and LooseVersion(
get_os_version(self._module),
) >= LooseVersion("1.4"):
commands.append(
self._form_attr_cmd(
key=attr + " " + w["connection_type"],
attr=key,
opr=opr,
),
)
else:
commands.append(
self._form_attr_cmd(
key=attr + " " + w["connection_type"],
attr=key,
val=self._bool_to_str(val),
opr=opr,
),
)
elif not opr and key in l_set:
if not h:
commands.append(
self._form_attr_cmd(
attr=attr + " " + w["connection_type"],
opr=opr,
),
)
break # delete the whole thing and move on
if (not self._in_target(h, key) or h[key] is None) and (
self._in_target(w, key) and w[key]
):
# delete if not being replaced and value currently exists
commands.append(
self._form_attr_cmd(
attr=attr + " " + w["connection_type"] + " " + key,
val=self._bool_to_str(val),
opr=opr,
),
)
return commands
def _render_route_redirects(self, attr, w, h, opr):
"""
This function forms the commands for 'route_redirects' attributes based on the 'opr'.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param opr: True/False.
:return: generated list of commands.
"""
commands = []
have = []
l_set = ("afi", "ip_src_route")
if w:
want = w.get(attr) or []
if h:
have = h.get(attr) or []
if want:
for w in want:
h = self.search_attrib_in_have(have, w, "afi")
if "afi" in w:
afi = w["afi"]
else:
if h and "afi" in h:
afi = h["afi"]
else:
afi = None
afi = None
for key, val in w.items():
if val is not None and key != "afi":
if opr and key in l_set and not (h and self._is_w_same(w, h, key)):
commands.append(
self._form_attr_cmd(
attr=key,
val=self._bool_to_str(val),
opr=opr,
type=afi,
),
)
elif not opr and key in l_set:
if self._is_del(l_set, h):
commands.append(
self._form_attr_cmd(
attr=key,
val=self._bool_to_str(val),
opr=opr,
type=afi,
),
)
continue
if not (h and self._in_target(h, key)) and not self._is_del(l_set, h):
commands.append(
self._form_attr_cmd(
attr=key,
val=self._bool_to_str(val),
opr=opr,
type=afi,
),
)
elif key == "icmp_redirects":
commands.extend(self._render_icmp_redirects(key, w, h, opr))
return commands
def _render_icmp_redirects(self, attr, w, h, opr):
"""
This function forms the commands for 'icmp_redirects' attributes
based on the 'opr'.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param opr: True/False.
:return: generated list of commands.
"""
commands = []
h_red = {}
l_set = ("send", "receive")
if w and "afi" in w:
afi = w["afi"]
else:
if h and "afi" in h:
afi = h["afi"]
else:
afi = None
if w[attr]:
if h and attr in h.keys():
h_red = h.get(attr) or {}
for item, value in w[attr].items():
if opr and item in l_set and not (h_red and self._is_w_same(w[attr], h_red, item)):
commands.append(
self._form_attr_cmd(
attr=item,
val=self._bool_to_str(value),
opr=opr,
type=afi,
),
)
elif (
not opr
and item in l_set
and not (h_red and self._is_w_same(w[attr], h_red, item))
):
commands.append(self._form_attr_cmd(attr=item, opr=opr, type=afi))
return commands
def search_attrib_in_have(self, have, want, attr):
"""
This function returns the attribute if it is present in target config.
:param have: the target config.
:param want: the desired config.
:param attr: attribute name .
:return: attribute/None
"""
if have:
for h in have:
if h[attr] == want[attr]:
return h
return None
def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True, type=None):
"""
This function forms the command for leaf attribute.
:param key: parent key.
:param attr: attribute name
:param value: value
:param opr: True/False.
:param type: AF type of attribute.
:return: generated command.
"""
command = self._compute_command(
key=key,
attr=self._map_attrib(attr, type=type),
val=val,
opr=opr,
)
return command
def _compute_command(self, key=None, attr=None, val=None, remove=False, opr=True):
"""
This function construct the add/delete command based on passed attributes.
:param key: parent key.
:param attr: attribute name
:param value: value
:param remove: True/False.
:param opr: True/False.
:return: generated command.
"""
if remove or not opr:
cmd = "delete firewall "
else:
cmd = "set firewall "
if (
attr
and key != "group"
and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
):
cmd += "global-options "
if key:
cmd += key.replace("_", "-") + " "
if attr:
cmd += attr.replace("_", "-")
if val and opr:
if key == "state_policy" and LooseVersion(get_os_version(self._module)) >= LooseVersion(
"1.4",
):
cmd += ""
else:
cmd += " '" + str(val) + "'"
return cmd.strip()
def _bool_to_str(self, val):
"""
This function converts the bool value into string.
:param val: bool value.
:return: enable/disable.
"""
return "enable" if str(val) == "True" else "disable" if str(val) == "False" else val
def _grp_type(self, val):
"""
This function returns the group member type based on value argument.
:param val: value.
:return: member type.
"""
return (
"address" if val == "address_group" else "network" if val == "network_group" else "port"
)
def _is_w_same(self, w, h, key):
"""
This function checks whether the key value is same in desired and
target config dictionary.
:param w: base config.
:param h: target config.
:param key:attribute name.
:return: True/False.
"""
return True if h and key in h and h[key] == w[key] else False
def _in_target(self, h, key):
"""
This function checks whether the target exist and key present in target config.
:param h: target config.
:param key: attribute name.
:return: True/False.
"""
return True if h and key in h else False
def _is_grp_del(self, w, h, key):
"""
This function checks whether group needed to be deleted based on
desired and target configs.
:param w: the desired config.
:param h: the target config.
:param key: group name.
:return: True/False.
"""
return True if h and key in h and (not w or key not in w or not w[key]) else False
def _is_root_del(self, w, h, key):
"""
This function checks whether a root attribute which can have
further child attributes needed to be deleted.
:param w: the desired config.
:param h: the target config.
:param key: attribute name.
:return: True/False.
"""
return True if h and key in h and (not w or key not in w or not w[key]) else False
def _is_del(self, b_set, h, key="number"):
"""
This function checks whether attribute needs to be deleted
when operation is false and attribute present in present target config.
:param b_set: attribute set.
:param h: target config.
:param key: number.
:return: True/False.
"""
return key in b_set and not self._in_target(h, key)
def _map_attrib(self, attrib, type=None):
"""
- This function construct the regex string.
- replace the underscore with hyphen.
:param attrib: attribute
:return: regex string
"""
regex = attrib.replace("_", "-")
if attrib == "send":
if type == "ipv6":
regex = "ipv6-send-redirects"
else:
regex = "send-redirects"
elif attrib == "ip_src_route":
if type == "ipv6":
regex = "ipv6-src-route"
elif attrib == "receive":
if type == "ipv6":
regex = "ipv6-receive-redirects"
else:
regex = "receive-redirects"
elif attrib == "disabled":
regex = "disable"
elif attrib == "all":
regex = "all-ping"
elif attrib == "broadcast":
regex = "broadcast-ping"
elif attrib == "validation":
regex = "source-validation"
return regex
diff --git a/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py b/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py
index 85a8042f..ec1aaef1 100644
--- a/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py
@@ -1,431 +1,431 @@
#
# -*- 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 vyos_firewall_interfaces 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
remove_empties,
search_obj_in_list,
to_list,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
get_interface_type,
)
class Firewall_interfaces(ConfigBase):
"""
The vyos_firewall_interfaces class
"""
gather_subset = [
"!all",
"!min",
]
gather_network_resources = [
"firewall_interfaces",
]
def __init__(self, module):
super(Firewall_interfaces, self).__init__(module)
def get_firewall_interfaces_facts(self, data=None):
"""Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
)
firewall_interfaces_facts = facts["ansible_network_resources"].get("firewall_interfaces")
if not firewall_interfaces_facts:
return []
return firewall_interfaces_facts
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
if self.state in self.ACTION_STATES:
existing_firewall_interfaces_facts = self.get_firewall_interfaces_facts()
else:
existing_firewall_interfaces_facts = []
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_firewall_interfaces_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_firewall_interfaces_facts = self.get_firewall_interfaces_facts()
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
result["parsed"] = self.get_firewall_interfaces_facts(data=running_config)
else:
changed_firewall_interfaces_facts = []
if self.state in self.ACTION_STATES:
result["before"] = existing_firewall_interfaces_facts
if result["changed"]:
result["after"] = changed_firewall_interfaces_facts
elif self.state == "gathered":
result["gathered"] = changed_firewall_interfaces_facts
result["warnings"] = warnings
return result
def set_config(self, existing_firewall_interfaces_facts):
"""Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_firewall_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, w, h):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "overridden", "rendered") and not w:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "overridden":
commands.extend(self._state_overridden(w, h))
elif self.state == "deleted":
commands.extend(self._state_deleted(w, h))
elif w:
if self.state == "merged" or self.state == "rendered":
commands.extend(self._state_merged(w, h))
elif self.state == "replaced":
commands.extend(self._state_replaced(w, h))
return commands
def _state_replaced(self, want, have):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
for h in have:
w = search_obj_in_list(h["name"], want)
commands.extend(self._render_access_rules(h, w, opr=False))
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
"""The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
for h_ar in have:
w_ar = search_obj_in_list(h_ar["name"], want)
if not w_ar and "access_rules" in h_ar:
commands.append(self._compute_command(name=h_ar["name"], opr=False))
else:
h_rules = h_ar.get("access_rules") or []
key = "direction"
if w_ar:
w_rules = w_ar.get("access_rules") or []
if not w_rules and h_rules:
commands.append(self._compute_command(name=h_ar["name"], opr=False))
if h_rules:
for h_rule in h_rules:
w_rule = search_obj_in_list(h_rule["afi"], w_rules, key="afi")
have_rules = h_rule.get("rules") or []
if w_rule:
want_rules = w_rule.get("rules") or []
for h in have_rules:
if key in h:
w = search_obj_in_list(h[key], want_rules, key=key)
if (
not w
or key not in w
or ("name" in h and w and "name" not in w)
):
commands.append(
self._compute_command(
afi=h_rule["afi"],
name=h_ar["name"],
attrib=h[key],
opr=False,
),
)
commands.extend(self._state_merged(want, have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
for w in want:
h = search_obj_in_list(w["name"], have)
commands.extend(self._render_access_rules(w, h))
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if want:
for w in want:
h = search_obj_in_list(w["name"], have)
if h and "access_rules" in h:
commands.extend(self._delete_access_rules(w, h, opr=False))
elif have:
for h in have:
if "access_rules" in h:
commands.append(self._compute_command(name=h["name"], opr=False))
return commands
def _delete_access_rules(self, want, have, opr=False):
"""
This function forms the delete commands based on the 'opr' type
for 'access_rules' attributes.
:param want: desired config.
:param have: target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
h_rules = {}
w_rs = deepcopy(remove_empties(want))
w_rules = w_rs.get("access_rules") or []
if have:
h_rs = deepcopy(remove_empties(have))
h_rules = h_rs.get("access_rules") or []
# if all firewall config needed to be deleted for specific interface
# when operation is delete.
if not w_rules and h_rules:
commands.append(self._compute_command(name=want["name"], opr=opr))
if w_rules:
for w in w_rules:
h = search_obj_in_list(w["afi"], h_rules, key="afi")
commands.extend(self._delete_rules(want["name"], w, h))
return commands
def _delete_rules(self, name, want, have, opr=False):
"""
This function forms the delete commands based on the 'opr' type
for rules attributes.
:param name: interface id/name.
:param want: desired config.
:param have: target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
h_rules = []
key = "direction"
w_rules = want.get("rules") or []
if have:
h_rules = have.get("rules") or []
# when rule set needed to be removed on
# (inbound|outbound|local interface)
if h_rules and not w_rules:
for h in h_rules:
if key in h:
commands.append(
self._compute_command(afi=want["afi"], name=name, attrib=h[key], opr=opr),
)
for w in w_rules:
h = search_obj_in_list(w[key], h_rules, key=key)
if (
key in w
and h
and key in h
and "name" in w
and "name" in h
and w["name"] == h["name"]
):
commands.append(
self._compute_command(
afi=want["afi"],
name=name,
attrib=w[key],
value=w["name"],
opr=opr,
),
)
return commands
def _render_access_rules(self, want, have, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for 'access_rules' attributes.
:param want: desired config.
:param have: target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
h_rules = {}
w_rs = deepcopy(remove_empties(want))
w_rules = w_rs.get("access_rules") or []
if have:
h_rs = deepcopy(remove_empties(have))
h_rules = h_rs.get("access_rules") or []
if w_rules:
for w in w_rules:
h = search_obj_in_list(w["afi"], h_rules, key="afi")
commands.extend(self._render_rules(want["name"], w, h, opr))
return commands
def _render_rules(self, name, want, have, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for rules attributes.
:param name: interface id/name.
:param want: desired config.
:param have: target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
h_rules = []
key = "direction"
w_rules = want.get("rules") or []
if have:
h_rules = have.get("rules") or []
for w in w_rules:
h = search_obj_in_list(w[key], h_rules, key=key)
if key in w:
if opr:
if "name" in w and not (h and h[key] == w[key] and h["name"] == w["name"]):
commands.append(
self._compute_command(
afi=want["afi"],
name=name,
attrib=w[key],
value=w["name"],
),
)
elif not (h and key in h):
commands.append(
self._compute_command(afi=want["afi"], name=name, attrib=w[key]),
)
elif not opr:
if not h or key not in h or ("name" in w and h and "name" not in h):
commands.append(
self._compute_command(
afi=want["afi"],
name=name,
attrib=w[key],
opr=opr,
),
)
return commands
def _compute_command(self, afi=None, name=None, attrib=None, value=None, opr=True):
"""
This function construct the add/delete command based on passed attributes.
:param afi: address type.
:param name: interface name.
:param attrib: attribute name.
:param value: attribute value.
:param opr: operation flag.
:return: generated command.
"""
# Append vif if interface contains a dot
vlan = None
interface_real = name
if "." in name:
interface_real, vlan = name.split(".")
if vlan is not None:
interface_real = interface_real + " vif " + vlan
# if interface name is bondX, then it's a bonding interface. Everything else is an ethernet
iftype = get_interface_type(interface_real)
if not opr:
cmd = "delete interfaces " + iftype + " " + interface_real + " firewall"
else:
cmd = "set interfaces " + iftype + " " + interface_real + " firewall"
if attrib:
cmd += " " + attrib
if afi:
cmd += " " + self._get_fw_type(afi)
if value:
cmd += " '" + str(value) + "'"
return cmd
def _get_fw_type(self, afi):
"""
This function returns the firewall rule-set type based on IP address.
:param afi: address type
:return: rule-set type.
"""
return "ipv6-name" if afi == "ipv6" else "name"
diff --git a/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py
index 3c768e32..be36d3fe 100644
--- a/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py
+++ b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py
@@ -1,1186 +1,1186 @@
#
# -*- 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 vyos_firewall_rules 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
remove_empties,
to_list,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
list_diff_want_only,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import (
get_os_version,
load_config,
)
class Firewall_rules(ConfigBase):
"""
The vyos_firewall_rules class
"""
gather_subset = [
"!all",
"!min",
]
gather_network_resources = [
"firewall_rules",
]
def __init__(self, module):
super(Firewall_rules, self).__init__(module)
def get_firewall_rules_facts(self, data=None):
"""Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
)
firewall_rules_facts = facts["ansible_network_resources"].get("firewall_rules")
if not firewall_rules_facts:
return []
return firewall_rules_facts
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
diff = None
try:
self._module.params["comment"]
except KeyError:
comment = []
else:
comment = self._module.params["comment"]
if self.state in self.ACTION_STATES:
existing_firewall_rules_facts = self.get_firewall_rules_facts()
else:
existing_firewall_rules_facts = []
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(deepcopy(existing_firewall_rules_facts)))
if commands and self._module._diff:
commit = not self._module.check_mode
diff = load_config(self._module, commands, commit=commit, comment=comment)
if diff:
result["diff"] = {"prepared": str(diff)}
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_firewall_rules_facts = self.get_firewall_rules_facts()
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
result["parsed"] = self.get_firewall_rules_facts(data=running_config)
else:
changed_firewall_rules_facts = []
if self.state in self.ACTION_STATES:
result["before"] = existing_firewall_rules_facts
if result["changed"]:
result["after"] = changed_firewall_rules_facts
elif self.state == "gathered":
result["gathered"] = changed_firewall_rules_facts
result["warnings"] = warnings
return result
def set_config(self, existing_firewall_rules_facts):
"""Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
self._prune_stubs(want)
have = existing_firewall_rules_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, w, h):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "overridden", "rendered") and not w:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "overridden":
commands.extend(self._state_overridden(w, h))
elif self.state == "deleted":
commands.extend(self._state_deleted(w, h))
elif w:
if self.state == "merged" or self.state == "rendered":
commands.extend(self._state_merged(w, h))
elif self.state == "replaced":
commands.extend(self._state_replaced(w, h))
return commands
def _state_replaced(self, want, have):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
# Iterate over the afi rule sets we already have.
for h in have:
r_sets = self._get_r_sets(h)
# Iterate over each rule set we already have.
for rs in r_sets:
# In the desired configuration, search for the rule set we
# already have (to be replaced by our desired
# configuration's rule set).
rs_id = self._rs_id(rs, h["afi"])
wanted_rule_set = self.search_r_sets_in_have(want, rs_id, "r_list")
if self._is_same_rs(remove_empties(wanted_rule_set), remove_empties(rs)):
continue
if wanted_rule_set is not None:
# Remove the rules that we already have if the wanted
# rules exist under the same name.
commands.extend(
self._add_r_sets(
h["afi"],
want=rs,
have=wanted_rule_set,
opr=False,
),
)
# Merge the desired configuration into what we already have.
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
"""The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
for h in have:
have_r_sets = self._get_r_sets(h)
for rs in have_r_sets:
rs_id = self._rs_id(rs, h["afi"])
w = self.search_r_sets_in_have(want, rs_id, "r_list")
if self._is_same_rs(remove_empties(w), remove_empties(rs)):
continue
else:
commands.append(self._compute_command(rs_id, remove=True))
# Blank out the only rule set that it is removed.
for entry in have:
if entry["afi"] == rs_id["afi"] and rs_id["name"]:
entry["rule_sets"] = [
rule_set
for rule_set in entry["rule_sets"]
if rule_set.get("name") != rs_id["name"]
]
elif entry["afi"] == rs_id["afi"] and rs_id["filter"]:
entry["rule_sets"] = [
rule_set
for rule_set in entry["rule_sets"]
if rule_set.get("filter") != rs_id["filter"]
]
commands.extend(self._state_merged(want, have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
for w in want:
r_sets = self._get_r_sets(w)
for rs in r_sets:
rs_id = self._rs_id(rs, w["afi"])
h = self.search_r_sets_in_have(have, rs_id, "r_list")
if self._is_same_rs(remove_empties(h), remove_empties(rs)):
continue
else:
commands.extend(self._add_r_sets(w["afi"], rs, h))
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if want:
for w in want:
r_sets = self._get_r_sets(w)
if r_sets:
for rs in r_sets:
rs_id = self._rs_id(rs, w["afi"])
h = self.search_r_sets_in_have(have, rs_id, "r_list")
if h:
commands.append(self._compute_command(rs_id, remove=True))
elif have:
for h in have:
if h["afi"] == w["afi"]:
commands.append(
self._compute_command(self._rs_id(None, w["afi"]), remove=True),
)
elif have:
for h in have:
r_sets = self._get_r_sets(h)
if r_sets:
commands.append(self._compute_command(self._rs_id(None, h["afi"]), remove=True))
return commands
def _add_r_sets(self, afi, want, have, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for rule-sets attributes.
:param afi: address type.
:param want: desired config.
:param have: target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
l_set = ("description", "default_action", "default_jump_target", "enable_default_log")
h_rs = {}
h_rules = {}
w_rs = deepcopy(remove_empties(want))
w_rules = w_rs.pop("rules", None)
rs_id = self._rs_id(want, afi=afi)
if have:
h_rs = deepcopy(remove_empties(have))
h_rules = h_rs.pop("rules", None)
if w_rs:
for key, val in w_rs.items():
if opr and key in l_set and not (h_rs and self._is_w_same(w_rs, h_rs, key)):
if key == "enable_default_log":
if val and (not h_rs or key not in h_rs or not h_rs[key]):
commands.append(self._add_rs_base_attrib(rs_id, key, w_rs))
else:
commands.append(self._add_rs_base_attrib(rs_id, key, w_rs))
elif not opr and key in l_set:
if (
key == "enable_default_log"
and val
and h_rs
and (key not in h_rs or not h_rs[key])
):
commands.append(self._add_rs_base_attrib(rs_id, key, w_rs, opr))
elif not (h_rs and self._in_target(h_rs, key)):
commands.append(self._add_rs_base_attrib(rs_id, key, w_rs, opr))
commands.extend(self._add_rules(rs_id, w_rules, h_rules, opr))
if h_rules:
have["rules"] = h_rules
if w_rules:
want["rules"] = w_rules
return commands
def _add_rules(self, rs_id, w_rules, h_rules, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for rules attributes.
:param rs_id: rule-set identifier.
:param w_rules: desired config.
:param h_rules: target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
l_set = (
"ipsec",
"action",
"number",
"protocol",
"fragment",
"disable",
"description",
"jump_target",
"offload_target",
)
if w_rules:
for w in w_rules:
cmd = self._compute_command(rs_id, w["number"], opr=opr)
h = self.search_rules_in_have_rs(h_rules, w["number"])
if w != h and self.state == "replaced":
h = {}
for key, val in w.items():
if val:
if opr and key in l_set and not (h and self._is_w_same(w, h, key)):
if key == "disable":
if not (not val and (not h or key not in h or not h[key])):
commands.append(self._add_r_base_attrib(rs_id, key, w))
else:
commands.append(self._add_r_base_attrib(rs_id, key, w))
elif not opr:
# Note: if you are experiencing sticky configuration on replace
# you may need to add an explicit check for the key here. Anything that
# doesn't have a custom operation is taken care of by the `l_set` check
# below, but I'm not sure how any of the others work.
# It's possible that historically the delete was forced (but now it's
# checked).
if key == "number" and self._is_del(l_set, h):
commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr))
continue
if (
key == "tcp"
and val
and h
and (key not in h or not h[key] or h[key] != w[key])
):
commands.extend(self._add_tcp(key, w, h, cmd, opr))
if (
key == "state"
and val
and h
and (key not in h or not h[key] or h[key] != w[key])
):
commands.extend(self._add_state(key, w, h, cmd, opr))
if (
key == "icmp"
and val
and h
and (key not in h or not h[key] or h[key] != w[key])
):
commands.extend(self._add_icmp(key, w, h, cmd, opr))
if (
key in ("packet_length", "packet_length_exclude")
and val
and h
and (key not in h or not h[key] or h[key] != w[key])
):
commands.extend(self._add_packet_length(key, w, h, cmd, opr))
elif key == "disable" and val and h and (key not in h or not h[key]):
commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr))
if (
key in ("inbound_interface", "outbound_interface")
and val
and h
and (key not in h or not h[key] or h[key] != w[key])
):
commands.extend(self._add_interface(key, w, h, cmd, opr))
elif (
key in l_set
and not (h and self._in_target(h, key))
and not self._is_del(l_set, h)
):
commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr))
elif key == "tcp":
commands.extend(self._add_tcp(key, w, h, cmd, opr))
elif key == "time":
commands.extend(self._add_time(key, w, h, cmd, opr))
elif key == "icmp":
commands.extend(self._add_icmp(key, w, h, cmd, opr))
elif key == "state":
commands.extend(self._add_state(key, w, h, cmd, opr))
elif key == "log":
commands.extend(self._add_log(key, w, h, cmd, opr))
elif key == "limit":
commands.extend(self._add_limit(key, w, h, cmd, opr))
elif key == "recent":
commands.extend(self._add_recent(key, w, h, cmd, opr))
elif key == "destination" or key == "source":
commands.extend(self._add_src_or_dest(key, w, h, cmd, opr))
elif key in ("packet_length", "packet_length_exclude"):
commands.extend(self._add_packet_length(key, w, h, cmd, opr))
elif key in ("inbound_interface", "outbound_interface"):
commands.extend(self._add_interface(key, w, h, cmd, opr))
return commands
def _add_state(self, attr, w, h, cmd, opr):
"""
This function forms the command for 'state' attributes based on the 'opr'.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
h_state = {}
commands = []
l_set = ("new", "invalid", "related", "established")
if w[attr]:
if h and attr in h.keys():
h_state = h.get(attr) or {}
for item, val in w[attr].items():
if (
opr
and item in l_set
and not (h_state and self._is_w_same(w[attr], h_state, item))
):
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
commands.append(cmd + (" " + attr + " " + item))
else:
commands.append(
cmd + (" " + attr + " " + item + " " + self._bool_to_str(val)),
)
elif not opr and item in l_set and not self._in_target(h_state, item):
commands.append(cmd + (" " + attr + " " + item))
return commands
def _add_log(self, attr, w, h, cmd, opr):
"""
This function forms the command for 'log' attributes based on the 'opr'.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
h_state = {}
commands = []
if w[attr]:
if h and attr in h.keys():
h_state = h.get(attr) or {}
if (
LooseVersion(get_os_version(self._module)) < LooseVersion("1.4")
and opr
and not (h and self._is_w_same(w, h, attr))
):
commands.append(cmd + " " + attr + " '" + w[attr] + "'")
elif (
LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
and opr
and not (h and self._is_w_same(w, h, attr))
):
commands.append(cmd + " " + attr)
elif not opr and not self._in_target(h_state, w[attr]):
commands.append(cmd + (" " + attr + " '" + w[attr] + "'"))
return commands
def _add_recent(self, attr, w, h, cmd, opr):
"""
This function forms the command for 'recent' attributes based on the 'opr'.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
commands = []
h_recent = {}
l_set = ("count", "time")
if w[attr]:
if h and attr in h.keys():
h_recent = h.get(attr) or {}
for item, val in w[attr].items():
if (
opr
and item in l_set
and not (h_recent and self._is_w_same(w[attr], h_recent, item))
):
commands.append(cmd + (" " + attr + " " + item + " " + str(val)))
elif (
not opr and item in l_set and not (h_recent and self._in_target(h_recent, item))
):
commands.append(cmd + (" " + attr + " " + item))
return commands
def _add_icmp(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'icmp' attributes based on the 'opr'.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
commands = []
h_icmp = {}
l_set = ("code", "type", "type_name")
if w[attr]:
if h and attr in h.keys():
h_icmp = h.get(attr) or {}
for item, val in w[attr].items():
if (
opr
and item in l_set
and not (h_icmp and self._is_w_same(w[attr], h_icmp, item))
):
if item == "type_name":
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
param_name = "type-name"
else:
param_name = "type"
if "ipv6" in cmd: # ipv6-name or ipv6
commands.append(cmd + (" " + "icmpv6" + " " + param_name + " " + val))
else:
commands.append(
cmd + (" " + attr + " " + item.replace("_", "-") + " " + val),
)
else:
if "ipv6" in cmd: # ipv6-name or ipv6
commands.append(cmd + (" " + "icmpv6" + " " + item + " " + str(val)))
else:
commands.append(cmd + (" " + attr + " " + item + " " + str(val)))
elif not opr and item in l_set and not self._in_target(h_icmp, item):
commands.append(
cmd + (" " + attr + " " + item.replace("_", "-") + " " + str(val)),
)
return commands
def _add_interface(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'interface' attributes based on the 'opr'.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
commands = []
h_if = {}
l_set = ("name", "group")
if w[attr]:
if h and attr in h.keys():
h_if = h.get(attr) or {}
for item, val in w[attr].items():
if opr and item in l_set and not (h_if and self._is_w_same(w[attr], h_if, item)):
commands.append(
cmd
+ (" " + attr.replace("_", "-") + " " + item.replace("_", "-") + " " + val),
)
elif not opr and item in l_set and not (h_if and self._in_target(h_if, item)):
commands.append(
cmd + (" " + attr.replace("_", "-") + " " + item.replace("_", "-")),
)
return commands
def _add_time(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'time' attributes based on the 'opr'.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
commands = []
h_time = {}
l_set = (
"utc",
"stopdate",
"stoptime",
"weekdays",
"monthdays",
"startdate",
"starttime",
)
if w[attr]:
if h and attr in h.keys():
h_time = h.get(attr) or {}
for item, val in w[attr].items():
if (
opr
and item in l_set
and not (h_time and self._is_w_same(w[attr], h_time, item))
):
if item == "utc":
if not (not val and (not h_time or item not in h_time)):
commands.append(cmd + (" " + attr + " " + item))
else:
commands.append(cmd + (" " + attr + " " + item + " " + val))
elif (
not opr
and item in l_set
and not (h_time and self._is_w_same(w[attr], h_time, item))
):
commands.append(cmd + (" " + attr + " " + item))
return commands
def _add_tcp_1_4(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'tcp' attributes based on the 'opr'.
Version 1.4+
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
commands = []
have = []
key = "flags"
want = []
if w:
if w.get(attr):
want = w.get(attr).get(key) or []
if h:
if h.get(attr):
have = h.get(attr).get(key) or []
if want:
if opr:
flags = list_diff_want_only(want, have)
for flag in flags:
invert = flag.get("invert", False)
commands.append(
cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"]),
)
elif not opr:
flags = list_diff_want_only(want, have)
for flag in flags:
invert = flag.get("invert", False)
commands.append(
cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"]),
)
return commands
def _add_packet_length(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'packet_length[_exclude]' attributes based on the 'opr'.
If < 1.4, handle tcp attributes.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
commands = []
have = []
want = []
if w:
if w.get(attr):
want = w.get(attr) or []
if h:
if h.get(attr):
have = h.get(attr) or []
attr = attr.replace("_", "-")
if want:
if opr:
lengths = list_diff_want_only(want, have)
for l_rec in lengths:
commands.append(cmd + " " + attr + " " + str(l_rec["length"]))
elif not opr:
lengths = list_diff_want_only(want, have)
for l_rec in lengths:
commands.append(cmd + " " + attr + " " + str(l_rec["length"]))
return commands
def _tcp_flags_string(self, flags):
"""
This function forms the tcp flags string.
:param flags: flags list.
:return: flags string or None.
"""
if not flags:
return ""
flag_str = ""
for flag in flags:
this_flag = flag["flag"].upper()
if flag.get("invert", False):
this_flag = "!" + this_flag
if len(flag_str) > 0:
flag_str = ",".join([flag_str, this_flag])
else:
flag_str = this_flag
return flag_str
def _add_tcp(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'tcp' attributes based on the 'opr'.
If < 1.4, handle tcp attributes.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
return self._add_tcp_1_4(attr, w, h, cmd, opr)
h_tcp = {}
commands = []
if w[attr]:
key = "flags"
flags = w[attr].get(key) or {}
if flags:
if h and key in h[attr].keys():
h_tcp = h[attr].get(key) or {}
if flags:
flag_str = self._tcp_flags_string(flags)
if opr and not (h_tcp and flags == h_tcp):
commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str))
if not opr and not (h_tcp and flags == h_tcp):
commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str))
return commands
def _add_limit(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'limit' attributes based on the 'opr'.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
h_limit = {}
commands = []
if w[attr]:
key = "burst"
if (
opr
and key in w[attr].keys()
and not (h and attr in h.keys() and self._is_w_same(w[attr], h[attr], key))
):
commands.append(cmd + (" " + attr + " " + key + " " + str(w[attr].get(key))))
elif (
not opr
and key in w[attr].keys()
and not (h and attr in h.keys() and self._in_target(h[attr], key))
):
commands.append(cmd + (" " + attr + " " + key + " " + str(w[attr].get(key))))
key = "rate"
rate = w[attr].get(key) or {}
if rate:
if h and key in h[attr].keys():
h_limit = h[attr].get(key) or {}
if "unit" in rate and "number" in rate:
if opr and not (
h_limit
and self._is_w_same(rate, h_limit, "unit")
and self._is_w_same(rate, h_limit, "number")
):
commands.append(
cmd
+ (
" "
+ attr
+ " "
+ key
+ " "
+ str(rate["number"])
+ "/"
+ rate["unit"]
),
)
if not opr and not (
h_limit
and self._is_w_same(rate, h_limit, "unit")
and self._is_w_same(rate, h_limit, "number")
):
commands.append(cmd + (" " + attr + " " + key))
return commands
def _add_src_or_dest(self, attr, w, h, cmd, opr=True):
"""
This function forms the commands for 'src/dest' attributes based on the 'opr'.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
commands = []
h_group = {}
g_set = ("port_group", "address_group", "network_group")
if w[attr]:
keys = ("address", "mac_address", "port")
for key in keys:
if (
opr
and key in w[attr].keys()
and not (h and attr in h.keys() and self._is_w_same(w[attr], h[attr], key))
):
commands.append(
cmd + (" " + attr + " " + key.replace("_", "-") + " " + w[attr].get(key)),
)
elif (
not opr
and key in w[attr].keys()
and not (h and attr in h.keys() and self._in_target(h[attr], key))
):
commands.append(cmd + (" " + attr + " " + key))
key = "group"
group = w[attr].get(key) or {}
if group:
h_group = {}
if h and h.get(attr) and key in h[attr].keys():
h_group = h[attr].get(key)
for item, val in group.items():
if val:
if (
opr
and item in g_set
and not (h_group and self._is_w_same(group, h_group, item))
):
commands.append(
cmd
+ (
" "
+ attr
+ " "
+ key
+ " "
+ item.replace("_", "-")
+ " "
+ val
),
)
elif (
not opr
and item in g_set
and not (h_group and self._in_target(h_group, item))
):
commands.append(
cmd + (" " + attr + " " + key + " " + item.replace("_", "-")),
)
return commands
def search_rules_in_have_rs(self, have_rules, r_number):
"""
This function returns the rule if it is present in target config.
:param have: target config.
:param rs_id: rule-set identifier.
:param r_number: rule-number.
:return: rule.
"""
if have_rules:
key = "number"
for r in have_rules:
if key in r and r[key] == r_number:
return r
return None
def search_r_sets_in_have(self, have, rs_id, type="rule_sets"):
"""
This function returns the rule-set/rule if it is present in target config.
:param have: target config.
:param rs_id: rule-identifier.
:param type: rule_sets if searching a rule_set and r_list if searching from a rule_list.
:return: rule-set/rule.
"""
if "afi" in rs_id:
afi = rs_id["afi"]
else:
afi = None
if rs_id["filter"]:
key = "filter"
w_value = rs_id["filter"]
elif rs_id["name"]:
key = "name"
w_value = rs_id["name"]
else:
raise ValueError("id must be specific to name or filter")
if type not in ("r_list", "rule_sets"):
raise ValueError("type must be rule_sets or r_list")
if have:
if type == "r_list":
for h in have:
if h["afi"] == afi:
r_sets = self._get_r_sets(h)
for rs in r_sets:
if key in rs and rs[key] == w_value:
return rs
else:
# searching a ruleset
for rs in have:
if key in rs and rs[key] == w_value:
return rs
return None
def _get_r_sets(self, item):
"""
This function returns the list of rule-sets.
:param item: config dictionary.
:return: list of rule-sets/rules.
"""
rs_list = []
type = "rule_sets"
r_sets = item[type]
if r_sets:
for rs in r_sets:
rs_list.append(rs)
return rs_list
def _compute_command(
self,
rs_id,
number=None,
attrib=None,
value=None,
remove=False,
opr=True,
):
"""
This function construct the add/delete command based on passed attributes.
:param rs_id: rule-set identifier.
:param number: rule-number.
:param attrib: attribute name.
:param value: value.
:param remove: True if delete command needed to be construct.
:param opr: operation flag.
:return: generated command.
"""
if rs_id["name"] and rs_id["filter"]:
raise ValueError("name and filter cannot be used together")
if remove or not opr:
cmd = "delete firewall " + self._get_fw_type(rs_id["afi"])
else:
cmd = "set firewall " + self._get_fw_type(rs_id["afi"])
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
if rs_id["name"]:
cmd += " name " + rs_id["name"]
elif rs_id["filter"]:
cmd += " " + rs_id["filter"] + " filter"
elif rs_id["name"]:
cmd += " " + rs_id["name"]
if number:
cmd += " rule " + str(number)
if attrib:
if (
LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
and attrib == "enable_default_log"
):
cmd += " " + "default-log"
else:
cmd += " " + attrib.replace("_", "-")
if value and opr and attrib != "enable_default_log" and attrib != "disable":
cmd += " '" + str(value) + "'"
return cmd
def _add_r_base_attrib(self, rs_id, attr, rule, opr=True):
"""
This function forms the command for 'rules' attributes which doesn't
have further sub attributes.
:param rs_id: rule-set identifier.
:param attrib: attribute name
:param rule: rule config dictionary.
:param opr: True/False.
:return: generated command.
"""
if attr == "number":
command = self._compute_command(rs_id, number=rule["number"], opr=opr)
else:
command = self._compute_command(
rs_id=rs_id,
number=rule["number"],
attrib=attr,
value=rule[attr],
opr=opr,
)
return command
def _rs_id(self, have, afi, name=None, filter=None):
"""
This function returns the rule-set identifier based on
the example rule, overriding the components as specified.
:param have: example rule.
:param afi: address type.
:param name: rule-set name.
:param filter: filter name.
:return: rule-set identifier.
"""
identifier = {"name": None, "filter": None}
if afi:
identifier["afi"] = afi
else:
raise ValueError("afi must be provided")
if name:
identifier["name"] = name
return identifier
elif filter:
identifier["filter"] = filter
return identifier
if have:
if "name" in have and have["name"]:
identifier["name"] = have["name"]
return identifier
if "filter" in have and have["filter"]:
identifier["filter"] = have["filter"]
return identifier
# raise ValueError("name or filter must be provided or present in have")
# unless we want a wildcard
return identifier
def _add_rs_base_attrib(self, rs_id, attrib, rule, opr=True):
"""
This function forms the command for 'rule-sets' attributes which don't
have further sub attributes.
:param rs_id: rule-set identifier.
:param attrib: attribute name
:param rule: rule config dictionary.
:param opr: True/False.
:return: generated command.
"""
command = self._compute_command(
rs_id=rs_id,
attrib=attrib,
value=rule[attrib],
opr=opr,
)
return command
def _bool_to_str(self, val):
"""
This function converts the bool value into string.
:param val: bool value.
:return: enable/disable.
"""
return "enable" if val else "disable"
def _get_fw_type(self, afi):
"""
This function returns the firewall rule-set type based on IP address.
:param afi: address type
:return: rule-set type.
"""
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
return "ipv6" if afi == "ipv6" else "ipv4"
return "ipv6-name" if afi == "ipv6" else "name"
def _is_del(self, l_set, h, key="number"):
"""
This function checks whether rule needs to be deleted based on
the rule number.
:param l_set: attribute set.
:param h: target config.
:param key: number.
:return: True/False.
"""
return key in l_set and not (h and self._in_target(h, key))
def _is_w_same(self, w, h, key):
"""
This function checks whether the key value is same in base and
target config dictionary.
:param w: base config.
:param h: target config.
:param key:attribute name.
:return: True/False.
"""
return True if h and key in h and h[key] == w[key] else False
def _in_target(self, h, key):
"""
This function checks whether the target exists and key present in target config.
:param h: target config.
:param key: attribute name.
:return: True/False.
"""
return True if h and key in h else False
def _prune_stubs(self, rs):
if isinstance(rs, list):
for item in rs:
self._prune_stubs(item)
elif isinstance(rs, dict):
keys_to_remove = [
key
for key, value in rs.items()
if (
(key == "disable" and value is False)
or (
key == "log"
and value == "disable"
and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
)
or (
key in ["new", "invalid", "related", "established"]
and value is False
and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
)
)
]
for key in keys_to_remove:
del rs[key]
for key in rs:
self._prune_stubs(rs[key])
def _is_same_rs(self, w, rs):
if isinstance(w, dict) and isinstance(rs, dict):
if w.keys() != rs.keys():
return False
for key in w:
if not self._is_same_rs(w[key], rs[key]):
return False
return True
elif isinstance(w, list) and isinstance(rs, list):
try:
def comparison(x):
if "name" in x:
return x["name"]
if "number" in x:
return x["number"]
return str(x)
sorted_list1 = sorted(w, key=comparison)
sorted_list2 = sorted(rs, key=comparison)
except TypeError:
return False
if len(sorted_list1) != len(sorted_list2):
return False
return all(self._is_same_rs(x, y) for x, y in zip(sorted_list1, sorted_list2))
else:
return w == rs
diff --git a/plugins/module_utils/network/vyos/config/hostname/hostname.py b/plugins/module_utils/network/vyos/config/hostname/hostname.py
index 36aba74f..8b30a693 100644
--- a/plugins/module_utils/network/vyos/config/hostname/hostname.py
+++ b/plugins/module_utils/network/vyos/config/hostname/hostname.py
@@ -1,75 +1,74 @@
#
# -*- coding: utf-8 -*-
# Copyright 2022 Red Hat
# 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
"""
The vyos_hostname config file.
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 its desired end-state is
created.
"""
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.hostname import (
HostnameTemplate,
)
class Hostname(ResourceModule):
"""
The vyos_hostname config class
"""
def __init__(self, module):
super(Hostname, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="hostname",
tmplt=HostnameTemplate(),
)
self.parsers = []
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = self.want
haved = self.have
if self.state == "deleted":
wantd = {}
self._compare(want=wantd, have=haved)
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 Hostname network resource.
"""
self.compare(parsers="hostname", want=want, have=have)
diff --git a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
index 0a55564e..944f7f42 100644
--- a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
+++ b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
@@ -1,340 +1,339 @@
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The vyos_interfaces 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
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_diff,
remove_empties,
to_list,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
dict_delete,
get_interface_type,
search_obj_in_list,
)
class Interfaces(ConfigBase):
"""
The vyos_interfaces class
"""
gather_subset = [
"!all",
"!min",
]
gather_network_resources = ["interfaces"]
def __init__(self, module):
super(Interfaces, self).__init__(module)
def get_interfaces_facts(self, data=None):
"""Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
)
interfaces_facts = facts["ansible_network_resources"].get("interfaces")
if not interfaces_facts:
return []
return interfaces_facts
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
commands = list()
warnings = list()
if self.state in self.ACTION_STATES:
existing_interfaces_facts = self.get_interfaces_facts()
else:
existing_interfaces_facts = []
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_interfaces_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_interfaces_facts = self.get_interfaces_facts()
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
result["parsed"] = self.get_interfaces_facts(data=running_config)
else:
changed_interfaces_facts = []
if self.state in self.ACTION_STATES:
result["before"] = existing_interfaces_facts
if result["changed"]:
result["after"] = changed_interfaces_facts
elif self.state == "gathered":
result["gathered"] = changed_interfaces_facts
result["warnings"] = warnings
return result
def set_config(self, existing_interfaces_facts):
"""Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "overridden", "rendered") and not want:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "overridden":
commands.extend(self._state_overridden(want=want, have=have))
elif self.state == "deleted":
if not want:
for intf in have:
commands.extend(self._state_deleted({"name": intf["name"]}, intf))
else:
for item in want:
obj_in_have = search_obj_in_list(item["name"], have)
commands.extend(self._state_deleted(item, obj_in_have))
else:
for item in want:
name = item["name"]
enable_state = item["enabled"]
obj_in_have = search_obj_in_list(name, have)
if not obj_in_have:
obj_in_have = {"name": name, "enabled": enable_state}
if self.state in ("merged", "rendered"):
commands.extend(self._state_merged(item, obj_in_have))
elif self.state == "replaced":
commands.extend(self._state_replaced(item, obj_in_have))
return commands
def _state_replaced(self, want, have):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
commands.extend(self._state_deleted(want, have))
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
"""The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for intf in have:
intf_in_want = search_obj_in_list(intf["name"], want)
if not intf_in_want:
commands.extend(self._state_deleted({"name": intf["name"]}, intf))
for intf in want:
intf_in_have = search_obj_in_list(intf["name"], have)
if not intf_in_have:
intf_in_have = {
"name": intf["name"],
"enabled": intf["enabled"],
}
commands.extend(self._state_replaced(intf, intf_in_have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(have)
want_vifs = want_copy.pop("vifs", [])
have_vifs = have_copy.pop("vifs", [])
updates = dict_diff(have_copy, want_copy)
if updates:
for key, value in updates.items():
commands.append(
self._compute_commands(key=key, value=value, interface=want_copy["name"]),
)
if want_vifs:
for want_vif in want_vifs:
have_vif = search_obj_in_list(want_vif["vlan_id"], have_vifs, key="vlan_id")
if not have_vif:
have_vif = {
"vlan_id": want_vif["vlan_id"],
"enabled": True,
}
vif_updates = dict_diff(have_vif, want_vif)
if vif_updates:
for key, value in vif_updates.items():
commands.append(
self._compute_commands(
key=key,
value=value,
interface=want_copy["name"],
vif=want_vif["vlan_id"],
),
)
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(have)
want_vifs = want_copy.pop("vifs", [])
have_vifs = have_copy.pop("vifs", [])
for key in dict_delete(have_copy, want_copy).keys():
if key == "enabled":
continue
commands.append(
self._compute_commands(key=key, interface=want_copy["name"], remove=True),
)
if have_copy["enabled"] is False and not (
"enabled" in want_copy and want_copy["enabled"] is False
):
commands.append(
self._compute_commands(key="enabled", value=True, interface=want_copy["name"]),
)
if have_vifs:
for have_vif in have_vifs:
want_vif = search_obj_in_list(have_vif["vlan_id"], want_vifs, key="vlan_id")
if not want_vif:
commands.append(
self._compute_commands(
key="",
interface=want_copy["name"],
vif=have_vif["vlan_id"],
remove=True,
),
)
continue
for key in dict_delete(have_vif, want_vif).keys():
if key == "enabled":
continue
commands.append(
self._compute_commands(
key=key,
interface=want_copy["name"],
vif=want_vif["vlan_id"],
remove=True,
),
)
return commands
def _compute_commands(self, interface, key, vif=None, value=None, remove=False):
interface_type = get_interface_type(interface)
if not interface_type:
self._module.fail_json(
msg="interface {0} is not a valid interface type".format(interface),
)
intf_context = "interfaces {0} {1}".format(interface_type, interface)
set_cmd = "set {0}".format(intf_context)
del_cmd = "delete {0}".format(intf_context)
if vif:
set_cmd = set_cmd + (" vif {0}".format(vif))
del_cmd = del_cmd + (" vif {0}".format(vif))
if key == "" or key is None:
if not remove:
command = "{0}".format(set_cmd)
else:
command = "{0}".format(del_cmd)
elif key == "enabled":
if not value:
command = "{0} disable".format(set_cmd)
else:
command = "{0} disable".format(del_cmd)
else:
if not remove:
command = "{0} {1} '{2}'".format(set_cmd, key, value)
else:
command = "{0} {1}".format(del_cmd, key)
return command
diff --git a/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py
index a23494cd..862e0272 100644
--- a/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py
@@ -1,354 +1,353 @@
#
# -*- 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 vyos_l3_interfaces 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
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
remove_empties,
to_list,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
diff_list_of_dicts,
get_interface_type,
search_obj_in_list,
)
class L3_interfaces(ConfigBase):
"""
The vyos_l3_interfaces class
"""
gather_subset = [
"!all",
"!min",
]
gather_network_resources = [
"l3_interfaces",
]
def __init__(self, module):
super(L3_interfaces, self).__init__(module)
def get_l3_interfaces_facts(self, data=None):
"""Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
)
l3_interfaces_facts = facts["ansible_network_resources"].get("l3_interfaces")
if not l3_interfaces_facts:
return []
return l3_interfaces_facts
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
if self.state in self.ACTION_STATES:
existing_l3_interfaces_facts = self.mutate_autoconfig(self.get_l3_interfaces_facts())
else:
existing_l3_interfaces_facts = []
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_l3_interfaces_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_l3_interfaces_facts = self.mutate_autoconfig(self.get_l3_interfaces_facts())
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
result["parsed"] = self.mutate_autoconfig(
self.get_l3_interfaces_facts(data=running_config),
)
else:
changed_l3_interfaces_facts = []
if self.state in self.ACTION_STATES:
result["before"] = existing_l3_interfaces_facts
if result["changed"]:
result["after"] = changed_l3_interfaces_facts
elif self.state == "gathered":
result["gathered"] = changed_l3_interfaces_facts
result["warnings"] = warnings
return result
def set_config(self, existing_l3_interfaces_facts):
"""Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_l3_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
state = self._module.params["state"]
if state in ("merged", "replaced", "overridden", "rendered") and not want:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(state),
)
if state == "overridden":
commands.extend(self._state_overridden(want=want, have=have))
elif state == "deleted":
if not want:
for intf in have:
commands.extend(self._state_deleted({"name": intf["name"]}, intf))
else:
for item in want:
obj_in_have = search_obj_in_list(item["name"], have)
commands.extend(self._state_deleted(item, obj_in_have))
else:
for item in want:
name = item["name"]
obj_in_have = search_obj_in_list(name, have)
if not obj_in_have:
obj_in_have = {"name": item["name"]}
if state in ("merged", "rendered"):
commands.extend(self._state_merged(item, obj_in_have))
elif state == "replaced":
commands.extend(self._state_replaced(item, obj_in_have))
commands = [command.replace("auto-config", "autoconf") for command in commands]
return commands
def _state_replaced(self, want, have):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
commands.extend(self._state_deleted(want, have))
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
"""The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for intf in have:
intf_in_want = search_obj_in_list(intf["name"], want)
if not intf_in_want:
commands.extend(self._state_deleted({"name": intf["name"]}, intf))
for intf in want:
intf_in_have = search_obj_in_list(intf["name"], have)
commands.extend(self._state_replaced(intf, intf_in_have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(remove_empties(have))
want_vifs = want_copy.pop("vifs", [])
have_vifs = have_copy.pop("vifs", [])
for update in self._get_updates(want_copy, have_copy):
for key, value in update.items():
commands.append(
self._compute_commands(key=key, value=value, interface=want_copy["name"]),
)
if want_vifs:
for want_vif in want_vifs:
have_vif = search_obj_in_list(want_vif["vlan_id"], have_vifs, key="vlan_id")
if not have_vif:
have_vif = {}
for update in self._get_updates(want_vif, have_vif):
for key, value in update.items():
commands.append(
self._compute_commands(
key=key,
value=value,
interface=want_copy["name"],
vif=want_vif["vlan_id"],
),
)
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(have)
if have_copy is not None:
if all(v in (None, {}, []) for k, v in want_copy.items() if k != "name"):
commands.append(
self._compute_commands(
key=None,
value=None,
interface=want_copy["name"],
remove=True,
),
)
return commands
want_vifs = want_copy.pop("vifs", [])
have_vifs = have_copy.pop("vifs", [])
if have_vifs:
for have_vif in have_vifs:
want_vif = search_obj_in_list(have_vif["vlan_id"], want_vifs, key="vlan_id")
if not want_vif:
want_vif = {"vlan_id": have_vif["vlan_id"]}
for update in self._get_updates(have_vif, want_vif):
for key, value in update.items():
commands.append(
self._compute_commands(
key=key,
interface=want_copy["name"],
value=value,
vif=want_vif["vlan_id"],
remove=True,
),
)
for update in self._get_updates(have_copy, want_copy):
for key, value in update.items():
commands.append(
self._compute_commands(
key=key,
value=value,
interface=want_copy["name"],
remove=True,
),
)
return commands
def _compute_commands(self, interface, key, vif=None, value=None, remove=False):
if value == "auto-config" and vif is None:
intf_context = "interfaces {0} {1} ipv6".format(
get_interface_type(interface),
interface,
)
else:
intf_context = "interfaces {0} {1}".format(get_interface_type(interface), interface)
set_cmd = "set {0}".format(intf_context)
del_cmd = "delete {0}".format(intf_context)
if vif:
suffix = " ipv6" if value == "auto-config" else ""
set_cmd += f" vif {vif}{suffix}"
del_cmd += f" vif {vif}{suffix}"
if remove and key and value:
command = "{0} {1} '{2}'".format(del_cmd, key, value)
elif remove and not (key and value):
command = "{0}".format(del_cmd)
else:
command = "{0} {1} '{2}'".format(set_cmd, key, value)
return command
def _get_updates(self, want, have):
updates = []
updates = diff_list_of_dicts(want.get("ipv4", []), have.get("ipv4", []))
updates.extend(diff_list_of_dicts(want.get("ipv6", []), have.get("ipv6", [])))
return updates
def mutate_autoconfig(self, obj):
if isinstance(obj, dict):
return dict(map(lambda kv: (kv[0], self.mutate_autoconfig(kv[1])), obj.items()))
if isinstance(obj, list):
return list(map(self.mutate_autoconfig, obj))
if isinstance(obj, str):
return obj.replace("autoconf", "auto-config")
return obj
diff --git a/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py
index 62270503..203519e4 100644
--- a/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py
@@ -1,433 +1,433 @@
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The vyos_lag_interfaces 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_diff,
to_list,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
get_lst_diff_for_dicts,
list_diff_have_only,
list_diff_want_only,
search_obj_in_list,
)
class Lag_interfaces(ConfigBase):
"""
The vyos_lag_interfaces class
"""
gather_subset = [
"!all",
"!min",
]
gather_network_resources = [
"lag_interfaces",
]
params = [
"arp_monitor",
"hash_policy",
"members",
"mode",
"name",
"primary",
]
def __init__(self, module):
super(Lag_interfaces, self).__init__(module)
def get_lag_interfaces_facts(self, data=None):
"""Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
)
lag_interfaces_facts = facts["ansible_network_resources"].get("lag_interfaces")
if not lag_interfaces_facts:
return []
return lag_interfaces_facts
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
if self.state in self.ACTION_STATES:
existing_lag_interfaces_facts = self.get_lag_interfaces_facts()
else:
existing_lag_interfaces_facts = []
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_lag_interfaces_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_lag_interfaces_facts = self.get_lag_interfaces_facts()
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
result["parsed"] = self.get_lag_interfaces_facts(data=running_config)
else:
changed_lag_interfaces_facts = []
if self.state in self.ACTION_STATES:
result["before"] = existing_lag_interfaces_facts
if result["changed"]:
result["after"] = changed_lag_interfaces_facts
elif self.state == "gathered":
result["gathered"] = changed_lag_interfaces_facts
result["warnings"] = warnings
return result
def set_config(self, existing_lag_interfaces_facts):
"""Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_lag_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "overridden", "rendered") and not want:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "overridden":
commands.extend(self._state_overridden(want, have))
elif self.state == "deleted":
if want:
for want_item in want:
name = want_item["name"]
obj_in_have = search_obj_in_list(name, have)
commands.extend(self._state_deleted(obj_in_have))
else:
for have_item in have:
commands.extend(self._state_deleted(have_item))
else:
for want_item in want:
name = want_item["name"]
obj_in_have = search_obj_in_list(name, have)
if self.state in ("merged", "rendered"):
commands.extend(self._state_merged(want_item, obj_in_have))
elif self.state == "replaced":
commands.extend(self._state_replaced(want_item, obj_in_have))
return commands
def _state_replaced(self, want, have):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
commands.extend(self._render_del_commands(want, have))
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
"""The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for have_item in have:
lag_name = have_item["name"]
obj_in_want = search_obj_in_list(lag_name, want)
if not obj_in_want:
commands.extend(self._purge_attribs(have_item))
for want_item in want:
name = want_item["name"]
obj_in_have = search_obj_in_list(name, have)
commands.extend(self._state_replaced(want_item, obj_in_have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
if have:
commands.extend(self._render_updates(want, have))
else:
commands.extend(self._render_set_commands(want))
return commands
def _state_deleted(self, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if have:
commands.extend(self._purge_attribs(have))
return commands
def _render_updates(self, want, have):
commands = []
temp_have_members = have.pop("members", None)
temp_want_members = want.pop("members", None)
updates = dict_diff(have, want)
if temp_have_members:
have["members"] = temp_have_members
if temp_want_members:
want["members"] = temp_want_members
commands.extend(self._add_bond_members(want, have))
if updates:
for key, value in updates.items():
if value:
if key == "arp_monitor":
commands.extend(self._add_arp_monitor(updates, key, want, have))
else:
commands.append(self._compute_command(have["name"], key, str(value)))
return commands
def _render_set_commands(self, want):
commands = []
have = []
params = Lag_interfaces.params
for attrib in params:
value = want[attrib]
if value:
if attrib == "arp_monitor":
commands.extend(self._add_arp_monitor(want, attrib, want, have))
elif attrib == "members":
commands.extend(self._add_bond_members(want, have))
elif attrib != "name":
commands.append(self._compute_command(want["name"], attrib, value=str(value)))
return commands
def _purge_attribs(self, have):
commands = []
for item in Lag_interfaces.params:
if have.get(item):
if item == "members":
commands.extend(self._delete_bond_members(have))
elif item != "name":
commands.append(self._compute_command(have["name"], attrib=item, remove=True))
return commands
def _render_del_commands(self, want, have):
commands = []
params = Lag_interfaces.params
for attrib in params:
if attrib == "members":
commands.extend(self._update_bond_members(attrib, want, have))
elif attrib == "arp_monitor":
commands.extend(self._update_arp_monitor(attrib, want, have))
elif have.get(attrib) and not want.get(attrib):
commands.append(self._compute_command(have["name"], attrib, remove=True))
return commands
def _add_bond_members(self, want, have):
commands = []
diff_members = get_lst_diff_for_dicts(want, have, "members")
if diff_members:
for key in diff_members:
commands.append(
self._compute_command(
want["name"],
"member interface",
key["member"],
),
)
return commands
def _add_arp_monitor(self, updates, key, want, have):
commands = []
arp_monitor = updates.get(key) or {}
diff_targets = self._get_arp_monitor_target_diff(want, have, key, "target")
if "interval" in arp_monitor:
commands.append(
self._compute_command(
key=want["name"] + " arp-monitor",
attrib="interval",
value=str(arp_monitor["interval"]),
),
)
if diff_targets:
for target in diff_targets:
commands.append(
self._compute_command(
key=want["name"] + " arp-monitor",
attrib="target",
value=target,
),
)
return commands
def _delete_bond_members(self, have):
commands = []
for member in have["members"]:
commands.append(
self._compute_command(
have["name"],
"member interface",
member["member"],
remove=True,
),
)
return commands
def _update_arp_monitor(self, key, want, have):
commands = []
want_arp_target = []
have_arp_target = []
want_arp_monitor = want.get(key) or {}
have_arp_monitor = have.get(key) or {}
if want_arp_monitor and "target" in want_arp_monitor:
want_arp_target = want_arp_monitor["target"]
if have_arp_monitor and "target" in have_arp_monitor:
have_arp_target = have_arp_monitor["target"]
if "interval" in have_arp_monitor and not want_arp_monitor:
commands.append(
self._compute_command(
key=have["name"] + " arp-monitor",
attrib="interval",
remove=True,
),
)
if "target" in have_arp_monitor:
target_diff = list_diff_have_only(want_arp_target, have_arp_target)
if target_diff:
for target in target_diff:
commands.append(
self._compute_command(
key=have["name"] + " arp-monitor",
attrib="target",
value=target,
remove=True,
),
)
return commands
def _update_bond_members(self, key, want, have):
commands = []
want_members = want.get(key) or []
have_members = have.get(key) or []
members_diff = list_diff_have_only(want_members, have_members)
if members_diff:
for member in members_diff:
commands.append(
self._compute_command(
member["member"],
"bond-group",
have["name"],
True,
"ethernet",
),
)
return commands
def _get_arp_monitor_target_diff(self, want_list, have_list, dict_name, lst):
want_arp_target = []
have_arp_target = []
want_arp_monitor = want_list.get(dict_name) or {}
if want_arp_monitor and lst in want_arp_monitor:
want_arp_target = want_arp_monitor[lst]
if not have_list:
diff = want_arp_target
else:
have_arp_monitor = have_list.get(dict_name) or {}
if have_arp_monitor and lst in have_arp_monitor:
have_arp_target = have_arp_monitor[lst]
diff = list_diff_want_only(want_arp_target, have_arp_target)
return diff
def _compute_command(self, key, attrib, value=None, remove=False, type="bonding"):
if remove:
cmd = "delete interfaces " + type
else:
cmd = "set interfaces " + type
cmd += " " + key
if attrib == "arp_monitor":
attrib = "arp-monitor"
elif attrib == "hash_policy":
attrib = "hash-policy"
cmd += " " + attrib
if value:
cmd += " '" + value + "'"
return cmd
diff --git a/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py b/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py
index 39724f1b..82a35a54 100644
--- a/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py
+++ b/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py
@@ -1,307 +1,307 @@
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The vyos_lldp_global 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_diff,
to_list,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
get_lst_diff_for_dicts,
list_diff_have_only,
)
class Lldp_global(ConfigBase):
"""
The vyos_lldp_global class
"""
gather_subset = [
"!all",
"!min",
]
gather_network_resources = [
"lldp_global",
]
# address intentionally omitted since it's coerced to addresses
params = ["enable", "addresses", "snmp", "legacy_protocols"]
def __init__(self, module):
super(Lldp_global, self).__init__(module)
def get_lldp_global_facts(self, data=None):
"""Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
)
lldp_global_facts = facts["ansible_network_resources"].get("lldp_global")
if not lldp_global_facts:
return []
return lldp_global_facts
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
# fix for new name/type
if self._module.params["config"]:
temp_have_address = self._module.params["config"].pop("address", None)
if temp_have_address:
self._module.params["config"]["addresses"] = [temp_have_address]
if self.state in self.ACTION_STATES:
existing_lldp_global_facts = self.get_lldp_global_facts()
else:
existing_lldp_global_facts = []
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_lldp_global_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_lldp_global_facts = self.get_lldp_global_facts()
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
result["parsed"] = self.get_lldp_global_facts(data=running_config)
else:
changed_lldp_global_facts = []
if self.state in self.ACTION_STATES:
result["before"] = existing_lldp_global_facts
if result["changed"]:
result["after"] = changed_lldp_global_facts
elif self.state == "gathered":
result["gathered"] = changed_lldp_global_facts
result["warnings"] = warnings
return result
def set_config(self, existing_lldp_global_facts):
"""Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_lldp_global_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "rendered") and not want:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "deleted":
commands.extend(self._state_deleted(want=None, have=have))
elif self.state in ("merged", "rendered"):
commands.extend(self._state_merged(want=want, have=have))
elif self.state == "replaced":
commands.extend(self._state_replaced(want=want, have=have))
return commands
def _state_replaced(self, want, have):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
commands.extend(self._state_deleted(want, have))
commands.extend(self._state_merged(want, have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
commands.extend(self._render_updates(want, have))
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if want:
for item in Lldp_global.params:
if item == "legacy_protocols":
commands.extend(self._update_lldp_protocols(want, have))
if item == "addresses":
commands.extend(self._update_management_addresses(want, have))
elif have.get(item) and not want.get(item) and item != "enable":
commands.append(Lldp_global.del_cmd + item)
elif have:
if have.get("enable"):
commands.append(self._compute_command(remove=True))
return commands
for item in Lldp_global.params:
if have.get(item):
if item == "legacy_protocols":
commands.append(self._compute_command("legacy-protocols", remove=True))
elif item == "addresses":
commands.append(self._compute_command("management-address", remove=True))
elif item == "snmp":
commands.append(self._compute_command(item, remove=True))
return commands
def _render_updates(self, want, have):
commands = []
if have:
temp_have_legacy_protos = have.pop("legacy_protocols", None)
temp_have_addreses = have.pop("addresses", None)
temp_have_address = have.pop("address", None)
if temp_have_address:
temp_have_addresses = [temp_have_address]
else:
have = {}
temp_want_legacy_protos = want.pop("legacy_protocols", None)
temp_want_addreses = want.pop("addresses", None)
temp_want_address = want.pop("address", None)
if temp_want_address:
temp_want_addresses = [temp_want_address]
updates = dict_diff(have, want)
if have and temp_have_legacy_protos:
have["legacy_protocols"] = temp_have_legacy_protos
if not have and temp_want_legacy_protos:
want["legacy_protocols"] = temp_want_legacy_protos
if have and temp_have_addreses:
have["addresses"] = temp_have_addreses
if not have and temp_want_addreses:
want["addresses"] = temp_want_addreses
commands.extend(self._add_lldp_protocols(want, have))
commands.extend(self._add_management_addresses(want, have))
if updates:
for key, value in updates.items():
if value is not None:
if key == "enable":
if value is False:
commands.append(self._compute_command(remove=True))
return commands
else:
commands.append(self._compute_command())
elif key == "snmp":
if value == "disable":
commands.append(self._compute_command(key, remove=True))
else:
commands.append(self._compute_command(key, str(value)))
return commands
def _add_lldp_protocols(self, want, have):
commands = []
diff_members = get_lst_diff_for_dicts(want, have, "legacy_protocols")
if diff_members:
for key in diff_members:
commands.append(self._compute_command("legacy-protocols", key))
return commands
def _add_management_addresses(self, want, have):
commands = []
diff_members = get_lst_diff_for_dicts(want, have, "addresses")
if diff_members:
for key in diff_members:
commands.append(self._compute_command("management-address", key))
return commands
def _update_lldp_protocols(self, want_item, have_item):
commands = []
want_protocols = want_item.get("legacy_protocols") or []
have_protocols = have_item.get("legacy_protocols") or []
members_diff = list_diff_have_only(want_protocols, have_protocols)
if members_diff:
for member in members_diff:
commands.append(self._compute_command("legacy-protocols", member, remove=True))
return commands
def _update_management_addresses(self, want_item, have_item):
commands = []
want_addresses = want_item.get("addresses") or []
have_addresses = have_item.get("addresses") or []
members_diff = list_diff_have_only(want_addresses, have_addresses)
if members_diff:
for member in members_diff:
commands.append(self._compute_command("management-address", member, remove=True))
return commands
def _compute_command(self, key=None, value=None, remove=False):
if remove:
cmd = "delete service lldp"
else:
cmd = "set service lldp"
if key:
cmd += " " + key
if value:
cmd += " '" + value + "'"
return cmd
diff --git a/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py
index 97fcbce5..36bf1b2d 100644
--- a/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py
@@ -1,348 +1,347 @@
#
# -*- 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 vyos_lldp_interfaces 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
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_diff,
to_list,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
is_dict_element_present,
key_value_in_dict,
search_obj_in_list,
)
class Lldp_interfaces(ConfigBase):
"""
The vyos_lldp_interfaces class
"""
gather_subset = [
"!all",
"!min",
]
gather_network_resources = [
"lldp_interfaces",
]
params = ["enable", "location", "name"]
def __init__(self, module):
super(Lldp_interfaces, self).__init__(module)
def get_lldp_interfaces_facts(self, data=None):
"""Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
)
lldp_interfaces_facts = facts["ansible_network_resources"].get("lldp_interfaces")
if not lldp_interfaces_facts:
return []
return lldp_interfaces_facts
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
if self.state in self.ACTION_STATES:
existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
else:
existing_lldp_interfaces_facts = []
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_lldp_interfaces_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
result["parsed"] = self.get_lldp_interfaces_facts(data=running_config)
else:
changed_lldp_interfaces_facts = []
if self.state in self.ACTION_STATES:
result["before"] = existing_lldp_interfaces_facts
if result["changed"]:
result["after"] = changed_lldp_interfaces_facts
elif self.state == "gathered":
result["gathered"] = changed_lldp_interfaces_facts
result["warnings"] = warnings
return result
def set_config(self, existing_lldp_interfaces_facts):
"""Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_lldp_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "overridden", "rendered") and not want:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "overridden":
commands.extend(self._state_overridden(want=want, have=have))
elif self.state == "deleted":
if want:
for item in want:
name = item["name"]
have_item = search_obj_in_list(name, have)
commands.extend(self._state_deleted(want=None, have=have_item))
else:
for have_item in have:
commands.extend(self._state_deleted(want=None, have=have_item))
else:
for want_item in want:
name = want_item["name"]
have_item = search_obj_in_list(name, have)
if self.state in ("merged", "rendered"):
commands.extend(self._state_merged(want=want_item, have=have_item))
if self.state == "replaced":
commands.extend(self._state_replaced(want=want_item, have=have_item))
return commands
def _state_replaced(self, want, have):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
commands.extend(self._state_deleted(want, have))
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
"""The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for have_item in have:
lldp_name = have_item["name"]
lldp_in_want = search_obj_in_list(lldp_name, want)
if not lldp_in_want:
commands.append(self._compute_command(have_item["name"], remove=True))
for want_item in want:
name = want_item["name"]
lldp_in_have = search_obj_in_list(name, have)
commands.extend(self._state_replaced(want_item, lldp_in_have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
if have:
commands.extend(self._render_updates(want, have))
else:
commands.extend(self._render_set_commands(want))
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if want:
params = Lldp_interfaces.params
for attrib in params:
if attrib == "location":
commands.extend(self._update_location(have["name"], want, have))
elif have:
commands.append(self._compute_command(have["name"], remove=True))
return commands
def _render_updates(self, want, have):
commands = []
lldp_name = have["name"]
commands.extend(self._configure_status(lldp_name, want, have))
commands.extend(self._add_location(lldp_name, want, have))
return commands
def _render_set_commands(self, want):
commands = []
have = {}
lldp_name = want["name"]
params = Lldp_interfaces.params
for attrib in params:
value = want[attrib]
if value:
if attrib == "location":
commands.extend(self._add_location(lldp_name, want, have))
elif attrib == "enable":
if not value:
commands.append(self._compute_command(lldp_name, value="disable"))
else:
commands.append(self._compute_command(lldp_name))
return commands
def _configure_status(self, name, want_item, have_item):
commands = []
if is_dict_element_present(have_item, "enable"):
temp_have_item = False
else:
temp_have_item = True
if want_item["enable"] != temp_have_item:
if want_item["enable"]:
commands.append(self._compute_command(name, value="disable", remove=True))
else:
commands.append(self._compute_command(name, value="disable"))
return commands
def _add_location(self, name, want_item, have_item):
commands = []
have_dict = {}
have_ca = {}
set_cmd = name + " location "
want_location_type = want_item.get("location") or {}
have_location_type = have_item.get("location") or {}
if want_location_type["coordinate_based"]:
want_dict = want_location_type.get("coordinate_based") or {}
if is_dict_element_present(have_location_type, "coordinate_based"):
have_dict = have_location_type.get("coordinate_based") or {}
location_type = "coordinate-based"
updates = dict_diff(have_dict, want_dict)
for key, value in updates.items():
if value:
commands.append(self._compute_command(set_cmd + location_type, key, str(value)))
elif want_location_type["elin"]:
location_type = "elin"
if is_dict_element_present(have_location_type, "elin"):
if want_location_type.get("elin") != have_location_type.get("elin"):
commands.append(
self._compute_command(
set_cmd + location_type,
value=str(want_location_type["elin"]),
),
)
else:
commands.append(
self._compute_command(
set_cmd + location_type,
value=str(want_location_type["elin"]),
),
)
return commands
def _update_location(self, name, want_item, have_item):
commands = []
del_cmd = name + " location"
want_location_type = want_item.get("location") or {}
have_location_type = have_item.get("location") or {}
if want_location_type["coordinate_based"]:
want_dict = want_location_type.get("coordinate_based") or {}
if is_dict_element_present(have_location_type, "coordinate_based"):
have_dict = have_location_type.get("coordinate_based") or {}
location_type = "coordinate-based"
for key, value in have_dict.items():
only_in_have = key_value_in_dict(key, value, want_dict)
if not only_in_have:
commands.append(
self._compute_command(del_cmd + location_type, key, str(value), True),
)
else:
commands.append(self._compute_command(del_cmd, remove=True))
else:
if is_dict_element_present(have_location_type, "elin"):
if want_location_type.get("elin") != have_location_type.get("elin"):
commands.append(self._compute_command(del_cmd, remove=True))
else:
commands.append(self._compute_command(del_cmd, remove=True))
return commands
def _compute_command(self, key, attrib=None, value=None, remove=False):
if remove:
cmd = "delete service lldp interface "
else:
cmd = "set service lldp interface "
cmd += key
if attrib:
cmd += " " + attrib
if value:
cmd += " '" + value + "'"
return cmd
diff --git a/plugins/module_utils/network/vyos/config/logging_global/logging_global.py b/plugins/module_utils/network/vyos/config/logging_global/logging_global.py
index 35a2624a..8bc9d1f9 100644
--- a/plugins/module_utils/network/vyos/config/logging_global/logging_global.py
+++ b/plugins/module_utils/network/vyos/config/logging_global/logging_global.py
@@ -1,222 +1,221 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos_logging_global config file.
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 its desired end-state is
created.
"""
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
get_from_dict,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.logging_global import (
Logging_globalTemplate,
)
class Logging_global(ResourceModule):
"""
The vyos_logging_global config class
"""
def __init__(self, module):
super(Logging_global, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="logging_global",
tmplt=Logging_globalTemplate(),
)
self.parsers = [
"console.facilities",
"global_params.archive.file_num",
"global_params.archive.size",
"global_params.marker_interval",
"global_params.preserve_fqdn",
"global_params.facilities",
"files.archive.size",
"files.archive.file_num",
"files",
"hosts.port",
"hosts.facility.protocol", # 1.3 and below
"hosts.protocol",
"hosts",
"users",
]
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
if self.want:
wantd = self.list_to_dict(self.want, "want")
else:
wantd = dict()
if self.have:
haved = self.list_to_dict(self.have, "have")
else:
haved = dict()
if self.state in ["overridden", "replaced"]:
if wantd != haved:
wantx, havex = self.call_op(wantd, haved, "overridden")
for k, have in havex.items():
if k not in wantx:
self._compare(want={}, have=have)
if not self.state == "deleted":
wantd, haved = self.call_op(wantd, haved)
if self.state == "merged":
wantd = dict_merge(haved, wantd)
for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
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 Logging_global network resource.
"""
self.compare(parsers=self.parsers, want=want, have=have)
self.handleStates(want=want, have=have)
def operation_rep(self, params):
op_val = dict()
for k, val in params.items():
if k in ["console", "global_params"]:
mod_val = deepcopy(val)
op_val.update(self.flatten_facility({k: mod_val}))
elif k in ["files", "hosts", "users"]:
for m, n in val.items():
mod_n = deepcopy(n)
if mod_n.get("archive"):
del mod_n["archive"]
if mod_n.get("facilities"):
del mod_n["facilities"]
if mod_n.get("port"):
del mod_n["port"]
tm = self.flatten_facility({k: {m: mod_n}})
op_val.update(tm)
return op_val
def call_op(self, _want, _have, mode=None):
if mode == "overridden":
w = self.operation_rep(_want)
h = self.operation_rep(_have)
else:
w = self.flatten_facility(_want)
h = self.flatten_facility(_have)
return w, h
def handleStates(self, want=None, have=None):
stateparsers = [
"syslog.state",
"console.state",
"global_params.state",
"global_params.archive.state",
"files.archive.state",
]
for par in stateparsers:
op = get_from_dict(want, par)
if op == "enabled":
self.addcmd(want, par)
elif op == "disabled":
self.addcmd(want, par, True)
break
def flatten_facility(self, param):
temp_param = dict()
for element, val in param.items():
if element in ["console", "global_params", "syslog"]:
if element != "syslog" and val.get("facilities"):
for k, v in val.get("facilities").items():
temp_param[k + element] = {element: {"facilities": v}}
del val["facilities"]
if val:
temp_param[element] = {element: val}
if element in ["files", "hosts", "users"]:
for k, v in val.items():
if v.get("facilities"):
for pk, dat in v.get("facilities").items():
temp_param[pk + k] = {
element: {
"facilities": dat,
self.pkey.get(element): v.get(self.pkey.get(element)),
},
}
del v["facilities"]
if len(list(v.keys())) > 1:
temp_param[k] = {element: v}
else:
temp_param[k] = {element: v}
return temp_param
def list_to_dict(self, param, op=None):
updated_param = dict()
if self.state == "deleted":
if op == "have" and param:
self.handleStates({"syslog": {"state": "disabled"}})
updated_param == {}
else:
self.pkey = {
"files": "path",
"hosts": "hostname",
"users": "username",
}
for element, val in param.items():
if element == "facilities": # only with recursion call
_tem_par = {}
for par in val:
if par.get("facility") and par.get("severity"):
_tem_par.update({par.get("facility") + par.get("severity"): par})
elif par.get("facility") and par.get("protocol"):
_tem_par.update({par.get("facility") + par.get("protocol"): par})
else:
_tem_par.update({par.get("facility"): par})
return _tem_par
elif element in ["console", "global_params", "syslog"]:
if element != "syslog" and val.get("facilities"):
val["facilities"] = self.list_to_dict(val)
updated_param[element] = val
elif element in ["hosts", "users", "files"]:
for v in val:
if v.get("facilities"):
v["facilities"] = self.list_to_dict(v)
if updated_param.get(element):
updated_param[element].update({v.get(self.pkey.get(element)): v})
else:
updated_param[element] = {v.get(self.pkey.get(element)): v}
return updated_param
diff --git a/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py b/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py
index 06ec1d6b..78b7a545 100644
--- a/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py
+++ b/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py
@@ -1,204 +1,203 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos_ntp config file.
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 its desired end-state is
created.
"""
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ntp_global import (
NtpTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ntp_global(ResourceModule):
"""
The vyos_ntp config class
"""
def __init__(self, module):
super(Ntp_global, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="ntp_global",
tmplt=NtpTemplate(),
)
self.parsers = [
"allow_clients",
"listen_addresses",
"server",
"options",
"allow_clients_delete",
"listen_addresses_delete",
]
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
path = "service"
ac = "allow-client"
else:
path = "system"
ac = "allow-clients"
self._tmplt.set_ntp_path(path)
self._tmplt.set_ntp_ac(ac)
wantd = self._ntp_list_to_dict(self.want)
haved = self._ntp_list_to_dict(self.have)
# if state is merged, merge want onto have and then compare
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":
haved = {k: v for k, v in haved.items() if k in wantd or not wantd}
wantd = {}
commandlist = self._commandlist(haved)
servernames = self._servernames(haved)
# removing the servername and commandlist from the list after deleting it from haved
# iterate through the top-level items to delete
for k, have in haved.items():
if k not in wantd:
for hk, hval in have.items():
if hk == "allow_clients" and hk in commandlist:
self.commands.append(
self._tmplt.render({"": hk}, "allow_clients_delete", True),
)
commandlist.remove(hk)
elif hk == "listen_addresses" and hk in commandlist:
self.commands.append(
self._tmplt.render({"": hk}, "listen_addresses_delete", True),
)
commandlist.remove(hk)
elif hk == "server" and have["server"] in servernames:
self._compareoverride(want={}, have=have)
servernames.remove(have["server"])
# if everything is deleted add the delete command for {path} ntp
# this should be equiv: servernames == [] and commandlist == ["server"]:
if wantd == {} and haved != {}:
self.commands.append(
self._tmplt.render({}, "service_delete", True),
)
# remove existing config for overridden and replaced
# Getting the list of the server names from haved
# to avoid the duplication of overridding/replacing the servers
if self.state in ["overridden", "replaced"]:
commandlist = self._commandlist(haved)
servernames = self._servernames(haved)
for k, have in haved.items():
if k not in wantd:
if "server" not in have:
self._compareoverride(want={}, have=have)
# removing the servername from the list after deleting it from haved
elif have["server"] in servernames:
self._compareoverride(want={}, have=have)
servernames.remove(have["server"])
for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
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 Ntp network resource.
"""
if "options" in want:
self.compare(parsers="options", want=want, have=have)
else:
self.compare(parsers=self.parsers, want=want, have=have)
def _compareoverride(self, want, have):
# do not delete configuration with options level
for i, val in have.items():
if i == "options":
pass
else:
self.compare(parsers=i, want={}, have=have)
def _ntp_list_to_dict(self, entry):
servers_dict = {}
for k, data in entry.items():
if k == "servers":
for value in data:
if "options" in value:
result = self._serveroptions_list_to_dict(value)
for res, resvalue in result.items():
servers_dict.update({res: resvalue})
else:
servers_dict.update({value["server"]: value})
else:
for value in data:
servers_dict.update({"ip_" + value: {k: value}})
return servers_dict
def _serveroptions_list_to_dict(self, entry):
serveroptions_dict = {}
for Opk, Op in entry.items():
if Opk == "options":
for val in Op:
dict = {}
dict.update({"server": entry["server"]})
dict.update({Opk: val})
serveroptions_dict.update({entry["server"] + "_" + val: dict})
return serveroptions_dict
def _commandlist(self, haved):
commandlist = []
for k, have in haved.items():
for ck, cval in have.items():
if ck != "options" and ck not in commandlist:
commandlist.append(ck)
return commandlist
def _servernames(self, haved):
servernames = []
for k, have in haved.items():
for sk, sval in have.items():
if sk != "options" and sval not in servernames:
servernames.append(sval)
return servernames
diff --git a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
index 837a2ddf..bc93ac24 100644
--- a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
@@ -1,194 +1,193 @@
#
# -*- coding: utf-8 -*-
# Copyright 2020 Red Hat
# 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
"""
The vyos_ospf_interfaces config file.
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 its desired end-state is
created.
"""
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import (
Ospf_interfacesTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import (
Ospf_interfacesTemplate14,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ospf_interfaces(ResourceModule):
"""
The vyos_ospf_interfaces config class
"""
def __init__(self, module):
super(Ospf_interfaces, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="ospf_interfaces",
tmplt=Ospf_interfacesTemplate(),
)
self.parsers = [
"authentication_password",
"authentication_md5",
"bandwidth",
"cost",
"hello_interval",
"dead_interval",
"mtu_ignore",
"network",
"priority",
"retransmit_interval",
"transmit_delay",
"ifmtu",
"instance",
"passive",
]
def _validate_template(self):
version = get_os_version(self._module)
if LooseVersion(version) >= LooseVersion("1.4"):
self._tmplt = Ospf_interfacesTemplate14()
else:
self._tmplt = Ospf_interfacesTemplate()
def parse(self):
"""override parse to check template"""
self._validate_template()
return super().parse()
def get_parser(self, name):
"""get_parsers"""
self._validate_template()
return super().get_parser(name)
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
self._validate_template()
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = {}
haved = {}
for entry in self.want:
wantd.update({entry["name"]: entry})
for entry in self.have:
haved.update({entry["name"]: entry})
# turn all lists of dicts into dicts prior to merge
for entry in wantd, haved:
self._ospf_int_list_to_dict(entry)
# if state is merged, merge want onto have and then compare
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":
h_del = {}
for k, v in haved.items():
if k in wantd or not wantd:
h_del.update({k: v})
haved = h_del
have_int = []
for k, have in haved.items():
if k in wantd:
have_int.append(k)
self._remove_ospf_int(have)
wantd = {}
if self.state == "overridden":
have_int = []
for k, have in haved.items():
if k not in wantd:
have_int.append(k)
self._remove_ospf_int(have)
# remove superfluous config for overridden and deleted
if self.state in ["overridden", "deleted"]:
# removing the interfaces from haved that are already negated
for interface in have_int:
haved.pop(interface)
for k, have in haved.items():
if k not in wantd:
self._compare(want={}, have=have)
for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _remove_ospf_int(self, entry):
int_name = entry.get("name", {})
int_addr = entry.get("address_family", {})
for k, addr in int_addr.items():
rem_entry = {"name": int_name, "address_family": {"afi": k}}
self.addcmd(rem_entry, "ip_ospf", True)
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.
"""
self._compare_addr_family(want=want, have=have)
def _compare_addr_family(self, want, have):
wdict = want.get("address_family", {})
hdict = have.get("address_family", {})
wname = want.get("name")
hname = have.get("name")
for name, entry in wdict.items():
for key, param in entry.items():
w_addr = {"afi": name, key: param}
h_addr = {}
if hdict.get(name):
h_addr = {"afi": name, key: hdict[name].pop(key, {})}
w = {"name": wname, "address_family": w_addr}
h = {"name": hname, "address_family": h_addr}
self.compare(parsers=self.parsers, want=w, have=h)
for name, entry in hdict.items():
for key, param in entry.items():
h_addr = {"afi": name, key: param}
w_addr = {}
w = {"name": wname, "address_family": w_addr}
h = {"name": hname, "address_family": h_addr}
self.compare(parsers=self.parsers, want=w, have=h)
def _ospf_int_list_to_dict(self, entry):
for name, family in entry.items():
if "address_family" in family:
addr_dict = {}
for entry in family.get("address_family", []):
addr_dict.update({entry["afi"]: entry})
family["address_family"] = addr_dict
self._ospf_int_list_to_dict(family["address_family"])
diff --git a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py
index ed973179..20821980 100644
--- a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py
+++ b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py
@@ -1,843 +1,843 @@
#
# -*- 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 vyos_ospfv2 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
remove_empties,
to_list,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
_bool_to_str,
_in_target,
_is_w_same,
list_diff_want_only,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ospfv2(ConfigBase):
"""
The vyos_ospfv2 class
"""
gather_subset = ["!all", "!min"]
gather_network_resources = ["ospfv2"]
def __init__(self, module):
super(Ospfv2, self).__init__(module)
def get_ospfv2_facts(self, data=None):
"""Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
- (facts, _warnings) = Facts(self._module).get_facts(
+ facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
)
ospfv2_facts = facts["ansible_network_resources"].get("ospfv2", {})
return ospfv2_facts
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
if self.state in self.ACTION_STATES:
existing_ospfv2_facts = self.get_ospfv2_facts()
else:
existing_ospfv2_facts = {}
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_ospfv2_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_ospfv2_facts = self.get_ospfv2_facts()
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
result["parsed"] = self.get_ospfv2_facts(data=running_config)
else:
changed_ospfv2_facts = {}
if self.state in self.ACTION_STATES:
result["before"] = existing_ospfv2_facts
if result["changed"]:
result["after"] = changed_ospfv2_facts
elif self.state == "gathered":
result["gathered"] = changed_ospfv2_facts
result["warnings"] = warnings
return result
def set_config(self, existing_ospfv2_facts):
"""Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_ospfv2_facts
resp = self.set_state(remove_empties(want), remove_empties(have))
return to_list(resp)
def set_state(self, w, h):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "overridden", "rendered") and not w:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "deleted":
commands.extend(self._state_deleted(h))
elif self.state in ("merged", "rendered"):
commands.extend(self._state_merged(w, h))
elif self.state == "replaced":
commands.extend(self._state_replaced(w, h))
return commands
def search_obj_in_have(self, have, w_name, key):
"""
This function returns the rule-set/rule if it is present in target config.
:param have: target config.
:param w_name: rule-set name.
:param type: rule_sets/rule/r_list.
:return: rule-set/rule.
"""
if have:
for item in have:
if item[key] == w_name[key]:
return item
return None
def _state_replaced(self, want, have):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
commands.extend(self._render_ospf_param(have, want, opr=False))
commands.extend(self._render_ospf_param(want, have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
commands.extend(self._render_ospf_param(want, have))
return commands
def _state_deleted(self, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if have:
commands.append("delete protocols ospf")
return commands
def _render_ospf_param(self, want, have, opr=True):
"""
This function forms the set/delete commands for ospf leaf attributes
and triggers the process for other child attributes.
for firewall_global attributes.
:param w: the desired config.
:param h: the target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
w = deepcopy(remove_empties(want))
leaf = ("default_metric", "log_adjacency_changes")
if w:
for key, val in w.items():
if opr and key in leaf and not _is_w_same(w, have, key):
commands.append(self._form_attr_cmd(attr=key, val=_bool_to_str(val), opr=opr))
elif not opr and key in leaf and not _in_target(have, key):
commands.append(self._form_attr_cmd(attr=key, val=_bool_to_str(val), opr=opr))
else:
commands.extend(self._render_child_param(w, have, key, opr))
return commands
def _render_child_param(self, w, h, key, opr=True):
"""
This function invoke the function to extend commands
based on the key.
:param w: the desired configuration.
:param h: the current configuration.
:param key: attribute name.
:param opr: operation.
:return: list of commands.
"""
commands = []
if key in ("neighbor", "redistribute"):
commands.extend(self._render_list_dict_param(key, w, h, opr=opr))
elif key in ("default_information", "max_metric"):
commands.extend(self._render_nested_dict_param(key, w, h, opr=opr))
elif key in ("mpls_te", "auto_cost", "parameters", "auto_cost"):
commands.extend(self._render_dict_param(key, w, h, opr=opr))
elif key in (
"route_map",
"passive_interface",
"passive_interface_exclude",
):
commands.extend(self._render_list_param(key, w, h, opr=opr))
elif key == "areas":
commands.extend(self._render_areas(key, w, h, opr=opr))
elif key == "timers":
commands.extend(self._render_timers(key, w, h, opr=opr))
elif key == "distance":
commands.extend(self._render_distance(key, w, h, opr=opr))
return commands
def _render_dict_param(self, attr, want, have, opr=True):
"""
This function generate the commands for dictionary elements.
:param attr: attribute name.
:param w: the desired configuration.
:param h: the target config.
:param opr: True/False.
:return: generated list of commands.
"""
commands = []
h = {}
if have:
h = have.get(attr) or {}
if not opr and not h:
commands.append(self._form_attr_cmd(attr=attr, opr=opr))
elif want[attr]:
leaf_dict = {
"auto_cost": "reference_bandwidth",
"mpls_te": ("enabled", "router_address"),
"parameters": (
"router_id",
"abr_type",
"opaque_lsa",
"rfc1583_compatibility",
),
}
leaf = leaf_dict[attr]
for item, value in want[attr].items():
if opr and item in leaf and not _is_w_same(want[attr], h, item):
if item == "enabled":
item = "enable"
if item in (
"opaque_lsa",
"enable",
"rfc1583_compatibility",
):
commands.append(self._form_attr_cmd(key=attr, attr=item, opr=opr))
else:
commands.append(
self._form_attr_cmd(key=attr, attr=item, val=value, opr=opr),
)
elif not opr and item in leaf and not _in_target(h, item):
if item == "enabled":
commands.append(self._form_attr_cmd(key=attr, attr="enable", opr=opr))
else:
commands.append(self._form_attr_cmd(key=attr, attr=item, opr=opr))
return commands
def _render_list_param(self, attr, want, have, cmd=None, opr=True):
"""
This function forms the commands for passed target list attributes'.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param cmd: commands to be prepend.
:param opr: True/False.
:return: generated list of commands.
"""
commands = []
h = []
if want:
w = want.get(attr) or []
if have:
h = have.get(attr) or []
if not cmd:
cmd = self._compute_command(opr=opr)
if w:
if opr:
members = list_diff_want_only(w, h)
for member in members:
command = cmd + attr.replace("_", "-") + " "
if attr == "network":
command += member["address"]
elif (
attr == "passive_interface"
and member != "default"
and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
):
command = (
command.replace("passive-interface", "interface") + member + " passive"
)
elif attr == "passive_interface_exclude" and LooseVersion(
get_os_version(self._module),
) >= LooseVersion("1.4"):
command = (
command.replace("passive-interface-exclude", "interface")
+ member
+ " passive disable"
)
else:
command += member
commands.append(command)
elif not opr:
if h:
for member in w:
if attr == "network":
if not self.search_obj_in_have(h, member, "address"):
commands.append(
cmd + attr.replace("_", "-") + " " + member["address"],
)
elif member not in h:
if (
attr == "passive_interface"
and member != "default"
and LooseVersion(get_os_version(self._module))
>= LooseVersion("1.4")
):
commands.append(cmd + "interface" + " " + member + " passive")
elif attr == "passive_interface_exclude" and LooseVersion(
get_os_version(self._module),
) >= LooseVersion("1.4"):
command = (
command.replace("passive-interface-exclude", "interface")
+ member
+ " passive disable"
)
else:
commands.append(cmd + attr.replace("_", "-") + " " + member)
else:
commands.append(cmd + " " + attr.replace("_", "-"))
return commands
def _render_vlink(self, attr, want, have, cmd=None, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for attributes with in desired list of dictionary.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param cmd: commands to be prepend.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
h = []
name = {"virtual_link": "address"}
leaf_dict = {
"virtual_link": (
"address",
"dead_interval",
"transmit_delay",
"hello_interval",
"retransmit_interval",
),
}
leaf = leaf_dict[attr]
w = want.get(attr) or []
if have:
h = have.get(attr) or []
if not opr and not h:
commands.append(cmd + attr.replace("_", "-"))
elif w:
for w_item in w:
for key, val in w_item.items():
if not cmd:
cmd = self._compute_command(opr=opr)
h_item = self.search_obj_in_have(h, w_item, name[attr])
if opr and key in leaf and not _is_w_same(w_item, h_item, key):
if key in "address":
commands.append(cmd + attr.replace("_", "-") + " " + str(val))
else:
commands.append(
cmd
+ attr.replace("_", "-")
+ " "
+ w_item[name[attr]]
+ " "
+ key.replace("_", "-")
+ " "
+ str(val),
)
elif not opr and key in leaf and not _in_target(h_item, key):
if key in "address":
commands.append(cmd + attr.replace("_", "-") + " " + str(val))
else:
commands.append(
cmd + attr.replace("_", "-") + " " + w_item[name[attr]] + " " + key,
)
elif key == "authentication":
commands.extend(
self._render_vlink_auth(
attr,
key,
w_item,
h_item,
w_item["address"],
cmd,
opr,
),
)
return commands
def _render_vlink_auth(self, attr, key, want, have, address, cmd=None, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for attributes with in desired list of dictionary.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param cmd: commands to be prepend.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
h = []
w = want.get(key) or {}
if have:
h = have.get(key) or {}
cmd += attr.replace("_", "-") + " " + address + " " + key + " "
commands.extend(self._render_list_dict_param("md5", w, h, cmd, opr))
return commands
def _render_list_dict_param(self, attr, want, have, cmd=None, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for attributes with in desired list of dictionary.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param cmd: commands to be prepend.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
h = []
name = {
"redistribute": "route_type",
"neighbor": "neighbor_id",
"range": "address",
"md5": "key_id",
"vlink": "address",
}
leaf_dict = {
"md5": "md5_key",
"redistribute": (
"metric",
"route_map",
"route_type",
"metric_type",
),
"neighbor": ("priority", "poll_interval", "neighbor_id"),
"range": ("cost", "address", "substitute", "not_advertise"),
"vlink": (
"address",
"dead_interval",
"transmit_delay",
"hello_interval",
"retransmit_interval",
),
}
leaf = leaf_dict[attr]
w = want.get(attr) or []
if have:
h = have.get(attr) or []
if not opr and not h:
commands.append(self._compute_command(attr=attr, opr=opr))
elif w:
for w_item in w:
for key, val in w_item.items():
if not cmd:
cmd = self._compute_command(opr=opr)
h_item = self.search_obj_in_have(h, w_item, name[attr])
if opr and key in leaf and not _is_w_same(w_item, h_item, key):
if key in (
"route_type",
"neighbor_id",
"address",
"key_id",
):
commands.append(cmd + attr + " " + str(val))
elif key == "cost":
commands.append(
cmd + attr + " " + w_item[name[attr]] + " " + key + " " + str(val),
)
elif key == "not_advertise":
commands.append(
cmd + attr + " " + w_item[name[attr]] + " " + key.replace("_", "-"),
)
elif key == "md5_key":
commands.append(
cmd
+ attr
+ " "
+ "key-id"
+ " "
+ str(w_item[name[attr]])
+ " "
+ key.replace("_", "-")
+ " "
+ w_item[key],
)
else:
commands.append(
cmd
+ attr
+ " "
+ w_item[name[attr]]
+ " "
+ key.replace("_", "-")
+ " "
+ str(val),
)
elif not opr and key in leaf and not _in_target(h_item, key):
if key in (
"route_type",
"neighbor_id",
"address",
"key_id",
):
commands.append(cmd + attr + " " + str(val))
else:
commands.append(cmd + attr + " " + w_item[name[attr]] + " " + key)
return commands
def _render_nested_dict_param(self, attr, want, have, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for attributes with in desired nested dicts.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param cmd: commands to be prepend.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
attr_dict = {
"default_information": "originate",
"max_metric": "router_lsa",
}
leaf_dict = {
"default_information": (
"always",
"metric",
"metric_type",
"route_map",
),
"max_metric": ("administrative", "on_startup", "on_shutdown"),
}
h = {}
w = want.get(attr) or {}
if have:
h = have.get(attr) or {}
if not opr and not h:
commands.append(self._form_attr_cmd(attr=attr, opr=opr))
elif w:
key = attr_dict[attr]
w_attrib = want[attr].get(key) or {}
cmd = self._compute_command(opr=opr)
h_attrib = {}
if w_attrib:
leaf = leaf_dict[attr]
if h and key in h.keys():
h_attrib = h.get(key) or {}
for item, val in w[key].items():
if opr and item in leaf and not _is_w_same(w[key], h_attrib, item):
if item in ("administrative", "always") and val:
commands.append(
cmd
+ attr.replace("_", "-")
+ " "
+ key.replace("_", "-")
+ " "
+ item.replace("_", "-"),
)
elif item not in ("administrative", "always"):
commands.append(
cmd
+ attr.replace("_", "-")
+ " "
+ key.replace("_", "-")
+ " "
+ item.replace("_", "-")
+ " "
+ str(val),
)
elif not opr and item in leaf and not _in_target(h_attrib, item):
commands.append(cmd + attr + " " + item)
return commands
def _render_areas(self, attr, want, have, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for ospf area attributes.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
h_lst = {}
w_lst = want.get(attr) or []
l_set = ("area_id", "shortcut", "authentication")
if have:
h_lst = have.get(attr) or []
if not opr and not h_lst:
commands.append(self._form_attr_cmd(attr="area", opr=opr))
elif w_lst:
for w_area in w_lst:
cmd = (
self._compute_command(
key="area",
attr=_bool_to_str(w_area["area_id"]),
opr=opr,
)
+ " "
)
h_area = self.search_obj_in_have(h_lst, w_area, "area_id")
if not opr and not h_area:
commands.append(
self._form_attr_cmd(key="area", attr=w_area["area_id"], opr=opr),
)
else:
for key, val in w_area.items():
if opr and key in l_set and not _is_w_same(w_area, h_area, key):
if key == "area_id":
commands.append(
self._form_attr_cmd(
attr="area",
val=_bool_to_str(val),
opr=opr,
),
)
else:
commands.append(
cmd + key + " " + _bool_to_str(val).replace("_", "-"),
)
elif not opr and key in l_set:
if key == "area_id" and not _in_target(h_area, key):
commands.append(cmd)
continue
if key != "area_id" and not _in_target(h_area, key):
commands.append(cmd + val + " " + key)
elif key == "area_type":
commands.extend(self._render_area_type(w_area, h_area, key, cmd, opr))
elif key == "network":
commands.extend(self._render_list_param(key, w_area, h_area, cmd, opr))
elif key == "range":
commands.extend(
self._render_list_dict_param(key, w_area, h_area, cmd, opr),
)
elif key == "virtual_link":
commands.extend(self._render_vlink(key, w_area, h_area, cmd, opr))
return commands
def _render_area_type(self, want, have, attr, cmd, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for area_types attributes.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param cmd: command to prepend.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
h_type = {}
w_type = want.get(attr) or []
if have:
h_type = have.get(attr) or {}
if not opr and not h_type:
commands.append(cmd + attr.replace("_", "-"))
elif w_type:
key = "normal"
if opr and key in w_type.keys() and not _is_w_same(w_type, h_type, key):
if not w_type[key] and h_type and h_type[key]:
commands.append(
cmd.replace("set", "delete") + attr.replace("_", "-") + " " + key,
)
elif w_type[key]:
commands.append(cmd + attr.replace("_", "-") + " " + key)
elif not opr and key in w_type.keys() and not (h_type and key in h_type.keys()):
commands.append(cmd + want["area"] + " " + attr.replace("_", "-"))
a_type = {
"nssa": ("set", "default_cost", "no_summary", "translate"),
"stub": ("set", "default_cost", "no_summary"),
}
for key in a_type:
w_area = want[attr].get(key) or {}
h_area = {}
if w_area:
if h_type and key in h_type.keys():
h_area = h_type.get(key) or {}
for item, val in w_type[key].items():
if (
opr
and item in a_type[key]
and not _is_w_same(w_type[key], h_area, item)
):
if item == "set" and val:
commands.append(cmd + attr.replace("_", "-") + " " + key)
elif not val and h_area and h_area[item]:
commands.append(
cmd.replace("set", "delete")
+ attr.replace("_", "-")
+ " "
+ key,
)
elif item != "set":
commands.append(
cmd
+ attr.replace("_", "-")
+ " "
+ key
+ " "
+ item.replace("_", "-")
+ " "
+ str(val),
)
elif not opr and item in a_type[key] and not (h_type and key in h_type):
if item == "set":
commands.append(cmd + attr.replace("_", "-") + " " + key)
else:
commands.append(
cmd
+ want["area"]
+ " "
+ attr.replace("_", "-")
+ " "
+ key
+ " "
+ item.replace("_", "-"),
)
return commands
def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True):
"""
This function forms the command for leaf attribute.
:param key: parent key.
:param attr: attribute name
:param value: value
:param opr: True/False.
:return: generated command.
"""
return self._compute_command(key, attr=self._map_attrib(attr), val=val, opr=opr)
def _compute_command(self, key=None, attr=None, val=None, remove=False, opr=True):
"""
This function construct the add/delete command based on passed attributes.
:param key: parent key.
:param attr: attribute name
:param value: value
:param opr: True/False.
:return: generated command.
"""
if remove or not opr:
cmd = "delete protocols ospf "
else:
cmd = "set protocols ospf "
if key:
cmd += key.replace("_", "-") + " "
if attr:
cmd += attr.replace("_", "-")
if val:
cmd += " '" + str(val) + "'"
return cmd
def _map_attrib(self, attrib):
"""
- This function construct the regex string.
- replace the underscore with hyphen.
:param attrib: attribute
:return: regex string
"""
return "disable" if attrib == "disabled" else attrib.replace("_", "-")
diff --git a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py
index 30d30571..a84899f5 100644
--- a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py
+++ b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py
@@ -1,450 +1,450 @@
#
# -*- 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 vyos_ospfv3 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
remove_empties,
search_obj_in_list,
to_list,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
_bool_to_str,
_in_target,
_is_w_same,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ospfv3(ConfigBase):
"""
The vyos_ospfv3 class
"""
gather_subset = [
"!all",
"!min",
]
gather_network_resources = [
"ospfv3",
]
def __init__(self, module):
super(Ospfv3, self).__init__(module)
def get_ospfv3_facts(self, data=None):
"""Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
)
ospfv3_facts = facts["ansible_network_resources"].get("ospfv3", {})
return ospfv3_facts
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
if self.state in self.ACTION_STATES:
existing_ospfv3_facts = self.get_ospfv3_facts()
else:
existing_ospfv3_facts = {}
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_ospfv3_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_ospfv3_facts = self.get_ospfv3_facts()
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
result["parsed"] = self.get_ospfv3_facts(data=running_config)
else:
changed_ospfv3_facts = {}
if self.state in self.ACTION_STATES:
result["before"] = existing_ospfv3_facts
if result["changed"]:
result["after"] = changed_ospfv3_facts
elif self.state == "gathered":
result["gathered"] = changed_ospfv3_facts
result["warnings"] = warnings
return result
def set_config(self, existing_ospfv3_facts):
"""Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_ospfv3_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, w, h):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "overridden", "rendered") and not w:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "deleted":
commands.extend(self._state_deleted(w, h))
elif self.state in ("merged", "rendered"):
commands.extend(self._state_merged(w, h))
elif self.state == "replaced":
commands.extend(self._state_replaced(w, h))
return commands
def _state_replaced(self, want, have):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
commands.extend(self._render_ospf_param(have, want, opr=False))
commands.extend(self._render_ospf_param(want, have))
return commands
def _state_merged(self, want, have):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
commands.extend(self._render_ospf_param(want, have))
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if have:
commands.append("delete protocols ospfv3")
return commands
def _render_ospf_param(self, want, have, opr=True):
"""
This function forms the set/delete commands for ospf leaf attributes
and triggers the process for other child attributes.
for firewall_global attributes.
:param w: the desired config.
:param h: the target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
w = deepcopy(remove_empties(want))
if w:
for key, val in w.items():
commands.extend(self._render_child_param(w, have, key, opr))
return commands
def _render_child_param(self, w, h, key, opr=True):
"""
This function invoke the function to extend commands
based on the key.
:param w: the desired configuration.
:param h: the current configuration.
:param key: attribute name.
:param opr: operation.
:return: list of commands.
"""
commands = []
if key == "areas":
commands.extend(self._render_areas(key, w, h, opr=opr))
elif key == "parameters":
commands.extend(self._render_dict_param(key, w, h, opr=opr))
elif key == "redistribute":
commands.extend(self._render_list_dict_param(key, w, h, opr=opr))
return commands
def _render_dict_param(self, attr, want, have, opr=True):
"""
This function generate the commands for dictionary elements.
:param attr: attribute name.
:param w: the desired configuration.
:param h: the target config.
:param opr: True/False.
:return: generated list of commands.
"""
commands = []
h = {}
if have:
h = have.get(attr) or {}
if not opr and not h:
commands.append(self._form_attr_cmd(attr=attr, opr=opr))
elif want[attr]:
leaf_dict = {"parameters": "router_id"}
leaf = leaf_dict[attr]
for item, value in want[attr].items():
if opr and item in leaf and not _is_w_same(want[attr], h, item):
commands.append(self._form_attr_cmd(key=attr, attr=item, val=value, opr=opr))
elif not opr and item in leaf and not _in_target(h, item):
commands.append(self._form_attr_cmd(key=attr, attr=item, opr=opr))
return commands
def _render_list_dict_param(self, attr, want, have, cmd=None, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for attributes with in desired list of dictionary.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param cmd: commands to be prepend.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
h = []
name = {
"redistribute": "route_type",
"range": "address",
"interface": "name",
}
leaf_dict = {
"redistribute": ("route_map", "route_type"),
"range": ("address", "advertise", "not_advertise"),
"interface": ("name"),
}
leaf = leaf_dict[attr]
w = want.get(attr) or []
if have:
h = have.get(attr) or []
if not opr and not h:
commands.append(self._compute_command(attr=attr, opr=opr))
elif w:
for w_item in w:
for key, val in w_item.items():
if not cmd:
cmd = self._compute_command(opr=opr)
h_item = search_obj_in_list(w_item[name[attr]], h, name[attr])
if opr and key in leaf and not _is_w_same(w_item, h_item, key):
if key in ["route_type", "name"] or (
key == "address"
and "advertise" not in w_item
and "not-advertise" not in w_item
):
if not val:
cmd = cmd.replace("set", "delete")
if (
LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
and attr == "interface"
):
words = cmd.split()
cmd14_list = []
for word in words:
cmd14_list.append(word)
if word == "ospfv3":
cmd14_list.append(attr + " " + str(val))
commands.append(" ".join(cmd14_list))
else:
commands.append(cmd + attr + " " + str(val))
elif key in leaf_dict["range"] and key != "address":
commands.append(
cmd + attr + " " + w_item[name[attr]] + " " + key.replace("_", "-"),
)
elif key == "route_map":
commands.append(
cmd
+ attr
+ " "
+ w_item[name[attr]]
+ " "
+ key.replace("_", "-")
+ " "
+ str(val),
)
elif not opr and key in leaf and not _in_target(h_item, key):
if key in ("route_type", "address", "name"):
if (
LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
and attr == "interface"
):
words = cmd.split()
cmd14_list = []
for word in words:
cmd14_list.append(word)
if word == "ospfv3":
cmd14_list.append(attr + " " + str(val))
commands.append(" ".join(cmd14_list))
else:
commands.append(cmd + attr + " " + str(val))
else:
commands.append(cmd + (attr + " " + w_item[name[attr]] + " " + key))
return commands
def _render_areas(self, attr, want, have, opr=True):
"""
This function forms the set/delete commands based on the 'opr' type
for ospf area attributes.
:param attr: attribute name.
:param w: the desired config.
:param h: the target config.
:param opr: True/False.
:return: generated commands list.
"""
commands = []
h_lst = {}
w_lst = want.get(attr) or []
l_set = ("area_id", "export_list", "import_list")
if have:
h_lst = have.get(attr) or []
if not opr and not h_lst:
commands.append(self._form_attr_cmd(attr="area", opr=opr))
elif w_lst:
for w_area in w_lst:
cmd = (
self._compute_command(
key="area",
attr=_bool_to_str(w_area["area_id"]),
opr=opr,
)
+ " "
)
h_area = search_obj_in_list(w_area["area_id"], h_lst, "area_id")
if not opr and not h_area:
commands.append(
self._form_attr_cmd(key="area", attr=w_area["area_id"], opr=opr),
)
else:
for key, val in w_area.items():
if opr and key in l_set and not _is_w_same(w_area, h_area, key):
if key == "area_id":
commands.append(
self._form_attr_cmd(
attr="area",
val=_bool_to_str(val),
opr=opr,
),
)
else:
commands.append(
cmd
+ key.replace("_", "-")
+ " "
+ _bool_to_str(val).replace("_", "-"),
)
elif not opr and key in l_set:
if key == "area_id" and not _in_target(h_area, key):
commands.append(cmd)
continue
if key != "area_id" and not _in_target(h_area, key):
commands.append(cmd + val + " " + key)
elif key == "range":
commands.extend(
self._render_list_dict_param(key, w_area, h_area, cmd, opr),
)
elif key == "interface":
commands.extend(
self._render_list_dict_param(key, w_area, h_area, cmd, opr),
)
return commands
def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True):
"""
This function forms the command for leaf attribute.
:param key: parent key.
:param attr: attribute name
:param value: value
:param opr: True/False.
:return: generated command.
"""
return self._compute_command(key, attr=self._map_attrib(attr), val=val, opr=opr)
def _compute_command(self, key=None, attr=None, val=None, remove=False, opr=True):
"""
This function construct the add/delete command based on passed attributes.
:param key: parent key.
:param attr: attribute name
:param value: value
:param opr: True/False.
:return: generated command.
"""
if remove or not opr:
cmd = "delete protocols ospfv3 "
else:
cmd = "set protocols ospfv3 "
if key:
cmd += key.replace("_", "-") + " "
if attr:
cmd += attr.replace("_", "-")
if val and opr:
cmd += " '" + str(val) + "'"
return cmd
def _map_attrib(self, attrib):
"""
- This function construct the regex string.
- replace the underscore with hyphen.
:param attrib: attribute
:return: regex string
"""
return "disable" if attrib == "disabled" else attrib.replace("_", "-")
diff --git a/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py b/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py
index 9b703399..05164c31 100644
--- a/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py
+++ b/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py
@@ -1,174 +1,173 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos_prefix_lists config file.
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 its desired end-state is
created.
"""
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.prefix_lists import (
Prefix_listsTemplate,
)
class Prefix_lists(ResourceModule):
"""
The vyos_prefix_lists config class
"""
def __init__(self, module):
super(Prefix_lists, self).__init__(
empty_fact_val=[],
facts_module=Facts(module),
module=module,
resource="prefix_lists",
tmplt=Prefix_listsTemplate(),
)
self.plist_parsers = [
"name",
"description",
]
self.entries_parsers = [
"sequence",
"action",
"rule_description",
"ge",
"le",
"prefix",
]
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = {entry["afi"]: entry for entry in self.want}
haved = {entry["afi"]: entry for entry in self.have}
self._prefix_list_list_to_dict(wantd)
self._prefix_list_list_to_dict(haved)
# if state is merged, merge want onto have and then compare
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":
haved = {k: v for k, v in haved.items() if k in wantd or not wantd}
for key, hvalue in haved.items():
wvalue = wantd.pop(key, {})
if wvalue:
wplists = wvalue.get("prefix_lists", {})
hplists = hvalue.get("prefix_lists", {})
hvalue["prefix_lists"] = {
k: v for k, v in hplists.items() if k in wplists or not wplists
}
# remove superfluous config for overridden and deleted
if self.state in ["overridden", "deleted"]:
for k, have in haved.items():
if k not in wantd:
self._compare(want={}, have=have)
for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
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 Prefix_lists network resource.
"""
wplists = want.get("prefix_lists", {})
hplists = have.get("prefix_lists", {})
self._compare_plists(want=wplists, have=hplists)
if self.state in ["overridden", "deleted"]:
# remove remaining prefix lists
for h in hplists.values():
self.commands.append(
"delete policy prefix-{0} {1}".format(
"list" if h["afi"] == "ipv4" else "list6",
h["name"],
),
)
def _compare_plists(self, want, have):
for wk, wentry in want.items():
hentry = have.pop(wk, {})
# parser list for name and descriptions
self.compare(
parsers=self.plist_parsers,
want=wentry,
have=hentry,
)
wplrules = wentry.get("entries", {})
hplrules = hentry.get("entries", {})
self._compare_rules(want=wplrules, have=hplrules)
def _compare_rules(self, want, have):
for wr, wrule in want.items():
hrule = have.pop(wr, {})
# parser list for entries
self.compare(
parsers=self.entries_parsers,
want=wrule,
have=hrule,
)
# remove remaining entries
for hr in have.values():
self.commands.append(
"delete policy prefix-{0} {1} rule {2}".format(
"list" if hr["afi"] == "ipv4" else "list6",
hr["name"],
hr["sequence"],
),
)
def _prefix_list_list_to_dict(self, entry):
for afi, value in entry.items():
if "prefix_lists" in value:
for pl in value["prefix_lists"]:
pl.update({"afi": afi})
if "entries" in pl:
for entry in pl["entries"]:
entry.update({"afi": afi, "name": pl["name"]})
pl["entries"] = {x["sequence"]: x for x in pl["entries"]}
value["prefix_lists"] = {entry["name"]: entry for entry in value["prefix_lists"]}
diff --git a/plugins/module_utils/network/vyos/config/route_maps/route_maps.py b/plugins/module_utils/network/vyos/config/route_maps/route_maps.py
index dca99d1c..948341d3 100644
--- a/plugins/module_utils/network/vyos/config/route_maps/route_maps.py
+++ b/plugins/module_utils/network/vyos/config/route_maps/route_maps.py
@@ -1,183 +1,182 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos_route_maps config file.
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 its desired end-state is
created.
"""
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps import (
Route_mapsTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps_14 import (
Route_mapsTemplate14,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Route_maps(ResourceModule):
"""
The vyos_route_maps config class
"""
def __init__(self, module):
super(Route_maps, self).__init__(
empty_fact_val=[],
facts_module=Facts(module),
module=module,
resource="route_maps",
tmplt=Route_mapsTemplate(),
)
self.parsers = [
"call",
"description",
"action",
"continue_sequence",
"set_aggregator_ip",
"set_aggregator_as",
"set_as_path_exclude",
"set_as_path_prepend",
"set_atomic_aggregate",
"set_bgp_extcommunity_rt",
"set_extcommunity_rt",
"set_extcommunity_soo",
"set_extcommunity_bandwidth",
"set_extcommunity_bandwidth_non_transitive",
"set_ip_next_hop",
"set_ipv6_next_hop",
"set_large_community",
"set_local_preference",
"set_metric",
"set_metric_type",
"set_origin",
"set_originator_id",
"set_src",
"set_tag",
"set_weight",
"set_table",
"set_comm_list",
"set_comm_list_delete",
"set_community",
"match_as_path",
"match_community_community_list",
"match_community_exact_match",
"match_extcommunity",
"match_interface",
"match_large_community_large_community_list",
"match_metric",
"match_origin",
"match_peer",
"match_ip_address",
"match_ip_next_hop",
"match_ip_route_source",
"on_match_goto",
"on_match_next",
"match_ipv6_address",
"match_ipv6_nexthop",
"match_protocol",
"match_rpki",
]
def _validate_template(self):
version = get_os_version(self._module)
if LooseVersion(version) >= LooseVersion("1.4"):
self._tmplt = Route_mapsTemplate14()
else:
self._tmplt = Route_mapsTemplate()
def parse(self):
"""override parse to check template"""
self._validate_template()
return super().parse()
def get_parser(self, name):
"""get_parsers"""
self._validate_template()
return super().get_parser(name)
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
self._validate_template()
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = self._route_maps_list_to_dict(self.want)
haved = self._route_maps_list_to_dict(self.have)
# if state is merged, merge want onto have and then compare
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":
haved = {k: v for k, v in haved.items() if k in wantd or not wantd}
wantd = {}
# remove superfluous config for overridden and deleted
if self.state in ["overridden", "deleted"]:
for k, have in haved.items():
if k not in wantd:
self.commands.append(self._tmplt.render({"route_map": k}, "route_map", True))
for wk, want in wantd.items():
self._compare(want=want, have=haved.pop(wk, {}))
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 Route_maps network resource.
"""
w_entries = want.get("entries", {})
h_entries = have.get("entries", {})
self._compare_entries(want=w_entries, have=h_entries)
def _compare_entries(self, want, have):
for wk, wentry in want.items():
hentry = have.pop(wk, {})
self.compare(parsers=self.parsers, want=wentry, have=hentry)
def _route_maps_list_to_dict(self, entry):
entry = {x["route_map"]: x for x in entry}
for rmap, data in entry.items():
if "entries" in data:
for x in data["entries"]:
x.update({"route_map": rmap})
data["entries"] = {
(rmap, entry.get("sequence")): entry for entry in data["entries"]
}
return entry
diff --git a/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py b/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py
index 4bc5ddc4..cdc1d6e1 100644
--- a/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py
+++ b/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py
@@ -1,305 +1,304 @@
#
# -*- coding: utf-8 -*-
# Copyright 2022 Red Hat
# 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
"""
The vyos_snmp_server config file.
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 its desired end-state is
created.
"""
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_merge,
get_from_dict,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.snmp_server import (
Snmp_serverTemplate,
)
class Snmp_server(ResourceModule):
"""
The vyos_snmp_server config class
"""
def __init__(self, module):
super(Snmp_server, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="snmp_server",
tmplt=Snmp_serverTemplate(),
)
self.parsers = [
"contact",
"description",
"location",
"smux_peer",
"trap_source",
"trap_target",
]
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = self._snmp_server_list_to_dict(self.want)
haved = self._snmp_server_list_to_dict(self.have)
# if state is merged, merge want onto have and then compare
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":
if haved:
self.commands.append("delete service snmp")
if self.state != "deleted":
self._compare(want=wantd, have=haved)
if self.state not in ["merged", "deleted"]:
self._move_negate_commands()
def _move_negate_commands(self):
command_set = []
for cmd in self.commands:
if re.search("delete service snmp", cmd):
command_set.insert(0, cmd)
else:
command_set.append(cmd)
self.commands = command_set
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 Snmp_server network resource.
"""
self._compare_lists(want, have)
self._compare_snmp_v3(want, have)
for key, value in want.items():
self.compare(
parsers=self.parsers,
want={key: value},
have={key: have.pop(key, "")},
)
for key, entry in have.items():
if entry:
self.compare(parsers=self.parsers, want={}, have={key: entry})
def _compare_lists(self, want, have):
parsers = [
"communities",
"listen_addresses",
]
for attrib in parsers:
wdict = get_from_dict(want, attrib) or {}
hdict = get_from_dict(have, attrib) or {}
for key, entry in wdict.items():
# self.addcmd(entry, attrib, False)
if attrib == "communities":
for k, v in entry.items():
if k in ["clients", "networks"]:
v.sort()
h = {}
if k != "name":
if hdict.get(key):
h = {
"communities": {
k: hdict[key].pop(k, ""),
"name": key,
},
}
self.compare(
parsers="communities",
want={"communities": {k: v, "name": key}},
have=h,
)
else:
self.compare(
parsers="listen_addresses",
want={"listen_addresses": entry},
have={"listen_addresses": hdict.pop(key, {})},
)
have.pop(attrib, {})
# remove remaining items in have for replaced
for key, entry in hdict.items():
if attrib == "communities":
for k, v in entry.items():
if k != "name":
self.compare(
parsers="communities",
have={"communities": {k: v, "name": key}},
want={},
)
else:
self.compare(parsers=parsers, want={}, have={attrib: entry})
def _compare_snmp_v3(self, want, have):
parsers = [
"snmp_v3.groups",
"snmp_v3.trap_targets.port",
"snmp_v3.trap_targets.protocol",
"snmp_v3.trap_targets.type",
"snmp_v3.trap_targets.user",
"snmp_v3.users.group",
"snmp_v3.users.mode",
"snmp_v3.views",
"snmp_v3.engine_id",
]
attribute_dict = {
"groups": "group",
"trap_targets": "address",
"users": "user",
"views": "view",
}
wdict = get_from_dict(want, "snmp_v3") or {}
hdict = get_from_dict(have, "snmp_v3") or {}
for attrib in attribute_dict.keys():
wattrib = get_from_dict(wdict, attrib) or {}
hattrib = get_from_dict(hdict, attrib) or {}
for key, entry in wattrib.items():
self._compare_snmp_v3_auth_privacy(entry, hattrib.get(key, {}), attrib)
for k, v in entry.items():
if k != attribute_dict[attrib]:
h = {}
if hattrib.get(key):
h = {
"snmp_v3": {
attrib: {
k: hattrib[key].pop(k, ""),
attribute_dict[attrib]: hattrib[key][
attribute_dict[attrib]
],
},
},
}
self.compare(
parsers=parsers,
want={
"snmp_v3": {
attrib: {
k: v,
attribute_dict[attrib]: entry[attribute_dict[attrib]],
},
},
},
have=h,
)
# remove remaining items in have for replaced
for key, entry in hattrib.items():
self._compare_snmp_v3_auth_privacy({}, entry, attrib)
self.compare(parsers=parsers, want={}, have={"snmp_v3": {attrib: entry}})
hdict.pop(attrib, {})
for key, entry in wdict.items():
# self.addcmd(entry, attrib, False)
self.compare(
parsers="snmp_v3.engine_id",
want={"snmp_v3": {key: entry}},
have={"snmp_v3": {key: hdict.pop(key, {})}},
)
# remove remaining items in have for replaced
for key, entry in hdict.items():
self.compare(parsers=parsers, want={}, have={"snmp_v3": {key: entry}})
def _compare_snmp_v3_auth_privacy(self, wattrib, hattrib, attrib):
parsers = [
"snmp_v3.trap_targets.authentication",
"snmp_v3.trap_targets.privacy",
"snmp_v3.users.authentication",
"snmp_v3.users.privacy",
]
if attrib in ["trap_targets", "users"]:
if attrib == "users":
primary_key = "user"
else:
primary_key = "address"
for key, entry in wattrib.items():
if key != primary_key and key in ["authentication", "privacy"]:
self.compare(
parsers=parsers,
want={
"snmp_v3": {
attrib: {
key: entry,
primary_key: wattrib[primary_key],
},
},
},
have={
"snmp_v3": {
attrib: {
key: hattrib.pop(key, {}),
primary_key: wattrib[primary_key],
},
},
},
)
for key, entry in hattrib.items():
if key != primary_key and key in ["authentication", "privacy"]:
self.compare(
parsers=parsers,
want={},
have={
"snmp_v3": {
attrib: {
key: entry,
primary_key: hattrib[primary_key],
},
},
},
)
def _snmp_server_list_to_dict(self, entry):
param_dict = {
"communities": "name",
"listen_addresses": "address",
}
v3_param_dict = {
"groups": "group",
"users": "user",
"views": "view",
"trap_targets": "address",
}
for k, v in param_dict.items():
if k in entry:
a_dict = {}
for el in entry[k]:
a_dict.update({el[v]: el})
entry[k] = a_dict
for k, v in v3_param_dict.items():
if entry.get("snmp_v3") and k in entry.get("snmp_v3"):
a_dict = {}
for el in entry["snmp_v3"][k]:
a_dict.update({el[v]: el})
entry["snmp_v3"][k] = a_dict
return entry
diff --git a/plugins/module_utils/network/vyos/config/static_routes/static_routes.py b/plugins/module_utils/network/vyos/config/static_routes/static_routes.py
index da8f8e3d..2a09c0f0 100644
--- a/plugins/module_utils/network/vyos/config/static_routes/static_routes.py
+++ b/plugins/module_utils/network/vyos/config/static_routes/static_routes.py
@@ -1,604 +1,603 @@
#
# -*- 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 vyos_static_routes 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
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
dict_diff,
remove_empties,
to_list,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
dict_delete,
get_lst_diff_for_dicts,
get_lst_same_for_dicts,
get_route_type,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Static_routes(ConfigBase):
"""
The vyos_static_routes class
"""
gather_subset = ["!all", "!min"]
gather_network_resources = ["static_routes"]
def __init__(self, module):
super(Static_routes, self).__init__(module)
def get_static_routes_facts(self, data=None):
"""Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
)
static_routes_facts = facts["ansible_network_resources"].get("static_routes")
if not static_routes_facts:
return []
return static_routes_facts
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {"changed": False}
warnings = list()
commands = list()
if self.state in self.ACTION_STATES:
existing_static_routes_facts = self.get_static_routes_facts()
else:
existing_static_routes_facts = []
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_static_routes_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_static_routes_facts = self.get_static_routes_facts()
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
result["parsed"] = self.get_static_routes_facts(data=running_config)
else:
changed_static_routes_facts = []
if self.state in self.ACTION_STATES:
result["before"] = existing_static_routes_facts
if result["changed"]:
result["after"] = changed_static_routes_facts
elif self.state == "gathered":
result["gathered"] = changed_static_routes_facts
result["warnings"] = warnings
return result
def set_config(self, existing_static_routes_facts):
"""Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params["config"]
have = existing_static_routes_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
"""Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if self.state in ("merged", "replaced", "overridden", "rendered") and not want:
self._module.fail_json(
msg="value of config parameter must not be empty for state {0}".format(self.state),
)
if self.state == "overridden":
commands.extend(self._state_overridden(want=want, have=have))
elif self.state == "deleted":
commands.extend(self._state_deleted(want=want, have=have))
elif want:
routes = self._get_routes(want)
for r in routes:
h_item = self.search_route_in_have(have, r["dest"])
if self.state in ("merged", "rendered"):
commands.extend(self._state_merged(want=r, have=h_item))
elif self.state == "replaced":
commands.extend(self._state_replaced(want=r, have=h_item))
return commands
def search_route_in_have(self, have, want_dest):
"""
This function returns the route if its found in
have config.
:param have:
:param dest:
:return: the matched route
"""
routes = self._get_routes(have)
for r in routes:
if r["dest"] == want_dest:
return r
return None
def _state_replaced(self, want, have):
"""The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
if have:
for key, value in want.items():
if value:
if key == "next_hops":
commands.extend(self._update_next_hop(want, have))
elif key == "blackhole_config":
commands.extend(self._update_blackhole(key, want, have))
commands.extend(self._state_merged(want, have))
return commands
def _state_overridden(self, want, have):
"""The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
routes = self._get_routes(have)
for r in routes:
route_in_want = self.search_route_in_have(want, r["dest"])
if not route_in_want:
commands.append(self._compute_command(r["dest"], remove=True))
routes = self._get_routes(want)
for r in routes:
route_in_have = self.search_route_in_have(have, r["dest"])
commands.extend(self._state_replaced(r, route_in_have))
return commands
def _state_merged(self, want, have, opr=True):
"""The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
if have:
commands.extend(self._render_updates(want, have))
else:
commands.extend(self._render_set_commands(want))
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
if want:
routes = self._get_routes(want)
if not routes:
for w in want:
af = w["address_families"]
for item in af:
if self.afi_in_have(have, item):
commands.append(self._compute_command(afi=item["afi"], remove=True))
else:
routes = self._get_routes(have)
if self._is_ip_route_exist(routes):
commands.append(self._compute_command(afi="ipv4", remove=True))
if self._is_ip_route_exist(routes, "route6"):
commands.append(self._compute_command(afi="ipv6", remove=True))
return commands
def _render_set_commands(self, want):
"""
This function returns the list of commands to add attributes which are
present in want
:param want:
:return: list of commands.
"""
commands = []
have = {}
for key, value in want.items():
if value:
if key == "dest":
commands.append(self._compute_command(dest=want["dest"]))
elif key == "blackhole_config":
commands.extend(self._add_blackhole(key, want, have))
elif key == "next_hops":
commands.extend(self._add_next_hop(want, have))
return commands
def _add_blackhole(self, key, want, have):
"""
This function gets the diff for blackhole config specific attributes
and form the commands for attributes which are present in want but not in have.
:param key:
:param want:
:param have:
:return: list of commands
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(remove_empties(have))
want_blackhole = want_copy.get(key) or {}
have_blackhole = have_copy.get(key) or {}
updates = dict_delete(want_blackhole, have_blackhole)
if updates:
for attrib, value in updates.items():
if value:
if attrib == "distance":
commands.append(
self._compute_command(
dest=want["dest"],
key="blackhole",
attrib=attrib,
remove=False,
value=str(value),
),
)
elif attrib == "type":
commands.append(self._compute_command(dest=want["dest"], key="blackhole"))
return commands
def _add_next_hop(self, want, have, opr=True):
"""
This function gets the diff for next hop specific attributes
and form the commands to add attributes which are present in want but not in have.
:param want:
:param have:
:return: list of commands.
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(remove_empties(have))
if not opr:
diff_next_hops = get_lst_same_for_dicts(want_copy, have_copy, "next_hops")
else:
diff_next_hops = get_lst_diff_for_dicts(want_copy, have_copy, "next_hops")
if diff_next_hops:
for hop in diff_next_hops:
for element in hop:
if element == "forward_router_address":
commands.append(
self._compute_command(
dest=want["dest"],
key="next-hop",
value=hop[element],
opr=opr,
),
)
elif element == "enabled" and not hop[element]:
commands.append(
self._compute_command(
dest=want["dest"],
key="next-hop",
attrib=hop["forward_router_address"],
value="disable",
opr=opr,
),
)
elif element == "admin_distance":
commands.append(
self._compute_command(
dest=want["dest"],
key="next-hop",
attrib=hop["forward_router_address"] + " " + "distance",
value=str(hop[element]),
opr=opr,
),
)
elif element == "interface" and LooseVersion(
get_os_version(self._module),
) < LooseVersion("1.4"):
commands.append(
self._compute_command(
dest=want["dest"],
key="next-hop-interface",
value=hop[element],
opr=opr,
).replace("route", "interface-route"),
)
elif element == "interface" and LooseVersion(
get_os_version(self._module),
) >= LooseVersion("1.4"):
commands.append(
self._compute_command(
dest=want["dest"],
key="interface",
value=hop[element],
opr=opr,
),
)
return commands
def _update_blackhole(self, key, want, have):
"""
This function gets the difference for blackhole dict and
form the commands to delete the attributes which are present in have but not in want.
:param want:
:param have:
:return: list of commands
:param key:
:param want:
:param have:
:return: list of commands
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(remove_empties(have))
want_blackhole = want_copy.get(key) or {}
have_blackhole = have_copy.get(key) or {}
updates = dict_delete(have_blackhole, want_blackhole)
if updates:
for attrib, value in updates.items():
if value:
if attrib == "distance":
commands.append(
self._compute_command(
dest=want["dest"],
key="blackhole",
attrib=attrib,
remove=True,
value=str(value),
),
)
elif attrib == "type" and "distance" not in want_blackhole.keys():
commands.append(
self._compute_command(dest=want["dest"], key="blackhole", remove=True),
)
return commands
def _update_next_hop(self, want, have, opr=True):
"""
This function gets the difference for next_hops list and
form the commands to delete the attributes which are present in have but not in want.
:param want:
:param have:
:return: list of commands
"""
commands = []
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(remove_empties(have))
diff_next_hops = get_lst_diff_for_dicts(have_copy, want_copy, "next_hops")
if diff_next_hops:
for hop in diff_next_hops:
for element in hop:
if element == "forward_router_address":
commands.append(
self._compute_command(
dest=want["dest"],
key="next-hop",
value=hop[element],
remove=True,
),
)
elif element == "enabled":
commands.append(
self._compute_command(
dest=want["dest"],
key="next-hop",
attrib=hop["forward_router_address"],
value="disable",
remove=True,
),
)
elif element == "admin_distance":
commands.append(
self._compute_command(
dest=want["dest"],
key="next-hop",
attrib=hop["forward_router_address"] + " " + "distance",
value=str(hop[element]),
remove=True,
),
)
elif element == "interface" and LooseVersion(
get_os_version(self._module),
) < LooseVersion("1.4"):
commands.append(
self._compute_command(
dest=want["dest"],
key="next-hop-interface",
value=hop[element],
remove=True,
).replace("route", "interface-route"),
)
elif element == "interface" and LooseVersion(
get_os_version(self._module),
) >= LooseVersion("1.4"):
commands.append(
self._compute_command(
dest=want["dest"],
key="interface",
value=hop[element],
remove=True,
),
)
return commands
def _render_updates(self, want, have, opr=True):
"""
This function takes the diff between want and have and
invokes the appropriate functions to create the commands
to update the attributes.
:param want:
:param have:
:return: list of commands
"""
commands = []
want_nh = want.get("next_hops") or []
# delete static route operation per destination
if not opr and not want_nh:
commands.append(self._compute_command(dest=want["dest"], remove=True))
else:
temp_have_next_hops = have.pop("next_hops", None)
temp_want_next_hops = want.pop("next_hops", None)
updates = dict_diff(have, want)
if temp_have_next_hops:
have["next_hops"] = temp_have_next_hops
if temp_want_next_hops:
want["next_hops"] = temp_want_next_hops
commands.extend(self._add_next_hop(want, have, opr=opr))
if opr and updates:
for key, value in updates.items():
if value:
if key == "blackhole_config":
commands.extend(self._add_blackhole(key, want, have))
return commands
def _compute_command(
self,
dest=None,
key=None,
attrib=None,
value=None,
remove=False,
afi=None,
opr=True,
):
"""
This functions construct the required command based on the passed arguments.
:param dest:
:param key:
:param attrib:
:param value:
:param remove:
:return: constructed command
"""
if remove or not opr:
cmd = "delete protocols static " + self.get_route_type(dest, afi)
else:
cmd = "set protocols static " + self.get_route_type(dest, afi)
if dest:
cmd += " " + dest
if key:
cmd += " " + key
if attrib:
cmd += " " + attrib
if value:
cmd += " '" + value + "'"
return cmd
def afi_in_have(self, have, w_item):
"""
This functions checks for the afi
list in have
:param have:
:param w_item:
:return:
"""
if have:
for h in have:
af = h.get("address_families") or []
for item in af:
if w_item["afi"] == item["afi"]:
return True
return False
def get_route_type(self, dest=None, afi=None):
"""
This function returns the route type based on
destination ip address or afi
:param address:
:return:
"""
if dest:
return get_route_type(dest)
elif afi == "ipv4":
return "route"
elif afi == "ipv6":
return "route6"
def _is_ip_route_exist(self, routes, type="route"):
"""
This functions checks for the type of route.
:param routes:
:param type:
:return: True/False
"""
for r in routes:
if type == self.get_route_type(r["dest"]):
return True
return False
def _get_routes(self, lst):
"""
This function returns the list of routes
:param lst: list of address families
:return: list of routes
"""
r_list = []
for item in lst:
af = item["address_families"]
for element in af:
routes = element.get("routes") or []
for r in routes:
r_list.append(r)
return r_list
diff --git a/plugins/module_utils/network/vyos/config/vrf/vrf.py b/plugins/module_utils/network/vyos/config/vrf/vrf.py
index aa4329d9..a55073b0 100644
--- a/plugins/module_utils/network/vyos/config/vrf/vrf.py
+++ b/plugins/module_utils/network/vyos/config/vrf/vrf.py
@@ -1,308 +1,307 @@
#
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos_vrf config file.
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 its desired end-state is
created.
"""
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_global.bgp_global import (
Bgp_global,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv2.ospfv2 import (
Ospfv2,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv3.ospfv3 import (
Ospfv3,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.static_routes.static_routes import (
Static_routes,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrf import (
VrfTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import combine
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Vrf(ResourceModule):
"""
The vyos_vrf config class
"""
def __init__(self, module):
super(Vrf, self).__init__(
empty_fact_val={},
facts_module=Facts(module),
module=module,
resource="vrf",
tmplt=VrfTemplate(),
)
self.parsers = [
"bind_to_all",
]
def _validate_template(self):
version = get_os_version(self._module)
if LooseVersion(version) >= LooseVersion("1.4"):
self._tmplt = VrfTemplate()
else:
self._module.fail_json(msg="VRF is not supported in this version of VyOS")
def parse(self):
"""override parse to check template"""
self._validate_template()
return super().parse()
def get_parser(self, name):
"""get_parsers"""
self._validate_template()
return super().get_parser(name)
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
return self.result
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
"""
wantd = {}
haved = {}
wantd = deepcopy(self.want)
haved = deepcopy(self.have)
# if state is merged, merge want onto have and then compare
if self.state in ["merged", "replaced"]:
wantd = combine(haved, wantd, recursive=True)
# if state is deleted, delete and empty out wantd
if self.state == "deleted":
w = deepcopy(wantd)
if w == {} and haved != {}:
self.commands = ["delete vrf"]
return
for k, want in w.items():
if not (k in haved and haved[k]):
del wantd[k]
else:
if isinstance(want, list):
for entry in want:
wname = entry.get("name")
haved["instances"] = [
i for i in haved.get("instances", []) if i.get("name") != wname
]
self.commands.append("delete vrf name {}".format(wname))
else:
self.commands.append("delete vrf {}".format(k.replace("_", "-")))
del wantd[k]
if self.state == "overridden":
w = deepcopy(wantd)
h = deepcopy(haved)
for k, want in w.items():
if k in haved and haved[k] != want:
if isinstance(want, list):
for entry in want:
wname = entry.get("name")
hdict = next(
(inst for inst in haved["instances"] if inst["name"] == wname),
None,
)
wantc = self._canonicalise(entry)
havec = self._canonicalise(hdict or {})
if wantc != havec:
haved["instances"] = [
i for i in haved.get("instances", []) if i.get("name") != wname
]
self.commands.append("delete vrf name {}".format(wname))
self.commands.append("commit")
for k, want in wantd.items():
if isinstance(want, list):
self._compare_instances(want=want, have=haved.pop(k, {}))
self.compare(
parsers=self.parsers,
want={k: want},
have={k: haved.pop(k, {})},
)
def _compare_instances(self, want, have):
"""Compare the instances of the VRF"""
parsers = [
"table_id",
"vni",
"description",
"disable_vrf",
]
for entry in want:
h = {}
wname = entry.get("name")
h = {
k: v
for vrf in have
if vrf.get("name") == wname
for k, v in vrf.items()
if k != "address_family"
}
self.compare(parsers=parsers, want=entry, have=h)
if "address_family" in entry:
wafi = {"name": wname, "address_family": entry.get("address_family", [])}
hdict = next((d for d in have if d.get("name") == wname), None)
hafi = {
"name": (hdict or {"name": wname})["name"],
"address_family": hdict.get("address_family", []) if hdict else [],
}
self._compare_addr_family(wafi, hafi)
if "protocols" in entry:
for protocol_name in entry["protocols"]:
w_p_dict = entry["protocols"][protocol_name]
h_p_dict = next(
(
v.get("protocols", {}).get(protocol_name, {})
for v in have
if v.get("name") == wname
),
{},
)
if protocol_name == "bgp":
bgp_module = Bgp_global(self._module)
bgp_module._validate_template()
bgp_module.want = w_p_dict
bgp_module.have = h_p_dict
bgp_module.generate_commands()
protocol_commands = bgp_module.commands
elif protocol_name == "ospf":
ospfv2_module = Ospfv2(self._module)
ospfv2_module._module.params["config"] = w_p_dict
ospfv2_module.state = self.state
protocol_commands = ospfv2_module.set_config(h_p_dict)
elif protocol_name == "ospfv3":
ospfv3_module = Ospfv3(self._module)
ospfv3_module._module.params["config"] = w_p_dict
ospfv3_module.state = self.state
protocol_commands = ospfv3_module.set_config(h_p_dict)
elif protocol_name == "static":
static_routes_module = Static_routes(self._module)
static_routes_module._module.params["config"] = w_p_dict
static_routes_module.state = self.state
protocol_commands = static_routes_module.set_config(h_p_dict)
else:
self._module.fail_json(
msg="The protocol {} is not supported".format(protocol_name),
)
self.commands.extend(
[
cmd.replace("protocols", "vrf name " + wname + " protocols", 1)
for cmd in protocol_commands
],
)
def _compare_addr_family(self, want, have):
"""Compare the address families of the VRF"""
afi_parsers = [
"disable_forwarding",
"disable_nht",
]
wafi = self.afi_to_list(want)
hafi = self.afi_to_list(have)
lookup = {(d["name"], d["afi"]): d for d in hafi}
pairs = [(d1, lookup.get((d1["name"], d1["afi"]), {})) for d1 in wafi]
for wafd, hafd in pairs:
if "route_maps" in wafd:
self._compare_route_maps(wafd, hafd)
self.compare(parsers=afi_parsers, want=wafd, have=hafd)
def afi_to_list(self, data):
"""Convert address family dict to list"""
return [
{"name": data["name"], **{**af, "afi": "ip" if af["afi"] == "ipv4" else af["afi"]}}
for af in data["address_family"]
]
def _compare_route_maps(self, wafd, hafd):
want_rms = wafd.get("route_maps", [])
have_rms = hafd.get("route_maps", [])
for want in want_rms:
match = next(
(
h
for h in have_rms
if h["rm_name"] == want["rm_name"] and h["protocol"] == want["protocol"]
),
{},
)
base = {"name": wafd["name"], "afi": wafd["afi"]}
self.compare(
parsers="route_maps",
want={**base, "route_maps": want},
have={**base, "route_maps": match},
)
def _canonicalise(self, obj):
if isinstance(obj, dict):
return {k: self._canonicalise(v) for k, v in obj.items()}
if isinstance(obj, list):
if not obj:
return obj
if not isinstance(obj[0], dict):
return sorted(obj)
canon = [self._canonicalise(i) for i in obj]
def sort_key(d):
for k in ("name", "address", "afi", "area_id", "neighbor_id", "dest"):
if k in d:
return d[k]
return tuple(sorted(d.items()))
return sorted(canon, key=sort_key)
return obj
diff --git a/plugins/module_utils/network/vyos/facts/bgp_address_family/bgp_address_family.py b/plugins/module_utils/network/vyos/facts/bgp_address_family/bgp_address_family.py
index 3386bd66..65495070 100644
--- a/plugins/module_utils/network/vyos/facts/bgp_address_family/bgp_address_family.py
+++ b/plugins/module_utils/network/vyos/facts/bgp_address_family/bgp_address_family.py
@@ -1,99 +1,100 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos bgp_address_family 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.
"""
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.bgp_address_family.bgp_address_family import (
Bgp_address_familyArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_address_family import (
Bgp_address_familyTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_address_family_14 import (
Bgp_address_familyTemplate14,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
class Bgp_address_familyFacts(object):
"""The vyos bgp_address_family facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Bgp_address_familyArgs.argument_spec
def get_device_data(self, connection):
return connection.get('show configuration commands | match "set protocols bgp"')
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Bgp_address_family network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = []
config_lines = []
if not data:
data = self.get_device_data(connection)
for resource in data.splitlines():
if "address-family" in resource or "system-as" in resource:
config_lines.append(re.sub("'", "", resource))
# parse native config using the Bgp_address_family template based on version
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
bgp_address_family_parser = Bgp_address_familyTemplate14(lines=config_lines)
else:
bgp_address_family_parser = Bgp_address_familyTemplate(lines=config_lines)
objs = bgp_address_family_parser.parse()
if objs:
if "address_family" in objs:
objs["address_family"] = list(objs["address_family"].values())
for af in objs["address_family"]:
if "networks" in af:
af["networks"] = sorted(af["networks"], key=lambda k: k["prefix"])
if "aggregate_address" in af:
af["aggregate_address"] = sorted(
af["aggregate_address"],
key=lambda k: k["prefix"],
)
if "neighbors" in objs:
objs["neighbors"] = list(objs["neighbors"].values())
objs["neighbors"] = sorted(objs["neighbors"], key=lambda k: k["neighbor_address"])
for neigh in objs["neighbors"]:
if "address_family" in neigh:
neigh["address_family"] = list(neigh["address_family"].values())
ansible_facts["ansible_network_resources"].pop("bgp_address_family", None)
params = utils.remove_empties(utils.validate_config(self.argument_spec, {"config": objs}))
facts["bgp_address_family"] = params.get("config", [])
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/bgp_global/bgp_global.py b/plugins/module_utils/network/vyos/facts/bgp_global/bgp_global.py
index dd793681..d04791ee 100644
--- a/plugins/module_utils/network/vyos/facts/bgp_global/bgp_global.py
+++ b/plugins/module_utils/network/vyos/facts/bgp_global/bgp_global.py
@@ -1,93 +1,94 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos bgp_global 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.
"""
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.bgp_global.bgp_global import (
Bgp_globalArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_global import (
Bgp_globalTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_global_14 import (
Bgp_globalTemplate14,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
class Bgp_globalFacts(object):
"""The vyos bgp_global facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Bgp_globalArgs.argument_spec
def get_device_data(self, connection):
return connection.get('show configuration commands | match "set protocols bgp"')
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Bgp_global network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = {}
config_lines = []
if not data:
data = self.get_device_data(connection)
for resource in data.splitlines():
if "address-family" not in resource:
config_lines.append(re.sub("'", "", resource))
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
bgp_global_parser = Bgp_globalTemplate14(lines=config_lines, module=self._module)
else:
bgp_global_parser = Bgp_globalTemplate(lines=config_lines, module=self._module)
objs = bgp_global_parser.parse()
if "neighbor" in objs:
objs["neighbor"] = list(objs["neighbor"].values())
objs["neighbor"] = sorted(objs["neighbor"], key=lambda k: k["address"])
if "network" in objs:
objs["network"] = sorted(objs["network"], key=lambda k: k["address"])
if "aggregate_address" in objs:
objs["aggregate_address"] = sorted(objs["aggregate_address"], key=lambda k: k["prefix"])
ansible_facts["ansible_network_resources"].pop("bgp_global", None)
params = utils.remove_empties(
bgp_global_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
)
facts["bgp_global"] = params.get("config", [])
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py
index 24c444dd..9bc39935 100644
--- a/plugins/module_utils/network/vyos/facts/facts.py
+++ b/plugins/module_utils/network/vyos/facts/facts.py
@@ -1,133 +1,132 @@
# 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 vyos
this file validates each subset of facts and selectively
calls the appropriate facts gathering function
"""
-from __future__ import absolute_import, division, print_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.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_address_family.bgp_address_family import (
Bgp_address_familyFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_global.bgp_global import (
Bgp_globalFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_global.firewall_global import (
Firewall_globalFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_interfaces.firewall_interfaces import (
Firewall_interfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules import (
Firewall_rulesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.hostname.hostname import (
HostnameFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.interfaces.interfaces import (
InterfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.l3_interfaces.l3_interfaces import (
L3_interfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lag_interfaces.lag_interfaces import (
Lag_interfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import (
Config,
Default,
Neighbors,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_global.lldp_global import (
Lldp_globalFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_interfaces.lldp_interfaces import (
Lldp_interfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.logging_global.logging_global import (
Logging_globalFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ntp_global.ntp_global import (
Ntp_globalFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces import (
Ospf_interfacesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2 import (
Ospfv2Facts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3 import (
Ospfv3Facts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.prefix_lists.prefix_lists import (
Prefix_listsFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.route_maps.route_maps import (
Route_mapsFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.snmp_server.snmp_server import (
Snmp_serverFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import (
Static_routesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vrf.vrf import VrfFacts
-
FACT_LEGACY_SUBSETS = dict(default=Default, neighbors=Neighbors, config=Config)
FACT_RESOURCE_SUBSETS = dict(
interfaces=InterfacesFacts,
l3_interfaces=L3_interfacesFacts,
lag_interfaces=Lag_interfacesFacts,
lldp_global=Lldp_globalFacts,
lldp_interfaces=Lldp_interfacesFacts,
static_routes=Static_routesFacts,
firewall_rules=Firewall_rulesFacts,
firewall_global=Firewall_globalFacts,
firewall_interfaces=Firewall_interfacesFacts,
ospfv3=Ospfv3Facts,
ospfv2=Ospfv2Facts,
ospf_interfaces=Ospf_interfacesFacts,
bgp_global=Bgp_globalFacts,
bgp_address_family=Bgp_address_familyFacts,
route_maps=Route_mapsFacts,
prefix_lists=Prefix_listsFacts,
logging_global=Logging_globalFacts,
ntp_global=Ntp_globalFacts,
snmp_server=Snmp_serverFacts,
hostname=HostnameFacts,
vrf=VrfFacts,
)
class Facts(FactsBase):
"""The fact class for vyos"""
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 vyos
:param legacy_facts_type: List of legacy facts types
:param resource_facts_type: List of resource fact types
:param data: previously collected conf
:rtype: dict
:return: the facts gathered
"""
if self.VALID_RESOURCE_SUBSETS:
self.get_network_resources_facts(FACT_RESOURCE_SUBSETS, resource_facts_type, data)
if self.VALID_LEGACY_GATHER_SUBSETS:
self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type)
return self.ansible_facts, self._warnings
diff --git a/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
index 3a48889e..c74e63d3 100644
--- a/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
@@ -1,402 +1,402 @@
#
# -*- 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 vyos firewall_global 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from re import M, findall, search
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_global.firewall_global import (
Firewall_globalArgs,
)
class Firewall_globalFacts(object):
"""The vyos firewall_global fact class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Firewall_globalArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def get_device_data(self, connection):
return connection.get_config()
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for firewall_global
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
# typically data is populated from the current device configuration
# data = connection.get('show running-config | section ^interface')
# using mock data instead
data = self.get_device_data(connection)
objs = {}
firewalls = findall(r"^set firewall .*$", data, M)
if firewalls:
objs = self.render_config(firewalls)
facts = {}
params = utils.validate_config(self.argument_spec, {"config": objs})
facts["firewall_global"] = utils.remove_empties(params["config"])
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def render_config(self, conf):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
conf = "\n".join(
filter(
lambda x: ("firewall ipv6-name" and "firewall name" not in x),
conf,
),
)
a_lst = [
"config_trap",
"validation",
"log_martians",
"syn_cookies",
"twa_hazards_protection",
]
firewall = self.parse_attr(conf, a_lst)
f_sub = {
"ping": self.parse_ping(conf),
"group": self.parse_group(conf),
"route_redirects": self.route_redirects(conf),
"state_policy": self.parse_state_policy(conf),
}
firewall.update(f_sub)
return firewall
def route_redirects(self, conf):
"""
This function forms the regex to fetch the afi and invoke
functions to fetch route redirects and source routes
:param conf: configuration data.
:return: generated rule list configuration.
"""
rr_lst = []
v6_attr = findall(
r"^set firewall (?:global-options )?(?:ipv6-src-route|ipv6-receive-redirects) (\S+)",
conf,
M,
)
if v6_attr:
obj = self.parse_rr_attrib(conf, "ipv6")
if obj:
rr_lst.append(obj)
v4_attr = findall(
r"^set firewall (?:global-options )?(?:ip-src-route|receive-redirects|send-redirects) (\S+)",
conf,
M,
)
if v4_attr:
obj = self.parse_rr_attrib(conf, "ipv4")
if obj:
rr_lst.append(obj)
return rr_lst
def parse_rr_attrib(self, conf, attrib=None):
"""
This function fetches the 'ip_src_route'
invoke function to parse icmp redirects.
:param conf: configuration to be parsed.
:param attrib: 'ipv4/ipv6'.
:return: generated config dictionary.
"""
cfg_dict = self.parse_attr(conf, ["ip_src_route"], type=attrib)
cfg_dict["icmp_redirects"] = self.parse_icmp_redirects(conf, attrib)
cfg_dict["afi"] = attrib
return cfg_dict
def parse_icmp_redirects(self, conf, attrib=None):
"""
This function triggers the parsing of 'icmp_redirects' attributes.
:param conf: configuration to be parsed.
:param attrib: 'ipv4/ipv6'.
:return: generated config dictionary.
"""
a_lst = ["send", "receive"]
cfg_dict = self.parse_attr(conf, a_lst, type=attrib)
return cfg_dict
def parse_ping(self, conf):
"""
This function triggers the parsing of 'ping' attributes.
:param conf: configuration to be parsed.
:return: generated config dictionary.
"""
a_lst = ["all", "broadcast"]
cfg_dict = self.parse_attr(conf, a_lst)
return cfg_dict
def parse_state_policy(self, conf):
"""
This function fetched the connecton type and invoke
function to parse other state-policy attributes.
:param conf: configuration data.
:return: generated rule list configuration.
"""
sp_lst = []
policies = findall(r"^set firewall (?:global-options )?state-policy (\S+)", conf, M)
policies = list(set(policies)) # remove redundancies
if policies:
rules_lst = []
for sp in set(policies):
sp_regex = r"^set firewall (?:global-options )?state-policy %s .+$" % sp
cfg = "\n".join(findall(sp_regex, conf, M))
obj = self.parse_policies(cfg, sp)
obj["connection_type"] = sp
if obj:
rules_lst.append(obj)
sp_lst = sorted(rules_lst, key=lambda i: i["connection_type"])
return sp_lst
def parse_policies(self, conf, attrib=None):
"""
This function triggers the parsing of policy attributes
action and log.
:param conf: configuration
:param attrib: connection type.
:return: generated rule configuration dictionary.
"""
a_lst = ["action", "log", "log_level"]
cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
return cfg_dict
def parse_group(self, conf):
"""
This function triggers the parsing of 'group' attributes.
:param conf: configuration.
:return: generated config dictionary.
"""
cfg_dict = {}
cfg_dict["port_group"] = self.parse_group_lst(conf, "port-group", False)
cfg_dict["address_group"] = self.parse_group_lst(
conf,
"address-group",
) + self.parse_group_lst(conf, "ipv6-address-group")
cfg_dict["network_group"] = self.parse_group_lst(
conf,
"network-group",
) + self.parse_group_lst(conf, "ipv6-network-group")
return cfg_dict
def parse_group_lst(self, conf, type, include_afi=True):
"""
This function fetches the name of group and invoke function to
parse group attributes'.
:param conf: configuration data.
:param type: type of group.
:param include_afi: if the afi should be included in the parsed object
:return: generated group list configuration.
"""
g_lst = []
groups = findall(r"^set firewall group " + type + " (\\S+)", conf, M)
if groups:
rules_lst = []
for gr in set(groups):
gr_regex = r"^set firewall group " + type + " %s .+$" % gr
cfg = "\n".join(findall(gr_regex, conf, M))
if "ipv6" in type:
# fmt: off
obj = self.parse_groups(cfg, type[len("ipv6-"):], gr)
# fmt: on
if include_afi:
obj["afi"] = "ipv6"
else:
obj = self.parse_groups(cfg, type, gr)
if include_afi:
obj["afi"] = "ipv4"
obj["name"] = gr.strip("'")
if obj:
rules_lst.append(obj)
g_lst = sorted(rules_lst, key=lambda i: i["name"])
return g_lst
def parse_groups(self, conf, type, name):
"""
This function fetches the description and invoke
the parsing of group members.
:param conf: configuration.
:param type: type of group.
:param name: name of group.
:return: generated configuration dictionary.
"""
a_lst = ["name", "description"]
group = self.parse_attr(conf, a_lst)
key = self.get_key(type)
r_sub = {key[0]: self.parse_address_port_lst(conf, name, key[1])}
group.update(r_sub)
return group
def parse_address_port_lst(self, conf, name, key):
"""
This function forms the regex to fetch the
group members attributes.
:param conf: configuration data.
:param name: name of group.
:param key: key value.
:return: generated member list configuration.
"""
l_lst = []
attribs = findall(r"^.*" + name + " " + key + " (\\S+)", conf, M)
if attribs:
for attr in attribs:
if key == "port":
l_lst.append({"port": attr.strip("'")})
else:
l_lst.append({"address": attr.strip("'")})
return l_lst
def parse_attr(self, conf, attr_list, match=None, type=None):
"""
This function peforms the following:
- Form the regex to fetch the required attribute config.
- Type cast the output in desired format.
:param conf: configuration.
:param attr_list: list of attributes.
:param match: parent node/attribute name.
:return: generated config dictionary.
"""
config = {}
for attrib in attr_list:
regex = self.map_regex(attrib, type)
if match:
regex = match + " " + regex
if conf:
if self.is_bool(attrib):
# fancy regex to make sure we don't get a substring
out = search(r"^.*" + regex + r"( 'disable')?(?=\s|$)", conf, M)
if out:
if out.group(1):
config[attrib] = False
else:
config[attrib] = True
else:
out = search(r"^.*" + regex + r" (.+)", conf, M)
if out:
val = out.group(1).strip("'")
if self.is_num(attrib):
val = int(val)
config[attrib] = val
return config
def get_key(self, type):
"""
This function map the group type to
member type
:param type:
:return:
"""
key = ()
if type == "port-group":
key = ("members", "port")
elif type == "address-group":
key = ("members", "address")
elif type == "network-group":
key = ("members", "network")
return key
def map_regex(self, attrib, type=None):
"""
- This function construct the regex string.
- replace the underscore with hyphen.
:param attrib: attribute
:return: regex string
"""
regex = attrib.replace("_", "-")
if attrib == "all":
regex = "all-ping"
elif attrib == "disabled":
regex = "disable"
elif attrib == "broadcast":
regex = "broadcast-ping"
elif attrib == "send":
if type == "ipv6":
regex = "ipv6-send-redirects"
else:
regex = "send-redirects"
elif attrib == "ip_src_route":
if type == "ipv6":
regex = "ipv6-src-route"
elif attrib == "receive":
if type == "ipv6":
regex = "ipv6-receive-redirects"
else:
regex = "receive-redirects"
return regex
def is_num(self, attrib):
"""
This function looks for the attribute in predefined integer type set.
:param attrib: attribute.
:return: True/false.
"""
num_set = ("time", "code", "type", "count", "burst", "number")
return True if attrib in num_set else False
def get_src_route(self, attrib):
"""
This function looks for the attribute in predefined integer type set.
:param attrib: attribute.
:return: True/false.
"""
return "ipv6_src_route" if attrib == "ipv6" else "ip_src_route"
def is_bool(self, attrib):
"""
This function looks for the attribute in predefined bool type set.
:param attrib: attribute.
:return: True/False
"""
bool_set = (
"all",
"log",
"send",
"receive",
"broadcast",
"config_trap",
"log_martians",
"syn_cookies",
"ip_src_route",
"twa_hazards_protection",
)
return True if attrib in bool_set else False
diff --git a/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py b/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py
index bac31920..34235af7 100644
--- a/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py
@@ -1,203 +1,203 @@
#
# -*- 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 vyos firewall_interfaces 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from re import M, findall, search
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_interfaces.firewall_interfaces import (
Firewall_interfacesArgs,
)
class Firewall_interfacesFacts(object):
"""The vyos firewall_interfaces fact class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Firewall_interfacesArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def get_device_data(self, connection):
return connection.get_config()
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for firewall_interfaces
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
# typically data is populated from the current device configuration
# data = connection.get('show running-config | section ^interface')
# using mock data instead
data = self.get_device_data(connection)
objs = []
# Search all set from configuration with set interface, including ethernet and bonding
interfaces_raw = findall(r"^set interfaces \S+ (\S+) firewall (?:\'*)", data, M)
interfaces_vif = findall(r"^set interfaces \S+ (\S+) vif (\d+)* firewall (?:\'*)", data, M)
interfaces = interfaces_raw + interfaces_vif
if interfaces:
objs = self.get_names(data, interfaces)
ansible_facts["ansible_network_resources"].pop("firewall_interfaces", None)
facts = {}
if objs:
facts["firewall_interfaces"] = []
params = utils.validate_config(self.argument_spec, {"config": objs})
for cfg in params["config"]:
facts["firewall_interfaces"].append(utils.remove_empties(cfg))
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def get_names(self, data, interfaces):
"""
This function performs following:
- Form regex to fetch 'interface name' from interfaces firewall data.
- Form the name list.
:param data: configuration.
:param rules: list of interfaces.
:return: generated firewall interfaces configuration.
"""
names = []
for r in set(interfaces):
myvif = None
if isinstance(r, tuple):
myinterface, myvif = r
else:
myinterface = r
# Parse interfaces that contains string or tuple when the interface is in a vlan
if myvif is not None:
int_regex = r" %s vif \d+ firewall .+$" % myinterface
cfg = findall(int_regex, data, M)
fi = self.render_config(cfg)
fi["name"] = myinterface + "." + myvif
else:
int_regex = r" %s firewall .+$" % myinterface
cfg = findall(int_regex, data, M)
fi = self.render_config(cfg)
fi["name"] = myinterface
names.append(fi)
if names:
names = sorted(names, key=lambda i: i["name"])
return names
def render_config(self, conf):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
conf = "\n".join(filter(lambda x: "firewall" in x, conf))
config = {"access_rules": self.parse_access_rules(conf)}
return config
def parse_access_rules(self, conf):
"""
This function forms the regex to fetch the 'access-rules'
for specific interface.
:param conf: configuration data.
:return: generated access-rules list configuration.
"""
ar_lst = []
v4_ar = findall(r"^.*(in|out|local) name .*$", conf, M)
v6_ar = findall(r"^.*(in|out|local) ipv6-name .*$", conf, M)
if v4_ar:
v4_conf = "\n".join(findall(r"(^.*?%s.*?$)" % " name", conf, M))
config = self.parse_int_rules(v4_conf, "ipv4")
if config:
ar_lst.append(config)
if v6_ar:
v6_conf = "\n".join(findall(r"(^.*?%s.*?$)" % " ipv6-name", conf, M))
config = self.parse_int_rules(v6_conf, "ipv6")
if config:
ar_lst.append(config)
if ar_lst:
ar_lst = sorted(ar_lst, key=lambda i: i["afi"])
else:
empty_rules = findall(r"^.*(in|out|local).*", conf, M)
if empty_rules:
ar_lst.append({"afi": "ipv4", "rules": []})
ar_lst.append({"afi": "ipv6", "rules": []})
return ar_lst
def parse_int_rules(self, conf, afi):
"""
This function forms the regex to fetch the 'access-rules'
for specific interface based on ip-type.
:param conf: configuration data.
:param rules: rules configured per interface.
:param afi: ip address type.
:return: generated rule configuration dictionary.
"""
r_lst = []
config = {}
rules = ["in", "out", "local"]
for r in set(rules):
fr = {}
r_regex = r" %s .+$" % r
cfg = "\n".join(findall(r_regex, conf, M))
if cfg:
fr = self.parse_rules(cfg, afi)
else:
out = search(r"^.*firewall " + "'" + r + "'" + "(.*)", conf, M)
if out:
fr = {"direction": r}
if fr:
r_lst.append(fr)
if r_lst:
r_lst = sorted(r_lst, key=lambda i: i["direction"])
config = {"afi": afi, "rules": r_lst}
return config
def parse_rules(self, conf, afi):
"""
This function triggers the parsing of 'rule' attributes.
a_lst is a list having rule attributes which doesn't
have further sub attributes.
:param conf: configuration.
:param afi: ip address type.
:return: generated rule configuration dictionary.
"""
cfg = {}
out = findall(r"[^\s]+", conf, M)
if out:
cfg["direction"] = out[0].strip("'")
if afi == "ipv6":
out = findall(r"[^\s]+ ipv6-name (?:\'*)(\S+)(?:\'*)", conf, M)
if out:
cfg["name"] = str(out[0]).strip("'")
else:
out = findall(r"[^\s]+ name (?:\'*)(\S+)(?:\'*)", conf, M)
if out:
cfg["name"] = out[-1].strip("'")
return cfg
diff --git a/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py
index 270b4a62..84f423cf 100644
--- a/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py
+++ b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py
@@ -1,544 +1,544 @@
#
# -*- 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 vyos firewall_rules 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from re import M, findall, search
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import (
Firewall_rulesArgs,
)
class Firewall_rulesFacts(object):
"""The vyos firewall_rules fact class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Firewall_rulesArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def get_device_data(self, connection):
return connection.get_config()
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for firewall_rules
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
# typically data is populated from the current device configuration
# data = connection.get('show running-config | section ^interface')
# using mock data instead
data = self.get_device_data(connection)
# split the config into instances of the resource
objs = []
# check 1.4+ first
new_rules = True
v6_rules = findall(
r"^set firewall ipv6 (name|forward|input|output) (?:\'*)(\S+)(?:\'*)",
data,
M,
)
if not v6_rules:
v6_rules = findall(r"^set firewall ipv6-name (?:\'*)(\S+)(?:\'*)", data, M)
if v6_rules:
new_rules = False
v4_rules = findall(
r"^set firewall ipv4 (name|forward|input|output) (?:\'*)(\S+)(?:\'*)",
data,
M,
)
if not v4_rules:
v4_rules = findall(r"^set firewall name (?:\'*)(\S+)(?:\'*)", data, M)
if v4_rules:
new_rules = False
if v6_rules:
if new_rules:
config = self.get_rules_post_1_4(data, v6_rules, type="ipv6")
else:
config = self.get_rules(data, v6_rules, type="ipv6")
if config:
config = utils.remove_empties(config)
objs.append(config)
if v4_rules:
if new_rules:
config = self.get_rules_post_1_4(data, v4_rules, type="ipv4")
else:
config = self.get_rules(data, v4_rules, type="ipv4")
if config:
config = utils.remove_empties(config)
objs.append(config)
ansible_facts["ansible_network_resources"].pop("firewall_rules", None)
facts = {}
if objs:
facts["firewall_rules"] = []
params = utils.validate_config(self.argument_spec, {"config": objs})
for cfg in params["config"]:
facts["firewall_rules"].append(utils.remove_empties(cfg))
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def get_rules(self, data, rules, type):
"""
This function performs following:
- Form regex to fetch 'rule-sets' specific config from data.
- Form the rule-set list based on ip address.
:param data: configuration.
:param rules: list of rule-sets.
:param type: ip address type.
:return: generated rule-sets configuration.
"""
r_v4 = []
r_v6 = []
for r in set(rules):
name_key = "ipv6-name" if type == "ipv6" else "name"
rule_regex = r" %s %s .+$" % (name_key, r.strip("'"))
cfg = findall(rule_regex, data, M)
fr = self.render_config(cfg, r.strip("'"))
fr["name"] = r.strip("'")
if type == "ipv6":
r_v6.append(fr)
else:
r_v4.append(fr)
if r_v4:
config = {"afi": "ipv4", "rule_sets": r_v4}
if r_v6:
config = {"afi": "ipv6", "rule_sets": r_v6}
return config
def get_rules_post_1_4(self, data, rules, type):
"""
This function performs following:
- Form regex to fetch 'rule-sets' specific config from data.
- Form the rule-set list based on ip address.
Specifically for v1.4+ version.
:param data: configuration.
:param rules: list of rule-sets.
:param type: ip address type.
:return: generated rule-sets configuration.
"""
r_v4 = []
r_v6 = []
for kind, name in set(rules):
rule_regex = r" %s %s %s .+$" % (type, kind, name.strip("'"))
cfg = findall(rule_regex, data, M)
fr = self.render_config(cfg, name.strip("'"))
if kind == "name":
fr["name"] = name.strip("'")
elif kind in ("forward", "input", "output"):
fr["filter"] = kind
else:
raise ValueError("Unknown rule kind: %s %s" % kind, name)
if type == "ipv6":
r_v6.append(fr)
else:
r_v4.append(fr)
if r_v4:
config = {"afi": "ipv4", "rule_sets": r_v4}
if r_v6:
config = {"afi": "ipv6", "rule_sets": r_v6}
return config
def render_config(self, conf, match):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
conf = "\n".join(filter(lambda x: x, conf))
a_lst = [
"description",
"default_action",
"default_jump_target",
"enable_default_log",
"default_log",
]
config = self.parse_attr(conf, a_lst, match)
if not config:
config = {}
if "default_log" in config:
config["enable_default_log"] = config.pop("default_log")
config["rules"] = self.parse_rules_lst(conf)
return config
def parse_rules_lst(self, conf):
"""
This function forms the regex to fetch the 'rules' with in
'rule-sets'
:param conf: configuration data.
:return: generated rule list configuration.
"""
r_lst = []
rules = findall(r"rule (?:\'*)(\d+)(?:\'*)", conf, M)
if rules:
rules_lst = []
for r in set(rules):
r_regex = r" %s .+$" % r
cfg = "\n".join(findall(r_regex, conf, M))
obj = self.parse_rules(cfg)
obj["number"] = int(r)
if obj:
rules_lst.append(obj)
r_lst = sorted(rules_lst, key=lambda i: i["number"])
return r_lst
def parse_rules(self, conf):
"""
This function triggers the parsing of 'rule' attributes.
a_lst is a list having rule attributes which doesn't
have further sub attributes.
:param conf: configuration
:return: generated rule configuration dictionary.
"""
a_lst = [
"ipsec",
"log",
"action",
"protocol",
"fragment",
"disable",
"description",
"icmp",
"jump_target",
"offload_target",
"queue",
"queue_options",
]
rule = self.parse_attr(conf, a_lst)
r_sub = {
"tcp": self.parse_tcp(conf),
"icmp": self.parse_icmp(conf, "icmp"),
"time": self.parse_time(conf, "time"),
"limit": self.parse_limit(conf, "limit"),
"state": self.parse_state(conf, "state"),
"recent": self.parse_recent(conf, "recent"),
"source": self.parse_src_or_dest(conf, "source"),
"destination": self.parse_src_or_dest(conf, "destination"),
"inbound_interface": self.parse_interface(conf, "inbound-interface"),
"outbound_interface": self.parse_interface(conf, "outbound-interface"),
"packet_length": self.parse_packet_length(conf, "packet-length"),
"packet_length_exclude": self.parse_packet_length(conf, "packet-length-exclude"),
}
rule.update(r_sub)
return rule
def parse_interface(self, conf, attrib):
"""
This function triggers the parsing of 'interface' attributes.
:param conf: configuration.
:param attrib: 'interface'.
:return: generated config dictionary.
"""
a_lst = ["name", "group"]
cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
return cfg_dict
def parse_packet_length(self, conf, attrib=None):
"""
This function triggers the parsing of 'packet-length' attributes.
:param conf: configuration.
:param attrib: 'packet-length'.
:return: generated config dictionary.
"""
lengths = []
rule_regex = r"%s (.+)$" % attrib
found_lengths = findall(rule_regex, conf, M)
if found_lengths:
lengths = []
for pplen in set(found_lengths):
obj = {"length": pplen.strip("'")}
lengths.append(obj)
return lengths
def parse_src_or_dest(self, conf, attrib=None):
"""
This function triggers the parsing of 'source or
destination' attributes.
:param conf: configuration.
:param attrib:'source/destination'.
:return:generated source/destination configuration dictionary.
"""
a_lst = ["port", "address", "mac_address"]
cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
cfg_dict["group"] = self.parse_group(conf, attrib + " group")
return cfg_dict
def parse_recent(self, conf, attrib=None):
"""
This function triggers the parsing of 'recent' attributes
:param conf: configuration.
:param attrib: 'recent'.
:return: generated config dictionary.
"""
a_lst = ["time", "count"]
cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
return cfg_dict
def parse_tcp(self, conf):
"""
This function triggers the parsing of 'tcp' attributes.
:param conf: configuration.
:param attrib: 'tcp'.
:return: generated config dictionary.
"""
f_lst = []
flags = findall(r"tcp flags (not )?(?:\'*)([\w!,]+)(?:\'*)", conf, M)
# for pre 1.4, this is a string including possible commas
# and ! as an inverter. For 1.4+ this is a single flag per
# command and 'not' as the inverter
if flags:
flag_lst = []
for n, f in set(flags):
f = f.strip("'").lower()
if "," in f:
# pre 1.4 version with multiple flags
fs = f.split(",")
for f in fs:
if "!" in f:
obj = {"flag": f.strip("'!"), "invert": True}
else:
obj = {"flag": f.strip("'")}
flag_lst.append(obj)
elif "!" in f:
obj = {"flag": f.strip("'!"), "invert": True}
flag_lst.append(obj)
else:
obj = {"flag": f.strip("'")}
if n:
obj["invert"] = True
flag_lst.append(obj)
f_lst = sorted(flag_lst, key=lambda i: i["flag"])
return {"flags": f_lst}
def parse_time(self, conf, attrib=None):
"""
This function triggers the parsing of 'time' attributes.
:param conf: configuration.
:param attrib: 'time'.
:return: generated config dictionary.
"""
a_lst = [
"stopdate",
"stoptime",
"weekdays",
"monthdays",
"startdate",
"starttime",
]
cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
return cfg_dict
def parse_state(self, conf, attrib=None):
"""
This function triggers the parsing of 'state' attributes.
:param conf: configuration
:param attrib: 'state'.
:return: generated config dictionary.
"""
a_lst = ["new", "invalid", "related", "established"]
cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
return cfg_dict
def parse_group(self, conf, attrib=None):
"""
This function triggers the parsing of 'group' attributes.
:param conf: configuration.
:param attrib: 'group'.
:return: generated config dictionary.
"""
a_lst = ["port_group", "address_group", "network_group"]
cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
return cfg_dict
def parse_icmp_attr(self, conf, match):
"""
This function peforms the following:
- parse ICMP arguemnts for firewall rules
- consider that older versions may need numbers or letters
in type, newer ones are more specific
:param conf: configuration.
:param match: parent node/attribute name.
:return: generated config dictionary.
"""
config = {}
if not conf:
return config
for attrib in ("code", "type", "type-name"):
regex = self.map_regex(attrib)
if match:
regex = match + " " + regex
out = search(r"^.*" + regex + " (.+)", conf, M)
if out:
val = out.group(1).strip("'")
if attrib == "type-name":
config["type_name"] = val
if attrib == "code":
config["code"] = int(val)
if attrib == "type":
# <1.3 could be # (type), #/# (type/code) or 'type' (type_name)
# recent this is only for strings
if "/" in val: # type/code
- (type_no, code) = val.split(".")
+ type_no, code = val.split(".")
config["type"] = type_no
config["code"] = code
elif val.isnumeric():
config["type"] = type_no
else:
config["type_name"] = val
return config
def parse_icmp(self, conf, attrib=None):
"""
This function triggers the parsing of 'icmp' attributes.
:param conf: configuration to be parsed.
:param attrib: 'icmp'.
:return: generated config dictionary.
"""
cfg_dict = self.parse_icmp_attr(conf, "icmp")
if len(cfg_dict) == 0:
cfg_dict = self.parse_icmp_attr(conf, "icmpv6")
return cfg_dict
def parse_limit(self, conf, attrib=None):
"""
This function triggers the parsing of 'limit' attributes.
:param conf: configuration to be parsed.
:param attrib: 'limit'
:return: generated config dictionary.
"""
cfg_dict = self.parse_attr(conf, ["burst"], match=attrib)
cfg_dict["rate"] = self.parse_rate(conf, "rate")
return cfg_dict
def parse_attr(self, conf, attr_list, match=None):
"""
This function peforms the following:
- Form the regex to fetch the required attribute config.
- Type cast the output in desired format.
:param conf: configuration.
:param attr_list: list of attributes.
:param match: parent node/attribute name.
:return: generated config dictionary.
"""
config = {}
for attrib in attr_list:
regex = self.map_regex(attrib)
if match:
regex = match + " " + regex
if conf:
if self.is_bool(attrib):
out = conf.find(attrib.replace("_", "-"))
dis = conf.find(attrib.replace("_", "-") + " 'disable'")
if out >= 1:
if dis >= 1:
config[attrib] = False
else:
config[attrib] = True
else:
out = search(r"^.*" + regex + " (.+)", conf, M)
if not out:
if attrib == "disable":
out = search(r"^.*\d+" + " (disable$)", conf, M)
if attrib == "log":
out = search(r"^.*\d+" + " (log$)", conf, M)
if out:
val = out.group(1).strip("'")
if self.is_num(attrib):
val = int(val)
if attrib == "disable":
val = True
if attrib == "log":
val = "enable"
config[attrib] = val
return config
def map_regex(self, attrib):
"""
- This function construct the regex string.
- replace the underscore with hyphen.
:param attrib: attribute
:return: regex string
"""
regex = attrib.replace("_", "-")
if attrib == "disabled":
regex = "disable"
return regex
def is_bool(self, attrib):
"""
This function looks for the attribute in predefined bool type set.
:param attrib: attribute.
:return: True/False
"""
bool_set = (
"new",
"invalid",
"related",
"disabled",
"established",
"enable_default_log",
"default_log",
)
return True if attrib in bool_set else False
def is_num(self, attrib):
"""
This function looks for the attribute in predefined integer type set.
:param attrib: attribute.
:return: True/false.
"""
num_set = ("time", "code", "type", "count", "burst", "number")
return True if attrib in num_set else False
def parse_rate(self, conf, match):
"""
This function triggers the parsing of 'rate' attributes.
:param conf: configuration.
:param attrib: 'rate'
:return: generated config dictionary.
"""
config = {}
out = search(r"^.*" + match + " (.+)", conf, M)
if out:
val = out.group(1).strip("'")
if "/" in val: # number/unit
- (number, unit) = val.split("/")
+ number, unit = val.split("/")
config["number"] = number
config["unit"] = unit
return config
diff --git a/plugins/module_utils/network/vyos/facts/hostname/hostname.py b/plugins/module_utils/network/vyos/facts/hostname/hostname.py
index b4f7c529..18a66357 100644
--- a/plugins/module_utils/network/vyos/facts/hostname/hostname.py
+++ b/plugins/module_utils/network/vyos/facts/hostname/hostname.py
@@ -1,72 +1,71 @@
# -*- coding: utf-8 -*-
# Copyright 2022 Red Hat
# 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
"""
The vyos hostname 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.
"""
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.hostname.hostname import (
HostnameArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.hostname import (
HostnameTemplate,
)
class HostnameFacts(object):
"""The vyos hostname facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = HostnameArgs.argument_spec
def get_config(self, connection):
return connection.get("show configuration commands | grep host-name")
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Snmp_server network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = []
config_lines = []
if not data:
data = self.get_config(connection)
for resource in data.splitlines():
config_lines.append(re.sub("'", "", resource))
# parse native config using the Hostname template
hostname_parser = HostnameTemplate(lines=config_lines, module=self._module)
objs = hostname_parser.parse()
ansible_facts["ansible_network_resources"].pop("hostname", None)
params = utils.remove_empties(
hostname_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
)
facts["hostname"] = params.get("config", {})
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
index cd8008c6..51f3c5f6 100644
--- a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
@@ -1,146 +1,145 @@
#
# -*- 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 vyos interfaces 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 re import M, findall, search
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.interfaces.interfaces import (
InterfacesArgs,
)
class InterfacesFacts(object):
"""The vyos interfaces fact class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = InterfacesArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def get_device_data(self, connection):
data = connection.get_config(flags=["| grep interfaces"])
return data
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for interfaces
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = self.get_device_data(connection)
objs = []
interface_names = findall(
r"^set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|loopback|vxlan|openvpn|wireguard) (?:\'*)(\S+)(?:\'*)",
data,
M,
)
if interface_names:
for interface in set(interface_names):
intf_regex = r" %s (.+$)" % interface.strip("'")
cfg = findall(intf_regex, data, M)
obj = self.render_config(cfg)
obj["name"] = interface.strip("'")
if obj:
objs.append(obj)
facts = {}
if objs:
facts["interfaces"] = []
params = utils.validate_config(self.argument_spec, {"config": objs})
for cfg in params["config"]:
facts["interfaces"].append(utils.remove_empties(cfg))
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def render_config(self, conf):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
vif_conf = "\n".join(filter(lambda x: ("vif" in x), conf))
eth_conf = "\n".join(filter(lambda x: ("vif" not in x), conf))
config = self.parse_attribs(["description", "speed", "mtu", "duplex"], eth_conf)
config["vifs"] = self.parse_vifs(vif_conf)
return utils.remove_empties(config)
def parse_vifs(self, conf):
vif_names = findall(r"vif (?:\'*)(\d+)(?:\'*)", conf, M)
vifs_list = None
if vif_names:
vifs_list = []
for vif in set(vif_names):
vif_regex = r"%s (.+$)" % vif
cfg = "\n".join(findall(vif_regex, conf, M))
obj = self.parse_attribs(["description", "mtu"], cfg)
obj["vlan_id"] = int(vif)
if obj:
vifs_list.append(obj)
vifs_list = sorted(vifs_list, key=lambda i: i["vlan_id"])
return vifs_list
def parse_attribs(self, attribs, conf):
"""
Parse the attributes of a network interface.
:param attribs: List of attribute names.
:param conf: Configuration string.
:rtype: dict
:returns: Parsed configuration dictionary.
"""
config = {}
for item in attribs:
value = utils.parse_conf_arg(conf, item)
if value and item == "mtu":
config[item] = int(value.strip("'"))
elif value:
config[item] = value.strip("'")
else:
config[item] = None
# match only on disable next to the interface name
# there are other sub-commands that can be disabled
match = search(r"^ *disable", conf, M)
if match:
config["enabled"] = False
else:
config["enabled"] = True
return utils.remove_empties(config)
diff --git a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
index d62ea9b8..9e79e24d 100644
--- a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
@@ -1,145 +1,144 @@
#
# -*- 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 vyos l3_interfaces fact class
It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
import re
from copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.l3_interfaces.l3_interfaces import (
L3_interfacesArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
get_ip_address_version,
)
class L3_interfacesFacts(object):
"""The vyos l3_interfaces fact class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = L3_interfacesArgs.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 populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for l3_interfaces
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = connection.get_config()
# operate on a collection of resource x
objs = []
interface_names = re.findall(
r"set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|loopback|vxlan) (?:\'*)(\S+)(?:\'*)",
data,
re.M,
)
if interface_names:
for interface in set(interface_names):
intf_regex = r" %s .+$" % interface
cfg = re.findall(intf_regex, data, re.M)
obj = self.render_config(cfg)
obj["name"] = interface.strip("'")
if obj:
objs.append(obj)
ansible_facts["ansible_network_resources"].pop("l3_interfaces", None)
facts = {}
if objs:
facts["l3_interfaces"] = []
params = utils.validate_config(self.argument_spec, {"config": objs})
for cfg in params["config"]:
facts["l3_interfaces"].append(utils.remove_empties(cfg))
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def render_config(self, conf):
"""
Render config as dictionary structure and delete keys from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
vif_conf = "\n".join(filter(lambda x: ("vif" in x), conf))
eth_conf = "\n".join(filter(lambda x: ("vif" not in x), conf))
config = self.parse_attribs(eth_conf)
config["vifs"] = self.parse_vifs(vif_conf)
return utils.remove_empties(config)
def parse_vifs(self, conf):
vif_names = re.findall(r"vif (\d+)", conf, re.M)
vifs_list = None
if vif_names:
vifs_list = []
for vif in set(vif_names):
vif_regex = r" %s .+$" % vif
cfg = "\n".join(re.findall(vif_regex, conf, re.M))
obj = self.parse_attribs(cfg)
obj["vlan_id"] = vif
if obj:
vifs_list.append(obj)
return vifs_list
def parse_attribs(self, conf):
config = {}
ipaddrs = re.findall(r"address (\S+)", conf, re.M)
config["ipv4"] = []
config["ipv6"] = []
for item in ipaddrs:
item = item.strip("'")
if item == "dhcp":
config["ipv4"].append({"address": item})
elif item == "dhcpv6":
config["ipv6"].append({"address": item})
elif item == "no-default-link-local":
config["ipv6"].append({"address": item})
elif item == "autoconf":
config["ipv6"].append({"address": item})
else:
ip_version = get_ip_address_version(item.split("/")[0])
if ip_version == 4:
config["ipv4"].append({"address": item})
else:
config["ipv6"].append({"address": item})
for key, value in config.items():
if value == []:
config[key] = None
return utils.remove_empties(config)
diff --git a/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py
index 8e1c8624..94c73416 100644
--- a/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py
@@ -1,145 +1,145 @@
#
# -*- 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 vyos lag_interfaces 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from re import M, findall, search
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.lag_interfaces.lag_interfaces import (
Lag_interfacesArgs,
)
class Lag_interfacesFacts(object):
"""The vyos lag_interfaces fact class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Lag_interfacesArgs.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_config(self, connection):
"""Get the configuration from the device
:param connection: the device connection
:rtype: string
:returns: The configuration
"""
return connection.get_config()
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for lag_interfaces
:param module: the module instance
:param connection: the device connection
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = self.get_config(connection)
objs = []
lag_names = findall(r"^set interfaces bonding (\S+)", data, M)
if lag_names:
for lag in set(lag_names):
lag_regex = r" %s .+$" % lag
cfg = findall(lag_regex, data, M)
obj = self.render_config(cfg)
members = []
member = {}
group_regex = r"%s member interface .*eth.*" % lag
g_cfg = findall(group_regex, data, M)
for item in g_cfg:
output = search("member interface '(\\S+)'", item, M)
if output:
member["member"] = output.group(1).strip("'")
members.append(deepcopy(member))
obj["name"] = lag.strip("'")
if members:
obj["members"] = members
if obj:
objs.append(obj)
facts = {}
if objs:
facts["lag_interfaces"] = []
params = utils.validate_config(self.argument_spec, {"config": objs})
for cfg in params["config"]:
facts["lag_interfaces"].append(utils.remove_empties(cfg))
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def render_config(self, conf):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
arp_monitor_conf = "\n".join(filter(lambda x: ("arp-monitor" in x), conf))
hash_policy_conf = "\n".join(filter(lambda x: ("hash-policy" in x), conf))
lag_conf = "\n".join(filter(lambda x: ("bond" in x), conf))
config = self.parse_attribs(["mode", "primary"], lag_conf)
config["arp_monitor"] = self.parse_arp_monitor(arp_monitor_conf)
config["hash_policy"] = self.parse_hash_policy(hash_policy_conf)
return utils.remove_empties(config)
def parse_attribs(self, attribs, conf):
config = {}
for item in attribs:
value = utils.parse_conf_arg(conf, item)
if value:
config[item] = value.strip("'")
else:
config[item] = None
return utils.remove_empties(config)
def parse_arp_monitor(self, conf):
arp_monitor = None
if conf:
arp_monitor = {}
target_list = []
interval = search(r"^.*arp-monitor interval (.+)", conf, M)
targets = findall(r"^.*arp-monitor target '(.+)'", conf, M)
if targets:
for target in targets:
target_list.append(target)
arp_monitor["target"] = target_list
if interval:
value = interval.group(1).strip("'")
arp_monitor["interval"] = int(value)
return arp_monitor
def parse_hash_policy(self, conf):
hash_policy = None
if conf:
hash_policy = search(r"^.*hash-policy (.+)", conf, M)
hash_policy = hash_policy.group(1).strip("'")
return hash_policy
diff --git a/plugins/module_utils/network/vyos/facts/legacy/base.py b/plugins/module_utils/network/vyos/facts/legacy/base.py
index 30978e0e..59666e19 100644
--- a/plugins/module_utils/network/vyos/facts/legacy/base.py
+++ b/plugins/module_utils/network/vyos/facts/legacy/base.py
@@ -1,160 +1,159 @@
# -*- 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 VyOS interfaces 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.vyos.vyos.plugins.module_utils.network.vyos.vyos import (
get_capabilities,
run_commands,
)
class LegacyFactsBase(object):
COMMANDS = frozenset()
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, list(self.COMMANDS))
class Default(LegacyFactsBase):
COMMANDS = [
"show version",
]
def populate(self):
super(Default, self).populate()
data = self.responses[0]
self.facts["serialnum"] = self.parse_serialnum(data)
self.facts.update(self.platform_facts())
def parse_serialnum(self, data):
match = re.search(r"(?:HW|Hardware) S/N:\s+(\S+)", data)
if match:
return match.group(1)
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"):
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 Config(LegacyFactsBase):
COMMANDS = [
"show configuration commands",
"show system commit",
]
def populate(self):
super(Config, self).populate()
self.facts["config"] = self.responses
commits = self.responses[1]
entries = list()
entry = None
for line in commits.split("\n"):
match = re.match(r"(\d+)\s+(.+)by(.+)via(.+)", line)
if match:
if entry:
entries.append(entry)
entry = dict(
revision=match.group(1),
datetime=match.group(2),
by=str(match.group(3)).strip(),
via=str(match.group(4)).strip(),
comment=None,
)
else:
entry["comment"] = line.strip()
self.facts["commits"] = entries
class Neighbors(LegacyFactsBase):
COMMANDS = [
"show lldp neighbors",
"show lldp neighbors detail",
]
def populate(self):
super(Neighbors, self).populate()
all_neighbors = self.responses[0]
if "LLDP not configured" not in all_neighbors:
neighbors = self.parse(self.responses[1])
self.facts["neighbors"] = self.parse_neighbors(neighbors)
def parse(self, data):
parsed = list()
values = None
for line in data.split("\n"):
if not line:
continue
if line[0] == " ":
values += "\n%s" % line
elif line.startswith("Interface"):
if values:
parsed.append(values)
values = line
if values:
parsed.append(values)
return parsed
def parse_neighbors(self, data):
facts = dict()
for item in data:
interface = self.parse_interface(item)
host = self.parse_host(item)
port = self.parse_port(item)
if interface not in facts:
facts[interface] = list()
facts[interface].append(dict(host=host, port=port))
return facts
def parse_interface(self, data):
match = re.search(r"^Interface:\s+(\S+),", data)
return match.group(1)
def parse_host(self, data):
match = re.search(r"SysName:\s+(.+)$", data, re.M)
if match:
return match.group(1)
def parse_port(self, data):
match = re.search(r"PortDescr:\s+(.+)$", data, re.M)
if match:
return match.group(1)
diff --git a/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py b/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py
index 7a6e9b8e..40ff2ad3 100644
--- a/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py
+++ b/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py
@@ -1,126 +1,126 @@
#
# -*- 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 vyos lldp_global 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from re import M, findall
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.lldp_global.lldp_global import (
Lldp_globalArgs,
)
class Lldp_globalFacts(object):
"""The vyos lldp_global fact class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Lldp_globalArgs.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_config(self, connection):
return connection.get("show configuration commands | grep lldp")
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for lldp_global
: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_config(connection)
objs = {}
lldp_output = findall(r"^set service lldp (\S+)", data, M)
if lldp_output:
for item in set(lldp_output):
lldp_regex = r" %s .+$" % item
cfg = findall(lldp_regex, data, M)
obj = self.render_config(cfg)
if obj:
objs.update(obj)
lldp_service = findall(r"^set service (lldp)?('lldp')", data, M)
if lldp_service or lldp_output:
lldp_obj = {}
lldp_obj["enable"] = True
objs.update(lldp_obj)
facts = {}
params = utils.validate_config(self.argument_spec, {"config": objs})
facts["lldp_global"] = utils.remove_empties(params["config"])
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def render_config(self, conf):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
protocol_conf = "\n".join(filter(lambda x: ("legacy-protocols" in x), conf))
att_conf = "\n".join(filter(lambda x: ("snmp" in x), conf))
addr_conf = "\n".join(filter(lambda x: ("management-address" in x), conf))
config = self.parse_attribs(["snmp"], att_conf)
config["legacy_protocols"] = self.parse_protocols(protocol_conf)
config["addresses"] = self.parse_addresses(addr_conf)
return utils.remove_empties(config)
def parse_protocols(self, conf):
protocol_support = None
if conf:
protocols = findall(r"^.*legacy-protocols (.+)", conf, M)
if protocols:
protocol_support = []
for protocol in protocols:
protocol_support.append(protocol.strip("'"))
return protocol_support
def parse_addresses(self, conf):
management_addresses = None
if conf:
addresses = findall(r"^.*management-address (.+)", conf, M)
if addresses:
management_addresses = []
for protocol in addresses:
management_addresses.append(protocol.strip("'"))
return management_addresses
def parse_attribs(self, attribs, conf):
config = {}
for item in attribs:
value = utils.parse_conf_arg(conf, item)
if value:
config[item] = value.strip("'")
else:
config[item] = None
return utils.remove_empties(config)
diff --git a/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py
index e029b47a..72bdf154 100644
--- a/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py
@@ -1,127 +1,126 @@
#
# -*- 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 vyos lldp_interfaces 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 re import M, findall, search
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.lldp_interfaces.lldp_interfaces import (
Lldp_interfacesArgs,
)
class Lldp_interfacesFacts(object):
"""The vyos lldp_interfaces fact class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Lldp_interfacesArgs.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 populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for lldp_interfaces
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = connection.get_config()
objs = []
lldp_names = findall(r"^set service lldp interface (\S+)", data, M)
if lldp_names:
for lldp in set(lldp_names):
lldp_regex = r" %s .+$" % lldp
cfg = findall(lldp_regex, data, M)
obj = self.render_config(cfg)
obj["name"] = lldp.strip("'")
if obj:
objs.append(obj)
facts = {}
if objs:
facts["lldp_interfaces"] = objs
ansible_facts["ansible_network_resources"].update(facts)
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def render_config(self, conf):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
config = {}
location = {}
elin_conf = "\n".join(filter(lambda x: ("elin" in x), conf))
coordinate_conf = "\n".join(filter(lambda x: ("coordinate-based" in x), conf))
disable = "\n".join(filter(lambda x: ("disable" in x), conf))
coordinate_based_conf = self.parse_attribs(
["altitude", "datum", "longitude", "latitude"],
coordinate_conf,
)
elin_based_conf = self.parse_lldp_elin_based(elin_conf)
if disable:
config["enable"] = False
if coordinate_conf:
location["coordinate_based"] = coordinate_based_conf
config["location"] = location
elif elin_conf:
location["elin"] = elin_based_conf
config["location"] = location
return utils.remove_empties(config)
def parse_attribs(self, attribs, conf):
config = {}
for item in attribs:
value = utils.parse_conf_arg(conf, item)
if value:
value = value.strip("'")
if item == "altitude":
value = int(value)
config[item] = value
else:
config[item] = None
return utils.remove_empties(config)
def parse_lldp_elin_based(self, conf):
elin_based = None
if conf:
e_num = search(r"^.* elin (.+)", conf, M)
elin_based = e_num.group(1).strip("'")
return elin_based
diff --git a/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py b/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py
index da80d967..1e89aad6 100644
--- a/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py
+++ b/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py
@@ -1,100 +1,99 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos logging_global 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 ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.logging_global.logging_global import (
Logging_globalArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.logging_global import (
Logging_globalTemplate,
)
class Logging_globalFacts(object):
"""The vyos logging_global facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Logging_globalArgs.argument_spec
def get_logging_data(self, connection):
return connection.get("show configuration commands | grep syslog")
def process_facts(self, objFinal):
if objFinal:
for ke, vl in objFinal.items():
if ke == "files":
_files = []
for k, v in vl.items():
_files.append(v)
objFinal[ke] = _files
objFinal[ke] = sorted(objFinal[ke], key=lambda item: item["path"])
elif ke == "hosts":
_hosts = []
for k, v in vl.items():
_hosts.append(v)
objFinal[ke] = _hosts
objFinal[ke] = sorted(objFinal[ke], key=lambda item: item["hostname"])
elif ke == "users":
_users = []
for k, v in vl.items():
_users.append(v)
objFinal[ke] = _users
objFinal[ke] = sorted(objFinal[ke], key=lambda item: item["username"])
elif ke == "console" or ke == "global_params":
if objFinal[ke].get("facilities"):
objFinal[ke]["facilities"] = sorted(
objFinal[ke]["facilities"],
key=lambda item: item["facility"],
)
return objFinal
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Logging_global network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = []
if not data:
data = self.get_logging_data(connection)
# parse native config using the Logging_global template
logging_global_parser = Logging_globalTemplate(lines=data.splitlines(), module=self._module)
objs = logging_global_parser.parse()
ansible_facts["ansible_network_resources"].pop("logging_global", None)
objs = self.process_facts(objs)
params = utils.remove_empties(
logging_global_parser.validate_config(
self.argument_spec,
{"config": objs},
redact=True,
),
)
facts["logging_global"] = params.get("config", {})
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/ntp_global/ntp_global.py b/plugins/module_utils/network/vyos/facts/ntp_global/ntp_global.py
index 880bc79f..a6d6892f 100644
--- a/plugins/module_utils/network/vyos/facts/ntp_global/ntp_global.py
+++ b/plugins/module_utils/network/vyos/facts/ntp_global/ntp_global.py
@@ -1,92 +1,91 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos ntp 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.
"""
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ntp_global.ntp_global import (
Ntp_globalArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ntp_global import (
NtpTemplate,
)
class Ntp_globalFacts(object):
"""The vyos ntp facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Ntp_globalArgs.argument_spec
def get_config(self, connection):
return connection.get("show configuration commands | grep ntp")
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Ntp network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = []
config_lines = []
if not data:
data = self.get_config(connection)
for resource in data.splitlines():
config_lines.append(re.sub("'", "", resource))
# parse native config using the Ntp template
ntp_parser = NtpTemplate(lines=config_lines, module=self._module)
objs = ntp_parser.parse()
if objs:
if "allow_clients" in objs:
objs["allow_clients"] = sorted(list(objs["allow_clients"]))
if "listen_addresses" in objs:
objs["listen_addresses"] = sorted(list(objs["listen_addresses"]))
""" if "options" in objs["servers"].values():
val = objs["servers"].values()
val["options"] = sorted(val["options"]) """
if "servers" in objs:
objs["servers"] = list(objs["servers"].values())
objs["servers"] = sorted(objs["servers"], key=lambda k: k["server"])
for i in objs["servers"]:
if "options" in i:
i["options"] = sorted(list(i["options"]))
ansible_facts["ansible_network_resources"].pop("ntp_global", None)
params = utils.remove_empties(
ntp_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
)
if params.get("config"):
facts["ntp_global"] = params["config"]
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py
index 2160fc71..1a9a687d 100644
--- a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py
@@ -1,135 +1,134 @@
# -*- coding: utf-8 -*-
# Copyright 2020 Red Hat
# 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
"""
The vyos ospf_interfaces 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.
"""
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospf_interfaces.ospf_interfaces import (
Ospf_interfacesArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import (
Ospf_interfacesTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import (
Ospf_interfacesTemplate14,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ospf_interfacesFacts(object):
"""The vyos ospf_interfaces facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Ospf_interfacesArgs.argument_spec
def get_device_data(self, connection):
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
# use set protocols ospf in order to get both ospf and ospfv3
return connection.get("show configuration commands | match 'set protocols ospf'")
return connection.get('show configuration commands | match "set interfaces"')
def get_config_set_1_4(self, data):
"""To classify the configurations beased on interface"""
config_dict = {}
for config_line in data.splitlines():
ospf_int = re.search(r"set protocols (?:ospf|ospfv3) interface (\S+).*", config_line)
if ospf_int:
config_dict[ospf_int.group(1)] = (
config_dict.get(ospf_int.group(1), "") + config_line + "\n"
)
return list(config_dict.values())
def get_config_set_1_2(self, data):
"""To classify the configurations beased on interface"""
interface_list = []
config_set = []
int_string = ""
for config_line in data.splitlines():
ospf_int_raw = re.findall(r"^set interfaces \S+ (\S+)", config_line, re.M)
ospf_int_vif = re.findall(r"^set interfaces \S+ (\S+) vif (\d+)", config_line, re.M)
ospf_int = ospf_int_raw + ospf_int_vif
if ospf_int:
if ospf_int not in interface_list:
if int_string:
config_set.append(int_string)
interface_list.append(ospf_int)
int_string = ""
int_string = int_string + config_line + "\n"
if int_string:
config_set.append(int_string)
return config_set
def get_config_set(self, data, connection):
"""To classify the configurations beased on interface"""
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
return self.get_config_set_1_4(data)
return self.get_config_set_1_2(data)
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Ospf_interfaces network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = []
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
ospf_interface_class = Ospf_interfacesTemplate14
else:
ospf_interface_class = Ospf_interfacesTemplate
ospf_interfaces_parser = ospf_interface_class(lines=[], module=self._module)
if not data:
data = self.get_device_data(connection)
# parse native config using the Ospf_interfaces template
ospf_interfaces_facts = []
resources = self.get_config_set(data, connection)
for resource in resources:
ospf_interfaces_parser = ospf_interface_class(
lines=resource.split("\n"),
module=self._module,
)
objs = ospf_interfaces_parser.parse()
for key, sortv in [("address_family", "afi")]:
if key in objs and objs[key]:
objs[key] = list(objs[key].values())
ospf_interfaces_facts.append(objs)
ansible_facts["ansible_network_resources"].pop("ospf_interfaces", None)
facts = {"ospf_interfaces": []}
params = utils.remove_empties(
ospf_interfaces_parser.validate_config(
self.argument_spec,
{"config": ospf_interfaces_facts},
redact=True,
),
)
if params.get("config"):
for cfg in params["config"]:
facts["ospf_interfaces"].append(utils.remove_empties(cfg))
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py
index d07bf13a..056f949d 100644
--- a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py
+++ b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py
@@ -1,509 +1,509 @@
#
# -*- 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 vyos ospfv2 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from re import M, findall, search
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv2.ospfv2 import (
Ospfv2Args,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ospfv2Facts(object):
"""The vyos ospfv2 fact class"""
def __init__(
self,
module,
subspec="config",
options="options",
):
self._module = module
self.argument_spec = Ospfv2Args.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def get_device_data(self, connection):
return connection.get_config()
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for ospfv2
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = self.get_device_data(connection)
# typically data is populated from the current device configuration
# data = connection.get('show running-config | section ^interface')
# using mock data instead
objs = {}
ospfv2 = findall(r"^set protocols ospf (.+)", data, M)
if ospfv2:
objs = self.render_config(ospfv2)
facts = {}
params = utils.validate_config(self.argument_spec, {"config": objs})
facts["ospfv2"] = utils.remove_empties(params["config"])
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def render_config(self, conf):
"""
Render config as dictionary structure
:param conf: The configuration
:returns: The generated config
"""
conf = "\n".join(filter(lambda x: x, conf))
a_lst = ["default_metric", "log_adjacency_changes"]
config = self.parse_attr(conf, a_lst)
if not config:
config = {}
config["timers"] = self.parse_timers(conf)
config["auto_cost"] = self.parse_auto_cost(conf)
config["distance"] = self.parse_distance(conf)
config["max_metric"] = self.parse_max_metric(conf)
config["default_information"] = self.parse_def_info(conf)
config["route_map"] = self.parse_leaf_list(conf, "route-map")
config["mpls_te"] = self.parse_attrib(conf, "mpls_te", "mpls-te")
config["areas"] = self.parse_attrib_list(conf, "area", "area_id")
config["parameters"] = self.parse_attrib(conf, "parameters", "parameters")
config["neighbor"] = self.parse_attrib_list(conf, "neighbor", "neighbor_id")
config["passive_interface"] = self.parse_passive(conf, "passive-interface")
config["redistribute"] = self.parse_attrib_list(conf, "redistribute", "route_type")
config["passive_interface_exclude"] = self.parse_passive(
conf,
"passive-interface-exclude",
)
return config
def parse_timers(self, conf):
"""
This function triggers the parsing of 'timers' attributes
:param conf: configuration
:return: generated config dictionary
"""
cfg_dict = {}
cfg_dict["refresh"] = self.parse_refresh(conf, "refresh")
cfg_dict["throttle"] = self.parse_throttle(conf, "spf")
return cfg_dict
def parse_throttle(self, conf, attrib=None):
"""
This function triggers the parsing of 'throttle' attributes
:param conf: configuration
:param attrib: 'spf'
:return: generated config dictionary
"""
cfg_dict = {}
cfg_dict[attrib] = self.parse_attrib(conf, attrib, match=attrib)
return cfg_dict
def parse_refresh(self, conf, attrib=None):
"""
This function triggers the parsing of 'refresh' attributes
:param conf: configuration
:param attrib: 'refresh'
:return: generated config dictionary
"""
cfg_dict = self.parse_attr(conf, ["timers"], match=attrib)
return cfg_dict
def parse_leaf_list(self, conf, attrib):
"""
This function forms the regex to fetch the listed attributes
from the configuration data
:param conf: configuration data
:param attrib: attribute name
:return: generated rule list configuration
"""
lst = []
items = findall(r"^" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M)
if items:
for i in set(items):
lst.append(i.strip("'"))
lst.sort()
return lst
def parse_passive(self, conf, attrib):
"""
This function forms the regex to fetch the listed attributes
from the configuration data
:param conf: configuration data
:param attrib: attribute name
:return: generated rule list configuration
"""
lst = []
items = []
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
if attrib == "passive-interface-exclude":
items = findall("^interface (?:'*)(\\S+)(?:'*) passive disable$", conf, M)
else:
items = findall("^interface (?:'*)(\\S+)(?:'*) passive$", conf, M)
items += findall(r"^" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M)
if items:
for i in set(items):
lst.append(i.strip("'"))
lst.sort()
return lst
def parse_distance(self, conf, attrib=None):
"""
This function triggers the parsing of 'distance' attributes
:param conf: configuration
:param attrib: attribute name
:return: generated config dictionary
"""
cfg_dict = self.parse_attr(conf, ["global"], match=attrib)
cfg_dict["ospf"] = self.parse_ospf(conf, "ospf")
return cfg_dict
def parse_ospf(self, conf, attrib=None):
"""
This function triggers the parsing of 'distance ospf' attributes
:param conf: configuration
:param attrib: 'ospf'
:return: generated config dictionary
"""
cfg_dict = self.parse_attrib(conf, "ospf", match=attrib)
return cfg_dict
def parse_max_metric(self, conf):
"""
This function triggers the parsing of 'max_metric' attributes
:param conf: configuration
:return: generated config dictionary
"""
cfg_dict = {}
cfg_dict["router_lsa"] = self.parse_attrib(conf, "router_lsa", match="router-lsa")
return cfg_dict
def parse_auto_cost(self, conf, attrib=None):
"""
This function triggers the parsing of 'auto_cost' attributes
:param conf: configuration
:param attrib: attribute name
:return: generated config dictionary
"""
cfg_dict = self.parse_attr(conf, ["reference_bandwidth"], match=attrib)
return cfg_dict
def parse_def_info(self, conf):
"""
This function triggers the parsing of 'default_information' attributes
:param conf: configuration
:return: generated config dictionary
"""
cfg_dict = {}
cfg_dict["originate"] = self.parse_attrib(conf, "originate", "originate")
return cfg_dict
def parse_area(self, conf, area_id):
"""
This function triggers the parsing of 'area' attributes.
:param conf: configuration data
:param area_id: area identity
:return: generated rule configuration dictionary.
"""
rule = self.parse_attrib(conf, "area_id", match=area_id)
r_sub = {
"area_type": self.parse_area_type(conf, "area-type"),
"network": self.parse_network(conf),
"range": self.parse_attrib_list(conf, "range", "address"),
"virtual_link": self.parse_attrib_list(conf, "virtual-link", "address"),
}
rule.update(r_sub)
return rule
def parse_key(self, conf, key_id):
"""
This function triggers the parsing of 'area' attributes.
:param conf: configuration data
:param area_id: area identity
:return: generated rule configuration dictionary.
"""
rule = self.parse_attrib(conf, "key_id", match=key_id)
return rule
def parse_area_type(self, conf, attrib=None):
"""
This function triggers the parsing of 'area_type' attributes
:param conf: configuration
:param attrib: 'area-type'
:return: generated config dictionary
"""
cfg_dict = self.parse_attr(conf, ["normal"], match=attrib)
cfg_dict["nssa"] = self.parse_attrib(conf, "nssa", match="nssa")
cfg_dict["stub"] = self.parse_attrib(conf, "stub", match="stub")
return cfg_dict
def parse_network(self, conf):
"""
This function forms the regex to fetch the 'network'
:param conf: configuration data
:return: generated rule list configuration
"""
a_lst = []
applications = findall(r"network (.+)", conf, M)
if applications:
app_lst = []
for r in set(applications):
obj = {"address": r.strip("'")}
app_lst.append(obj)
a_lst = sorted(app_lst, key=lambda i: i["address"])
return a_lst
def parse_vlink(self, conf):
"""
This function triggers the parsing of 'virtual_link' attributes
:param conf: configuration data
:return: generated rule configuration dictionary
"""
rule = self.parse_attrib(conf, "vlink")
r_sub = {"authentication": self.parse_authentication(conf, "authentication")}
rule.update(r_sub)
return rule
def parse_authentication(self, conf, attrib=None):
"""
This function triggers the parsing of 'authentication' attributes.
:param conf: configuration
:param attrib: 'authentication'
:return: generated config dictionary
"""
cfg_dict = self.parse_attr(conf, ["plaintext_password"], match=attrib)
cfg_dict["md5"] = self.parse_attrib_list(conf, "key-id", "key_id")
return cfg_dict
def parse_attrib_list(self, conf, attrib, param):
"""
This function forms the regex to fetch the listed attributes
from config
:param conf: configuration data
:param attrib: attribute name
:param param: parameter data
:return: generated rule list configuration
"""
r_lst = []
if attrib == "area":
items = findall(
r"^" + attrib.replace("_", "-") + " (?:'*)(\\S+)(?:'*)",
conf,
M,
)
elif attrib == "key-id":
items = findall(
r"^.*" + attrib.replace("_", "-") + " (?:'*)(\\S+)(?:'*)",
conf,
M,
)
else:
items = findall(r"" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M)
if items:
a_lst = []
for item in set(items):
i_regex = r" %s .+$" % item
cfg = "\n".join(findall(i_regex, conf, M))
if attrib == "area":
obj = self.parse_area(cfg, item)
elif attrib == "virtual-link":
obj = self.parse_vlink(cfg)
elif attrib == "key-id":
obj = self.parse_key(cfg, item)
else:
obj = self.parse_attrib(cfg, attrib)
obj[param] = item.strip("'")
if obj:
a_lst.append(obj)
r_lst = sorted(a_lst, key=lambda i: i[param])
return r_lst
def parse_attrib(self, conf, param, match=None):
"""
This function triggers the parsing of 'ospf' attributes
:param conf: configuration data
:return: generated configuration dictionary
"""
param_lst = {
"key_id": ["md5_key"],
"mpls_te": ["enabled", "router_address"],
"area_id": ["shortcut", "authentication"],
"neighbor": ["priority", "poll_interval"],
"stub": ["set", "default_cost", "no_summary"],
"range": ["cost", "substitute", "not_advertise"],
"ospf": ["external", "inter_area", "intra_area"],
"spf": ["delay", "max_holdtime", "initial_holdtime"],
"redistribute": ["metric", "metric_type", "route_map"],
"nssa": ["set", "translate", "default_cost", "no_summary"],
"config_routes": ["default_metric", "log_adjacency_changes"],
"originate": ["always", "metric", "metric_type", "route_map"],
"router_lsa": ["administrative", "on_shutdown", "on_startup"],
"parameters": [
"abr_type",
"opaque_lsa",
"router_id",
"rfc1583_compatibility",
],
"vlink": [
"dead_interval",
"hello_interval",
"transmit_delay",
"retransmit_interval",
],
}
cfg_dict = self.parse_attr(conf, param_lst[param], match)
return cfg_dict
def parse_attr(self, conf, attr_list, match=None):
"""
This function peforms the following:
- Form the regex to fetch the required attribute config.
- Type cast the output in desired format.
:param conf: configuration.
:param attr_list: list of attributes.
:param match: parent node/attribute name.
:return: generated config dictionary.
"""
config = {}
for attrib in attr_list:
regex = self.map_regex(attrib)
if match:
regex = match.replace("_", "-") + " " + regex
if conf:
if self.is_bool(attrib):
out = conf.find(attrib.replace("_", "-"))
dis = conf.find(attrib.replace("_", "-") + " 'disable'")
if match:
if attrib == "set" and conf.find(match) >= 1:
config[attrib] = True
en = conf.find(match + " enable") != -1
if out >= 1:
if dis >= 1:
config[attrib] = False
else:
config[attrib] = True
elif match and en:
config[attrib] = True
else:
out = search(r"^.*" + regex + " (.+)", conf, M)
if out:
val = out.group(1).strip("'")
if self.is_num(attrib):
val = int(val)
config[attrib] = val
return config
def map_regex(self, attrib):
"""
- This function construct the regex string.
- replace the underscore with hyphen.
:param attrib: attribute
:return: regex string
"""
return (
"disable"
if attrib == "disabled"
else (
"enable"
if attrib == "enabled"
else ("area" if attrib == "area_id" else attrib.replace("_", "-"))
)
)
def is_bool(self, attrib):
"""
This function looks for the attribute in predefined bool type set.
:param attrib: attribute.
:return: True/False
"""
bool_set = (
"set",
"always",
"normal",
"enabled",
"opaque_lsa",
"not_advertise",
"administrative",
"rfc1583_compatibility",
)
return True if attrib in bool_set else False
def is_num(self, attrib):
"""
This function looks for the attribute in predefined integer type set.
:param attrib: attribute.
:return: True/false.
"""
num_set = (
"ospf",
"delay",
"metric",
"inter_area",
"intra_area",
"on_startup",
"metric_type",
"on_shutdown",
"max_holdtime",
"poll_interval",
"default_metric",
"initial_holdtime",
"key_id",
)
return True if attrib in num_set else False
diff --git a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py
index ae67a4b6..5fd9e627 100644
--- a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py
+++ b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py
@@ -1,217 +1,217 @@
#
# -*- 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 vyos ospfv3 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
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
from re import M, findall, search
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv3.ospfv3 import (
Ospfv3Args,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ospfv3Facts(object):
"""The vyos ospfv3 fact class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Ospfv3Args.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def get_device_data(self, connection):
return connection.get_config()
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for ospfv3
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = self.get_device_data(connection)
# typically data is populated from the current device configuration
# data = connection.get('show running-config | section ^interface')
# using mock data instead
objs = {}
ospfv3 = findall(r"^set protocols ospfv3 (.+)", data, M)
if ospfv3:
objs = self.render_config(ospfv3)
facts = {}
params = utils.validate_config(self.argument_spec, {"config": objs})
facts["ospfv3"] = utils.remove_empties(params["config"])
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def render_config(self, conf):
"""
Render config as dictionary structure
:param conf: The configuration
:returns: The generated config
"""
conf = "\n".join(filter(lambda x: x, conf))
config = {}
config["parameters"] = self.parse_attrib(conf, "parameters", "parameters")
config["areas"] = self.parse_attrib_list(conf, "area", "area_id")
config["redistribute"] = self.parse_attrib_list(conf, "redistribute", "route_type")
return config
def parse_attrib_list(self, conf, attrib, param):
"""
This function forms the regex to fetch the listed attributes
from config
:param conf: configuration data
:param attrib: attribute name
:param param: parameter data
:return: generated rule list configuration
"""
r_lst = []
if attrib == "area":
items = findall(r"^" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M)
else:
items = findall(r"" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M)
if items:
a_lst = []
for item in set(items):
i_regex = r" %s .+$" % item
cfg = "\n".join(findall(i_regex, conf, M))
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
cfg14 = findall(r"(interface .+) area '%s'$" % item, conf, M)
cfg += "\n " + item + " " + " ".join(cfg14)
if attrib == "area":
obj = self.parse_area(cfg, item)
else:
obj = self.parse_attrib(cfg, attrib)
obj[param] = item.strip("'")
if obj:
a_lst.append(obj)
r_lst = sorted(a_lst, key=lambda i: i[param])
return r_lst
def parse_area(self, conf, area_id):
"""
This function triggers the parsing of 'area' attributes.
:param conf: configuration data
:param area_id: area identity
:return: generated rule configuration dictionary.
"""
rule = self.parse_attrib(conf, "area_id", match=area_id)
r_sub = {"range": self.parse_attrib_list(conf, "range", "address")}
rule.update(r_sub)
r_int = {"interface": self.parse_attrib_list(conf, "interface", "name")}
rule.update(r_int)
return rule
def parse_attrib(self, conf, param, match=None):
"""
This function triggers the parsing of 'ospf' attributes
:param conf: configuration data
:return: generated configuration dictionary
"""
param_lst = {
"area_id": ["export_list", "import_list"],
"redistribute": ["route_map"],
"range": ["advertise", "not_advertise"],
"interface": ["name"],
"parameters": ["router_id"],
}
cfg_dict = self.parse_attr(conf, param_lst[param], match)
return cfg_dict
def parse_attr(self, conf, attr_list, match=None):
"""
This function peforms the following:
- Form the regex to fetch the required attribute config.
- Type cast the output in desired format.
:param conf: configuration.
:param attr_list: list of attributes.
:param match: parent node/attribute name.
:return: generated config dictionary.
"""
config = {}
for attrib in attr_list:
regex = self.map_regex(attrib)
if match:
regex = match.replace("_", "-") + " " + regex
if conf:
if self.is_bool(attrib):
out = conf.find(attrib.replace("_", "-"))
dis = conf.find(attrib.replace("_", "-") + " 'disable'")
if match:
en = conf.find(match + " 'enable'")
if out >= 1:
if dis >= 1:
config[attrib] = False
else:
config[attrib] = True
elif match and en >= 1:
config[attrib] = True
else:
out = search(r"^.*" + regex + " (.+)", conf, M)
if out:
val = out.group(1).strip("'")
if self.is_num(attrib):
val = int(val)
config[attrib] = val
return config
def map_regex(self, attrib):
"""
- This function construct the regex string.
- replace the underscore with hyphen.
:param attrib: attribute
:return: regex string
"""
return (
"disable"
if attrib == "disabled"
else "enable" if attrib == "enabled" else attrib.replace("_", "-")
)
def is_bool(self, attrib):
"""
This function looks for the attribute in predefined bool type set.
:param attrib: attribute.
:return: True/False
"""
bool_set = ("enabled", "advertise", "not_advertise")
return True if attrib in bool_set else False
def is_num(self, attrib):
"""
This function looks for the attribute in predefined integer type set.
:param attrib: attribute.
:return: True/false.
"""
num_set = "ospf"
return True if attrib in num_set else False
diff --git a/plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py b/plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py
index 17f63fb2..4bdcbf8c 100644
--- a/plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py
+++ b/plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py
@@ -1,89 +1,88 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos prefix_lists 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 ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.prefix_lists.prefix_lists import (
Prefix_listsArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.prefix_lists import (
Prefix_listsTemplate,
)
class Prefix_listsFacts(object):
"""The vyos prefix_lists facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Prefix_listsArgs.argument_spec
def get_config(self, connection):
return connection.get("show configuration commands | grep prefix-list")
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Prefix_lists network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = []
if not data:
data = self.get_config(connection)
# parse native config using the Prefix_lists template
prefix_lists_parser = Prefix_listsTemplate(lines=data.splitlines(), module=self._module)
objs = prefix_lists_parser.parse()
objs = sorted(
list(objs.values()),
key=lambda k: k["afi"],
)
if objs:
for item in objs:
item["prefix_lists"] = sorted(
list(item["prefix_lists"].values()),
key=lambda k: k["name"],
)
for pl in item["prefix_lists"]:
if "entries" in pl:
pl["entries"] = sorted(
list(pl["entries"].values()),
key=lambda k: k["sequence"],
)
ansible_facts["ansible_network_resources"].pop("prefix_lists", None)
params = utils.remove_empties(
prefix_lists_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
)
if params.get("config"):
facts["prefix_lists"] = params["config"]
else:
facts["prefix_lists"] = []
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py b/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py
index cfae26e3..d4084fd7 100644
--- a/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py
+++ b/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py
@@ -1,96 +1,95 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos route_maps 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 copy import deepcopy
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.route_maps.route_maps import (
Route_mapsArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps import (
Route_mapsTemplate,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps_14 import (
Route_mapsTemplate14,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
LooseVersion,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Route_mapsFacts(object):
"""The vyos route_maps facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Route_mapsArgs.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_config(self, connection):
return connection.get("show configuration commands | grep route-map")
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Route_maps network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = []
if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
route_maps_class = Route_mapsTemplate14
else:
route_maps_class = Route_mapsTemplate
if not data:
data = self.get_config(connection)
# parse native config using the Route_maps template
route_maps_parser = route_maps_class(lines=data.splitlines())
if route_maps_parser.parse().get("route_maps"):
objs = list(route_maps_parser.parse().get("route_maps").values())
for item in objs:
if item.get("entries"):
item["entries"] = list(item["entries"].values())
ansible_facts["ansible_network_resources"].pop("route_maps", None)
# import epdb;epdb.serve()
params = utils.remove_empties(utils.validate_config(self.argument_spec, {"config": objs}))
if params.get("config"):
facts["route_maps"] = params["config"]
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py b/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py
index d961a0ff..d3ff02e5 100644
--- a/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py
+++ b/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py
@@ -1,113 +1,112 @@
# -*- coding: utf-8 -*-
# Copyright 2022 Red Hat
# 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
"""
The vyos snmp_server 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.
"""
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.snmp_server.snmp_server import (
Snmp_serverArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.snmp_server import (
Snmp_serverTemplate,
)
class Snmp_serverFacts(object):
"""The vyos snmp_server facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Snmp_serverArgs.argument_spec
def get_config(self, connection):
return connection.get("show configuration commands | grep snmp")
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Snmp_server network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = []
config_lines = []
if not data:
data = self.get_config(connection)
for resource in data.splitlines():
config_lines.append(re.sub("'", "", resource))
# parse native config using the Snmp_server template
snmp_server_parser = Snmp_serverTemplate(lines=config_lines, module=self._module)
objs = snmp_server_parser.parse()
if objs:
if "communities" in objs:
for k in objs["communities"].values():
for param, val in k.items():
if param in ["clients", "networks"]:
if None in val:
val.remove(None)
val.sort()
objs["communities"] = sorted(
list(objs["communities"].values()),
key=lambda k, sk="name": k[sk],
)
if "listen_addresses" in objs:
objs["listen_addresses"] = sorted(
list(objs["listen_addresses"].values()),
key=lambda k, sk="address": k[sk],
)
if "snmp_v3" in objs:
if "groups" in objs["snmp_v3"]:
objs["snmp_v3"]["groups"] = sorted(
list(objs["snmp_v3"]["groups"].values()),
key=lambda k, sk="group": k[sk],
)
if "trap_targets" in objs["snmp_v3"]:
objs["snmp_v3"]["trap_targets"] = sorted(
list(objs["snmp_v3"]["trap_targets"].values()),
key=lambda k, sk="address": k[sk],
)
if "users" in objs["snmp_v3"]:
objs["snmp_v3"]["users"] = sorted(
list(objs["snmp_v3"]["users"].values()),
key=lambda k, sk="user": k[sk],
)
if "views" in objs["snmp_v3"]:
objs["snmp_v3"]["views"] = sorted(
list(objs["snmp_v3"]["views"].values()),
key=lambda k, sk="view": k[sk],
)
else:
objs = {}
ansible_facts["ansible_network_resources"].pop("snmp_server", None)
params = utils.remove_empties(
snmp_server_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
)
facts["snmp_server"] = params.get("config", {})
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py b/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py
index 710d91fe..1bce772c 100644
--- a/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py
+++ b/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py
@@ -1,178 +1,177 @@
#
# -*- 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 vyos static_routes 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 re import M, findall, search
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.static_routes.static_routes import (
Static_routesArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
get_route_type,
)
class Static_routesFacts(object):
"""The vyos static_routes fact class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = Static_routesArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def get_device_data(self, connection):
return connection.get_config()
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for static_routes
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = self.get_device_data(connection)
# typically data is populated from the current device configuration
# data = connection.get('show running-config | section ^interface')
# using mock data instead
objs = []
r_v4 = []
r_v6 = []
af = []
static_routes = findall(r"set protocols static route(6)? (\S+)", data, M)
if static_routes:
for route in set(static_routes):
route_regex = r" %s .+$" % route[1]
cfg = findall(route_regex, data, M)
sr = self.render_config(cfg)
sr["dest"] = route[1].strip("'")
afi = self.get_afi(sr["dest"])
if afi == "ipv4":
r_v4.append(sr)
else:
r_v6.append(sr)
if r_v4:
afi_v4 = {"afi": "ipv4", "routes": r_v4}
af.append(afi_v4)
if r_v6:
afi_v6 = {"afi": "ipv6", "routes": r_v6}
af.append(afi_v6)
config = {"address_families": af}
if config:
objs.append(config)
ansible_facts["ansible_network_resources"].pop("static_routes", None)
facts = {}
if objs:
facts["static_routes"] = []
params = utils.validate_config(self.argument_spec, {"config": objs})
for cfg in params["config"]:
facts["static_routes"].append(utils.remove_empties(cfg))
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def render_config(self, conf):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
next_hops_conf = "\n".join(filter(lambda x: ("next-hop" in x or "interface" in x), conf))
blackhole_conf = "\n".join(filter(lambda x: ("blackhole" in x), conf))
routes_dict = {
"blackhole_config": self.parse_blackhole(blackhole_conf),
"next_hops": self.parse_next_hop(next_hops_conf),
}
return routes_dict
def parse_blackhole(self, conf):
blackhole = None
if conf:
distance = search(r"^.*blackhole distance (.\S+)", conf, M)
bh = conf.find("blackhole")
if distance is not None:
blackhole = {}
value = distance.group(1).strip("'")
blackhole["distance"] = int(value)
elif bh:
blackhole = {}
blackhole["type"] = "blackhole"
return blackhole
def get_afi(self, address):
route_type = get_route_type(address)
if route_type == "route":
return "ipv4"
elif route_type == "route6":
return "ipv6"
def parse_next_hop(self, conf):
nh_list = None
nh_info = {}
if conf:
nh_list = []
hop_list = [
match[0] if match[0] else match[1]
for match in findall(r"^.*next-hop(.+)|(\s+interface.+)$", conf, M)
]
if hop_list:
for hop in hop_list:
distance = search(r"^.*distance (.\S+)", hop, M)
interface = search(r"^.*interface (.\S+)", hop, M)
dis = hop.find("disable")
hop_info = hop.split(" ")
if interface:
nh_info["interface"] = interface.group(1).strip("'")
else:
nh_info = {"forward_router_address": hop_info[1].strip("'")}
if distance:
value = distance.group(1).strip("'")
nh_info["admin_distance"] = int(value)
elif dis >= 1:
nh_info["enabled"] = False
for element in nh_list:
if element.get("forward_router_address") == nh_info.get(
"forward_router_address",
):
if "admin_distance" in nh_info.keys():
element["admin_distance"] = nh_info["admin_distance"]
if "enabled" in nh_info.keys():
element["enabled"] = nh_info["enabled"]
nh_info = None
if nh_info is not None:
nh_list.append(nh_info)
nh_info = {}
return nh_list
diff --git a/plugins/module_utils/network/vyos/facts/vrf/vrf.py b/plugins/module_utils/network/vyos/facts/vrf/vrf.py
index 6c7b8918..74f46b45 100644
--- a/plugins/module_utils/network/vyos/facts/vrf/vrf.py
+++ b/plugins/module_utils/network/vyos/facts/vrf/vrf.py
@@ -1,227 +1,226 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The vyos vrf 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.
"""
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.vrf.vrf import VrfArgs
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_global.bgp_global import (
Bgp_globalFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2 import (
Ospfv2Facts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3 import (
Ospfv3Facts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import (
Static_routesFacts,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrf import (
VrfTemplate,
)
class VrfFacts(object):
"""The vyos vrf facts class"""
def __init__(self, module, subspec="config", options="options"):
self._module = module
self.argument_spec = VrfArgs.argument_spec
def get_config(self, connection):
return connection.get("show configuration commands | match 'set vrf'")
def get_config_set(self, data, connection):
"""To classify the configurations beased on vrf"""
config_dict = {}
for config_line in data.splitlines():
vrf_inst = re.search(r"set vrf name (\S+).*", config_line)
vrf_bta = re.search(r"set vrf bind-to-all", config_line)
if vrf_bta:
config_dict["bind_to_all"] = config_dict.get("bind_to_all", "") + config_line + "\n"
if vrf_inst:
config_dict[vrf_inst.group(1)] = (
config_dict.get(vrf_inst.group(1), "") + config_line + "\n"
)
return list(config_dict.values())
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Vrf network resource
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
facts = {}
objs = []
if not data:
data = self.get_config(connection)
vrf_facts = {}
instances = []
vrf_parser = VrfTemplate(lines=[], module=self._module)
resources = self.get_config_set(data, connection)
for resource in resources:
vrf_parser = VrfTemplate(
lines=resource.split("\n"),
module=self._module,
)
objs = vrf_parser.parse()
if objs and "protocols" in resource:
protocol_lines = []
for line in resource.strip().split("\n"):
if "protocols" in line:
idx = line.index("protocols")
protocol_lines.append("set " + line[idx:])
objs["protocols"] = self._parse_protocols("\n".join(protocol_lines))
if objs:
if "bind_to_all" in objs:
vrf_facts.update(objs)
if "name" in objs:
instances.append(self._normalise_instance(objs))
if instances:
vrf_facts.update({"instances": instances})
ansible_facts["ansible_network_resources"].pop("vrf_facts", None)
facts = {"vrf": []}
params = utils.remove_empties(
vrf_parser.validate_config(
self.argument_spec,
{"config": vrf_facts},
redact=True,
),
)
if not resources:
params["config"].pop("bind_to_all", None)
if params.get("config"):
facts["vrf"] = params["config"]
ansible_facts["ansible_network_resources"].update(facts)
return ansible_facts
def _normalise_instance(self, instance):
n_inst = instance.copy()
af_map = {}
for af in instance.get("address_family", []):
afi = af.get("afi")
if not afi:
continue
if afi not in af_map:
af_map[afi] = {"afi": afi}
for k, v in af.items():
if k == "afi":
continue
elif k == "route_maps":
if "route_maps" not in af_map[afi]:
af_map[afi]["route_maps"] = []
af_map[afi]["route_maps"].extend(v)
else:
af_map[afi][k] = v
for afi_data in af_map.values():
if "route_maps" in afi_data:
seen = []
deduped = []
for item in afi_data["route_maps"]:
if item not in seen:
seen.append(item)
deduped.append(item)
afi_data["route_maps"] = deduped
n_inst["address_family"] = list(af_map.values())
return n_inst
def _parse_protocols(self, protocols):
"""Parse protocols and return a dictionary"""
protocol_chunks = {}
parsed_protocols = {}
for line in protocols.split("\n"):
parts = line.split()
if len(parts) > 2 and parts[0] == "set" and parts[1] == "protocols":
protocol = parts[2]
protocol_chunks.setdefault(protocol, []).append(line)
protocol_strings = {proto: "\n".join(lines) for proto, lines in protocol_chunks.items()}
for protocol_name, protocol_string in protocol_strings.items():
protocol_dict = {}
if protocol_name == "bgp":
bgp_module = Bgp_globalFacts(self._module)
protocol_dict = bgp_module.populate_facts(
connection=self._module._connection,
ansible_facts={"ansible_network_resources": {}},
data=protocol_string,
)
parsed_protocols[protocol_name] = list(
protocol_dict.get("ansible_network_resources").values(),
)[0]
elif protocol_name == "ospf":
ospf_module = Ospfv2Facts(self._module)
protocol_dict = ospf_module.populate_facts(
connection=self._module._connection,
ansible_facts={"ansible_network_resources": {}},
data=protocol_string,
)
parsed_protocols[protocol_name] = list(
protocol_dict.get("ansible_network_resources").values(),
)[0]
elif protocol_name == "ospfv3":
ospfv3_module = Ospfv3Facts(self._module)
protocol_dict = ospfv3_module.populate_facts(
connection=self._module._connection,
ansible_facts={"ansible_network_resources": {}},
data=protocol_string,
)
parsed_protocols[protocol_name] = list(
protocol_dict.get("ansible_network_resources").values(),
)[0]
elif protocol_name == "static":
static_routes_module = Static_routesFacts(self._module)
protocol_dict = static_routes_module.populate_facts(
connection=self._module._connection,
ansible_facts={"ansible_network_resources": {}},
data=protocol_string,
)
parsed_protocols[protocol_name] = list(
protocol_dict.get("ansible_network_resources").values(),
)[0]
else:
self._module.fail_json(msg="The protocol is not supported" + protocol_name)
return parsed_protocols
diff --git a/plugins/module_utils/network/vyos/rm_templates/bgp_address_family.py b/plugins/module_utils/network/vyos/rm_templates/bgp_address_family.py
index f8f86cd2..6c945151 100644
--- a/plugins/module_utils/network/vyos/rm_templates/bgp_address_family.py
+++ b/plugins/module_utils/network/vyos/rm_templates/bgp_address_family.py
@@ -1,1450 +1,1449 @@
# -*- coding: utf-8 -*-
# Copyright 2021 Red Hat
# 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
"""
The Bgp_address_family parser templates file. This contains
a list of parser definitions and associated functions that
facilitates both facts gathering and native command generation for
the given network resource.
"""
import re
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
NetworkTemplate,
)
def _tmplt_bgp_af_aggregate_address(config_data):
afi = config_data["address_family"]["afi"] + "-unicast"
command = "protocols bgp {as_number} address-family ".format(**config_data)
config_data = config_data["address_family"]
if config_data["aggregate_address"].get("as_set"):
command += afi + " aggregate-address {prefix} as-set".format(
**config_data["aggregate_address"],
)
if config_data["aggregate_address"].get("summary_only"):
command += afi + " aggregate-address {prefix} summary-only".format(
**config_data["aggregate_address"],
)
return command
def _tmplt_bgp_af_redistribute(config_data):
afi = config_data["address_family"]["afi"] + "-unicast"
command = "protocols bgp {as_number} address-family ".format(**config_data)
config_data = config_data["address_family"]["redistribute"]
command += afi + " redistribute {protocol}".format(**config_data)
if config_data.get("metric"):
command += " metric {metric}".format(**config_data)
elif config_data.get("route_map"):
command += " route-map {route_map}".format(**config_data)
elif config_data.get("table"):
command += " table {table}".format(**config_data)
return command
def _tmplt_bgp_af_redistribute_delete(config_data):
afi = config_data["address_family"]["afi"] + "-unicast"
command = "protocols bgp {as_number} address-family ".format(**config_data)
config_data = config_data["address_family"]["redistribute"]
command += afi + " redistribute {protocol}".format(**config_data)
if config_data.get("metric"):
command += " metric"
elif config_data.get("route_map"):
command += " route-map"
elif config_data.get("table"):
command += " table"
return command
def _tmplt_bgp_af_neighbor_distribute_list(config_data):
command = []
afi = config_data["neighbors"]["address_family"]["afi"] + "-unicast"
cmd = "protocols bgp {as_number} neighbor ".format(**config_data)
cmd += "{neighbor_address} address-family ".format(**config_data["neighbors"])
config_data = config_data["neighbors"]["address_family"]
for list_el in config_data["distribute_list"]:
command.append(
cmd + afi + " distribute-list " + list_el["action"] + " " + str(list_el["acl"]),
)
return command
def _tmplt_bgp_af_neighbor_route_map(config_data):
command = []
afi = config_data["neighbors"]["address_family"]["afi"] + "-unicast"
cmd = "protocols bgp {as_number} neighbor ".format(**config_data)
cmd += "{neighbor_address} address-family ".format(**config_data["neighbors"])
config_data = config_data["neighbors"]["address_family"]
for list_el in config_data["route_map"]:
command.append(
cmd + afi + " route-map " + list_el["action"] + " " + str(list_el["route_map"]),
)
return command
def _tmplt_bgp_af_neighbor_prefix_list(config_data):
command = []
afi = config_data["neighbors"]["address_family"]["afi"] + "-unicast"
cmd = "protocols bgp {as_number} neighbor ".format(**config_data)
cmd += "{neighbor_address} address-family ".format(**config_data["neighbors"])
config_data = config_data["neighbors"]["address_family"]
for list_el in config_data["prefix_list"]:
command.append(
cmd + afi + " prefix-list " + list_el["action"] + " " + str(list_el["prefix_list"]),
)
return command
def _tmplt_bgp_af_neighbor_filter_list(config_data):
command = []
afi = config_data["neighbors"]["address_family"]["afi"] + "-unicast"
cmd = "protocols bgp {as_number} neighbor ".format(**config_data)
cmd += "{neighbor_address} address-family ".format(**config_data["neighbors"])
config_data = config_data["neighbors"]["address_family"]
for list_el in config_data["filter_list"]:
command.append(
cmd + afi + " filter-list " + list_el["action"] + " " + str(list_el["path_list"]),
)
return command
def _tmplt_bgp_af_neighbor_attribute(config_data):
command = []
afi = config_data["neighbors"]["address_family"]["afi"] + "-unicast"
cmd = "protocols bgp {as_number} neighbor ".format(**config_data)
cmd += "{neighbor_address} address-family ".format(**config_data["neighbors"])
config_data = config_data["neighbors"]["address_family"]
for k in config_data["attribute_unchanged"].keys():
if config_data["attribute_unchanged"][k]:
k = re.sub("_", "-", k)
c = cmd + afi + " attribute-unchanged " + k
command.append(c)
return command
def _tmplt_bgp_af_neighbor_delete(config_data):
afi = config_data["neighbors"]["address_family"]["afi"] + "-unicast"
command = "protocols bgp {as_number} ".format(**config_data)
command += (
"neighbor {neighbor_address} address-family ".format(**config_data["neighbors"]) + afi
)
config_data = config_data["neighbors"]["address_family"]
if config_data.get("allowas_in"):
command += " allowas-in"
elif config_data.get("as_override"):
command += " as-override"
elif config_data.get("attribute_unchanged"):
command += " attribute-unchanged"
elif config_data.get("capability"):
command += " capability"
elif config_data.get("default_originate"):
command += " default-originate"
elif config_data.get("maximum_prefix"):
command += " maximum-prefix"
elif config_data.get("nexthop_local"):
command += " nexthop-local"
elif config_data.get("nexthop_self"):
command += " nexthop-self"
elif config_data.get("peer_group"):
command += " peer-group"
elif config_data.get("remote_private_as"):
command += " remote-private-as"
elif config_data.get("route_reflector_client"):
command += " route-reflector-client"
elif config_data.get("route_server_client"):
command += " route-server-client"
elif config_data.get("soft_reconfiguration"):
command += " soft-reconfiguration"
elif config_data.get("unsuppress_map"):
command += " unsuppress-map"
elif config_data.get("weight"):
command += " weight"
elif config_data.get("filter_list"):
command += " filter-list"
elif config_data.get("prefix_list"):
command += " prefix-list"
elif config_data.get("distribute_list"):
command += " distribute-list"
elif config_data.get("route_map"):
command += " route-map"
return command
def _tmplt_bgp_af_neighbor(config_data):
afi = config_data["neighbors"]["address_family"]["afi"] + "-unicast"
command = "protocols bgp {as_number} ".format(**config_data)
command += (
"neighbor {neighbor_address} address-family ".format(**config_data["neighbors"]) + afi
)
config_data = config_data["neighbors"]["address_family"]
if config_data.get("allowas_in"):
command += " allowas-in number {allowas_in}".format(**config_data)
elif config_data.get("as_override"):
command += " as-override"
elif config_data.get("capability"):
command += " capability "
if config_data["capability"].get("dynamic"):
command += "dynamic"
elif config_data["capability"].get("orf"):
command += " prefix-list {orf}".format(**config_data["capability"])
elif config_data.get("default_originate"):
command += " default-originate route-map {default_originate}".format(**config_data)
elif config_data.get("maximum_prefix"):
command += " maximum-prefix {maximum_prefix}".format(**config_data)
elif config_data.get("nexthop_local"):
command += " nexthop-local"
elif config_data.get("nexthop_self"):
command += " nexthop-self"
elif config_data.get("peer_group"):
command += " peer-group {peer_group}".format(**config_data)
elif config_data.get("remote_private_as"):
command += " remote-private-as"
elif config_data.get("route_reflector_client"):
command += " route-reflector-client"
elif config_data.get("route_server_client"):
command += " route-server-client"
elif config_data.get("soft_reconfiguration"):
command += " soft-reconfiguration inbound"
elif config_data.get("unsuppress_map"):
command += " unsuppress-map {unsuppress_map}".format(**config_data)
elif config_data.get("weight"):
command += " weight {weight}".format(**config_data)
return command
def _tmplt_bgp_af_network(config_data):
afi = config_data["address_family"]["afi"] + "-unicast"
command = "protocols bgp {as_number} address-family ".format(**config_data)
config_data = config_data["address_family"]["networks"]
command += afi + " network {prefix}".format(**config_data)
if config_data.get("backdoor"):
command += " backdoor"
elif config_data.get("route_map"):
command += " route-map {route_map}".format(**config_data)
return command
def _tmplt_bgp_af_network_delete(config_data):
afi = config_data["address_family"]["afi"] + "-unicast"
command = "protocols bgp {as_number} address-family ".format(**config_data)
config_data = config_data["address_family"]["networks"]
command += afi + " network {prefix}".format(**config_data)
if config_data.get("backdoor"):
command += " backdoor"
elif config_data.get("route_map"):
command += " route_map"
return command
class Bgp_address_familyTemplate(NetworkTemplate):
def __init__(self, lines=None, module=None):
prefix = {"set": "set", "remove": "delete"}
super(Bgp_address_familyTemplate, self).__init__(
lines=lines,
tmplt=self,
prefix=prefix,
module=module,
)
# fmt: off
PARSERS = [
{
"name": "address_family",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P\d+)
\s+address-family
\s+(?P\S+)-unicast
*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} address-family {{ address_family.afi }}-unicast",
"compval": "as_number",
"result": {
"as_number": "{{ as_num }}",
"address_family": {
"{{ afi }}": {
"afi": "{{ afi }}",
},
},
},
},
{
"name": "aggregate_address",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P\d+)
\s+address-family
\s+(?P\S+)-unicast
\s+aggregate-address
\s+(?P\S+)
\s*(?Pas-set)*
\s*(?Psummary-only)*
$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_aggregate_address,
"remval": "protocols bgp {{ as_number }} address-family {{ address_family.afi }}-unicast aggregate-address" +
" {{ address_family.aggregate_address.prefix }}",
"compval": "address_family.aggregate_address",
"result": {
"as_number": "{{ as_num }}",
"address_family": {
"{{ afi }}": {
"afi": "{{ afi }}",
"aggregate_address": [
{
"prefix": "{{ address }}",
"as_set": "{{ True if as_set is defined }}",
"summary_only": "{{ True if summary_only is defined }}",
},
],
},
},
},
},
{
"name": "network",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P\d+)
\s+address-family
\s+(?P\S+)-unicast
\s+network
\s+(?P\S+)
$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_network,
"remval": "protocols bgp {{ as_number }} address-family {{ address_family.afi }}-unicast network {{ address_family.networks.prefix }}",
"compval": "address_family.networks.prefix",
"result": {
"as_number": "{{ as_num }}",
"address_family": {
"{{ afi }}": {
"afi": "{{ afi }}",
"networks": [
{
"prefix": "{{ address }}",
},
],
},
},
},
},
{
"name": "network.backdoor",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P\d+)
\s+address-family
\s+(?P\S+)-unicast
\s+network
\s+(?P\S+)
\s+backdoor
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_network,
"remval": _tmplt_bgp_af_network_delete,
"compval": "address_family.networks.backdoor",
"result": {
"as_number": "{{ as_num }}",
"address_family": {
"{{ afi }}": {
"afi": "{{ afi }}",
"networks": [
{
"prefix": "{{ address }}",
"backdoor": "{{ True }}",
},
],
},
},
},
},
{
"name": "network.path_limit",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P\d+)
\s+address-family
\s+(?P\S+)-unicast
\s+network
\s+(?P\S+)
\s+path-limit
\s+(?P\S+)
*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_network,
"remval": _tmplt_bgp_af_network_delete,
"compval": "address_family.networks.path_limit",
"result": {
"as_number": "{{ as_num }}",
"address_family": {
"{{ afi }}": {
"afi": "{{ afi }}",
"networks": [
{
"prefix": "{{ address }}",
"path_limit": "{{ limit|int }}",
},
],
},
},
},
},
{
"name": "network.route_map",
"getval": re.compile(
r"""
^set
\s+protocols
\s+bgp
\s+(?P\d+)
\s+address-family
\s+(?P\S+)-unicast
\s+network
\s+(?P\S+)
\s+route-map
\s+(?P