diff --git a/plugins/module_utils/network/vyos/config/vrf/vrf.py b/plugins/module_utils/network/vyos/config/vrf/vrf.py index 1268a139..dd807b7b 100644 --- a/plugins/module_utils/network/vyos/config/vrf/vrf.py +++ b/plugins/module_utils/network/vyos/config/vrf/vrf.py @@ -1,144 +1,144 @@ # # -*- coding: utf-8 -*- # Copyright 2021 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # from __future__ import absolute_import, division, print_function __metaclass__ = type """ The vyos_ntp config file. It is in this file where the current configuration (as dict) is compared to the provided configuration (as dict) and the command set necessary to bring the current configuration to its desired end-state is created. """ from copy import deepcopy from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( ResourceModule, ) from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( dict_merge, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrf import ( VrfTemplate, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import ( LooseVersion, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version class Vrf(ResourceModule): """ The vyos_vrf config class """ def __init__(self, module): super(Vrf, self).__init__( empty_fact_val={}, facts_module=Facts(module), module=module, resource="vrf", tmplt=VrfTemplate(), ) self.parsers = [ "bind_to_all", ] def _validate_template(self): version = get_os_version(self._module) if LooseVersion(version) >= LooseVersion("1.4"): self._tmplt = VrfTemplate() else: self._module.fail_json(msg="VRF is not supported in this version of VyOS") def parse(self): """override parse to check template""" self._validate_template() return super().parse() def get_parser(self, name): """get_parsers""" self._validate_template() return super().get_parser(name) def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ if self.state not in ["parsed", "gathered"]: self.generate_commands() self.run_commands() return self.result def generate_commands(self): """Generate configuration commands to send based on want, have and desired state. """ wantd = {} haved = {} wantd = deepcopy(self.want) haved = deepcopy(self.have) # if state is merged, merge want onto have and then compare if self.state == "merged": wantd = dict_merge(haved, wantd) # # if state is deleted, empty out wantd and set haved to wantd # if self.state == "deleted": # haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} # wantd = {} # if self.state in ["overridden", "replaced"]: # for k, have in iteritems(haved): # if k not in wantd: # self.commands.append(self._tmplt.render({"route_map": k}, "route_map", True)) for k, want in iteritems(wantd): # self._module.fail_json(msg=k) self._compare(want=want, have=haved.pop(k, {})) self._module.fail_json(msg=self.commands) def _compare(self, want, have): """Leverages the base class `compare()` method and populates the list of commands to be run by comparing the `want` and `have` data with the `parsers` defined for the Ntp network resource. """ if isinstance(want, list): self._compare_inststances(want=want, have=have) # if "options" in want: # self.compare(parsers="options", want=want, have=have) # else: # self.compare(parsers=self.parsers, want=want, have=have) def _compare_inststances(self, want, have): """Compare the instances of the VRF""" parsers = [ "table", "vni", "description", "disable_vrf", "disable_forwarding", "disable_nht", ] # wname = want.get("name") # hname = have.get("name") for entry in want: wname = entry.get("name") h = next(d for d in have if d["name"] == wname) - self._module.fail_json(msg=str(entry) + str(h)) + # self._module.fail_json(msg=str(entry) + str(h)) self.compare(parsers=parsers, want=entry, have=h) diff --git a/plugins/module_utils/network/vyos/facts/vrf/vrf.py b/plugins/module_utils/network/vyos/facts/vrf/vrf.py index 74fde0fc..d9580f0f 100644 --- a/plugins/module_utils/network/vyos/facts/vrf/vrf.py +++ b/plugins/module_utils/network/vyos/facts/vrf/vrf.py @@ -1,122 +1,103 @@ # -*- coding: utf-8 -*- # Copyright 2021 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type """ The vyos vrf fact class It is in this file the configuration is collected from the device for a given resource, parsed, and the facts tree is populated based on the configuration. """ import re from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.vrf.vrf import VrfArgs from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrf import ( VrfTemplate, ) class VrfFacts(object): """The vyos vrf facts class""" def __init__(self, module, subspec="config", options="options"): self._module = module self.argument_spec = VrfArgs.argument_spec def get_config(self, connection): return connection.get("show configuration commands | match 'set vrf'") def get_config_set(self, data, connection): """To classify the configurations beased on vrf""" config_dict = {} for config_line in data.splitlines(): vrf_inst = re.search(r"set vrf name (\S+).*", config_line) + vrf_bta = re.search(r"set vrf bind-to-all", config_line) + if vrf_bta: + config_dict["bind_to_all"] = config_dict.get("bind_to_all", "") + config_line + "\n" if vrf_inst: config_dict[vrf_inst.group(1)] = ( config_dict.get(vrf_inst.group(1), "") + config_line + "\n" ) return list(config_dict.values()) def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for Vrf network resource :param connection: the device connection :param ansible_facts: Facts dictionary :param data: previously collected conf :rtype: dictionary :returns: facts """ facts = {} objs = [] if not data: data = self.get_config(connection) vrf_facts = {} instances = [] resources = self.get_config_set(data, connection) - # self._module.fail_json(msg=resources) for resource in resources: vrf_parser = VrfTemplate( lines=resource.split("\n"), module=self._module, ) objs = vrf_parser.parse() - if "name" in objs and objs["name"]: - instances.append(objs) - # for key, sortv in [("address_family", "afi")]: - # if key in objs and objs[key]: - # objs[key] = list(objs[key].values()) - vrf_facts["instances"] = instances - # for resource in data.splitlines(): - # config_lines.append(re.sub("'", "", resource)) - # # parse native config using the Vrf template - # vrf_parser = VrfTemplate(lines=config_lines, module=self._module) - - # objs = vrf_parser.parse() - # self._module.fail_json(msg=objs) - - # if objs: - # if "allow_clients" in objs: - # objs["allow_clients"] = sorted(list(objs["allow_clients"])) - - # if "listen_addresses" in objs: - # objs["listen_addresses"] = sorted(list(objs["listen_addresses"])) - - # """ if "options" in objs["servers"].values(): - # val = objs["servers"].values() - # val["options"] = sorted(val["options"]) """ - - # if "servers" in objs: - # objs["servers"] = list(objs["servers"].values()) - # objs["servers"] = sorted(objs["servers"], key=lambda k: k["server"]) - # for i in objs["servers"]: - # if "options" in i: - # i["options"] = sorted(list(i["options"])) - - # self._module.fail_json(msg=vrf_facts) - ansible_facts["ansible_network_resources"].pop("vrf", None) + + if objs: + if "bind_to_all" in objs: + vrf_facts.update(objs) + if "instances" in objs and objs["instances"]: + # name = objs["instances"].get("name") + # attrib = {k: v for k, v in objs["instances"].items() if k != 'name'} + # instances.update({name: attrib}) + instances.append(objs["instances"]) + + vrf_facts.update({"instances": instances}) + + ansible_facts["ansible_network_resources"].pop("vrf_facts", None) + facts = {"vrf": []} params = utils.remove_empties( - vrf_parser.validate_config(self.argument_spec, {"config": vrf_facts}, redact=True), + vrf_parser.validate_config( + self.argument_spec, + {"config": vrf_facts}, + redact=True, + ), ) - # self._module.fail_json(msg=params) - if params.get("config"): facts["vrf"] = params["config"] ansible_facts["ansible_network_resources"].update(facts) - - self._module.fail_json(msg=ansible_facts) - return ansible_facts diff --git a/plugins/module_utils/network/vyos/rm_templates/vrf.py b/plugins/module_utils/network/vyos/rm_templates/vrf.py index a5cf605e..eebfdd96 100644 --- a/plugins/module_utils/network/vyos/rm_templates/vrf.py +++ b/plugins/module_utils/network/vyos/rm_templates/vrf.py @@ -1,179 +1,187 @@ # -*- 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 Ntp 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, ) class VrfTemplate(NetworkTemplate): def __init__(self, lines=None, module=None): prefix = {"set": "set", "remove": "delete"} super(VrfTemplate, self).__init__(lines=lines, tmplt=self, prefix=prefix, module=module) # fmt: off PARSERS = [ { "name": "bind_to_all", "getval": re.compile( r""" ^set \svrf \s(?Pbind-to-all) $""", re.VERBOSE, ), "setval": "vrf bind-to-all", "compval": "bta", "result": { "bind_to_all": "{{ True if bta is defined }}", }, }, { "name": "table", "getval": re.compile( r""" ^set \svrf \sname \s(?P\S+) \stable \s(?P\S+) $""", re.VERBOSE, ), "setval": "vrf name {{name}} table {{tid}}", "compval": "table_id", "result": { - "name": "{{ name }}", - "table_id": "{{ tid }}", + "instances": { + "name": "{{ name }}", + "table_id": "{{ tid }}", + }, }, }, { "name": "vni", "getval": re.compile( r""" ^set \svrf \sname \s(?P\S+) \svni \s(?P\S+) $""", re.VERBOSE, ), "setval": "vrf name {{name}} vni {{vni}}", "compval": "vni", "result": { - "name": "{{ name }}", - "vni": "{{ vni }}", + "instances": { + "name": "{{ name }}", + "vni": "{{ vni }}", + }, }, }, { "name": "description", "getval": re.compile( r""" ^set \svrf \sname \s(?P\S+) \sdescription \s(?P\S+) $""", re.VERBOSE, ), "setval": "vrf name {{name}} description {{desc}}", "compval": "desc", "result": { - "name": "{{ name }}", - "description": "{{ desc }}", + "instances": { + "name": "{{ name }}", + "description": "{{ desc }}", + }, }, }, { "name": "disable_vrf", "getval": re.compile( r""" ^set \svrf \sname \s(?P\S+) \s(?Pdisable) $""", re.VERBOSE, ), "setval": "vrf name {{name}} disable", "compval": "desc", "result": { - "name": "{{ name }}", - "disable": "{{ True if disable is defined }}", + "instances": { + "name": "{{ name }}", + "disable": "{{ True if disable is defined }}", + }, }, }, { "name": "disable_forwarding", "getval": re.compile( r""" ^set \svrf \sname \s(?P\S+) \s(?P\S+) \s(?Pdisable-forwarding) $""", re.VERBOSE, ), "setval": "vrf name {{name}} {{ af }} disable-forwarding", "compval": "address_family.disable_forwarding", "result": { "name": "{{ name }}", "address_family": { '{{ "ipv4" if af == "ip" else "ipv6" }}': { "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}', "disable_forwarding": "{{ True if df is defined }}", }, }, }, }, { "name": "disable_nht", "getval": re.compile( r""" ^set \svrf \sname \s(?P\S+) \s(?P\S+) \snht \s(?Pno-resolve-via-default) $""", re.VERBOSE, ), "setval": "vrf name {{name}} {{ af }} nht no-resolve-via-default", "compval": "address_family.no_resolve_via_default", "result": { "name": "{{ name }}", "address_family": { '{{ "ipv4" if af == "ip" else "ipv6" }}': { "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}', "no_resolve_via_default": "{{ True if nht is defined }}", }, }, }, }, ] # fmt: on