diff --git a/plugins/module_utils/network/vyos/argspec/vrrp/vrrp.py b/plugins/module_utils/network/vyos/argspec/vrrp/vrrp.py index 3d9ac26c..fb39b388 100644 --- a/plugins/module_utils/network/vyos/argspec/vrrp/vrrp.py +++ b/plugins/module_utils/network/vyos/argspec/vrrp/vrrp.py @@ -1,192 +1,192 @@ # -*- coding: utf-8 -*- # Copyright 2024 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type ############################################# # WARNING # ############################################# # # This file is auto generated by the # cli_rm_builder. # # Manually editing this file is not advised. # # To update the argspec make the desired changes # in the module docstring and re-run # cli_rm_builder. # ############################################# """ The arg spec for the vyos_vrrp module """ class VrrpArgs(object): # pylint: disable=R0903 """The arg spec for the vyos_vrrp module""" argument_spec = { "config": { "type": "dict", "required": False, "options": { "disable": {"type": "bool", "default": False}, "virtual_servers": { "type": "list", "elements": "dict", "options": { "name": {"type": "str", "required": True}, "address": {"type": "str"}, "algorithm": {"type": "str"}, "delay_loop": {"type": "int"}, "forward_method": {"type": "str", "choices": ["direct", "nat"]}, "fwmark": {"type": "str"}, "persistence_timeout": {"type": "str"}, "port": {"type": "int"}, "protocol": {"type": "str", "choices": ["tcp", "udp"]}, "real_server": { "type": "list", "elements": "dict", "options": { "address": {"type": "str", "required": True}, "port": {"type": "int"}, "connection_timeout": {"type": "int"}, "health_check_script": {"type": "str"}, }, }, }, }, "vrrp": { "type": "dict", "options": { "global_parameters": { "type": "dict", "options": { "garp": { "type": "dict", "options": { "interval": {"type": "int"}, "master_delay": {"type": "int"}, "master_refresh": {"type": "int"}, "master_refresh_repeat": {"type": "int"}, "master_repeat": {"type": "int"}, }, }, "startup_delay": {"type": "int"}, "version": {"type": "str"}, }, }, "groups": { "type": "list", "elements": "dict", "options": { "name": {"type": "str", "required": True}, "address": {"type": "str"}, "advertise_interval": {"type": "int"}, "authentication": { "type": "dict", "options": { "password": {"type": "str", "no_log": True}, "type": {"type": "str"}, }, }, "description": {"type": "str"}, "disable": {"type": "bool", "default": False}, "excluded_address": {"type": "list", "elements": "str"}, "garp": { "type": "dict", "options": { "interval": {"type": "int"}, "master_delay": {"type": "int"}, "master_refresh": {"type": "int"}, "master_refresh_repeat": {"type": "int"}, "master_repeat": {"type": "int"}, }, }, "health_check": { "type": "dict", "options": { "failure_count": {"type": "int"}, "interval": {"type": "int"}, "ping": {"type": "str"}, "script": {"type": "str"}, }, }, "hello_source_address": {"type": "str"}, "interface": {"type": "str"}, "no_preempt": {"type": "bool"}, "peer_address": {"type": "str"}, "preempt_delay": {"type": "int"}, "priority": {"type": "int"}, "rfc3768_compatibility": {"type": "bool"}, "track": { "type": "dict", "options": { "exclude_vrrp_interface": {"type": "bool"}, "interface": {"type": "list", "elements": "str"}, }, }, "transition_script": { "type": "dict", "options": { "backup": {"type": "str"}, "fault": {"type": "str"}, "master": {"type": "str"}, "stop": {"type": "str"}, }, }, "vrid": {"type": "int", "required": True}, }, }, - "snmp": {"type": "bool"}, + "snmp": {"type": "bool", "default": False}, "sync_groups": { "type": "list", "elements": "dict", "options": { "name": {"type": "str", "required": True}, "health_check": { "type": "dict", "options": { "failure_count": {"type": "int"}, "interval": {"type": "int"}, "ping": {"type": "str"}, "script": {"type": "str"}, }, }, "member": {"type": "list", "elements": "str"}, "transition_script": { "type": "dict", "options": { "backup": {"type": "str"}, "fault": {"type": "str"}, "master": {"type": "str"}, "stop": {"type": "str"}, }, }, }, }, }, }, }, }, "state": { "type": "str", "choices": [ "deleted", "merged", "purged", "replaced", "gathered", "rendered", "parsed", ], "default": "merged", }, "running_config": {"type": "str"}, } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/vrrp/vrrp.py b/plugins/module_utils/network/vyos/config/vrrp/vrrp.py index c556758f..ad00de44 100644 --- a/plugins/module_utils/network/vyos/config/vrrp/vrrp.py +++ b/plugins/module_utils/network/vyos/config/vrrp/vrrp.py @@ -1,448 +1,455 @@ # # -*- coding: utf-8 -*- # Copyright 2021 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # from __future__ import absolute_import, division, print_function __metaclass__ = type """ The vyos_vrrp config file. It is in this file where the current configuration (as dict) is compared to the provided configuration (as dict) and the command set necessary to bring the current configuration to its desired end-state is created. """ from copy import deepcopy from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( ResourceModule, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrrp import ( VrrpTemplate, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import combine from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import ( LooseVersion, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version class Vrrp(ResourceModule): """ The vyos_vrrp config class """ def __init__(self, module): super(Vrrp, self).__init__( empty_fact_val={}, facts_module=Facts(module), module=module, resource="vrrp", tmplt=VrrpTemplate(), ) self.parsers = [ "disable", ] def _validate_template(self): version = get_os_version(self._module) if LooseVersion(version) >= LooseVersion("1.4"): self._tmplt = VrrpTemplate() else: self._module.fail_json(msg="VRRP is not supported in this version of VyOS") def parse(self): """override parse to check template""" self._validate_template() return super().parse() def get_parser(self, name): """get_parsers""" self._validate_template() return super().get_parser(name) def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ if self.state not in ["parsed", "gathered"]: self.generate_commands() self.run_commands() return self.result def generate_commands(self): """Generate configuration commands to send based on want, have and desired state. """ wantd = {} haved = {} wantd = deepcopy(self.want) haved = deepcopy(self.have) for entry in wantd, haved: # self._module.fail_json(msg="Before normalize_vrrp_groups - entry: " + str(entry)) self._vrrp_groups_list_to_dict(entry) self._virtual_servers_list_to_dict(entry) self._vrrp_sync_groups_list_to_dict(entry) + self._normalize_lists(entry) # self._module.fail_json(msg="Normalise - want: " + str(wantd) + " (((()))) have: " + str(haved)) # if state is merged, merge want onto have and then compare if self.state in ["merged"]: wantd = combine(haved, wantd, recursive=True) # self._module.fail_json(msg="Want: " + str(wantd) + "**** H: " + str(haved)) # if state is deleted, delete and empty out wantd # if self.state == "deleted": # w = deepcopy(wantd) # if w == {} and haved != {}: # self.commands = ["delete vrrp"] # return # for k, want in w.items(): # if not (k in haved and haved[k]): # del wantd[k] # else: # if isinstance(want, list): # for entry in want: # wname = entry.get("name") # haved["instances"] = [ # i for i in haved.get("instances", []) if i.get("name") != wname # ] # self.commands.append("delete vrrp name {}".format(wname)) # else: # self.commands.append("delete vrrp {}".format(k.replace("_", "-"))) # del wantd[k] # # if self.state == "overridden": # w = deepcopy(wantd) # h = deepcopy(haved) # for k, want in w.items(): # if k in haved anzd haved[k] != want: # if isinstance(want, list): # for entry in want: # wname = entry.get("name") # hdict = next( # (inst for inst in haved["instances"] if inst["name"] == wname), # None, # ) # if entry != hdict: # # self._module.fail_json(msg="Want: " + str(entry) + "**** H: " + str(hdict)) # haved["instances"] = [ # i for i in haved.get("instances", []) if i.get("name") != wname # ] # self.commands.append("delete vrrp name {}".format(wname)) # self.commands.append("commit") # for k, want in wantd.items(): if k == "vrrp": self._compare_vrrp(want, haved.get(k, {})) if k == "virtual_servers": # self._module.fail_json(msg="VSERVERS: " + str(want) + " ---- " + str(haved.get(k, {}))) self._compare_vsrvs(want, haved.get(k, {})) # self._module.fail_json(msg=str(want) + " +++ " + str(haved.pop(k, {}))) self.compare( parsers=self.parsers, want={k: want}, have={k: haved.pop(k, {})}, ) self._module.fail_json(msg=self.commands) def _compare_vsrvs(self, want, have): - """Compare virtual servers of VRRP.py""" + """Compare virtual servers of VRRP""" vs_parsers = [ "virtual_servers", "virtual_servers.real_server", ] pairs = [] hlist = self._extract_named_leafs(have) # self._module.fail_json(msg="Want: " + str(self._extract_named_leafs(want)) + "&&&&&&&&&&&&&&&&&&&& have: " + str(hlist)) for wdict in self._extract_named_leafs(want): hdict = self._find_matching_by_path(wdict, hlist) - pairs.append((wdict, hdict)) + # pairs.append((wdict, hdict)) self.compare( parsers=vs_parsers, want={"virtual_servers": wdict}, have={"virtual_servers": hdict}, ) # self._module.fail_json(msg=pairs) - # for pair in self._extract_named_leafs(want): - # self.compare( - # parsers=vs_parsers, - # want={"virtual_servers": pair}, - # have={"virtual_servers": {}}, - # ) - def _compare_vrrp(self, want, have): """Compare the instances of VRRP""" vrrp_parsers = [ "vrrp.snmp", "vrrp.global_parameters", "vrrp.global_parameters.garp", "vrrp.groups", "vrrp.groups.excluded_address", "vrrp.groups.garp", "vrrp.groups.authentication", "vrrp.groups.transition_script", "vrrp.groups.health_check", "vrrp.groups.track", "vrrp.sync_groups.member", "vrrp.sync_groups.transition_script", "vrrp.sync_groups.health_check", ] pairs = [] # self._module.fail_json(msg="Want: " + str(want) + "&&&&&&&&&&&&&&&&&&&& have: " + str(have)) hlist = self._extract_leaf_items(have) - # self.compare(parsers=vrrp_parsers, want={"vrrp": wdict}, have={"vrrp": hdict}) for wdict in self._extract_leaf_items(want): - # hdict = {} hdict = self._find_matching_by_path(wdict, hlist) pairs.append((wdict, hdict)) self.compare(parsers=vrrp_parsers, want={"vrrp": wdict}, have={"vrrp": hdict}) # self._module.fail_json(msg=pairs) def _vrrp_groups_list_to_dict(self, data): vrrp = data.get("vrrp", {}) groups = vrrp.get("groups") if not groups: return data if isinstance(groups, dict): return data if isinstance(groups, list): new_groups = {} for item in groups: name = item.get("name") if not name: continue new_groups[name] = item data["vrrp"]["groups"] = new_groups return data return data def _vrrp_sync_groups_list_to_dict(self, data): - vrrp = data.get("vrrp", {}) groups = vrrp.get("sync_groups") if not groups: return data if isinstance(groups, dict): return data if isinstance(groups, list): new_groups = {} for item in groups: name = item.get("name") if not name: continue new_groups[name] = item data["vrrp"]["sync_groups"] = new_groups return data return data def _virtual_servers_list_to_dict(self, data): vss = data.get("virtual_servers") if not vss: return data if isinstance(vss, dict): for vs in vss.items(): rs = vs.get("real_server") if isinstance(rs, list): vs["real_server"] = { item["address"]: item for item in rs if isinstance(item, dict) and item.get("address") } - return data if isinstance(vss, list): new_vss = {} for vs in vss: if not isinstance(vs, dict): continue name = vs.get("name") if not name: continue rs = vs.get("real_server") if isinstance(rs, list): vs["real_server"] = { item["address"]: item for item in rs if isinstance(item, dict) and item.get("address") } new_vss[name] = vs data["virtual_servers"] = new_vss return data return data def _extract_leaf_items(self, data, path=None, parent_name=None): path = path or [] results = [] if isinstance(data, dict): current_name = data.get("name", parent_name) for k, v in data.items(): if k == "name": continue results.extend(self._extract_leaf_items(v, path + [k], current_name)) return results leaf_key = path[-1] top_key = path[0] if top_key in ["groups", "sync_groups"]: subkeys = path[2:] else: subkeys = path[1:] nested = {leaf_key: data} for p in reversed(subkeys[:-1]): nested = {p: nested} if parent_name: out = {top_key: {"name": parent_name}} out[top_key].update(nested) else: out = {top_key: nested} results.append(out) return results def _extract_named_leafs(self, data, parent_name=None, prefix_key=None): results = [] if prefix_key == "real_server" and isinstance(data, dict): for server_name, server_data in data.items(): if isinstance(server_data, dict): results.append( { "name": parent_name, "real_server": server_data, }, ) return results if isinstance(data, dict): current_name = data.get("name", parent_name) for k, v in data.items(): if k == "name": continue leaves = self._extract_named_leafs(v, current_name, k) results.extend(leaves) return results return [ { "name": parent_name, prefix_key: data, }, ] def _lookup_by_path(self, want_item, have_list): """ Find matching object in have_list by structural path + name (if present). Ignore values. Return {} if not found. """ def extract_signature(d): sig = [] while isinstance(d, dict) and d: k = next(iter(d)) sig.append(k) d = d[k] - # capture identity if present if isinstance(d, dict) and "name" in d: sig.append(("name", d["name"])) - # self._module.fail_json(msg="SIG: " + str(sig)) return tuple(sig) want_sig = extract_signature(want_item) for obj in have_list: if extract_signature(obj) == want_sig: return obj return {} def _find_matching_by_path(self, want_item, have_list): """ Match extracted leaf dicts from _extract_named_leafs(). Supports both container-style and flat-style leaves. """ def build_sig(item): if not isinstance(item, dict) or not item: return () - # CASE 1: container leaf if len(item) == 1: top = next(iter(item)) node = item[top] sig = [top] if isinstance(node, dict): if "name" in node: sig.append(("name", node["name"])) for k, v in node.items(): if k == "name": continue sig.append(k) if isinstance(v, dict): if k == "real_server" and "address" in v: sig.append(("address", v["address"])) else: sig.append(next(iter(v))) break return tuple(sig) - # CASE 2: flat leaf sig = [] if "name" in item: sig.append(("name", item["name"])) for k, v in item.items(): if k != "name": sig.append(k) if isinstance(v, dict) and k == "real_server" and "address" in v: sig.append(("address", v["address"])) break return tuple(sig) sig_want = build_sig(want_item) for obj in have_list: if build_sig(obj) == sig_want: return obj return {} + + def _normalize_lists(self, node): + """ + Recursively normalize all lists inside a dict or list. + All lists are sorted to ensure consistent ordering for comparison. + """ + if isinstance(node, dict): + for k, v in node.items(): + if isinstance(v, list): + # sort the list if it contains only scalars + if all(not isinstance(i, (dict, list)) for i in v): + node[k] = sorted(v) + else: + # recurse into each item if the list contains dicts + for item in v: + self._normalize_lists(item) + elif isinstance(v, dict): + self._normalize_lists(v) + elif isinstance(node, list): + for item in node: + self._normalize_lists(item) diff --git a/plugins/module_utils/network/vyos/facts/vrrp/vrrp.py b/plugins/module_utils/network/vyos/facts/vrrp/vrrp.py index 5e9abf42..38818b5f 100644 --- a/plugins/module_utils/network/vyos/facts/vrrp/vrrp.py +++ b/plugins/module_utils/network/vyos/facts/vrrp/vrrp.py @@ -1,160 +1,159 @@ # -*- coding: utf-8 -*- # Copyright 2021 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type """ The vyos vrrp fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.vrrp.vrrp import ( VrrpArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrrp import ( VrrpTemplate, ) class VrrpFacts(object): """The vyos vrrp facts class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = VrrpArgs.argument_spec def get_device_data(self, connection): return connection.get('show configuration commands | match "set high-availability"') def get_config_set(self, data, connection): """To classify the configurations beased on vrrp""" config_dict = {} for config_line in data.splitlines(): vrrp_grp = re.search(r"set high-availability vrrp group (\S+).*", config_line) vrrp_gp = re.search( r"set high-availability vrrp global-parameters (\S+).*", config_line, ) vrrp_sg = re.search(r"set high-availability vrrp sync-group (\S+).*", config_line) vrrp_vsrv = re.search(r"set high-availability virtual-server (\S+).*", config_line) vrrp_disable = re.search(r"set high-availability disable", config_line) vrrp_snmp = re.search(r"set high-availability vrrp snmp", config_line) if vrrp_disable: config_dict["disable"] = [config_dict.get("disable", "") + config_line] if vrrp_snmp: config_dict.setdefault("vrrp", []).append(config_line) if vrrp_gp: config_dict.setdefault("global_parameters", []).append(config_line) if vrrp_vsrv: config_dict.setdefault(vrrp_vsrv.group(1), []).append(config_line) if vrrp_sg: config_dict.setdefault(vrrp_sg.group(1), []).append(config_line) if vrrp_grp: config_dict.setdefault(vrrp_grp.group(1), []).append(config_line) return list(config_dict.values()) def deep_merge(self, dest, src): for key, value in src.items(): if key in dest and isinstance(dest[key], dict) and isinstance(value, dict): self.deep_merge(dest[key], value) else: dest[key] = value return dest def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for vrrp network resource :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ facts = {} objs = {} - config_lines = [] if not data: data = self.get_device_data(connection) # self._module.fail_json(msg="Data: " + str(data)) resources = self.get_config_set(data, connection) # self._module.fail_json(msg="Resources: " + str(resources)) vrrp_facts = {"disable": False, "virtual_servers": {}, "vrrp": {}} for resource in resources: vrrp_parser = VrrpTemplate( lines=resource, module=self._module, ) objs = vrrp_parser.parse() # self._module.fail_json(msg="VRRP Objs: " + str(objs)) if "disable" in objs: vrrp_facts["disable"] = objs["disable"] for section in ("virtual_servers", "vrrp"): if section in objs: # self._module.fail_json(msg="Section: " + str(section) + " Objs[section]: " + str(objs[section])) for name, data in objs[section].items(): if not isinstance(data, dict): vrrp_facts[section][name] = data continue existing = vrrp_facts[section].get(name, {}) vrrp_facts[section][name] = self.deep_merge(existing, data) ansible_facts["ansible_network_resources"].pop("vrrp", None) # vrrp_facts = self.normalize_config(vrrp_facts) # # self._module.fail_json(msg="VRRP_Facts: " + str(vrrp_facts)) validate_parser = VrrpTemplate(lines=[], module=self._module) params = utils.remove_empties( validate_parser.validate_config( self.argument_spec, {"config": vrrp_facts}, redact=True, ), ) # self._module.fail_json(msg="Params: " + str(params)) facts["vrrp"] = params.get("config", []) ansible_facts["ansible_network_resources"].update(facts) # self._module.fail_json(msg='Facts - ' + str(ansible_facts)) return ansible_facts def normalize_config(self, config): if not config: return config # Normalize virtual_servers if isinstance(config.get("virtual_servers"), dict): config["virtual_servers"] = list(config["virtual_servers"].values()) # Normalize vrrp vrrp = config.get("vrrp", {}) # self._module.fail_json(msg=config.get("vrrp", {})) if isinstance(vrrp.get("groups"), dict): vrrp["groups"] = list(vrrp["groups"].values()) # self._module.fail_json(msg="Groups: " + str(vrrp["groups"])) if isinstance(vrrp.get("sync_groups"), dict): vrrp["sync_groups"] = list(vrrp["sync_groups"].values()) # self._module.fail_json(msg="SGroups: " + str(vrrp["sync_groups"])) # Normalize real_server inside each virtual_server for vs in config.get("virtual_servers", []): if isinstance(vs.get("real_server"), dict): vs["real_server"] = list(vs["real_server"].values()) return config diff --git a/plugins/module_utils/network/vyos/rm_templates/vrrp.py b/plugins/module_utils/network/vyos/rm_templates/vrrp.py index 1df30c49..4fde034e 100644 --- a/plugins/module_utils/network/vyos/rm_templates/vrrp.py +++ b/plugins/module_utils/network/vyos/rm_templates/vrrp.py @@ -1,676 +1,676 @@ # -*- coding: utf-8 -*- # Copyright 2021 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type """ The Vrrp parser templates file. This contains a list of parser definitions and associated functions that facilitates both facts gathering and native command generation for the given network resource. """ import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( NetworkTemplate, ) def _tmplt_vsrvs(config_data): config_data = config_data["virtual_servers"] command = [] cmd = "high-availability virtual-server {name}".format(**config_data) for key, value in config_data.items(): if key == "name" or isinstance(value, dict) or value is None: continue else: command.append(f"{cmd} {key.replace('_', '-')} {value}") return command def _tmplt_vsrvs_rsrv(config_data): config_data = config_data["virtual_servers"] command = [] cmd = "high-availability virtual-server {name}".format(**config_data) config_data = config_data["real_server"] address = config_data["address"] for key, value in config_data.items(): if key == "address" or value is None: continue if value is not None and key == "health_check_script": command.append(cmd + " real-server " + address + " health-check script " + value) else: command.append(cmd + " real-server " + f"{address} {key.replace('_', '-')} {value}") return command def _tmplt_vrrp_sgroup_hc(config_data): config_data = config_data["vrrp"]["sync_groups"] command = [] cmd = "high-availability vrrp sync-group {name}".format(**config_data) config_data = config_data["health_check"] for key, value in config_data.items(): if value is not None: command.append(cmd + " health-check " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_sgroup_ts(config_data): config_data = config_data["vrrp"]["sync_groups"] command = [] cmd = "high-availability vrrp sync-group {name}".format(**config_data) config_data = config_data["transition_script"] for key, value in config_data.items(): if value is not None: command.append(cmd + " transition-script " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_gp(config_data): config_data = config_data["vrrp"]["global_parameters"] command = [] cmd = "high-availability vrrp global-parameters".format(**config_data) for key, value in config_data.items(): if isinstance(value, dict) or value is None: continue else: command.append(f"{cmd} {key.replace('_', '-')} {value}") return command def _tmplt_vrrp_gp_garp(config_data): config_data = config_data["vrrp"]["global_parameters"]["garp"] command = [] cmd = "high-availability vrrp global-parameters garp" for key, value in config_data.items(): if value is None: continue command.append(f"{cmd} {key.replace('_', '-')} {value}") return command def _tmplt_vrrp_group(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) for key, value in config_data.items(): if key == "name" or isinstance(value, dict) or isinstance(value, list) or value is None: continue elif isinstance(value, bool) and value is not None: command.append(f"{cmd} {key.replace('_', '-')}") else: command.append(f"{cmd} {key.replace('_', '-')} {value}") return command def _tmplt_vrrp_group_garp(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) config_data = config_data["garp"] for key, value in config_data.items(): if value is not None: command.append(cmd + " garp " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_group_auth(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) config_data = config_data["authentication"] for key, value in config_data.items(): if value is not None: command.append(cmd + " authentication " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_group_ts(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) config_data = config_data["transition_script"] for key, value in config_data.items(): if value is not None: command.append(cmd + " transition-script " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_sgroup_member(config_data): sgroup = config_data["vrrp"]["sync_groups"] command = [] cmd = "high-availability vrrp sync-group {name}".format(**sgroup) members = sgroup.get("member", []) for member in members: if member is None: continue command.append(f"{cmd} member {member}") return command def _tmplt_vrrp_group_exaddress(config_data): group = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**group) exaddresses = group.get("excluded_address", []) for exaddress in exaddresses: if exaddress is None: continue command.append(f"{cmd} excluded-address {exaddress}") return command def _tmplt_vrrp_group_hc(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) config_data = config_data["health_check"] for key, value in config_data.items(): if value is not None: command.append(cmd + " health-check " + f"{key.replace('_', '-')} {value}") return command def _tmplt_vrrp_group_track(config_data): config_data = config_data["vrrp"]["groups"] command = [] cmd = "high-availability vrrp group {name}".format(**config_data) config_data = config_data["track"] for key, value in config_data.items(): if isinstance(value, bool) and value is not None: command.append(cmd + " track " + f"{key.replace('_', '-')}") elif isinstance(value, list): for item in value: command.append(cmd + " track " + f"{key.replace('_', '-')} {item}") return command class VrrpTemplate(NetworkTemplate): def __init__(self, lines=None, module=None): prefix = {"set": "set", "remove": "delete"} super(VrrpTemplate, self).__init__( lines=lines, tmplt=self, prefix=prefix, module=module, ) # fmt: off PARSERS = [ { "name": "disable", "getval": re.compile( r""" ^set \shigh-availability \s(?Pdisable) $""", re.VERBOSE, ), "setval": "high-availability disable", "result": { "disable": "{{ True if disable is defined else False }}", }, }, { "name": "virtual_servers", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) (?:\s+address\s+(?P
\S+))? (?:\s+algorithm\s+(?P\S+))? (?:\s+delay-loop\s+(?P\S+))? (?:\s+forward-method\s+(?P\S+))? (?:\s+fwmark\s+(?P\S+))? (?:\s+persistence-timeout\s+(?P\S+))? (?:\s+port\s+(?P\S+))? (?:\s+protocol\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "address": "{{ address if address is defined else None }}", "algorithm": "{{ algorithm if algorithm is defined else None }}", "delay_loop": "{{ delay_loop if delay_loop is defined else None }}", "forward_method": "{{ forward_method if forward_method is defined else None }}", "fwmark": "{{ fwmark if fwmark is defined else None }}", "persistence_timeout": "{{ persistence_timeout if persistence_timeout is defined else None }}", "port": "{{ port if port is defined else None }}", "protocol": "{{ protocol if protocol is defined else None }}", }, }, }, }, { "name": "virtual_servers.real_server", "getval": re.compile( r""" ^set\shigh-availability\svirtual-server \s+(?P\S+) \sreal-server \s+(?P
\S+) (?:\s+port\s+(?P\S+))? (?:\s+health-check\sscript\s+(?P\S+))? (?:\s+connection-timeout\s+(?P\S+))? $ """, re.VERBOSE, ), "setval": _tmplt_vsrvs_rsrv, "result": { "virtual_servers": { "{{ name }}": { "name": "{{ name }}", "real_server": { "{{ address }}": { "address": "{{ address }}", "port": "{{ port if port is defined else None }}", "health_check_script": "{{ hcscript if hcscript is defined else None }}", "connection_timeout": "{{ cont if cont is defined else None }}", }, }, }, }, }, }, { "name": "vrrp.sync_groups.member", "getval": re.compile( r""" ^set\shigh-availability\svrrp\ssync-group \s+(?P\S+) \smember \s+(?P\S+) $ """, re.VERBOSE, ), "setval": _tmplt_vrrp_sgroup_member, "result": { "vrrp": { "sync_groups": { "{{ sgname }}": { "name": "{{ sgname }}", "member": [ "{{ member }}", ], }, }, }, }, }, { "name": "vrrp.sync_groups.health_check", "getval": re.compile( r""" ^set\shigh-availability\svrrp\ssync-group \s+(?P\S+) \shealth-check (?:\s+failure-count\s+(?P\S+)) ?(?:\s+interval\s+(?P\S+)) ?(?:\s+ping\s+(?P\S+)) ?(?:\s+script\s+(?P