diff --git a/plugins/module_utils/network/vyos/argspec/firewall_rules/__init__.py b/plugins/module_utils/network/vyos/argspec/firewall_rules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py new file mode 100644 index 0000000..a018cc0 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py @@ -0,0 +1,263 @@ +# +# -*- 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) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The arg spec for the vyos_firewall_rules module +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +class Firewall_rulesArgs(object): # pylint: disable=R0903 + """The arg spec for the vyos_firewall_rules module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "afi": { + "choices": ["ipv4", "ipv6"], + "required": True, + "type": "str", + }, + "rule_sets": { + "elements": "dict", + "options": { + "default_action": { + "choices": ["drop", "reject", "accept"], + "type": "str", + }, + "description": {"type": "str"}, + "enable_default_log": {"type": "bool"}, + "name": {"type": "str"}, + "rules": { + "elements": "dict", + "options": { + "action": { + "choices": [ + "drop", + "reject", + "accept", + "inspect", + ], + "type": "str", + }, + "description": {"type": "str"}, + "destination": { + "options": { + "address": {"type": "str"}, + "group": { + "options": { + "address_group": { + "type": "str" + }, + "network_group": { + "type": "str" + }, + "port_group": {"type": "str"}, + }, + "type": "dict", + }, + "port": {"type": "str"}, + }, + "type": "dict", + }, + "disabled": {"type": "bool"}, + "fragment": { + "choices": [ + "match-frag", + "match-non-frag", + ], + "type": "str", + }, + "icmp": { + "options": { + "code": {"type": "int"}, + "type": {"type": "int"}, + "type_name": { + "choices": [ + "any", + "echo-reply", + "destination-unreachable", + "network-unreachable", + "host-unreachable", + "protocol-unreachable", + "port-unreachable", + "fragmentation-needed", + "source-route-failed", + "network-unknown", + "host-unknown", + "network-prohibited", + "host-prohibited", + "TOS-network-unreachable", + "TOS-host-unreachable", + "communication-prohibited", + "host-precedence-violation", + "precedence-cutoff", + "source-quench", + "redirect", + "network-redirect", + "host-redirect", + "TOS-network-redirect", + "TOS-host-redirect", + "echo-request", + "router-advertisement", + "router-solicitation", + "time-exceeded", + "ttl-zero-during-transit", + "ttl-zero-during-reassembly", + "parameter-problem", + "ip-header-bad", + "required-option-missing", + "timestamp-request", + "timestamp-reply", + "address-mask-request", + "address-mask-reply", + "ping", + "pong", + "ttl-exceeded", + ], + "type": "str", + }, + }, + "type": "dict", + }, + "ipsec": { + "choices": ["match-ipsec", "match-none"], + "type": "str", + }, + "limit": { + "options": { + "burst": {"type": "int"}, + "rate": { + "options": { + "number": {"type": "int"}, + "unit": {"type": "str"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "number": {"required": True, "type": "int"}, + "p2p": { + "elements": "dict", + "options": { + "application": { + "choices": [ + "all", + "applejuice", + "bittorrent", + "directconnect", + "edonkey", + "gnutella", + "kazaa", + ], + "type": "str", + } + }, + "type": "list", + }, + "protocol": {"type": "str"}, + "recent": { + "options": { + "count": {"type": "int"}, + "time": {"type": "int"}, + }, + "type": "dict", + }, + "source": { + "options": { + "address": {"type": "str"}, + "group": { + "options": { + "address_group": { + "type": "str" + }, + "network_group": { + "type": "str" + }, + "port_group": {"type": "str"}, + }, + "type": "dict", + }, + "mac_address": {"type": "str"}, + "port": {"type": "str"}, + }, + "type": "dict", + }, + "state": { + "options": { + "established": {"type": "bool"}, + "invalid": {"type": "bool"}, + "new": {"type": "bool"}, + "related": {"type": "bool"}, + }, + "type": "dict", + }, + "tcp": { + "options": {"flags": {"type": "str"}}, + "type": "dict", + }, + "time": { + "options": { + "monthdays": {"type": "str"}, + "startdate": {"type": "str"}, + "starttime": {"type": "str"}, + "stopdate": {"type": "str"}, + "stoptime": {"type": "str"}, + "utc": {"type": "bool"}, + "weekdays": {"type": "str"}, + }, + "type": "dict", + }, + }, + "type": "list", + }, + }, + "type": "list", + }, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/argspec/static_routes/__init__.py b/plugins/module_utils/network/vyos/argspec/static_routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py b/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py new file mode 100644 index 0000000..8ecd955 --- /dev/null +++ b/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py @@ -0,0 +1,99 @@ +# +# -*- 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) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# +""" +The arg spec for the vyos_static_routes module +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +class Static_routesArgs(object): # pylint: disable=R0903 + """The arg spec for the vyos_static_routes module + """ + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "address_families": { + "elements": "dict", + "options": { + "afi": { + "choices": ["ipv4", "ipv6"], + "required": True, + "type": "str", + }, + "routes": { + "elements": "dict", + "options": { + "blackhole_config": { + "options": { + "distance": {"type": "int"}, + "type": {"type": "str"}, + }, + "type": "dict", + }, + "dest": {"required": True, "type": "str"}, + "next_hops": { + "elements": "dict", + "options": { + "admin_distance": {"type": "int"}, + "enabled": {"type": "bool"}, + "forward_router_address": { + "required": True, + "type": "str", + }, + "interface": {"type": "str"}, + }, + "type": "list", + }, + }, + "type": "list", + }, + }, + "type": "list", + } + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/plugins/module_utils/network/vyos/config/firewall_rules/__init__.py b/plugins/module_utils/network/vyos/config/firewall_rules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py new file mode 100644 index 0000000..e58593f --- /dev/null +++ b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py @@ -0,0 +1,1034 @@ +# +# -*- 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_firewall_rules 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_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, + remove_empties, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( + Facts, +) +from ansible.module_utils.six import iteritems +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( + list_diff_want_only, +) + + +class Firewall_rules(ConfigBase): + """ + The vyos_firewall_rules class + """ + + gather_subset = [ + "!all", + "!min", + ] + + gather_network_resources = [ + "firewall_rules", + ] + + def __init__(self, module): + super(Firewall_rules, self).__init__(module) + + def get_firewall_rules_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 + ) + firewall_rules_facts = facts["ansible_network_resources"].get( + "firewall_rules" + ) + if not firewall_rules_facts: + return [] + return firewall_rules_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_firewall_rules_facts = self.get_firewall_rules_facts() + else: + existing_firewall_rules_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_firewall_rules_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_firewall_rules_facts = self.get_firewall_rules_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_firewall_rules_facts( + data=running_config + ) + else: + changed_firewall_rules_facts = [] + + if self.state in self.ACTION_STATES: + result["before"] = existing_firewall_rules_facts + if result["changed"]: + result["after"] = changed_firewall_rules_facts + elif self.state == "gathered": + result["gathered"] = changed_firewall_rules_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_firewall_rules_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_firewall_rules_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 == "overridden": + commands.extend(self._state_overridden(w, h)) + elif self.state == "deleted": + commands.extend(self._state_deleted(w, h)) + elif w: + if self.state == "merged" or self.state == "rendered": + commands.extend(self._state_merged(w, h)) + elif self.state == "replaced": + commands.extend(self._state_replaced(w, h)) + return commands + + 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: + for h in have: + r_sets = self._get_r_sets(h) + for rs in r_sets: + w = self.search_r_sets_in_have(want, rs["name"], "r_list") + commands.extend( + self._add_r_sets(h["afi"], rs, w, opr=False) + ) + commands.extend(self._state_merged(want, have)) + return commands + + def _state_overridden(self, want, have): + """ The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + if have: + for h in have: + r_sets = self._get_r_sets(h) + for rs in r_sets: + w = self.search_r_sets_in_have(want, rs["name"], "r_list") + if not w: + commands.append( + self._compute_command( + h["afi"], rs["name"], remove=True + ) + ) + else: + commands.extend( + self._add_r_sets(h["afi"], rs, w, opr=False) + ) + commands.extend(self._state_merged(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 = [] + for w in want: + r_sets = self._get_r_sets(w) + for rs in r_sets: + h = self.search_r_sets_in_have(have, rs["name"], "r_list") + commands.extend(self._add_r_sets(w["afi"], rs, h)) + return commands + + def _state_deleted(self, want, 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 want: + for w in want: + r_sets = self._get_r_sets(w) + if r_sets: + for rs in r_sets: + h = self.search_r_sets_in_have( + have, rs["name"], "r_list" + ) + if h: + w_rules = rs.get("rules") or [] + h_rules = h.get("rules") or [] + if w_rules and h_rules: + for rule in w_rules: + if self.search_r_sets_in_have( + h_rules, rule["number"], "rules" + ): + commands.append( + self._add_r_base_attrib( + w["afi"], + rs["name"], + "number", + rule, + opr=False, + ) + ) + else: + commands.append( + self._compute_command( + w["afi"], h["name"], remove=True + ) + ) + elif have: + for h in have: + if h["afi"] == w["afi"]: + commands.append( + self._compute_command(w["afi"], remove=True) + ) + elif have: + for h in have: + r_sets = self._get_r_sets(h) + if r_sets: + commands.append( + self._compute_command(afi=h["afi"], remove=True) + ) + return commands + + def _add_r_sets(self, afi, want, have, opr=True): + """ + This function forms the set/delete commands based on the 'opr' type + for rule-sets attributes. + :param afi: address type. + :param want: desired config. + :param have: target config. + :param opr: True/False. + :return: generated commands list. + """ + commands = [] + l_set = ("description", "default_action", "enable_default_log") + h_rs = {} + h_rules = {} + w_rs = deepcopy(remove_empties(want)) + w_rules = w_rs.pop("rules", None) + if have: + h_rs = deepcopy(remove_empties(have)) + h_rules = h_rs.pop("rules", None) + if w_rs: + for key, val in iteritems(w_rs): + if ( + opr + and key in l_set + and not (h_rs and self._is_w_same(w_rs, h_rs, key)) + ): + if key == "enable_default_log": + if val and ( + not h_rs or key not in h_rs or not h_rs[key] + ): + commands.append( + self._add_rs_base_attrib( + afi, want["name"], key, w_rs + ) + ) + else: + commands.append( + self._add_rs_base_attrib( + afi, want["name"], key, w_rs + ) + ) + elif not opr and key in l_set: + if ( + key == "enable_default_log" + and val + and h_rs + and (key not in h_rs or not h_rs[key]) + ): + commands.append( + self._add_rs_base_attrib( + afi, want["name"], key, w_rs, opr + ) + ) + elif not (h_rs and self._in_target(h_rs, key)): + commands.append( + self._add_rs_base_attrib( + afi, want["name"], key, w_rs, opr + ) + ) + commands.extend( + self._add_rules(afi, want["name"], w_rules, h_rules, opr) + ) + if h_rules: + have["rules"] = h_rules + if w_rules: + want["rules"] = w_rules + return commands + + def _add_rules(self, afi, name, w_rules, h_rules, opr=True): + """ + This function forms the set/delete commands based on the 'opr' type + for rules attributes. + :param want: desired config. + :param have: target config. + :return: generated commands list. + """ + commands = [] + l_set = ( + "ipsec", + "action", + "number", + "protocol", + "fragment", + "disabled", + "description", + ) + if w_rules: + for w in w_rules: + cmd = self._compute_command(afi, name, w["number"], opr=opr) + h = self.search_r_sets_in_have( + h_rules, w["number"], type="rules" + ) + for key, val in iteritems(w): + if val: + if ( + opr + and key in l_set + and not (h and self._is_w_same(w, h, key)) + ): + if key == "disabled": + if not ( + not val + and (not h or key not in h or not h[key]) + ): + commands.append( + self._add_r_base_attrib( + afi, name, key, w + ) + ) + else: + commands.append( + self._add_r_base_attrib(afi, name, key, w) + ) + elif not opr: + if key == "number" and self._is_del(l_set, h): + commands.append( + self._add_r_base_attrib( + afi, name, key, w, opr=opr + ) + ) + continue + elif ( + key == "disabled" + and val + and h + and (key not in h or not h[key]) + ): + commands.append( + self._add_r_base_attrib( + afi, name, key, w, opr=opr + ) + ) + elif ( + key in l_set + and not (h and self._in_target(h, key)) + and not self._is_del(l_set, h) + ): + commands.append( + self._add_r_base_attrib( + afi, name, key, w, opr=opr + ) + ) + elif key == "p2p": + commands.extend(self._add_p2p(key, w, h, cmd, opr)) + elif key == "tcp": + commands.extend(self._add_tcp(key, w, h, cmd, opr)) + elif key == "time": + commands.extend( + self._add_time(key, w, h, cmd, opr) + ) + elif key == "icmp": + commands.extend( + self._add_icmp(key, w, h, cmd, opr) + ) + elif key == "state": + commands.extend( + self._add_state(key, w, h, cmd, opr) + ) + elif key == "limit": + commands.extend( + self._add_limit(key, w, h, cmd, opr) + ) + elif key == "recent": + commands.extend( + self._add_recent(key, w, h, cmd, opr) + ) + elif key == "destination" or key == "source": + commands.extend( + self._add_src_or_dest(key, w, h, cmd, opr) + ) + return commands + + def _add_p2p(self, attr, w, h, cmd, opr): + """ + This function forms the set/delete commands based on the 'opr' type + for p2p applications attributes. + :param want: desired config. + :param have: target config. + :return: generated commands list. + """ + commands = [] + have = [] + if w: + want = w.get(attr) or [] + if h: + have = h.get(attr) or [] + if want: + if opr: + applications = list_diff_want_only(want, have) + for app in applications: + commands.append( + cmd + (" " + attr + " " + app["application"]) + ) + elif not opr and have: + applications = list_diff_want_only(want, have) + for app in applications: + commands.append( + cmd + (" " + attr + " " + app["application"]) + ) + return commands + + def _add_state(self, attr, w, h, cmd, opr): + """ + This function forms the command for 'state' attributes based on the 'opr'. + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + h_state = {} + commands = [] + l_set = ("new", "invalid", "related", "established") + if w[attr]: + if h and attr in h.keys(): + h_state = h.get(attr) or {} + for item, val in iteritems(w[attr]): + if ( + opr + and item in l_set + and not ( + h_state and self._is_w_same(w[attr], h_state, item) + ) + ): + commands.append( + cmd + + ( + " " + + attr + + " " + + item + + " " + + self._bool_to_str(val) + ) + ) + elif ( + not opr + and item in l_set + and not (h_state and self._in_target(h_state, item)) + ): + commands.append(cmd + (" " + attr + " " + item)) + return commands + + def _add_recent(self, attr, w, h, cmd, opr): + """ + This function forms the command for 'recent' attributes based on the 'opr'. + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + commands = [] + h_recent = {} + l_set = ("count", "time") + if w[attr]: + if h and attr in h.keys(): + h_recent = h.get(attr) or {} + for item, val in iteritems(w[attr]): + if ( + opr + and item in l_set + and not ( + h_recent and self._is_w_same(w[attr], h_recent, item) + ) + ): + commands.append( + cmd + (" " + attr + " " + item + " " + str(val)) + ) + elif ( + not opr + and item in l_set + and not (h_recent and self._in_target(h_recent, item)) + ): + commands.append(cmd + (" " + attr + " " + item)) + return commands + + def _add_icmp(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'icmp' attributes based on the 'opr'. + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + commands = [] + h_icmp = {} + l_set = ("code", "type", "type_name") + if w[attr]: + if h and attr in h.keys(): + h_icmp = h.get(attr) or {} + for item, val in iteritems(w[attr]): + if ( + opr + and item in l_set + and not (h_icmp and self._is_w_same(w[attr], h_icmp, item)) + ): + if item == "type_name": + if "ipv6-name" in cmd: + commands.append( + cmd + + (" " + "icmpv6" + " " + "type" + " " + val) + ) + else: + commands.append( + cmd + + ( + " " + + attr + + " " + + item.replace("_", "-") + + " " + + val + ) + ) + else: + commands.append( + cmd + (" " + attr + " " + item + " " + str(val)) + ) + elif ( + not opr + and item in l_set + and not (h_icmp and self._in_target(h_icmp, item)) + ): + commands.append(cmd + (" " + attr + " " + item)) + return commands + + def _add_time(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'time' attributes based on the 'opr'. + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + commands = [] + h_time = {} + l_set = ( + "utc", + "stopdate", + "stoptime", + "weekdays", + "monthdays", + "startdate", + "starttime", + ) + if w[attr]: + if h and attr in h.keys(): + h_time = h.get(attr) or {} + for item, val in iteritems(w[attr]): + if ( + opr + and item in l_set + and not (h_time and self._is_w_same(w[attr], h_time, item)) + ): + if item == "utc": + if not ( + not val and (not h_time or item not in h_time) + ): + commands.append(cmd + (" " + attr + " " + item)) + else: + commands.append( + cmd + (" " + attr + " " + item + " " + val) + ) + elif ( + not opr + and item in l_set + and not (h_time and self._is_w_same(w[attr], h_time, item)) + ): + commands.append(cmd + (" " + attr + " " + item)) + return commands + + def _add_tcp(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'tcp' attributes based on the 'opr'. + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + h_tcp = {} + commands = [] + if w[attr]: + key = "flags" + flags = w[attr].get(key) or {} + if flags: + if h and key in h[attr].keys(): + h_tcp = h[attr].get(key) or {} + if flags: + if opr and not ( + h_tcp and self._is_w_same(w[attr], h[attr], key) + ): + commands.append( + cmd + (" " + attr + " " + key + " " + flags) + ) + if not opr and not ( + h_tcp and self._is_w_same(w[attr], h[attr], key) + ): + commands.append( + cmd + (" " + attr + " " + key + " " + flags) + ) + return commands + + def _add_limit(self, attr, w, h, cmd, opr): + """ + This function forms the commands for 'limit' attributes based on the 'opr'. + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + h_limit = {} + commands = [] + if w[attr]: + key = "burst" + if ( + opr + and key in w[attr].keys() + and not ( + h + and attr in h.keys() + and self._is_w_same(w[attr], h[attr], key) + ) + ): + commands.append( + cmd + + (" " + attr + " " + key + " " + str(w[attr].get(key))) + ) + elif ( + not opr + and key in w[attr].keys() + and not ( + h and attr in h.keys() and self._in_target(h[attr], key) + ) + ): + commands.append( + cmd + + (" " + attr + " " + key + " " + str(w[attr].get(key))) + ) + key = "rate" + rate = w[attr].get(key) or {} + if rate: + if h and key in h[attr].keys(): + h_limit = h[attr].get(key) or {} + if "unit" in rate and "number" in rate: + if opr and not ( + h_limit + and self._is_w_same(rate, h_limit, "unit") + and self.is_w_same(rate, h_limit, "number") + ): + commands.append( + cmd + + ( + " " + + attr + + " " + + key + + " " + + str(rate["number"]) + + "/" + + rate["unit"] + ) + ) + if not opr and not ( + h_limit + and self._is_w_same(rate, h_limit, "unit") + and self._is_w_same(rate, h_limit, "number") + ): + commands.append(cmd + (" " + attr + " " + key)) + return commands + + def _add_src_or_dest(self, attr, w, h, cmd, opr=True): + """ + This function forms the commands for 'src/dest' attributes based on the 'opr'. + :param attr: attribute name. + :param w: base config. + :param h: target config. + :param cmd: commands to be prepend. + :return: generated list of commands. + """ + commands = [] + h_group = {} + g_set = ("port_group", "address_group", "network_group") + if w[attr]: + keys = ("address", "mac_address", "port") + for key in keys: + if ( + opr + and key in w[attr].keys() + and not ( + h + and attr in h.keys() + and self._is_w_same(w[attr], h[attr], key) + ) + ): + commands.append( + cmd + + ( + " " + + attr + + " " + + key.replace("_", "-") + + " " + + w[attr].get(key) + ) + ) + elif ( + not opr + and key in w[attr].keys() + and not ( + h + and attr in h.keys() + and self._in_target(h[attr], key) + ) + ): + commands.append(cmd + (" " + attr + " " + key)) + + key = "group" + group = w[attr].get(key) or {} + if group: + if h and key in h[attr].keys(): + h_group = h[attr].get(key) or {} + for item, val in iteritems(group): + if val: + if ( + opr + and item in g_set + and not ( + h_group + and self._is_w_same(group, h_group, item) + ) + ): + commands.append( + cmd + + ( + " " + + attr + + " " + + key + + " " + + item.replace("_", "-") + + " " + + val + ) + ) + elif ( + not opr + and item in g_set + and not ( + h_group and self._in_target(h_group, item) + ) + ): + commands.append( + cmd + + ( + " " + + attr + + " " + + key + + " " + + item.replace("_", "-") + ) + ) + return commands + + def search_r_sets_in_have(self, have, w_name, type="rule_sets"): + """ + 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: + key = "name" + if type == "rules": + key = "number" + for r in have: + if r[key] == w_name: + return r + elif type == "r_list": + for h in have: + r_sets = self._get_r_sets(h) + for rs in r_sets: + if rs[key] == w_name: + return rs + else: + for rs in have: + if rs[key] == w_name: + return rs + return None + + def _get_r_sets(self, item, type="rule_sets"): + """ + This function returns the list of rule-sets/rules. + :param item: config dictionary. + :param type: rule_sets/rule/r_list. + :return: list of rule-sets/rules. + """ + rs_list = [] + r_sets = item[type] + if r_sets: + for rs in r_sets: + rs_list.append(rs) + return rs_list + + def _compute_command( + self, + afi, + name=None, + number=None, + attrib=None, + value=None, + remove=False, + opr=True, + ): + """ + This function construct the add/delete command based on passed attributes. + :param afi: address type. + :param name: rule-set name. + :param number: rule-number. + :param attrib: attribute name. + :param value: value. + :param remove: True if delete command needed to be construct. + :param opr: opeeration flag. + :return: generated command. + """ + if remove or not opr: + cmd = "delete firewall " + self._get_fw_type(afi) + else: + cmd = "set firewall " + self._get_fw_type(afi) + if name: + cmd += " " + name + if number: + cmd += " rule " + str(number) + if attrib: + cmd += " " + attrib.replace("_", "-") + if ( + value + and opr + and attrib != "enable_default_log" + and attrib != "disabled" + ): + cmd += " '" + str(value) + "'" + return cmd + + def _add_r_base_attrib(self, afi, name, attr, rule, opr=True): + """ + This function forms the command for 'rules' attributes which doesn't + have further sub attributes. + :param afi: address type. + :param name: rule-set name + :param attrib: attribute name + :param rule: rule config dictionary. + :param opr: True/False. + :return: generated command. + """ + if attr == "number": + command = self._compute_command( + afi=afi, name=name, number=rule["number"], opr=opr + ) + else: + command = self._compute_command( + afi=afi, + name=name, + number=rule["number"], + attrib=attr, + value=rule[attr], + opr=opr, + ) + return command + + def _add_rs_base_attrib(self, afi, name, attrib, rule, opr=True): + """ + + This function forms the command for 'rule-sets' attributes which doesn't + have further sub attributes. + :param afi: address type. + :param name: rule-set name + :param attrib: attribute name + :param rule: rule config dictionary. + :param opr: True/False. + :return: generated command. + """ + command = self._compute_command( + afi=afi, name=name, attrib=attrib, value=rule[attrib], opr=opr + ) + return command + + def _bool_to_str(self, val): + """ + This function converts the bool value into string. + :param val: bool value. + :return: enable/disable. + """ + return "enable" if val else "disable" + + def _get_fw_type(self, afi): + """ + This function returns the firewall rule-set type based on IP address. + :param afi: address type + :return: rule-set type. + """ + return "ipv6-name" if afi == "ipv6" else "name" + + def _is_del(self, l_set, h, key="number"): + """ + This function checks whether rule needs to be deleted based on + the rule number. + :param l_set: attribute set. + :param h: target config. + :param key: number. + :return: True/False. + """ + return key in l_set and not (h and self._in_target(h, key)) + + def _is_w_same(self, w, h, key): + """ + This function checks whether the key value is same in base and + target config dictionary. + :param w: base config. + :param h: target config. + :param key:attribute name. + :return: True/False. + """ + return True if h and key in h and h[key] == w[key] else False + + def _in_target(self, h, key): + """ + This function checks whether the target nexist and key present in target config. + :param h: target config. + :param key: attribute name. + :return: True/False. + """ + return True if h and key in h else False + + def _is_base_attrib(self, key): + """ + This function checks whether key is present in predefined + based attribute set. + :param key: + :return: True/False. + """ + r_set = ( + "p2p", + "ipsec", + "action", + "fragment", + "protocol", + "disabled", + "description", + "mac_address", + "default_action", + "enable_default_log", + ) + return True if key in r_set else False diff --git a/plugins/module_utils/network/vyos/config/static_routes/__init__.py b/plugins/module_utils/network/vyos/config/static_routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/config/static_routes/static_routes.py b/plugins/module_utils/network/vyos/config/static_routes/static_routes.py new file mode 100644 index 0000000..e93d4ee --- /dev/null +++ b/plugins/module_utils/network/vyos/config/static_routes/static_routes.py @@ -0,0 +1,627 @@ +# +# -*- 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_static_routes 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_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( + ConfigBase, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, + dict_diff, + remove_empties, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( + Facts, +) +from ansible.module_utils.six import iteritems +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( + get_route_type, + get_lst_diff_for_dicts, + get_lst_same_for_dicts, + dict_delete, +) + + +class Static_routes(ConfigBase): + """ + The vyos_static_routes class + """ + + gather_subset = [ + "!all", + "!min", + ] + + gather_network_resources = [ + "static_routes", + ] + + def __init__(self, module): + super(Static_routes, self).__init__(module) + + def get_static_routes_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 + ) + static_routes_facts = facts["ansible_network_resources"].get( + "static_routes" + ) + if not static_routes_facts: + return [] + return static_routes_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_static_routes_facts = self.get_static_routes_facts() + else: + existing_static_routes_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_static_routes_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_static_routes_facts = self.get_static_routes_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_static_routes_facts( + data=running_config + ) + else: + changed_static_routes_facts = [] + + if self.state in self.ACTION_STATES: + result["before"] = existing_static_routes_facts + if result["changed"]: + result["after"] = changed_static_routes_facts + elif self.state == "gathered": + result["gathered"] = changed_static_routes_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_static_routes_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_static_routes_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """ 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 want + ): + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + self.state + ) + ) + if self.state == "overridden": + commands.extend(self._state_overridden(want=want, have=have)) + elif self.state == "deleted": + commands.extend(self._state_deleted(want=want, have=have)) + elif want: + routes = self._get_routes(want) + for r in routes: + h_item = self.search_route_in_have(have, r["dest"]) + if self.state == "merged" or self.state == "rendered": + commands.extend(self._state_merged(want=r, have=h_item)) + elif self.state == "replaced": + commands.extend(self._state_replaced(want=r, have=h_item)) + return commands + + def search_route_in_have(self, have, want_dest): + """ + This function returns the route if its found in + have config. + :param have: + :param dest: + :return: the matched route + """ + routes = self._get_routes(have) + for r in routes: + if r["dest"] == want_dest: + return r + 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: + for key, value in iteritems(want): + if value: + if key == "next_hops": + commands.extend(self._update_next_hop(want, have)) + elif key == "blackhole_config": + commands.extend( + self._update_blackhole(key, want, have) + ) + commands.extend(self._state_merged(want, have)) + return commands + + def _state_overridden(self, want, have): + """ The command generator when state is overridden + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + routes = self._get_routes(have) + for r in routes: + route_in_want = self.search_route_in_have(want, r["dest"]) + if not route_in_want: + commands.append(self._compute_command(r["dest"], remove=True)) + routes = self._get_routes(want) + for r in routes: + route_in_have = self.search_route_in_have(have, r["dest"]) + commands.extend(self._state_replaced(r, route_in_have)) + return commands + + def _state_merged(self, want, have, opr=True): + """ The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + if have: + commands.extend(self._render_updates(want, have)) + else: + commands.extend(self._render_set_commands(want)) + return commands + + def _state_deleted(self, want, 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 want: + routes = self._get_routes(want) + if not routes: + for w in want: + af = w["address_families"] + for item in af: + if self.afi_in_have(have, item): + commands.append( + self._compute_command( + afi=item["afi"], remove=True + ) + ) + for r in routes: + h_route = self.search_route_in_have(have, r["dest"]) + if h_route: + commands.extend( + self._render_updates(r, h_route, opr=False) + ) + else: + routes = self._get_routes(have) + if self._is_ip_route_exist(routes): + commands.append(self._compute_command(afi="ipv4", remove=True)) + if self._is_ip_route_exist(routes, "route6"): + commands.append(self._compute_command(afi="ipv6", remove=True)) + return commands + + def _render_set_commands(self, want): + """ + This function returns the list of commands to add attributes which are + present in want + :param want: + :return: list of commands. + """ + commands = [] + have = {} + for key, value in iteritems(want): + if value: + if key == "dest": + commands.append(self._compute_command(dest=want["dest"])) + elif key == "blackhole_config": + commands.extend(self._add_blackhole(key, want, have)) + + elif key == "next_hops": + commands.extend(self._add_next_hop(want, have)) + + return commands + + def _add_blackhole(self, key, want, have): + """ + This function gets the diff for blackhole config specific attributes + and form the commands for attributes which are present in want but not in have. + :param key: + :param want: + :param have: + :return: list of commands + """ + commands = [] + want_copy = deepcopy(remove_empties(want)) + have_copy = deepcopy(remove_empties(have)) + + want_blackhole = want_copy.get(key) or {} + have_blackhole = have_copy.get(key) or {} + + updates = dict_delete(want_blackhole, have_blackhole) + if updates: + for attrib, value in iteritems(updates): + if value: + if attrib == "distance": + commands.append( + self._compute_command( + dest=want["dest"], + key="blackhole", + attrib=attrib, + remove=False, + value=str(value), + ) + ) + elif attrib == "type": + commands.append( + self._compute_command( + dest=want["dest"], key="blackhole" + ) + ) + return commands + + def _add_next_hop(self, want, have, opr=True): + """ + This function gets the diff for next hop specific attributes + and form the commands to add attributes which are present in want but not in have. + :param want: + :param have: + :return: list of commands. + """ + commands = [] + want_copy = deepcopy(remove_empties(want)) + have_copy = deepcopy(remove_empties(have)) + if not opr: + diff_next_hops = get_lst_same_for_dicts( + want_copy, have_copy, "next_hops" + ) + else: + diff_next_hops = get_lst_diff_for_dicts( + want_copy, have_copy, "next_hops" + ) + if diff_next_hops: + for hop in diff_next_hops: + for element in hop: + if element == "forward_router_address": + commands.append( + self._compute_command( + dest=want["dest"], + key="next-hop", + value=hop[element], + opr=opr, + ) + ) + elif element == "enabled" and not hop[element]: + commands.append( + self._compute_command( + dest=want["dest"], + key="next-hop", + attrib=hop["forward_router_address"], + value="disable", + opr=opr, + ) + ) + elif element == "admin_distance": + commands.append( + self._compute_command( + dest=want["dest"], + key="next-hop", + attrib=hop["forward_router_address"] + + " " + + element, + value=str(hop[element]), + opr=opr, + ) + ) + elif element == "interface": + commands.append( + self._compute_command( + dest=want["dest"], + key="next-hop", + attrib=hop["forward_router_address"] + + " " + + element, + value=hop[element], + opr=opr, + ) + ) + return commands + + def _update_blackhole(self, key, want, have): + """ + This function gets the difference for blackhole dict and + form the commands to delete the attributes which are present in have but not in want. + :param want: + :param have: + :return: list of commands + :param key: + :param want: + :param have: + :return: list of commands + """ + commands = [] + want_copy = deepcopy(remove_empties(want)) + have_copy = deepcopy(remove_empties(have)) + + want_blackhole = want_copy.get(key) or {} + have_blackhole = have_copy.get(key) or {} + updates = dict_delete(have_blackhole, want_blackhole) + if updates: + for attrib, value in iteritems(updates): + if value: + if attrib == "distance": + commands.append( + self._compute_command( + dest=want["dest"], + key="blackhole", + attrib=attrib, + remove=True, + value=str(value), + ) + ) + elif ( + attrib == "type" + and "distance" not in want_blackhole.keys() + ): + commands.append( + self._compute_command( + dest=want["dest"], key="blackhole", remove=True + ) + ) + return commands + + def _update_next_hop(self, want, have, opr=True): + """ + This function gets the difference for next_hops list and + form the commands to delete the attributes which are present in have but not in want. + :param want: + :param have: + :return: list of commands + """ + commands = [] + + want_copy = deepcopy(remove_empties(want)) + have_copy = deepcopy(remove_empties(have)) + + diff_next_hops = get_lst_diff_for_dicts( + have_copy, want_copy, "next_hops" + ) + if diff_next_hops: + for hop in diff_next_hops: + for element in hop: + if element == "forward_router_address": + commands.append( + self._compute_command( + dest=want["dest"], + key="next-hop", + value=hop[element], + remove=True, + ) + ) + elif element == "enabled": + commands.append( + self._compute_command( + dest=want["dest"], + key="next-hop", + attrib=hop["forward_router_address"], + value="disable", + remove=True, + ) + ) + elif element == "admin_distance": + commands.append( + self._compute_command( + dest=want["dest"], + key="next-hop", + attrib=hop["forward_router_address"] + + " " + + element, + value=str(hop[element]), + remove=True, + ) + ) + elif element == "interface": + commands.append( + self._compute_command( + dest=want["dest"], + key="next-hop", + attrib=hop["forward_router_address"] + + " " + + element, + value=hop[element], + remove=True, + ) + ) + return commands + + def _render_updates(self, want, have, opr=True): + """ + This function takes the diff between want and have and + invokes the appropriate functions to create the commands + to update the attributes. + :param want: + :param have: + :return: list of commands + """ + commands = [] + want_nh = want.get("next_hops") or [] + # delete static route operation per destination + if not opr and not want_nh: + commands.append( + self._compute_command(dest=want["dest"], remove=True) + ) + + else: + temp_have_next_hops = have.pop("next_hops", None) + temp_want_next_hops = want.pop("next_hops", None) + updates = dict_diff(have, want) + if temp_have_next_hops: + have["next_hops"] = temp_have_next_hops + if temp_want_next_hops: + want["next_hops"] = temp_want_next_hops + commands.extend(self._add_next_hop(want, have, opr=opr)) + + if opr and updates: + for key, value in iteritems(updates): + if value: + if key == "blackhole_config": + commands.extend( + self._add_blackhole(key, want, have) + ) + return commands + + def _compute_command( + self, + dest=None, + key=None, + attrib=None, + value=None, + remove=False, + afi=None, + opr=True, + ): + """ + This functions construct the required command based on the passed arguments. + :param dest: + :param key: + :param attrib: + :param value: + :param remove: + :return: constructed command + """ + if remove or not opr: + cmd = "delete protocols static " + self.get_route_type(dest, afi) + else: + cmd = "set protocols static " + self.get_route_type(dest, afi) + if dest: + cmd += " " + dest + if key: + cmd += " " + key + if attrib: + cmd += " " + attrib + if value: + cmd += " '" + value + "'" + return cmd + + def afi_in_have(self, have, w_item): + """ + This functions checks for the afi + list in have + :param have: + :param w_item: + :return: + """ + if have: + for h in have: + af = h.get("address_families") or [] + for item in af: + if w_item["afi"] == item["afi"]: + return True + return False + + def get_route_type(self, dest=None, afi=None): + """ + This function returns the route type based on + destination ip address or afi + :param address: + :return: + """ + if dest: + return get_route_type(dest) + elif afi == "ipv4": + return "route" + elif afi == "ipv6": + return "route6" + + def _is_ip_route_exist(self, routes, type="route"): + """ + This functions checks for the type of route. + :param routes: + :param type: + :return: True/False + """ + for r in routes: + if type == self.get_route_type(r["dest"]): + return True + return False + + def _get_routes(self, lst): + """ + This function returns the list of routes + :param lst: list of address families + :return: list of routes + """ + r_list = [] + for item in lst: + af = item["address_families"] + for element in af: + routes = element.get("routes") or [] + for r in routes: + r_list.append(r) + return r_list diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py index b5816c2..8f0a3bb 100644 --- a/plugins/module_utils/network/vyos/facts/facts.py +++ b/plugins/module_utils/network/vyos/facts/facts.py @@ -1,76 +1,83 @@ # Copyright 2019 Red Hat # GNU General Public License v3.0+ # (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) """ The facts class for vyos this file validates each subset of facts and selectively calls the appropriate facts gathering function """ from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import ( FactsBase, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.interfaces.interfaces import ( InterfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.l3_interfaces.l3_interfaces import ( L3_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lag_interfaces.lag_interfaces import ( Lag_interfacesFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_global.lldp_global import ( Lldp_globalFacts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_interfaces.lldp_interfaces import ( Lldp_interfacesFacts, ) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules import ( + Firewall_rulesFacts, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import ( + Static_routesFacts, +) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.legacy.base import ( Default, Neighbors, Config, ) FACT_LEGACY_SUBSETS = dict(default=Default, neighbors=Neighbors, config=Config) FACT_RESOURCE_SUBSETS = dict( interfaces=InterfacesFacts, l3_interfaces=L3_interfacesFacts, lag_interfaces=Lag_interfacesFacts, lldp_global=Lldp_globalFacts, lldp_interfaces=Lldp_interfacesFacts, + static_routes=Static_routesFacts, + firewall_rules=Firewall_rulesFacts, ) class Facts(FactsBase): """ The fact class for vyos """ VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys()) def __init__(self, module): super(Facts, self).__init__(module) def get_facts( self, legacy_facts_type=None, resource_facts_type=None, data=None ): """ Collect the facts for vyos :param legacy_facts_type: List of legacy facts types :param resource_facts_type: List of resource fact types :param data: previously collected conf :rtype: dict :return: the facts gathered """ if self.VALID_RESOURCE_SUBSETS: self.get_network_resources_facts( FACT_RESOURCE_SUBSETS, resource_facts_type, data ) if self.VALID_LEGACY_GATHER_SUBSETS: self.get_network_legacy_facts( FACT_LEGACY_SUBSETS, legacy_facts_type ) - return self.ansible_facts, self._warnings diff --git a/plugins/module_utils/network/vyos/facts/firewall_rules/__init__.py b/plugins/module_utils/network/vyos/facts/firewall_rules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py new file mode 100644 index 0000000..971ea6f --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py @@ -0,0 +1,380 @@ +# +# -*- 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 firewall_rules 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 re import findall, search, M +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import ( + Firewall_rulesArgs, +) + + +class Firewall_rulesFacts(object): + """ The vyos firewall_rules fact class + """ + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Firewall_rulesArgs.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 firewall_rules + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if not data: + # typically data is populated from the current device configuration + # data = connection.get('show running-config | section ^interface') + # using mock data instead + data = self.get_device_data(connection) + # split the config into instances of the resource + objs = [] + v6_rules = findall( + r"^set firewall ipv6-name (?:\'*)(\S+)(?:\'*)", data, M + ) + v4_rules = findall(r"^set firewall name (?:\'*)(\S+)(?:\'*)", data, M) + if v6_rules: + config = self.get_rules(data, v6_rules, type="ipv6") + if config: + config = utils.remove_empties(config) + objs.append(config) + if v4_rules: + config = self.get_rules(data, v4_rules, type="ipv4") + if config: + config = utils.remove_empties(config) + objs.append(config) + + ansible_facts["ansible_network_resources"].pop("firewall_rules", None) + facts = {} + if objs: + facts["firewall_rules"] = [] + params = utils.validate_config( + self.argument_spec, {"config": objs} + ) + for cfg in params["config"]: + facts["firewall_rules"].append(utils.remove_empties(cfg)) + + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def get_rules(self, data, rules, type): + """ + This function performs following: + - Form regex to fetch 'rule-sets' specific config from data. + - Form the rule-set list based on ip address. + :param data: configuration. + :param rules: list of rule-sets. + :param type: ip address type. + :return: generated rule-sets configuration. + """ + r_v4 = [] + r_v6 = [] + for r in set(rules): + rule_regex = r" %s .+$" % r.strip("'") + cfg = findall(rule_regex, data, M) + fr = self.render_config(cfg, r.strip("'")) + fr["name"] = r.strip("'") + if type == "ipv6": + r_v6.append(fr) + else: + r_v4.append(fr) + if r_v4: + config = {"afi": "ipv4", "rule_sets": r_v4} + if r_v6: + config = {"afi": "ipv6", "rule_sets": r_v6} + return config + + def render_config(self, conf, match): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + conf = "\n".join(filter(lambda x: x, conf)) + a_lst = ["description", "default_action", "enable_default_log"] + config = self.parse_attr(conf, a_lst, match) + if not config: + config = {} + config["rules"] = self.parse_rules_lst(conf) + return config + + def parse_rules_lst(self, conf): + """ + This function forms the regex to fetch the 'rules' with in + 'rule-sets' + :param conf: configuration data. + :return: generated rule list configuration. + """ + r_lst = [] + rules = findall(r"rule (?:\'*)(\d+)(?:\'*)", conf, M) + if rules: + rules_lst = [] + for r in set(rules): + r_regex = r" %s .+$" % r + cfg = "\n".join(findall(r_regex, conf, M)) + obj = self.parse_rules(cfg) + obj["number"] = int(r) + if obj: + rules_lst.append(obj) + r_lst = sorted(rules_lst, key=lambda i: i["number"]) + return r_lst + + def parse_rules(self, conf): + """ + This function triggers the parsing of 'rule' attributes. + a_lst is a list having rule attributes which doesn't + have further sub attributes. + :param conf: configuration + :return: generated rule configuration dictionary. + """ + a_lst = [ + "ipsec", + "action", + "protocol", + "fragment", + "disabled", + "description", + ] + rule = self.parse_attr(conf, a_lst) + r_sub = { + "p2p": self.parse_p2p(conf), + "tcp": self.parse_tcp(conf, "tcp"), + "icmp": self.parse_icmp(conf, "icmp"), + "time": self.parse_time(conf, "time"), + "limit": self.parse_limit(conf, "limit"), + "state": self.parse_state(conf, "state"), + "recent": self.parse_recent(conf, "recent"), + "source": self.parse_src_or_dest(conf, "source"), + "destination": self.parse_src_or_dest(conf, "destination"), + } + rule.update(r_sub) + return rule + + def parse_p2p(self, conf): + """ + This function forms the regex to fetch the 'p2p' with in + 'rules' + :param conf: configuration data. + :return: generated rule list configuration. + """ + a_lst = [] + applications = findall(r"p2p (?:\'*)(\d+)(?:\'*)", conf, M) + if applications: + app_lst = [] + for r in set(applications): + obj = {"application": r.strip("'")} + app_lst.append(obj) + a_lst = sorted(app_lst, key=lambda i: i["application"]) + return a_lst + + def parse_src_or_dest(self, conf, attrib=None): + """ + This function triggers the parsing of 'source or + destination' attributes. + :param conf: configuration. + :param attrib:'source/destination'. + :return:generated source/destination configuration dictionary. + """ + a_lst = ["port", "address", "mac_address"] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + cfg_dict["group"] = self.parse_group(conf, attrib + " group") + return cfg_dict + + def parse_recent(self, conf, attrib=None): + """ + This function triggers the parsing of 'recent' attributes + :param conf: configuration. + :param attrib: 'recent'. + :return: generated config dictionary. + """ + a_lst = ["time", "count"] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_tcp(self, conf, attrib=None): + """ + This function triggers the parsing of 'tcp' attributes. + :param conf: configuration. + :param attrib: 'tcp'. + :return: generated config dictionary. + """ + cfg_dict = self.parse_attr(conf, ["flags"], match=attrib) + return cfg_dict + + def parse_time(self, conf, attrib=None): + """ + This function triggers the parsing of 'time' attributes. + :param conf: configuration. + :param attrib: 'time'. + :return: generated config dictionary. + """ + a_lst = [ + "stopdate", + "stoptime", + "weekdays", + "monthdays", + "startdate", + "starttime", + ] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_state(self, conf, attrib=None): + """ + This function triggers the parsing of 'state' attributes. + :param conf: configuration + :param attrib: 'state'. + :return: generated config dictionary. + """ + a_lst = ["new", "invalid", "related", "established"] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_group(self, conf, attrib=None): + """ + This function triggers the parsing of 'group' attributes. + :param conf: configuration. + :param attrib: 'group'. + :return: generated config dictionary. + """ + a_lst = ["port_group", "address_group", "network_group"] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_icmp(self, conf, attrib=None): + """ + This function triggers the parsing of 'icmp' attributes. + :param conf: configuration to be parsed. + :param attrib: 'icmp'. + :return: generated config dictionary. + """ + a_lst = ["code", "type", "type_name"] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + return cfg_dict + + def parse_limit(self, conf, attrib=None): + """ + This function triggers the parsing of 'limit' attributes. + :param conf: configuration to be parsed. + :param attrib: 'limit' + :return: generated config dictionary. + """ + cfg_dict = self.parse_attr(conf, ["burst"], match=attrib) + cfg_dict["rate"] = self.parse_rate(conf, "rate") + return cfg_dict + + def parse_rate(self, conf, attrib=None): + """ + This function triggers the parsing of 'rate' attributes. + :param conf: configuration. + :param attrib: 'rate' + :return: generated config dictionary. + """ + a_lst = ["unit", "number"] + cfg_dict = self.parse_attr(conf, a_lst, match=attrib) + 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 + " " + regex + if conf: + if self.is_bool(attrib): + out = conf.find(attrib.replace("_", "-")) + + dis = conf.find(attrib.replace("_", "-") + " 'disable'") + if out >= 1: + if dis >= 1: + config[attrib] = False + else: + 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 + """ + regex = attrib.replace("_", "-") + if attrib == "disabled": + regex = "disable" + return regex + + def is_bool(self, attrib): + """ + This function looks for the attribute in predefined bool type set. + :param attrib: attribute. + :return: True/False + """ + bool_set = ( + "new", + "invalid", + "related", + "disabled", + "established", + "enable_default_log", + ) + 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 = ("time", "code", "type", "count", "burst", "number") + return True if attrib in num_set else False diff --git a/plugins/module_utils/network/vyos/facts/static_routes/__init__.py b/plugins/module_utils/network/vyos/facts/static_routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py b/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py new file mode 100644 index 0000000..0004947 --- /dev/null +++ b/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py @@ -0,0 +1,181 @@ +# +# -*- 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 static_routes 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 re import findall, search, M +from copy import deepcopy +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.static_routes.static_routes import ( + Static_routesArgs, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import ( + get_route_type, +) + + +class Static_routesFacts(object): + """ The vyos static_routes fact class + """ + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Static_routesArgs.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 static_routes + :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 = [] + r_v4 = [] + r_v6 = [] + af = [] + static_routes = findall( + r"set protocols static route(6)? (\S+)", data, M + ) + if static_routes: + for route in set(static_routes): + route_regex = r" %s .+$" % route[1] + cfg = findall(route_regex, data, M) + sr = self.render_config(cfg) + sr["dest"] = route[1].strip("'") + afi = self.get_afi(sr["dest"]) + if afi == "ipv4": + r_v4.append(sr) + else: + r_v6.append(sr) + if r_v4: + afi_v4 = {"afi": "ipv4", "routes": r_v4} + af.append(afi_v4) + if r_v6: + afi_v6 = {"afi": "ipv6", "routes": r_v6} + af.append(afi_v6) + config = {"address_families": af} + if config: + objs.append(config) + + ansible_facts["ansible_network_resources"].pop("static_routes", None) + facts = {} + if objs: + facts["static_routes"] = [] + params = utils.validate_config( + self.argument_spec, {"config": objs} + ) + for cfg in params["config"]: + facts["static_routes"].append(utils.remove_empties(cfg)) + + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def render_config(self, conf): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + next_hops_conf = "\n".join(filter(lambda x: ("next-hop" in x), conf)) + blackhole_conf = "\n".join(filter(lambda x: ("blackhole" in x), conf)) + routes_dict = { + "blackhole_config": self.parse_blackhole(blackhole_conf), + "next_hops": self.parse_next_hop(next_hops_conf), + } + return routes_dict + + def parse_blackhole(self, conf): + blackhole = None + if conf: + distance = search(r"^.*blackhole distance (.\S+)", conf, M) + bh = conf.find("blackhole") + if distance is not None: + blackhole = {} + value = distance.group(1).strip("'") + blackhole["distance"] = int(value) + elif bh: + blackhole = {} + blackhole["type"] = "blackhole" + return blackhole + + def get_afi(self, address): + route_type = get_route_type(address) + if route_type == "route": + return "ipv4" + elif route_type == "route6": + return "ipv6" + + def parse_next_hop(self, conf): + nh_list = None + if conf: + nh_list = [] + hop_list = findall(r"^.*next-hop (.+)", conf, M) + if hop_list: + for hop in hop_list: + distance = search(r"^.*distance (.\S+)", hop, M) + interface = search(r"^.*interface (.\S+)", hop, M) + + dis = hop.find("disable") + hop_info = hop.split(" ") + nh_info = { + "forward_router_address": hop_info[0].strip("'") + } + if interface: + nh_info["interface"] = interface.group(1).strip("'") + if distance: + value = distance.group(1).strip("'") + nh_info["admin_distance"] = int(value) + elif dis >= 1: + nh_info["enabled"] = False + for element in nh_list: + if ( + element["forward_router_address"] + == nh_info["forward_router_address"] + ): + if "interface" in nh_info.keys(): + element["interface"] = nh_info["interface"] + if "admin_distance" in nh_info.keys(): + element["admin_distance"] = nh_info[ + "admin_distance" + ] + if "enabled" in nh_info.keys(): + element["enabled"] = nh_info["enabled"] + nh_info = None + if nh_info is not None: + nh_list.append(nh_info) + return nh_list diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py index 6504bcd..402adfc 100644 --- a/plugins/module_utils/network/vyos/utils/utils.py +++ b/plugins/module_utils/network/vyos/utils/utils.py @@ -1,180 +1,231 @@ # -*- 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) # utils from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ( + ipaddress, +) def search_obj_in_list(name, lst, key="name"): for item in lst: if item[key] == name: return item return None def get_interface_type(interface): """Gets the type of interface """ if interface.startswith("eth"): return "ethernet" elif interface.startswith("bond"): return "bonding" elif interface.startswith("vti"): return "vti" elif interface.startswith("lo"): return "loopback" def dict_delete(base, comparable): """ This function generates a dict containing key, value pairs for keys that are present in the `base` dict but not present in the `comparable` dict. :param base: dict object to base the diff on :param comparable: dict object to compare against base :returns: new dict object with key, value pairs that needs to be deleted. """ to_delete = dict() for key in base: if isinstance(base[key], dict): sub_diff = dict_delete(base[key], comparable.get(key, {})) if sub_diff: to_delete[key] = sub_diff else: if key not in comparable: to_delete[key] = base[key] return to_delete def diff_list_of_dicts(want, have): diff = [] set_w = set(tuple(d.items()) for d in want) set_h = set(tuple(d.items()) for d in have) difference = set_w.difference(set_h) for element in difference: diff.append(dict((x, y) for x, y in element)) return diff def get_lst_diff_for_dicts(want, have, lst): """ This function generates a list containing values that are only in want and not in list in have dict :param want: dict object to want :param have: dict object to have :param lst: list the diff on :return: new list object with values which are only in want. """ if not have: diff = want.get(lst) or [] else: want_elements = want.get(lst) or {} have_elements = have.get(lst) or {} diff = list_diff_want_only(want_elements, have_elements) return diff +def get_lst_same_for_dicts(want, have, lst): + """ + This function generates a list containing values + that are common for list in want and list in have dict + :param want: dict object to want + :param have: dict object to have + :param lst: list the comparison on + :return: new list object with values which are common in want and have. + """ + diff = None + if want and have: + want_list = want.get(lst) or {} + have_list = have.get(lst) or {} + diff = [ + i + for i in want_list and have_list + if i in have_list and i in want_list + ] + return diff + + def list_diff_have_only(want_list, have_list): """ This function generated the list containing values that are only in have list. :param want_list: :param have_list: :return: new list with values which are only in have list """ if have_list and not want_list: diff = have_list elif not have_list: diff = None else: diff = [ i for i in have_list + want_list if i in have_list and i not in want_list ] return diff def list_diff_want_only(want_list, have_list): """ This function generated the list containing values that are only in want list. :param want_list: :param have_list: :return: new list with values which are only in want list """ if have_list and not want_list: diff = None elif not have_list: diff = want_list else: diff = [ i for i in have_list + want_list if i in want_list and i not in have_list ] return diff def search_dict_tv_in_list(d_val1, d_val2, lst, key1, key2): """ This function return the dict object if it exist in list. :param d_val1: :param d_val2: :param lst: :param key1: :param key2: :return: """ obj = next( ( item for item in lst if item[key1] == d_val1 and item[key2] == d_val2 ), None, ) if obj: return obj else: return None def key_value_in_dict(have_key, have_value, want_dict): """ This function checks whether the key and values exist in dict :param have_key: :param have_value: :param want_dict: :return: """ for key, value in iteritems(want_dict): if key == have_key and value == have_value: return True return False def is_dict_element_present(dict, key): """ This function checks whether the key is present in dict. :param dict: :param key: :return: """ for item in dict: if item == key: return True return False + + +def get_ip_address_version(address): + """ + This function returns the version of IP address + :param address: IP address + :return: + """ + try: + address = unicode(address) + except NameError: + address = str(address) + version = ipaddress.ip_address(address.split("/")[0]).version + return version + + +def get_route_type(address): + """ + This function returns the route type based on IP address + :param address: + :return: + """ + version = get_ip_address_version(address) + if version == 6: + return "route6" + elif version == 4: + return "route" diff --git a/plugins/modules/vyos_facts.py b/plugins/modules/vyos_facts.py index 9eaa278..19fb727 100644 --- a/plugins/modules/vyos_facts.py +++ b/plugins/modules/vyos_facts.py @@ -1,174 +1,174 @@ #!/usr/bin/python # -*- 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 module file for vyos_facts """ ANSIBLE_METADATA = { "metadata_version": "1.1", "status": [u"preview"], "supported_by": "network", } DOCUMENTATION = """module: vyos_facts short_description: Get facts about vyos devices. description: - Collects facts from network devices running the vyos operating system. This module places the facts gathered in the fact tree keyed by the respective resource name. The facts module will always collect a base set of facts from the device and can enable or disable collection of additional facts. author: - Nathaniel Case (@qalthos) - Nilashish Chakraborty (@Nilashishc) - Rohit Thakur (@rohitthakur2590) extends_documentation_fragment: - vyos.vyos.vyos notes: - Tested against VyOS 1.1.8 (helium). - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). options: gather_subset: description: - When supplied, this argument will restrict the facts collected to a given subset. Possible values for this argument include all, default, config, and neighbors. Can specify a list of values to include a larger subset. Values can also be used with an initial C(M(!)) to specify that a specific subset should not be collected. required: false default: '!config' gather_network_resources: description: - When supplied, this argument will restrict the facts collected to a given subset. Possible values for this argument include all and the resources like interfaces. Can specify a list of values to include a larger subset. Values can also be used with an initial C(M(!)) to specify that a specific subset should not be collected. Valid subsets are 'all', 'interfaces', 'l3_interfaces', 'lag_interfaces', - 'lldp_global', 'lldp_interfaces'. + 'lldp_global', 'lldp_interfaces', 'static_routes', 'firewall_rules'. required: false """ EXAMPLES = """ # Gather all facts - vyos_facts: gather_subset: all gather_network_resources: all # collect only the config and default facts - vyos_facts: gather_subset: config # collect everything exception the config - vyos_facts: gather_subset: "!config" # Collect only the interfaces facts - vyos_facts: gather_subset: - '!all' - '!min' gather_network_resources: - interfaces # Do not collect interfaces facts - vyos_facts: gather_network_resources: - "!interfaces" # Collect interfaces and minimal default facts - vyos_facts: gather_subset: min gather_network_resources: interfaces """ RETURN = """ ansible_net_config: description: The running-config from the device returned: when config is configured type: str ansible_net_commits: description: The set of available configuration revisions returned: when present type: list ansible_net_hostname: description: The configured system hostname returned: always type: str ansible_net_model: description: The device model string returned: always type: str ansible_net_serialnum: description: The serial number of the device returned: always type: str ansible_net_version: description: The version of the software running returned: always type: str ansible_net_neighbors: description: The set of LLDP neighbors returned: when interface is configured type: list ansible_net_gather_subset: description: The list of subsets gathered by the module returned: always type: list ansible_net_api: description: The name of the transport returned: always type: str ansible_net_python_version: description: The Python version Ansible controller is using returned: always type: str ansible_net_gather_network_resources: description: The list of fact resource subsets collected from the device returned: always type: list """ from ansible.module_utils.basic import AnsibleModule from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.facts.facts import ( FactsArgs, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import ( Facts, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def main(): """ Main entry point for module execution :returns: ansible_facts """ argument_spec = FactsArgs.argument_spec argument_spec.update(vyos_argument_spec) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True ) warnings = [] if module.params["gather_subset"] == "!config": warnings.append( "default value for `gather_subset` will be changed to `min` from `!config` v2.11 onwards" ) result = Facts(module).get_facts() ansible_facts, additional_warnings = result warnings.extend(additional_warnings) module.exit_json(ansible_facts=ansible_facts, warnings=warnings) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_firewall_rules.py b/plugins/modules/vyos_firewall_rules.py new file mode 100644 index 0000000..a9e676b --- /dev/null +++ b/plugins/modules/vyos_firewall_rules.py @@ -0,0 +1,1565 @@ +#!/usr/bin/python +# -*- 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) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for vyos_firewall_rules +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "network", +} + +DOCUMENTATION = """module: vyos_firewall_rules +short_description: Manage firewall rule-set attributes on VyOS devices +description: This module manages firewall rule-set attributes on VyOS devices +notes: +- Tested against VyOS 1.1.8 (helium). +- This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). +author: +- Rohit Thakur (@rohitthakur2590) +options: + config: + description: A dictionary of Firewall rule-set options. + type: list + elements: dict + suboptions: + afi: + description: + - Specifies the type of rule-set. + type: str + choices: + - ipv4 + - ipv6 + required: true + rule_sets: + description: + - The Firewall rule-set list. + type: list + elements: dict + suboptions: + name: + description: + - Firewall rule set name. + type: str + default_action: + description: + - Default action for rule-set. + - drop (Drop if no prior rules are hit (default)) + - reject (Drop and notify source if no prior rules are hit) + - accept (Accept if no prior rules are hit) + type: str + choices: + - drop + - reject + - accept + description: + description: + - Rule set description. + type: str + enable_default_log: + description: + - Option to log packets hitting default-action. + type: bool + rules: + description: + - A ditionary that specifies the rule-set configurations. + type: list + elements: dict + suboptions: + number: + description: + - Rule number. + type: int + required: true + description: + description: + - Description of this rule. + type: str + action: + description: + - Specifying the action. + type: str + choices: + - drop + - reject + - accept + - inspect + destination: + description: + - Specifying the destination parameters. + type: dict + suboptions: + address: + description: + - Destination ip address subnet or range. + - IPv4/6 address, subnet or range to match. + - Match everything except the specified address, subnet or range. + - Destination ip address subnet or range. + type: str + group: + description: + - Destination group. + type: dict + suboptions: + address_group: + description: + - Group of addresses. + type: str + network_group: + description: + - Group of networks. + type: str + port_group: + description: + - Group of ports. + type: str + port: + description: + - Multiple destination ports can be specified as a comma-separated + list. + - The whole list can also be "negated" using '!'. + - For example:'!22,telnet,http,123,1001-1005'. + type: str + disabled: + description: + - Option to disable firewall rule. + type: bool + fragment: + description: + - IP fragment match. + type: str + choices: + - match-frag + - match-non-frag + icmp: + description: + - ICMP type and code information. + type: dict + suboptions: + type_name: + description: + - ICMP type-name. + type: str + choices: + - any + - echo-reply + - destination-unreachable + - network-unreachable + - host-unreachable + - protocol-unreachable + - port-unreachable + - fragmentation-needed + - source-route-failed + - network-unknown + - host-unknown + - network-prohibited + - host-prohibited + - TOS-network-unreachable + - TOS-host-unreachable + - communication-prohibited + - host-precedence-violation + - precedence-cutoff + - source-quench + - redirect + - network-redirect + - host-redirect + - TOS-network-redirect + - TOS-host-redirect + - echo-request + - router-advertisement + - router-solicitation + - time-exceeded + - ttl-zero-during-transit + - ttl-zero-during-reassembly + - parameter-problem + - ip-header-bad + - required-option-missing + - timestamp-request + - timestamp-reply + - address-mask-request + - address-mask-reply + - ping + - pong + - ttl-exceeded + code: + description: + - ICMP code. + type: int + type: + description: + - ICMP type. + type: int + ipsec: + description: + - Inboud ip sec packets. + type: str + choices: + - match-ipsec + - match-none + limit: + description: + - Rate limit using a token bucket filter. + type: dict + suboptions: + burst: + description: + - Maximum number of packets to allow in excess of rate. + type: int + rate: + description: + - format for rate (integer/time unit). + - any one of second, minute, hour or day may be used to specify + time unit. + - eg. 1/second implies rule to be matched at an average of once + per second. + type: dict + suboptions: + number: + description: + - This is the integer value. + type: int + unit: + description: + - This is the time unit. + type: str + p2p: + description: + - P2P application packets. + type: list + elements: dict + suboptions: + application: + description: + - Name of the application. + type: str + choices: + - all + - applejuice + - bittorrent + - directconnect + - edonkey + - gnutella + - kazaa + protocol: + description: + - Protocol to match (protocol name in /etc/protocols or protocol number + or all). + - IP protocol name from /etc/protocols (e.g. "tcp" or "udp"). + - <0-255> IP protocol number. + - tcp_udp Both TCP and UDP. + - all All IP protocols. + - (!)All IP protocols except for the specified name or number. + type: str + recent: + description: + - Parameters for matching recently seen sources. + type: dict + suboptions: + count: + description: + - Source addresses seen more than N times. + type: int + time: + description: + - Source addresses seen in the last N seconds. + type: int + source: + description: + - Source parameters. + type: dict + suboptions: + address: + description: + - Source ip address subnet or range. + - IPv4/6 address, subnet or range to match. + - Match everything except the specified address, subnet or range. + - Source ip address subnet or range. + type: str + group: + description: + - Source group. + type: dict + suboptions: + address_group: + description: + - Group of addresses. + type: str + network_group: + description: + - Group of networks. + type: str + port_group: + description: + - Group of ports. + type: str + port: + description: + - Multiple source ports can be specified as a comma-separated + list. + - The whole list can also be "negated" using '!'. + - For example:'!22,telnet,http,123,1001-1005'. + type: str + mac_address: + description: + - MAC address to match. + - Match everything except the specified MAC address. + type: str + state: + description: + - Session state. + type: dict + suboptions: + established: + description: + - Established state. + type: bool + invalid: + description: + - Invalid state. + type: bool + new: + description: + - New state. + type: bool + related: + description: + - Related state. + type: bool + tcp: + description: + - TCP flags to match. + type: dict + suboptions: + flags: + description: + - TCP flags to be matched. + type: str + time: + description: + - Time to match rule. + type: dict + suboptions: + utc: + description: + - Interpret times for startdate, stopdate, starttime and stoptime + to be UTC. + type: bool + monthdays: + description: + - Monthdays to match rule on. + type: str + startdate: + description: + - Date to start matching rule. + type: str + starttime: + description: + - Time of day to start matching rule. + type: str + stopdate: + description: + - Date to stop matching rule. + type: str + stoptime: + description: + - Time of day to stop matching rule. + type: str + weekdays: + description: + - Weekdays to match rule on. + type: str + running_config: + description: + - The module, by default, will connect to the remote device and retrieve the current + running-config to use as a base for comparing against the contents of source. + There are times when it is not desirable to have the task get the current running-config + for every task in a playbook. The I(running_config) argument allows the implementer + to pass in the configuration to use as the base config for comparison. This + value of this option should be the output received from device by executing + command C(show configuration commands | grep 'firewall' + type: str + state: + description: + - The state the configuration should be left in + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged +""" +EXAMPLES = """ +# Using deleted to delete firewall rules based on rule-set name +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 501 action 'accept' +# set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' +# set firewall name Downlink rule 501 ipsec 'match-ipsec' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' +# +- name: Delete attributes of given firewall rules. + vyos_firewall_rules: + config: + - afi: ipv4 + rule_sets: + - name: 'Downlink' + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete firewall name Downlink" +# ] +# +# "after": [] +# After state +# ------------ +# vyos@vyos# run show configuration commands | grep firewall +# set firewall group address-group 'inbound' + + +# Using deleted to delete all the the firewall rules when provided config is empty +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 501 action 'accept' +# set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' +# set firewall name Downlink rule 501 ipsec 'match-ipsec' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' +# +- name: Delete attributes of given firewall rules. + vyos_firewall_rules: + config: + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete firewall name" +# ] +# +# "after": [] +# After state +# ------------ +# vyos@vyos# run show configuration commands | grep firewall +# set firewall group address-group 'inbound' + + +# Using deleted to delete the the firewall rules based on afi +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 501 action 'accept' +# set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' +# set firewall name Downlink rule 501 ipsec 'match-ipsec' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' +# +- name: Delete attributes of given firewall rules. + vyos_firewall_rules: + config: + - afi: ipv4 + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete firewall name", +# ] +# +# "after": [] +# After state +# ------------ +# vyos@vyos# run show configuration commands | grep firewall +# set firewall group address-group 'inbound' + + + +# Using deleted to delete the the firewall rules based on rule number/id +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 501 action 'accept' +# set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' +# set firewall name Downlink rule 501 ipsec 'match-ipsec' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' +# +- name: Delete attributes of given firewall rules. + vyos_firewall_rules: + config: + - afi: ipv4 + rule_sets: + - name: 'Downlink' + rules: + - number: 501 + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete firewall ipv6-name Downlink rule 501" +# ] +# +# "after": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# After state +# ------------ +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' + + +# Using merged +# +# Before state: +# ------------- +# +# vyos@vyos# run show configuration commands | grep firewall +# set firewall group address-group 'inbound' +# +- name: Merge the provided configuration with the exisiting running configuration + vyos_firewall_rules: + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + rules: + - number: 1 + action: 'accept' + description: 'Fwipv6-Rule 1 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 2 + action: 'accept' + description: 'Fwipv6-Rule 2 is configured by Ansible' + ipsec: 'match-ipsec' + + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 102 + action: 'reject' + description: 'Rule 102 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 103 + action: 'accept' + description: 'Rule 103 is configured by Ansible' + destination: + group: + address_group: 'inbound' + source: + address: '192.0.2.0' + state: + established: true + new: false + invalid: false + related: true + state: merged +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# before": [] +# +# "commands": [ +# "set firewall ipv6-name UPLINK default-action 'accept'", +# "set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set'", +# "set firewall ipv6-name UPLINK rule 1 action 'accept'", +# "set firewall ipv6-name UPLINK rule 1", +# "set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible'", +# "set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec'", +# "set firewall ipv6-name UPLINK rule 2 action 'accept'", +# "set firewall ipv6-name UPLINK rule 2", +# "set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible'", +# "set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec'", +# "set firewall name INBOUND default-action 'accept'", +# "set firewall name INBOUND description 'IPv4 INBOUND rule set'", +# "set firewall name INBOUND rule 101 action 'accept'", +# "set firewall name INBOUND rule 101", +# "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", +# "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", +# "set firewall name INBOUND rule 102 action 'reject'", +# "set firewall name INBOUND rule 102", +# "set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", +# "set firewall name INBOUND rule 102 ipsec 'match-ipsec'", +# "set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible'", +# "set firewall name INBOUND rule 103 destination group address-group inbound", +# "set firewall name INBOUND rule 103", +# "set firewall name INBOUND rule 103 source address 192.0.2.0", +# "set firewall name INBOUND rule 103 state established enable", +# "set firewall name INBOUND rule 103 state related enable", +# "set firewall name INBOUND rule 103 state invalid disable", +# "set firewall name INBOUND rule 103 state new disable", +# "set firewall name INBOUND rule 103 action 'accept'" +# ] +# +# "after": [ +# { +# "afi": "ipv6", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "This is ipv6 specific rule-set", +# "name": "UPLINK", +# "rules": [ +# { +# "action": "accept", +# "description": "Fwipv6-Rule 1 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 1 +# }, +# { +# "action": "accept", +# "description": "Fwipv6-Rule 2 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 2 +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "INBOUND", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 101 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 101 +# }, +# { +# "action": "reject", +# "description": "Rule 102 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 102 +# }, +# { +# "action": "accept", +# "description": "Rule 103 is configured by Ansible", +# "destination": { +# "group": { +# "address_group": "inbound" +# } +# }, +# "number": 103, +# "source": { +# "address": "192.0.2.0" +# }, +# "state": { +# "established": true, +# "invalid": false, +# "new": false, +# "related": true +# } +# } +# ] +# } +# ] +# } +# ] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall ipv6-name UPLINK rule 1 action 'accept' +# set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' +# set firewall ipv6-name UPLINK rule 2 action 'accept' +# set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 102 action 'reject' +# set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' +# set firewall name INBOUND rule 102 ipsec 'match-ipsec' +# set firewall name INBOUND rule 103 action 'accept' +# set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' +# set firewall name INBOUND rule 103 destination group address-group 'inbound' +# set firewall name INBOUND rule 103 source address '192.0.2.0' +# set firewall name INBOUND rule 103 state established 'enable' +# set firewall name INBOUND rule 103 state invalid 'disable' +# set firewall name INBOUND rule 103 state new 'disable' +# set firewall name INBOUND rule 103 state related 'enable' + + +# Using replaced +# +# Before state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall ipv6-name UPLINK rule 1 action 'accept' +# set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' +# set firewall ipv6-name UPLINK rule 2 action 'accept' +# set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 102 action 'reject' +# set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' +# set firewall name INBOUND rule 102 ipsec 'match-ipsec' +# set firewall name INBOUND rule 103 action 'accept' +# set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' +# set firewall name INBOUND rule 103 destination group address-group 'inbound' +# set firewall name INBOUND rule 103 source address '192.0.2.0' +# set firewall name INBOUND rule 103 state established 'enable' +# set firewall name INBOUND rule 103 state invalid 'disable' +# set firewall name INBOUND rule 103 state new 'disable' +# set firewall name INBOUND rule 103 state related 'enable' +# +- name: Replace device configurations of listed firewall rules with provided configurations + vyos_firewall_rules: + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 104 + action: 'reject' + description: 'Rule 104 is configured by Ansible' + ipsec: 'match-none' + state: replaced +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "before": [ +# { +# "afi": "ipv6", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "This is ipv6 specific rule-set", +# "name": "UPLINK", +# "rules": [ +# { +# "action": "accept", +# "description": "Fwipv6-Rule 1 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 1 +# }, +# { +# "action": "accept", +# "description": "Fwipv6-Rule 2 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 2 +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "INBOUND", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 101 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 101 +# }, +# { +# "action": "reject", +# "description": "Rule 102 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 102 +# }, +# { +# "action": "accept", +# "description": "Rule 103 is configured by Ansible", +# "destination": { +# "group": { +# "address_group": "inbound" +# } +# }, +# "number": 103, +# "source": { +# "address": "192.0.2.0" +# }, +# "state": { +# "established": true, +# "invalid": false, +# "new": false, +# "related": true +# } +# } +# ] +# } +# ] +# } +# ] +# +# "commands": [ +# "delete firewall ipv6-name UPLINK rule 1", +# "delete firewall ipv6-name UPLINK rule 2", +# "delete firewall name INBOUND rule 102", +# "delete firewall name INBOUND rule 103", +# "set firewall name INBOUND rule 104 action 'reject'", +# "set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible'", +# "set firewall name INBOUND rule 104", +# "set firewall name INBOUND rule 104 ipsec 'match-none'" +# ] +# +# "after": [ +# { +# "afi": "ipv6", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "This is ipv6 specific rule-set", +# "name": "UPLINK" +# } +# ] +# }, +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "INBOUND", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 101 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 101 +# }, +# { +# "action": "reject", +# "description": "Rule 104 is configured by Ansible", +# "ipsec": "match-none", +# "number": 104 +# } +# ] +# } +# ] +# } +# ] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 104 action 'reject' +# set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible' +# set firewall name INBOUND rule 104 ipsec 'match-none' + + +# Using overridden +# +# Before state +# -------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 104 action 'reject' +# set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible' +# set firewall name INBOUND rule 104 ipsec 'match-none' +# +- name: Overrides all device configuration with provided configuration + vyos_firewall_rules: + config: + - afi: 'ipv4' + rule_sets: + - name: 'Downlink' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 501 + action: 'accept' + description: 'Rule 501 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 502 + action: 'reject' + description: 'Rule 502 is configured by Ansible' + ipsec: 'match-ipsec' + state: overridden +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "before": [ +# { +# "afi": "ipv6", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "This is ipv6 specific rule-set", +# "name": "UPLINK" +# } +# ] +# }, +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "INBOUND", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 101 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 101 +# }, +# { +# "action": "reject", +# "description": "Rule 104 is configured by Ansible", +# "ipsec": "match-none", +# "number": 104 +# } +# ] +# } +# ] +# } +# ] +# +# "commands": [ +# "delete firewall ipv6-name UPLINK", +# "delete firewall name INBOUND", +# "set firewall name Downlink default-action 'accept'", +# "set firewall name Downlink description 'IPv4 INBOUND rule set'", +# "set firewall name Downlink rule 501 action 'accept'", +# "set firewall name Downlink rule 501", +# "set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible'", +# "set firewall name Downlink rule 501 ipsec 'match-ipsec'", +# "set firewall name Downlink rule 502 action 'reject'", +# "set firewall name Downlink rule 502", +# "set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible'", +# "set firewall name Downlink rule 502 ipsec 'match-ipsec'" +# +# +# "after": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] +# +# +# After state +# ------------ +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall name Downlink default-action 'accept' +# set firewall name Downlink description 'IPv4 INBOUND rule set' +# set firewall name Downlink rule 501 action 'accept' +# set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' +# set firewall name Downlink rule 501 ipsec 'match-ipsec' +# set firewall name Downlink rule 502 action 'reject' +# set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' +# set firewall name Downlink rule 502 ipsec 'match-ipsec' + + +# Using gathered +# +# Before state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall ipv6-name UPLINK rule 1 action 'accept' +# set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' +# set firewall ipv6-name UPLINK rule 2 action 'accept' +# set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 102 action 'reject' +# set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' +# set firewall name INBOUND rule 102 ipsec 'match-ipsec' +# set firewall name INBOUND rule 103 action 'accept' +# set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' +# set firewall name INBOUND rule 103 destination group address-group 'inbound' +# set firewall name INBOUND rule 103 source address '192.0.2.0' +# set firewall name INBOUND rule 103 state established 'enable' +# set firewall name INBOUND rule 103 state invalid 'disable' +# set firewall name INBOUND rule 103 state new 'disable' +# set firewall name INBOUND rule 103 state related 'enable' +# +- name: Gather listed firewall rules with provided configurations + vyos_firewall_rules: + config: + state: gathered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "gathered": [ +# { +# "afi": "ipv6", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "This is ipv6 specific rule-set", +# "name": "UPLINK", +# "rules": [ +# { +# "action": "accept", +# "description": "Fwipv6-Rule 1 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 1 +# }, +# { +# "action": "accept", +# "description": "Fwipv6-Rule 2 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 2 +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "INBOUND", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 101 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 101 +# }, +# { +# "action": "reject", +# "description": "Rule 102 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 102 +# }, +# { +# "action": "accept", +# "description": "Rule 103 is configured by Ansible", +# "destination": { +# "group": { +# "address_group": "inbound" +# } +# }, +# "number": 103, +# "source": { +# "address": "192.0.2.0" +# }, +# "state": { +# "established": true, +# "invalid": false, +# "new": false, +# "related": true +# } +# } +# ] +# } +# ] +# } +# ] +# +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep firewall +# set firewall group address-group 'inbound' +# set firewall ipv6-name UPLINK default-action 'accept' +# set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +# set firewall ipv6-name UPLINK rule 1 action 'accept' +# set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' +# set firewall ipv6-name UPLINK rule 2 action 'accept' +# set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' +# set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' +# set firewall name INBOUND default-action 'accept' +# set firewall name INBOUND description 'IPv4 INBOUND rule set' +# set firewall name INBOUND rule 101 action 'accept' +# set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +# set firewall name INBOUND rule 101 ipsec 'match-ipsec' +# set firewall name INBOUND rule 102 action 'reject' +# set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' +# set firewall name INBOUND rule 102 ipsec 'match-ipsec' +# set firewall name INBOUND rule 103 action 'accept' +# set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' +# set firewall name INBOUND rule 103 destination group address-group 'inbound' +# set firewall name INBOUND rule 103 source address '192.0.2.0' +# set firewall name INBOUND rule 103 state established 'enable' +# set firewall name INBOUND rule 103 state invalid 'disable' +# set firewall name INBOUND rule 103 state new 'disable' +# set firewall name INBOUND rule 103 state related 'enable' + + +# Using rendered +# +# +- name: Render the commands for provided configuration + vyos_firewall_rules: + config: + - afi: 'ipv6' + rule_sets: + - name: 'UPLINK' + description: 'This is ipv6 specific rule-set' + default_action: 'accept' + - afi: 'ipv4' + rule_sets: + - name: 'INBOUND' + description: 'IPv4 INBOUND rule set' + default_action: 'accept' + rules: + - number: 101 + action: 'accept' + description: 'Rule 101 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 102 + action: 'reject' + description: 'Rule 102 is configured by Ansible' + ipsec: 'match-ipsec' + - number: 103 + action: 'accept' + description: 'Rule 103 is configured by Ansible' + destination: + group: + address_group: 'inbound' + source: + address: '192.0.2.0' + state: + established: true + new: false + invalid: false + related: true + state: rendered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "rendered": [ +# "set firewall ipv6-name UPLINK default-action 'accept'", +# "set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set'", +# "set firewall name INBOUND default-action 'accept'", +# "set firewall name INBOUND description 'IPv4 INBOUND rule set'", +# "set firewall name INBOUND rule 101 action 'accept'", +# "set firewall name INBOUND rule 101", +# "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", +# "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", +# "set firewall name INBOUND rule 102 action 'reject'", +# "set firewall name INBOUND rule 102", +# "set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible'", +# "set firewall name INBOUND rule 102 ipsec 'match-ipsec'", +# "set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible'", +# "set firewall name INBOUND rule 103 destination group address-group inbound", +# "set firewall name INBOUND rule 103", +# "set firewall name INBOUND rule 103 source address 192.0.2.0", +# "set firewall name INBOUND rule 103 state established enable", +# "set firewall name INBOUND rule 103 state related enable", +# "set firewall name INBOUND rule 103 state invalid disable", +# "set firewall name INBOUND rule 103 state new disable", +# "set firewall name INBOUND rule 103 action 'accept'" +# ] + + +# Using parsed +# +# +- name: Render the commands for provided configuration + vyos_firewall_rules: + running_config: + "set firewall group address-group 'inbound' + set firewall name Downlink default-action 'accept' + set firewall name Downlink description 'IPv4 INBOUND rule set' + set firewall name Downlink rule 501 action 'accept' + set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' + set firewall name Downlink rule 501 ipsec 'match-ipsec' + set firewall name Downlink rule 502 action 'reject' + set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' + set firewall name Downlink rule 502 ipsec 'match-ipsec'" + state: parsed +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "parsed": [ +# { +# "afi": "ipv4", +# "rule_sets": [ +# { +# "default_action": "accept", +# "description": "IPv4 INBOUND rule set", +# "name": "Downlink", +# "rules": [ +# { +# "action": "accept", +# "description": "Rule 501 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 501 +# }, +# { +# "action": "reject", +# "description": "Rule 502 is configured by Ansible", +# "ipsec": "match-ipsec", +# "number": 502 +# } +# ] +# } +# ] +# } +# ] + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: + - "set firewall name Downlink default-action 'accept'" + - "set firewall name Downlink description 'IPv4 INBOUND rule set'" + - "set firewall name Downlink rule 501 action 'accept'" + - "set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible'" + - "set firewall name Downlink rule 502 ipsec 'match-ipsec'" +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.firewall_rules.firewall_rules import ( + Firewall_rulesArgs, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules import ( + Firewall_rules, +) + + +def main(): + """ + Main entry point for module execution + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "overridden", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=Firewall_rulesArgs.argument_spec, + required_if=required_if, + supports_check_mode=True, + mutually_exclusive=mutually_exclusive, + ) + result = Firewall_rules(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/vyos_static_route.py b/plugins/modules/vyos_static_route.py index e0c40e7..af9a1e3 100644 --- a/plugins/modules/vyos_static_route.py +++ b/plugins/modules/vyos_static_route.py @@ -1,292 +1,302 @@ #!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2017, Ansible by Red Hat, inc # # This file is part of Ansible by Red Hat # # 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 . # ANSIBLE_METADATA = { "metadata_version": "1.1", - "status": ["preview"], + "status": ["deprecated"], "supported_by": "network", } DOCUMENTATION = """module: vyos_static_route author: Trishna Guha (@trishnaguha) short_description: Manage static IP routes on Vyatta VyOS network devices description: - This module provides declarative management of static IP routes on Vyatta VyOS network devices. +deprecated: + removed_in: '2.13' + alternative: vyos_static_routes + why: Updated modules released with more functionality. notes: - Tested against VyOS 1.1.8 (helium). - This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). options: prefix: description: - Network prefix of the static route. C(mask) param should be ignored if C(prefix) is provided with C(mask) value C(prefix/mask). + type: str mask: description: - Network prefix mask of the static route. + type: str next_hop: description: - Next hop IP of the static route. + type: str admin_distance: description: - Admin distance of the static route. + type: int aggregate: description: List of static route definitions + type: list state: description: - State of the static route configuration. default: present choices: - present - absent + type: str extends_documentation_fragment: - vyos.vyos.vyos """ EXAMPLES = """ - name: configure static route vyos_static_route: prefix: 192.168.2.0 mask: 24 next_hop: 10.0.0.1 - name: configure static route prefix/mask vyos_static_route: prefix: 192.168.2.0/16 next_hop: 10.0.0.1 - name: remove configuration vyos_static_route: prefix: 192.168.2.0 mask: 16 next_hop: 10.0.0.1 state: absent - name: configure aggregates of static routes vyos_static_route: aggregate: - { prefix: 192.168.2.0, mask: 24, next_hop: 10.0.0.1 } - { prefix: 192.168.3.0, mask: 16, next_hop: 10.0.2.1 } - { prefix: 192.168.3.0/16, next_hop: 10.0.2.1 } - name: Remove static route collections vyos_static_route: aggregate: - { prefix: 172.24.1.0/24, next_hop: 192.168.42.64 } - { prefix: 172.24.3.0/24, next_hop: 192.168.42.64 } state: absent """ RETURN = """ commands: description: The list of configuration mode commands to send to the device returned: always type: list sample: - set protocols static route 192.168.2.0/16 next-hop 10.0.0.1 """ import re from copy import deepcopy from ansible.module_utils.basic import AnsibleModule from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( remove_default_spec, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( get_config, load_config, ) from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import ( vyos_argument_spec, ) def spec_to_commands(updates, module): commands = list() want, have = updates for w in want: prefix = w["prefix"] mask = w["mask"] next_hop = w["next_hop"] admin_distance = w["admin_distance"] state = w["state"] del w["state"] if state == "absent" and w in have: commands.append( "delete protocols static route %s/%s" % (prefix, mask) ) elif state == "present" and w not in have: cmd = "set protocols static route %s/%s next-hop %s" % ( prefix, mask, next_hop, ) if admin_distance != "None": cmd += " distance %s" % (admin_distance) commands.append(cmd) return commands def config_to_dict(module): data = get_config(module) obj = [] for line in data.split("\n"): if line.startswith("set protocols static route"): match = re.search(r"static route (\S+)", line, re.M) prefix = match.group(1).split("/")[0] mask = match.group(1).split("/")[1] if "next-hop" in line: match_hop = re.search(r"next-hop (\S+)", line, re.M) next_hop = match_hop.group(1).strip("'") match_distance = re.search(r"distance (\S+)", line, re.M) if match_distance is not None: admin_distance = match_distance.group(1)[1:-1] else: admin_distance = None if admin_distance is not None: obj.append( { "prefix": prefix, "mask": mask, "next_hop": next_hop, "admin_distance": admin_distance, } ) else: obj.append( { "prefix": prefix, "mask": mask, "next_hop": next_hop, "admin_distance": "None", } ) return obj def map_params_to_obj(module, required_together=None): obj = [] aggregate = module.params.get("aggregate") if aggregate: for item in aggregate: for key in item: if item.get(key) is None: item[key] = module.params[key] module._check_required_together(required_together, item) d = item.copy() if "/" in d["prefix"]: d["mask"] = d["prefix"].split("/")[1] d["prefix"] = d["prefix"].split("/")[0] if "admin_distance" in d: d["admin_distance"] = str(d["admin_distance"]) obj.append(d) else: prefix = module.params["prefix"].strip() if "/" in prefix: mask = prefix.split("/")[1] prefix = prefix.split("/")[0] else: mask = module.params["mask"].strip() next_hop = module.params["next_hop"].strip() admin_distance = str(module.params["admin_distance"]) state = module.params["state"] obj.append( { "prefix": prefix, "mask": mask, "next_hop": next_hop, "admin_distance": admin_distance, "state": state, } ) return obj def main(): """ main entry point for module execution """ element_spec = dict( prefix=dict(type="str"), mask=dict(type="str"), next_hop=dict(type="str"), admin_distance=dict(type="int"), state=dict(default="present", choices=["present", "absent"]), ) aggregate_spec = deepcopy(element_spec) aggregate_spec["prefix"] = dict(required=True) # remove default in aggregate spec, to handle common arguments remove_default_spec(aggregate_spec) argument_spec = dict( aggregate=dict(type="list", elements="dict", options=aggregate_spec), ) argument_spec.update(element_spec) argument_spec.update(vyos_argument_spec) required_one_of = [["aggregate", "prefix"]] required_together = [["prefix", "next_hop"]] mutually_exclusive = [["aggregate", "prefix"]] module = AnsibleModule( argument_spec=argument_spec, required_one_of=required_one_of, required_together=required_together, mutually_exclusive=mutually_exclusive, supports_check_mode=True, ) warnings = list() result = {"changed": False} if warnings: result["warnings"] = warnings want = map_params_to_obj(module, required_together=required_together) have = config_to_dict(module) commands = spec_to_commands((want, have), module) result["commands"] = commands if commands: commit = not module.check_mode load_config(module, commands, commit=commit) result["changed"] = True module.exit_json(**result) if __name__ == "__main__": main() diff --git a/plugins/modules/vyos_static_routes.py b/plugins/modules/vyos_static_routes.py new file mode 100644 index 0000000..6e50203 --- /dev/null +++ b/plugins/modules/vyos_static_routes.py @@ -0,0 +1,1156 @@ +#!/usr/bin/python +# -*- 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) + +############################################# +# WARNING # +############################################# +# +# This file is auto generated by the resource +# module builder playbook. +# +# Do not edit this file manually. +# +# Changes to this file will be over written +# by the resource module builder. +# +# Changes should be made in the model used to +# generate this file or in the resource module +# builder template. +# +############################################# + +""" +The module file for vyos_static_routes +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.1", + "status": ["preview"], + "supported_by": "network", +} + +DOCUMENTATION = """module: vyos_static_routes +short_description: Manages attributes of static routes on VyOS network devices. +description: This module manages attributes of static routes on VyOS network devices. +notes: +- Tested against VyOS 1.1.8 (helium). +- This module works with connection C(network_cli). See L(the VyOS OS Platform Options,../network/user_guide/platform_vyos.html). +author: +- Rohit Thakur (@rohitthakur2590) +options: + config: + description: A provided static route configuration. + type: list + elements: dict + suboptions: + address_families: + description: A dictionary specifying the address family to which the static + route(s) belong. + type: list + elements: dict + suboptions: + afi: + description: + - Specifies the type of route. + type: str + choices: + - ipv4 + - ipv6 + required: true + routes: + description: A ditionary that specify the static route configurations. + type: list + elements: dict + suboptions: + dest: + description: + - An IPv4/v6 address in CIDR notation that specifies the destination + network for the static route. + type: str + required: true + blackhole_config: + description: + - Configured to silently discard packets. + type: dict + suboptions: + type: + description: + - This is to configure only blackhole. + type: str + distance: + description: + - Distance for the route. + type: int + next_hops: + description: + - Next hops to the specified destination. + type: list + elements: dict + suboptions: + forward_router_address: + description: + - The IP address of the next hop that can be used to reach the + destination network. + type: str + required: true + enabled: + description: + - Disable IPv4/v6 next-hop static route. + type: bool + admin_distance: + description: + - Distance value for the route. + type: int + interface: + description: + - Name of the outgoing interface. + type: str + running_config: + description: + - The module, by default, will connect to the remote device and retrieve the current + running-config to use as a base for comparing against the contents of source. + There are times when it is not desirable to have the task get the current running-config + for every task in a playbook. The I(running_config) argument allows the implementer + to pass in the configuration to use as the base config for comparison. This + value of this option should be the output received from device by executing + command C(show configuration commands | grep 'static route') + type: str + state: + description: + - The state of the configuration after module completion. + type: str + choices: + - merged + - replaced + - overridden + - deleted + - gathered + - rendered + - parsed + default: merged +""" +EXAMPLES = """ +# Using merged +# +# Before state: +# ------------- +# +# vyos@vyos:~$ show configuration commands | grep static +# +- name: Merge the provided configuration with the exisiting running configuration + vyos_static_routes: + config: + - address_families: + - afi: 'ipv4' + routes: + - dest: 192.0.2.32/28 + blackhole_config: + type: 'blackhole' + next_hops: + - forward_router_address: 192.0.2.6 + - forward_router_address: 192.0.2.7 + - address_families: + - afi: 'ipv6' + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 + state: merged +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# before": [] +# +# "commands": [ +# "set protocols static route 192.0.2.32/28", +# "set protocols static route 192.0.2.32/28 blackhole", +# "set protocols static route 192.0.2.32/28 next-hop '192.0.2.6'", +# "set protocols static route 192.0.2.32/28 next-hop '192.0.2.7'", +# "set protocols static route6 2001:db8:1000::/36", +# "set protocols static route6 2001:db8:1000::/36 blackhole distance '2'", +# "set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1'", +# "set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2'" +# ] +# +# "after": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "blackhole_config": { +# "type": "blackhole" +# }, +# "dest": "192.0.2.32/28", +# "next_hops": [ +# { +# "forward_router_address": "192.0.2.6" +# }, +# { +# "forward_router_address": "192.0.2.7" +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv6", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "2001:db8:1000::/36", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::1" +# }, +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# } +# ] +# } +# ] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 192.0.2.32/28 'blackhole' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.6' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' +# set protocols static route6 2001:db8:1000::/36 blackhole distance '2' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' + + +# Using replaced +# +# Before state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 192.0.2.32/28 'blackhole' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.6' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' +# set protocols static route 192.0.2.33/28 'blackhole' +# set protocols static route 192.0.2.33/28 next-hop '192.0.2.3' +# set protocols static route 192.0.2.33/28 next-hop '192.0.2.4' +# set protocols static route6 2001:db8:1000::/36 blackhole distance '2' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' +# +- name: Replace device configurations of listed static routes with provided configurations + vyos_static_routes: + 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 + enabled: false + - forward_router_address: 192.0.2.9 + state: replaced +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "before": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "blackhole_config": { +# "type": "blackhole" +# }, +# "dest": "192.0.2.32/28", +# "next_hops": [ +# { +# "forward_router_address": "192.0.2.6" +# }, +# { +# "forward_router_address": "192.0.2.7" +# } +# ] +# }, +# { +# "blackhole_config": { +# "type": "blackhole" +# }, +# "dest": "192.0.2.33/28", +# "next_hops": [ +# { +# "forward_router_address": "192.0.2.3" +# }, +# { +# "forward_router_address": "192.0.2.4" +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv6", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "2001:db8:1000::/36", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::1" +# }, +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# } +# ] +# } +# ] +# +# "commands": [ +# "delete protocols static route 192.0.2.32/28 next-hop '192.0.2.6'", +# "delete protocols static route 192.0.2.32/28 next-hop '192.0.2.7'", +# "set protocols static route 192.0.2.32/28 next-hop 192.0.2.7 'disable'", +# "set protocols static route 192.0.2.32/28 next-hop '192.0.2.7'", +# "set protocols static route 192.0.2.32/28 next-hop '192.0.2.9'", +# "set protocols static route 192.0.2.32/28 blackhole distance '2'" +# ] +# +# "after": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "192.0.2.32/28", +# "next_hops": [ +# { +# "enabled": false, +# "forward_router_address": "192.0.2.7" +# }, +# { +# "forward_router_address": "192.0.2.9" +# } +# ] +# }, +# { +# "blackhole_config": { +# "type": "blackhole" +# }, +# "dest": "192.0.2.33/28", +# "next_hops": [ +# { +# "forward_router_address": "192.0.2.3" +# }, +# { +# "forward_router_address": "192.0.2.4" +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv6", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "2001:db8:1000::/36", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::1" +# }, +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# } +# ] +# } +# ] +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 192.0.2.32/28 blackhole distance '2' +# set protocols static route 192.0.2.32/28 next-hop 192.0.2.7 'disable' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' +# set protocols static route 192.0.2.33/28 'blackhole' +# set protocols static route 192.0.2.33/28 next-hop '192.0.2.3' +# set protocols static route 192.0.2.33/28 next-hop '192.0.2.4' +# set protocols static route6 2001:db8:1000::/36 blackhole distance '2' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' + + +# Using overridden +# +# Before state +# -------------- +# +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 192.0.2.32/28 blackhole distance '2' +# set protocols static route 192.0.2.32/28 next-hop 192.0.2.7 'disable' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' +# set protocols static route6 2001:db8:1000::/36 blackhole distance '2' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' +# +- name: Overrides all device configuration with provided configuration + vyos_static_routes: + config: + - address_families: + - afi: 'ipv4' + routes: + - dest: 198.0.2.48/28 + next_hops: + - forward_router_address: 192.0.2.18 + state: overridden +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "before": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "192.0.2.32/28", +# "next_hops": [ +# { +# "enabled": false, +# "forward_router_address": "192.0.2.7" +# }, +# { +# "forward_router_address": "192.0.2.9" +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv6", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "2001:db8:1000::/36", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::1" +# }, +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# } +# ] +# } +# ] +# +# "commands": [ +# "delete protocols static route 192.0.2.32/28", +# "delete protocols static route6 2001:db8:1000::/36", +# "set protocols static route 198.0.2.48/28", +# "set protocols static route 198.0.2.48/28 next-hop '192.0.2.18'" +# +# +# "after": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "dest": "198.0.2.48/28", +# "next_hops": [ +# { +# "forward_router_address": "192.0.2.18" +# } +# ] +# } +# ] +# } +# ] +# } +# ] +# +# +# After state +# ------------ +# +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 198.0.2.48/28 next-hop '192.0.2.18' + + +# Using deleted to delete static route based on destination +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 192.0.2.32/28 'blackhole' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.6' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' +# set protocols static route6 2001:db8:1000::/36 blackhole distance '2' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' +# +- name: Delete static route per destination. + vyos_static_routes: + config: + - address_families: + - afi: 'ipv4' + routes: + - dest: '192.0.2.32/28' + - afi: 'ipv6' + routes: + - dest: '2001:db8:1000::/36' + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "blackhole_config": { +# "type": "blackhole" +# }, +# "dest": "192.0.2.32/28", +# "next_hops": [ +# { +# "forward_router_address": "192.0.2.6" +# }, +# { +# "forward_router_address": "192.0.2.7" +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv6", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "2001:db8:1000::/36", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::1" +# }, +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete protocols static route 192.0.2.32/28", +# "delete protocols static route6 2001:db8:1000::/36" +# ] +# +# "after": [] +# After state +# ------------ +# vyos@vyos# run show configuration commands | grep static +# set protocols 'static' + + +# Using deleted to delete static route based on afi +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 192.0.2.32/28 'blackhole' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.6' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' +# set protocols static route6 2001:db8:1000::/36 blackhole distance '2' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' +# +- name: Delete static route based on afi. + vyos_static_routes: + config: + - address_families: + - afi: 'ipv4' + - afi: 'ipv6' + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "blackhole_config": { +# "type": "blackhole" +# }, +# "dest": "192.0.2.32/28", +# "next_hops": [ +# { +# "forward_router_address": "192.0.2.6" +# }, +# { +# "forward_router_address": "192.0.2.7" +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv6", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "2001:db8:1000::/36", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::1" +# }, +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete protocols static route", +# "delete protocols static route6" +# ] +# +# "after": [] +# After state +# ------------ +# vyos@vyos# run show configuration commands | grep static +# set protocols 'static' + + +# Using deleted to delete all the static routes when passes config is empty +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 192.0.2.32/28 'blackhole' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.6' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' +# set protocols static route6 2001:db8:1000::/36 blackhole distance '2' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' +# +- name: Delete all the static routes. + vyos_static_routes: + config: + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "blackhole_config": { +# "type": "blackhole" +# }, +# "dest": "192.0.2.32/28", +# "next_hops": [ +# { +# "forward_router_address": "192.0.2.6" +# }, +# { +# "forward_router_address": "192.0.2.7" +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv6", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "2001:db8:1000::/36", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::1" +# }, +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete protocols static route", +# "delete protocols static route6" +# ] +# +# "after": [] +# After state +# ------------ +# vyos@vyos# run show configuration commands | grep static +# set protocols 'static' + + +# Using deleted to delete static route based on next-hop +# +# Before state +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 192.0.2.32/28 'blackhole' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.6' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' +# set protocols static route6 2001:db8:1000::/36 blackhole distance '2' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' +# +- name: Delete static routes per next-hops + vyos_static_routes: + config: + - address_families: + - afi: 'ipv4' + routes: + - dest: '192.0.2.32/28' + next-hops: + - forward_router_address: '192.0.2.6' + - afi: 'ipv6' + routes: + - dest: '2001:db8:1000::/36' + next-hops: + - forward_router_address: '2001:db8:2000:2::1' + state: deleted +# +# +# ------------------------ +# Module Execution Results +# ------------------------ +# +# "before": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "blackhole_config": { +# "type": "blackhole" +# }, +# "dest": "192.0.2.32/28", +# "next_hops": [ +# { +# "forward_router_address": "192.0.2.6" +# }, +# { +# "forward_router_address": "192.0.2.7" +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv6", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "2001:db8:1000::/36", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::1" +# }, +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# } +# ] +# } +# ] +# "commands": [ +# "delete protocols static route 192.0.2.32/28 next-hop '192.0.2.6'", +# "delete protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1'" +# ] +# +# "after": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "blackhole_config": { +# "type": "blackhole" +# }, +# "dest": "192.0.2.32/28", +# "next_hops": [ +# { +# "forward_router_address": "192.0.2.7" +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv6", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "2001:db8:1000::/36", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# } +# ] +# } +# ] +# After state +# ------------ +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 192.0.2.32/28 'blackhole' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' +# set protocols static route6 2001:db8:1000::/36 blackhole distance '2' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' + + +# Using rendered +# +# +- name: Render the commands for provided configuration + vyos_static_routes: + config: + - address_families: + - afi: 'ipv4' + routes: + - dest: 192.0.2.32/28 + blackhole_config: + type: 'blackhole' + next_hops: + - forward_router_address: 192.0.2.6 + - forward_router_address: 192.0.2.7 + - address_families: + - afi: 'ipv6' + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 + state: rendered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "rendered": [ +# "set protocols static route 192.0.2.32/28", +# "set protocols static route 192.0.2.32/28 blackhole", +# "set protocols static route 192.0.2.32/28 next-hop '192.0.2.6'", +# "set protocols static route 192.0.2.32/28 next-hop '192.0.2.7'", +# "set protocols static route6 2001:db8:1000::/36", +# "set protocols static route6 2001:db8:1000::/36 blackhole distance '2'", +# "set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1'", +# "set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2'" +# ] + + +# Using parsed +# +# +- name: Render the commands for provided configuration + vyos_static_routes: + running_config: + "set protocols static route 192.0.2.32/28 'blackhole' + set protocols static route 192.0.2.32/28 next-hop '192.0.2.6' + set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' + set protocols static route6 2001:db8:1000::/36 blackhole distance '2' + set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' + set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2'" + state: parsed +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# +# "parsed": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "192.0.2.32/28", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv6", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "2001:db8:1000::/36", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# } +# ] +# } +# ] + + +# Using gathered +# +# Before state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 192.0.2.32/28 'blackhole' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.6' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' +# set protocols static route6 2001:db8:1000::/36 blackhole distance '2' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' +# +- name: Gather listed static routes with provided configurations + vyos_static_routes: + config: + state: gathered +# +# +# ------------------------- +# Module Execution Result +# ------------------------- +# +# "gathered": [ +# { +# "address_families": [ +# { +# "afi": "ipv4", +# "routes": [ +# { +# "blackhole_config": { +# "type": "blackhole" +# }, +# "dest": "192.0.2.32/28", +# "next_hops": [ +# { +# "forward_router_address": "192.0.2.6" +# }, +# { +# "forward_router_address": "192.0.2.7" +# } +# ] +# } +# ] +# }, +# { +# "afi": "ipv6", +# "routes": [ +# { +# "blackhole_config": { +# "distance": 2 +# }, +# "dest": "2001:db8:1000::/36", +# "next_hops": [ +# { +# "forward_router_address": "2001:db8:2000:2::1" +# }, +# { +# "forward_router_address": "2001:db8:2000:2::2" +# } +# ] +# } +# ] +# } +# ] +# } +# ] +# +# +# After state: +# ------------- +# +# vyos@vyos:~$ show configuration commands| grep static +# set protocols static route 192.0.2.32/28 'blackhole' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.6' +# set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' +# set protocols static route6 2001:db8:1000::/36 blackhole distance '2' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' +# set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' + + +""" +RETURN = """ +before: + description: The configuration prior to the model invocation. + returned: always + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +after: + description: The resulting configuration model invocation. + returned: when changed + type: list + sample: > + The configuration returned will always be in the same format + of the parameters above. +commands: + description: The set of commands pushed to the remote device. + returned: always + type: list + sample: + - "set protocols static route 192.0.2.32/28 next-hop '192.0.2.6'" + - "set protocols static route 192.0.2.32/28 'blackhole'" +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.static_routes.static_routes import ( + Static_routesArgs, +) +from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.static_routes.static_routes import ( + Static_routes, +) + + +def main(): + """ + Main entry point for module execution + + :returns: the result form module invocation + """ + required_if = [ + ("state", "merged", ("config",)), + ("state", "replaced", ("config",)), + ("state", "overridden", ("config",)), + ("state", "parsed", ("running_config",)), + ] + mutually_exclusive = [("config", "running_config")] + + module = AnsibleModule( + argument_spec=Static_routesArgs.argument_spec, + required_if=required_if, + supports_check_mode=True, + mutually_exclusive=mutually_exclusive, + ) + result = Static_routes(module).execute_module() + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/tests/integration/targets/vyos_firewall_rules/defaults/main.yaml b/tests/integration/targets/vyos_firewall_rules/defaults/main.yaml new file mode 100644 index 0000000..852a6be --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: '[^_].*' +test_items: [] diff --git a/tests/integration/targets/vyos_firewall_rules/meta/main.yaml b/tests/integration/targets/vyos_firewall_rules/meta/main.yaml new file mode 100644 index 0000000..7413320 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/meta/main.yaml @@ -0,0 +1,3 @@ +--- +dependencies: + - prepare_vyos_tests diff --git a/tests/integration/targets/vyos_firewall_rules/tasks/cli.yaml b/tests/integration/targets/vyos_firewall_rules/tasks/cli.yaml new file mode 100644 index 0000000..93eb2fe --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tasks/cli.yaml @@ -0,0 +1,19 @@ +--- +- name: Collect all cli test cases + find: + paths: '{{ role_path }}/tests/cli' + patterns: '{{ testcase }}.yaml' + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: Run test case (connection=ansible.netcommon.network_cli) + include: '{{ test_case_to_run }}' + vars: + ansible_connection: ansible.netcommon.network_cli + with_items: '{{ test_items }}' + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/vyos_firewall_rules/tasks/main.yaml b/tests/integration/targets/vyos_firewall_rules/tasks/main.yaml new file mode 100644 index 0000000..a3db933 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tasks/main.yaml @@ -0,0 +1,4 @@ +--- +- include: cli.yaml + tags: + - cli diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/_parsed_config.cfg b/tests/integration/targets/vyos_firewall_rules/tests/cli/_parsed_config.cfg new file mode 100644 index 0000000..b54c109 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/_parsed_config.cfg @@ -0,0 +1,25 @@ +set firewall group address-group 'inbound' +set firewall ipv6-name UPLINK default-action 'accept' +set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' +set firewall ipv6-name UPLINK rule 1 action 'accept' +set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured by Ansible' +set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' +set firewall ipv6-name UPLINK rule 2 action 'accept' +set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured by Ansible' +set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' +set firewall name INBOUND default-action 'accept' +set firewall name INBOUND description 'IPv4 INBOUND rule set' +set firewall name INBOUND rule 101 action 'accept' +set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' +set firewall name INBOUND rule 101 ipsec 'match-ipsec' +set firewall name INBOUND rule 102 action 'reject' +set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' +set firewall name INBOUND rule 102 ipsec 'match-ipsec' +set firewall name INBOUND rule 103 action 'accept' +set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' +set firewall name INBOUND rule 103 destination group address-group 'inbound' +set firewall name INBOUND rule 103 source address '192.0.2.0' +set firewall name INBOUND rule 103 state established 'enable' +set firewall name INBOUND rule 103 state invalid 'disable' +set firewall name INBOUND rule 103 state new 'disable' +set firewall name INBOUND rule 103 state related 'enable' diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/_populate.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/_populate.yaml new file mode 100644 index 0000000..551736e --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/_populate.yaml @@ -0,0 +1,27 @@ +--- +- name: Setup + vars: + lines: "set firewall group address-group 'inbound'\nset firewall ipv6-name UPLINK\ + \ default-action 'accept'\nset firewall ipv6-name UPLINK description 'This\ + \ is ipv6 specific rule-set'\nset firewall ipv6-name UPLINK rule 1 action\ + \ 'accept'\nset firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule\ + \ 1 is configured by Ansible'\nset firewall ipv6-name UPLINK rule 1 ipsec\ + \ 'match-ipsec'\nset firewall ipv6-name UPLINK rule 2 action 'accept'\nset\ + \ firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured\ + \ by Ansible'\nset firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec'\n\ + set firewall name INBOUND default-action 'accept'\nset firewall name INBOUND\ + \ description 'IPv4 INBOUND rule set'\nset firewall name INBOUND rule 101\ + \ action 'accept'\nset firewall name INBOUND rule 101 description 'Rule 101\ + \ is configured by Ansible'\nset firewall name INBOUND rule 101 ipsec 'match-ipsec'\n\ + set firewall name INBOUND rule 102 action 'reject'\nset firewall name INBOUND\ + \ rule 102 description 'Rule 102 is configured by Ansible'\nset firewall name\ + \ INBOUND rule 102 ipsec 'match-ipsec'\nset firewall name INBOUND rule 103\ + \ action 'accept'\nset firewall name INBOUND rule 103 description 'Rule 103\ + \ is configured by Ansible'\nset firewall name INBOUND rule 103 destination\ + \ group address-group 'inbound'\nset firewall name INBOUND rule 103 source\ + \ address '192.0.2.0'\nset firewall name INBOUND rule 103 state established\ + \ 'enable'\nset firewall name INBOUND rule 103 state invalid 'disable'\nset\ + \ firewall name INBOUND rule 103 state new 'disable'\nset firewall name INBOUND\ + \ rule 103 state related 'enable'\n" + ansible.netcommon.cli_config: + config: '{{ lines }}' diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/_remove_config.yaml new file mode 100644 index 0000000..acb0803 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/_remove_config.yaml @@ -0,0 +1,6 @@ +--- +- name: Remove Config + vars: + lines: "delete firewall ipv6-name\ndelete firewall name\n" + ansible.netcommon.cli_config: + config: '{{ lines }}' diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted.yaml new file mode 100644 index 0000000..7acfe65 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted.yaml @@ -0,0 +1,60 @@ +--- +- debug: + msg: Start vyos_firewall_rules deleted integration tests ansible_connection={{ + ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + + - name: Delete firewall rule set. + register: result + vyos.vyos.vyos_firewall_rules: &id001 + config: + + - afi: ipv6 + rule_sets: + + - name: UPLINK + + - afi: ipv4 + rule_sets: + + - name: INBOUND + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_rs['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_rs['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + register: result + vyos.vyos.vyos_firewall_rules: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands|length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_rs['after'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted_afi.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted_afi.yaml new file mode 100644 index 0000000..e20670d --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted_afi.yaml @@ -0,0 +1,54 @@ +--- +- debug: + msg: Start vyos_firewall_rules deleted integration tests ansible_connection={{ + ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + + - name: Delete firewall rule. + register: result + vyos.vyos.vyos_firewall_rules: &id001 + config: + + - afi: ipv6 + + - afi: ipv4 + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_afi_all['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['after'])\ + \ |length == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + register: result + vyos.vyos.vyos_firewall_rules: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands|length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['before'])\ + \ |length == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted_all.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted_all.yaml new file mode 100644 index 0000000..16e563c --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted_all.yaml @@ -0,0 +1,50 @@ +--- +- debug: + msg: Start vyos_firewall_rules deleted integration tests ansible_connection={{ + ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + + - name: Delete all the firewall rules. + register: result + vyos.vyos.vyos_firewall_rules: &id001 + config: + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_afi_all['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['after'])\ + \ |length == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + register: result + vyos.vyos.vyos_firewall_rules: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands|length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['before'])\ + \ |length == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted_rule.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted_rule.yaml new file mode 100644 index 0000000..d77e2a9 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/deleted_rule.yaml @@ -0,0 +1,58 @@ +--- +- debug: + msg: Start vyos_firewall_rules deleted integration tests ansible_connection={{ + ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + + - name: Delete firewall rule. + register: result + vyos.vyos.vyos_firewall_rules: &id001 + config: + + - afi: ipv6 + rule_sets: + + - name: UPLINK + rules: + + - number: 1 + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_r['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_r['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + register: result + vyos.vyos.vyos_firewall_rules: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands|length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_r['after'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/empty_config.yaml new file mode 100644 index 0000000..c30cf03 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/empty_config.yaml @@ -0,0 +1,60 @@ +--- +- debug: + msg: START vyos_firewall_rules empty_config integration tests on connection={{ + ansible_connection }} + +- name: Merged with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_firewall_rules: + config: + state: merged + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state merged' + +- name: Replaced with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_firewall_rules: + config: + state: replaced + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state replaced' + +- name: Overridden with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_firewall_rules: + config: + state: overridden + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Parsed with empty running_config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_firewall_rules: + running_config: + state: parsed + +- assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state + parsed' + +- name: Rendered with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_firewall_rules: + config: + state: rendered + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/gathered.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/gathered.yaml new file mode 100644 index 0000000..cdc8e51 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/gathered.yaml @@ -0,0 +1,34 @@ +--- +- debug: + msg: START vyos_firewall_rules gathered integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Merge the provided configuration with the exisiting running configuration + register: result + vyos.vyos.vyos_firewall_rules: &id001 + config: + state: gathered + + - name: Assert that gathered dicts was correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['gathered']) |length == 0\ + \ }}" + + - name: Gather the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_firewall_rules: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/merged.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/merged.yaml new file mode 100644 index 0000000..adf7e47 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/merged.yaml @@ -0,0 +1,102 @@ +--- +- debug: + msg: START vyos_firewall_rules merged integration tests on connection={{ ansible_connection + }} + +- include_tasks: _populate.yaml + +- include_tasks: _remove_config.yaml + +- block: + + - name: Merge the provided configuration with the exisiting running configuration + register: result + vyos.vyos.vyos_firewall_rules: &id001 + config: + + - afi: ipv6 + rule_sets: + + - name: UPLINK + description: This is ipv6 specific rule-set + default_action: accept + rules: + + - number: 1 + action: accept + description: Fwipv6-Rule 1 is configured by Ansible + ipsec: match-ipsec + + - number: 2 + action: accept + description: Fwipv6-Rule 2 is configured by Ansible + ipsec: match-ipsec + + - afi: ipv4 + rule_sets: + + - name: INBOUND + description: IPv4 INBOUND rule set + default_action: accept + rules: + + - number: 101 + action: accept + description: Rule 101 is configured by Ansible + ipsec: match-ipsec + + - number: 102 + action: reject + description: Rule 102 is configured by Ansible + ipsec: match-ipsec + + - number: 103 + action: accept + description: Rule 103 is configured by Ansible + destination: + group: + address_group: inbound + source: + address: 192.0.2.0 + state: + established: true + new: false + invalid: false + related: true + state: merged + + - name: Assert that before dicts were correctly generated + assert: + that: "{{ merged['before'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) |length\ + \ == 0 }}" + + - name: Assert that after dicts was correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Merge the provided configuration with the existing running configuration + (IDEMPOTENT) + register: result + vyos.vyos.vyos_firewall_rules: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/overridden.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/overridden.yaml new file mode 100644 index 0000000..6acc951 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/overridden.yaml @@ -0,0 +1,69 @@ +--- +- debug: + msg: START vyos_firewall_rules overridden integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Overrides all device configuration with provided configuration + register: result + vyos.vyos.vyos_firewall_rules: &id001 + config: + + - afi: ipv4 + rule_sets: + + - name: Downlink + description: IPv4 INBOUND rule set + default_action: accept + rules: + + - number: 501 + action: accept + description: Rule 501 is configured by Ansible + ipsec: match-ipsec + + - number: 502 + action: reject + description: Rule 502 is configured by Ansible + ipsec: match-ipsec + state: overridden + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that correct commands were generated + assert: + that: + - "{{ overridden['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that after dicts were correctly generated + assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Overrides all device configuration with provided configurations (IDEMPOTENT) + register: result + vyos.vyos.vyos_firewall_rules: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/parsed.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/parsed.yaml new file mode 100644 index 0000000..a793ac5 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/parsed.yaml @@ -0,0 +1,41 @@ +--- +- debug: + msg: START vyos_firewall_rules parsed integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Gather firewall_rules facts + register: firewall_rules_facts + vyos.vyos.vyos_facts: + gather_subset: + - default + gather_network_resources: + - firewall_rules + + - name: Provide the running configuration for parsing (config to be parsed) + register: result + vyos.vyos.vyos_firewall_rules: &id001 + running_config: "{{ lookup('file', '_parsed_config.cfg') }}" + state: parsed + + - name: Assert that correct parsing done + assert: + that: "{{ ansible_facts['network_resources']['firewall_rules'] | symmetric_difference(result['parsed'])\ + \ |length == 0 }}" + + - name: Gather the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_firewall_rules: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/rendered.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/rendered.yaml new file mode 100644 index 0000000..f000998 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/rendered.yaml @@ -0,0 +1,73 @@ +--- +- debug: + msg: START vyos_firewall_rules rendered integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Structure provided configuration into device specific commands + register: result + vyos.vyos.vyos_firewall_rules: &id001 + config: + + - afi: ipv6 + rule_sets: + + - name: UPLINK + description: This is ipv6 specific rule-set + default_action: accept + + - afi: ipv4 + rule_sets: + + - name: INBOUND + description: IPv4 INBOUND rule set + default_action: accept + rules: + + - number: 101 + action: accept + description: Rule 101 is configured by Ansible + ipsec: match-ipsec + + - number: 102 + action: reject + description: Rule 102 is configured by Ansible + ipsec: match-ipsec + + - number: 103 + action: accept + description: Rule 103 is configured by Ansible + destination: + group: + address_group: inbound + source: + address: 192.0.2.0 + state: + established: true + new: false + invalid: false + related: true + state: rendered + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ rendered['commands'] | symmetric_difference(result['rendered'])\ + \ |length == 0 }}" + + - name: Structure provided configuration into device specific commands (IDEMPOTENT) + register: result + vyos.vyos.vyos_firewall_rules: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/replaced.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/replaced.yaml new file mode 100644 index 0000000..eba1689 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/replaced.yaml @@ -0,0 +1,78 @@ +--- +- debug: + msg: START vyos_firewall_rules replaced integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Replace device configurations of listed firewall rules with provided + configurations + register: result + vyos.vyos.vyos_firewall_rules: &id001 + config: + + - afi: ipv6 + rule_sets: + + - name: UPLINK + description: This is ipv6 specific rule-set + default_action: accept + + - afi: ipv4 + rule_sets: + + - name: INBOUND + description: IPv4 INBOUND rule set + default_action: accept + rules: + + - number: 101 + action: accept + description: Rule 101 is configured by Ansible + ipsec: match-ipsec + + - number: 104 + action: reject + description: Rule 104 is configured by Ansible + ipsec: match-none + state: replaced + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that before dicts are correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that after dict is correctly generated + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Replace device configurations of listed firewall rules with provided + configurarions (IDEMPOTENT) + register: result + vyos.vyos.vyos_firewall_rules: *id001 + + - name: Assert that task was idempotent + assert: + that: + - result['changed'] == false + + - name: Assert that before dict is correctly generated + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_rules/tests/cli/rtt.yaml b/tests/integration/targets/vyos_firewall_rules/tests/cli/rtt.yaml new file mode 100644 index 0000000..762086f --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/tests/cli/rtt.yaml @@ -0,0 +1,101 @@ +--- +- debug: + msg: START vyos_firewall_rules round trip integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + + - name: Apply the provided configuration (base config) + register: base_config + vyos.vyos.vyos_firewall_rules: + config: + + - afi: ipv6 + rule_sets: + + - name: UPLINK + description: This is ipv6 specific rule-set + default_action: accept + rules: + + - number: 1 + action: accept + description: Fwipv6-Rule 1 is configured by Ansible + ipsec: match-ipsec + + - number: 2 + action: accept + description: Fwipv6-Rule 2 is configured by Ansible + ipsec: match-ipsec + + - afi: ipv4 + rule_sets: + + - name: INBOUND + description: IPv4 INBOUND rule set + default_action: accept + rules: + + - number: 101 + action: accept + description: Rule 101 is configured by Ansible + ipsec: match-ipsec + + - number: 102 + action: reject + description: Rule 102 is configured by Ansible + ipsec: match-ipsec + state: merged + + - name: Gather firewall_rules facts + vyos.vyos.vyos_facts: + gather_subset: + - default + gather_network_resources: + - firewall_rules + + - name: Apply the provided configuration (config to be reverted) + register: result + vyos.vyos.vyos_firewall_rules: + config: + + - afi: ipv4 + rule_sets: + + - name: INBOUND + description: IPv4 INBOUND rule set + default_action: accept + rules: + + - number: 103 + action: accept + description: Rule 103 is configured by Ansible + source: + address: 192.0.2.0 + state: + established: true + new: false + invalid: false + related: true + state: merged + + - name: Assert that changes were applied + assert: + that: "{{ round_trip['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Revert back to base config using facts round trip + register: revert + vyos.vyos.vyos_firewall_rules: + config: "{{ ansible_facts['network_resources']['firewall_rules'] }}" + state: overridden + + - name: Assert that config was reverted + assert: + that: "{{ base_config['after'] | symmetric_difference(revert['after']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_firewall_rules/vars/main.yaml b/tests/integration/targets/vyos_firewall_rules/vars/main.yaml new file mode 100644 index 0000000..c15a101 --- /dev/null +++ b/tests/integration/targets/vyos_firewall_rules/vars/main.yaml @@ -0,0 +1,312 @@ +--- +merged: + before: [] + commands: + - set firewall ipv6-name UPLINK default-action 'accept' + - set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' + - set firewall ipv6-name UPLINK rule 1 action 'accept' + - set firewall ipv6-name UPLINK rule 1 + - set firewall ipv6-name UPLINK rule 1 description 'Fwipv6-Rule 1 is configured + by Ansible' + - set firewall ipv6-name UPLINK rule 1 ipsec 'match-ipsec' + - set firewall ipv6-name UPLINK rule 2 action 'accept' + - set firewall ipv6-name UPLINK rule 2 + - set firewall ipv6-name UPLINK rule 2 description 'Fwipv6-Rule 2 is configured + by Ansible' + - set firewall ipv6-name UPLINK rule 2 ipsec 'match-ipsec' + - set firewall name INBOUND default-action 'accept' + - set firewall name INBOUND description 'IPv4 INBOUND rule set' + - set firewall name INBOUND rule 101 action 'accept' + - set firewall name INBOUND rule 101 + - set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' + - set firewall name INBOUND rule 101 ipsec 'match-ipsec' + - set firewall name INBOUND rule 102 action 'reject' + - set firewall name INBOUND rule 102 + - set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' + - set firewall name INBOUND rule 102 ipsec 'match-ipsec' + - set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' + - set firewall name INBOUND rule 103 destination group address-group inbound + - set firewall name INBOUND rule 103 + - set firewall name INBOUND rule 103 source address 192.0.2.0 + - set firewall name INBOUND rule 103 state established enable + - set firewall name INBOUND rule 103 state related enable + - set firewall name INBOUND rule 103 state invalid disable + - set firewall name INBOUND rule 103 state new disable + - set firewall name INBOUND rule 103 action 'accept' + after: + - afi: ipv6 + rule_sets: + - name: UPLINK + description: This is ipv6 specific rule-set + default_action: accept + rules: + - number: 1 + action: accept + description: Fwipv6-Rule 1 is configured by Ansible + ipsec: match-ipsec + - number: 2 + action: accept + description: Fwipv6-Rule 2 is configured by Ansible + ipsec: match-ipsec + - afi: ipv4 + rule_sets: + - name: INBOUND + description: IPv4 INBOUND rule set + default_action: accept + rules: + - number: 101 + action: accept + description: Rule 101 is configured by Ansible + ipsec: match-ipsec + - number: 102 + action: reject + description: Rule 102 is configured by Ansible + ipsec: match-ipsec + - number: 103 + action: accept + description: Rule 103 is configured by Ansible + destination: + group: + address_group: inbound + source: + address: 192.0.2.0 + state: + established: true + new: false + invalid: false + related: true +populate: + - afi: ipv6 + rule_sets: + - name: UPLINK + description: This is ipv6 specific rule-set + default_action: accept + rules: + - number: 1 + action: accept + description: Fwipv6-Rule 1 is configured by Ansible + ipsec: match-ipsec + - number: 2 + action: accept + description: Fwipv6-Rule 2 is configured by Ansible + ipsec: match-ipsec + - afi: ipv4 + rule_sets: + - name: INBOUND + description: IPv4 INBOUND rule set + default_action: accept + rules: + - number: 101 + action: accept + description: Rule 101 is configured by Ansible + ipsec: match-ipsec + - number: 102 + action: reject + description: Rule 102 is configured by Ansible + ipsec: match-ipsec + - number: 103 + action: accept + description: Rule 103 is configured by Ansible + destination: + group: + address_group: inbound + source: + address: 192.0.2.0 + state: + established: true + new: false + invalid: false + related: true +replaced: + commands: + - delete firewall ipv6-name UPLINK rule 1 + - delete firewall ipv6-name UPLINK rule 2 + - delete firewall name INBOUND rule 102 + - delete firewall name INBOUND rule 103 + - set firewall name INBOUND rule 104 action 'reject' + - set firewall name INBOUND rule 104 description 'Rule 104 is configured by Ansible' + - set firewall name INBOUND rule 104 + - set firewall name INBOUND rule 104 ipsec 'match-none' + after: + - afi: ipv6 + rule_sets: + - name: UPLINK + description: This is ipv6 specific rule-set + default_action: accept + - afi: ipv4 + rule_sets: + - name: INBOUND + description: IPv4 INBOUND rule set + default_action: accept + rules: + - number: 101 + action: accept + description: Rule 101 is configured by Ansible + ipsec: match-ipsec + - number: 104 + action: reject + description: Rule 104 is configured by Ansible + ipsec: match-none +overridden: + before: + - afi: ipv6 + rule_sets: + - name: UPLINK + description: This is ipv6 specific rule-set + default_action: accept + - afi: ipv4 + rule_sets: + - name: INBOUND + description: IPv4 INBOUND rule set + default_action: accept + rules: + - number: 101 + action: accept + description: Rule 101 is configured by Ansible + ipsec: match-ipsec + - number: 104 + action: reject + description: Rule 104 is configured by Ansible + ipsec: match-none + commands: + - delete firewall ipv6-name UPLINK + - delete firewall name INBOUND + - set firewall name Downlink default-action 'accept' + - set firewall name Downlink description 'IPv4 INBOUND rule set' + - set firewall name Downlink rule 501 action 'accept' + - set firewall name Downlink rule 501 + - set firewall name Downlink rule 501 description 'Rule 501 is configured by Ansible' + - set firewall name Downlink rule 501 ipsec 'match-ipsec' + - set firewall name Downlink rule 502 action 'reject' + - set firewall name Downlink rule 502 + - set firewall name Downlink rule 502 description 'Rule 502 is configured by Ansible' + - set firewall name Downlink rule 502 ipsec 'match-ipsec' + after: + - afi: ipv4 + rule_sets: + - name: Downlink + description: IPv4 INBOUND rule set + default_action: accept + rules: + - number: 501 + action: accept + description: Rule 501 is configured by Ansible + ipsec: match-ipsec + - number: 502 + action: reject + description: Rule 502 is configured by Ansible + ipsec: match-ipsec +rendered: + commands: + - set firewall ipv6-name UPLINK default-action 'accept' + - set firewall ipv6-name UPLINK description 'This is ipv6 specific rule-set' + - set firewall name INBOUND default-action 'accept' + - set firewall name INBOUND description 'IPv4 INBOUND rule set' + - set firewall name INBOUND rule 101 action 'accept' + - set firewall name INBOUND rule 101 + - set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible' + - set firewall name INBOUND rule 101 ipsec 'match-ipsec' + - set firewall name INBOUND rule 102 action 'reject' + - set firewall name INBOUND rule 102 + - set firewall name INBOUND rule 102 description 'Rule 102 is configured by Ansible' + - set firewall name INBOUND rule 102 ipsec 'match-ipsec' + - set firewall name INBOUND rule 103 description 'Rule 103 is configured by Ansible' + - set firewall name INBOUND rule 103 destination group address-group inbound + - set firewall name INBOUND rule 103 + - set firewall name INBOUND rule 103 source address 192.0.2.0 + - set firewall name INBOUND rule 103 state established enable + - set firewall name INBOUND rule 103 state related enable + - set firewall name INBOUND rule 103 state invalid disable + - set firewall name INBOUND rule 103 state new disable + - set firewall name INBOUND rule 103 action 'accept' +deleted_rs: + commands: + - delete firewall ipv6-name UPLINK + - delete firewall name INBOUND + after: [] +deleted_afi_all: + commands: + - delete firewall ipv6-name + - delete firewall name + after: [] +deleted_r: + commands: + - delete firewall ipv6-name UPLINK rule 1 + after: + - afi: ipv6 + rule_sets: + - name: UPLINK + description: This is ipv6 specific rule-set + default_action: accept + rules: + - number: 2 + action: accept + description: Fwipv6-Rule 2 is configured by Ansible + ipsec: match-ipsec + - afi: ipv4 + rule_sets: + - name: INBOUND + description: IPv4 INBOUND rule set + default_action: accept + rules: + - number: 101 + action: accept + description: Rule 101 is configured by Ansible + ipsec: match-ipsec + - number: 102 + action: reject + description: Rule 102 is configured by Ansible + ipsec: match-ipsec + - number: 103 + action: accept + description: Rule 103 is configured by Ansible + destination: + group: + address_group: inbound + source: + address: 192.0.2.0 + state: + established: true + new: false + invalid: false + related: true +round_trip: + after: + - afi: ipv6 + rule_sets: + - name: UPLINK + description: This is ipv6 specific rule-set + default_action: accept + rules: + - number: 1 + action: accept + description: Fwipv6-Rule 1 is configured by Ansible + ipsec: match-ipsec + - number: 2 + action: accept + description: Fwipv6-Rule 2 is configured by Ansible + ipsec: match-ipsec + - afi: ipv4 + rule_sets: + - name: INBOUND + description: IPv4 INBOUND rule set + default_action: accept + rules: + - number: 101 + action: accept + description: Rule 101 is configured by Ansible + ipsec: match-ipsec + - number: 102 + action: reject + description: Rule 102 is configured by Ansible + ipsec: match-ipsec + - number: 103 + action: accept + description: Rule 103 is configured by Ansible + source: + address: 192.0.2.0 + state: + established: true + new: false + invalid: false + related: true diff --git a/tests/integration/targets/vyos_static_routes/defaults/main.yaml b/tests/integration/targets/vyos_static_routes/defaults/main.yaml new file mode 100644 index 0000000..852a6be --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/defaults/main.yaml @@ -0,0 +1,3 @@ +--- +testcase: '[^_].*' +test_items: [] diff --git a/tests/integration/targets/vyos_static_routes/meta/main.yaml b/tests/integration/targets/vyos_static_routes/meta/main.yaml new file mode 100644 index 0000000..91da2a7 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/meta/main.yaml @@ -0,0 +1,2 @@ +--- +... diff --git a/tests/integration/targets/vyos_static_routes/tasks/cli.yaml b/tests/integration/targets/vyos_static_routes/tasks/cli.yaml new file mode 100644 index 0000000..93eb2fe --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tasks/cli.yaml @@ -0,0 +1,19 @@ +--- +- name: Collect all cli test cases + find: + paths: '{{ role_path }}/tests/cli' + patterns: '{{ testcase }}.yaml' + use_regex: true + register: test_cases + delegate_to: localhost + +- name: Set test_items + set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}" + +- name: Run test case (connection=ansible.netcommon.network_cli) + include: '{{ test_case_to_run }}' + vars: + ansible_connection: ansible.netcommon.network_cli + with_items: '{{ test_items }}' + loop_control: + loop_var: test_case_to_run diff --git a/tests/integration/targets/vyos_static_routes/tasks/main.yaml b/tests/integration/targets/vyos_static_routes/tasks/main.yaml new file mode 100644 index 0000000..a3db933 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tasks/main.yaml @@ -0,0 +1,4 @@ +--- +- include: cli.yaml + tags: + - cli diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/_parsed_config.cfg b/tests/integration/targets/vyos_static_routes/tests/cli/_parsed_config.cfg new file mode 100644 index 0000000..b2ecd4e --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/_parsed_config.cfg @@ -0,0 +1,6 @@ +set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' +set protocols static route 192.0.2.32/28 next-hop '192.0.2.10' +set protocols static route 192.0.2.32/28 blackhole +set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' +set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' +set protocols static route6 2001:db8:1000::/36 blackhole distance '2' diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/_populate.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/_populate.yaml new file mode 100644 index 0000000..f292e5d --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/_populate.yaml @@ -0,0 +1,12 @@ +--- +- name: Setup + vars: + lines: "set protocols static route 192.0.2.32/28 next-hop '192.0.2.10'\nset\ + \ protocols static route 192.0.2.32/28 next-hop '192.0.2.9'\nset protocols\ + \ static route 192.0.2.32/28 blackhole\nset protocols static route 192.0.2.32/28\n\ + set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1'\n\ + set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2'\n\ + set protocols static route6 2001:db8:1000::/36 blackhole distance '2'\nset\ + \ protocols static route6 2001:db8:1000::/36\n" + ansible.netcommon.cli_config: + config: '{{ lines }}' diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/_remove_config.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/_remove_config.yaml new file mode 100644 index 0000000..5a5cccb --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/_remove_config.yaml @@ -0,0 +1,6 @@ +--- +- name: Remove Config + vars: + lines: "delete protocols static route\ndelete protocols static route6\n" + ansible.netcommon.cli_config: + config: '{{ lines }}' diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/deleted.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/deleted.yaml new file mode 100644 index 0000000..7f098f5 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/deleted.yaml @@ -0,0 +1,62 @@ +--- +- debug: + msg: Start vyos_static_routes deleted integration tests ansible_connection={{ + ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + + - name: Delete static route based on destiation. + register: result + vyos.vyos.vyos_static_routes: &id001 + config: + + - address_families: + + - afi: ipv4 + routes: + + - dest: 192.0.2.32/28 + + - afi: ipv6 + routes: + + - dest: 2001:db8:1000::/36 + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_dest['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_dest['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + register: result + vyos.vyos.vyos_static_routes: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands|length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_dest['after'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/deleted_afi.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/deleted_afi.yaml new file mode 100644 index 0000000..221f1b5 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/deleted_afi.yaml @@ -0,0 +1,56 @@ +--- +- debug: + msg: Start vyos_static_routes deleted integration tests ansible_connection={{ + ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + + - name: Delete static route based on afi. + register: result + vyos.vyos.vyos_static_routes: &id001 + config: + + - address_families: + + - afi: ipv4 + + - afi: ipv6 + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_afi_all['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['after'])\ + \ |length == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + register: result + vyos.vyos.vyos_static_routes: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands|length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['before'])\ + \ |length == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/deleted_all.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/deleted_all.yaml new file mode 100644 index 0000000..e10f1bc --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/deleted_all.yaml @@ -0,0 +1,50 @@ +--- +- debug: + msg: Start vyos_static_routes deleted integration tests ansible_connection={{ + ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + + - name: Delete all the static routes. + register: result + vyos.vyos.vyos_static_routes: &id001 + config: + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_afi_all['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['after'])\ + \ |length == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + register: result + vyos.vyos.vyos_static_routes: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands|length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_afi_all['after'] | symmetric_difference(result['before'])\ + \ |length == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/deleted_nh.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/deleted_nh.yaml new file mode 100644 index 0000000..f6075d2 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/deleted_nh.yaml @@ -0,0 +1,68 @@ +--- +- debug: + msg: Start vyos_static_routes deleted integration tests ansible_connection={{ + ansible_connection }} + +- include_tasks: _populate.yaml + +- block: + + - name: Delete static route based on next_hop. + register: result + vyos.vyos.vyos_static_routes: &id001 + config: + + - address_families: + + - afi: ipv4 + routes: + + - dest: 192.0.2.32/28 + next_hops: + + - forward_router_address: 192.0.2.9 + + - afi: ipv6 + routes: + + - dest: 2001:db8:1000::/36 + next_hops: + + - forward_router_address: 2001:db8:2000:2::1 + state: deleted + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that the correct set of commands were generated + assert: + that: + - "{{ deleted_nh['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that the after dicts were correctly generated + assert: + that: + - "{{ deleted_nh['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Delete attributes of given interfaces (IDEMPOTENT) + register: result + vyos.vyos.vyos_static_routes: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result.changed == false + - result.commands|length == 0 + + - name: Assert that the before dicts were correctly generated + assert: + that: + - "{{ deleted_nh['after'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/empty_config.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/empty_config.yaml new file mode 100644 index 0000000..f58ef39 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/empty_config.yaml @@ -0,0 +1,60 @@ +--- +- debug: + msg: START vyos_static_routes empty_config integration tests on connection={{ + ansible_connection }} + +- name: Merged with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_static_routes: + config: + state: merged + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state merged' + +- name: Replaced with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_static_routes: + config: + state: replaced + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state replaced' + +- name: Overridden with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_static_routes: + config: + state: overridden + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state overridden' + +- name: Parsed with empty running_config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_static_routes: + running_config: + state: parsed + +- assert: + that: + - result.msg == 'value of running_config parameter must not be empty for state + parsed' + +- name: Rendered with empty config should give appropriate error message + register: result + ignore_errors: true + vyos.vyos.vyos_static_routes: + config: + state: rendered + +- assert: + that: + - result.msg == 'value of config parameter must not be empty for state rendered' diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/gathered.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/gathered.yaml new file mode 100644 index 0000000..d3b84d1 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/gathered.yaml @@ -0,0 +1,34 @@ +--- +- debug: + msg: START vyos_static_routes gathered integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Merge the provided configuration with the exisiting running configuration + register: result + vyos.vyos.vyos_static_routes: &id001 + config: + state: gathered + + - name: Assert that gathered dicts was correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['gathered']) |length == 0\ + \ }}" + + - name: Gather the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_static_routes: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/merged.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/merged.yaml new file mode 100644 index 0000000..999ae86 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/merged.yaml @@ -0,0 +1,78 @@ +--- +- debug: + msg: START vyos_static_routes merged integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- block: + + - name: Merge the provided configuration with the exisiting running configuration + register: result + vyos.vyos.vyos_static_routes: &id001 + config: + + - address_families: + + - afi: ipv4 + routes: + + - dest: 192.0.2.32/28 + blackhole_config: + type: blackhole + next_hops: + + - forward_router_address: 192.0.2.10 + + - forward_router_address: 192.0.2.9 + + - address_families: + + - afi: ipv6 + routes: + + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + + - forward_router_address: 2001:db8:2000:2::1 + + - forward_router_address: 2001:db8:2000:2::2 + state: merged + + - name: Assert that before dicts were correctly generated + assert: + that: "{{ merged['before'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ merged['commands'] | symmetric_difference(result['commands']) |length\ + \ == 0 }}" + + - name: Assert that after dicts was correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Merge the provided configuration with the existing running configuration + (IDEMPOTENT) + register: result + vyos.vyos.vyos_static_routes: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ merged['after'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/overridden.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/overridden.yaml new file mode 100644 index 0000000..a9112a5 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/overridden.yaml @@ -0,0 +1,61 @@ +--- +- debug: + msg: START vyos_static_routes overridden integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Overrides all device configuration with provided configuration + register: result + vyos.vyos.vyos_static_routes: &id001 + config: + + - address_families: + + - afi: ipv4 + routes: + + - dest: 198.0.2.48/28 + next_hops: + + - forward_router_address: 192.0.2.18 + state: overridden + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that correct commands were generated + assert: + that: + - "{{ overridden['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that after dicts were correctly generated + assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Overrides all device configuration with provided configurations (IDEMPOTENT) + register: result + vyos.vyos.vyos_static_routes: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + + - name: Assert that before dicts were correctly generated + assert: + that: + - "{{ overridden['after'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/parsed.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/parsed.yaml new file mode 100644 index 0000000..4b6e434 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/parsed.yaml @@ -0,0 +1,41 @@ +--- +- debug: + msg: START vyos_static_routes parsed integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Gather static_routes facts + register: static_routes_facts + vyos.vyos.vyos_facts: + gather_subset: + - default + gather_network_resources: + - static_routes + + - name: Provide the running configuration for parsing (config to be parsed) + register: result + vyos.vyos.vyos_static_routes: &id001 + running_config: "{{ lookup('file', '_parsed_config.cfg') }}" + state: parsed + + - name: Assert that correct parsing done + assert: + that: "{{ ansible_facts['network_resources']['static_routes'] | symmetric_difference(result['parsed'])\ + \ |length == 0 }}" + + - name: Gather the existing running configuration (IDEMPOTENT) + register: result + vyos.vyos.vyos_static_routes: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/rendered.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/rendered.yaml new file mode 100644 index 0000000..ff18523 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/rendered.yaml @@ -0,0 +1,62 @@ +--- +- debug: + msg: START vyos_static_routes rendered integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - name: Structure provided configuration into device specific commands + register: result + vyos.vyos.vyos_static_routes: &id001 + config: + + - address_families: + + - afi: ipv4 + routes: + + - dest: 192.0.2.32/28 + blackhole_config: + type: blackhole + next_hops: + + - forward_router_address: 192.0.2.10 + + - forward_router_address: 192.0.2.9 + + - address_families: + + - afi: ipv6 + routes: + + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + + - forward_router_address: 2001:db8:2000:2::1 + + - forward_router_address: 2001:db8:2000:2::2 + state: rendered + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ rendered['commands'] | symmetric_difference(result['rendered'])\ + \ |length == 0 }}" + + - name: Structure provided configuration into device specific commands (IDEMPOTENT) + register: result + vyos.vyos.vyos_static_routes: *id001 + + - name: Assert that the previous task was idempotent + assert: + that: + - result['changed'] == false + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/replaced.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/replaced.yaml new file mode 100644 index 0000000..80ed801 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/replaced.yaml @@ -0,0 +1,69 @@ +--- +- debug: + msg: START vyos_static_routes replaced integration tests on connection={{ ansible_connection + }} + +- include_tasks: _remove_config.yaml + +- include_tasks: _populate.yaml + +- block: + + - 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 + + - name: Assert that correct set of commands were generated + assert: + that: + - "{{ replaced['commands'] | symmetric_difference(result['commands'])\ + \ |length == 0 }}" + + - name: Assert that before dicts are correctly generated + assert: + that: + - "{{ populate | symmetric_difference(result['before']) |length == 0 }}" + + - name: Assert that after dict is correctly generated + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Replace device configurations of listed static routes with provided + configurarions (IDEMPOTENT) + register: result + vyos.vyos.vyos_static_routes: *id001 + + - name: Assert that task was idempotent + assert: + that: + - result['changed'] == false + + - name: Assert that before dict is correctly generated + assert: + that: + - "{{ replaced['after'] | symmetric_difference(result['before']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_static_routes/tests/cli/rtt.yaml b/tests/integration/targets/vyos_static_routes/tests/cli/rtt.yaml new file mode 100644 index 0000000..340fde9 --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/tests/cli/rtt.yaml @@ -0,0 +1,90 @@ +--- +- debug: + msg: START vyos_static_routes round trip integration tests on connection={{ + ansible_connection }} + +- include_tasks: _remove_config.yaml + +- block: + + - name: Apply the provided configuration (base config) + register: base_config + vyos.vyos.vyos_static_routes: + config: + + - address_families: + + - afi: ipv4 + routes: + + - dest: 192.0.2.32/28 + blackhole_config: + type: blackhole + next_hops: + + - forward_router_address: 192.0.2.10 + + - forward_router_address: 192.0.2.9 + + - address_families: + + - afi: ipv6 + routes: + + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + + - forward_router_address: 2001:db8:2000:2::1 + + - forward_router_address: 2001:db8:2000:2::2 + state: merged + + - name: Gather static_routes facts + vyos.vyos.vyos_facts: + gather_subset: + - default + gather_network_resources: + - static_routes + + - name: Apply the provided configuration (config to be reverted) + register: result + vyos.vyos.vyos_static_routes: + 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: merged + + - name: Assert that changes were applied + assert: + that: "{{ round_trip['after'] | symmetric_difference(result['after']) |length\ + \ == 0 }}" + + - name: Revert back to base config using facts round trip + register: revert + vyos.vyos.vyos_static_routes: + config: "{{ ansible_facts['network_resources']['static_routes'] }}" + state: overridden + + - name: Assert that config was reverted + assert: + that: "{{ base_config['after'] | symmetric_difference(revert['after']) |length\ + \ == 0 }}" + always: + + - include_tasks: _remove_config.yaml diff --git a/tests/integration/targets/vyos_static_routes/vars/main.yaml b/tests/integration/targets/vyos_static_routes/vars/main.yaml new file mode 100644 index 0000000..93b875f --- /dev/null +++ b/tests/integration/targets/vyos_static_routes/vars/main.yaml @@ -0,0 +1,147 @@ +--- +merged: + before: [] + commands: + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.10' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' + - set protocols static route 192.0.2.32/28 blackhole + - set protocols static route 192.0.2.32/28 + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' + - set protocols static route6 2001:db8:1000::/36 blackhole distance '2' + - set protocols static route6 2001:db8:1000::/36 + after: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.32/28 + blackhole_config: + type: blackhole + next_hops: + - forward_router_address: 192.0.2.9 + - forward_router_address: 192.0.2.10 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 +populate: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.32/28 + blackhole_config: + type: blackhole + next_hops: + - forward_router_address: 192.0.2.9 + - forward_router_address: 192.0.2.10 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 +replaced: + commands: + - delete protocols static route 192.0.2.32/28 next-hop '192.0.2.10' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.7' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.8' + - set protocols static route 192.0.2.32/28 blackhole distance '2' + after: + - 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 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 +overridden: + commands: + - delete protocols static route 192.0.2.32/28 + - delete protocols static route6 2001:db8:1000::/36 + - set protocols static route 198.0.2.48/28 next-hop '192.0.2.18' + - set protocols static route 198.0.2.48/28 + after: + - address_families: + - afi: ipv4 + routes: + - dest: 198.0.2.48/28 + next_hops: + - forward_router_address: 192.0.2.18 +rendered: + commands: + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.10' + - set protocols static route 192.0.2.32/28 next-hop '192.0.2.9' + - set protocols static route 192.0.2.32/28 blackhole + - set protocols static route 192.0.2.32/28 + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' + - set protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::2' + - set protocols static route6 2001:db8:1000::/36 blackhole distance '2' + - set protocols static route6 2001:db8:1000::/36 +deleted_dest: + commands: + - delete protocols static route 192.0.2.32/28 + - delete protocols static route6 2001:db8:1000::/36 + after: [] +deleted_nh: + commands: + - delete protocols static route 192.0.2.32/28 next-hop '192.0.2.9' + - delete protocols static route6 2001:db8:1000::/36 next-hop '2001:db8:2000:2::1' + after: + - address_families: + - afi: ipv4 + routes: + - dest: 192.0.2.32/28 + blackhole_config: + type: blackhole + next_hops: + - forward_router_address: 192.0.2.10 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::2 +deleted_afi_all: + commands: + - delete protocols static route + - delete protocols static route6 + after: [] +round_trip: + after: + - 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 + - forward_router_address: 192.0.2.10 + - afi: ipv6 + routes: + - dest: 2001:db8:1000::/36 + blackhole_config: + distance: 2 + next_hops: + - forward_router_address: 2001:db8:2000:2::1 + - forward_router_address: 2001:db8:2000:2::2 diff --git a/tests/sanity/ignore-2.10.txt b/tests/sanity/ignore-2.10.txt index d25476f..a62f497 100644 --- a/tests/sanity/ignore-2.10.txt +++ b/tests/sanity/ignore-2.10.txt @@ -1,93 +1,83 @@ plugins/module_utils/network/vyos/vyos.py future-import-boilerplate plugins/module_utils/network/vyos/vyos.py metaclass-boilerplate plugins/modules/vyos_banner.py future-import-boilerplate plugins/modules/vyos_banner.py metaclass-boilerplate plugins/modules/vyos_banner.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_banner.py validate-modules:doc-missing-type plugins/modules/vyos_banner.py validate-modules:doc-required-mismatch plugins/modules/vyos_command.py future-import-boilerplate plugins/modules/vyos_command.py metaclass-boilerplate plugins/modules/vyos_command.py pylint:blacklisted-name plugins/modules/vyos_command.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_command.py validate-modules:doc-missing-type plugins/modules/vyos_command.py validate-modules:doc-required-mismatch plugins/modules/vyos_command.py validate-modules:parameter-list-no-elements plugins/modules/vyos_command.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_config.py future-import-boilerplate plugins/modules/vyos_config.py metaclass-boilerplate plugins/modules/vyos_config.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_config.py validate-modules:doc-missing-type plugins/modules/vyos_config.py validate-modules:doc-required-mismatch plugins/modules/vyos_config.py validate-modules:parameter-list-no-elements plugins/modules/vyos_config.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_facts.py future-import-boilerplate plugins/modules/vyos_facts.py metaclass-boilerplate plugins/modules/vyos_facts.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_facts.py validate-modules:doc-required-mismatch plugins/modules/vyos_facts.py validate-modules:parameter-list-no-elements plugins/modules/vyos_facts.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_interfaces.py validate-modules:doc-elements-mismatch plugins/modules/vyos_lag_interfaces.py validate-modules:doc-elements-mismatch plugins/modules/vyos_lag_interfaces.py validate-modules:parameter-list-no-elements plugins/modules/vyos_lldp_global.py validate-modules:parameter-list-no-elements plugins/modules/vyos_lldp_interfaces.py validate-modules:doc-elements-mismatch plugins/modules/vyos_lldp_interfaces.py validate-modules:doc-required-mismatch plugins/modules/vyos_logging.py future-import-boilerplate plugins/modules/vyos_logging.py metaclass-boilerplate plugins/modules/vyos_logging.py validate-modules:doc-choices-do-not-match-spec plugins/modules/vyos_logging.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_logging.py validate-modules:doc-elements-mismatch plugins/modules/vyos_logging.py validate-modules:doc-missing-type plugins/modules/vyos_logging.py validate-modules:doc-required-mismatch plugins/modules/vyos_logging.py validate-modules:missing-suboption-docs plugins/modules/vyos_logging.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_logging.py validate-modules:undocumented-parameter plugins/modules/vyos_ping.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_ping.py validate-modules:doc-required-mismatch plugins/modules/vyos_ping.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_static_route.py future-import-boilerplate -plugins/modules/vyos_static_route.py metaclass-boilerplate -plugins/modules/vyos_static_route.py validate-modules:doc-choices-do-not-match-spec -plugins/modules/vyos_static_route.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_static_route.py validate-modules:doc-elements-mismatch -plugins/modules/vyos_static_route.py validate-modules:doc-missing-type -plugins/modules/vyos_static_route.py validate-modules:doc-required-mismatch -plugins/modules/vyos_static_route.py validate-modules:missing-suboption-docs -plugins/modules/vyos_static_route.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_static_route.py validate-modules:undocumented-parameter plugins/modules/vyos_system.py future-import-boilerplate plugins/modules/vyos_system.py metaclass-boilerplate plugins/modules/vyos_system.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_system.py validate-modules:doc-required-mismatch plugins/modules/vyos_system.py validate-modules:parameter-list-no-elements plugins/modules/vyos_system.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_user.py future-import-boilerplate plugins/modules/vyos_user.py metaclass-boilerplate plugins/modules/vyos_user.py validate-modules:doc-choices-do-not-match-spec plugins/modules/vyos_user.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_user.py validate-modules:doc-elements-mismatch plugins/modules/vyos_user.py validate-modules:doc-missing-type plugins/modules/vyos_user.py validate-modules:doc-required-mismatch plugins/modules/vyos_user.py validate-modules:missing-suboption-docs plugins/modules/vyos_user.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_user.py validate-modules:undocumented-parameter plugins/modules/vyos_vlan.py validate-modules:doc-choices-do-not-match-spec plugins/modules/vyos_vlan.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_vlan.py validate-modules:doc-elements-mismatch plugins/modules/vyos_vlan.py validate-modules:doc-missing-type plugins/modules/vyos_vlan.py validate-modules:doc-required-mismatch plugins/modules/vyos_vlan.py validate-modules:missing-suboption-docs plugins/modules/vyos_vlan.py validate-modules:parameter-list-no-elements plugins/modules/vyos_vlan.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_vlan.py validate-modules:undocumented-parameter plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` plugins/doc_fragments/vyos.py future-import-boilerplate plugins/doc_fragments/vyos.py metaclass-boilerplate tests/unit/mock/path.py future-import-boilerplate tests/unit/mock/path.py metaclass-boilerplate tests/unit/mock/yaml_helper.py future-import-boilerplate tests/unit/mock/yaml_helper.py metaclass-boilerplate tests/unit/modules/conftest.py future-import-boilerplate tests/unit/modules/conftest.py metaclass-boilerplate tests/unit/modules/utils.py future-import-boilerplate tests/unit/modules/utils.py metaclass-boilerplate \ No newline at end of file diff --git a/tests/sanity/ignore-2.9.txt b/tests/sanity/ignore-2.9.txt index d25476f..a62f497 100644 --- a/tests/sanity/ignore-2.9.txt +++ b/tests/sanity/ignore-2.9.txt @@ -1,93 +1,83 @@ plugins/module_utils/network/vyos/vyos.py future-import-boilerplate plugins/module_utils/network/vyos/vyos.py metaclass-boilerplate plugins/modules/vyos_banner.py future-import-boilerplate plugins/modules/vyos_banner.py metaclass-boilerplate plugins/modules/vyos_banner.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_banner.py validate-modules:doc-missing-type plugins/modules/vyos_banner.py validate-modules:doc-required-mismatch plugins/modules/vyos_command.py future-import-boilerplate plugins/modules/vyos_command.py metaclass-boilerplate plugins/modules/vyos_command.py pylint:blacklisted-name plugins/modules/vyos_command.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_command.py validate-modules:doc-missing-type plugins/modules/vyos_command.py validate-modules:doc-required-mismatch plugins/modules/vyos_command.py validate-modules:parameter-list-no-elements plugins/modules/vyos_command.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_config.py future-import-boilerplate plugins/modules/vyos_config.py metaclass-boilerplate plugins/modules/vyos_config.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_config.py validate-modules:doc-missing-type plugins/modules/vyos_config.py validate-modules:doc-required-mismatch plugins/modules/vyos_config.py validate-modules:parameter-list-no-elements plugins/modules/vyos_config.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_facts.py future-import-boilerplate plugins/modules/vyos_facts.py metaclass-boilerplate plugins/modules/vyos_facts.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_facts.py validate-modules:doc-required-mismatch plugins/modules/vyos_facts.py validate-modules:parameter-list-no-elements plugins/modules/vyos_facts.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_interfaces.py validate-modules:doc-elements-mismatch plugins/modules/vyos_lag_interfaces.py validate-modules:doc-elements-mismatch plugins/modules/vyos_lag_interfaces.py validate-modules:parameter-list-no-elements plugins/modules/vyos_lldp_global.py validate-modules:parameter-list-no-elements plugins/modules/vyos_lldp_interfaces.py validate-modules:doc-elements-mismatch plugins/modules/vyos_lldp_interfaces.py validate-modules:doc-required-mismatch plugins/modules/vyos_logging.py future-import-boilerplate plugins/modules/vyos_logging.py metaclass-boilerplate plugins/modules/vyos_logging.py validate-modules:doc-choices-do-not-match-spec plugins/modules/vyos_logging.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_logging.py validate-modules:doc-elements-mismatch plugins/modules/vyos_logging.py validate-modules:doc-missing-type plugins/modules/vyos_logging.py validate-modules:doc-required-mismatch plugins/modules/vyos_logging.py validate-modules:missing-suboption-docs plugins/modules/vyos_logging.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_logging.py validate-modules:undocumented-parameter plugins/modules/vyos_ping.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_ping.py validate-modules:doc-required-mismatch plugins/modules/vyos_ping.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_static_route.py future-import-boilerplate -plugins/modules/vyos_static_route.py metaclass-boilerplate -plugins/modules/vyos_static_route.py validate-modules:doc-choices-do-not-match-spec -plugins/modules/vyos_static_route.py validate-modules:doc-default-does-not-match-spec -plugins/modules/vyos_static_route.py validate-modules:doc-elements-mismatch -plugins/modules/vyos_static_route.py validate-modules:doc-missing-type -plugins/modules/vyos_static_route.py validate-modules:doc-required-mismatch -plugins/modules/vyos_static_route.py validate-modules:missing-suboption-docs -plugins/modules/vyos_static_route.py validate-modules:parameter-type-not-in-doc -plugins/modules/vyos_static_route.py validate-modules:undocumented-parameter plugins/modules/vyos_system.py future-import-boilerplate plugins/modules/vyos_system.py metaclass-boilerplate plugins/modules/vyos_system.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_system.py validate-modules:doc-required-mismatch plugins/modules/vyos_system.py validate-modules:parameter-list-no-elements plugins/modules/vyos_system.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_user.py future-import-boilerplate plugins/modules/vyos_user.py metaclass-boilerplate plugins/modules/vyos_user.py validate-modules:doc-choices-do-not-match-spec plugins/modules/vyos_user.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_user.py validate-modules:doc-elements-mismatch plugins/modules/vyos_user.py validate-modules:doc-missing-type plugins/modules/vyos_user.py validate-modules:doc-required-mismatch plugins/modules/vyos_user.py validate-modules:missing-suboption-docs plugins/modules/vyos_user.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_user.py validate-modules:undocumented-parameter plugins/modules/vyos_vlan.py validate-modules:doc-choices-do-not-match-spec plugins/modules/vyos_vlan.py validate-modules:doc-default-does-not-match-spec plugins/modules/vyos_vlan.py validate-modules:doc-elements-mismatch plugins/modules/vyos_vlan.py validate-modules:doc-missing-type plugins/modules/vyos_vlan.py validate-modules:doc-required-mismatch plugins/modules/vyos_vlan.py validate-modules:missing-suboption-docs plugins/modules/vyos_vlan.py validate-modules:parameter-list-no-elements plugins/modules/vyos_vlan.py validate-modules:parameter-type-not-in-doc plugins/modules/vyos_vlan.py validate-modules:undocumented-parameter plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local` plugins/doc_fragments/vyos.py future-import-boilerplate plugins/doc_fragments/vyos.py metaclass-boilerplate tests/unit/mock/path.py future-import-boilerplate tests/unit/mock/path.py metaclass-boilerplate tests/unit/mock/yaml_helper.py future-import-boilerplate tests/unit/mock/yaml_helper.py metaclass-boilerplate tests/unit/modules/conftest.py future-import-boilerplate tests/unit/modules/conftest.py metaclass-boilerplate tests/unit/modules/utils.py future-import-boilerplate tests/unit/modules/utils.py metaclass-boilerplate \ No newline at end of file diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg new file mode 100644 index 0000000..f65b386 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg @@ -0,0 +1,13 @@ +set firewall name V4-INGRESS default-action 'accept' +set firewall ipv6-name V6-INGRESS default-action 'accept' +set firewall name V4-INGRESS description 'This is IPv4 V4-INGRESS rule set' +set firewall name V4-INGRESS enable-default-log +set firewall name V4-INGRESS rule 101 protocol 'icmp' +set firewall name V4-INGRESS rule 101 description 'Rule 101 is configured by Ansible' +set firewall name V4-INGRESS rule 101 fragment 'match-frag' +set firewall name V4-INGRESS rule 101 +set firewall name V4-INGRESS rule 101 disabled +set firewall name V4-INGRESS rule 101 action 'accept' +set firewall name V4-INGRESS rule 101 ipsec 'match-ipsec' +set firewall name V4-EGRESS default-action 'reject' +set firewall ipv6-name V6-EGRESS default-action 'reject' diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_static_routes_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_static_routes_config.cfg new file mode 100644 index 0000000..0411dc9 --- /dev/null +++ b/tests/unit/modules/network/vyos/fixtures/vyos_static_routes_config.cfg @@ -0,0 +1,2 @@ +'set protocols static route 192.0.2.32/28 next-hop 192.0.2.9' +'set protocols static route 192.0.2.32/28 next-hop 192.0.2.10' diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py b/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py new file mode 100644 index 0000000..86fcc65 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py @@ -0,0 +1,1039 @@ +# (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 . + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.vyos.vyos.tests.unit.compat.mock import patch +from ansible_collections.vyos.vyos.plugins.modules import vyos_firewall_rules +from ansible_collections.vyos.vyos.tests.unit.modules.utils import ( + set_module_args, +) +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosFirewallRulesModule(TestVyosModule): + + module = vyos_firewall_rules + + def setUp(self): + super(TestVyosFirewallRulesModule, self).setUp() + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config" + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config" + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection" + ) + self.get_resource_connection_config = ( + self.mock_get_resource_connection_config.start() + ) + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection" + ) + self.get_resource_connection_facts = ( + self.mock_get_resource_connection_facts.start() + ) + self.mock_execute_show_command = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes.Static_routesFacts.get_device_data" + ) + + self.mock_execute_show_command = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules.Firewall_rulesFacts.get_device_data" + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestVyosFirewallRulesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + return load_fixture("vyos_firewall_rules_config.cfg") + + self.execute_show_command.side_effect = load_from_file + + def test_vyos_firewall_rule_set_01_merged(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + name="V6-INBOUND", + description="This is IPv6 INBOUND rule set", + default_action="reject", + enable_default_log=True, + rules=[], + ), + dict( + name="V6-OUTBOUND", + description="This is IPv6 OUTBOUND rule set", + default_action="accept", + enable_default_log=False, + rules=[], + ), + ], + ), + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-INBOUND", + description="This is IPv4 INBOUND rule set", + default_action="reject", + enable_default_log=True, + rules=[], + ), + dict( + name="V4-OUTBOUND", + description="This is IPv4 OUTBOUND rule set", + default_action="accept", + enable_default_log=False, + rules=[], + ), + ], + ), + ], + state="merged", + ) + ) + commands = [ + "set firewall ipv6-name V6-INBOUND default-action 'reject'", + "set firewall ipv6-name V6-INBOUND description 'This is IPv6 INBOUND rule set'", + "set firewall ipv6-name V6-INBOUND enable-default-log", + "set firewall ipv6-name V6-OUTBOUND default-action 'accept'", + "set firewall ipv6-name V6-OUTBOUND description 'This is IPv6 OUTBOUND rule set'", + "set firewall name V4-INBOUND default-action 'reject'", + "set firewall name V4-INBOUND description 'This is IPv4 INBOUND rule set'", + "set firewall name V4-INBOUND enable-default-log", + "set firewall name V4-OUTBOUND default-action 'accept'", + "set firewall name V4-OUTBOUND description 'This is IPv4 OUTBOUND rule set'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_rule_set_02_merged(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + name="V6-INBOUND", + description="This is IPv6 INBOUND rule set", + default_action="reject", + enable_default_log=True, + rules=[], + ), + dict( + name="V6-OUTBOUND", + description="This is IPv6 OUTBOUND rule set", + default_action="accept", + enable_default_log=False, + rules=[], + ), + ], + ), + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-INBOUND", + description="This is IPv4 INBOUND rule set", + default_action="reject", + enable_default_log=True, + rules=[], + ), + dict( + name="V4-OUTBOUND", + description="This is IPv4 OUTBOUND rule set", + default_action="accept", + enable_default_log=False, + rules=[], + ), + ], + ), + ], + state="merged", + ) + ) + commands = [ + "set firewall ipv6-name V6-INBOUND default-action 'reject'", + "set firewall ipv6-name V6-INBOUND description 'This is IPv6 INBOUND rule set'", + "set firewall ipv6-name V6-INBOUND enable-default-log", + "set firewall ipv6-name V6-OUTBOUND default-action 'accept'", + "set firewall ipv6-name V6-OUTBOUND description 'This is IPv6 OUTBOUND rule set'", + "set firewall name V4-INBOUND default-action 'reject'", + "set firewall name V4-INBOUND description 'This is IPv4 INBOUND rule set'", + "set firewall name V4-INBOUND enable-default-log", + "set firewall name V4-OUTBOUND default-action 'accept'", + "set firewall name V4-OUTBOUND description 'This is IPv4 OUTBOUND rule set'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="INBOUND", + description="This is IPv4 INBOUND rule set", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="101", + action="accept", + description="Rule 101 is configured by Ansible", + ipsec="match-ipsec", + protocol="icmp", + fragment="match-frag", + disabled=True, + ) + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall name INBOUND default-action 'accept'", + "set firewall name INBOUND description 'This is IPv4 INBOUND rule set'", + "set firewall name INBOUND enable-default-log", + "set firewall name INBOUND rule 101 protocol 'icmp'", + "set firewall name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", + "set firewall name INBOUND rule 101 fragment 'match-frag'", + "set firewall name INBOUND rule 101", + "set firewall name INBOUND rule 101 disabled", + "set firewall name INBOUND rule 101 action 'accept'", + "set firewall name INBOUND rule 101 ipsec 'match-ipsec'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_02(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="INBOUND", + rules=[ + dict( + number="101", + protocol="tcp", + source=dict( + address="192.0.2.0", + mac_address="38:00:25:19:76:0c", + port=2127, + ), + destination=dict( + address="192.0.1.0", port=2124 + ), + limit=dict( + burst=10, + rate=dict( + number=20, unit="second" + ), + ), + recent=dict(count=10, time=20), + state=dict( + established=True, + related=True, + invalid=True, + new=True, + ), + ) + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall name INBOUND rule 101 protocol 'tcp'", + "set firewall name INBOUND rule 101 destination address 192.0.1.0", + "set firewall name INBOUND rule 101 destination port 2124", + "set firewall name INBOUND rule 101", + "set firewall name INBOUND rule 101 source address 192.0.2.0", + "set firewall name INBOUND rule 101 source mac-address 38:00:25:19:76:0c", + "set firewall name INBOUND rule 101 source port 2127", + "set firewall name INBOUND rule 101 state new enable", + "set firewall name INBOUND rule 101 state invalid enable", + "set firewall name INBOUND rule 101 state related enable", + "set firewall name INBOUND rule 101 state established enable", + "set firewall name INBOUND rule 101 limit burst 10", + "set firewall name INBOUND rule 101 limit rate 20/second", + "set firewall name INBOUND rule 101 recent count 10", + "set firewall name INBOUND rule 101 recent time 20", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_03(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="INBOUND", + rules=[ + dict( + number="101", + destination=dict( + group=dict( + address_group="OUT-ADDR-GROUP", + network_group="OUT-NET-GROUP", + port_group="OUT-PORT-GROUP", + ) + ), + source=dict( + group=dict( + address_group="IN-ADDR-GROUP", + network_group="IN-NET-GROUP", + port_group="IN-PORT-GROUP", + ) + ), + ) + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall name INBOUND rule 101 source group address-group IN-ADDR-GROUP", + "set firewall name INBOUND rule 101 source group network-group IN-NET-GROUP", + "set firewall name INBOUND rule 101 source group port-group IN-PORT-GROUP", + "set firewall name INBOUND rule 101 destination group address-group OUT-ADDR-GROUP", + "set firewall name INBOUND rule 101 destination group network-group OUT-NET-GROUP", + "set firewall name INBOUND rule 101 destination group port-group OUT-PORT-GROUP", + "set firewall name INBOUND rule 101", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_04(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="INBOUND", + rules=[ + dict( + number="101", + time=dict( + monthdays="2", + startdate="2020-01-24", + starttime="13:20:00", + stopdate="2020-01-28", + stoptime="13:30:00", + weekdays="!Sat,Sun", + utc=True, + ), + tcp=dict(flags="ALL"), + ) + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall name INBOUND rule 101", + "set firewall name INBOUND rule 101 tcp flags ALL", + "set firewall name INBOUND rule 101 time utc", + "set firewall name INBOUND rule 101 time monthdays 2", + "set firewall name INBOUND rule 101 time startdate 2020-01-24", + "set firewall name INBOUND rule 101 time stopdate 2020-01-28", + "set firewall name INBOUND rule 101 time weekdays !Sat,Sun", + "set firewall name INBOUND rule 101 time stoptime 13:30:00", + "set firewall name INBOUND rule 101 time starttime 13:20:00", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v6_rule_sets_rule_merged_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + name="INBOUND", + description="This is IPv6 INBOUND rule set", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="101", + action="accept", + description="Rule 101 is configured by Ansible", + ipsec="match-ipsec", + protocol="icmp", + disabled=True, + ) + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall ipv6-name INBOUND default-action 'accept'", + "set firewall ipv6-name INBOUND description 'This is IPv6 INBOUND rule set'", + "set firewall ipv6-name INBOUND enable-default-log", + "set firewall ipv6-name INBOUND rule 101 protocol 'icmp'", + "set firewall ipv6-name INBOUND rule 101 description 'Rule 101 is configured by Ansible'", + "set firewall ipv6-name INBOUND rule 101", + "set firewall ipv6-name INBOUND rule 101 disabled", + "set firewall ipv6-name INBOUND rule 101 action 'accept'", + "set firewall ipv6-name INBOUND rule 101 ipsec 'match-ipsec'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v6_rule_sets_rule_merged_02(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + name="INBOUND", + rules=[ + dict( + number="101", + protocol="tcp", + source=dict( + address="2001:db8::12", + mac_address="38:00:25:19:76:0c", + port=2127, + ), + destination=dict( + address="2001:db8::11", port=2124 + ), + limit=dict( + burst=10, + rate=dict( + number=20, unit="second" + ), + ), + recent=dict(count=10, time=20), + state=dict( + established=True, + related=True, + invalid=True, + new=True, + ), + ) + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall ipv6-name INBOUND rule 101 protocol 'tcp'", + "set firewall ipv6-name INBOUND rule 101 destination address 2001:db8::11", + "set firewall ipv6-name INBOUND rule 101 destination port 2124", + "set firewall ipv6-name INBOUND rule 101", + "set firewall ipv6-name INBOUND rule 101 source address 2001:db8::12", + "set firewall ipv6-name INBOUND rule 101 source mac-address 38:00:25:19:76:0c", + "set firewall ipv6-name INBOUND rule 101 source port 2127", + "set firewall ipv6-name INBOUND rule 101 state new enable", + "set firewall ipv6-name INBOUND rule 101 state invalid enable", + "set firewall ipv6-name INBOUND rule 101 state related enable", + "set firewall ipv6-name INBOUND rule 101 state established enable", + "set firewall ipv6-name INBOUND rule 101 limit burst 10", + "set firewall ipv6-name INBOUND rule 101 recent count 10", + "set firewall ipv6-name INBOUND rule 101 recent time 20", + "set firewall ipv6-name INBOUND rule 101 limit rate 20/second", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v6_rule_sets_rule_merged_03(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + name="INBOUND", + rules=[ + dict( + number="101", + destination=dict( + group=dict( + address_group="OUT-ADDR-GROUP", + network_group="OUT-NET-GROUP", + port_group="OUT-PORT-GROUP", + ) + ), + source=dict( + group=dict( + address_group="IN-ADDR-GROUP", + network_group="IN-NET-GROUP", + port_group="IN-PORT-GROUP", + ) + ), + ) + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall ipv6-name INBOUND rule 101 source group address-group IN-ADDR-GROUP", + "set firewall ipv6-name INBOUND rule 101 source group network-group IN-NET-GROUP", + "set firewall ipv6-name INBOUND rule 101 source group port-group IN-PORT-GROUP", + "set firewall ipv6-name INBOUND rule 101 destination group address-group OUT-ADDR-GROUP", + "set firewall ipv6-name INBOUND rule 101 destination group network-group OUT-NET-GROUP", + "set firewall ipv6-name INBOUND rule 101 destination group port-group OUT-PORT-GROUP", + "set firewall ipv6-name INBOUND rule 101", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v6_rule_sets_rule_merged_04(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + name="INBOUND", + rules=[ + dict( + number="101", + time=dict( + monthdays="2", + startdate="2020-01-24", + starttime="13:20:00", + stopdate="2020-01-28", + stoptime="13:30:00", + weekdays="!Sat,Sun", + utc=True, + ), + tcp=dict(flags="ALL"), + ) + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall ipv6-name INBOUND rule 101", + "set firewall ipv6-name INBOUND rule 101 tcp flags ALL", + "set firewall ipv6-name INBOUND rule 101 time utc", + "set firewall ipv6-name INBOUND rule 101 time monthdays 2", + "set firewall ipv6-name INBOUND rule 101 time startdate 2020-01-24", + "set firewall ipv6-name INBOUND rule 101 time stopdate 2020-01-28", + "set firewall ipv6-name INBOUND rule 101 time weekdays !Sat,Sun", + "set firewall ipv6-name INBOUND rule 101 time stoptime 13:30:00", + "set firewall ipv6-name INBOUND rule 101 time starttime 13:20:00", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v6_rule_sets_rule_merged_icmp_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv6", + rule_sets=[ + dict( + name="INBOUND", + rules=[ + dict( + number="101", + protocol="icmp", + icmp=dict( + type_name="port-unreachable" + ), + ) + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall ipv6-name INBOUND rule 101 icmpv6 type port-unreachable", + "set firewall ipv6-name INBOUND rule 101 protocol 'icmp'", + "set firewall ipv6-name INBOUND rule 101", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="INBOUND", + rules=[ + dict( + number="101", + protocol="icmp", + icmp=dict(type=1, code=1), + ) + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall name INBOUND rule 101 icmp type 1", + "set firewall name INBOUND rule 101 icmp code 1", + "set firewall name INBOUND rule 101 protocol 'icmp'", + "set firewall name INBOUND rule 101", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_rule_merged_icmp_02(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="INBOUND", + rules=[ + dict( + number="101", + protocol="icmp", + icmp=dict(type_name="echo-request"), + ) + ], + ), + ], + ) + ], + state="merged", + ) + ) + commands = [ + "set firewall name INBOUND rule 101 icmp type-name echo-request", + "set firewall name INBOUND rule 101 protocol 'icmp'", + "set firewall name INBOUND rule 101", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4_rule_sets_del_01(self): + set_module_args( + dict( + config=[ + dict(afi="ipv4", rule_sets=[dict(name="V4-INGRESS"),]) + ], + state="deleted", + ) + ) + commands = ["delete firewall name V4-INGRESS"] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_del_02(self): + set_module_args( + dict( + config=[ + dict(afi="ipv4", rule_sets=[dict(name="V4-INGRESS"),]), + dict(afi="ipv6", rule_sets=[dict(name="V6-INGRESS"),]), + ], + state="deleted", + ) + ) + commands = [ + "delete firewall name V4-INGRESS", + "delete firewall ipv6-name V6-INGRESS", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_del_03(self): + set_module_args(dict(config=[], state="deleted")) + commands = ["delete firewall name", "delete firewall ipv6-name"] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_del_04(self): + set_module_args( + dict( + config=[ + dict(afi="ipv4", rule_sets=[dict(name="V4-ING"),]), + dict(afi="ipv6", rule_sets=[dict(name="V6-ING"),]), + ], + state="deleted", + ) + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_firewall_v4v6_rule_sets_rule_rep_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-INGRESS", + description="This is IPv4 INGRESS rule set", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="101", + action="reject", + description="Rule 101 is configured by Ansible RM", + ipsec="match-ipsec", + protocol="tcp", + fragment="match-frag", + disabled=False, + ), + dict( + number="102", + action="accept", + description="Rule 102 is configured by Ansible RM", + protocol="icmp", + disabled=True, + ), + ], + ), + ], + ), + dict( + afi="ipv6", + rule_sets=[ + dict( + name="V6-INGRESS", + default_action="accept", + description="This rule-set is configured by Ansible RM", + ), + dict( + name="V6-EGRESS", + default_action="reject", + description="This rule-set is configured by Ansible RM", + ), + ], + ), + ], + state="replaced", + ) + ) + commands = [ + "delete firewall name V4-INGRESS rule 101 disabled", + "delete firewall name V4-EGRESS default-action", + "set firewall name V4-INGRESS description 'This is IPv4 INGRESS rule set'", + "set firewall name V4-INGRESS rule 101 protocol 'tcp'", + "set firewall name V4-INGRESS rule 101 description 'Rule 101 is configured by Ansible RM'", + "set firewall name V4-INGRESS rule 101 action 'reject'", + "set firewall name V4-INGRESS rule 102 disabled", + "set firewall name V4-INGRESS rule 102 action 'accept'", + "set firewall name V4-INGRESS rule 102 protocol 'icmp'", + "set firewall name V4-INGRESS rule 102 description 'Rule 102 is configured by Ansible RM'", + "set firewall name V4-INGRESS rule 102", + "set firewall ipv6-name V6-INGRESS description 'This rule-set is configured by Ansible RM'", + "set firewall ipv6-name V6-EGRESS description 'This rule-set is configured by Ansible RM'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_rule_rep_02(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-INGRESS", + description="This is IPv4 V4-INGRESS rule set", + default_action="accept", + enable_default_log=False, + rules=[ + dict( + number="101", + action="accept", + description="Rule 101 is configured by Ansible", + ipsec="match-ipsec", + protocol="icmp", + fragment="match-frag", + disabled=True, + ), + ], + ), + ], + ), + dict( + afi="ipv6", + rule_sets=[ + dict(name="V6-INGRESS", default_action="accept",), + dict(name="V6-EGRESS", default_action="reject",), + ], + ), + ], + state="replaced", + ) + ) + commands = [ + "delete firewall name V4-INGRESS enable-default-log", + "delete firewall name V4-EGRESS default-action", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_rule_rep_idem_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-INGRESS", + description="This is IPv4 V4-INGRESS rule set", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="101", + action="accept", + description="Rule 101 is configured by Ansible", + ipsec="match-ipsec", + protocol="icmp", + fragment="match-frag", + disabled=True, + ) + ], + ), + dict(name="V4-EGRESS", default_action="reject",), + ], + ), + dict( + afi="ipv6", + rule_sets=[ + dict(name="V6-INGRESS", default_action="accept",), + dict(name="V6-EGRESS", default_action="reject",), + ], + ), + ], + state="replaced", + ) + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_firewall_v4v6_rule_sets_rule_mer_idem_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-INGRESS", + description="This is IPv4 V4-INGRESS rule set", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="101", + action="accept", + description="Rule 101 is configured by Ansible", + ipsec="match-ipsec", + protocol="icmp", + fragment="match-frag", + disabled=True, + ) + ], + ), + dict(name="V4-EGRESS", default_action="reject",), + ], + ), + dict( + afi="ipv6", + rule_sets=[ + dict(name="V6-INGRESS", default_action="accept",), + dict(name="V6-EGRESS", default_action="reject",), + ], + ), + ], + state="merged", + ) + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_firewall_v4v6_rule_sets_rule_ovr_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-IN", + description="This is IPv4 INGRESS rule set", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="1", + action="reject", + description="Rule 1 is configured by Ansible RM", + ipsec="match-ipsec", + protocol="tcp", + fragment="match-frag", + disabled=False, + ), + dict( + number="2", + action="accept", + description="Rule 102 is configured by Ansible RM", + protocol="icmp", + disabled=True, + ), + ], + ), + ], + ), + dict( + afi="ipv6", + rule_sets=[ + dict( + name="V6-IN", + default_action="accept", + description="This rule-set is configured by Ansible RM", + ), + dict( + name="V6-EG", + default_action="reject", + description="This rule-set is configured by Ansible RM", + ), + ], + ), + ], + state="overridden", + ) + ) + commands = [ + "delete firewall ipv6-name V6-INGRESS", + "delete firewall ipv6-name V6-EGRESS", + "delete firewall name V4-INGRESS", + "delete firewall name V4-EGRESS", + "set firewall name V4-IN default-action 'accept'", + "set firewall name V4-IN description 'This is IPv4 INGRESS rule set'", + "set firewall name V4-IN enable-default-log", + "set firewall name V4-IN rule 1 protocol 'tcp'", + "set firewall name V4-IN rule 1 description 'Rule 1 is configured by Ansible RM'", + "set firewall name V4-IN rule 1 fragment 'match-frag'", + "set firewall name V4-IN rule 1", + "set firewall name V4-IN rule 1 action 'reject'", + "set firewall name V4-IN rule 1 ipsec 'match-ipsec'", + "set firewall name V4-IN rule 2 disabled", + "set firewall name V4-IN rule 2 action 'accept'", + "set firewall name V4-IN rule 2 protocol 'icmp'", + "set firewall name V4-IN rule 2 description 'Rule 102 is configured by Ansible RM'", + "set firewall name V4-IN rule 2", + "set firewall ipv6-name V6-IN default-action 'accept'", + "set firewall ipv6-name V6-IN description 'This rule-set is configured by Ansible RM'", + "set firewall ipv6-name V6-EG default-action 'reject'", + "set firewall ipv6-name V6-EG description 'This rule-set is configured by Ansible RM'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_firewall_v4v6_rule_sets_rule_ovr_idem_01(self): + set_module_args( + dict( + config=[ + dict( + afi="ipv4", + rule_sets=[ + dict( + name="V4-INGRESS", + description="This is IPv4 V4-INGRESS rule set", + default_action="accept", + enable_default_log=True, + rules=[ + dict( + number="101", + action="accept", + description="Rule 101 is configured by Ansible", + ipsec="match-ipsec", + protocol="icmp", + fragment="match-frag", + disabled=True, + ) + ], + ), + dict(name="V4-EGRESS", default_action="reject",), + ], + ), + dict( + afi="ipv6", + rule_sets=[ + dict(name="V6-INGRESS", default_action="accept",), + dict(name="V6-EGRESS", default_action="reject",), + ], + ), + ], + state="overridden", + ) + ) + self.execute_module(changed=False, commands=[]) diff --git a/tests/unit/modules/network/vyos/test_vyos_static_route.py b/tests/unit/modules/network/vyos/test_vyos_static_route.py index e020ca5..762508c 100644 --- a/tests/unit/modules/network/vyos/test_vyos_static_route.py +++ b/tests/unit/modules/network/vyos/test_vyos_static_route.py @@ -1,71 +1,71 @@ # (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 . # Make coding more python3-ish from __future__ import absolute_import, division, print_function __metaclass__ = type from ansible_collections.vyos.vyos.tests.unit.compat.mock import patch -from ansible_collections.vyos.vyos.plugins.modules import vyos_static_route +from ansible.modules.network.vyos import _vyos_static_route from ansible_collections.vyos.vyos.tests.unit.modules.utils import ( set_module_args, ) from .vyos_module import TestVyosModule class TestVyosStaticRouteModule(TestVyosModule): - module = vyos_static_route + module = _vyos_static_route def setUp(self): super(TestVyosStaticRouteModule, self).setUp() self.mock_get_config = patch( - "ansible_collections.vyos.vyos.plugins.modules.vyos_static_route.get_config" + "ansible.modules.network.vyos._vyos_static_route.get_config" ) self.get_config = self.mock_get_config.start() self.mock_load_config = patch( - "ansible_collections.vyos.vyos.plugins.modules.vyos_static_route.load_config" + "ansible.modules.network.vyos._vyos_static_route.load_config" ) self.load_config = self.mock_load_config.start() def tearDown(self): super(TestVyosStaticRouteModule, self).tearDown() self.mock_get_config.stop() self.mock_load_config.stop() def load_fixtures(self, commands=None, transport="cli"): self.load_config.return_value = dict(diff=None, session="session") def test_vyos_static_route_present(self): set_module_args( dict( prefix="172.26.0.0/16", next_hop="172.26.4.1", admin_distance="1", ) ) result = self.execute_module(changed=True) self.assertEqual( result["commands"], [ "set protocols static route 172.26.0.0/16 next-hop 172.26.4.1 distance 1" ], ) diff --git a/tests/unit/modules/network/vyos/test_vyos_static_routes.py b/tests/unit/modules/network/vyos/test_vyos_static_routes.py new file mode 100644 index 0000000..3646d61 --- /dev/null +++ b/tests/unit/modules/network/vyos/test_vyos_static_routes.py @@ -0,0 +1,293 @@ +# (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 . + +# Make coding more python3-ish +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +from ansible_collections.vyos.vyos.tests.unit.compat.mock import patch +from ansible_collections.vyos.vyos.plugins.modules import vyos_static_routes +from ansible_collections.vyos.vyos.tests.unit.modules.utils import ( + set_module_args, +) +from .vyos_module import TestVyosModule, load_fixture + + +class TestVyosStaticRoutesModule(TestVyosModule): + + module = vyos_static_routes + + def setUp(self): + super(TestVyosStaticRoutesModule, self).setUp() + self.mock_get_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.get_config" + ) + self.get_config = self.mock_get_config.start() + + self.mock_load_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network.Config.load_config" + ) + self.load_config = self.mock_load_config.start() + + self.mock_get_resource_connection_config = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base.get_resource_connection" + ) + self.get_resource_connection_config = ( + self.mock_get_resource_connection_config.start() + ) + + self.mock_get_resource_connection_facts = patch( + "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts.get_resource_connection" + ) + self.get_resource_connection_facts = ( + self.mock_get_resource_connection_facts.start() + ) + + self.mock_execute_show_command = patch( + "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes.Static_routesFacts.get_device_data" + ) + self.execute_show_command = self.mock_execute_show_command.start() + + def tearDown(self): + super(TestVyosStaticRoutesModule, self).tearDown() + self.mock_get_resource_connection_config.stop() + self.mock_get_resource_connection_facts.stop() + self.mock_get_config.stop() + self.mock_load_config.stop() + self.mock_execute_show_command.stop() + + def load_fixtures(self, commands=None): + def load_from_file(*args, **kwargs): + return load_fixture("vyos_static_routes_config.cfg") + + self.execute_show_command.side_effect = load_from_file + + def test_vyos_static_routes_merged(self): + set_module_args( + dict( + config=[ + dict( + address_families=[ + dict( + afi="ipv4", + routes=[ + dict( + dest="192.0.2.48/28", + next_hops=[ + dict( + forward_router_address="192.0.2.9" + ), + dict( + forward_router_address="192.0.2.10" + ), + ], + ) + ], + ) + ] + ) + ], + state="merged", + ) + ) + commands = [ + "set protocols static route 192.0.2.48/28", + "set protocols static route 192.0.2.48/28 next-hop '192.0.2.9'", + "set protocols static route 192.0.2.48/28 next-hop '192.0.2.10'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_static_routes_merged_idempotent(self): + set_module_args( + dict( + config=[ + dict( + address_families=[ + dict( + afi="ipv4", + routes=[ + dict( + dest="192.0.2.32/28", + next_hops=[ + dict( + forward_router_address="192.0.2.9" + ), + dict( + forward_router_address="192.0.2.10" + ), + ], + ) + ], + ) + ] + ) + ], + state="merged", + ) + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_static_routes_replaced(self): + set_module_args( + dict( + config=[ + dict( + address_families=[ + dict( + afi="ipv4", + routes=[ + dict( + dest="192.0.2.48/28", + next_hops=[ + dict( + forward_router_address="192.0.2.9" + ), + dict( + forward_router_address="192.0.2.10" + ), + ], + ) + ], + ) + ] + ) + ], + state="replaced", + ) + ) + commands = [ + "set protocols static route 192.0.2.48/28", + "set protocols static route 192.0.2.48/28 next-hop '192.0.2.9'", + "set protocols static route 192.0.2.48/28 next-hop '192.0.2.10'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_static_routes_replaced_idempotent(self): + set_module_args( + dict( + config=[ + dict( + address_families=[ + dict( + afi="ipv4", + routes=[ + dict( + dest="192.0.2.32/28", + next_hops=[ + dict( + forward_router_address="192.0.2.9" + ), + dict( + forward_router_address="192.0.2.10" + ), + ], + ) + ], + ) + ] + ) + ], + state="replaced", + ) + ) + + self.execute_module(changed=False, commands=[]) + + def test_vyos_static_routes_overridden(self): + set_module_args( + dict( + config=[ + dict( + address_families=[ + dict( + afi="ipv4", + routes=[ + dict( + dest="192.0.2.48/28", + next_hops=[ + dict( + forward_router_address="192.0.2.9" + ), + dict( + forward_router_address="192.0.2.10" + ), + ], + ) + ], + ) + ] + ) + ], + state="overridden", + ) + ) + commands = [ + "delete protocols static route 192.0.2.32/28", + "set protocols static route 192.0.2.48/28", + "set protocols static route 192.0.2.48/28 next-hop '192.0.2.9'", + "set protocols static route 192.0.2.48/28 next-hop '192.0.2.10'", + ] + self.execute_module(changed=True, commands=commands) + + def test_vyos_static_routes_overridden_idempotent(self): + set_module_args( + dict( + config=[ + dict( + address_families=[ + dict( + afi="ipv4", + routes=[ + dict( + dest="192.0.2.32/28", + next_hops=[ + dict( + forward_router_address="192.0.2.9" + ), + dict( + forward_router_address="192.0.2.10" + ), + ], + ) + ], + ) + ] + ) + ], + state="overridden", + ) + ) + self.execute_module(changed=False, commands=[]) + + def test_vyos_static_routes_deleted(self): + set_module_args( + dict( + config=[ + dict( + address_families=[ + dict( + afi="ipv4", routes=[dict(dest="192.0.2.32/28")] + ) + ] + ) + ], + state="deleted", + ) + ) + commands = ["delete protocols static route 192.0.2.32/28"] + self.execute_module(changed=True, commands=commands)