diff options
Diffstat (limited to 'plugins/module_utils')
28 files changed, 2822 insertions, 169 deletions
diff --git a/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py b/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py index 3542cb19..fd8b6123 100644 --- a/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py +++ b/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py @@ -61,8 +61,17 @@ class InterfacesArgs(object): # pylint: disable=R0903 }, "type": "list", }, + "running_config": {"type": "str"}, "state": { - "choices": ["merged", "replaced", "overridden", "deleted"], + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "parsed", + "gathered", + ], "default": "merged", "type": "str", }, 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 91434e4b..2f1dfe45 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 @@ -25,7 +25,6 @@ The arg spec for the vyos_l3_interfaces module """ - from __future__ import absolute_import, division, print_function __metaclass__ = type @@ -73,8 +72,17 @@ class L3_interfacesArgs(object): # pylint: disable=R0903 }, "type": "list", }, + "running_config": {"type": "str"}, "state": { - "choices": ["merged", "replaced", "overridden", "deleted"], + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "gathered", + "parsed", + ], "default": "merged", "type": "str", }, 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 97c5d5a2..6cfdabf1 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 @@ -19,7 +19,6 @@ # builder template. # ############################################# - """ The arg spec for the vyos_lag_interfaces module """ @@ -72,8 +71,17 @@ class Lag_interfacesArgs(object): # pylint: disable=R0903 }, "type": "list", }, + "running_config": {"type": "str"}, "state": { - "choices": ["merged", "replaced", "overridden", "deleted"], + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "gathered", + "parsed", + ], "default": "merged", "type": "str", }, 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 84bbc00c..6205fd77 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 @@ -19,7 +19,6 @@ # builder template. # ############################################# - """ The arg spec for the vyos_lldp_global module """ @@ -48,8 +47,16 @@ class Lldp_globalArgs(object): # pylint: disable=R0903 }, "type": "dict", }, + "running_config": {"type": "str"}, "state": { - "choices": ["merged", "replaced", "deleted"], + "choices": [ + "merged", + "replaced", + "deleted", + "rendered", + "parsed", + "gathered", + ], "default": "merged", "type": "str", }, 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 2976fc09..109ea430 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 @@ -81,8 +81,17 @@ class Lldp_interfacesArgs(object): # pylint: disable=R0903 }, "type": "list", }, + "running_config": {"type": "str"}, "state": { - "choices": ["merged", "replaced", "overridden", "deleted"], + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "gathered", + "parsed", + ], "default": "merged", "type": "str", }, diff --git a/plugins/module_utils/network/vyos/argspec/ospfv2/__init__.py b/plugins/module_utils/network/vyos/argspec/ospfv2/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/ospfv2/__init__.py diff --git a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py new file mode 100644 index 00000000..275aaf36 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py @@ -0,0 +1,269 @@ +# +# -*- 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 +""" + + +class Ospfv2Args(object): # pylint: disable=R0903 + """The arg spec for the vyos_ospfv2 module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "options": { + "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", + }, + "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": {"type": "str"}, + }, + "type": "list", + }, + "plaintext_password": {"type": "str"}, + }, + "type": "dict", + }, + "dead_interval": {"type": "int"}, + "hello_interval": {"type": "int"}, + "retransmit_interval": {"type": "int"}, + "transmit_delay": {"type": "int"}, + }, + "type": "list", + }, + }, + "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": {"type": "list"}, + "passive_interface_exclude": {"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": {"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/__init__.py b/plugins/module_utils/network/vyos/argspec/ospfv3/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/ospfv3/__init__.py diff --git a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py new file mode 100644 index 00000000..66aaa8c4 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py @@ -0,0 +1,94 @@ +# +# -*- 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 +""" + + +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"}, + "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/config/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py index e58593f4..5c377410 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 @@ -235,28 +235,11 @@ class Firewall_rules(ConfigBase): 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 - ) + commands.append( + self._compute_command( + w["afi"], h["name"], remove=True ) + ) elif have: for h in have: if h["afi"] == w["afi"]: diff --git a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py index deb504c2..51bf98e3 100644 --- a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py +++ b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py @@ -13,7 +13,6 @@ 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, @@ -49,14 +48,14 @@ class Interfaces(ConfigBase): def __init__(self, module): super(Interfaces, self).__init__(module) - def get_interfaces_facts(self): + 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 + self.gather_subset, self.gather_network_resources, data=data ) interfaces_facts = facts["ansible_network_resources"].get("interfaces") if not interfaces_facts: @@ -72,25 +71,42 @@ class Interfaces(ConfigBase): commands = list() warnings = list() - existing_interfaces_facts = self.get_interfaces_facts() - commands.extend(self.set_config(existing_interfaces_facts)) - if commands: - if self._module.check_mode: - resp = self._connection.edit_config(commands, commit=False) - else: - resp = self._connection.edit_config(commands) - result["changed"] = True + if self.state in self.ACTION_STATES: + existing_interfaces_facts = self.get_interfaces_facts() + else: + existing_interfaces_facts = [] - result["commands"] = commands + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_interfaces_facts)) - if self._module._diff: - result["diff"] = resp["diff"] if result["changed"] else None + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True - changed_interfaces_facts = self.get_interfaces_facts() + 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 = [] - result["before"] = existing_interfaces_facts - if result["changed"]: - result["after"] = 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 @@ -118,19 +134,21 @@ class Interfaces(ConfigBase): to the desired configuration """ commands = [] - state = self._module.params["state"] - if state in ("merged", "replaced", "overridden") and not want: + 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( - state + self.state ) ) - if state == "overridden": + if self.state == "overridden": commands.extend(self._state_overridden(want=want, have=have)) - elif state == "deleted": + elif self.state == "deleted": if not want: for intf in have: commands.extend( @@ -146,12 +164,12 @@ class Interfaces(ConfigBase): obj_in_have = search_obj_in_list(name, have) if not obj_in_have: - obj_in_have = {"name": item["name"]} + obj_in_have = {"name": name} - elif state == "merged": + if self.state in ("merged", "rendered"): commands.extend(self._state_merged(item, obj_in_have)) - elif state == "replaced": + elif self.state == "replaced": commands.extend(self._state_replaced(item, obj_in_have)) return commands 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 a23e417f..122cc1eb 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 @@ -52,14 +52,14 @@ class L3_interfaces(ConfigBase): def __init__(self, module): super(L3_interfaces, self).__init__(module) - def get_l3_interfaces_facts(self): + 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 + self.gather_subset, self.gather_network_resources, data=data ) l3_interfaces_facts = facts["ansible_network_resources"].get( "l3_interfaces" @@ -78,25 +78,44 @@ class L3_interfaces(ConfigBase): warnings = list() commands = list() - existing_l3_interfaces_facts = self.get_l3_interfaces_facts() - commands.extend(self.set_config(existing_l3_interfaces_facts)) - if commands: - if self._module.check_mode: - resp = self._connection.edit_config(commands, commit=False) - else: - resp = self._connection.edit_config(commands) - result["changed"] = True + if self.state in self.ACTION_STATES: + existing_l3_interfaces_facts = self.get_l3_interfaces_facts() + else: + existing_l3_interfaces_facts = [] - result["commands"] = commands + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_l3_interfaces_facts)) - if self._module._diff: - result["diff"] = resp["diff"] if result["changed"] else None + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True - changed_l3_interfaces_facts = self.get_l3_interfaces_facts() + 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.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.get_l3_interfaces_facts( + data=running_config + ) + else: + changed_l3_interfaces_facts = [] - result["before"] = existing_l3_interfaces_facts - if result["changed"]: - result["after"] = 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 @@ -126,7 +145,10 @@ class L3_interfaces(ConfigBase): commands = [] state = self._module.params["state"] - if state in ("merged", "replaced", "overridden") and not want: + 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 @@ -154,7 +176,7 @@ class L3_interfaces(ConfigBase): if not obj_in_have: obj_in_have = {"name": item["name"]} - if state == "merged": + if state in ("merged", "rendered"): commands.extend(self._state_merged(item, obj_in_have)) elif state == "replaced": 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 2a9efd99..452670f1 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 @@ -56,14 +56,14 @@ class Lag_interfaces(ConfigBase): def __init__(self, module): super(Lag_interfaces, self).__init__(module) - def get_lag_interfaces_facts(self): + 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 + self.gather_subset, self.gather_network_resources, data=data ) lag_interfaces_facts = facts["ansible_network_resources"].get( "lag_interfaces" @@ -79,27 +79,47 @@ class Lag_interfaces(ConfigBase): :returns: The result from module execution """ result = {"changed": False} - commands = list() warnings = list() - existing_lag_interfaces_facts = self.get_lag_interfaces_facts() - commands.extend(self.set_config(existing_lag_interfaces_facts)) - if commands: - if self._module.check_mode: - resp = self._connection.edit_config(commands, commit=False) - else: - resp = self._connection.edit_config(commands) - result["changed"] = True + commands = list() + + if self.state in self.ACTION_STATES: + existing_lag_interfaces_facts = self.get_lag_interfaces_facts() + else: + existing_lag_interfaces_facts = [] - result["commands"] = commands + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_lag_interfaces_facts)) - if self._module._diff: - result["diff"] = resp["diff"] if result["changed"] else None + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True - changed_lag_interfaces_facts = self.get_lag_interfaces_facts() + 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 = [] - result["before"] = existing_lag_interfaces_facts - if result["changed"]: - result["after"] = 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 @@ -127,16 +147,18 @@ class Lag_interfaces(ConfigBase): to the desired configuration """ commands = [] - state = self._module.params["state"] - if state in ("merged", "replaced", "overridden") and not want: + 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( - state + self.state ) ) - if state == "overridden": + if self.state == "overridden": commands.extend(self._state_overridden(want, have)) - elif state == "deleted": + elif self.state == "deleted": if want: for want_item in want: name = want_item["name"] @@ -149,9 +171,9 @@ class Lag_interfaces(ConfigBase): for want_item in want: name = want_item["name"] obj_in_have = search_obj_in_list(name, have) - if state == "merged": + if self.state in ("merged", "rendered"): commands.extend(self._state_merged(want_item, obj_in_have)) - elif state == "replaced": + elif self.state == "replaced": commands.extend( self._state_replaced(want_item, obj_in_have) ) 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 010e96dd..c70d27ff 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 @@ -47,20 +47,20 @@ class Lldp_global(ConfigBase): def __init__(self, module): super(Lldp_global, self).__init__(module) - def get_lldp_global_facts(self): + 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 + 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 [] return lldp_global_facts def execute_module(self): @@ -70,22 +70,45 @@ class Lldp_global(ConfigBase): :returns: The result from module execution """ result = {"changed": False} - commands = list() warnings = list() + commands = list() + + if self.state in self.ACTION_STATES: + existing_lldp_global_facts = self.get_lldp_global_facts() + else: + existing_lldp_global_facts = [] - existing_lldp_global_facts = self.get_lldp_global_facts() - commands.extend(self.set_config(existing_lldp_global_facts)) - if commands: + 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 - result["commands"] = commands - changed_lldp_global_facts = self.get_lldp_global_facts() + 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 = [] - result["before"] = existing_lldp_global_facts - if result["changed"]: - result["after"] = 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 @@ -113,18 +136,17 @@ class Lldp_global(ConfigBase): to the desired configuration """ commands = [] - state = self._module.params["state"] - if state in ("merged", "replaced") and not want: + 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( - state + self.state ) ) - if state == "deleted": + if self.state == "deleted": commands.extend(self._state_deleted(want=None, have=have)) - elif state == "merged": + elif self.state in ("merged", "rendered"): commands.extend(self._state_merged(want=want, have=have)) - elif state == "replaced": + elif self.state == "replaced": commands.extend(self._state_replaced(want=want, have=have)) return commands 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 377fec9a..94e39c35 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 @@ -15,7 +15,6 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type - from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) @@ -54,14 +53,14 @@ class Lldp_interfaces(ConfigBase): def __init__(self, module): super(Lldp_interfaces, self).__init__(module) - def get_lldp_interfaces_facts(self): + 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 + self.gather_subset, self.gather_network_resources, data=data ) lldp_interfaces_facts = facts["ansible_network_resources"].get( "lldp_interfaces" @@ -77,26 +76,47 @@ class Lldp_interfaces(ConfigBase): :returns: The result from module execution """ result = {"changed": False} - commands = list() warnings = list() - existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts() - commands.extend(self.set_config(existing_lldp_interfaces_facts)) - if commands: - if self._module.check_mode: - resp = self._connection.edit_config(commands, commit=False) - else: - resp = self._connection.edit_config(commands) - result["changed"] = True + commands = list() + + if self.state in self.ACTION_STATES: + existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts() + else: + existing_lldp_interfaces_facts = [] - result["commands"] = commands + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_lldp_interfaces_facts)) - if self._module._diff: - result["diff"] = resp["diff"] if result["changed"] else None + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True - changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts() - result["before"] = existing_lldp_interfaces_facts - if result["changed"]: - result["after"] = changed_lldp_interfaces_facts + 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 @@ -124,16 +144,18 @@ class Lldp_interfaces(ConfigBase): to the desired configuration """ commands = [] - state = self._module.params["state"] - if state in ("merged", "replaced", "overridden") and not want: + 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( - state + self.state ) ) - if state == "overridden": + if self.state == "overridden": commands.extend(self._state_overridden(want=want, have=have)) - elif state == "deleted": + elif self.state == "deleted": if want: for item in want: name = item["name"] @@ -150,11 +172,11 @@ class Lldp_interfaces(ConfigBase): for want_item in want: name = want_item["name"] have_item = search_obj_in_list(name, have) - if state == "merged": + if self.state in ("merged", "rendered"): commands.extend( self._state_merged(want=want_item, have=have_item) ) - else: + if self.state == "replaced": commands.extend( self._state_replaced(want=want_item, have=have_item) ) @@ -243,7 +265,6 @@ class Lldp_interfaces(ConfigBase): lldp_name = want["name"] params = Lldp_interfaces.params - commands.extend(self._add_location(lldp_name, want, have)) for attrib in params: value = want[attrib] if value: diff --git a/plugins/module_utils/network/vyos/config/ospfv2/__init__.py b/plugins/module_utils/network/vyos/config/ospfv2/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/config/ospfv2/__init__.py diff --git a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py new file mode 100644 index 00000000..fd25c178 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py @@ -0,0 +1,949 @@ +# +# -*- 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 + +__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, + _in_target, + _is_w_same, + _bool_to_str, +) + + +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( + 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(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(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 iteritems(w): + 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 iteritems(want[attr]): + 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"] + 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: + 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 iteritems(w_item): + 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 iteritems(w_item): + 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 iteritems(w[key]): + 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 iteritems(w_area): + 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 + elif 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 iteritems(w_type[key]): + 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/__init__.py b/plugins/module_utils/network/vyos/config/ospfv3/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/config/ospfv3/__init__.py diff --git a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py new file mode 100644 index 00000000..acda3801 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py @@ -0,0 +1,464 @@ +# +# -*- 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 + +__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, + search_obj_in_list, +) +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 ( + _in_target, + _is_w_same, + _bool_to_str, +) + + +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 iteritems(w): + 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 iteritems(want[attr]): + 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", + } + leaf_dict = { + "redistribute": ("route_map", "route_type"), + "range": ("address", "advertise", "not_advertise"), + } + 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 iteritems(w_item): + 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 == "route_type" or ( + key == "address" + and "advertise" not in w_item + and "not-advertise" not in w_item + ): + if not val: + cmd = cmd.replace("set", "delete") + 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"): + 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 iteritems(w_area): + 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 + elif 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 + ) + ) + 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/static_routes/static_routes.py b/plugins/module_utils/network/vyos/config/static_routes/static_routes.py index e93d4ee3..b359dbba 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 @@ -160,7 +160,7 @@ class Static_routes(ConfigBase): 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": + 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)) @@ -253,12 +253,6 @@ class Static_routes(ConfigBase): 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): diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index ff3d0988..4c7b340d 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -40,6 +40,12 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firew 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.ospfv3.ospfv3 import ( + Ospfv3Facts, +) +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.legacy.base import ( Default, Neighbors, @@ -58,6 +64,8 @@ FACT_RESOURCE_SUBSETS = dict( firewall_rules=Firewall_rulesFacts, firewall_global=Firewall_globalFacts, firewall_interfaces=Firewall_interfacesFacts, + ospfv3=Ospfv3Facts, + ospfv2=Ospfv2Facts, ) 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 d1d62c23..3b99d347 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 @@ -10,7 +10,12 @@ 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, + unicode_literals, +) __metaclass__ = type 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 9201e5c6..90562947 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 @@ -12,6 +12,7 @@ based on the configuration. from __future__ import absolute_import, division, print_function __metaclass__ = type + from re import findall, search, M from copy import deepcopy @@ -59,30 +60,21 @@ class Lag_interfacesFacts(object): lag_regex = r" %s .+$" % lag cfg = findall(lag_regex, data, M) obj = self.render_config(cfg) - - output = connection.run_commands( - ["show interfaces bonding " + lag + " slaves"] - ) - lines = output[0].splitlines() members = [] member = {} - if len(lines) > 1: - for line in lines[2:]: - splitted_line = line.split() - - if len(splitted_line) > 1: - member["member"] = splitted_line[0] - members.append(member) - else: - members = [] - member = {} + + group_regex = r".*eth.* '%s'" % lag + g_cfg = findall(group_regex, data, M) + for item in g_cfg: + output = search("^set interfaces ethernet (\\S+)", item, M) + if output: + member["member"] = output.group(1).strip("'") + members.append(member) obj["name"] = lag.strip("'") if members: obj["members"] = members - if obj: objs.append(obj) - facts = {} if objs: facts["lag_interfaces"] = [] diff --git a/plugins/module_utils/network/vyos/facts/ospfv2/__init__.py b/plugins/module_utils/network/vyos/facts/ospfv2/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/ospfv2/__init__.py diff --git a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py new file mode 100644 index 00000000..d62fa9ab --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py @@ -0,0 +1,499 @@ +# +# -*- 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 + +__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.ospfv2.ospfv2 import ( + Ospfv2Args, +) + + +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_leaf_list( + conf, "passive-interface" + ) + config["redistribute"] = self.parse_attrib_list( + conf, "redistribute", "route_type" + ) + config["passive_interface_exclude"] = self.parse_leaf_list( + 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_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'") + 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 ( + "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/__init__.py b/plugins/module_utils/network/vyos/facts/ospfv3/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/ospfv3/__init__.py diff --git a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py new file mode 100644 index 00000000..457a9636 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py @@ -0,0 +1,213 @@ +# +# -*- 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 + +__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.ospfv3.ospfv3 import ( + Ospfv3Args, +) + + +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 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) + 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"], + "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/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 4635234c..7e0f3cc3 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -230,3 +230,40 @@ def get_route_type(address): return "route6" elif version == 4: return "route" + + +def _bool_to_str(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 _is_w_same(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(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 |