diff options
Diffstat (limited to 'plugins/module_utils')
8 files changed, 626 insertions, 0 deletions
diff --git a/plugins/module_utils/network/vyos/argspec/prefix_lists/__init__.py b/plugins/module_utils/network/vyos/argspec/prefix_lists/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/prefix_lists/__init__.py diff --git a/plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py b/plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py new file mode 100644 index 0000000..c7d2e98 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py @@ -0,0 +1,82 @@ +# -*- 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 + +############################################# +# 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_prefix_lists module +""" + + +class Prefix_listsArgs(object): # pylint: disable=R0903 + """The arg spec for the vyos_prefix_lists module""" + + argument_spec = { + "config": { + "type": "list", + "elements": "dict", + "options": { + "afi": { + "type": "str", + "choices": ["ipv4", "ipv6"], + "required": True, + }, + "prefix_lists": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str", "required": True}, + "description": {"type": "str"}, + "entries": { + "type": "list", + "elements": "dict", + "options": { + "sequence": {"type": "int", "required": True}, + "description": {"type": "str"}, + "action": { + "type": "str", + "choices": ["permit", "deny"], + }, + "ge": {"type": "int"}, + "le": {"type": "int"}, + "prefix": {"type": "str"}, + }, + }, + }, + }, + }, + }, + "running_config": {"type": "str"}, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/prefix_lists/__init__.py b/plugins/module_utils/network/vyos/config/prefix_lists/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/module_utils/network/vyos/config/prefix_lists/__init__.py diff --git a/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py b/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py new file mode 100644 index 0000000..b2c119d --- /dev/null +++ b/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py @@ -0,0 +1,182 @@ +# +# -*- 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_prefix_lists 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 ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) +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.prefix_lists import ( + Prefix_listsTemplate, +) + + +class Prefix_lists(ResourceModule): + """ + The vyos_prefix_lists config class + """ + + def __init__(self, module): + super(Prefix_lists, self).__init__( + empty_fact_val=[], + facts_module=Facts(module), + module=module, + resource="prefix_lists", + tmplt=Prefix_listsTemplate(), + ) + self.plist_parsers = [ + "name", + "description", + ] + self.entries_parsers = [ + "sequence", + "action", + "rule_description", + "ge", + "le", + "prefix", + ] + + 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 = {entry["afi"]: entry for entry in self.want} + haved = {entry["afi"]: entry for entry in self.have} + + self._prefix_list_list_to_dict(wantd) + self._prefix_list_list_to_dict(haved) + + # 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 + } + for key, hvalue in iteritems(haved): + wvalue = wantd.pop(key, {}) + if wvalue: + wplists = wvalue.get("prefix_lists", {}) + hplists = hvalue.get("prefix_lists", {}) + hvalue["prefix_lists"] = { + k: v + for k, v in iteritems(hplists) + if k in wplists or not wplists + } + + # remove superfluous config for overridden and deleted + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + self._compare(want={}, have=have) + + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {})) + + 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 Prefix_lists network resource. + """ + wplists = want.get("prefix_lists", {}) + hplists = have.get("prefix_lists", {}) + + self._compare_plists(want=wplists, have=hplists) + + if self.state in ["overridden", "deleted"]: + # remove remaining prefix lists + for h in hplists.values(): + self.commands.append( + "delete policy prefix-{0} {1}".format( + "list" if h["afi"] == "ipv4" else "list6", h["name"] + ) + ) + + def _compare_plists(self, want, have): + for wk, wentry in iteritems(want): + hentry = have.pop(wk, {}) + + # parser list for name and descriptions + self.compare( + parsers=self.plist_parsers, + want=wentry, + have=hentry, + ) + + wplrules = wentry.get("entries", {}) + hplrules = hentry.get("entries", {}) + + self._compare_rules(want=wplrules, have=hplrules) + + def _compare_rules(self, want, have): + for wr, wrule in iteritems(want): + hrule = have.pop(wr, {}) + + # parser list for entries + self.compare( + parsers=self.entries_parsers, + want=wrule, + have=hrule, + ) + + # remove remaining entries + for hr in have.values(): + self.commands.append( + "delete policy prefix-{0} {1} rule {2}".format( + "list" if hr["afi"] == "ipv4" else "list6", + hr["name"], + hr["sequence"], + ) + ) + + def _prefix_list_list_to_dict(self, entry): + for afi, value in iteritems(entry): + if "prefix_lists" in value: + for pl in value["prefix_lists"]: + pl.update({"afi": afi}) + if "entries" in pl: + for entry in pl["entries"]: + entry.update({"afi": afi, "name": pl["name"]}) + pl["entries"] = { + x["sequence"]: x for x in pl["entries"] + } + value["prefix_lists"] = { + entry["name"]: entry for entry in value["prefix_lists"] + } diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index 81518f8..90ee03c 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -58,6 +58,9 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_a from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.route_maps.route_maps import ( Route_mapsFacts, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.prefix_lists.prefix_lists import ( + Prefix_listsFacts, +) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import ( Default, Neighbors, @@ -82,6 +85,7 @@ FACT_RESOURCE_SUBSETS = dict( bgp_global=Bgp_globalFacts, bgp_address_family=Bgp_address_familyFacts, route_maps=Route_mapsFacts, + prefix_lists=Prefix_listsFacts, ) diff --git a/plugins/module_utils/network/vyos/facts/prefix_lists/__init__.py b/plugins/module_utils/network/vyos/facts/prefix_lists/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/prefix_lists/__init__.py diff --git a/plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py b/plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py new file mode 100644 index 0000000..15a2db9 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py @@ -0,0 +1,93 @@ +# -*- 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 prefix_lists 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 ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.prefix_lists import ( + Prefix_listsTemplate, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.prefix_lists.prefix_lists import ( + Prefix_listsArgs, +) + + +class Prefix_listsFacts(object): + """The vyos prefix_lists facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Prefix_listsArgs.argument_spec + + def get_config(self, connection): + return connection.get("show configuration commands | grep prefix-list") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Prefix_lists 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) + + # parse native config using the Prefix_lists template + prefix_lists_parser = Prefix_listsTemplate( + lines=data.splitlines(), module=self._module + ) + + objs = prefix_lists_parser.parse() + objs = sorted( + list(objs.values()), + key=lambda k: k["afi"], + ) + + if objs: + for item in objs: + item["prefix_lists"] = sorted( + list(item["prefix_lists"].values()), + key=lambda k: k["name"], + ) + for pl in item["prefix_lists"]: + if "entries" in pl: + pl["entries"] = sorted( + list(pl["entries"].values()), + key=lambda k: k["sequence"], + ) + + ansible_facts["ansible_network_resources"].pop("prefix_lists", None) + + params = utils.remove_empties( + prefix_lists_parser.validate_config( + self.argument_spec, {"config": objs}, redact=True + ) + ) + + if params.get("config"): + facts["prefix_lists"] = params["config"] + else: + facts["prefix_lists"] = [] + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/plugins/module_utils/network/vyos/rm_templates/prefix_lists.py b/plugins/module_utils/network/vyos/rm_templates/prefix_lists.py new file mode 100644 index 0000000..9a66a8d --- /dev/null +++ b/plugins/module_utils/network/vyos/rm_templates/prefix_lists.py @@ -0,0 +1,265 @@ +# -*- 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 Prefix_lists 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 Prefix_listsTemplate(NetworkTemplate): + def __init__(self, lines=None, module=None): + prefix = {"set": "set", "remove": "delete"} + super(Prefix_listsTemplate, self).__init__( + lines=lines, tmplt=self, module=module, prefix=prefix + ) + + # fmt: off + PARSERS = [ + # policy prefix-list <list-name> + { + "name": "name", + "getval": re.compile( + r""" + ^set + \spolicy + \sprefix-(?P<afi>\S+) + \s(?P<name>\S+) + $""", re.VERBOSE), + "setval": "policy prefix-{{ 'list' if afi == 'ipv4' else 'list6' }} {{ name }}", + "result": { + "{{ 'ipv4' if afi == 'list' else 'ipv6' }}": { + "afi": "{{ 'ipv4' if afi == 'list' else 'ipv6' }}", + "prefix_lists": { + "{{ name }}": { + "name": "{{ name }}", + } + } + } + }, + }, + + # policy prefix-list <list-name> description <desc> + { + "name": "description", + "getval": re.compile( + r""" + ^set + \spolicy + \sprefix-(?P<afi>\S+) + \s(?P<name>\S+) + \sdescription\s'(?P<description>.+)' + $""", re.VERBOSE), + "setval": "policy prefix-{{ 'list' if afi == 'ipv4' else 'list6' }} {{ name }} description '{{ description }}'", + "result": { + "{{ 'ipv4' if afi == 'list' else 'ipv6' }}": { + "afi": "{{ 'ipv4' if afi == 'list' else 'ipv6' }}", + "prefix_lists": { + "{{ name }}": { + "name": "{{ name }}", + "description": "{{ description }}" + } + } + } + }, + }, + + # policy prefix-list <list-name> rule <rule-num> + { + "name": "sequence", + "getval": re.compile( + r""" + ^set + \spolicy + \sprefix-(?P<afi>\S+) + \s(?P<name>\S+) + \srule\s(?P<sequence>\d+) + $""", re.VERBOSE), + "setval": "policy prefix-{{ 'list' if afi == 'ipv4' else 'list6' }} {{ name }} rule {{ sequence }}", + "result": { + "{{ 'ipv4' if afi == 'list' else 'ipv6' }}": { + "afi": "{{ 'ipv4' if afi == 'list' else 'ipv6' }}", + "prefix_lists": { + "{{ name }}": { + "name": "{{ name }}", + "entries": { + "{{ sequence }}": { + "sequence": "{{ sequence }}" + } + } + } + } + } + }, + }, + + # policy prefix-list <list-name> rule <rule-num> action + { + "name": "action", + "getval": re.compile( + r""" + ^set + \spolicy + \sprefix-(?P<afi>\S+) + \s(?P<name>\S+) + \srule\s(?P<sequence>\d+) + \saction\s'(?P<action>permit|deny)' + $""", re.VERBOSE), + "setval": "policy prefix-{{ 'list' if afi == 'ipv4' else 'list6' }} {{ name }} rule {{ sequence }} action '{{ action }}'", + "result": { + "{{ 'ipv4' if afi == 'list' else 'ipv6' }}": { + "afi": "{{ 'ipv4' if afi == 'list' else 'ipv6' }}", + "prefix_lists": { + "{{ name }}": { + "name": "{{ name }}", + "entries": { + "{{ sequence }}": { + "sequence": "{{ sequence }}", + "action": "{{ action }}" + } + } + } + } + } + }, + }, + + # policy prefix-list <list-name> rule <rule-num> description <desc> + { + "name": "rule_description", + "getval": re.compile( + r""" + ^set + \spolicy + \sprefix-(?P<afi>\S+) + \s(?P<name>\S+) + \srule\s(?P<sequence>\d+) + \sdescription\s'(?P<rule_description>.+)' + $""", re.VERBOSE), + "compval": "description", + "setval": "policy prefix-{{ 'list' if afi == 'ipv4' else 'list6' }} {{ name }} rule {{ sequence }} description '{{ description }}'", + "result": { + "{{ 'ipv4' if afi == 'list' else 'ipv6' }}": { + "afi": "{{ 'ipv4' if afi == 'list' else 'ipv6' }}", + "prefix_lists": { + "{{ name }}": { + "name": "{{ name }}", + "entries": { + "{{ sequence }}": { + "sequence": "{{ sequence }}", + "description": "{{ rule_description }}" + } + } + } + } + } + }, + }, + + # policy prefix-list <list-name> rule <rule-num> ge <value> + { + "name": "ge", + "getval": re.compile( + r""" + ^set + \spolicy + \sprefix-(?P<afi>\S+) + \s(?P<name>\S+) + \srule\s(?P<sequence>\d+) + \sge\s'(?P<ge>\d+)' + $""", re.VERBOSE), + "setval": "policy prefix-{{ 'list' if afi == 'ipv4' else 'list6' }} {{ name }} rule {{ sequence }} ge '{{ ge }}'", + "result": { + "{{ 'ipv4' if afi == 'list' else 'ipv6' }}": { + "afi": "{{ 'ipv4' if afi == 'list' else 'ipv6' }}", + "prefix_lists": { + "{{ name }}": { + "name": "{{ name }}", + "entries": { + "{{ sequence }}": { + "sequence": "{{ sequence }}", + "ge": "{{ ge }}" + } + } + } + } + } + }, + }, + + # policy prefix-list <list-name> rule <rule-num> le <value> + { + "name": "le", + "getval": re.compile( + r""" + ^set + \spolicy + \sprefix-(?P<afi>\S+) + \s(?P<name>\S+) + \srule\s(?P<sequence>\d+) + \sle\s'(?P<le>\d+)' + $""", re.VERBOSE), + "setval": "policy prefix-{{ 'list' if afi == 'ipv4' else 'list6' }} {{ name }} rule {{ sequence }} le '{{ le }}'", + "result": { + "{{ 'ipv4' if afi == 'list' else 'ipv6' }}": { + "afi": "{{ 'ipv4' if afi == 'list' else 'ipv6' }}", + "prefix_lists": { + "{{ name }}": { + "name": "{{ name }}", + "entries": { + "{{ sequence }}": { + "sequence": "{{ sequence }}", + "le": "{{ le }}" + } + } + } + } + } + }, + }, + + # policy prefix-list <list-name> rule <rule-num> prefix <ip> + { + "name": "prefix", + "getval": re.compile( + r""" + ^set + \spolicy + \sprefix-(?P<afi>\S+) + \s(?P<name>\S+) + \srule\s(?P<sequence>\d+) + \sprefix\s'(?P<prefix>\S+)' + $""", re.VERBOSE), + "setval": "policy prefix-{{ 'list' if afi == 'ipv4' else 'list6' }} {{ name }} rule {{ sequence }} prefix '{{ prefix }}'", + "result": { + "{{ 'ipv4' if afi == 'list' else 'ipv6' }}": { + "afi": "{{ 'ipv4' if afi == 'list' else 'ipv6' }}", + "prefix_lists": { + "{{ name }}": { + "name": "{{ name }}", + "entries": { + "{{ sequence }}": { + "sequence": "{{ sequence }}", + "prefix": "{{ prefix }}" + } + } + } + } + } + }, + }, + ] + # fmt: on |