summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--changelogs/fragments/cliconf.yml4
-rw-r--r--changelogs/fragments/firewall_global_14.yml7
-rw-r--r--changelogs/fragments/firewall_rules.yml8
-rw-r--r--changelogs/fragments/interfaces_update.yml9
-rw-r--r--changelogs/fragments/tests.yml3
-rw-r--r--docs/vyos.vyos.vyos_firewall_global_module.rst2
-rw-r--r--docs/vyos.vyos.vyos_firewall_rules_module.rst6
-rw-r--r--plugins/cliconf/vyos.py5
-rw-r--r--plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py3
-rw-r--r--plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py107
-rw-r--r--plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py80
-rw-r--r--plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py443
-rw-r--r--plugins/module_utils/network/vyos/config/interfaces/interfaces.py2
-rw-r--r--plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py28
-rw-r--r--plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py18
-rw-r--r--plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py180
-rw-r--r--plugins/module_utils/network/vyos/facts/interfaces/interfaces.py20
-rw-r--r--plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py2
-rw-r--r--plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py51
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py650
-rw-r--r--plugins/module_utils/network/vyos/utils/version.py13
-rw-r--r--plugins/module_utils/network/vyos/vyos.py8
-rw-r--r--plugins/modules/vyos_firewall_global.py13
-rw-r--r--plugins/modules/vyos_firewall_rules.py180
-rw-r--r--plugins/modules/vyos_ospf_interfaces.py2
-rw-r--r--requirements.txt1
-rw-r--r--tests/sanity/ignore-2.19.txt1
-rw-r--r--tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg16
-rw-r--r--tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg1
-rw-r--r--tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config_v14.cfg26
-rw-r--r--tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config.cfg1
-rw-r--r--tests/unit/modules/network/vyos/fixtures/vyos_ospf_interfaces_config_14.cfg4
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_facts.py1
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_firewall_global.py128
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_firewall_global14.py460
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_firewall_rules.py580
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_interfaces.py35
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_logging_global.py1
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py14
-rw-r--r--tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py511
40 files changed, 3379 insertions, 245 deletions
diff --git a/changelogs/fragments/cliconf.yml b/changelogs/fragments/cliconf.yml
new file mode 100644
index 0000000..53c26ad
--- /dev/null
+++ b/changelogs/fragments/cliconf.yml
@@ -0,0 +1,4 @@
+---
+
+minor_changes:
+ - added `network_os_major_version` to facts
diff --git a/changelogs/fragments/firewall_global_14.yml b/changelogs/fragments/firewall_global_14.yml
new file mode 100644
index 0000000..269c3e5
--- /dev/null
+++ b/changelogs/fragments/firewall_global_14.yml
@@ -0,0 +1,7 @@
+---
+minor_changes:
+ - with 1.4+, use the the global keyword to define global firewall rules
+ - Fixed ipv6 route-redirects and tests
+ - Added support for input, output, and forward chains (1.4+)
+ - Fixed state-policy deletion (partial and full)
+ - Added support for log-level in state-policy (1.4+)
diff --git a/changelogs/fragments/firewall_rules.yml b/changelogs/fragments/firewall_rules.yml
new file mode 100644
index 0000000..7cd02a2
--- /dev/null
+++ b/changelogs/fragments/firewall_rules.yml
@@ -0,0 +1,8 @@
+---
+breaking_changes:
+ - firewall_rules - tcp.flags is now a list with an inversion flag to support 1.4+ firewall rules, but still supports 1.3-
+
+minor_changes:
+ - firewall_rules - Added support for 1.4+ firewall rules
+ - fix tests for 1.4+ firewall rules (ICMP V6 code and type)
+ - added support for 1.5+ firewall `match-ipsec-in`, `match-ipsec-out`, `match-none-in`, `match-none-out`
diff --git a/changelogs/fragments/interfaces_update.yml b/changelogs/fragments/interfaces_update.yml
new file mode 100644
index 0000000..e9a6d21
--- /dev/null
+++ b/changelogs/fragments/interfaces_update.yml
@@ -0,0 +1,9 @@
+---
+minor_changes:
+ - fixed bug where 'replace' would delete an active disable and not reinstate it
+ - make l3_interfaces pick up loopback interfaces
+ - added tests for verifying both of these
+ - fixed over-zealous handling of disable, which could catch other interface
+ items that are disabled.
+ - enable support for 1.4 firewall
+ - support for 1.4 ospf
diff --git a/changelogs/fragments/tests.yml b/changelogs/fragments/tests.yml
new file mode 100644
index 0000000..78e3d59
--- /dev/null
+++ b/changelogs/fragments/tests.yml
@@ -0,0 +1,3 @@
+---
+trivial:
+ - ignore 2.19 sanity tests for now
diff --git a/docs/vyos.vyos.vyos_firewall_global_module.rst b/docs/vyos.vyos.vyos_firewall_global_module.rst
index 34293b1..a77ce80 100644
--- a/docs/vyos.vyos.vyos_firewall_global_module.rst
+++ b/docs/vyos.vyos.vyos_firewall_global_module.rst
@@ -852,6 +852,7 @@ Examples
- connection_type: established
action: accept
log: true
+ log_level: emer
- connection_type: invalid
action: reject
route_redirects:
@@ -897,6 +898,7 @@ Examples
# "set firewall config-trap 'enable'",
# "set firewall state-policy established action 'accept'",
# "set firewall state-policy established log 'enable'",
+ # "set firewall state-policy established log-level 'emer'",
# "set firewall state-policy invalid action 'reject'",
# "set firewall broadcast-ping 'enable'",
# "set firewall all-ping 'enable'",
diff --git a/docs/vyos.vyos.vyos_firewall_rules_module.rst b/docs/vyos.vyos.vyos_firewall_rules_module.rst
index 246824b..b3d619b 100644
--- a/docs/vyos.vyos.vyos_firewall_rules_module.rst
+++ b/docs/vyos.vyos.vyos_firewall_rules_module.rst
@@ -546,8 +546,14 @@ Parameters
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
+ <br><i>VyOS 1.4 & older:</i><br>
<li>match-ipsec</li>
<li>match-none</li>
+ <br><i>VyOS 1.5+ :</i><br>
+ <li>match-ipsec-in</li>
+ <li>match-ipsec-out</li>
+ <li>match-none-in</li>
+ <li>match-none-out</li>
</ul>
</td>
<td>
diff --git a/plugins/cliconf/vyos.py b/plugins/cliconf/vyos.py
index 7e6b0b1..5beffaa 100644
--- a/plugins/cliconf/vyos.py
+++ b/plugins/cliconf/vyos.py
@@ -80,6 +80,11 @@ class Cliconf(CliconfBase):
if match:
device_info["network_os_version"] = match.group(1)
+ if device_info["network_os_version"]:
+ match = re.search(r"VyOS\s*(\d+\.\d+)", device_info["network_os_version"])
+ if match:
+ device_info["network_os_major_version"] = match.group(1)
+
match = re.search(r"(?:HW|Hardware) model:\s*(\S+)", data)
if match:
device_info["network_os_model"] = match.group(1)
diff --git a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
index 2326bea..f79454e 100644
--- a/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py
@@ -134,6 +134,9 @@ class Firewall_globalArgs(object): # pylint: disable=R0903
"type": "str",
},
"log": {"type": "bool"},
+ "log_level": {
+ "choices": ["emerg", "alert", "crit", "err", "warn", "notice", "info", "debug"]
+ }
},
"type": "list",
},
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
index eb285cf..4d0973e 100644
--- a/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py
+++ b/plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py
@@ -50,11 +50,16 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903
"elements": "dict",
"options": {
"default_action": {
- "choices": ["drop", "reject", "accept"],
+ "choices": ["drop", "reject", "accept", "jump"],
"type": "str",
},
+ "default_jump_target": {"type": "str"},
"description": {"type": "str"},
"enable_default_log": {"type": "bool"},
+ "filter": {
+ "choices": ["input", "output", "forward"],
+ "type": "str"
+ },
"name": {"type": "str"},
"rules": {
"elements": "dict",
@@ -65,6 +70,11 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903
"reject",
"accept",
"inspect",
+ "continue",
+ "return",
+ "jump",
+ "queue",
+ "synproxy",
],
"type": "str",
},
@@ -147,9 +157,23 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903
},
"type": "dict",
},
+ "inbound_interface": {
+ "options": {
+ "group": {
+ "type": "str",
+ },
+ "name": {
+ "type": "str",
+ },
+ },
+ "type": "dict",
+ },
"ipsec": {
- "choices": ["match-ipsec", "match-none"],
- "type": "str",
+ "choices": ["match-ipsec", "match-none", "match-ipsec-in", "match-ipsec-out", "match-none-in", "match-none-out"],
+ "type": "str"
+ },
+ "jump_target": {
+ "type": "str"
},
"limit": {
"options": {
@@ -169,6 +193,17 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903
"choices": ["enable", "disable"],
},
"number": {"required": True, "type": "int"},
+ "outbound_interface": {
+ "options": {
+ "group": {
+ "type": "str",
+ },
+ "name": {
+ "type": "str",
+ },
+ },
+ "type": "dict",
+ },
"p2p": {
"elements": "dict",
"options": {
@@ -185,19 +220,52 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903
"type": "str",
},
},
+ "type": "list"
+ },
+ "packet_length": {
+ "elements": "dict",
+ "options": {
+ "length": {
+ "type": "str",
+ },
+ },
+ "type": "list"
+ },
+ "packet_length_exclude": {
+ "elements": "dict",
+ "options": {
+ "length": {
+ "type": "str",
+ }
+ },
"type": "list",
},
+ "packet_type": {
+ "choices": [
+ "broadcast",
+ "multicast",
+ "host",
+ "other"
+ ],
+ "type": "str"
+ },
"protocol": {"type": "str"},
+ "queue": {"type": "str"},
+ "queue_options": {
+ "choices": ["bypass", "fanout"],
+ "type": "str"
+ },
"recent": {
"options": {
"count": {"type": "int"},
- "time": {"type": "int"},
+ "time": {"type": "str"},
},
"type": "dict",
},
"source": {
"options": {
"address": {"type": "str"},
+ "fqdn": {"type": "str"},
"group": {
"options": {
"address_group": {"type": "str"},
@@ -220,8 +288,37 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903
},
"type": "dict",
},
+ "synproxy": {
+ "options": {
+ "mss": {"type": "int"},
+ "window_scale": {"type": "int"},
+ },
+ "type": "dict",
+ },
"tcp": {
- "options": {"flags": {"type": "str"}},
+ "options": {
+ "flags": {
+ "elements": "dict",
+ "options": {
+ "flag": {
+ "choices": [
+ "ack",
+ "cwr",
+ "ecn",
+ "fin",
+ "psh",
+ "rst",
+ "syn",
+ "urg",
+ "all",
+ ],
+ "type": "str"
+ },
+ "invert": {"type": "bool"}
+ },
+ "type": "list"
+ }
+ },
"type": "dict",
},
"time": {
diff --git a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
index 8694f11..7e978ff 100644
--- a/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py
@@ -31,6 +31,10 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils
list_diff_want_only,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
+
class Firewall_global(ConfigBase):
"""
@@ -255,7 +259,7 @@ class Firewall_global(ConfigBase):
continue
if (
key in l_set
- and not (h and self._in_target(h, key))
+ and not self._in_target(h, key)
and not self._is_del(l_set, h)
):
commands.append(
@@ -455,7 +459,10 @@ class Firewall_global(ConfigBase):
"""
commands = []
have = []
- l_set = ("log", "action", "connection_type")
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ l_set = ("log", "action", "connection_type", "log_level")
+ else:
+ l_set = ("log", "action", "connection_type")
if not opr and self._is_root_del(h, w, attr):
commands.append(self._form_attr_cmd(attr=attr, opr=opr))
else:
@@ -478,25 +485,23 @@ class Firewall_global(ConfigBase):
),
)
elif not opr and key in l_set:
- if not (h and self._in_target(h, key)) and not self._is_del(
- l_set,
- h,
- ):
- if key == "action":
- commands.append(
- self._form_attr_cmd(
- attr=attr + " " + w["connection_type"],
- opr=opr,
- ),
- )
- else:
- commands.append(
- self._form_attr_cmd(
- attr=attr + " " + w["connection_type"],
- val=self._bool_to_str(val),
- opr=opr,
- ),
- )
+ if not h:
+ commands.append(
+ self._form_attr_cmd(
+ attr=attr + " " + w["connection_type"],
+ opr=opr,
+ ),
+ )
+ break # delete the whole thing and move on
+ if (not self._in_target(h, key) or h[key] is None) and (self._in_target(w, key) and w[key]):
+ # delete if not being replaced and value currently exists
+ commands.append(
+ self._form_attr_cmd(
+ attr=attr + " " + w["connection_type"] + " " + key,
+ val=self._bool_to_str(val),
+ opr=opr,
+ ),
+ )
return commands
def _render_route_redirects(self, attr, w, h, opr):
@@ -520,6 +525,14 @@ class Firewall_global(ConfigBase):
if want:
for w in want:
h = self.search_attrib_in_have(have, w, "afi")
+ if 'afi' in w:
+ afi = w['afi']
+ else:
+ if h and 'afi' in h:
+ afi = h['afi']
+ else:
+ afi = None
+ afi = None
for key, val in iteritems(w):
if val and key != "afi":
if opr and key in l_set and not (h and self._is_w_same(w, h, key)):
@@ -528,6 +541,7 @@ class Firewall_global(ConfigBase):
attr=key,
val=self._bool_to_str(val),
opr=opr,
+ type=afi
),
)
elif not opr and key in l_set:
@@ -537,6 +551,7 @@ class Firewall_global(ConfigBase):
attr=key,
val=self._bool_to_str(val),
opr=opr,
+ type=afi
),
)
continue
@@ -546,6 +561,7 @@ class Firewall_global(ConfigBase):
attr=key,
val=self._bool_to_str(val),
opr=opr,
+ type=afi
),
)
elif key == "icmp_redirects":
@@ -565,20 +581,27 @@ class Firewall_global(ConfigBase):
commands = []
h_red = {}
l_set = ("send", "receive")
+ if w and 'afi' in w:
+ afi = w['afi']
+ else:
+ if h and 'afi' in h:
+ afi = h['afi']
+ else:
+ afi = None
if w[attr]:
if h and attr in h.keys():
h_red = h.get(attr) or {}
for item, value in iteritems(w[attr]):
if opr and item in l_set and not (h_red and self._is_w_same(w[attr], h_red, item)):
commands.append(
- self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr),
+ self._form_attr_cmd(attr=item, val=self._bool_to_str(value), opr=opr, type=afi)
)
elif (
not opr
and item in l_set
and not (h_red and self._is_w_same(w[attr], h_red, item))
):
- commands.append(self._form_attr_cmd(attr=item, opr=opr))
+ commands.append(self._form_attr_cmd(attr=item, opr=opr, type=afi))
return commands
def search_attrib_in_have(self, have, want, attr):
@@ -595,16 +618,17 @@ class Firewall_global(ConfigBase):
return h
return None
- def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True):
+ def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True, type=None):
"""
This function forms the command for leaf attribute.
:param key: parent key.
:param attr: attribute name
:param value: value
:param opr: True/False.
+ :param type: AF type of attribute.
:return: generated command.
"""
- command = self._compute_command(key=key, attr=self._map_attrib(attr), val=val, opr=opr)
+ command = self._compute_command(key=key, attr=self._map_attrib(attr, type=type), val=val, opr=opr)
return command
def _compute_command(self, key=None, attr=None, val=None, remove=False, opr=True):
@@ -621,13 +645,15 @@ class Firewall_global(ConfigBase):
cmd = "delete firewall "
else:
cmd = "set firewall "
+ if key != "group" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ cmd += "global-options "
if key:
cmd += key.replace("_", "-") + " "
if attr:
cmd += attr.replace("_", "-")
if val and opr:
cmd += " '" + str(val) + "'"
- return cmd
+ return cmd.strip()
def _bool_to_str(self, val):
"""
@@ -698,7 +724,7 @@ class Firewall_global(ConfigBase):
:param key: number.
:return: True/False.
"""
- return key in b_set and not (h and self._in_target(h, key))
+ return key in b_set and not self._in_target(h, key)
def _map_attrib(self, attrib, type=None):
"""
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
index 09e19d7..106b2b8 100644
--- a/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py
+++ b/plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py
@@ -15,8 +15,6 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
-
from copy import deepcopy
from ansible.module_utils.six import iteritems
@@ -33,6 +31,10 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils
list_diff_want_only,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
+
class Firewall_rules(ConfigBase):
"""
@@ -171,12 +173,8 @@ class Firewall_rules(ConfigBase):
# In the desired configuration, search for the rule set we
# already have (to be replaced by our desired
# configuration's rule set).
- wanted_rule_set = self.search_r_sets_in_have(
- want,
- rs["name"],
- "r_list",
- h["afi"],
- )
+ rs_id = self._rs_id(rs, h["afi"])
+ wanted_rule_set = self.search_r_sets_in_have(want, rs_id, "r_list")
if wanted_rule_set is not None:
# Remove the rules that we already have if the wanted
# rules exist under the same name.
@@ -202,11 +200,12 @@ class Firewall_rules(ConfigBase):
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", h["afi"])
+ have_r_sets = self._get_r_sets(h)
+ for rs in have_r_sets:
+ rs_id = self._rs_id(rs, h["afi"])
+ w = self.search_r_sets_in_have(want, rs_id, "r_list")
if not w:
- commands.append(self._compute_command(h["afi"], rs["name"], remove=True))
+ commands.append(self._compute_command(rs_id, remove=True))
else:
commands.extend(self._add_r_sets(h["afi"], rs, w, opr=False))
commands.extend(self._state_merged(want, have))
@@ -223,7 +222,8 @@ class Firewall_rules(ConfigBase):
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", w["afi"])
+ rs_id = self._rs_id(rs, w["afi"])
+ h = self.search_r_sets_in_have(have, rs_id, "r_list")
commands.extend(self._add_r_sets(w["afi"], rs, h))
return commands
@@ -240,18 +240,21 @@ class Firewall_rules(ConfigBase):
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", w["afi"])
+ rs_id = self._rs_id(rs, w["afi"])
+ h = self.search_r_sets_in_have(have, rs_id, "r_list")
if h:
- commands.append(self._compute_command(w["afi"], h["name"], remove=True))
+ commands.append(self._compute_command(rs_id, remove=True))
elif have:
for h in have:
if h["afi"] == w["afi"]:
- commands.append(self._compute_command(w["afi"], remove=True))
+ commands.append(
+ self._compute_command(self._rs_id(None, 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))
+ commands.append(self._compute_command(self._rs_id(None, h["afi"]), remove=True))
return commands
def _add_r_sets(self, afi, want, have, opr=True):
@@ -265,11 +268,12 @@ class Firewall_rules(ConfigBase):
:return: generated commands list.
"""
commands = []
- l_set = ("description", "default_action", "enable_default_log")
+ l_set = ("description", "default_action", "default_jump_target", "enable_default_log")
h_rs = {}
h_rules = {}
w_rs = deepcopy(remove_empties(want))
w_rules = w_rs.pop("rules", None)
+ rs_id = self._rs_id(want, afi=afi)
if have:
h_rs = deepcopy(remove_empties(have))
h_rules = h_rs.pop("rules", None)
@@ -278,9 +282,9 @@ class Firewall_rules(ConfigBase):
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))
+ commands.append(self._add_rs_base_attrib(rs_id, key, w_rs))
else:
- commands.append(self._add_rs_base_attrib(afi, want["name"], key, w_rs))
+ commands.append(self._add_rs_base_attrib(rs_id, key, w_rs))
elif not opr and key in l_set:
if (
key == "enable_default_log"
@@ -288,22 +292,24 @@ class Firewall_rules(ConfigBase):
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))
+ commands.append(self._add_rs_base_attrib(rs_id, 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))
+ commands.append(self._add_rs_base_attrib(rs_id, key, w_rs, opr))
+ commands.extend(self._add_rules(rs_id, 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):
+ def _add_rules(self, rs_id, 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.
+ :param rs_id: rule-set identifier.
+ :param w_rules: desired config.
+ :param h_rules: target config.
+ :param opr: True/False.
:return: generated commands list.
"""
commands = []
@@ -316,31 +322,70 @@ class Firewall_rules(ConfigBase):
"disable",
"description",
"log",
+ "jump_target",
)
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")
+ cmd = self._compute_command(rs_id, w["number"], opr=opr)
+ h = self.search_rules_in_have_rs(h_rules, w["number"])
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 == "disable":
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))
+ commands.append(self._add_r_base_attrib(rs_id, key, w))
else:
- commands.append(self._add_r_base_attrib(afi, name, key, w))
+ commands.append(self._add_r_base_attrib(rs_id, key, w))
elif not opr:
+ # Note: if you are experiencing sticky configuration on replace
+ # you may need to add an explicit check for the key here. Anything that
+ # doesn't have a custom operation is taken care of by the `l_set` check
+ # below, but I'm not sure how any of the others work.
+ # It's possible that historically the delete was forced (but now it's
+ # checked).
if key == "number" and self._is_del(l_set, h):
- commands.append(self._add_r_base_attrib(afi, name, key, w, opr=opr))
+ commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr))
continue
- if key == "disable" 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))
+ if (
+ key == "tcp"
+ and val
+ and h
+ and (key not in h or not h[key] or h[key] != w[key])
+ ):
+ commands.extend(self._add_tcp(key, w, h, cmd, opr))
+ if (
+ key == "state"
+ and val
+ and h
+ and (key not in h or not h[key] or h[key] != w[key])
+ ):
+ commands.extend(self._add_state(key, w, h, cmd, opr))
+ if (
+ key == "icmp"
+ and val
+ and h
+ and (key not in h or not h[key] or h[key] != w[key])
+ ):
+ commands.extend(self._add_icmp(key, w, h, cmd, opr))
+ if (
+ key in ("packet_length", "packet_length_exclude")
+ and val
+ and h
+ and (key not in h or not h[key] or h[key] != w[key])
+ ):
+ commands.extend(self._add_packet_length(key, w, h, cmd, opr))
+ elif key == "disable" and val and h and (key not in h or not h[key]):
+ commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr))
+ elif key in ("inbound_interface", "outbound_interface") and not (
+ h and self._is_w_same(w, h, key)
+ ):
+ commands.extend(self._add_interface(key, w, h, cmd, 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))
+ commands.append(self._add_r_base_attrib(rs_id, key, w, opr=opr))
elif key == "p2p":
commands.extend(self._add_p2p(key, w, h, cmd, opr))
elif key == "tcp":
@@ -357,6 +402,10 @@ class Firewall_rules(ConfigBase):
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))
+ elif key in ("packet_length", "packet_length_exclude"):
+ commands.extend(self._add_packet_length(key, w, h, cmd, opr))
+ elif key in ("inbound_interface", "outbound_interface"):
+ commands.extend(self._add_interface(key, w, h, cmd, opr))
return commands
def _add_p2p(self, attr, w, h, cmd, opr):
@@ -405,8 +454,11 @@ class Firewall_rules(ConfigBase):
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)):
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ commands.append(cmd + (" " + attr + " " + item))
+ else:
+ commands.append(cmd + (" " + attr + " " + item + " " + self._bool_to_str(val)))
+ elif not opr and item in l_set and not self._in_target(h_state, item):
commands.append(cmd + (" " + attr + " " + item))
return commands
@@ -460,26 +512,50 @@ class Firewall_rules(ConfigBase):
and not (h_icmp and self._is_w_same(w[attr], h_icmp, item))
):
if item == "type_name":
- os_version = self._get_os_version()
- ver = re.search(
- "vyos ([\\d\\.]+)-?.*", # noqa: W605
- os_version,
- re.IGNORECASE,
- )
- if ver.group(1) >= "1.4":
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.3"):
param_name = "type-name"
else:
param_name = "type"
- if "ipv6-name" in cmd:
+ if "ipv6" in cmd: # ipv6-name or ipv6
commands.append(cmd + (" " + "icmpv6" + " " + param_name + " " + 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))
+ if "ipv6" in cmd: # ipv6-name or ipv6
+ commands.append(cmd + (" " + "icmpv6" + " " + item + " " + str(val)))
+ else:
+ commands.append(cmd + (" " + attr + " " + item + " " + str(val)))
+ elif not opr and item in l_set and not self._in_target(h_icmp, item):
+ commands.append(cmd + (" " + attr + " " + item.replace("_", "-") + " " + str(val)))
+ return commands
+
+ def _add_interface(self, attr, w, h, cmd, opr):
+ """
+ This function forms the commands for 'interface' 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_if = {}
+ l_set = ("name", "group")
+ if w[attr]:
+ if h and attr in h.keys():
+ h_if = h.get(attr) or {}
+ for item, val in iteritems(w[attr]):
+ if opr and item in l_set and not (h_if and self._is_w_same(w[attr], h_if, item)):
+ commands.append(
+ cmd
+ + (" " + attr.replace("_", "-") + " " + item.replace("_", "-") + " " + val)
+ )
+ elif not opr and item in l_set and not (h_if and self._in_target(h_if, item)):
+ commands.append(
+ cmd + (" " + attr.replace("_", "-") + " " + item.replace("_", "-"))
+ )
return commands
def _add_time(self, attr, w, h, cmd, opr):
@@ -524,15 +600,107 @@ class Firewall_rules(ConfigBase):
commands.append(cmd + (" " + attr + " " + item))
return commands
+ def _add_tcp_1_4(self, attr, w, h, cmd, opr):
+ """
+ This function forms the commands for 'tcp' attributes based on the 'opr'.
+ Version 1.4+
+ :param attr: attribute name.
+ :param w: base config.
+ :param h: target config.
+ :param cmd: commands to be prepend.
+ :return: generated list of commands.
+ """
+ commands = []
+ have = []
+ key = "flags"
+ want = []
+
+ if w:
+ if w.get(attr):
+ want = w.get(attr).get(key) or []
+ if h:
+ if h.get(attr):
+ have = h.get(attr).get(key) or []
+ if want:
+ if opr:
+ flags = list_diff_want_only(want, have)
+ for flag in flags:
+ invert = flag.get("invert", False)
+ commands.append(
+ cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"])
+ )
+ elif not opr:
+ flags = list_diff_want_only(want, have)
+ for flag in flags:
+ invert = flag.get("invert", False)
+ commands.append(
+ cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"])
+ )
+ return commands
+
+ def _add_packet_length(self, attr, w, h, cmd, opr):
+ """
+ This function forms the commands for 'packet_length[_exclude]' attributes based on the 'opr'.
+ If < 1.4, handle tcp attributes.
+ :param attr: attribute name.
+ :param w: base config.
+ :param h: target config.
+ :param cmd: commands to be prepend.
+ :return: generated list of commands.
+ """
+ commands = []
+ have = []
+ want = []
+
+ if w:
+ if w.get(attr):
+ want = w.get(attr) or []
+ if h:
+ if h.get(attr):
+ have = h.get(attr) or []
+ attr = attr.replace("_", "-")
+ if want:
+ if opr:
+ lengths = list_diff_want_only(want, have)
+ for l_rec in lengths:
+ commands.append(cmd + " " + attr + " " + str(l_rec["length"]))
+ elif not opr:
+ lengths = list_diff_want_only(want, have)
+ for l_rec in lengths:
+ commands.append(cmd + " " + attr + " " + str(l_rec["length"]))
+ return commands
+
+ def _tcp_flags_string(self, flags):
+ """
+ This function forms the tcp flags string.
+ :param flags: flags list.
+ :return: flags string or None.
+ """
+ if not flags:
+ return ""
+ flag_str = ""
+ for flag in flags:
+ this_flag = flag["flag"].upper()
+ if flag.get("invert", False):
+ this_flag = "!" + this_flag
+ if len(flag_str) > 0:
+ flag_str = ",".join([flag_str, this_flag])
+ else:
+ flag_str = this_flag
+ return flag_str
+
def _add_tcp(self, attr, w, h, cmd, opr):
"""
This function forms the commands for 'tcp' attributes based on the 'opr'.
+ If < 1.4, handle tcp attributes.
:param attr: attribute name.
:param w: base config.
:param h: target config.
:param cmd: commands to be prepend.
:return: generated list of commands.
"""
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ return self._add_tcp_1_4(attr, w, h, cmd, opr)
h_tcp = {}
commands = []
if w[attr]:
@@ -542,10 +710,11 @@ class Firewall_rules(ConfigBase):
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))
+ flag_str = self._tcp_flags_string(flags)
+ if opr and not (h_tcp and flags == h_tcp):
+ commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str))
+ if not opr and not (h_tcp and flags == h_tcp):
+ commands.append(cmd + (" " + attr + " " + "flags" + " " + flag_str))
return commands
def _add_limit(self, attr, w, h, cmd, opr):
@@ -671,43 +840,68 @@ class Firewall_rules(ConfigBase):
)
return commands
- def search_r_sets_in_have(self, have, w_name, type="rule_sets", afi=None):
+ def search_rules_in_have_rs(self, have_rules, r_number):
+ """
+ This function returns the rule if it is present in target config.
+ :param have: target config.
+ :param rs_id: rule-set identifier.
+ :param r_number: rule-number.
+ :return: rule.
+ """
+ if have_rules:
+ for h in have_rules:
+ key = "number"
+ for r in have_rules:
+ if key in r and r[key] == r_number:
+ return r
+ return None
+
+ def search_r_sets_in_have(self, have, rs_id, 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.
- :param afi: address family (when type is r_list).
+ :param rs_id: rule-identifier.
+ :param type: rule_sets if searching a rule_set and r_list if searching from a rule_list.
:return: rule-set/rule.
"""
- if have:
+ if "afi" in rs_id:
+ afi = rs_id["afi"]
+ else:
+ afi = None
+ if rs_id["filter"]:
+ key = "filter"
+ w_value = rs_id["filter"]
+ elif rs_id["name"]:
key = "name"
- if type == "rules":
- key = "number"
- for r in have:
- if r[key] == w_name:
- return r
- elif type == "r_list":
+ w_value = rs_id["name"]
+ else:
+ raise ValueError("id must be specific to name or filter")
+
+ if type not in ("r_list", "rule_sets"):
+ raise ValueError("type must be rule_sets or r_list")
+ if have:
+ if type == "r_list":
for h in have:
if h["afi"] == afi:
r_sets = self._get_r_sets(h)
for rs in r_sets:
- if rs[key] == w_name:
+ if key in rs and rs[key] == w_value:
return rs
else:
+ # searching a ruleset
for rs in have:
- if rs[key] == w_name:
+ if key in rs and rs[key] == w_value:
return rs
return None
- def _get_r_sets(self, item, type="rule_sets"):
+ def _get_r_sets(self, item):
"""
- This function returns the list of rule-sets/rules.
+ This function returns the list of rule-sets.
:param item: config dictionary.
- :param type: rule_sets/rule/r_list.
:return: list of rule-sets/rules.
"""
rs_list = []
+ type = "rule_sets"
r_sets = item[type]
if r_sets:
for rs in r_sets:
@@ -716,8 +910,7 @@ class Firewall_rules(ConfigBase):
def _compute_command(
self,
- afi,
- name=None,
+ rs_id,
number=None,
attrib=None,
value=None,
@@ -726,46 +919,53 @@ class Firewall_rules(ConfigBase):
):
"""
This function construct the add/delete command based on passed attributes.
- :param afi: address type.
- :param name: rule-set name.
+ :param rs_id: rule-set identifier.
: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.
+ :param opr: operation flag.
:return: generated command.
"""
+ if rs_id["name"] and rs_id["filter"]:
+ raise ValueError("name and filter cannot be used together")
if remove or not opr:
- cmd = "delete firewall " + self._get_fw_type(afi)
+ cmd = "delete firewall " + self._get_fw_type(rs_id["afi"])
else:
- cmd = "set firewall " + self._get_fw_type(afi)
- if name:
- cmd += " " + name
+ cmd = "set firewall " + self._get_fw_type(rs_id["afi"])
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ if rs_id["name"]:
+ cmd += " name " + rs_id["name"]
+ elif rs_id["filter"]:
+ cmd += " " + rs_id["filter"] + " filter"
+ elif rs_id["name"]:
+ cmd += " " + rs_id["name"]
if number:
cmd += " rule " + str(number)
if attrib:
- cmd += " " + attrib.replace("_", "-")
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4") and attrib == "enable_default_log":
+ cmd += " " + "default-log"
+ else:
+ cmd += " " + attrib.replace("_", "-")
if value and opr and attrib != "enable_default_log" and attrib != "disable":
cmd += " '" + str(value) + "'"
return cmd
- def _add_r_base_attrib(self, afi, name, attr, rule, opr=True):
+ def _add_r_base_attrib(self, rs_id, 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 rs_id: rule-set identifier.
: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)
+ command = self._compute_command(rs_id, number=rule["number"], opr=opr)
else:
command = self._compute_command(
- afi=afi,
- name=name,
+ rs_id=rs_id,
number=rule["number"],
attrib=attr,
value=rule[attr],
@@ -773,21 +973,54 @@ class Firewall_rules(ConfigBase):
)
return command
- def _add_rs_base_attrib(self, afi, name, attrib, rule, opr=True):
+ def _rs_id(self, have, afi, name=None, filter=None):
"""
+ This function returns the rule-set identifier based on
+ the example rule, overriding the components as specified.
- This function forms the command for 'rule-sets' attributes which doesn't
- have further sub attributes.
+ :param have: example rule.
:param afi: address type.
- :param name: rule-set name
+ :param name: rule-set name.
+ :param filter: filter name.
+ :return: rule-set identifier.
+ """
+ identifier = {"name": None, "filter": None}
+ if afi:
+ identifier["afi"] = afi
+ else:
+ raise ValueError("afi must be provided")
+
+ if name:
+ identifier["name"] = name
+ return identifier
+ elif filter:
+ identifier["filter"] = filter
+ return identifier
+ if have:
+ if "name" in have and have["name"]:
+ identifier["name"] = have["name"]
+ return identifier
+ if "filter" in have and have["filter"]:
+ identifier["filter"] = have["filter"]
+ return identifier
+ # raise ValueError("name or filter must be provided or present in have")
+ # unless we want a wildcard
+ return identifier
+
+ def _add_rs_base_attrib(self, rs_id, attrib, rule, opr=True):
+ """
+
+ This function forms the command for 'rule-sets' attributes which don't
+ have further sub attributes.
+
+ :param rs_id: rule-set identifier.
:param attrib: attribute name
:param rule: rule config dictionary.
:param opr: True/False.
:return: generated command.
"""
command = self._compute_command(
- afi=afi,
- name=name,
+ rs_id=rs_id,
attrib=attrib,
value=rule[attrib],
opr=opr,
@@ -808,6 +1041,8 @@ class Firewall_rules(ConfigBase):
:param afi: address type
:return: rule-set type.
"""
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ return "ipv6" if afi == "ipv6" else "ipv4"
return "ipv6-name" if afi == "ipv6" else "name"
def _is_del(self, l_set, h, key="number"):
@@ -834,37 +1069,9 @@ class Firewall_rules(ConfigBase):
def _in_target(self, h, key):
"""
- This function checks whether the target nexist and key present in target config.
+ This function checks whether the target exists 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",
- "log",
- "action",
- "fragment",
- "protocol",
- "disable",
- "description",
- "mac_address",
- "default_action",
- "enable_default_log",
- )
- return True if key in r_set else False
-
- def _get_os_version(self):
- os_version = "1.2"
- if self._connection:
- os_version = self._connection.get_device_info()["network_os_version"]
- return os_version
diff --git a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
index 731014c..62e4f92 100644
--- a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
+++ b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
@@ -275,7 +275,7 @@ class Interfaces(ConfigBase):
commands.append(
self._compute_commands(key=key, interface=want_copy["name"], remove=True),
)
- if have_copy["enabled"] is False:
+ if have_copy["enabled"] is False and not ('enabled' in want_copy and want_copy["enabled"] is False):
commands.append(
self._compute_commands(key="enabled", value=True, interface=want_copy["name"]),
)
diff --git a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
index a7652a6..51b4749 100644
--- a/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py
@@ -27,9 +27,17 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.u
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import (
- Ospf_interfacesTemplate,
+ Ospf_interfacesTemplate
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import (
+ Ospf_interfacesTemplate14
+)
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
+
class Ospf_interfaces(ResourceModule):
"""
@@ -61,12 +69,30 @@ class Ospf_interfaces(ResourceModule):
"passive",
]
+ def _validate_template(self):
+ version = get_os_version(self._module)
+ if LooseVersion(version) >= LooseVersion("1.4"):
+ self._tmplt = Ospf_interfacesTemplate14()
+ else:
+ self._tmplt = Ospf_interfacesTemplate()
+
+ def parse(self):
+ """ override parse to check template """
+ self._validate_template()
+ return super().parse()
+
+ def get_parser(self, name):
+ """get_parsers"""
+ self._validate_template()
+ return super().get_parser(name)
+
def execute_module(self):
"""Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
+ self._validate_template()
if self.state not in ["parsed", "gathered"]:
self.generate_commands()
self.run_commands()
diff --git a/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
index 5b47222..3f4da3e 100644
--- a/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
+++ b/plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py
@@ -174,9 +174,8 @@ class Firewall_globalFacts(object):
:return: generated rule list configuration.
"""
sp_lst = []
- attrib = "state-policy"
- policies = findall(r"^set firewall " + attrib + " (\\S+)", conf, M)
-
+ policies = findall(r"^set firewall (?:global-options )state-policy (\S+)", conf, M)
+ policies = list(set(policies)) # remove redundancies
if policies:
rules_lst = []
for sp in set(policies):
@@ -197,7 +196,7 @@ class Firewall_globalFacts(object):
:param attrib: connection type.
:return: generated rule configuration dictionary.
"""
- a_lst = ["action", "log"]
+ a_lst = ["action", "log", "log_level"]
cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
return cfg_dict
@@ -304,16 +303,15 @@ class Firewall_globalFacts(object):
regex = match + " " + regex
if conf:
if self.is_bool(attrib):
- attr = self.map_regex(attrib, type)
- out = conf.find(attr.replace("_", "-"))
- dis = conf.find(attr.replace("_", "-") + " 'disable'")
- if out >= 1:
- if dis >= 1:
+ # fancy regex to make sure we don't get a substring
+ out = search(r"^.*" + regex + r"( 'disable')?(?=\s|$)", conf, M)
+ if out:
+ if out.group(1):
config[attrib] = False
else:
config[attrib] = True
else:
- out = search(r"^.*" + regex + " (.+)", conf, M)
+ out = search(r"^.*" + regex + r" (.+)", conf, M)
if out:
val = out.group(1).strip("'")
if self.is_num(attrib):
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
index ead038a..1fc7025 100644
--- a/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py
+++ b/plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py
@@ -14,8 +14,6 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
-import re
-
from copy import deepcopy
from re import M, findall, search
@@ -61,15 +59,31 @@ class Firewall_rulesFacts(object):
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)
+ # check 1.4+ first
+ new_rules = True
+ v6_rules = findall(r"^set firewall ipv6 (name|forward|input|output) (?:\'*)(\S+)(?:\'*)", data, M)
+ if not v6_rules:
+ v6_rules = findall(r"^set firewall ipv6-name (?:\'*)(\S+)(?:\'*)", data, M)
+ if v6_rules:
+ new_rules = False
+ v4_rules = findall(r"^set firewall ipv4 (name|forward|input|output) (?:\'*)(\S+)(?:\'*)", data, M)
+ if not v4_rules:
+ v4_rules = findall(r"^set firewall name (?:\'*)(\S+)(?:\'*)", data, M)
+ if v4_rules:
+ new_rules = False
if v6_rules:
- config = self.get_rules(data, v6_rules, type="ipv6")
+ if new_rules:
+ config = self.get_rules_post_1_4(data, v6_rules, type="ipv6")
+ else:
+ 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 new_rules:
+ config = self.get_rules_post_1_4(data, v4_rules, type="ipv4")
+ else:
+ config = self.get_rules(data, v4_rules, type="ipv4")
if config:
config = utils.remove_empties(config)
objs.append(config)
@@ -113,6 +127,39 @@ class Firewall_rulesFacts(object):
config = {"afi": "ipv6", "rule_sets": r_v6}
return config
+ def get_rules_post_1_4(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.
+ Specifically for v1.4+ version.
+ :param data: configuration.
+ :param rules: list of rule-sets.
+ :param type: ip address type.
+ :return: generated rule-sets configuration.
+ """
+ r_v4 = []
+ r_v6 = []
+ for kind, name in set(rules):
+ rule_regex = r" %s %s %s .+$" % (type, kind, name.strip("'"))
+ cfg = findall(rule_regex, data, M)
+ fr = self.render_config(cfg, name.strip("'"))
+ if kind == "name":
+ fr["name"] = name.strip("'")
+ elif kind in ("forward", "input", "output"):
+ fr["filter"] = kind
+ else:
+ raise ValueError("Unknown rule kind: %s %s" % kind, name)
+ 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
@@ -124,10 +171,12 @@ class Firewall_rulesFacts(object):
:returns: The generated config
"""
conf = "\n".join(filter(lambda x: x, conf))
- a_lst = ["description", "default_action", "enable_default_log"]
+ a_lst = ["description", "default_action", "default_jump_target", "enable_default_log", "default_log"]
config = self.parse_attr(conf, a_lst, match)
if not config:
config = {}
+ if 'default_log' in config:
+ config['enable_default_log'] = config.pop('default_log')
config["rules"] = self.parse_rules_lst(conf)
return config
@@ -169,11 +218,14 @@ class Firewall_rulesFacts(object):
"disable",
"description",
"icmp",
+ "jump_target",
+ "queue",
+ "queue_options",
]
rule = self.parse_attr(conf, a_lst)
r_sub = {
"p2p": self.parse_p2p(conf),
- "tcp": self.parse_tcp(conf, "tcp"),
+ "tcp": self.parse_tcp(conf),
"icmp": self.parse_icmp(conf, "icmp"),
"time": self.parse_time(conf, "time"),
"limit": self.parse_limit(conf, "limit"),
@@ -181,10 +233,42 @@ class Firewall_rulesFacts(object):
"recent": self.parse_recent(conf, "recent"),
"source": self.parse_src_or_dest(conf, "source"),
"destination": self.parse_src_or_dest(conf, "destination"),
+ "inbound_interface": self.parse_interface(conf, "inbound-interface"),
+ "outbound_interface": self.parse_interface(conf, "outbound-interface"),
+ "packet_length": self.parse_packet_length(conf, "packet-length"),
+ "packet_length_exclude": self.parse_packet_length(conf, "packet-length-exclude"),
}
rule.update(r_sub)
return rule
+ def parse_interface(self, conf, attrib):
+ """
+ This function triggers the parsing of 'interface' attributes.
+ :param conf: configuration.
+ :param attrib: 'interface'.
+ :return: generated config dictionary.
+ """
+ a_lst = ["name", "group"]
+ cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
+ return cfg_dict
+
+ def parse_packet_length(self, conf, attrib=None):
+ """
+ This function triggers the parsing of 'packet-length' attributes.
+ :param conf: configuration.
+ :param attrib: 'packet-length'.
+ :return: generated config dictionary.
+ """
+ lengths = []
+ rule_regex = r"%s (\d+)" % attrib
+ found_lengths = findall(rule_regex, conf, M)
+ if found_lengths:
+ lengths = []
+ for l in set(found_lengths):
+ obj = {"length": l.strip("'")}
+ lengths.append(obj)
+ return lengths
+
def parse_p2p(self, conf):
"""
This function forms the regex to fetch the 'p2p' with in
@@ -226,15 +310,41 @@ class Firewall_rulesFacts(object):
cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
return cfg_dict
- def parse_tcp(self, conf, attrib=None):
+ def parse_tcp(self, conf):
"""
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
+ f_lst = []
+ flags = findall(r"tcp flags (not )?(?:\'*)([\w!,]+)(?:\'*)", conf, M)
+ # for pre 1.4, this is a string including possible commas
+ # and ! as an inverter. For 1.4+ this is a single flag per
+ # command and 'not' as the inverter
+ if flags:
+ flag_lst = []
+ for n, f in set(flags):
+ f = f.strip("'").lower()
+ if "," in f:
+ # pre 1.4 version with multiple flags
+ fs = f.split(",")
+ for f in fs:
+ if "!" in f:
+ obj = {"flag": f.strip("'!"), "invert": True}
+ else:
+ obj = {"flag": f.strip("'")}
+ flag_lst.append(obj)
+ elif "!" in f:
+ obj = {"flag": f.strip("'!"), "invert": True}
+ flag_lst.append(obj)
+ else:
+ obj = {"flag": f.strip("'")}
+ if n:
+ obj["invert"] = True
+ flag_lst.append(obj)
+ f_lst = sorted(flag_lst, key=lambda i: i["flag"])
+ return {"flags": f_lst}
def parse_time(self, conf, attrib=None):
"""
@@ -276,6 +386,44 @@ class Firewall_rulesFacts(object):
cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
return cfg_dict
+ def parse_icmp_attr(self, conf, match):
+ """
+ This function peforms the following:
+ - parse ICMP arguemnts for firewall rules
+ - consider that older versions may need numbers or letters
+ in type, newer ones are more specific
+ :param conf: configuration.
+ :param match: parent node/attribute name.
+ :return: generated config dictionary.
+ """
+ config = {}
+ if not conf:
+ return config
+
+ for attrib in ("code", "type", "type-name"):
+ regex = self.map_regex(attrib)
+ if match:
+ regex = match + " " + regex
+ out = search(r"^.*" + regex + " (.+)", conf, M)
+ if out:
+ val = out.group(1).strip("'")
+ if attrib == 'type-name':
+ config['type_name'] = val
+ if attrib == 'code':
+ config['code'] = int(val)
+ if attrib == 'type':
+ # <1.3 could be # (type), #/# (type/code) or 'type' (type_name)
+ # recent this is only for strings
+ if "/" in val: # type/code
+ (type_no, code) = val.split(".")
+ config['type'] = type_no
+ config['code'] = code
+ elif val.isnumeric():
+ config['type'] = type_no
+ else:
+ config['type_name'] = val
+ return config
+
def parse_icmp(self, conf, attrib=None):
"""
This function triggers the parsing of 'icmp' attributes.
@@ -283,11 +431,9 @@ class Firewall_rulesFacts(object):
:param attrib: 'icmp'.
:return: generated config dictionary.
"""
- a_lst = ["code", "type", "type_name"]
- if attrib == "icmp":
- attrib = "icmpv6"
- conf = re.sub("icmpv6 type", "icmpv6 type-name", conf)
- cfg_dict = self.parse_attr(conf, a_lst, match=attrib)
+ cfg_dict = self.parse_icmp_attr(conf, "icmp")
+ if (len(cfg_dict) == 0):
+ cfg_dict = self.parse_icmp_attr(conf, "icmpv6")
return cfg_dict
def parse_limit(self, conf, attrib=None):
@@ -330,7 +476,6 @@ class Firewall_rulesFacts(object):
if conf:
if self.is_bool(attrib):
out = conf.find(attrib.replace("_", "-"))
-
dis = conf.find(attrib.replace("_", "-") + " 'disable'")
if out >= 1:
if dis >= 1:
@@ -375,6 +520,7 @@ class Firewall_rulesFacts(object):
"disabled",
"established",
"enable_default_log",
+ "default_log",
)
return True if attrib in bool_set else False
diff --git a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
index 995be91..cd8008c 100644
--- a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
@@ -17,7 +17,7 @@ __metaclass__ = type
from copy import deepcopy
-from re import M, findall
+from re import M, findall, search
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
@@ -66,7 +66,7 @@ class InterfacesFacts(object):
)
if interface_names:
for interface in set(interface_names):
- intf_regex = r" %s .+$" % interface.strip("'")
+ intf_regex = r" %s (.+$)" % interface.strip("'")
cfg = findall(intf_regex, data, M)
obj = self.render_config(cfg)
obj["name"] = interface.strip("'")
@@ -106,7 +106,7 @@ class InterfacesFacts(object):
if vif_names:
vifs_list = []
for vif in set(vif_names):
- vif_regex = r" %s .+$" % vif
+ vif_regex = r"%s (.+$)" % vif
cfg = "\n".join(findall(vif_regex, conf, M))
obj = self.parse_attribs(["description", "mtu"], cfg)
obj["vlan_id"] = int(vif)
@@ -117,6 +117,14 @@ class InterfacesFacts(object):
return vifs_list
def parse_attribs(self, attribs, conf):
+ """
+ Parse the attributes of a network interface.
+
+ :param attribs: List of attribute names.
+ :param conf: Configuration string.
+ :rtype: dict
+ :returns: Parsed configuration dictionary.
+ """
config = {}
for item in attribs:
value = utils.parse_conf_arg(conf, item)
@@ -126,7 +134,11 @@ class InterfacesFacts(object):
config[item] = value.strip("'")
else:
config[item] = None
- if "disable" in conf:
+
+ # match only on disable next to the interface name
+ # there are other sub-commands that can be disabled
+ match = search(r"^ *disable", conf, M)
+ if match:
config["enabled"] = False
else:
config["enabled"] = True
diff --git a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
index be467a0..7d4d1a0 100644
--- a/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py
@@ -62,7 +62,7 @@ class L3_interfacesFacts(object):
# operate on a collection of resource x
objs = []
interface_names = re.findall(
- r"set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|vxlan) (?:\'*)(\S+)(?:\'*)",
+ r"set interfaces (?:ethernet|bonding|bridge|dummy|tunnel|vti|loopback|vxlan) (?:\'*)(\S+)(?:\'*)",
data,
re.M,
)
diff --git a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py
index af6c577..c0e7489 100644
--- a/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py
@@ -23,9 +23,17 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.osp
Ospf_interfacesArgs,
)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces import (
- Ospf_interfacesTemplate,
+ Ospf_interfacesTemplate
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ospf_interfaces_14 import (
+ Ospf_interfacesTemplate14
+)
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
+
class Ospf_interfacesFacts(object):
"""The vyos ospf_interfaces facts class"""
@@ -35,9 +43,30 @@ class Ospf_interfacesFacts(object):
self.argument_spec = Ospf_interfacesArgs.argument_spec
def get_device_data(self, connection):
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ # use set protocols ospf in order to get both ospf and ospfv3
+ return connection.get("show configuration commands | match 'set protocols ospf'")
return connection.get('show configuration commands | match "set interfaces"')
- def get_config_set(self, data):
+ def get_config_set_1_4(self, data):
+ """To classify the configurations beased on interface"""
+ interface_list = []
+ config_set = []
+ int_string = ""
+ for config_line in data.splitlines():
+ ospf_int = re.search(r"set protocols (?:ospf|ospfv3) interface (\S+) .*", config_line)
+ if ospf_int:
+ if ospf_int.group(1) not in interface_list:
+ if int_string:
+ config_set.append(int_string)
+ interface_list.append(ospf_int.group(1))
+ int_string = ""
+ int_string = int_string + config_line + "\n"
+ if int_string:
+ config_set.append(int_string)
+ return config_set
+
+ def get_config_set_1_2(self, data):
"""To classify the configurations beased on interface"""
interface_list = []
config_set = []
@@ -55,6 +84,12 @@ class Ospf_interfacesFacts(object):
config_set.append(int_string)
return config_set
+ def get_config_set(self, data, connection):
+ """To classify the configurations beased on interface"""
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ return self.get_config_set_1_4(data)
+ return self.get_config_set_1_2(data)
+
def populate_facts(self, connection, ansible_facts, data=None):
"""Populate the facts for Ospf_interfaces network resource
@@ -67,16 +102,20 @@ class Ospf_interfacesFacts(object):
"""
facts = {}
objs = []
- ospf_interfaces_parser = Ospf_interfacesTemplate(lines=[], module=self._module)
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ ospf_interface_class = Ospf_interfacesTemplate14
+ else:
+ ospf_interface_class = Ospf_interfacesTemplate
+ ospf_interfaces_parser = ospf_interface_class(lines=[], module=self._module)
if not data:
data = self.get_device_data(connection)
# parse native config using the Ospf_interfaces template
ospf_interfaces_facts = []
- resources = self.get_config_set(data)
+ resources = self.get_config_set(data, connection)
for resource in resources:
- ospf_interfaces_parser = Ospf_interfacesTemplate(
+ ospf_interfaces_parser = ospf_interface_class(
lines=resource.split("\n"),
module=self._module,
)
@@ -93,7 +132,7 @@ class Ospf_interfacesFacts(object):
self.argument_spec,
{"config": ospf_interfaces_facts},
redact=True,
- ),
+ )
)
if params.get("config"):
for cfg in params["config"]:
diff --git a/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py
new file mode 100644
index 0000000..7f49d47
--- /dev/null
+++ b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py
@@ -0,0 +1,650 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+"""
+The Ospf_interfaces parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _get_parameters(data):
+ if data["afi"] == "ipv6":
+ val = ["ospfv3", "ipv6"]
+ else:
+ val = ["ospf", "ip"]
+ return val
+
+
+def _tmplt_ospf_int_delete(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols " + params[0] + " interface {name}".format(**config_data)
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_cost(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " cost {cost}".format(**config_data["address_family"])
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_auth_password(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " authentication plaintext-password {plaintext_password}".format(
+ **config_data["address_family"]["authentication"]
+ )
+ )
+ return command
+
+
+def _tmplt_ospf_int_auth_md5(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " authentication md5 key-id {key_id} ".format(
+ **config_data["address_family"]["authentication"]["md5_key"]
+ )
+ + "md5-key {key}".format(**config_data["address_family"]["authentication"]["md5_key"])
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_auth_md5_delete(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " authentication"
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_bw(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " bandwidth {bandwidth}".format(**config_data["address_family"])
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_hello_interval(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " hello-interval {hello_interval}".format(**config_data["address_family"])
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_dead_interval(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " dead-interval {dead_interval}".format(**config_data["address_family"])
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_mtu_ignore(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " mtu-ignore"
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_network(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " network {network}".format(**config_data["address_family"])
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_priority(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " priority {priority}".format(**config_data["address_family"])
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_retransmit_interval(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " retransmit-interval {retransmit_interval}".format(**config_data["address_family"])
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_transmit_delay(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " transmit-delay {transmit_delay}".format(**config_data["address_family"])
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_ifmtu(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " ifmtu {ifmtu}".format(**config_data["address_family"])
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_instance(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " instance-id {instance}".format(**config_data["address_family"])
+ )
+
+ return command
+
+
+def _tmplt_ospf_int_passive(config_data):
+ params = _get_parameters(config_data["address_family"])
+ command = (
+ "protocols "
+ + params[0]
+ + " interface {name}".format(**config_data)
+ + " passive"
+ )
+
+ return command
+
+
+class Ospf_interfacesTemplate14(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ prefix = {"set": "set", "remove": "delete"}
+ super(Ospf_interfacesTemplate14, self).__init__(
+ lines=lines, tmplt=self, prefix=prefix, module=module
+ )
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "ip_ospf",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "remval": _tmplt_ospf_int_delete,
+ "compval": "address_family",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ }
+ }
+ }
+ },
+ {
+ "name": "authentication_password",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+authentication
+ \s+plaintext-password
+ \s+(?P<text>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_auth_password,
+ "compval": "address_family.authentication",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "authentication": {
+ "plaintext_password": "{{ text }}"
+ }
+ }
+ }
+ }
+ },
+ {
+ "name": "authentication_md5",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+authentication
+ \s+md5
+ \s+key-id
+ \s+(?P<id>\d+)
+ \s+md5-key
+ \s+(?P<text>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_auth_md5,
+ "remval": _tmplt_ospf_int_auth_md5_delete,
+ "compval": "address_family.authentication",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "authentication": {
+ "md5_key": {
+ "key_id": "{{ id }}",
+ "key": "{{ text }}"
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "name": "bandwidth",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+bandwidth
+ \s+(?P<bw>\'\d+\')
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_bw,
+ "compval": "address_family.bandwidth",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "bandwidth": "{{ bw }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "cost",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+cost
+ \s+(?P<val>\'\d+\')
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_cost,
+ "compval": "address_family.cost",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "cost": "{{ val }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "hello_interval",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+hello-interval
+ \s+(?P<val>\'\d+\')
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_hello_interval,
+ "compval": "address_family.hello_interval",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "hello_interval": "{{ val }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "dead_interval",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+dead-interval
+ \s+(?P<val>\'\d+\')
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_dead_interval,
+ "compval": "address_family.dead_interval",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "dead_interval": "{{ val }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "mtu_ignore",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+(?P<mtu>\'mtu-ignore\')
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_mtu_ignore,
+ "compval": "address_family.mtu_ignore",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "mtu_ignore": "{{ True if mtu is defined }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "network",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+network
+ \s+(?P<val>\S+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_network,
+ "compval": "address_family.network",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "network": "{{ val }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "priority",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+priority
+ \s+(?P<val>\'\d+\')
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_priority,
+ "compval": "address_family.priority",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "priority": "{{ val }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "retransmit_interval",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+retransmit-interval
+ \s+(?P<val>\'\d+\')
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_retransmit_interval,
+ "compval": "address_family.retransmit_interval",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "retransmit_interval": "{{ val }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "transmit_delay",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+transmit-delay
+ \s+(?P<val>\'\d+\')
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_transmit_delay,
+ "compval": "address_family.transmit_delay",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "transmit_delay": "{{ val }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "ifmtu",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+ifmtu
+ \s+(?P<val>\'\d+\')
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_ifmtu,
+ "compval": "address_family.ifmtu",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "ifmtu": "{{ val }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "instance",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+instance-id
+ \s+(?P<val>\'\d+\')
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_instance,
+ "compval": "address_family.instance",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "instance": "{{ val }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "passive",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ \s+(?P<pass>\'passive\')
+ *$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_ospf_int_passive,
+ "compval": "address_family.passive",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": {
+ '{{ "ipv4" if proto == "ospf" else "ipv6" }}': {
+ "afi": '{{ "ipv4" if proto == "ospf" else "ipv6" }}',
+ "passive": "{{ True if pass is defined }}"
+ }
+ }
+ }
+ },
+ {
+ "name": "interface_name",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+protocols
+ \s+(?P<proto>ospf|ospfv3)
+ \s+interface
+ \s+(?P<name>\S+)
+ .*$""",
+ re.VERBOSE,
+ ),
+ "setval": "set protocols {{ proto }} interface {{ name }}",
+ "result": {
+ "name": "{{ name }}",
+ }
+ },
+ ]
+ # fmt: on
diff --git a/plugins/module_utils/network/vyos/utils/version.py b/plugins/module_utils/network/vyos/utils/version.py
new file mode 100644
index 0000000..cc3028c
--- /dev/null
+++ b/plugins/module_utils/network/vyos/utils/version.py
@@ -0,0 +1,13 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2021, Felix Fontein <felix@fontein.de>
+# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+"""Provide version object to compare version numbers."""
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+from ansible.module_utils.compat.version import LooseVersion # pylint: disable=unused-import
diff --git a/plugins/module_utils/network/vyos/vyos.py b/plugins/module_utils/network/vyos/vyos.py
index 4fcb331..1430b1b 100644
--- a/plugins/module_utils/network/vyos/vyos.py
+++ b/plugins/module_utils/network/vyos/vyos.py
@@ -34,7 +34,6 @@ import json
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection, ConnectionError
-
_DEVICE_CONFIGS = {}
@@ -100,3 +99,10 @@ def load_config(module, commands, commit=False, comment=None):
module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
return response.get("diff")
+
+
+def get_os_version(module):
+ connection = get_connection(module)
+ if connection.get_device_info():
+ os_version = connection.get_device_info()["network_os_major_version"]
+ return os_version
diff --git a/plugins/modules/vyos_firewall_global.py b/plugins/modules/vyos_firewall_global.py
index 205ef13..befe5e7 100644
--- a/plugins/modules/vyos_firewall_global.py
+++ b/plugins/modules/vyos_firewall_global.py
@@ -253,6 +253,19 @@ options:
description:
- Enable logging of packets part of an established connection.
type: bool
+ log_level:
+ description:
+ - Only available in 1.4+
+ type: str
+ choices:
+ - emerg
+ - alert
+ - crit
+ - err
+ - warn
+ - notice
+ - info
+ - debug
running_config:
description:
- The module, by default, will connect to the remote device and retrieve the current
diff --git a/plugins/modules/vyos_firewall_rules.py b/plugins/modules/vyos_firewall_rules.py
index 06a300f..fd2e7d5 100644
--- a/plugins/modules/vyos_firewall_rules.py
+++ b/plugins/modules/vyos_firewall_rules.py
@@ -31,6 +31,11 @@ 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
@@ -62,9 +67,16 @@ options:
type: list
elements: dict
suboptions:
+ filter:
+ description:
+ - Filter type (exclusive to "name").
+ - Supported in 1.4 and later.
+ type: str
+ choices: ['input', 'output', 'forward']
name:
description:
- Firewall rule set name.
+ - Required for 1.3- and optional for 1.4+.
type: str
default_action:
description:
@@ -72,11 +84,15 @@ options:
- 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)
+ - jump (Jump to another rule-set, 1.4+)
+ type: str
+ choices: ['drop', 'reject', 'accept', 'jump']
+ default_jump_target:
+ description:
+ - Default jump target if the default action is jump.
+ - Only valid in 1.4 and later.
+ - Only valid when default_action = jump.
type: str
- choices:
- - drop
- - reject
- - accept
description:
description:
- Rule set description.
@@ -103,12 +119,19 @@ options:
action:
description:
- Specifying the action.
+ - inspect is available < 1.4
+ - continue, return, jump, queue, synproxy are available >= 1.4
type: str
choices:
- drop
- reject
- accept
- inspect
+ - continue
+ - return
+ - jump
+ - queue
+ - synproxy
destination:
description:
- Specifying the destination parameters.
@@ -148,6 +171,7 @@ options:
disable:
description:
- Option to disable firewall rule.
+ - aliased to disabled
type: bool
aliases: ["disabled"]
fragment:
@@ -215,6 +239,21 @@ options:
description:
- ICMP type.
type: int
+ inbound_interface:
+ description:
+ - Inbound interface.
+ - Only valid in 1.4 and later.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - Interface name.
+ - Can have wildcards
+ type: str
+ group:
+ description:
+ - Interface group.
+ type: str
ipsec:
description:
- Inbound ip sec packets.
@@ -222,13 +261,16 @@ options:
choices:
- match-ipsec
- match-none
- log:
+ - match-ipsec-in
+ - match-ipsec-out
+ - match-none-in
+ - match-none-out
+ jump_target:
description:
- - Option to log packets matching rule
+ - Jump target if the action is jump.
+ - Only valid in 1.4 and later.
+ - Only valid when action = jump.
type: str
- choices:
- - disable
- - enable
limit:
description:
- Rate limit using a token bucket filter.
@@ -255,6 +297,55 @@ options:
description:
- This is the time unit.
type: str
+ log:
+ description:
+ - Log matching packets.
+ type: str
+ choices: ['disable', 'enable']
+ outbound_interface:
+ description:
+ - Match outbound interface.
+ - Only valid in 1.4 and later.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - Interface name.
+ - Can have wildcards
+ type: str
+ group:
+ description:
+ - Interface group.
+ type: str
+ packet_length:
+ description:
+ - Packet length match.
+ - Only valid in 1.4 and later.
+ - Multiple values from 1 to 65535 and ranges are supported
+ type: list
+ elements: dict
+ suboptions:
+ length:
+ description:
+ - Packet length or range.
+ type: str
+ packet_length_exclude:
+ description:
+ - Packet length match.
+ - Only valid in 1.4 and later.
+ - Multiple values from 1 to 65535 and ranges are supported
+ type: list
+ elements: dict
+ suboptions:
+ length:
+ description:
+ - Packet length or range.
+ type: str
+ packet_type:
+ description:
+ - Packet type match.
+ type: str
+ choices: ['broadcast', 'multicast', 'host', 'other']
p2p:
description:
- P2P application packets.
@@ -283,6 +374,20 @@ options:
- all All IP protocols.
- (!)All IP protocols except for the specified name or number.
type: str
+ queue:
+ description:
+ - Queue options.
+ - Only valid in 1.4 and later.
+ - Only valid when action = queue.
+ - Can be a queue number or range.
+ type: str
+ queue_options:
+ description:
+ - Queue options.
+ - Only valid in 1.4 and later.
+ - Only valid when action = queue.
+ type: str
+ choices: ['bypass', 'fanout']
recent:
description:
- Parameters for matching recently seen sources.
@@ -295,7 +400,8 @@ options:
time:
description:
- Source addresses seen in the last N seconds.
- type: int
+ - Since 1.4, this is a string of second/minute/hour
+ type: str
source:
description:
- Source parameters.
@@ -337,6 +443,12 @@ options:
- <MAC address> MAC address to match.
- <!MAC address> Match everything except the specified MAC address.
type: str
+ fqdn:
+ description:
+ - Fully qualified domain name.
+ - Available in 1.4 and later.
+ type: str
+
state:
description:
- Session state.
@@ -358,6 +470,21 @@ options:
description:
- Related state.
type: bool
+ synproxy:
+ description:
+ - SYN proxy options.
+ - Only valid in 1.4 and later.
+ - Only valid when action = synproxy.
+ type: dict
+ suboptions:
+ mss:
+ description:
+ - Adjust MSS (501-65535)
+ type: int
+ window_scale:
+ description:
+ - Window scale (1-14).
+ type: int
tcp:
description:
- TCP flags to match.
@@ -365,8 +492,22 @@ options:
suboptions:
flags:
description:
- - TCP flags to be matched.
- type: str
+ - list of tcp flags to be matched
+ - 5.0 breaking change to support 1.4+ and 1.3-
+ type: list
+ elements: dict
+ suboptions:
+ flag:
+ description:
+ - TCP flag to be matched.
+ - syn, ack, fin, rst, urg, psh, all (1.3-)
+ - syn, ack, fin, rst, urg, psh, cwr, ecn (1.4+)
+ type: str
+ choices: ['ack', 'cwr', 'ecn', 'fin', 'psh', 'rst', 'syn', 'urg', 'all']
+ invert:
+ description:
+ - Invert the match.
+ type: bool
time:
description:
- Time to match rule.
@@ -1460,14 +1601,14 @@ RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
- type: list
+ type: dict
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
+ type: dict
sample: >
The configuration returned will always be in the same format
of the parameters above.
@@ -1486,17 +1627,14 @@ commands:
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,
-)
+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 = [
@@ -1518,5 +1656,5 @@ def main():
module.exit_json(**result)
-if __name__ == "__main__":
+if __name__ == '__main__':
main()
diff --git a/plugins/modules/vyos_ospf_interfaces.py b/plugins/modules/vyos_ospf_interfaces.py
index c232689..3329058 100644
--- a/plugins/modules/vyos_ospf_interfaces.py
+++ b/plugins/modules/vyos_ospf_interfaces.py
@@ -901,7 +901,7 @@ def main():
argument_spec=Ospf_interfacesArgs.argument_spec,
mutually_exclusive=[],
required_if=[],
- supports_check_mode=False,
+ supports_check_mode=True,
)
result = Ospf_interfaces(module).execute_module()
diff --git a/requirements.txt b/requirements.txt
index e4c0f75..ee91c10 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,2 @@
-ansible-pylibssh
paramiko
scp
diff --git a/tests/sanity/ignore-2.19.txt b/tests/sanity/ignore-2.19.txt
new file mode 100644
index 0000000..c835eef
--- /dev/null
+++ b/tests/sanity/ignore-2.19.txt
@@ -0,0 +1 @@
+plugins/action/vyos.py action-plugin-docs # base class for deprecated network platform modules using `connection: local`
diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg
new file mode 100644
index 0000000..7b281de
--- /dev/null
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_global_config_v14.cfg
@@ -0,0 +1,16 @@
+set firewall group address-group RND-HOSTS address 192.0.2.1
+set firewall group address-group RND-HOSTS address 192.0.2.3
+set firewall group address-group RND-HOSTS address 192.0.2.5
+set firewall group address-group RND-HOSTS description 'This group has the Management hosts address lists'
+set firewall group ipv6-address-group LOCAL-v6 address ::1
+set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1
+set firewall group ipv6-address-group LOCAL-v6 description 'This group has the hosts address lists of this machine'
+set firewall group network-group RND network 192.0.2.0/24
+set firewall group network-group RND description 'This group has the Management network addresses'
+set firewall group ipv6-network-group UNIQUE-LOCAL-v6 network fc00::/7
+set firewall group ipv6-network-group UNIQUE-LOCAL-v6 description 'This group encompasses the ULA address space in IPv6'
+set firewall group port-group SSH port 22
+set firewall group port-group SSH description 'This group has the ssh ports'
+set firewall global-options all-ping enable
+set firewall global-options state-policy related action 'accept'
+set firewall global-options state-policy related log-level 'alert'
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
index a3aec78..f1fdf1e 100644
--- a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config.cfg
@@ -9,6 +9,7 @@ set firewall name V4-INGRESS rule 101
set firewall name V4-INGRESS rule 101 'disable'
set firewall name V4-INGRESS rule 101 action 'accept'
set firewall name V4-INGRESS rule 101 ipsec 'match-ipsec'
+set firewall name V4-INGRESS rule 101 log 'enable'
set firewall name EGRESS default-action 'reject'
set firewall ipv6-name EGRESS default-action 'reject'
set firewall ipv6-name EGRESS rule 20
diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config_v14.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config_v14.cfg
new file mode 100644
index 0000000..ef596cd
--- /dev/null
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_firewall_rules_config_v14.cfg
@@ -0,0 +1,26 @@
+set firewall ipv4 name V4-INGRESS default-action 'accept'
+set firewall ipv6 name V6-INGRESS default-action 'accept'
+set firewall ipv4 name V4-INGRESS description 'This is IPv4 V4-INGRESS rule set'
+set firewall ipv4 name V4-INGRESS default-log
+set firewall ipv4 name V4-INGRESS rule 101 protocol 'icmp'
+set firewall ipv4 name V4-INGRESS rule 101 description 'Rule 101 is configured by Ansible'
+set firewall ipv4 name V4-INGRESS rule 101 packet-length-exclude 100
+set firewall ipv4 name V4-INGRESS rule 101 packet-length-exclude 300
+set firewall ipv4 name V4-INGRESS rule 101 log
+set firewall ipv4 name V4-INGRESS rule 101
+set firewall ipv4 name V4-INGRESS rule 101 'disable'
+set firewall ipv4 name V4-INGRESS rule 101 action 'accept'
+set firewall ipv4 name EGRESS default-action 'reject'
+set firewall ipv6 name EGRESS default-action 'reject'
+set firewall ipv6 name EGRESS rule 20
+set firewall ipv6 name EGRESS rule 20 icmpv6 type-name 'echo-request'
+set firewall ipv6 input filter 1 jump-target 'V6-INGRESS'
+set firewall ipv6 output filter 1 jump-target 'EGRESS'
+set firewall ipv4 input filter 1 jump-target 'INGRESS'
+set firewall ipv4 output filter 1 jump-target 'EGRESS'
+set firewall ipv4 name IF-TEST rule 10 'disable'
+set firewall ipv4 name IF-TEST rule 10 action 'accept'
+set firewall ipv4 name IF-TEST rule 10 inbound-interface name 'eth0'
+set firewall ipv4 name IF-TEST rule 10 outbound-interface group 'the-ethers'
+set firewall ipv4 name IF-TEST rule 10 icmp type-name 'echo-request'
+set firewall ipv4 name IF-TEST rule 10 state 'related'
diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config.cfg
index bed0b01..175a656 100644
--- a/tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config.cfg
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_interfaces_config.cfg
@@ -3,6 +3,7 @@ set interfaces ethernet eth0 hw-id '08:00:27:7c:85:05'
set interfaces ethernet eth1 description 'test-interface'
set interfaces ethernet eth2 hw-id '08:00:27:04:85:99'
set interfaces ethernet eth3 hw-id '08:00:27:1c:82:d1'
+set interfaces ethernet eth3 disable
set interfaces ethernet eth3 description 'Ethernet 3'
set interfaces wireguard wg02 description 'wire guard int 2'
set interfaces loopback 'lo'
diff --git a/tests/unit/modules/network/vyos/fixtures/vyos_ospf_interfaces_config_14.cfg b/tests/unit/modules/network/vyos/fixtures/vyos_ospf_interfaces_config_14.cfg
new file mode 100644
index 0000000..d630d94
--- /dev/null
+++ b/tests/unit/modules/network/vyos/fixtures/vyos_ospf_interfaces_config_14.cfg
@@ -0,0 +1,4 @@
+set protocols ospfv3 interface eth0 instance-id '33'
+set protocols ospfv3 interface eth0 'mtu-ignore'
+set protocols ospf interface eth1 cost '100'
+set protocols ospfv3 interface eth1 ifmtu '33'
diff --git a/tests/unit/modules/network/vyos/test_vyos_facts.py b/tests/unit/modules/network/vyos/test_vyos_facts.py
index dd3a796..7e192e3 100644
--- a/tests/unit/modules/network/vyos/test_vyos_facts.py
+++ b/tests/unit/modules/network/vyos/test_vyos_facts.py
@@ -54,6 +54,7 @@ class TestVyosFactsModule(TestVyosModule):
"network_os_hostname": "vyos01",
"network_os_model": "VMware",
"network_os_version": "VyOS 1.1.7",
+ "network_os_major_version": "1.1",
},
"network_api": "cliconf",
}
diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_global.py b/tests/unit/modules/network/vyos/test_vyos_firewall_global.py
index 25c5632..0cc611c 100644
--- a/tests/unit/modules/network/vyos/test_vyos_firewall_global.py
+++ b/tests/unit/modules/network/vyos/test_vyos_firewall_global.py
@@ -58,6 +58,12 @@ class TestVyosFirewallRulesModule(TestVyosModule):
"ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_global.firewall_global.Firewall_globalFacts.get_device_data",
)
+ self.mock_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_global.firewall_global.get_os_version"
+ )
+ self.get_os_version = self.mock_get_os_version.start()
+ self.get_os_version.return_value = "1.2"
+
self.execute_show_command = self.mock_execute_show_command.start()
def tearDown(self):
@@ -67,6 +73,7 @@ class TestVyosFirewallRulesModule(TestVyosModule):
self.mock_get_config.stop()
self.mock_load_config.stop()
self.mock_execute_show_command.stop()
+ self.mock_get_os_version.stop()
def load_fixtures(self, commands=None, filename=None):
def load_from_file(*args, **kwargs):
@@ -89,6 +96,7 @@ class TestVyosFirewallRulesModule(TestVyosModule):
connection_type="established",
action="accept",
log=True,
+ log_level="emerg",
),
dict(connection_type="invalid", action="reject"),
],
@@ -358,5 +366,123 @@ class TestVyosFirewallRulesModule(TestVyosModule):
def test_vyos_firewall_global_set_01_deleted(self):
set_module_args(dict(config=dict(), state="deleted"))
- commands = ["delete firewall "]
+ commands = ["delete firewall"]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_global_set_01_replaced_version(self):
+ self.get_os_version.return_value = "1.4"
+ set_module_args(
+ dict(
+ config=dict(
+ validation="strict",
+ config_trap=True,
+ log_martians=True,
+ syn_cookies=True,
+ twa_hazards_protection=True,
+ ping=dict(all=True, broadcast=True),
+ state_policy=[
+ dict(
+ connection_type="established",
+ action="accept",
+ log=True,
+ ),
+ dict(connection_type="invalid", action="reject"),
+ ],
+ route_redirects=[
+ dict(
+ afi="ipv4",
+ ip_src_route=True,
+ icmp_redirects=dict(send=True, receive=False),
+ ),
+ dict(
+ afi="ipv6",
+ ip_src_route=True,
+ icmp_redirects=dict(receive=False),
+ )
+ ],
+ group=dict(
+ address_group=[
+ dict(
+ afi="ipv4",
+ name="MGMT-HOSTS",
+ description="This group has the Management hosts address lists",
+ members=[
+ dict(address="192.0.1.1"),
+ dict(address="192.0.1.3"),
+ dict(address="192.0.1.5"),
+ ],
+ ),
+ dict(
+ afi="ipv6",
+ name="GOOGLE-DNS-v6",
+ members=[
+ dict(address="2001:4860:4860::8888"),
+ dict(address="2001:4860:4860::8844"),
+ ],
+ ),
+ ],
+ network_group=[
+ dict(
+ afi="ipv4",
+ name="MGMT",
+ description="This group has the Management network addresses",
+ members=[dict(address="192.0.1.0/24")],
+ ),
+ dict(
+ afi="ipv6",
+ name="DOCUMENTATION-v6",
+ description="IPv6 Addresses reserved for documentation per RFC 3849",
+ members=[
+ dict(address="2001:0DB8::/32"),
+ dict(address="3FFF:FFFF::/32"),
+ ],
+ ),
+ ],
+ port_group=[
+ dict(
+ name="TELNET",
+ description="This group has the telnet ports",
+ members=[dict(port="23")],
+ )
+ ],
+ ),
+ ),
+ state="merged",
+ )
+ )
+ commands = [
+ "set firewall group address-group MGMT-HOSTS address 192.0.1.1",
+ "set firewall group address-group MGMT-HOSTS address 192.0.1.3",
+ "set firewall group address-group MGMT-HOSTS address 192.0.1.5",
+ "set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address lists'",
+ "set firewall group address-group MGMT-HOSTS",
+ "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8888",
+ "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8844",
+ "set firewall group ipv6-address-group GOOGLE-DNS-v6",
+ "set firewall group network-group MGMT network 192.0.1.0/24",
+ "set firewall group network-group MGMT description 'This group has the Management network addresses'",
+ "set firewall group network-group MGMT",
+ "set firewall group ipv6-network-group DOCUMENTATION-v6 network 2001:0DB8::/32",
+ "set firewall group ipv6-network-group DOCUMENTATION-v6 network 3FFF:FFFF::/32",
+ "set firewall group ipv6-network-group DOCUMENTATION-v6 description 'IPv6 Addresses reserved for documentation per RFC 3849'",
+ "set firewall group ipv6-network-group DOCUMENTATION-v6",
+ "set firewall group port-group TELNET port 23",
+ "set firewall group port-group TELNET description 'This group has the telnet ports'",
+ "set firewall group port-group TELNET",
+ "set firewall global-options ip-src-route 'enable'",
+ "set firewall global-options receive-redirects 'disable'",
+ "set firewall global-options send-redirects 'enable'",
+ "set firewall global-options config-trap 'enable'",
+ "set firewall global-options ipv6-src-route 'enable'",
+ "set firewall global-options ipv6-receive-redirects 'disable'",
+ "set firewall global-options state-policy established action 'accept'",
+ "set firewall global-options state-policy established log 'enable'",
+ "set firewall global-options state-policy invalid action 'reject'",
+ "set firewall global-options broadcast-ping 'enable'",
+ "set firewall global-options all-ping 'enable'",
+ "set firewall global-options log-martians 'enable'",
+ "set firewall global-options twa-hazards-protection 'enable'",
+ "set firewall global-options syn-cookies 'enable'",
+ "set firewall global-options source-validation 'strict'",
+ ]
self.execute_module(changed=True, commands=commands)
diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py b/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py
new file mode 100644
index 0000000..c594a1f
--- /dev/null
+++ b/tests/unit/modules/network/vyos/test_vyos_firewall_global14.py
@@ -0,0 +1,460 @@
+# (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 <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from unittest.mock import patch
+
+from ansible_collections.vyos.vyos.plugins.modules import vyos_firewall_global
+from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args
+
+from .vyos_module import TestVyosModule, load_fixture
+
+
+class TestVyosFirewallRulesModule14(TestVyosModule):
+ module = vyos_firewall_global
+
+ def setUp(self):
+ super(TestVyosFirewallRulesModule14, 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.firewall_global.firewall_global.Firewall_globalFacts.get_device_data",
+ )
+
+ self.mock_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_global.firewall_global.get_os_version"
+ )
+ self.get_os_version = self.mock_get_os_version.start()
+ self.get_os_version.return_value = "1.4"
+
+ self.execute_show_command = self.mock_execute_show_command.start()
+ self.maxDiff = None
+
+ def tearDown(self):
+ super(TestVyosFirewallRulesModule14, 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()
+ self.mock_get_os_version.stop()
+
+ def load_fixtures(self, commands=None, filename=None):
+ def load_from_file(*args, **kwargs):
+ return load_fixture("vyos_firewall_global_config_v14.cfg")
+
+ self.execute_show_command.side_effect = load_from_file
+
+ def test_vyos_firewall_global_set_01_merged(self):
+ set_module_args(
+ dict(
+ config=dict(
+ validation="strict",
+ config_trap=True,
+ log_martians=True,
+ syn_cookies=True,
+ twa_hazards_protection=True,
+ ping=dict(all=True, broadcast=True),
+ state_policy=[
+ dict(
+ connection_type="established",
+ action="accept",
+ log=True,
+ log_level="emerg",
+ ),
+ dict(connection_type="invalid", action="reject"),
+ ],
+ route_redirects=[
+ dict(
+ afi="ipv4",
+ ip_src_route=True,
+ icmp_redirects=dict(send=True, receive=False),
+ ),
+ dict(
+ afi="ipv6",
+ ip_src_route=True,
+ icmp_redirects=dict(receive=False),
+ )
+ ],
+ group=dict(
+ address_group=[
+ dict(
+ afi="ipv4",
+ name="MGMT-HOSTS",
+ description="This group has the Management hosts address lists",
+ members=[
+ dict(address="192.0.1.1"),
+ dict(address="192.0.1.3"),
+ dict(address="192.0.1.5"),
+ ],
+ ),
+ dict(
+ afi="ipv6",
+ name="GOOGLE-DNS-v6",
+ members=[
+ dict(address="2001:4860:4860::8888"),
+ dict(address="2001:4860:4860::8844"),
+ ],
+ ),
+ ],
+ network_group=[
+ dict(
+ afi="ipv4",
+ name="MGMT",
+ description="This group has the Management network addresses",
+ members=[dict(address="192.0.1.0/24")],
+ ),
+ dict(
+ afi="ipv6",
+ name="DOCUMENTATION-v6",
+ description="IPv6 Addresses reserved for documentation per RFC 3849",
+ members=[
+ dict(address="2001:0DB8::/32"),
+ dict(address="3FFF:FFFF::/32"),
+ ],
+ ),
+ ],
+ port_group=[
+ dict(
+ name="TELNET",
+ description="This group has the telnet ports",
+ members=[dict(port="23")],
+ )
+ ],
+ ),
+ ),
+ state="merged",
+ )
+ )
+ commands = [
+ "set firewall group address-group MGMT-HOSTS address 192.0.1.1",
+ "set firewall group address-group MGMT-HOSTS address 192.0.1.3",
+ "set firewall group address-group MGMT-HOSTS address 192.0.1.5",
+ "set firewall group address-group MGMT-HOSTS description 'This group has the Management hosts address lists'",
+ "set firewall group address-group MGMT-HOSTS",
+ "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8888",
+ "set firewall group ipv6-address-group GOOGLE-DNS-v6 address 2001:4860:4860::8844",
+ "set firewall group ipv6-address-group GOOGLE-DNS-v6",
+ "set firewall group network-group MGMT network 192.0.1.0/24",
+ "set firewall group network-group MGMT description 'This group has the Management network addresses'",
+ "set firewall group network-group MGMT",
+ "set firewall group ipv6-network-group DOCUMENTATION-v6 network 2001:0DB8::/32",
+ "set firewall group ipv6-network-group DOCUMENTATION-v6 network 3FFF:FFFF::/32",
+ "set firewall group ipv6-network-group DOCUMENTATION-v6 description 'IPv6 Addresses reserved for documentation per RFC 3849'",
+ "set firewall group ipv6-network-group DOCUMENTATION-v6",
+ "set firewall group port-group TELNET port 23",
+ "set firewall group port-group TELNET description 'This group has the telnet ports'",
+ "set firewall group port-group TELNET",
+ "set firewall global-options ip-src-route 'enable'",
+ "set firewall global-options receive-redirects 'disable'",
+ "set firewall global-options send-redirects 'enable'",
+ "set firewall global-options config-trap 'enable'",
+ "set firewall global-options ipv6-src-route 'enable'",
+ "set firewall global-options ipv6-receive-redirects 'disable'",
+ "set firewall global-options state-policy established action 'accept'",
+ "set firewall global-options state-policy established log 'enable'",
+ "set firewall global-options state-policy established log-level 'emerg'",
+ "set firewall global-options state-policy invalid action 'reject'",
+ "set firewall global-options broadcast-ping 'enable'",
+ "set firewall global-options log-martians 'enable'",
+ "set firewall global-options twa-hazards-protection 'enable'",
+ "set firewall global-options syn-cookies 'enable'",
+ "set firewall global-options source-validation 'strict'",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_global_set_01_merged_idem(self):
+ set_module_args(
+ dict(
+ config=dict(
+ group=dict(
+ address_group=[
+ dict(
+ afi="ipv4",
+ name="RND-HOSTS",
+ description="This group has the Management hosts address lists",
+ members=[
+ dict(address="192.0.2.1"),
+ dict(address="192.0.2.3"),
+ dict(address="192.0.2.5"),
+ ],
+ ),
+ dict(
+ afi="ipv6",
+ name="LOCAL-v6",
+ description="This group has the hosts address lists of this machine",
+ members=[
+ dict(address="::1"),
+ dict(address="fdec:2503:89d6:59b3::1"),
+ ],
+ ),
+ ],
+ network_group=[
+ dict(
+ afi="ipv4",
+ name="RND",
+ description="This group has the Management network addresses",
+ members=[dict(address="192.0.2.0/24")],
+ ),
+ dict(
+ afi="ipv6",
+ name="UNIQUE-LOCAL-v6",
+ description="This group encompasses the ULA address space in IPv6",
+ members=[dict(address="fc00::/7")],
+ ),
+ ],
+ port_group=[
+ dict(
+ name="SSH",
+ description="This group has the ssh ports",
+ members=[dict(port="22")],
+ ),
+ ],
+ ),
+ ),
+ state="merged",
+ ),
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_firewall_global_set_01_replaced(self):
+ set_module_args(
+ dict(
+ config=dict(
+ state_policy=[
+ dict(connection_type="invalid", action="reject"),
+ ],
+ group=dict(
+ address_group=[
+ dict(
+ afi="ipv4",
+ name="RND-HOSTS",
+ description="This group has the Management hosts address lists",
+ members=[
+ dict(address="192.0.2.1"),
+ dict(address="192.0.2.7"),
+ dict(address="192.0.2.9"),
+ ],
+ ),
+ dict(
+ afi="ipv6",
+ name="LOCAL-v6",
+ description="This group has the hosts address lists of this machine",
+ members=[
+ dict(address="::1"),
+ dict(address="fdec:2503:89d6:59b3::2"),
+ ],
+ ),
+ ],
+ network_group=[
+ dict(
+ afi="ipv4",
+ name="RND",
+ description="This group has the Management network addresses",
+ members=[dict(address="192.0.2.0/24")],
+ ),
+ dict(
+ afi="ipv6",
+ name="UNIQUE-LOCAL-v6",
+ description="This group encompasses the ULA address space in IPv6",
+ members=[dict(address="fc00::/7")],
+ ),
+ ],
+ port_group=[
+ dict(
+ name="SSH",
+ description="This group has the ssh ports",
+ members=[dict(port="2222")],
+ ),
+ ],
+ ),
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete firewall group address-group RND-HOSTS address 192.0.2.3",
+ "delete firewall group address-group RND-HOSTS address 192.0.2.5",
+ "delete firewall global-options all-ping",
+ "delete firewall global-options state-policy related",
+ "set firewall global-options state-policy invalid action 'reject'",
+ "set firewall group address-group RND-HOSTS address 192.0.2.7",
+ "set firewall group address-group RND-HOSTS address 192.0.2.9",
+ "delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1",
+ "set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2",
+ "delete firewall group port-group SSH port 22",
+ "set firewall group port-group SSH port 2222",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_global_set_01_replaced_idem(self):
+ set_module_args(
+ dict(
+ config=dict(
+ ping=dict(all=True),
+ state_policy=[
+ dict(connection_type="related", action="accept", log_level="alert"),
+ ],
+ group=dict(
+ address_group=[
+ dict(
+ afi="ipv4",
+ name="RND-HOSTS",
+ description="This group has the Management hosts address lists",
+ members=[
+ dict(address="192.0.2.1"),
+ dict(address="192.0.2.3"),
+ dict(address="192.0.2.5"),
+ ],
+ ),
+ dict(
+ afi="ipv6",
+ name="LOCAL-v6",
+ description="This group has the hosts address lists of this machine",
+ members=[
+ dict(address="::1"),
+ dict(address="fdec:2503:89d6:59b3::1"),
+ ],
+ ),
+ ],
+ network_group=[
+ dict(
+ afi="ipv4",
+ name="RND",
+ description="This group has the Management network addresses",
+ members=[dict(address="192.0.2.0/24")],
+ ),
+ dict(
+ afi="ipv6",
+ name="UNIQUE-LOCAL-v6",
+ description="This group encompasses the ULA address space in IPv6",
+ members=[dict(address="fc00::/7")],
+ ),
+ ],
+ port_group=[
+ dict(
+ name="SSH",
+ description="This group has the ssh ports",
+ members=[dict(port="22")],
+ ),
+ ],
+ ),
+ ),
+ state="replaced",
+ ),
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_firewall_global_set_02_replaced(self):
+ set_module_args(
+ dict(
+ config=dict(
+ state_policy=[
+ dict(connection_type="invalid", action="reject"),
+ dict(connection_type="related", action="drop"),
+ ],
+ group=dict(
+ address_group=[
+ dict(
+ afi="ipv4",
+ name="RND-HOSTS",
+ description="This group has the Management hosts address lists",
+ members=[
+ dict(address="192.0.2.1"),
+ dict(address="192.0.2.7"),
+ dict(address="192.0.2.9"),
+ ],
+ ),
+ dict(
+ afi="ipv6",
+ name="LOCAL-v6",
+ description="This group has the hosts address lists of this machine",
+ members=[
+ dict(address="::1"),
+ dict(address="fdec:2503:89d6:59b3::2"),
+ ],
+ ),
+ ],
+ network_group=[
+ dict(
+ afi="ipv4",
+ name="RND",
+ description="This group has the Management network addresses",
+ members=[dict(address="192.0.2.0/24")],
+ ),
+ dict(
+ afi="ipv6",
+ name="UNIQUE-LOCAL-v6",
+ description="This group encompasses the ULA address space in IPv6",
+ members=[dict(address="fc00::/7")],
+ ),
+ ],
+ port_group=[
+ dict(
+ name="SSH",
+ description="This group has the ssh ports",
+ members=[dict(port="2222")],
+ ),
+ ],
+ ),
+ ),
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete firewall group address-group RND-HOSTS address 192.0.2.3",
+ "delete firewall group address-group RND-HOSTS address 192.0.2.5",
+ "delete firewall global-options all-ping",
+ "set firewall global-options state-policy related action 'drop'",
+ "delete firewall global-options state-policy related log-level",
+ "set firewall global-options state-policy invalid action 'reject'",
+ "set firewall group address-group RND-HOSTS address 192.0.2.7",
+ "set firewall group address-group RND-HOSTS address 192.0.2.9",
+ "delete firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::1",
+ "set firewall group ipv6-address-group LOCAL-v6 address fdec:2503:89d6:59b3::2",
+ "delete firewall group port-group SSH port 22",
+ "set firewall group port-group SSH port 2222",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_global_set_01_deleted(self):
+ set_module_args(dict(config=dict(), state="deleted"))
+ commands = ["delete firewall global-options"]
+ self.execute_module(changed=True, commands=commands)
diff --git a/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py b/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py
index b43b11c..c0815bf 100644
--- a/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py
+++ b/tests/unit/modules/network/vyos/test_vyos_firewall_rules.py
@@ -63,10 +63,10 @@ class TestVyosFirewallRulesModule(TestVyosModule):
self.execute_show_command = self.mock_execute_show_command.start()
self.mock_get_os_version = patch(
- "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules.Firewall_rules._get_os_version",
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules.get_os_version",
)
self.get_os_version = self.mock_get_os_version.start()
- self.get_os_version.return_value = "Vyos 1.2"
+ self.get_os_version.return_value = "1.2"
def tearDown(self):
super(TestVyosFirewallRulesModule, self).tearDown()
@@ -374,7 +374,12 @@ class TestVyosFirewallRulesModule(TestVyosModule):
weekdays="!Sat,Sun",
utc=True,
),
- tcp=dict(flags="ALL"),
+ tcp=dict(
+ flags=[
+ dict(flag="all"),
+ ]
+ ),
+
),
],
),
@@ -416,7 +421,7 @@ class TestVyosFirewallRulesModule(TestVyosModule):
description="Rule 101 is configured by Ansible",
ipsec="match-ipsec",
protocol="icmp",
- disabled=True,
+ disable=True,
icmp=dict(type_name="echo-request"),
),
],
@@ -566,8 +571,22 @@ class TestVyosFirewallRulesModule(TestVyosModule):
weekdays="!Sat,Sun",
utc=True,
),
- tcp=dict(flags="ALL"),
+ tcp=dict(
+ flags=[
+ dict(flag="all"),
+ ]
+ ),
),
+ dict(
+ number="102",
+ tcp=dict(
+ flags=[
+ dict(flag="ack"),
+ dict(flag="syn"),
+ dict(flag="fin", invert=True),
+ ],
+ )
+ )
],
),
],
@@ -586,6 +605,8 @@ class TestVyosFirewallRulesModule(TestVyosModule):
"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",
+ "set firewall ipv6-name INBOUND rule 102",
+ "set firewall ipv6-name INBOUND rule 102 tcp flags ACK,SYN,!FIN",
]
self.execute_module(changed=True, commands=commands)
@@ -743,14 +764,14 @@ class TestVyosFirewallRulesModule(TestVyosModule):
ipsec="match-ipsec",
protocol="tcp",
fragment="match-frag",
- disabled=False,
+ disable=False,
),
dict(
number="102",
action="accept",
description="Rule 102 is configured by Ansible RM",
protocol="icmp",
- disabled=True,
+ disable=True,
),
],
),
@@ -787,6 +808,7 @@ class TestVyosFirewallRulesModule(TestVyosModule):
"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'",
+ "delete firewall name V4-INGRESS rule 101 log",
"set firewall name V4-INGRESS rule 102 disable",
"set firewall name V4-INGRESS rule 102 action 'accept'",
"set firewall name V4-INGRESS rule 102 protocol 'icmp'",
@@ -817,7 +839,7 @@ class TestVyosFirewallRulesModule(TestVyosModule):
ipsec="match-ipsec",
protocol="icmp",
fragment="match-frag",
- disabled=True,
+ disable=True,
),
],
),
@@ -848,6 +870,7 @@ class TestVyosFirewallRulesModule(TestVyosModule):
)
commands = [
"delete firewall name V4-INGRESS enable-default-log",
+ "delete firewall name V4-INGRESS rule 101 log",
]
self.execute_module(changed=True, commands=commands)
@@ -871,8 +894,9 @@ class TestVyosFirewallRulesModule(TestVyosModule):
ipsec="match-ipsec",
protocol="icmp",
fragment="match-frag",
- disabled=True,
- ),
+ disable=True,
+ log="enable",
+ )
],
),
dict(
@@ -926,7 +950,8 @@ class TestVyosFirewallRulesModule(TestVyosModule):
ipsec="match-ipsec",
protocol="icmp",
fragment="match-frag",
- disabled=True,
+ disable=True,
+ log="enable"
),
],
),
@@ -958,8 +983,8 @@ class TestVyosFirewallRulesModule(TestVyosModule):
ipsec="match-ipsec",
protocol="icmp",
fragment="match-frag",
- disabled=True,
- ),
+ disable=True,
+ )
],
),
dict(
@@ -1014,7 +1039,7 @@ class TestVyosFirewallRulesModule(TestVyosModule):
log="enable",
protocol="tcp",
fragment="match-frag",
- disabled=False,
+ disable=False,
source=dict(
group=dict(
address_group="IN-ADDR-GROUP",
@@ -1028,7 +1053,7 @@ class TestVyosFirewallRulesModule(TestVyosModule):
action="accept",
description="Rule 102 is configured by Ansible RM",
protocol="icmp",
- disabled=True,
+ disable=True,
),
],
),
@@ -1103,8 +1128,9 @@ class TestVyosFirewallRulesModule(TestVyosModule):
ipsec="match-ipsec",
protocol="icmp",
fragment="match-frag",
- disabled=True,
- ),
+ disable=True,
+ log="enable",
+ )
],
),
dict(
@@ -1139,7 +1165,7 @@ class TestVyosFirewallRulesModule(TestVyosModule):
self.execute_module(changed=False, commands=[])
def test_vyos_firewall_v6_rule_sets_rule_merged_01_version(self):
- self.get_os_version.return_value = "VyOS 1.4-rolling-202007010117"
+ self.get_os_version.return_value = "1.4"
set_module_args(
dict(
config=[
@@ -1158,8 +1184,16 @@ class TestVyosFirewallRulesModule(TestVyosModule):
description="Rule 101 is configured by Ansible",
ipsec="match-ipsec",
protocol="icmp",
- disabled=True,
+ disable=True,
icmp=dict(type_name="echo-request"),
+ log="enable",
+ ),
+ dict(
+ number="102",
+ action="reject",
+ description="Rule 102 is configured by Ansible",
+ protocol="ipv6-icmp",
+ icmp=dict(type=7),
),
],
),
@@ -1170,15 +1204,503 @@ class TestVyosFirewallRulesModule(TestVyosModule):
),
)
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 disable",
- "set firewall ipv6-name INBOUND rule 101 action 'accept'",
- "set firewall ipv6-name INBOUND rule 101 ipsec 'match-ipsec'",
- "set firewall ipv6-name INBOUND rule 101 icmpv6 type-name echo-request",
+ "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 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 disable",
+ "set firewall ipv6 name INBOUND rule 101 action 'accept'",
+ "set firewall ipv6 name INBOUND rule 101 ipsec 'match-ipsec'",
+ "set firewall ipv6 name INBOUND rule 101 icmpv6 type-name echo-request",
+ "set firewall ipv6 name INBOUND rule 101 log 'enable'",
+ "set firewall ipv6 name INBOUND rule 102",
+ "set firewall ipv6 name INBOUND rule 102 action 'reject'",
+ "set firewall ipv6 name INBOUND rule 102 description 'Rule 102 is configured by Ansible'",
+ "set firewall ipv6 name INBOUND rule 102 protocol 'ipv6-icmp'",
+ 'set firewall ipv6 name INBOUND rule 102 icmpv6 type 7',
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_jump_rules_merged_01(self):
+ self.get_os_version.return_value = "1.4"
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ afi="ipv6",
+ rule_sets=[
+ dict(
+ name="INBOUND",
+ description="This is IPv6 INBOUND rule set with a jump action",
+ default_action="accept",
+ enable_default_log=True,
+ rules=[
+ dict(
+ number="101",
+ action="jump",
+ description="Rule 101 is configured by Ansible",
+ ipsec="match-ipsec",
+ protocol="icmp",
+ icmp=dict(type_name="echo-request"),
+ jump_target="PROTECT-RE",
+ packet_length_exclude=[dict(length=100), dict(length=200)]
+ ),
+ dict(
+ number="102",
+ action="reject",
+ description="Rule 102 is configured by Ansible",
+ protocol="ipv6-icmp",
+ icmp=dict(type=7),
+ ),
+ ],
+ ),
+ ],
+ )
+ ],
+ state="merged",
+ )
+ )
+ commands = [
+ "set firewall ipv6 name INBOUND default-action 'accept'",
+ "set firewall ipv6 name INBOUND description 'This is IPv6 INBOUND rule set with a jump action'",
+ "set firewall ipv6 name INBOUND default-log",
+ "set firewall ipv6 name INBOUND rule 101 protocol 'icmp'",
+ "set firewall ipv6 name INBOUND rule 101 packet-length-exclude 100",
+ "set firewall ipv6 name INBOUND rule 101 packet-length-exclude 200",
+ "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 ipsec 'match-ipsec'",
+ "set firewall ipv6 name INBOUND rule 101 icmpv6 type-name echo-request",
+ "set firewall ipv6 name INBOUND rule 101 action 'jump'",
+ "set firewall ipv6 name INBOUND rule 101 jump-target 'PROTECT-RE'",
+ "set firewall ipv6 name INBOUND rule 102",
+ "set firewall ipv6 name INBOUND rule 102 action 'reject'",
+ "set firewall ipv6 name INBOUND rule 102 description 'Rule 102 is configured by Ansible'",
+ "set firewall ipv6 name INBOUND rule 102 protocol 'ipv6-icmp'",
+ 'set firewall ipv6 name INBOUND rule 102 icmpv6 type 7',
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+
+class TestVyosFirewallRulesModule14(TestVyosModule):
+ module = vyos_firewall_rules
+
+ def setUp(self):
+ super(TestVyosFirewallRulesModule14, 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()
+
+ self.mock_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.firewall_rules.firewall_rules.get_os_version"
+ )
+ self.get_os_version = self.mock_get_os_version.start()
+ self.get_os_version.return_value = "1.4"
+ self.maxDiff = None
+
+ def tearDown(self):
+ super(TestVyosFirewallRulesModule14, 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()
+ self.mock_get_os_version.stop()
+
+ def load_fixtures(self, commands=None, filename=None):
+ def load_from_file(*args, **kwargs):
+ return load_fixture("vyos_firewall_rules_config_v14.cfg")
+
+ self.execute_show_command.side_effect = load_from_file
+
+ def test_vyos_firewall_packet_length_merged_01(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ afi="ipv6",
+ rule_sets=[
+ dict(
+ name="INBOUND",
+ description="This is IPv6 INBOUND rule set with a jump action",
+ default_action="accept",
+ enable_default_log=True,
+ rules=[
+ dict(
+ number="101",
+ action="jump",
+ description="Rule 101 is configured by Ansible",
+ jump_target="PROTECT-RE",
+ packet_length_exclude=[dict(length=100), dict(length=200)],
+ packet_length=[dict(length=22)]
+ ),
+ ],
+ ),
+ ],
+ )
+ ],
+ state="merged",
+ )
+ )
+ commands = [
+ "set firewall ipv6 name INBOUND default-action 'accept'",
+ "set firewall ipv6 name INBOUND description 'This is IPv6 INBOUND rule set with a jump action'",
+ "set firewall ipv6 name INBOUND default-log",
+ "set firewall ipv6 name INBOUND rule 101 packet-length-exclude 100",
+ "set firewall ipv6 name INBOUND rule 101 packet-length-exclude 200",
+ "set firewall ipv6 name INBOUND rule 101 packet-length 22",
+ "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 action 'jump'",
+ "set firewall ipv6 name INBOUND rule 101 jump-target 'PROTECT-RE'",
+ ]
+ self.maxDiff = None
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_packet_length_replace_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",
+ packet_length_exclude=[dict(length=100), dict(length=200)],
+ packet_length=[dict(length=22)]
+ ),
+ ],
+ ),
+ ],
+ )
+ ],
+ state="replaced",
+ )
+ )
+ commands = [
+ "delete firewall ipv4 name V4-INGRESS rule 101 protocol",
+ "delete firewall ipv4 name V4-INGRESS rule 101 disable",
+ "delete firewall ipv4 name V4-INGRESS rule 101 packet-length-exclude 300",
+ "set firewall ipv4 name V4-INGRESS rule 101 packet-length-exclude 200",
+ "set firewall ipv4 name V4-INGRESS rule 101 packet-length 22",
+ ]
+ self.maxDiff = None
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_filter_merged_01(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ afi="ipv6",
+ rule_sets=[
+ dict(
+ filter="input",
+ description="This is IPv6 INBOUND rule set with a jump action",
+ default_action="accept",
+ enable_default_log=True,
+ rules=[
+ dict(
+ number="101",
+ action="jump",
+ description="Rule 101 is configured by Ansible",
+ jump_target="PROTECT-RE",
+ packet_length_exclude=[dict(length=100), dict(length=200)],
+ packet_length=[dict(length=22)]
+ ),
+ ],
+ ),
+ ],
+ )
+ ],
+ state="merged",
+ )
+ )
+ commands = [
+ "set firewall ipv6 input filter default-action 'accept'",
+ "set firewall ipv6 input filter description 'This is IPv6 INBOUND rule set with a jump action'",
+ "set firewall ipv6 input filter default-log",
+ "set firewall ipv6 input filter rule 101 packet-length-exclude 100",
+ "set firewall ipv6 input filter rule 101 packet-length-exclude 200",
+ "set firewall ipv6 input filter rule 101 packet-length 22",
+ "set firewall ipv6 input filter rule 101 description 'Rule 101 is configured by Ansible'",
+ "set firewall ipv6 input filter rule 101",
+ "set firewall ipv6 input filter rule 101 action 'jump'",
+ "set firewall ipv6 input filter rule 101 jump-target 'PROTECT-RE'",
+ ]
+ self.maxDiff = None
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_interface_merged_01(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ afi="ipv6",
+ rule_sets=[
+ dict(
+ name="V6-INGRESS",
+ description="This is IPv6 INBOUND rule set with a jump action",
+ default_action="accept",
+ rules=[
+ dict(
+ number="101",
+ action="jump",
+ description="Rule 101 is configured by Ansible",
+ jump_target="PROTECT-RE",
+ inbound_interface=dict(name="eth0"),
+ outbound_interface=dict(group="eth1"),
+ ),
+ ],
+ ),
+ ],
+ )
+ ],
+ state="merged",
+ )
+ )
+ commands = [
+ "set firewall ipv6 name V6-INGRESS description 'This is IPv6 INBOUND rule set with a jump action'",
+ "set firewall ipv6 name V6-INGRESS rule 101 inbound-interface name eth0",
+ "set firewall ipv6 name V6-INGRESS rule 101 outbound-interface group eth1",
+ "set firewall ipv6 name V6-INGRESS rule 101 description 'Rule 101 is configured by Ansible'",
+ "set firewall ipv6 name V6-INGRESS rule 101",
+ "set firewall ipv6 name V6-INGRESS rule 101 action 'jump'",
+ "set firewall ipv6 name V6-INGRESS rule 101 jump-target 'PROTECT-RE'",
+ ]
+ self.maxDiff = None
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_interface_replace_02(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ afi="ipv4",
+ rule_sets=[
+ dict(
+ name="IF-TEST",
+ description="Changed",
+ rules=[
+ dict(
+ number="10",
+ action="accept",
+ description="Rule 10 is configured by Ansible",
+ inbound_interface=dict(name="eth1"),
+ ),
+ ],
+ ),
+ ],
+ )
+ ],
+ state="replaced",
+ )
+ )
+ commands = [
+ "set firewall ipv4 name IF-TEST description 'Changed'",
+ "set firewall ipv4 name IF-TEST rule 10 description 'Rule 10 is configured by Ansible'",
+ 'set firewall ipv4 name IF-TEST rule 10 inbound-interface name eth1',
+ "delete firewall ipv4 name IF-TEST rule 10 outbound-interface group",
+ "delete firewall ipv4 name IF-TEST rule 10 disable",
+ "delete firewall ipv4 name IF-TEST rule 10 state related",
+ "delete firewall ipv4 name IF-TEST rule 10 icmp type-name echo-request",
+ ]
+ self.maxDiff = None
+ 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 ipv4 name INBOUND rule 101 protocol 'tcp'",
+ "set firewall ipv4 name INBOUND rule 101 destination port 2124",
+ "set firewall ipv4 name INBOUND rule 101",
+ "set firewall ipv4 name INBOUND rule 101 destination address 192.0.1.0",
+ "set firewall ipv4 name INBOUND rule 101 source address 192.0.2.0",
+ "set firewall ipv4 name INBOUND rule 101 source mac-address 38:00:25:19:76:0c",
+ "set firewall ipv4 name INBOUND rule 101 source port 2127",
+ "set firewall ipv4 name INBOUND rule 101 state new",
+ "set firewall ipv4 name INBOUND rule 101 state invalid",
+ "set firewall ipv4 name INBOUND rule 101 state related",
+ "set firewall ipv4 name INBOUND rule 101 state established",
+ "set firewall ipv4 name INBOUND rule 101 limit burst 10",
+ "set firewall ipv4 name INBOUND rule 101 limit rate 20/second",
+ "set firewall ipv4 name INBOUND rule 101 recent count 10",
+ "set firewall ipv4 name INBOUND rule 101 recent time 20",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_firewall_v4_rule_sets_change_state_01(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ afi="ipv4",
+ rule_sets=[
+ dict(
+ name="IF-TEST",
+ rules=[
+ dict(
+ number="10",
+ disable=False,
+ action="accept",
+ state=dict(
+ established=True,
+ new=True,
+ ),
+ ),
+ ],
+ ),
+ ],
+ ),
+ ],
+ state="replaced",
+ ),
+ )
+ commands = [
+ "delete firewall ipv4 name IF-TEST rule 10 disable",
+ "delete firewall ipv4 name IF-TEST rule 10 inbound-interface name",
+ "delete firewall ipv4 name IF-TEST rule 10 icmp type-name echo-request",
+ "delete firewall ipv4 name IF-TEST rule 10 outbound-interface group",
+ "delete firewall ipv4 name IF-TEST rule 10 state related",
+ "set firewall ipv4 name IF-TEST rule 10 state established",
+ "set firewall ipv4 name IF-TEST rule 10 state new",
+ ]
+ 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 ipv4", "delete firewall ipv6"]
+ 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=[
+ dict(flag="all"),
+ ]
+ ),
+ ),
+ dict(
+ number="102",
+ tcp=dict(
+ flags=[
+ dict(flag="ack"),
+ dict(flag="syn"),
+ dict(flag="fin", invert=True),
+ ],
+ )
+ )
+ ],
+ ),
+ ],
+ ),
+ ],
+ 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",
+ "set firewall ipv6 name INBOUND rule 102",
+ "set firewall ipv6 name INBOUND rule 102 tcp flags ack",
+ "set firewall ipv6 name INBOUND rule 102 tcp flags not fin",
+ "set firewall ipv6 name INBOUND rule 102 tcp flags syn",
]
self.execute_module(changed=True, commands=commands)
diff --git a/tests/unit/modules/network/vyos/test_vyos_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_interfaces.py
index affb4f8..14e49c3 100644
--- a/tests/unit/modules/network/vyos/test_vyos_interfaces.py
+++ b/tests/unit/modules/network/vyos/test_vyos_interfaces.py
@@ -183,5 +183,40 @@ class TestVyosFirewallInterfacesModule(TestVyosModule):
"set interfaces ethernet eth4 speed 'auto'",
"delete interfaces wireguard wg02 description",
"delete interfaces ethernet eth3 description",
+ "delete interfaces ethernet eth3 disable",
]
self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_interfaces_idempotent_disable(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth3",
+ description="Ethernet 3",
+ enabled=False,
+ ),
+ ],
+ state="merged",
+ )
+ )
+
+ commands = []
+ self.execute_module(changed=False, commands=commands)
+
+ def test_vyos_interfaces_idempotent_disable_replace(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth3",
+ description="Ethernet 3",
+ enabled=False,
+ ),
+ ],
+ state="replaced",
+ )
+ )
+
+ commands = []
+ self.execute_module(changed=False, commands=commands)
diff --git a/tests/unit/modules/network/vyos/test_vyos_logging_global.py b/tests/unit/modules/network/vyos/test_vyos_logging_global.py
index 872769e..a675151 100644
--- a/tests/unit/modules/network/vyos/test_vyos_logging_global.py
+++ b/tests/unit/modules/network/vyos/test_vyos_logging_global.py
@@ -386,7 +386,6 @@ class TestVyosLoggingGlobalModule(TestVyosModule):
playbook["state"] = "overridden"
set_module_args(playbook)
result = self.execute_module(changed=True)
- print(result["commands"])
self.maxDiff = None
self.assertEqual(sorted(result["commands"]), sorted(compare_cmds))
diff --git a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py
index 1d12a3c..c7d69d0 100644
--- a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py
+++ b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces.py
@@ -43,11 +43,25 @@ class TestVyosOspfInterfacesModule(TestVyosModule):
"ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.Ospf_interfacesFacts.get_device_data",
)
self.execute_show_command = self.mock_execute_show_command.start()
+ self.mock_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospf_interfaces.ospf_interfaces.get_os_version"
+ )
+ self.test_version = "1.2"
+ self.get_os_version = self.mock_get_os_version.start()
+ self.get_os_version.return_value = self.test_version
+ self.mock_facts_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.get_os_version"
+ )
+ self.get_facts_os_version = self.mock_facts_get_os_version.start()
+ self.get_facts_os_version.return_value = self.test_version
+ self.maxDiff = None
def tearDown(self):
super(TestVyosOspfInterfacesModule, self).tearDown()
self.mock_get_resource_connection_config.stop()
self.mock_execute_show_command.stop()
+ self.mock_get_os_version.stop()
+ self.mock_facts_get_os_version.stop()
def load_fixtures(self, commands=None, filename=None):
if filename is None:
diff --git a/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py
new file mode 100644
index 0000000..ef27860
--- /dev/null
+++ b/tests/unit/modules/network/vyos/test_vyos_ospf_interfaces14.py
@@ -0,0 +1,511 @@
+# Spawned from test_vyos_ospf_interfaces (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 <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from unittest.mock import patch
+
+from ansible_collections.vyos.vyos.plugins.modules import vyos_ospf_interfaces
+from ansible_collections.vyos.vyos.tests.unit.modules.utils import set_module_args
+
+from .vyos_module import TestVyosModule, load_fixture
+
+
+class TestVyosOspfInterfacesModule14(TestVyosModule):
+ module = vyos_ospf_interfaces
+
+ def setUp(self):
+ super(TestVyosOspfInterfacesModule14, self).setUp()
+ self.mock_get_resource_connection_config = patch(
+ "ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module_base.get_resource_connection"
+ )
+ self.get_resource_connection_config = self.mock_get_resource_connection_config.start()
+
+ self.mock_execute_show_command = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.Ospf_interfacesFacts.get_device_data"
+ )
+ self.execute_show_command = self.mock_execute_show_command.start()
+ self.mock_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospf_interfaces.ospf_interfaces.get_os_version"
+ )
+ self.test_version = "1.4"
+ self.get_os_version = self.mock_get_os_version.start()
+ self.get_os_version.return_value = self.test_version
+ self.mock_facts_get_os_version = patch(
+ "ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospf_interfaces.ospf_interfaces.get_os_version"
+ )
+ self.get_facts_os_version = self.mock_facts_get_os_version.start()
+ self.get_facts_os_version.return_value = self.test_version
+ self.maxDiff = None
+
+ def tearDown(self):
+ super(TestVyosOspfInterfacesModule14, self).tearDown()
+ self.mock_get_resource_connection_config.stop()
+ self.mock_execute_show_command.stop()
+ self.mock_get_os_version.stop()
+
+ def load_fixtures(self, commands=None, filename=None):
+ if filename is None:
+ filename = "vyos_ospf_interfaces_config_14.cfg"
+
+ def load_from_file(*args, **kwargs):
+ output = load_fixture(filename)
+ return output
+
+ self.execute_show_command.side_effect = load_from_file
+
+ def sort_address_family(self, entry_list):
+ for entry in entry_list:
+ if entry.get("address_family"):
+ entry["address_family"].sort(key=lambda i: i.get("afi"))
+
+ def test_vyos_ospf_interfaces_merged_new_config(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth0",
+ address_family=[
+ dict(
+ afi="ipv4",
+ cost=100,
+ authentication=dict(plaintext_password="abcdefg!"),
+ priority=55,
+ ),
+ dict(afi="ipv6", mtu_ignore=True, instance=20),
+ ],
+ ),
+ dict(
+ name="bond2",
+ address_family=[
+ dict(
+ afi="ipv4",
+ transmit_delay=9,
+ ),
+ dict(afi="ipv6", passive=True),
+ ],
+ ),
+ ],
+ state="merged",
+ )
+ )
+ commands = [
+ "set protocols ospf interface bond2 transmit-delay 9",
+ "set protocols ospfv3 interface bond2 passive",
+ "set protocols ospf interface eth0 cost 100",
+ "set protocols ospf interface eth0 priority 55",
+ "set protocols ospf interface eth0 authentication plaintext-password abcdefg!",
+ "set protocols ospfv3 interface eth0 instance-id 20",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_ospf_interfaces_merged_idempotent(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth0",
+ address_family=[
+ dict(afi="ipv6", mtu_ignore=True, instance=33),
+ ],
+ ),
+ dict(
+ name="eth1",
+ address_family=[
+ dict(
+ afi="ipv4",
+ cost=100,
+ ),
+ dict(afi="ipv6", ifmtu=33),
+ ],
+ ),
+ ],
+ )
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_ospf_interfaces_existing_config_merged(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth0",
+ address_family=[
+ dict(afi="ipv6", cost=500),
+ ],
+ ),
+ dict(
+ name="eth1",
+ address_family=[
+ dict(
+ afi="ipv4",
+ priority=100,
+ ),
+ dict(afi="ipv6", ifmtu=25),
+ ],
+ ),
+ ],
+ )
+ )
+ commands = [
+ "set protocols ospfv3 interface eth0 cost 500",
+ "set protocols ospf interface eth1 priority 100",
+ "set protocols ospfv3 interface eth1 ifmtu 25",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_ospf_interfaces_replaced(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth0",
+ address_family=[
+ dict(
+ afi="ipv4",
+ cost=100,
+ authentication=dict(plaintext_password="abcdefg!"),
+ priority=55,
+ ),
+ ],
+ ),
+ dict(
+ name="bond2",
+ address_family=[
+ dict(
+ afi="ipv4",
+ transmit_delay=9,
+ ),
+ dict(afi="ipv6", passive=True),
+ ],
+ ),
+ ],
+ state="replaced",
+ )
+ )
+ commands = [
+ "set protocols ospf interface bond2 transmit-delay 9",
+ "set protocols ospfv3 interface bond2 passive",
+ "set protocols ospf interface eth0 cost 100",
+ "set protocols ospf interface eth0 priority 55",
+ "set protocols ospf interface eth0 authentication plaintext-password abcdefg!",
+ "delete protocols ospfv3 interface eth0 instance-id 33",
+ "delete protocols ospfv3 interface eth0 mtu-ignore",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_ospf_passive_interfaces_replaced(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth0",
+ address_family=[
+ dict(
+ afi="ipv4",
+ passive=True,
+ ),
+ ],
+ ),
+ dict(
+ name="eth1",
+ address_family=[
+ dict(
+ afi="ipv4",
+ passive=True,
+ ),
+ dict(
+ afi="ipv6",
+ passive=True,
+ ),
+ ],
+ ),
+ dict(
+ name="bond2",
+ address_family=[
+ dict(
+ afi="ipv4",
+ passive=True,
+ ),
+ dict(afi="ipv6", passive=True),
+ ],
+ ),
+ ],
+ state="replaced",
+ )
+ )
+ commands = [
+ "delete protocols ospf interface eth1 cost 100",
+ "delete protocols ospfv3 interface eth0 instance-id 33",
+ "delete protocols ospfv3 interface eth0 mtu-ignore",
+ "delete protocols ospfv3 interface eth1 ifmtu 33",
+ "set protocols ospf interface bond2 passive",
+ "set protocols ospfv3 interface bond2 passive",
+ "set protocols ospf interface eth0 passive",
+ "set protocols ospf interface eth1 passive",
+ "set protocols ospfv3 interface eth1 passive",
+ ]
+ self.maxDiff = None
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_ospf_interfaces_replaced_idempotent(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth0",
+ address_family=[
+ dict(afi="ipv6", mtu_ignore=True, instance=33),
+ ],
+ ),
+ dict(
+ name="eth1",
+ address_family=[
+ dict(
+ afi="ipv4",
+ cost=100,
+ ),
+ dict(afi="ipv6", ifmtu=33),
+ ],
+ ),
+ ],
+ state="replaced",
+ )
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_ospf_interfaces_overridden(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth0",
+ address_family=[
+ dict(
+ afi="ipv4",
+ cost=100,
+ authentication=dict(plaintext_password="abcdefg!"),
+ priority=55,
+ ),
+ ],
+ ),
+ dict(
+ name="bond2",
+ address_family=[
+ dict(
+ afi="ipv4",
+ transmit_delay=9,
+ ),
+ dict(afi="ipv6", passive=True),
+ ],
+ ),
+ ],
+ state="overridden",
+ )
+ )
+ commands = [
+ "set protocols ospf interface bond2 transmit-delay 9",
+ "set protocols ospfv3 interface bond2 passive",
+ "set protocols ospf interface eth0 cost 100",
+ "set protocols ospf interface eth0 priority 55",
+ "set protocols ospf interface eth0 authentication plaintext-password abcdefg!",
+ "delete protocols ospf interface eth1",
+ "delete protocols ospfv3 interface eth1",
+ "delete protocols ospfv3 interface eth0 mtu-ignore",
+ "delete protocols ospfv3 interface eth0 instance-id 33",
+ ]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_ospf_interfaces_overridden_idempotent(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth0",
+ address_family=[
+ dict(afi="ipv6", mtu_ignore=True, instance=33),
+ ],
+ ),
+ dict(
+ name="eth1",
+ address_family=[
+ dict(
+ afi="ipv4",
+ cost=100,
+ ),
+ dict(afi="ipv6", ifmtu=33),
+ ],
+ ),
+ ],
+ state="overridden",
+ )
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_ospf_interfaces_deleted(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth0",
+ ),
+ ],
+ state="deleted",
+ )
+ )
+ commands = ["delete protocols ospfv3 interface eth0"]
+ self.execute_module(changed=True, commands=commands)
+
+ def test_vyos_ospf_interfaces_notpresent_deleted(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth3",
+ ),
+ ],
+ state="deleted",
+ )
+ )
+ self.execute_module(changed=False, commands=[])
+
+ def test_vyos_ospf_interfaces_rendered(self):
+ set_module_args(
+ dict(
+ config=[
+ dict(
+ name="eth0",
+ address_family=[
+ dict(
+ afi="ipv4",
+ cost=100,
+ authentication=dict(plaintext_password="abcdefg!"),
+ priority=55,
+ ),
+ dict(afi="ipv6", mtu_ignore=True, instance=20),
+ ],
+ ),
+ dict(
+ name="bond2",
+ address_family=[
+ dict(
+ afi="ipv4",
+ transmit_delay=9,
+ ),
+ dict(afi="ipv6", passive=True),
+ ],
+ ),
+ ],
+ state="rendered",
+ )
+ )
+ commands = [
+ "set protocols ospf interface eth0 cost 100",
+ "set protocols ospf interface eth0 authentication plaintext-password abcdefg!",
+ "set protocols ospf interface eth0 priority 55",
+ "set protocols ospfv3 interface eth0 mtu-ignore",
+ "set protocols ospfv3 interface eth0 instance-id 20",
+ "set protocols ospf interface bond2 transmit-delay 9",
+ "set protocols ospfv3 interface bond2 passive",
+ ]
+ result = self.execute_module(changed=False)
+ self.assertEqual(sorted(result["rendered"]), sorted(commands), result["rendered"])
+
+ def test_vyos_ospf_interfaces_parsed(self):
+ commands = [
+ "set protocols ospf interface bond2 authentication md5 key-id 10 md5-key '1111111111232345'",
+ "set protocols ospf interface bond2 bandwidth '70'",
+ "set protocols ospf interface bond2 transmit-delay '45'",
+ "set protocols ospfv3 interface bond2 'passive'",
+ "set protocols ospf interface eth0 cost '50'",
+ "set protocols ospf interface eth0 priority '26'",
+ "set protocols ospfv3 interface eth0 instance-id '33'",
+ "set protocols ospfv3 interface eth0 'mtu-ignore'",
+ "set protocols ospf interface eth1 network 'point-to-point'",
+ "set protocols ospf interface eth1 priority '26'",
+ "set protocols ospf interface eth1 transmit-delay '50'",
+ "set protocols ospfv3 interface eth1 dead-interval '39'",
+ ]
+
+ parsed_str = "\n".join(commands)
+ set_module_args(dict(running_config=parsed_str, state="parsed"))
+ result = self.execute_module(changed=False)
+ parsed_list = [
+ {
+ "address_family": [
+ {
+ "afi": "ipv4",
+ "authentication": {
+ "md5_key": {
+ "key": "1111111111232345",
+ "key_id": 10,
+ }
+ },
+ "bandwidth": 70,
+ "transmit_delay": 45,
+ },
+ {"afi": "ipv6", "passive": True},
+ ],
+ "name": "bond2",
+ },
+ {
+ "address_family": [
+ {"afi": "ipv4", "cost": 50, "priority": 26},
+ {"afi": "ipv6", "instance": "33", "mtu_ignore": True},
+ ],
+ "name": "eth0",
+ },
+ {
+ "address_family": [
+ {
+ "afi": "ipv4",
+ "network": "point-to-point",
+ "priority": 26,
+ "transmit_delay": 50,
+ },
+ {"afi": "ipv6", "dead_interval": 39},
+ ],
+ "name": "eth1",
+ },
+ ]
+ result_list = self.sort_address_family(result["parsed"])
+ given_list = self.sort_address_family(parsed_list)
+ self.assertEqual(result_list, given_list)
+
+ def test_vyos_ospf_interfaces_gathered(self):
+ set_module_args(dict(state="gathered"))
+ result = self.execute_module(changed=False, filename="vyos_ospf_interfaces_config.cfg")
+ gathered_list = [
+ {
+ "address_family": [{"afi": "ipv6", "instance": "33", "mtu_ignore": True}],
+ "name": "eth0",
+ },
+ {
+ "address_family": [
+ {"afi": "ipv4", "cost": 100},
+ {"afi": "ipv6", "ifmtu": 33},
+ ],
+ "name": "eth1",
+ },
+ ]
+
+ result_list = self.sort_address_family(result["gathered"])
+ given_list = self.sort_address_family(gathered_list)
+ self.assertEqual(result_list, given_list)