diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a9ef17e..0f5d0b9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,34 +1,29 @@ --- repos: - - repo: https://github.com/ansible-network/collection_prep - rev: 1.1.1 - hooks: - - id: update-docs - - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - id: check-merge-conflict - id: debug-statements - id: end-of-file-fixer - id: no-commit-to-branch - id: trailing-whitespace - repo: https://github.com/pre-commit/mirrors-prettier rev: "v3.0.3" hooks: - id: prettier additional_dependencies: - prettier - prettier-plugin-toml - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - id: isort args: ["--filter-files"] - repo: https://github.com/psf/black - rev: 23.9.1 + rev: 24.4.2 hooks: - id: black diff --git a/README.md b/README.md index ef670fb..c3e888b 100644 --- a/README.md +++ b/README.md @@ -1,169 +1,172 @@ # VyOS Collection [![CI](https://zuul-ci.org/gated.svg)](https://dashboard.zuul.ansible.com/t/ansible/project/github.com/ansible-collections/vyos.vyos) [![Codecov](https://codecov.io/gh/ansible-collections/vyos.vyos/branch/main/graph/badge.svg)](https://codecov.io/gh/ansible-collections/vyos.vyos) + +⚠️ **The vyos.vyos collection has been [deprecated](https://forum.ansible.com/t/the-bullhorn-123/2568#project-updates-8) and will reach it's end-of-life on December, 2025. We are no longer accepting new pull requests, except for ones that fix critical bugs or security vulnerabilities. Compatibility with ansible-core>2.17 is not guaranteed.** + The Ansible VyOS collection includes a variety of Ansible content to help automate the management of VyOS network appliances. This collection has been tested against VyOS 1.1.8 (helium). ## Ansible version compatibility -This collection has been tested against following Ansible versions: **>=2.9.10**. +This collection has been tested against following Ansible versions: **<2.18.0,>=2.15.0**. For collections that support Ansible 2.9, please ensure you update your `network_os` to use the fully qualified collection name (for example, `cisco.ios.ios`). Plugins and modules within a collection may be tested with only specific Ansible versions. A collection may contain metadata that identifies these versions. PEP440 is the schema used to describe the versions of Ansible. ### Supported connections The VyOS collection supports ``network_cli`` connections. ## Included content ### Cliconf plugins Name | Description --- | --- [vyos.vyos.vyos](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_cliconf.rst)|Use vyos cliconf to run command on VyOS platform ### Modules Name | Description --- | --- [vyos.vyos.vyos_banner](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_banner_module.rst)|Manage multiline banners on VyOS devices [vyos.vyos.vyos_bgp_address_family](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_bgp_address_family_module.rst)|BGP Address Family Resource Module. [vyos.vyos.vyos_bgp_global](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_bgp_global_module.rst)|BGP Global Resource Module. [vyos.vyos.vyos_command](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_command_module.rst)|Run one or more commands on VyOS devices [vyos.vyos.vyos_config](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_config_module.rst)|Manage VyOS configuration on remote device [vyos.vyos.vyos_facts](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_facts_module.rst)|Get facts about vyos devices. [vyos.vyos.vyos_firewall_global](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_global_module.rst)|FIREWALL global resource module [vyos.vyos.vyos_firewall_interfaces](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_interfaces_module.rst)|FIREWALL interfaces resource module [vyos.vyos.vyos_firewall_rules](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_firewall_rules_module.rst)|FIREWALL rules resource module [vyos.vyos.vyos_hostname](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_hostname_module.rst)|Manages hostname resource module [vyos.vyos.vyos_interfaces](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_interfaces_module.rst)|Interfaces resource module [vyos.vyos.vyos_l3_interfaces](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_l3_interfaces_module.rst)|L3 interfaces resource module [vyos.vyos.vyos_lag_interfaces](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_lag_interfaces_module.rst)|LAG interfaces resource module [vyos.vyos.vyos_lldp_global](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_lldp_global_module.rst)|LLDP global resource module [vyos.vyos.vyos_lldp_interfaces](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_lldp_interfaces_module.rst)|LLDP interfaces resource module [vyos.vyos.vyos_logging](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_logging_module.rst)|Manage logging on network devices [vyos.vyos.vyos_logging_global](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_logging_global_module.rst)|Logging resource module [vyos.vyos.vyos_ntp_global](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ntp_global_module.rst)|Manages ntp modules of Vyos network devices [vyos.vyos.vyos_ospf_interfaces](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ospf_interfaces_module.rst)|OSPF Interfaces Resource Module. [vyos.vyos.vyos_ospfv2](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ospfv2_module.rst)|OSPFv2 resource module [vyos.vyos.vyos_ospfv3](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ospfv3_module.rst)|OSPFV3 resource module [vyos.vyos.vyos_ping](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_ping_module.rst)|Tests reachability using ping from VyOS network devices [vyos.vyos.vyos_prefix_lists](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_prefix_lists_module.rst)|Prefix-Lists resource module for VyOS [vyos.vyos.vyos_route_maps](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_route_maps_module.rst)|Route Map Resource Module. [vyos.vyos.vyos_snmp_server](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_snmp_server_module.rst)|Manages snmp_server resource module [vyos.vyos.vyos_static_routes](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_static_routes_module.rst)|Static routes resource module [vyos.vyos.vyos_system](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_system_module.rst)|Run `set system` commands on VyOS devices [vyos.vyos.vyos_user](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_user_module.rst)|Manage the collection of local users on VyOS device [vyos.vyos.vyos_vlan](https://github.com/ansible-collections/vyos.vyos/blob/main/docs/vyos.vyos.vyos_vlan_module.rst)|Manage VLANs on VyOS network devices Click the ``Content`` button to see the list of content included in this collection. ## Installing this collection You can install the VyOS collection with the Ansible Galaxy CLI: ansible-galaxy collection install vyos.vyos You can also include it in a `requirements.yml` file and install it with `ansible-galaxy collection install -r requirements.yml`, using the format: ```yaml --- collections: - name: vyos.vyos ``` ## Using this collection This collection includes [network resource modules](https://docs.ansible.com/ansible/latest/network/user_guide/network_resource_modules.html). ### Using modules from the VyOS collection in your playbooks You can call modules by their Fully Qualified Collection Namespace (FQCN), such as `vyos.vyos.vyos_static_routes`. The following example task replaces configuration changes in the existing configuration on a VyOS network device, using the FQCN: ```yaml --- - name: Replace device configurations of listed static routes with provided configurations register: result vyos.vyos.vyos_static_routes: &id001 config: - address_families: - afi: ipv4 routes: - dest: 192.0.2.32/28 blackhole_config: distance: 2 next_hops: - forward_router_address: 192.0.2.7 - forward_router_address: 192.0.2.8 - forward_router_address: 192.0.2.9 state: replaced ``` **NOTE**: For Ansible 2.9, you may not see deprecation warnings when you run your playbooks with this collection. Use this documentation to track when a module is deprecated. ### See Also: * [VyOS Platform Options](https://docs.ansible.com/ansible/latest/network/user_guide/platform_vyos.html) * [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details. ## Contributing to this collection We welcome community contributions to this collection. If you find problems, please open an issue or create a PR against the [VyOS collection repository](https://github.com/ansible-collections/vyos). See [Contributing to Ansible-maintained collections](https://docs.ansible.com/ansible/devel/community/contributing_maintained_collections.html#contributing-maintained-collections) for complete details. You can also join us on: - IRC - the ``#ansible-network`` [irc.libera.chat](https://libera.chat/) channel - Slack - https://ansiblenetwork.slack.com See the [Ansible Community Guide](https://docs.ansible.com/ansible/latest/community/index.html) for details on contributing to Ansible. ### Code of Conduct This collection follows the Ansible project's [Code of Conduct](https://docs.ansible.com/ansible/devel/community/code_of_conduct.html). Please read and familiarize yourself with this document. ## Changelogs ## Release notes Release notes are available [here](https://github.com/ansible-collections/vyos.vyos/blob/main/CHANGELOG.rst). ## Roadmap ## More information - [Ansible network resources](https://docs.ansible.com/ansible/latest/network/getting_started/network_resources.html) - [Ansible Collection overview](https://github.com/ansible-collections/overview) - [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html) - [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html) - [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html) ## Licensing GNU General Public License v3.0 or later. See [LICENSE](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text. diff --git a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py index dedc241..3f9e4e6 100644 --- a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py +++ b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py @@ -1,809 +1,808 @@ # # -*- 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.module_utils.six import iteritems 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 ( remove_empties, to_list, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( _bool_to_str, _in_target, _is_w_same, list_diff_want_only, ) 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 if 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/facts/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py index a5c4636..5121c98 100644 --- a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py +++ b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py @@ -1,481 +1,480 @@ # # -*- 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 copy import deepcopy from re import M, findall, search 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/ospfv3.py b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py index 121a348..d4b3e44 100644 --- a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py +++ b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py @@ -1,208 +1,206 @@ # # -*- 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 copy import deepcopy from re import M, findall, search 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("_", "-") + 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/test-requirements.txt b/test-requirements.txt index de3685a..b3159d7 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,12 +1,4 @@ -# For ansible-tox-linters -black==23.3.0 ; python_version >= '3.7' -flake8 -yamllint - # Unit test runner -pytest-ansible ; python_version >= '3.9' -git+https://github.com/ansible-community/pytest-ansible-units.git ; python_version < '3.9' +pytest-ansible pytest-xdist - -# For integration tests -pexpect +pytest-cov diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt deleted file mode 100644 index 496cf86..0000000 --- a/tests/sanity/ignore-2.10.txt +++ /dev/null @@ -1,12 +0,0 @@ -plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` -plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py compile-2.6!skip -plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py import-2.6!skip -plugins/module_utils/network/vyos/config/route_maps/route_maps.py compile-2.6!skip -plugins/module_utils/network/vyos/config/route_maps/route_maps.py import-2.6!skip -plugins/modules/vyos_route_maps.py import-2.6!skip -plugins/modules/vyos_prefix_lists.py import-2.6!skip -plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py import-2.6!skip -plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py compile-2.6!skip -plugins/modules/vyos_logging_global.py import-2.6!skip -plugins/module_utils/network/vyos/config/logging_global/logging_global.py import-2.6!skip -plugins/module_utils/network/vyos/config/logging_global/logging_global.py compile-2.6!skip diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt deleted file mode 100644 index 496cf86..0000000 --- a/tests/sanity/ignore-2.11.txt +++ /dev/null @@ -1,12 +0,0 @@ -plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` -plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py compile-2.6!skip -plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py import-2.6!skip -plugins/module_utils/network/vyos/config/route_maps/route_maps.py compile-2.6!skip -plugins/module_utils/network/vyos/config/route_maps/route_maps.py import-2.6!skip -plugins/modules/vyos_route_maps.py import-2.6!skip -plugins/modules/vyos_prefix_lists.py import-2.6!skip -plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py import-2.6!skip -plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py compile-2.6!skip -plugins/modules/vyos_logging_global.py import-2.6!skip -plugins/module_utils/network/vyos/config/logging_global/logging_global.py import-2.6!skip -plugins/module_utils/network/vyos/config/logging_global/logging_global.py compile-2.6!skip diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt deleted file mode 100644 index 496cf86..0000000 --- a/tests/sanity/ignore-2.12.txt +++ /dev/null @@ -1,12 +0,0 @@ -plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` -plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py compile-2.6!skip -plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py import-2.6!skip -plugins/module_utils/network/vyos/config/route_maps/route_maps.py compile-2.6!skip -plugins/module_utils/network/vyos/config/route_maps/route_maps.py import-2.6!skip -plugins/modules/vyos_route_maps.py import-2.6!skip -plugins/modules/vyos_prefix_lists.py import-2.6!skip -plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py import-2.6!skip -plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py compile-2.6!skip -plugins/modules/vyos_logging_global.py import-2.6!skip -plugins/module_utils/network/vyos/config/logging_global/logging_global.py import-2.6!skip -plugins/module_utils/network/vyos/config/logging_global/logging_global.py compile-2.6!skip diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt deleted file mode 100644 index c835eef..0000000 --- a/tests/sanity/ignore-2.14.txt +++ /dev/null @@ -1 +0,0 @@ -plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.18.txt similarity index 100% rename from tests/sanity/ignore-2.13.txt rename to tests/sanity/ignore-2.18.txt diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt deleted file mode 100644 index 5a69ef6..0000000 --- a/tests/sanity/ignore-2.9.txt +++ /dev/null @@ -1,14 +0,0 @@ -plugins/modules/vyos_logging.py validate-modules:deprecation-mismatch # 2.9 expects METADATA -plugins/modules/vyos_logging.py validate-modules:invalid-documentation # removed_at_date not supported in `deprecated` dict -plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` -plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py compile-2.6!skip -plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py import-2.6!skip -plugins/module_utils/network/vyos/config/route_maps/route_maps.py compile-2.6!skip -plugins/module_utils/network/vyos/config/route_maps/route_maps.py import-2.6!skip -plugins/modules/vyos_route_maps.py import-2.6!skip -plugins/modules/vyos_prefix_lists.py import-2.6!skip -plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py import-2.6!skip -plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py compile-2.6!skip -plugins/modules/vyos_logging_global.py import-2.6!skip -plugins/module_utils/network/vyos/config/logging_global/logging_global.py import-2.6!skip -plugins/module_utils/network/vyos/config/logging_global/logging_global.py compile-2.6!skip diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 6bd8489..0000000 --- a/tox.ini +++ /dev/null @@ -1,32 +0,0 @@ -[tox] -minversion = 1.4.2 -envlist = linters -skipsdist = True - -[testenv] -deps = -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt - -[testenv:black] -install_command = pip install {opts} {packages} -commands = - black --exclude '\.git|\.mypy_cache|\.tox|tests/output' {toxinidir} - -[testenv:linters] -install_command = pip install {opts} {packages} -commands = - black --exclude '\.git|\.mypy_cache|\.tox|tests/output' --check {toxinidir} - flake8 {posargs} - yamllint -s . - -[testenv:venv] -commands = {posargs} - -[flake8] -# E123, E125 skipped as they are invalid PEP-8. - -show-source = True -ignore = E123,E125,E402,W503,W504 -max-line-length = 160 -builtins = _ -exclude = .git,.tox,tests/unit/compat/