summaryrefslogtreecommitdiff
path: root/plugins/module_utils
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/module_utils')
-rw-r--r--plugins/module_utils/network/vyos/argspec/firewall_rules/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py263
-rw-r--r--plugins/module_utils/network/vyos/argspec/static_routes/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py99
-rw-r--r--plugins/module_utils/network/vyos/config/firewall_rules/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py1034
-rw-r--r--plugins/module_utils/network/vyos/config/static_routes/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/config/static_routes/static_routes.py627
-rw-r--r--plugins/module_utils/network/vyos/facts/facts.py9
-rw-r--r--plugins/module_utils/network/vyos/facts/firewall_rules/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py380
-rw-r--r--plugins/module_utils/network/vyos/facts/static_routes/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/facts/static_routes/static_routes.py181
-rw-r--r--plugins/module_utils/network/vyos/utils/utils.py51
14 files changed, 2643 insertions, 1 deletions
diff --git a/plugins/module_utils/network/vyos/argspec/firewall_rules/__init__.py b/plugins/module_utils/network/vyos/argspec/firewall_rules/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/firewall_rules/__init__.py
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
new file mode 100644
index 00000000..a018cc0b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py
@@ -0,0 +1,263 @@
+#
+# -*- 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"],
+ "type": "str",
+ },
+ "description": {"type": "str"},
+ "enable_default_log": {"type": "bool"},
+ "name": {"type": "str"},
+ "rules": {
+ "elements": "dict",
+ "options": {
+ "action": {
+ "choices": [
+ "drop",
+ "reject",
+ "accept",
+ "inspect",
+ ],
+ "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",
+ },
+ "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",
+ },
+ "ipsec": {
+ "choices": ["match-ipsec", "match-none"],
+ "type": "str",
+ },
+ "limit": {
+ "options": {
+ "burst": {"type": "int"},
+ "rate": {
+ "options": {
+ "number": {"type": "int"},
+ "unit": {"type": "str"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "number": {"required": True, "type": "int"},
+ "p2p": {
+ "elements": "dict",
+ "options": {
+ "application": {
+ "choices": [
+ "all",
+ "applejuice",
+ "bittorrent",
+ "directconnect",
+ "edonkey",
+ "gnutella",
+ "kazaa",
+ ],
+ "type": "str",
+ }
+ },
+ "type": "list",
+ },
+ "protocol": {"type": "str"},
+ "recent": {
+ "options": {
+ "count": {"type": "int"},
+ "time": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ "source": {
+ "options": {
+ "address": {"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",
+ },
+ "tcp": {
+ "options": {"flags": {"type": "str"}},
+ "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/static_routes/__init__.py b/plugins/module_utils/network/vyos/argspec/static_routes/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/static_routes/__init__.py
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
new file mode 100644
index 00000000..8ecd955a
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py
@@ -0,0 +1,99 @@
+#
+# -*- 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": {
+ "required": True,
+ "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/config/firewall_rules/__init__.py b/plugins/module_utils/network/vyos/config/firewall_rules/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/firewall_rules/__init__.py
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
new file mode 100644
index 00000000..e58593f4
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py
@@ -0,0 +1,1034 @@
+#
+# -*- 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
+
+__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 (
+ to_list,
+ remove_empties,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import (
+ Facts,
+)
+from ansible.module_utils.six import iteritems
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
+ list_diff_want_only,
+)
+
+
+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()
+
+ 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(existing_firewall_rules_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_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"]
+ 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:
+ for h in have:
+ r_sets = self._get_r_sets(h)
+ for rs in r_sets:
+ w = self.search_r_sets_in_have(want, rs["name"], "r_list")
+ commands.extend(
+ self._add_r_sets(h["afi"], rs, 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 in have:
+ r_sets = self._get_r_sets(h)
+ for rs in r_sets:
+ w = self.search_r_sets_in_have(want, rs["name"], "r_list")
+ if not w:
+ commands.append(
+ self._compute_command(
+ h["afi"], rs["name"], remove=True
+ )
+ )
+ else:
+ commands.extend(
+ self._add_r_sets(h["afi"], rs, w, 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:
+ r_sets = self._get_r_sets(w)
+ for rs in r_sets:
+ h = self.search_r_sets_in_have(have, rs["name"], "r_list")
+ 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:
+ h = self.search_r_sets_in_have(
+ have, rs["name"], "r_list"
+ )
+ if h:
+ w_rules = rs.get("rules") or []
+ h_rules = h.get("rules") or []
+ if w_rules and h_rules:
+ for rule in w_rules:
+ if self.search_r_sets_in_have(
+ h_rules, rule["number"], "rules"
+ ):
+ commands.append(
+ self._add_r_base_attrib(
+ w["afi"],
+ rs["name"],
+ "number",
+ rule,
+ opr=False,
+ )
+ )
+ else:
+ commands.append(
+ self._compute_command(
+ w["afi"], h["name"], remove=True
+ )
+ )
+ elif have:
+ for h in have:
+ if h["afi"] == w["afi"]:
+ commands.append(
+ self._compute_command(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(afi=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", "enable_default_log")
+ h_rs = {}
+ h_rules = {}
+ w_rs = deepcopy(remove_empties(want))
+ w_rules = w_rs.pop("rules", None)
+ if have:
+ h_rs = deepcopy(remove_empties(have))
+ h_rules = h_rs.pop("rules", None)
+ if w_rs:
+ for key, val in iteritems(w_rs):
+ 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(
+ afi, want["name"], key, w_rs
+ )
+ )
+ else:
+ commands.append(
+ self._add_rs_base_attrib(
+ afi, want["name"], 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(
+ afi, want["name"], key, w_rs, opr
+ )
+ )
+ elif not (h_rs and self._in_target(h_rs, key)):
+ commands.append(
+ self._add_rs_base_attrib(
+ afi, want["name"], key, w_rs, opr
+ )
+ )
+ commands.extend(
+ self._add_rules(afi, want["name"], 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, afi, name, w_rules, h_rules, opr=True):
+ """
+ This function forms the set/delete commands based on the 'opr' type
+ for rules attributes.
+ :param want: desired config.
+ :param have: target config.
+ :return: generated commands list.
+ """
+ commands = []
+ l_set = (
+ "ipsec",
+ "action",
+ "number",
+ "protocol",
+ "fragment",
+ "disabled",
+ "description",
+ )
+ if w_rules:
+ for w in w_rules:
+ cmd = self._compute_command(afi, name, w["number"], opr=opr)
+ h = self.search_r_sets_in_have(
+ h_rules, w["number"], type="rules"
+ )
+ for key, val in iteritems(w):
+ if val:
+ if (
+ opr
+ and key in l_set
+ and not (h and self._is_w_same(w, h, key))
+ ):
+ if key == "disabled":
+ if not (
+ not val
+ and (not h or key not in h or not h[key])
+ ):
+ commands.append(
+ self._add_r_base_attrib(
+ afi, name, key, w
+ )
+ )
+ else:
+ commands.append(
+ self._add_r_base_attrib(afi, name, key, w)
+ )
+ elif not opr:
+ if key == "number" and self._is_del(l_set, h):
+ commands.append(
+ self._add_r_base_attrib(
+ afi, name, key, w, opr=opr
+ )
+ )
+ continue
+ elif (
+ key == "disabled"
+ and val
+ and h
+ and (key not in h or not h[key])
+ ):
+ commands.append(
+ self._add_r_base_attrib(
+ afi, name, key, w, opr=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(
+ afi, name, key, w, opr=opr
+ )
+ )
+ elif key == "p2p":
+ commands.extend(self._add_p2p(key, w, h, cmd, 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 == "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)
+ )
+ return commands
+
+ def _add_p2p(self, attr, w, h, cmd, opr):
+ """
+ This function forms the set/delete commands based on the 'opr' type
+ for p2p applications attributes.
+ :param want: desired config.
+ :param have: target config.
+ :return: generated commands list.
+ """
+ commands = []
+ have = []
+ if w:
+ want = w.get(attr) or []
+ if h:
+ have = h.get(attr) or []
+ if want:
+ if opr:
+ applications = list_diff_want_only(want, have)
+ for app in applications:
+ commands.append(
+ cmd + (" " + attr + " " + app["application"])
+ )
+ elif not opr and have:
+ applications = list_diff_want_only(want, have)
+ for app in applications:
+ commands.append(
+ cmd + (" " + attr + " " + app["application"])
+ )
+ 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 iteritems(w[attr]):
+ if (
+ opr
+ and item in l_set
+ and not (
+ h_state and self._is_w_same(w[attr], h_state, item)
+ )
+ ):
+ commands.append(
+ cmd
+ + (
+ " "
+ + attr
+ + " "
+ + item
+ + " "
+ + self._bool_to_str(val)
+ )
+ )
+ elif (
+ not opr
+ and item in l_set
+ and not (h_state and self._in_target(h_state, item))
+ ):
+ commands.append(cmd + (" " + attr + " " + item))
+ 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 iteritems(w[attr]):
+ 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 iteritems(w[attr]):
+ 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 "ipv6-name" in cmd:
+ commands.append(
+ cmd
+ + (" " + "icmpv6" + " " + "type" + " " + val)
+ )
+ else:
+ commands.append(
+ cmd
+ + (
+ " "
+ + attr
+ + " "
+ + item.replace("_", "-")
+ + " "
+ + val
+ )
+ )
+ else:
+ commands.append(
+ cmd + (" " + attr + " " + item + " " + str(val))
+ )
+ elif (
+ not opr
+ and item in l_set
+ and not (h_icmp and self._in_target(h_icmp, item))
+ ):
+ commands.append(cmd + (" " + attr + " " + item))
+ 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 iteritems(w[attr]):
+ 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(self, attr, w, h, cmd, opr):
+ """
+ This function forms the commands for 'tcp' 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_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:
+ if opr and not (
+ h_tcp and self._is_w_same(w[attr], h[attr], key)
+ ):
+ commands.append(
+ cmd + (" " + attr + " " + key + " " + flags)
+ )
+ if not opr and not (
+ h_tcp and self._is_w_same(w[attr], h[attr], key)
+ ):
+ commands.append(
+ cmd + (" " + attr + " " + key + " " + flags)
+ )
+ 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:
+ if h and key in h[attr].keys():
+ h_group = h[attr].get(key) or {}
+ for item, val in iteritems(group):
+ 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_r_sets_in_have(self, have, w_name, type="rule_sets"):
+ """
+ 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:
+ key = "name"
+ if type == "rules":
+ key = "number"
+ for r in have:
+ if r[key] == w_name:
+ return r
+ elif type == "r_list":
+ for h in have:
+ r_sets = self._get_r_sets(h)
+ for rs in r_sets:
+ if rs[key] == w_name:
+ return rs
+ else:
+ for rs in have:
+ if rs[key] == w_name:
+ return rs
+ return None
+
+ def _get_r_sets(self, item, type="rule_sets"):
+ """
+ This function returns the list of rule-sets/rules.
+ :param item: config dictionary.
+ :param type: rule_sets/rule/r_list.
+ :return: list of rule-sets/rules.
+ """
+ rs_list = []
+ r_sets = item[type]
+ if r_sets:
+ for rs in r_sets:
+ rs_list.append(rs)
+ return rs_list
+
+ def _compute_command(
+ self,
+ afi,
+ name=None,
+ number=None,
+ attrib=None,
+ value=None,
+ remove=False,
+ opr=True,
+ ):
+ """
+ This function construct the add/delete command based on passed attributes.
+ :param afi: address type.
+ :param name: rule-set name.
+ :param number: rule-number.
+ :param attrib: attribute name.
+ :param value: value.
+ :param remove: True if delete command needed to be construct.
+ :param opr: opeeration flag.
+ :return: generated command.
+ """
+ if remove or not opr:
+ cmd = "delete firewall " + self._get_fw_type(afi)
+ else:
+ cmd = "set firewall " + self._get_fw_type(afi)
+ if name:
+ cmd += " " + name
+ if number:
+ cmd += " rule " + str(number)
+ if attrib:
+ cmd += " " + attrib.replace("_", "-")
+ if (
+ value
+ and opr
+ and attrib != "enable_default_log"
+ and attrib != "disabled"
+ ):
+ cmd += " '" + str(value) + "'"
+ return cmd
+
+ def _add_r_base_attrib(self, afi, name, attr, rule, opr=True):
+ """
+ This function forms the command for 'rules' attributes which doesn't
+ have further sub attributes.
+ :param afi: address type.
+ :param name: rule-set name
+ :param attrib: attribute name
+ :param rule: rule config dictionary.
+ :param opr: True/False.
+ :return: generated command.
+ """
+ if attr == "number":
+ command = self._compute_command(
+ afi=afi, name=name, number=rule["number"], opr=opr
+ )
+ else:
+ command = self._compute_command(
+ afi=afi,
+ name=name,
+ number=rule["number"],
+ attrib=attr,
+ value=rule[attr],
+ opr=opr,
+ )
+ return command
+
+ def _add_rs_base_attrib(self, afi, name, attrib, rule, opr=True):
+ """
+
+ This function forms the command for 'rule-sets' attributes which doesn't
+ have further sub attributes.
+ :param afi: address type.
+ :param name: rule-set name
+ :param attrib: attribute name
+ :param rule: rule config dictionary.
+ :param opr: True/False.
+ :return: generated command.
+ """
+ command = self._compute_command(
+ afi=afi, name=name, 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.
+ """
+ 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 nexist 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_base_attrib(self, key):
+ """
+ This function checks whether key is present in predefined
+ based attribute set.
+ :param key:
+ :return: True/False.
+ """
+ r_set = (
+ "p2p",
+ "ipsec",
+ "action",
+ "fragment",
+ "protocol",
+ "disabled",
+ "description",
+ "mac_address",
+ "default_action",
+ "enable_default_log",
+ )
+ return True if key in r_set else False
diff --git a/plugins/module_utils/network/vyos/config/static_routes/__init__.py b/plugins/module_utils/network/vyos/config/static_routes/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/static_routes/__init__.py
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
new file mode 100644
index 00000000..e93d4ee3
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/static_routes/static_routes.py
@@ -0,0 +1,627 @@
+#
+# -*- 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 (
+ to_list,
+ dict_diff,
+ remove_empties,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import (
+ Facts,
+)
+from ansible.module_utils.six import iteritems
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
+ get_route_type,
+ get_lst_diff_for_dicts,
+ get_lst_same_for_dicts,
+ dict_delete,
+)
+
+
+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 == "merged" or self.state == "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 iteritems(want):
+ 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
+ )
+ )
+ for r in routes:
+ h_route = self.search_route_in_have(have, r["dest"])
+ if h_route:
+ commands.extend(
+ self._render_updates(r, h_route, opr=False)
+ )
+ 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 iteritems(want):
+ 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 iteritems(updates):
+ 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"]
+ + " "
+ + element,
+ value=str(hop[element]),
+ opr=opr,
+ )
+ )
+ elif element == "interface":
+ commands.append(
+ self._compute_command(
+ dest=want["dest"],
+ key="next-hop",
+ attrib=hop["forward_router_address"]
+ + " "
+ + element,
+ 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 iteritems(updates):
+ 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"]
+ + " "
+ + element,
+ value=str(hop[element]),
+ remove=True,
+ )
+ )
+ elif element == "interface":
+ commands.append(
+ self._compute_command(
+ dest=want["dest"],
+ key="next-hop",
+ attrib=hop["forward_router_address"]
+ + " "
+ + element,
+ 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 iteritems(updates):
+ 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/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py
index b5816c20..8f0a3bb6 100644
--- a/plugins/module_utils/network/vyos/facts/facts.py
+++ b/plugins/module_utils/network/vyos/facts/facts.py
@@ -27,6 +27,12 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_
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.firewall_rules.firewall_rules import (
+ Firewall_rulesFacts,
+)
+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.legacy.base import (
Default,
Neighbors,
@@ -41,6 +47,8 @@ FACT_RESOURCE_SUBSETS = dict(
lag_interfaces=Lag_interfacesFacts,
lldp_global=Lldp_globalFacts,
lldp_interfaces=Lldp_interfacesFacts,
+ static_routes=Static_routesFacts,
+ firewall_rules=Firewall_rulesFacts,
)
@@ -72,5 +80,4 @@ class Facts(FactsBase):
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_rules/__init__.py b/plugins/module_utils/network/vyos/facts/firewall_rules/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/firewall_rules/__init__.py
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
new file mode 100644
index 00000000..971ea6fe
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py
@@ -0,0 +1,380 @@
+#
+# -*- 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
+
+__metaclass__ = type
+
+from re import findall, search, M
+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.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 = []
+ v6_rules = findall(
+ r"^set firewall ipv6-name (?:\'*)(\S+)(?:\'*)", data, M
+ )
+ v4_rules = findall(r"^set firewall name (?:\'*)(\S+)(?:\'*)", data, M)
+ if v6_rules:
+ config = self.get_rules(data, v6_rules, type="ipv6")
+ if config:
+ config = utils.remove_empties(config)
+ objs.append(config)
+ if v4_rules:
+ 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):
+ rule_regex = r" %s .+$" % 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 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", "enable_default_log"]
+ config = self.parse_attr(conf, a_lst, match)
+ if not config:
+ config = {}
+ 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",
+ "action",
+ "protocol",
+ "fragment",
+ "disabled",
+ "description",
+ ]
+ rule = self.parse_attr(conf, a_lst)
+ r_sub = {
+ "p2p": self.parse_p2p(conf),
+ "tcp": self.parse_tcp(conf, "tcp"),
+ "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"),
+ }
+ rule.update(r_sub)
+ return rule
+
+ def parse_p2p(self, conf):
+ """
+ This function forms the regex to fetch the 'p2p' with in
+ 'rules'
+ :param conf: configuration data.
+ :return: generated rule list configuration.
+ """
+ a_lst = []
+ applications = findall(r"p2p (?:\'*)(\d+)(?:\'*)", conf, M)
+ if applications:
+ app_lst = []
+ for r in set(applications):
+ obj = {"application": r.strip("'")}
+ app_lst.append(obj)
+ a_lst = sorted(app_lst, key=lambda i: i["application"])
+ return a_lst
+
+ 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, attrib=None):
+ """
+ This function triggers the parsing of 'tcp' attributes.
+ :param conf: configuration.
+ :param attrib: 'tcp'.
+ :return: generated config dictionary.
+ """
+ cfg_dict = self.parse_attr(conf, ["flags"], match=attrib)
+ return cfg_dict
+
+ 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(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.
+ """
+ a_lst = ["code", "type", "type_name"]
+ cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
+ 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_rate(self, conf, attrib=None):
+ """
+ This function triggers the parsing of 'rate' attributes.
+ :param conf: configuration.
+ :param attrib: 'rate'
+ :return: generated config dictionary.
+ """
+ a_lst = ["unit", "number"]
+ cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
+ 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 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
+ """
+ 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",
+ )
+ 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
diff --git a/plugins/module_utils/network/vyos/facts/static_routes/__init__.py b/plugins/module_utils/network/vyos/facts/static_routes/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/static_routes/__init__.py
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
new file mode 100644
index 00000000..00049475
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py
@@ -0,0 +1,181 @@
+#
+# -*- 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 re import findall, search, M
+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.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), 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
+ if conf:
+ nh_list = []
+ hop_list = findall(r"^.*next-hop (.+)", 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(" ")
+ nh_info = {
+ "forward_router_address": hop_info[0].strip("'")
+ }
+ if interface:
+ nh_info["interface"] = interface.group(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["forward_router_address"]
+ == nh_info["forward_router_address"]
+ ):
+ if "interface" in nh_info.keys():
+ element["interface"] = nh_info["interface"]
+ 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)
+ return nh_list
diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py
index 6504bcd9..402adfc9 100644
--- a/plugins/module_utils/network/vyos/utils/utils.py
+++ b/plugins/module_utils/network/vyos/utils/utils.py
@@ -8,6 +8,9 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.compat import (
+ ipaddress,
+)
def search_obj_in_list(name, lst, key="name"):
@@ -87,6 +90,27 @@ def get_lst_diff_for_dicts(want, have, lst):
return diff
+def get_lst_same_for_dicts(want, have, lst):
+ """
+ This function generates a list containing values
+ that are common for list in want and list in have dict
+ :param want: dict object to want
+ :param have: dict object to have
+ :param lst: list the comparison on
+ :return: new list object with values which are common in want and have.
+ """
+ diff = None
+ if want and have:
+ want_list = want.get(lst) or {}
+ have_list = have.get(lst) or {}
+ diff = [
+ i
+ for i in want_list and have_list
+ if i in have_list and i in want_list
+ ]
+ return diff
+
+
def list_diff_have_only(want_list, have_list):
"""
This function generated the list containing values
@@ -178,3 +202,30 @@ def is_dict_element_present(dict, key):
if item == key:
return True
return False
+
+
+def get_ip_address_version(address):
+ """
+ This function returns the version of IP address
+ :param address: IP address
+ :return:
+ """
+ try:
+ address = unicode(address)
+ except NameError:
+ address = str(address)
+ version = ipaddress.ip_address(address.split("/")[0]).version
+ return version
+
+
+def get_route_type(address):
+ """
+ This function returns the route type based on IP address
+ :param address:
+ :return:
+ """
+ version = get_ip_address_version(address)
+ if version == 6:
+ return "route6"
+ elif version == 4:
+ return "route"