diff --git a/plugins/action/vrf.py b/plugins/action/vrf.py deleted file mode 100644 index 148d7c64..00000000 --- a/plugins/action/vrf.py +++ /dev/null @@ -1,55 +0,0 @@ -# -# (c) 2016 Red Hat Inc. -# -# This file is part of Ansible -# -# Ansible is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Ansible is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with Ansible. If not, see . -# -from __future__ import absolute_import, division, print_function - - -__metaclass__ = type - -from ansible.utils.display import Display -from ansible_collections.ansible.netcommon.plugins.action.network import ( - ActionModule as ActionNetworkModule, -) - - -display = Display() - - -class ActionModule(ActionNetworkModule): - def run(self, tmp=None, task_vars=None): - del tmp # tmp no longer has any effect - - module_name = self._task.action.split(".")[-1] - self._config_module = True if module_name in ["vyos_config", "config"] else False - persistent_connection = self._play_context.connection.split(".")[-1] - warnings = [] - - if persistent_connection != "network_cli": - return { - "failed": True, - "msg": "Connection type %s is not valid for this module" - % self._play_context.connection, - } - - result = super(ActionModule, self).run(task_vars=task_vars) - if warnings: - if "warnings" in result: - result["warnings"].extend(warnings) - else: - result["warnings"] = warnings - return result diff --git a/plugins/action/vrf.py b/plugins/action/vrf.py new file mode 120000 index 00000000..331a791f --- /dev/null +++ b/plugins/action/vrf.py @@ -0,0 +1 @@ +vyos.py \ No newline at end of file diff --git a/plugins/module_utils/network/vyos/config/vrf/vrf.py b/plugins/module_utils/network/vyos/config/vrf/vrf.py index 00ec540b..9a74d66f 100644 --- a/plugins/module_utils/network/vyos/config/vrf/vrf.py +++ b/plugins/module_utils/network/vyos/config/vrf/vrf.py @@ -1,230 +1,232 @@ # # -*- 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.plugins.filter.core import combine 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.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 # from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( # dict_merge, # ) class Vrf(ResourceModule): """ The vyos_vrf config class """ def __init__(self, module): super(Vrf, self).__init__( empty_fact_val={}, facts_module=Facts(module), module=module, resource="vrf", tmplt=VrfTemplate(), ) self.parsers = [ "bind_to_all", ] def _validate_template(self): version = get_os_version(self._module) if LooseVersion(version) >= LooseVersion("1.4"): self._tmplt = VrfTemplate() else: self._module.fail_json(msg="VRF is not supported in this version of VyOS") def parse(self): """override parse to check template""" self._validate_template() return super().parse() def get_parser(self, name): """get_parsers""" self._validate_template() return super().get_parser(name) def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ if self.state not in ["parsed", "gathered"]: self.generate_commands() self.run_commands() return self.result def generate_commands(self): """Generate configuration commands to send based on want, have and desired state. """ wantd = {} haved = {} wantd = deepcopy(self.want) haved = deepcopy(self.have) # if state is merged, merge want onto have and then compare if self.state in ["merged", "replaced"]: # wantd = dict_merge(wantd, haved) wantd = haved | combine(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) for k, want in iteritems(w): if not (k in haved and haved[k]): del wantd[k] else: if isinstance(want, list): for entry in want: wname = entry.get("name") haved["instances"] = [ i for i in haved.get("instances", []) if i.get("name") != wname ] self.commands.append("delete vrf name {}".format(wname)) else: self.commands.append("delete vrf {}".format(k.replace("_", "-"))) del wantd[k] if self.state == "overridden": w = deepcopy(wantd) h = deepcopy(haved) for k, want in iteritems(w): if k in haved and haved[k] != want: if isinstance(want, list): for entry in want: wname = entry.get("name") hdict = next( (inst for inst in haved["instances"] if inst["name"] == wname), None, ) if entry != hdict: haved["instances"] = [ i for i in haved.get("instances", []) if i.get("name") != wname ] self.commands.append("delete vrf name {}".format(wname)) self.commands.append("commit") for k, want in iteritems(wantd): if isinstance(want, list): self._compare_instances(want=want, have=haved.pop(k, {})) self.compare( parsers=self.parsers, want={k: want}, have={k: haved.pop(k, {})}, ) self._module.fail_json(msg=self.commands) def _compare_instances(self, want, have): """Compare the instances of the VRF""" parsers = [ "table_id", "vni", "description", "disable_vrf", ] # self._module.fail_json(msg="want: " + str(want) + "**** have: " + str(have)) for entry in want: h = {} wname = entry.get("name") # h = next((vrf for vrf in have if vrf["name"] == wname), {}) h = { k: v for vrf in have if vrf.get("name") == wname for k, v in vrf.items() if k != "address_family" } self.compare(parsers=parsers, want=entry, have=h) if "address_family" in entry: wafi = {"name": wname, "address_family": entry.get("address_family", [])} # hdict = next((item for item in have if item["name"] == wname), None) hdict = next((d for d in have if d.get("name") == wname), None) hafi = { "name": (hdict or {"name": wname})["name"], "address_family": hdict.get("address_family", []) if hdict else [], } # self._module.fail_json(msg="wafi: " + str(wafi) + "**** hafi: " + str(hafi)) self._compare_addr_family(wafi, hafi) def _compare_addr_family(self, want, have): """Compare the address families of the VRF""" afi_parsers = [ # "address_family", "disable_forwarding", "disable_nht", + "route_maps", ] # self._module.fail_json(msg="wAfi: " + str(want) + "**** hAfi: " + str(have)) wafi = self.afi_to_list(want) hafi = self.afi_to_list(have) lookup = {(d["name"], d["afi"]): d for d in hafi} pairs = [(d1, lookup.get((d1["name"], d1["afi"]), {})) for d1 in wafi] for wafd, hafd in pairs: + if "route_maps" in wafd: + self._module.fail_json(msg="wafd: " + str(wafd) + " **** hafd: " + str(hafd)) self.compare(parsers=afi_parsers, want=wafd, have=hafd) - # self._module.fail_json(msg="wafi: " + str(wafi) + " **** hafi: " + str(hafi)) # self.compare(parsers=afi_parsers, want=wafi, have=hafi) def afi_to_dict(self, data): name = data.get("name", "") af_list = data.get("address_family", []) afi_dict = {"name": name, "ip": {}, "ipv6": {}} for af in af_list: afi = af.get("afi") if afi in ("ipv4", "ipv6"): afi_dict["ip" if afi == "ipv4" else "ipv6"] = { k: v for k, v in af.items() if k != "afi" } return afi_dict def afi_to_list(self, data): """Convert address family dict to list""" return [ {"name": data["name"], **{**af, "afi": "ip" if af["afi"] == "ipv4" else af["afi"]}} for af in data["address_family"] ] diff --git a/plugins/module_utils/network/vyos/rm_templates/vrf.py b/plugins/module_utils/network/vyos/rm_templates/vrf.py index 0c54b4f1..4fa36512 100644 --- a/plugins/module_utils/network/vyos/rm_templates/vrf.py +++ b/plugins/module_utils/network/vyos/rm_templates/vrf.py @@ -1,245 +1,248 @@ # -*- 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": "table_id", "getval": re.compile( r""" ^set \s+vrf \s+name \s+(?P\S+) \s+table \s+'(?P\S+)' $""", re.VERBOSE, ), "setval": "vrf name {{ name }} table {{ table_id }}", "result": { "name": "{{ name }}", "table_id": "{{ tid }}", }, }, { "name": "bind_to_all", "getval": re.compile( r""" ^set \svrf \s(?Pbind-to-all) $""", re.VERBOSE, ), "setval": "vrf bind-to-all", "result": { "bind_to_all": "{{ True if bta is defined }}", }, }, { "name": "vni", "getval": re.compile( r""" ^set \s+vrf \s+name \s+(?P\S+) \s+vni \s'(?P\S+)' $""", re.VERBOSE, ), "setval": "vrf name {{name}} vni {{vni}}", "result": { "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 {{description}}", "result": { "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": "disable", "result": { "name": "{{ name }}", "disable": "{{ True if disable is defined }}", }, }, # { # "name": "address_family", # "getval": re.compile( # r""" # ^set # \svrf # \sname # \s(?P\S+) # \s(?Pip|ipv6) # $""", # re.VERBOSE, # ), # "setval": "vrf name {{name}} {{ af }}", # 'compval': "address_family", # "result": { # "name": "{{ name }}", # "address_family": { # '{{ "ipv4" if af == "ip" else "ipv6" }}': { # "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}', # }, # }, # }, # }, # { # "name": "address_family.disable_forwarding", # "getval": re.compile( # r""" # ^set # \svrf # \sname # \s(?P\S+) # \s(?Pip|ipv6) # \s(?Pdisable-forwarding) # $""", # re.VERBOSE, # ), # "setval": "vrf name {{name}} {{ afi }} disable-forwarding", # # "compval": "address_family.ipv6.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_forwarding", "getval": re.compile( r""" ^set \svrf \sname \s(?P\S+) \s(?Pip|ipv6) \s(?Pdisable-forwarding) $""", re.VERBOSE, ), "setval": "vrf name {{name}} {{ afi }} disable-forwarding", "compval": "disable_forwarding", "result": { "name": "{{ name }}", 'address_family': [{ "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(?Pip|ipv6) \snht \s(?Pno-resolve-via-default) $""", re.VERBOSE, ), "setval": "vrf name {{name}} {{ afi }} nht no-resolve-via-default", "compval": "nht_no_resolve_via_default", "result": { "name": "{{ name }}", "address_family": [{ "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}', "nht_no_resolve_via_default": "{{ True if nht is defined }}", }], }, }, { "name": "route_maps", "getval": re.compile( r""" ^set \svrf \sname \s(?P\S+) \s(?Pip|ipv6) \sprotocol \s(?P\S+) \sroute-map \s'(?P\S+)' $""", re.VERBOSE, ), "setval": "vrf name {{name}} {{ afi }} protocol {{ proto }} route-map {{ rm }}", - "compval": "nht_no_resolve_via_default", + "compval": "route_maps.name", "result": { "name": "{{ name }}", "address_family": [{ "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}', - "nht_no_resolve_via_default": "{{ True if nht is defined }}", + "route_maps": [{ + "name": "{{ rm }}", + "protocol": "{{ proto }}", + }], }], }, }, ] # fmt: on