summaryrefslogtreecommitdiff
path: root/plugins/module_utils/network
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/module_utils/network')
-rw-r--r--plugins/module_utils/network/vyos/argspec/bgp_address_family/bgp_address_family.py1
-rw-r--r--plugins/module_utils/network/vyos/argspec/bgp_global/bgp_global.py1
-rw-r--r--plugins/module_utils/network/vyos/argspec/facts/facts.py2
-rw-r--r--plugins/module_utils/network/vyos/argspec/firewall_global/firewall_global.py77
-rw-r--r--plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py2
-rw-r--r--plugins/module_utils/network/vyos/argspec/firewall_rules/firewall_rules.py6
-rw-r--r--plugins/module_utils/network/vyos/argspec/ha/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/argspec/ha/ha.py196
-rw-r--r--plugins/module_utils/network/vyos/argspec/hostname/hostname.py1
-rw-r--r--plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py20
-rw-r--r--plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py2
-rw-r--r--plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py2
-rw-r--r--plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py2
-rw-r--r--plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py2
-rw-r--r--plugins/module_utils/network/vyos/argspec/logging_global/logging_global.py1
-rw-r--r--plugins/module_utils/network/vyos/argspec/nat/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/argspec/nat/nat.py652
-rw-r--r--plugins/module_utils/network/vyos/argspec/ntp_global/ntp_global.py1
-rw-r--r--plugins/module_utils/network/vyos/argspec/ospf_interfaces/ospf_interfaces.py1
-rw-r--r--plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py2
-rw-r--r--plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py8
-rw-r--r--plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py1
-rw-r--r--plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py21
-rw-r--r--plugins/module_utils/network/vyos/argspec/snmp_server/snmp_server.py1
-rw-r--r--plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py2
-rw-r--r--plugins/module_utils/network/vyos/argspec/vpn_ipsec/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/argspec/vpn_ipsec/vpn_ipsec.py206
-rw-r--r--plugins/module_utils/network/vyos/argspec/vpn_ipsec_s2s/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/argspec/vpn_ipsec_s2s/vpn_ipsec_s2s.py186
-rw-r--r--plugins/module_utils/network/vyos/argspec/vrf/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/argspec/vrf/vrf.py130
-rw-r--r--plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py63
-rw-r--r--plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py24
-rw-r--r--plugins/module_utils/network/vyos/config/firewall_global/firewall_global.py445
-rw-r--r--plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py2
-rw-r--r--plugins/module_utils/network/vyos/config/firewall_rules/firewall_rules.py120
-rw-r--r--plugins/module_utils/network/vyos/config/ha/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/config/ha/ha.py705
-rw-r--r--plugins/module_utils/network/vyos/config/hostname/hostname.py1
-rw-r--r--plugins/module_utils/network/vyos/config/interfaces/interfaces.py7
-rw-r--r--plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py128
-rw-r--r--plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py6
-rw-r--r--plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py6
-rw-r--r--plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py6
-rw-r--r--plugins/module_utils/network/vyos/config/logging_global/logging_global.py87
-rw-r--r--plugins/module_utils/network/vyos/config/nat/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/config/nat/nat.py577
-rw-r--r--plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py28
-rw-r--r--plugins/module_utils/network/vyos/config/ospf_interfaces/ospf_interfaces.py37
-rw-r--r--plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py62
-rw-r--r--plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py53
-rw-r--r--plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py18
-rw-r--r--plugins/module_utils/network/vyos/config/route_maps/route_maps.py41
-rw-r--r--plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py32
-rw-r--r--plugins/module_utils/network/vyos/config/static_routes/static_routes.py12
-rw-r--r--plugins/module_utils/network/vyos/config/vpn_ipsec/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/config/vpn_ipsec/vpn_ipsec.py665
-rw-r--r--plugins/module_utils/network/vyos/config/vpn_ipsec_s2s/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/config/vpn_ipsec_s2s/vpn_ipsec_s2s.py355
-rw-r--r--plugins/module_utils/network/vyos/config/vrf/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/config/vrf/vrf.py307
-rw-r--r--plugins/module_utils/network/vyos/facts/bgp_address_family/bgp_address_family.py7
-rw-r--r--plugins/module_utils/network/vyos/facts/bgp_global/bgp_global.py8
-rw-r--r--plugins/module_utils/network/vyos/facts/facts.py21
-rw-r--r--plugins/module_utils/network/vyos/facts/firewall_global/firewall_global.py132
-rw-r--r--plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py2
-rw-r--r--plugins/module_utils/network/vyos/facts/firewall_rules/firewall_rules.py61
-rw-r--r--plugins/module_utils/network/vyos/facts/ha/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/facts/ha/ha.py174
-rw-r--r--plugins/module_utils/network/vyos/facts/hostname/hostname.py1
-rw-r--r--plugins/module_utils/network/vyos/facts/interfaces/interfaces.py4
-rw-r--r--plugins/module_utils/network/vyos/facts/l3_interfaces/l3_interfaces.py4
-rw-r--r--plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py2
-rw-r--r--plugins/module_utils/network/vyos/facts/legacy/base.py1
-rw-r--r--plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py2
-rw-r--r--plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py1
-rw-r--r--plugins/module_utils/network/vyos/facts/logging_global/logging_global.py24
-rw-r--r--plugins/module_utils/network/vyos/facts/nat/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/facts/nat/nat.py174
-rw-r--r--plugins/module_utils/network/vyos/facts/ntp_global/ntp_global.py1
-rw-r--r--plugins/module_utils/network/vyos/facts/ospf_interfaces/ospf_interfaces.py29
-rw-r--r--plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py39
-rw-r--r--plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py12
-rw-r--r--plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py1
-rw-r--r--plugins/module_utils/network/vyos/facts/route_maps/route_maps.py17
-rw-r--r--plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py4
-rw-r--r--plugins/module_utils/network/vyos/facts/static_routes/static_routes.py8
-rw-r--r--plugins/module_utils/network/vyos/facts/vpn_ipsec/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/facts/vpn_ipsec/vpn_ipsec.py113
-rw-r--r--plugins/module_utils/network/vyos/facts/vpn_ipsec_s2s/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/facts/vpn_ipsec_s2s/vpn_ipsec_s2s.py115
-rw-r--r--plugins/module_utils/network/vyos/facts/vrf/__init__.py0
-rw-r--r--plugins/module_utils/network/vyos/facts/vrf/vrf.py226
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/bgp_address_family.py50
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/bgp_address_family_14.py50
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/bgp_global.py103
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/bgp_global_14.py108
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/ha.py1011
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/hostname.py1
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/logging_global.py1
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/logging_global_15.py241
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/nat.py1186
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/ntp_global.py1
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/ospf_interfaces.py149
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py36
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/prefix_lists.py1
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/route_maps.py256
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/route_maps_14.py1363
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/snmp_server.py79
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/vpn_ipsec.py997
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/vpn_ipsec_s2s.py670
-rw-r--r--plugins/module_utils/network/vyos/rm_templates/vrf.py248
-rw-r--r--plugins/module_utils/network/vyos/utils/utils.py90
-rw-r--r--plugins/module_utils/network/vyos/utils/version.py2
-rw-r--r--plugins/module_utils/network/vyos/vyos.py43
-rw-r--r--plugins/module_utils/network/vyos/vyos_file.py99
116 files changed, 12384 insertions, 897 deletions
diff --git a/plugins/module_utils/network/vyos/argspec/bgp_address_family/bgp_address_family.py b/plugins/module_utils/network/vyos/argspec/bgp_address_family/bgp_address_family.py
index 13f9fab0..946f6861 100644
--- a/plugins/module_utils/network/vyos/argspec/bgp_address_family/bgp_address_family.py
+++ b/plugins/module_utils/network/vyos/argspec/bgp_address_family/bgp_address_family.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
#############################################
diff --git a/plugins/module_utils/network/vyos/argspec/bgp_global/bgp_global.py b/plugins/module_utils/network/vyos/argspec/bgp_global/bgp_global.py
index 42fb5abf..13cca54d 100644
--- a/plugins/module_utils/network/vyos/argspec/bgp_global/bgp_global.py
+++ b/plugins/module_utils/network/vyos/argspec/bgp_global/bgp_global.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
#############################################
diff --git a/plugins/module_utils/network/vyos/argspec/facts/facts.py b/plugins/module_utils/network/vyos/argspec/facts/facts.py
index b274c507..ddcd8d14 100644
--- a/plugins/module_utils/network/vyos/argspec/facts/facts.py
+++ b/plugins/module_utils/network/vyos/argspec/facts/facts.py
@@ -4,8 +4,8 @@
"""
The arg spec for the vyos facts module.
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
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 8421b6de..dd17ef85 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
@@ -4,31 +4,12 @@
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-#############################################
-# WARNING #
-#############################################
-#
-# This file is auto generated by the resource
-# module builder playbook.
-#
-# Do not edit this file manually.
-#
-# Changes to this file will be over written
-# by the resource module builder.
-#
-# Changes should be made in the model used to
-# generate this file or in the resource module
-# builder template.
-#
-#############################################
"""
The arg spec for the vyos_firewall_global module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
@@ -187,6 +168,64 @@ class Firewall_globalArgs(object): # pylint: disable=R0903
],
"type": "str",
},
+ "zone": {
+ "elements": "dict",
+ "type": "list",
+ "options": {
+ "description": {"type": "str"},
+ "default_action": {
+ "choices": [
+ "drop",
+ "reject",
+ ],
+ "default": "drop",
+ "type": "str",
+ },
+ "default_log": {"type": "bool"},
+ "interfaces": {
+ "elements": "str",
+ "type": "list",
+ },
+ "local_zone": {"type": "bool"},
+ "name": {
+ "required": True,
+ "type": "str",
+ },
+ "intra_zone_filtering": {
+ "type": "dict",
+ "options": {
+ "action": {
+ "choices": ["accept", "drop"],
+ "type": "str",
+ },
+ "firewall": {
+ "type": "dict",
+ "options": {
+ "name": {"type": "str"},
+ "ipv6_name": {"type": "str"},
+ },
+ },
+ },
+ },
+ "sources": {
+ "elements": "dict",
+ "type": "list",
+ "options": {
+ "zone": {
+ "required": True,
+ "type": "str",
+ },
+ "firewall": {
+ "type": "dict",
+ "options": {
+ "name": {"type": "str"},
+ "ipv6_name": {"type": "str"},
+ },
+ },
+ },
+ },
+ },
+ },
},
"type": "dict",
},
diff --git a/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py b/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py
index 93c898e8..d925a7f4 100644
--- a/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/firewall_interfaces/firewall_interfaces.py
@@ -25,10 +25,8 @@
The arg spec for the vyos_firewall_interfaces module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
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 6ae17585..0378a65b 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
@@ -25,10 +25,8 @@
The arg spec for the vyos_firewall_rules module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
@@ -56,6 +54,8 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903
"reject",
"accept",
"jump",
+ "return",
+ "continue",
],
"type": "str",
},
@@ -83,6 +83,7 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903
"continue",
"return",
"jump",
+ "offload",
"queue",
"synproxy",
],
@@ -210,6 +211,7 @@ class Firewall_rulesArgs(object): # pylint: disable=R0903
"required": True,
"type": "int",
},
+ "offload_target": {"type": "str"},
"outbound_interface": {
"options": {
"group": {"type": "str"},
diff --git a/plugins/module_utils/network/vyos/argspec/ha/__init__.py b/plugins/module_utils/network/vyos/argspec/ha/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/ha/__init__.py
diff --git a/plugins/module_utils/network/vyos/argspec/ha/ha.py b/plugins/module_utils/network/vyos/argspec/ha/ha.py
new file mode 100644
index 00000000..4fe431cd
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/ha/ha.py
@@ -0,0 +1,196 @@
+# -*- coding: utf-8 -*-
+# Copyright 2024 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the vyos_ha module
+"""
+
+
+class HaArgs(object): # pylint: disable=R0903
+ """The arg spec for the vyos_ha module"""
+
+ argument_spec = {
+ "config": {
+ "type": "dict",
+ "required": False,
+ "options": {
+ "disable": {"type": "bool", "default": False},
+ "virtual_servers": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str", "required": True},
+ "address": {"type": "str"},
+ "algorithm": {"type": "str"},
+ "delay_loop": {"type": "int"},
+ "forward_method": {"type": "str", "choices": ["direct", "nat"]},
+ "fwmark": {"type": "int"},
+ "persistence_timeout": {"type": "int"},
+ "port": {"type": "int"},
+ "protocol": {"type": "str", "choices": ["tcp", "udp"]},
+ "real_server": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "address": {"type": "str", "required": True},
+ "port": {"type": "int"},
+ "connection_timeout": {"type": "int"},
+ "health_check_script": {"type": "str"},
+ },
+ },
+ },
+ },
+ "vrrp": {
+ "type": "dict",
+ "options": {
+ "global_parameters": {
+ "type": "dict",
+ "options": {
+ "garp": {
+ "type": "dict",
+ "options": {
+ "interval": {"type": "int"},
+ "master_delay": {"type": "int"},
+ "master_refresh": {"type": "int"},
+ "master_refresh_repeat": {"type": "int"},
+ "master_repeat": {"type": "int"},
+ },
+ },
+ "startup_delay": {"type": "int"},
+ "version": {"type": "str"},
+ },
+ },
+ "groups": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str", "required": True},
+ "address": {"type": "list", "elements": "str"},
+ "advertise_interval": {"type": "int"},
+ "authentication": {
+ "type": "dict",
+ "options": {
+ "password": {"type": "str", "no_log": True},
+ "type": {"type": "str"},
+ },
+ },
+ "description": {"type": "str"},
+ "disable": {"type": "bool", "default": False},
+ "excluded_address": {"type": "list", "elements": "str"},
+ "garp": {
+ "type": "dict",
+ "options": {
+ "interval": {"type": "int"},
+ "master_delay": {"type": "int"},
+ "master_refresh": {"type": "int"},
+ "master_refresh_repeat": {"type": "int"},
+ "master_repeat": {"type": "int"},
+ },
+ },
+ "health_check": {
+ "type": "dict",
+ "options": {
+ "failure_count": {"type": "int"},
+ "interval": {"type": "int"},
+ "ping": {"type": "str"},
+ "script": {"type": "str"},
+ },
+ },
+ "hello_source_address": {"type": "str"},
+ "interface": {"type": "str"},
+ "no_preempt": {"type": "bool", "default": False},
+ "peer_address": {"type": "str"},
+ "preempt_delay": {"type": "int"},
+ "priority": {"type": "int"},
+ "rfc3768_compatibility": {"type": "bool", "default": False},
+ "track": {
+ "type": "dict",
+ "options": {
+ "exclude_vrrp_interface": {"type": "bool"},
+ "interface": {"type": "list", "elements": "str"},
+ },
+ },
+ "transition_script": {
+ "type": "dict",
+ "options": {
+ "backup": {"type": "str"},
+ "fault": {"type": "str"},
+ "master": {"type": "str"},
+ "stop": {"type": "str"},
+ },
+ },
+ "vrid": {"type": "int", "required": False},
+ },
+ },
+ "snmp": {
+ "type": "str",
+ "choices": ["disabled", "enabled"],
+ },
+ "sync_groups": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str", "required": True},
+ "health_check": {
+ "type": "dict",
+ "options": {
+ "failure_count": {"type": "int"},
+ "interval": {"type": "int"},
+ "ping": {"type": "str"},
+ "script": {"type": "str"},
+ },
+ },
+ "member": {"type": "list", "elements": "str"},
+ "transition_script": {
+ "type": "dict",
+ "options": {
+ "backup": {"type": "str"},
+ "fault": {"type": "str"},
+ "master": {"type": "str"},
+ "stop": {"type": "str"},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "deleted",
+ "merged",
+ "purged",
+ "replaced",
+ "gathered",
+ "rendered",
+ "parsed",
+ "overridden",
+ ],
+ "default": "merged",
+ },
+ "running_config": {"type": "str"},
+ } # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/hostname/hostname.py b/plugins/module_utils/network/vyos/argspec/hostname/hostname.py
index 12864e66..b1af7b9c 100644
--- a/plugins/module_utils/network/vyos/argspec/hostname/hostname.py
+++ b/plugins/module_utils/network/vyos/argspec/hostname/hostname.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
#############################################
diff --git a/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py b/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py
index 14b67c28..8ba119f3 100644
--- a/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/interfaces/interfaces.py
@@ -4,31 +4,12 @@
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
-#############################################
-# WARNING #
-#############################################
-#
-# This file is auto generated by the resource
-# module builder playbook.
-#
-# Do not edit this file manually.
-#
-# Changes to this file will be over written
-# by the resource module builder.
-#
-# Changes should be made in the model used to
-# generate this file or in the resource module
-# builder template.
-#
-#############################################
"""
The arg spec for the vyos_interfaces module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
@@ -79,6 +60,7 @@ class InterfacesArgs(object): # pylint: disable=R0903
},
"type": "list",
},
+ "vrf": {"type": "str"},
},
"type": "list",
},
diff --git a/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py
index 4dee518e..d6d9e298 100644
--- a/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/l3_interfaces/l3_interfaces.py
@@ -25,10 +25,8 @@
The arg spec for the vyos_l3_interfaces module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
diff --git a/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py
index 956c4385..7744eea0 100644
--- a/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/lag_interfaces/lag_interfaces.py
@@ -25,10 +25,8 @@
The arg spec for the vyos_lag_interfaces module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
diff --git a/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py b/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py
index d79de617..6d1129e8 100644
--- a/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py
+++ b/plugins/module_utils/network/vyos/argspec/lldp_global/lldp_global.py
@@ -25,10 +25,8 @@
The arg spec for the vyos_lldp_global module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
diff --git a/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py
index fd6c6271..4bb742f5 100644
--- a/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/lldp_interfaces/lldp_interfaces.py
@@ -25,10 +25,8 @@
The arg spec for the vyos_lldp_interfaces module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
diff --git a/plugins/module_utils/network/vyos/argspec/logging_global/logging_global.py b/plugins/module_utils/network/vyos/argspec/logging_global/logging_global.py
index 734d190e..f6800e9b 100644
--- a/plugins/module_utils/network/vyos/argspec/logging_global/logging_global.py
+++ b/plugins/module_utils/network/vyos/argspec/logging_global/logging_global.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
#############################################
diff --git a/plugins/module_utils/network/vyos/argspec/nat/__init__.py b/plugins/module_utils/network/vyos/argspec/nat/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/nat/__init__.py
diff --git a/plugins/module_utils/network/vyos/argspec/nat/nat.py b/plugins/module_utils/network/vyos/argspec/nat/nat.py
new file mode 100644
index 00000000..6f81c53c
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/nat/nat.py
@@ -0,0 +1,652 @@
+# -*- coding: utf-8 -*-
+# 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 arg spec for the vyos_nat module
+"""
+
+
+class NatArgs(object): # pylint: disable=R0903
+ """The arg spec for the vyos_nat module"""
+
+ argument_spec = {
+ "config": {
+ "type": "dict",
+ "options": {
+ "nat": {
+ "type": "dict",
+ "options": {
+ "cgnat": {
+ "type": "dict",
+ "options": {
+ "log_allocation": {
+ "type": "bool",
+ },
+ "pool": {
+ "type": "dict",
+ "options": {
+ "external": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {
+ "type": "str",
+ "required": True,
+ },
+ "external_port_range": {
+ "type": "str",
+ },
+ "per_user_limit": {
+ "type": "dict",
+ "options": {
+ "port": {
+ "type": "str",
+ },
+ },
+ },
+ "range": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "value": {
+ "type": "str",
+ "required": True,
+ },
+ "seq": {
+ "type": "str",
+ },
+ },
+ },
+ },
+ },
+ "internal": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {
+ "type": "str",
+ "required": True,
+ },
+ "range": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ },
+ },
+ "rule": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "id": {
+ "type": "int",
+ "required": True,
+ },
+ "source": {
+ "type": "dict",
+ "options": {
+ "pool": {
+ "type": "str",
+ },
+ },
+ },
+ "translation": {
+ "type": "dict",
+ "options": {
+ "pool": {
+ "type": "str",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "destination": {
+ "type": "dict",
+ "options": {
+ "rule": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "id": {
+ "type": "int",
+ "required": True,
+ },
+ "description": {
+ "type": "str",
+ },
+ "protocol": {
+ "type": "str",
+ },
+ "packet_type": {
+ "type": "str",
+ },
+ "exclude": {
+ "type": "bool",
+ },
+ "log": {
+ "type": "bool",
+ },
+ "disable": {
+ "type": "bool",
+ },
+ "inbound_interface": {
+ "type": "dict",
+ "options": {
+ "name": {
+ "type": "str",
+ },
+ "group": {
+ "type": "str",
+ },
+ },
+ },
+ "destination": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ "fqdn": {
+ "type": "str",
+ },
+ "port": {
+ "type": "str",
+ },
+ "address_group": {
+ "type": "str",
+ },
+ "domain_group": {
+ "type": "str",
+ },
+ "mac_group": {
+ "type": "str",
+ },
+ "network_group": {
+ "type": "str",
+ },
+ "port_group": {
+ "type": "str",
+ },
+ },
+ },
+ "translation": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ "port": {
+ "type": "str",
+ },
+ "redirect_port": {
+ "type": "str",
+ },
+ "address_mapping": {
+ "type": "str",
+ "choices": [
+ "random",
+ "persistent",
+ ],
+ },
+ "port_mapping": {
+ "type": "str",
+ "choices": [
+ "random",
+ "none",
+ ],
+ },
+ },
+ },
+ "load_balance": {
+ "type": "dict",
+ "options": {
+ "backend": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "ip": {"type": "str"},
+ "weight": {"type": "int"},
+ },
+ },
+ "hash": {
+ "type": "list",
+ "elements": "str",
+ "choices": [
+ "source-address",
+ "destination-address",
+ "source-port",
+ "destination-port",
+ "random",
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "source": {
+ "type": "dict",
+ "options": {
+ "rule": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "id": {
+ "type": "int",
+ "required": True,
+ },
+ "description": {
+ "type": "str",
+ },
+ "protocol": {
+ "type": "str",
+ },
+ "packet_type": {
+ "type": "str",
+ },
+ "exclude": {
+ "type": "bool",
+ },
+ "log": {
+ "type": "bool",
+ },
+ "disable": {
+ "type": "bool",
+ },
+ "outbound_interface": {
+ "type": "dict",
+ "options": {
+ "name": {
+ "type": "str",
+ },
+ "group": {
+ "type": "str",
+ },
+ },
+ },
+ "destination": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ "fqdn": {
+ "type": "str",
+ },
+ "address_group": {
+ "type": "str",
+ },
+ "domain_group": {
+ "type": "str",
+ },
+ "mac_group": {
+ "type": "str",
+ },
+ "network_group": {
+ "type": "str",
+ },
+ "port_group": {
+ "type": "str",
+ },
+ "port": {
+ "type": "str",
+ },
+ },
+ },
+ "source": {
+ "type": "dict",
+ "options": {
+ "address": {"type": "str"},
+ "fqdn": {"type": "str"},
+ "port": {"type": "str"},
+ "address_group": {"type": "str"},
+ "domain_group": {"type": "str"},
+ "mac_group": {"type": "str"},
+ "network_group": {"type": "str"},
+ "port_group": {"type": "str"},
+ },
+ },
+ "translation": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ "port": {
+ "type": "str",
+ },
+ "address_mapping": {
+ "type": "str",
+ "choices": [
+ "random",
+ "persistent",
+ ],
+ },
+ "port_mapping": {
+ "type": "str",
+ "choices": [
+ "random",
+ "none",
+ ],
+ },
+ },
+ },
+ "load_balance": {
+ "type": "dict",
+ "options": {
+ "backend": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "ip": {"type": "str"},
+ "weight": {"type": "int"},
+ },
+ },
+ "hash": {
+ "type": "list",
+ "elements": "str",
+ "choices": [
+ "source-address",
+ "destination-address",
+ "source-port",
+ "destination-port",
+ "random",
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "static": {
+ "type": "dict",
+ "options": {
+ "rule": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "id": {
+ "type": "int",
+ "required": True,
+ },
+ "description": {
+ "type": "str",
+ },
+ "destination": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ },
+ },
+ "inbound_interface": {
+ "type": "str",
+ },
+ "log": {
+ "type": "bool",
+ },
+ "translation": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "nat64": {
+ "type": "dict",
+ "options": {
+ "source": {
+ "type": "dict",
+ "options": {
+ "rule": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "id": {
+ "type": "int",
+ "required": True,
+ },
+ "description": {
+ "type": "str",
+ },
+ "disable": {
+ "type": "bool",
+ },
+ "match": {
+ "type": "dict",
+ "options": {
+ "mark": {
+ "type": "int",
+ },
+ },
+ },
+ "source": {
+ "type": "dict",
+ "options": {
+ "prefix": {
+ "type": "str",
+ },
+ },
+ },
+ "translation": {
+ "type": "dict",
+ "options": {
+ "pool": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "id": {
+ "type": "int",
+ "required": True,
+ },
+ "address": {
+ "type": "str",
+ },
+ "description": {
+ "type": "str",
+ },
+ "disable": {
+ "type": "bool",
+ },
+ "port": {
+ "type": "str",
+ },
+ "protocol": {
+ "type": "str",
+ "choices": [
+ "icmp",
+ "tcp",
+ "udp",
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "nat66": {
+ "type": "dict",
+ "options": {
+ "destination": {
+ "type": "dict",
+ "options": {
+ "rule": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "id": {
+ "type": "int",
+ "required": True,
+ },
+ "description": {
+ "type": "str",
+ },
+ "destination": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ "port": {
+ "type": "str",
+ },
+ },
+ },
+ "disable": {
+ "type": "bool",
+ },
+ "exclude": {
+ "type": "bool",
+ },
+ "inbound_interface": {
+ "type": "dict",
+ "options": {
+ "name": {
+ "type": "str",
+ },
+ },
+ },
+ "log": {
+ "type": "bool",
+ },
+ "protocol": {
+ "type": "str",
+ },
+ "source": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ "port": {
+ "type": "str",
+ },
+ },
+ },
+ "translation": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ "port": {
+ "type": "str",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "source": {
+ "type": "dict",
+ "options": {
+ "rule": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "id": {
+ "type": "int",
+ "required": True,
+ },
+ "description": {
+ "type": "str",
+ },
+ "destination": {
+ "type": "dict",
+ "options": {
+ "port": {
+ "type": "str",
+ },
+ "prefix": {
+ "type": "str",
+ },
+ },
+ },
+ "disable": {
+ "type": "bool",
+ },
+ "exclude": {
+ "type": "bool",
+ },
+ "log": {
+ "type": "bool",
+ },
+ "outbound_interface": {
+ "type": "dict",
+ "options": {
+ "name": {
+ "type": "str",
+ },
+ },
+ },
+ "protocol": {
+ "type": "str",
+ },
+ "source": {
+ "type": "dict",
+ "options": {
+ "port": {
+ "type": "str",
+ },
+ "prefix": {
+ "type": "str",
+ },
+ },
+ },
+ "translation": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "str",
+ },
+ "port": {
+ "type": "str",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "running_config": {"type": "str"},
+ "state": {
+ "type": "str",
+ "choices": [
+ "deleted",
+ "merged",
+ "overridden",
+ "replaced",
+ "gathered",
+ "rendered",
+ "parsed",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/ntp_global/ntp_global.py b/plugins/module_utils/network/vyos/argspec/ntp_global/ntp_global.py
index 6940fb7e..5e10d05b 100644
--- a/plugins/module_utils/network/vyos/argspec/ntp_global/ntp_global.py
+++ b/plugins/module_utils/network/vyos/argspec/ntp_global/ntp_global.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
#############################################
diff --git a/plugins/module_utils/network/vyos/argspec/ospf_interfaces/ospf_interfaces.py b/plugins/module_utils/network/vyos/argspec/ospf_interfaces/ospf_interfaces.py
index 0b5814be..ced9f98e 100644
--- a/plugins/module_utils/network/vyos/argspec/ospf_interfaces/ospf_interfaces.py
+++ b/plugins/module_utils/network/vyos/argspec/ospf_interfaces/ospf_interfaces.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
#############################################
diff --git a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py
index 0a422e89..a939652b 100644
--- a/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py
+++ b/plugins/module_utils/network/vyos/argspec/ospfv2/ospfv2.py
@@ -25,10 +25,8 @@
The arg spec for the vyos_ospfv2 module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
diff --git a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py
index a59606dd..77a17d79 100644
--- a/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py
+++ b/plugins/module_utils/network/vyos/argspec/ospfv3/ospfv3.py
@@ -25,10 +25,8 @@
The arg spec for the vyos_ospfv3 module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
@@ -47,6 +45,12 @@ class Ospfv3Args(object): # pylint: disable=R0903
"area_id": {"type": "str"},
"export_list": {"type": "str"},
"import_list": {"type": "str"},
+ "interface": {
+ "aliases": ["interfaces"],
+ "type": "list",
+ "elements": "dict",
+ "options": {"name": {"type": "str"}},
+ },
"range": {
"elements": "dict",
"options": {
diff --git a/plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py b/plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py
index b01a3e82..c74400ba 100644
--- a/plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py
+++ b/plugins/module_utils/network/vyos/argspec/prefix_lists/prefix_lists.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
#############################################
diff --git a/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py b/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py
index 196db0c7..58ffa3e5 100644
--- a/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py
+++ b/plugins/module_utils/network/vyos/argspec/route_maps/route_maps.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
#############################################
@@ -74,6 +73,8 @@ class Route_mapsArgs(object): # pylint: disable=R0903
},
"extcommunity_rt": {"type": "str"},
"extcommunity_soo": {"type": "str"},
+ "extcommunity_bandwidth": {"type": "str"},
+ "extcommunity_bandwidth_non_transitive": {"type": "bool"},
"ip_next_hop": {"type": "str"},
"ipv6_next_hop": {
"type": "dict",
@@ -100,6 +101,7 @@ class Route_mapsArgs(object): # pylint: disable=R0903
"src": {"type": "str"},
"tag": {"type": "str"},
"weight": {"type": "str"},
+ "table": {"type": "str"},
},
},
"match": {
@@ -178,6 +180,23 @@ class Route_mapsArgs(object): # pylint: disable=R0903
"next_hop": {"type": "str"},
},
},
+ "protocol": {
+ "type": "str",
+ "choices": [
+ "babel",
+ "bgp",
+ "connected",
+ "isis",
+ "kernel",
+ "ospf",
+ "ospfv3",
+ "rip",
+ "ripng",
+ "static",
+ "table",
+ "vnc",
+ ],
+ },
"large_community_large_community_list": {
"type": "str",
},
diff --git a/plugins/module_utils/network/vyos/argspec/snmp_server/snmp_server.py b/plugins/module_utils/network/vyos/argspec/snmp_server/snmp_server.py
index b94c2639..53516c4f 100644
--- a/plugins/module_utils/network/vyos/argspec/snmp_server/snmp_server.py
+++ b/plugins/module_utils/network/vyos/argspec/snmp_server/snmp_server.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
#############################################
diff --git a/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py b/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py
index 365df48a..d8a4f11a 100644
--- a/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py
+++ b/plugins/module_utils/network/vyos/argspec/static_routes/static_routes.py
@@ -25,10 +25,8 @@
The arg spec for the vyos_static_routes module
"""
-
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
diff --git a/plugins/module_utils/network/vyos/argspec/vpn_ipsec/__init__.py b/plugins/module_utils/network/vyos/argspec/vpn_ipsec/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/vpn_ipsec/__init__.py
diff --git a/plugins/module_utils/network/vyos/argspec/vpn_ipsec/vpn_ipsec.py b/plugins/module_utils/network/vyos/argspec/vpn_ipsec/vpn_ipsec.py
new file mode 100644
index 00000000..75fc1ce5
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/vpn_ipsec/vpn_ipsec.py
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+# Copyright 2026 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the vyos_vpn_ipsec module
+"""
+
+
+class Vpn_ipsecArgs(object): # pylint: disable=R0903
+ """The arg spec for the vyos_vpn_ipsec module"""
+
+ argument_spec = {
+ "config": {
+ "type": "dict",
+ "options": {
+ "ike_group": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str", "required": True},
+ "close_action": {
+ "type": "str",
+ "choices": ["none", "trap", "start"],
+ },
+ "dead_peer_detection": {
+ "type": "dict",
+ "options": {
+ "action": {
+ "type": "str",
+ "choices": ["trap", "clear", "restart"],
+ },
+ "interval": {"type": "int"},
+ "timeout": {"type": "int"},
+ },
+ },
+ "disable_mobike": {"type": "bool"},
+ "ikev2_reauth": {"type": "bool"},
+ "key_exchange": {
+ "type": "str",
+ "choices": ["ikev1", "ikev2"],
+ },
+ "lifetime": {"type": "int"},
+ "mode": {"type": "str", "choices": ["main", "aggressive"]},
+ "proposal": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "proposal_id": {"type": "int"},
+ "dh_group": {"type": "int"},
+ "encryption": {"type": "str"},
+ "hash": {"type": "str"},
+ "prf": {"type": "str"},
+ },
+ },
+ },
+ },
+ "esp_group": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str", "required": True},
+ "compression": {"type": "bool"},
+ "disable_rekey": {"type": "bool"},
+ "life_bytes": {"type": "int"},
+ "life_packets": {"type": "int"},
+ "lifetime": {"type": "int"},
+ "mode": {
+ "type": "str",
+ "choices": ["tunnel", "transport"],
+ },
+ "pfs": {"type": "str"},
+ "proposal": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "proposal_id": {"type": "int"},
+ "encryption": {"type": "str"},
+ "hash": {"type": "str"},
+ },
+ },
+ },
+ },
+ "authentication": {
+ "type": "dict",
+ "options": {
+ "psk": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str", "required": True},
+ "id": {"type": "list", "elements": "str"},
+ "dhcp_interface": {
+ "type": "list",
+ "elements": "str",
+ },
+ "secret": {"type": "str", "no_log": True},
+ "secret_type": {
+ "type": "str",
+ "choices": ["base64", "hex", "plaintext"],
+ },
+ },
+ },
+ "ppk": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str", "required": True},
+ "id": {"type": "list", "elements": "str"},
+ "secret": {"type": "str", "no_log": True},
+ "secret_type": {
+ "type": "str",
+ "choices": ["base64", "hex", "plaintext"],
+ },
+ },
+ },
+ },
+ },
+ "profile": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str", "required": True},
+ "authentication": {
+ "type": "dict",
+ "options": {
+ "mode": {
+ "type": "str",
+ "choices": ["pre-shared-secret"],
+ },
+ "pre_shared_secret": {"type": "str", "no_log": True},
+ },
+ },
+ "bind_tunnel": {"type": "list", "elements": "str"},
+ "disable": {"type": "bool"},
+ "esp_group": {"type": "str"},
+ "ike_group": {"type": "str"},
+ },
+ },
+ "interface": {"type": "list", "elements": "str"},
+ "log": {
+ "type": "dict",
+ "options": {
+ "level": {"type": "int"},
+ "subsystem": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "options": {
+ "type": "dict",
+ "options": {
+ "disable_route_autoinstall": {"type": "bool"},
+ "flexvpn": {"type": "bool"},
+ "interface": {"type": "str"},
+ "retransmission": {
+ "type": "dict",
+ "options": {
+ "attempts": {"type": "int"},
+ "base": {"type": "float"},
+ "timeout": {"type": "int"},
+ },
+ },
+ "virtual_ip": {"type": "bool"},
+ },
+ },
+ "disable_uniqreqids": {"type": "bool"},
+ },
+ },
+ "running_config": {"type": "str"},
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "gathered",
+ "rendered",
+ "parsed",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/vpn_ipsec_s2s/__init__.py b/plugins/module_utils/network/vyos/argspec/vpn_ipsec_s2s/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/vpn_ipsec_s2s/__init__.py
diff --git a/plugins/module_utils/network/vyos/argspec/vpn_ipsec_s2s/vpn_ipsec_s2s.py b/plugins/module_utils/network/vyos/argspec/vpn_ipsec_s2s/vpn_ipsec_s2s.py
new file mode 100644
index 00000000..bd23d4d3
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/vpn_ipsec_s2s/vpn_ipsec_s2s.py
@@ -0,0 +1,186 @@
+# -*- coding: utf-8 -*-
+# Copyright 2026 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the vyos_vpn_ipsec_s2s module
+"""
+
+
+class Vpn_ipsec_s2sArgs(object): # pylint: disable=R0903
+ """The arg spec for the vyos_vpn_ipsec_s2s module"""
+
+ argument_spec = {
+ "config": {
+ "type": "dict",
+ "options": {
+ "peer": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str", "required": True},
+ "disable": {"type": "bool"},
+ "authentication": {
+ "type": "dict",
+ "options": {
+ "local_id": {"type": "str"},
+ "ppk": {
+ "type": "dict",
+ "options": {
+ "id": {"type": "str"},
+ "required": {"type": "bool"},
+ },
+ },
+ "rsa": {
+ "type": "dict",
+ "options": {
+ "local_key": {"type": "str", "no_log": True},
+ "passphrase": {"type": "str", "no_log": True},
+ "remote_key": {"type": "str", "no_log": True},
+ },
+ },
+ "x509": {
+ "type": "dict",
+ "options": {
+ "certificate": {"type": "str"},
+ "passphrase": {"type": "str", "no_log": True},
+ "ca_certificate": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "mode": {
+ "type": "str",
+ "choices": [
+ "pre-shared-secret",
+ "rsa",
+ "x509",
+ ],
+ },
+ "remote_id": {"type": "str"},
+ "use_x509_id": {"type": "bool"},
+ },
+ },
+ "childless": {
+ "type": "str",
+ "choices": ["allow", "prefer", "force", "never"],
+ },
+ "connection_type": {
+ "type": "str",
+ "choices": ["initiate", "trap", "none"],
+ },
+ "default_esp_group": {"type": "str"},
+ "description": {"type": "str"},
+ "dhcp_interface": {"type": "str"},
+ "force_udp_encapsulation": {"type": "bool"},
+ "ike_group": {"type": "str"},
+ "ikev2_reauth": {
+ "type": "str",
+ "choices": ["yes", "no", "inherit"],
+ },
+ "local_address": {"type": "str"},
+ "remote_address": {"type": "list", "elements": "str"},
+ "replay_window": {"type": "int"},
+ "tunnel": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "tunnel_id": {"type": "int", "required": True},
+ "disable": {"type": "bool"},
+ "esp_group": {"type": "str"},
+ "local": {
+ "type": "dict",
+ "options": {
+ "port": {"type": "int"},
+ "prefix": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "protocol": {"type": "str"},
+ "priority": {"type": "int"},
+ "remote": {
+ "type": "dict",
+ "options": {
+ "port": {"type": "int"},
+ "prefix": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ },
+ },
+ "virtual_address": {"type": "list", "elements": "str"},
+ "vti": {
+ "type": "dict",
+ "options": {
+ "bind": {"type": "str"},
+ "esp_group": {"type": "str"},
+ "traffic_selector": {
+ "type": "dict",
+ "options": {
+ "local": {
+ "type": "dict",
+ "options": {
+ "prefix": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "remote": {
+ "type": "dict",
+ "options": {
+ "prefix": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "running_config": {"type": "str"},
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "gathered",
+ "rendered",
+ "parsed",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/argspec/vrf/__init__.py b/plugins/module_utils/network/vyos/argspec/vrf/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/vrf/__init__.py
diff --git a/plugins/module_utils/network/vyos/argspec/vrf/vrf.py b/plugins/module_utils/network/vyos/argspec/vrf/vrf.py
new file mode 100644
index 00000000..20947f9d
--- /dev/null
+++ b/plugins/module_utils/network/vyos/argspec/vrf/vrf.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+# Copyright 2024 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the vyos_vrf module
+"""
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.bgp_global.bgp_global import (
+ Bgp_globalArgs,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv2.ospfv2 import (
+ Ospfv2Args,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv3.ospfv3 import (
+ Ospfv3Args,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.static_routes.static_routes import (
+ Static_routesArgs,
+)
+
+
+class VrfArgs(object): # pylint: disable=R0903
+ """The arg spec for the vyos_vrf module"""
+
+ bgp_argument_spec = Bgp_globalArgs.argument_spec["config"]
+ static_routes_argument_spec = Static_routesArgs.argument_spec["config"]
+ ospfv2_argument_spec = Ospfv2Args.argument_spec["config"]
+ ospfv3_argument_spec = Ospfv3Args.argument_spec["config"]
+
+ argument_spec = {
+ "config": {
+ "type": "dict",
+ "options": {
+ "bind_to_all": {"type": "bool", "default": False},
+ "instances": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"required": True, "type": "str"},
+ "description": {"type": "str"},
+ "disable": {
+ "aliases": ["disabled"],
+ "default": False,
+ "type": "bool",
+ },
+ "table_id": {"type": "int"},
+ "vni": {"type": "int"},
+ "address_family": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "afi": {
+ "type": "str",
+ "choices": ["ipv4", "ipv6"],
+ },
+ "disable_forwarding": {"type": "bool", "default": False},
+ "nht_no_resolve_via_default": {"type": "bool", "default": False},
+ "route_maps": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "rm_name": {"type": "str", "required": True},
+ "protocol": {
+ "type": "str",
+ "choices": [
+ "any",
+ "babel",
+ "bgp",
+ "eigrp",
+ "isis",
+ "ospf",
+ "rip",
+ "static",
+ ],
+ },
+ },
+ },
+ },
+ },
+ "protocols": {
+ # "type": "list", # sanity
+ # "elements": "dict",
+ "type": "dict",
+ "options": {
+ "bgp": bgp_argument_spec,
+ "ospf": ospfv2_argument_spec,
+ "ospfv3": ospfv3_argument_spec,
+ "static": static_routes_argument_spec,
+ },
+ },
+ },
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "deleted",
+ "merged",
+ "replaced",
+ "overridden",
+ "gathered",
+ "rendered",
+ "parsed",
+ ],
+ "default": "merged",
+ },
+ "running_config": {"type": "str"},
+ } # pylint: disable=C0301
diff --git a/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py b/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py
index 0e6bec81..f1494698 100644
--- a/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py
+++ b/plugins/module_utils/network/vyos/config/bgp_address_family/bgp_address_family.py
@@ -7,7 +7,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -20,7 +19,6 @@ created.
import re
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
@@ -32,15 +30,14 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_address_family import (
Bgp_address_familyTemplate,
)
-
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_address_family_14 import (
Bgp_address_familyTemplate14,
)
-
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
-
class Bgp_address_family(ResourceModule):
"""
@@ -65,7 +62,7 @@ class Bgp_address_family(ResourceModule):
self._tmplt = Bgp_address_familyTemplate()
def parse(self):
- """ override parse to check template """
+ """override parse to check template"""
self._validate_template()
return super().parse()
@@ -93,9 +90,11 @@ class Bgp_address_family(ResourceModule):
wantd = {}
haved = {}
- if (self.want.get("as_number") == self.have.get("as_number") or
- not self.have or
- LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")):
+ if (
+ self.want.get("as_number") == self.have.get("as_number")
+ or not self.have
+ or LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
+ ):
if self.want:
wantd = {self.want["as_number"]: self.want}
if self.have:
@@ -113,16 +112,16 @@ class Bgp_address_family(ResourceModule):
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
- for k, have in iteritems(haved):
+ for k, have in haved.items():
self._delete_af(wantd, have)
wantd = {}
if self.state == "overridden":
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
self._compare(want={}, have=have)
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _compare(self, want, have):
@@ -149,23 +148,23 @@ class Bgp_address_family(ResourceModule):
def _compare_af(self, want, have):
waf = want.get("address_family", {})
haf = have.get("address_family", {})
- for name, entry in iteritems(waf):
+ for name, entry in waf.items():
self._compare_lists(
entry,
have=haf.get(name, {}),
as_number=want["as_number"],
afi=name,
)
- for name, entry in iteritems(haf):
+ for name, entry in haf.items():
if name not in waf.keys() and self.state == "replaced":
continue
self._compare_lists({}, entry, as_number=have["as_number"], afi=name)
def _delete_af(self, want, have):
- for as_num, entry in iteritems(want):
- for afi, af_entry in iteritems(entry.get("address_family", {})):
+ for as_num, entry in want.items():
+ for afi, af_entry in entry.get("address_family", {}).items():
if have.get("address_family"):
- for hafi, hentry in iteritems(have["address_family"]):
+ for hafi, hentry in have["address_family"].items():
if hafi == afi:
self.commands.append(
self._tmplt.render(
@@ -177,9 +176,9 @@ class Bgp_address_family(ResourceModule):
True,
),
)
- for neigh, neigh_entry in iteritems(entry.get("neighbors", {})):
+ for neigh, neigh_entry in entry.get("neighbors", {}).items():
if have.get("neighbors"):
- for hneigh, hnentry in iteritems(have["neighbors"]):
+ for hneigh, hnentry in have["neighbors"].items():
if hneigh == neigh:
if not neigh_entry.get("address_family"):
self.commands.append(
@@ -239,9 +238,9 @@ class Bgp_address_family(ResourceModule):
]
wneigh = want.get("neighbors", {})
hneigh = have.get("neighbors", {})
- for name, entry in iteritems(wneigh):
- for afi, af_entry in iteritems(entry.get("address_family")):
- for k, val in iteritems(af_entry):
+ for name, entry in wneigh.items():
+ for afi, af_entry in entry.get("address_family").items():
+ for k, val in af_entry.items():
w = {
"as_number": want["as_number"],
"neighbors": {
@@ -268,7 +267,7 @@ class Bgp_address_family(ResourceModule):
want=w,
have=h,
)
- for name, entry in iteritems(hneigh):
+ for name, entry in hneigh.items():
if name not in wneigh.keys():
# remove surplus config for overridden and replaced
if self.state != "replaced":
@@ -284,9 +283,9 @@ class Bgp_address_family(ResourceModule):
)
continue
- for hafi, haf_entry in iteritems(entry.get("address_family")):
+ for hafi, haf_entry in entry.get("address_family").items():
# remove surplus configs for given neighbor - replace and overridden
- for k, val in iteritems(haf_entry):
+ for k, val in haf_entry.items():
h = {
"as_number": have["as_number"],
"neighbors": {
@@ -317,7 +316,7 @@ class Bgp_address_family(ResourceModule):
for attrib in ["redistribute", "networks", "aggregate_address"]:
wdict = want.pop(attrib, {})
hdict = have.pop(attrib, {})
- for key, entry in iteritems(wdict):
+ for key, entry in wdict.items():
if entry != hdict.get(key, {}):
self.compare(
parsers=parsers,
@@ -348,7 +347,7 @@ class Bgp_address_family(ResourceModule):
+ attrib,
)
hdict = {}
- for key, entry in iteritems(hdict):
+ for key, entry in hdict.items():
self.compare(
parsers=parsers,
want={},
@@ -358,7 +357,7 @@ class Bgp_address_family(ResourceModule):
},
)
# de-duplicate child commands if parent command is present
- for val in (self.commands):
+ for val in self.commands:
for val2 in self.commands:
if val != val2 and val2.startswith(val):
self.commands.remove(val2)
@@ -366,13 +365,11 @@ class Bgp_address_family(ResourceModule):
def _compare_asn(self, want, have):
if want.get("as_number") and not have.get("as_number"):
self.commands.append(
- "set protocols bgp "
- + "system-as "
- + str(want.get("as_number")),
+ "set protocols bgp " + "system-as " + str(want.get("as_number")),
)
def _bgp_af_list_to_dict(self, entry):
- for name, proc in iteritems(entry):
+ for name, proc in entry.items():
if "address_family" in proc:
af_dict = {}
for entry in proc.get("address_family"):
diff --git a/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py b/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py
index 91a5af12..2d7d3e63 100644
--- a/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py
+++ b/plugins/module_utils/network/vyos/config/bgp_global/bgp_global.py
@@ -7,7 +7,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -18,7 +17,6 @@ necessary to bring the current configuration to its desired end-state is
created.
"""
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
@@ -118,10 +116,10 @@ class Bgp_global(ResourceModule):
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "purged":
h_del = {}
- for k, v in iteritems(haved):
+ for k, v in haved.items():
if k in wantd or not wantd:
h_del.update({k: v})
- for num, entry in iteritems(h_del):
+ for num, entry in h_del.items():
self.commands.append(self._tmplt.render({"as_number": num}, "router", True))
wantd = {}
@@ -129,7 +127,7 @@ class Bgp_global(ResourceModule):
self._compare(want={}, have=self.have)
wantd = {}
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _compare(self, want, have):
@@ -144,7 +142,7 @@ class Bgp_global(ResourceModule):
parsers = ["maximum_paths", "timers"]
self._compare_neighbor(want, have)
self._compare_bgp_params(want, have)
- for name, entry in iteritems(want):
+ for name, entry in want.items():
if name != "as_number":
self.compare(
parsers=parsers,
@@ -154,7 +152,7 @@ class Bgp_global(ResourceModule):
name: have.pop(name, {}),
},
)
- for name, entry in iteritems(have):
+ for name, entry in have.items():
if name != "as_number":
self.compare(
parsers=parsers,
@@ -217,7 +215,7 @@ class Bgp_global(ResourceModule):
hneigh = have.pop("neighbor", {})
self._compare_neigh_lists(wneigh, hneigh)
- for name, entry in iteritems(wneigh):
+ for name, entry in wneigh.items():
for k, v in entry.items():
if k == "address":
continue
@@ -233,7 +231,7 @@ class Bgp_global(ResourceModule):
},
have={"as_number": want["as_number"], "neighbor": h},
)
- for name, entry in iteritems(hneigh):
+ for name, entry in hneigh.items():
if name not in wneigh.keys():
if self._check_af(name):
msg = "Use the _bgp_address_family module to delete the address_family under neighbor {0}, before replacing/deleting the neighbor.".format(
@@ -281,7 +279,7 @@ class Bgp_global(ResourceModule):
wbgp = want.pop("bgp_params", {})
hbgp = have.pop("bgp_params", {})
- for name, entry in iteritems(wbgp):
+ for name, entry in wbgp.items():
if name == "confederation":
if entry != hbgp.pop(name, {}):
self.addcmd(
@@ -325,7 +323,7 @@ class Bgp_global(ResourceModule):
if not wbgp and hbgp:
self.commands.append("delete protocols bgp" + self._asn_mod + " parameters")
hbgp = {}
- for name, entry in iteritems(hbgp):
+ for name, entry in hbgp.items():
if name == "confederation":
self.commands.append(
"delete protocols bgp" + self._asn_mod + " parameters confederation",
@@ -362,7 +360,7 @@ class Bgp_global(ResourceModule):
]:
wdict = want.pop(attrib, {})
hdict = have.pop(attrib, {})
- for key, entry in iteritems(wdict):
+ for key, entry in wdict.items():
if entry != hdict.pop(key, {}):
self.addcmd(entry, "neighbor.{0}".format(attrib), False)
# remove remaining items in have for replaced
@@ -370,7 +368,7 @@ class Bgp_global(ResourceModule):
self.addcmd(entry, "neighbor.{0}".format(attrib), True)
def _bgp_global_list_to_dict(self, entry):
- for name, proc in iteritems(entry):
+ for name, proc in entry.items():
if "neighbor" in proc:
neigh_dict = {}
for entry in proc.get("neighbor", []):
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 34dc0ed6..289037ef 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
@@ -10,14 +10,13 @@ is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -28,12 +27,16 @@ 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.utils.utils import (
+ in_target_not_none,
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
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import (
+ get_os_version,
+ load_config,
+)
class Firewall_global(ConfigBase):
@@ -74,6 +77,13 @@ class Firewall_global(ConfigBase):
warnings = list()
commands = list()
+ try:
+ self._module.params["comment"]
+ except KeyError:
+ comment = []
+ else:
+ comment = self._module.params["comment"]
+
if self.state in self.ACTION_STATES:
existing_firewall_global_facts = self.get_firewall_global_facts()
else:
@@ -82,6 +92,12 @@ class Firewall_global(ConfigBase):
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_firewall_global_facts))
+ if commands and self._module._diff:
+ commit = not self._module.check_mode
+ diff = load_config(self._module, commands, commit=commit, comment=comment)
+ if diff:
+ result["diff"] = {"prepared": str(diff)}
+
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
@@ -189,7 +205,7 @@ class Firewall_global(ConfigBase):
"twa_hazards_protection",
)
if want:
- for key, val in iteritems(want):
+ for key, val in want.items():
if val and key in b_set and not have:
commands.append(self._form_attr_cmd(attr=key, opr=False))
elif val and key in b_set and have and key in have and have[key] != val:
@@ -199,7 +215,7 @@ class Firewall_global(ConfigBase):
elif not want and have:
commands.append(self._compute_command(opr=False))
elif have:
- for key, val in iteritems(have):
+ for key, val in have.items():
if val and key in b_set:
commands.append(self._form_attr_cmd(attr=key, opr=False))
else:
@@ -225,6 +241,8 @@ class Firewall_global(ConfigBase):
commands.extend(self._render_state_policy(key, w, h, opr=opr))
elif key == "route_redirects":
commands.extend(self._render_route_redirects(key, w, h, opr=opr))
+ elif key == "zone":
+ commands.extend(self._render_zone(key, w, h, opr=opr))
return commands
def _add_global_attr(self, w, h, opr=True):
@@ -246,7 +264,7 @@ class Firewall_global(ConfigBase):
"twa_hazards_protection",
)
if w_fg:
- for key, val in iteritems(w_fg):
+ for key, val in w_fg.items():
if opr and key in l_set and not (h and self._is_w_same(w_fg, h, key)):
commands.append(
self._form_attr_cmd(attr=key, val=self._bool_to_str(val), opr=opr),
@@ -257,11 +275,7 @@ class Firewall_global(ConfigBase):
self._form_attr_cmd(attr=key, key=self._bool_to_str(val), opr=opr),
)
continue
- if (
- key in l_set
- and not self._in_target(h, key)
- and not self._is_del(l_set, h)
- ):
+ if key in l_set and not self._in_target(h, key) and not self._is_del(l_set, h):
commands.append(
self._form_attr_cmd(attr=key, val=self._bool_to_str(val), opr=opr),
)
@@ -284,13 +298,13 @@ class Firewall_global(ConfigBase):
if h:
h_ping = h.get(attr) or {}
if self._is_root_del(w[attr], h_ping, attr):
- for item, value in iteritems(h[attr]):
+ for item, value in h[attr].items():
if not opr and item in l_set:
commands.append(self._form_attr_cmd(attr=item, opr=opr))
elif w[attr]:
if h and attr in h.keys():
h_ping = h.get(attr) or {}
- for item, value in iteritems(w[attr]):
+ for item, value in w[attr].items():
if (
opr
and item in l_set
@@ -354,7 +368,7 @@ class Firewall_global(ConfigBase):
cmd = self._compute_command(key="group", attr="ipv6-" + attr, opr=opr)
else:
cmd = self._compute_command(key="group", attr=attr, opr=opr)
- for key, val in iteritems(want):
+ for key, val in want.items():
if val:
if opr and key in l_set and not (h and self._is_w_same(want, h, key)):
if key == "name":
@@ -372,12 +386,18 @@ class Firewall_global(ConfigBase):
)
elif not opr and key in l_set:
if key == "name" and self._is_grp_del(h, want, key):
+ if len(commands) > 0 and commands[-1] == cmd + " " + want[
+ "name"
+ ] + " " + self._grp_type(
+ attr,
+ ):
+ commands.pop()
commands.append(cmd + " " + want["name"])
continue
- if not (h and self._in_target(h, key)) and not self._is_grp_del(
+ if not (h and in_target_not_none(h, key)) and not self._is_grp_del(
h,
want,
- key,
+ "name",
):
commands.append(cmd + " " + want["name"] + " " + key)
elif key == "members":
@@ -438,6 +458,10 @@ class Firewall_global(ConfigBase):
+ " "
+ member[self._get_mem_type(type)],
)
+ elif not opr and not have:
+ commands.append(
+ cmd + " " + name + " " + self._grp_type(type),
+ )
return commands
def _get_mem_type(self, group):
@@ -473,10 +497,12 @@ class Firewall_global(ConfigBase):
if want:
for w in want:
h = self.search_attrib_in_have(have, w, "connection_type")
- for key, val in iteritems(w):
+ for key, val in w.items():
if val and key != "connection_type":
if opr and key in l_set and not (h and self._is_w_same(w, h, key)):
- if key == "log" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ if key == "log" and LooseVersion(
+ get_os_version(self._module),
+ ) >= LooseVersion("1.4"):
commands.append(
self._form_attr_cmd(
key=attr + " " + w["connection_type"],
@@ -502,7 +528,9 @@ class Firewall_global(ConfigBase):
),
)
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]):
+ 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(
@@ -534,23 +562,23 @@ 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']
+ if "afi" in w:
+ afi = w["afi"]
else:
- if h and 'afi' in h:
- afi = h['afi']
+ 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":
+ for key, val in w.items():
+ if val is not None and key != "afi":
if opr and key in l_set and not (h and self._is_w_same(w, h, key)):
commands.append(
self._form_attr_cmd(
attr=key,
val=self._bool_to_str(val),
opr=opr,
- type=afi
+ type=afi,
),
)
elif not opr and key in l_set:
@@ -560,7 +588,7 @@ class Firewall_global(ConfigBase):
attr=key,
val=self._bool_to_str(val),
opr=opr,
- type=afi
+ type=afi,
),
)
continue
@@ -570,7 +598,7 @@ class Firewall_global(ConfigBase):
attr=key,
val=self._bool_to_str(val),
opr=opr,
- type=afi
+ type=afi,
),
)
elif key == "icmp_redirects":
@@ -590,20 +618,25 @@ class Firewall_global(ConfigBase):
commands = []
h_red = {}
l_set = ("send", "receive")
- if w and 'afi' in w:
- afi = w['afi']
+ if w and "afi" in w:
+ afi = w["afi"]
else:
- if h and 'afi' in h:
- afi = h['afi']
+ 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]):
+ for item, value in w[attr].items():
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, type=afi)
+ self._form_attr_cmd(
+ attr=item,
+ val=self._bool_to_str(value),
+ opr=opr,
+ type=afi,
+ ),
)
elif (
not opr
@@ -637,7 +670,12 @@ class Firewall_global(ConfigBase):
:param type: AF type of attribute.
:return: generated command.
"""
- command = self._compute_command(key=key, attr=self._map_attrib(attr, type=type), 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):
@@ -654,14 +692,20 @@ class Firewall_global(ConfigBase):
cmd = "delete firewall "
else:
cmd = "set firewall "
- if attr and key != "group" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ if (
+ attr
+ and key not in ["group", "zone"]
+ 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:
- if key == "state_policy" and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ if key == "state_policy" and LooseVersion(get_os_version(self._module)) >= LooseVersion(
+ "1.4",
+ ):
cmd += ""
else:
cmd += " '" + str(val) + "'"
@@ -768,3 +812,324 @@ class Firewall_global(ConfigBase):
elif attrib == "validation":
regex = "source-validation"
return regex
+
+ def _render_zone(self, attr, w, h, opr):
+ """
+ This function forms the commands for group list/members attributes based on the 'opr'.
+ :param attr: attribute name.
+ :param w: the desired config.
+ :param h: the target config.
+ :param opr: True/False.
+ :return: generated list of commands.
+ """
+ commands = []
+ h_grp = []
+ w_grp = []
+ l_set = ("name", "description", "default_action", "default_log", "local_zone")
+ if w:
+ w_grp = w.get(attr) or []
+ if h:
+ h_grp = h.get(attr) or []
+
+ if w_grp:
+ for want in w_grp:
+ h = self.search_attrib_in_have(h_grp, want, "name")
+
+ cmd = self._compute_command(key="zone", attr="", opr=opr)
+
+ if not opr and self._is_grp_del(h, want, "name"):
+ commands.append(cmd + " " + want["name"])
+ continue
+
+ for key, val in want.items():
+ if val:
+ if opr and key in l_set and not (h and self._is_w_same(want, h, key)):
+ if key == "name":
+ pass
+ elif isinstance(val, bool):
+ commands.append(
+ cmd + " " + want["name"] + " " + key.replace("_", "-"),
+ )
+ else:
+ commands.append(
+ cmd
+ + " "
+ + want["name"]
+ + " "
+ + key.replace("_", "-")
+ + " '"
+ + str(want[key])
+ + "'",
+ )
+ elif not opr and key in l_set:
+ if not (h and in_target_not_none(h, key)) and not self._is_grp_del(
+ h,
+ want,
+ "name",
+ ):
+ commands.append(
+ cmd + " " + want["name"] + " " + key.replace("_", "-"),
+ )
+ elif key == "interfaces":
+ commands.extend(
+ self._render_interfaces(
+ key,
+ want,
+ h,
+ opr,
+ cmd,
+ want["name"],
+ attr,
+ ),
+ )
+ elif key == "intra_zone_filtering":
+ commands.extend(
+ self._render_izf(
+ key,
+ want,
+ h,
+ opr,
+ cmd,
+ want["name"],
+ attr,
+ ),
+ )
+ elif key == "sources":
+ commands.extend(
+ self._render_sources(
+ key,
+ want,
+ h,
+ opr,
+ cmd,
+ want["name"],
+ attr,
+ ),
+ )
+ return commands
+
+ def _render_interfaces(self, attr, w, h, opr, cmd, name, type):
+ """
+ This function forms the commands for interfaces
+ based on the 'opr'.
+ :param attr: attribute name.
+ :param w: the desired config.
+ :param h: the target config.
+ :param cmd: commands to be prepend.
+ :param name: name of group.
+ :param type: group type.
+ :return: generated list of commands.
+ """
+ commands = []
+ have = []
+ if w:
+ want = w.get(attr) or []
+ if h:
+ have = h.get(attr) or []
+
+ # VyOS 1.5.0 GA moved 'interface' under a new 'member' node
+ # ("set firewall zone <name> member interface <ifname>"). 1.4.x and
+ # 1.5-rolling snapshots predating this change still use the bare
+ # 'interface' node. Known limitation: a 1.5-rolling build reporting
+ # "1.5" that predates this change will incorrectly get the new
+ # syntax -- accepted trade-off, see PR notes.
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.5"):
+ iface_kw = "member interface"
+ else:
+ iface_kw = "interface"
+
+ if want:
+ if opr:
+ interfaces = list_diff_want_only(want, have)
+
+ for interface in interfaces:
+ commands.append(
+ cmd + " " + name + " " + iface_kw + " " + interface,
+ )
+ elif not opr and have:
+ interfaces = list_diff_want_only(want, have)
+ for interface in interfaces:
+ commands.append(
+ cmd + " " + name + " " + iface_kw + " " + interface,
+ )
+ elif not opr and not have:
+ for interface in want:
+ commands.append(
+ cmd + " " + name + " " + iface_kw + " " + interface,
+ )
+ else:
+ self._module.fail_json(msg={"want": want, "have": have, "opr": opr})
+
+ return commands
+
+ def _render_izf(self, attr, w, h, opr, cmd, name, type):
+ """
+ This function forms the commands for intra zone filtering
+ based on the 'opr'.
+ :param attr: attribute name.
+ :param w: the desired config.
+ :param h: the target config.
+ :param cmd: commands to be prepend.
+ :param name: name of group.
+ :param type: group type.
+ :return: generated list of commands.
+ """
+ commands = []
+ have = []
+ if w:
+ want = w.get(attr) or []
+ if h:
+ have = h.get(attr) or []
+
+ if want:
+ if opr:
+ izfs = self._dict_diff(want, have)
+ for izf in izfs:
+ commands.append(
+ cmd
+ + " "
+ + name
+ + " intra-zone-filtering "
+ + izf[0].replace(".", " ")
+ + " "
+ + izf[1],
+ )
+ elif not opr and have:
+ izfs = self._dict_diff(want, have)
+
+ for izf in izfs:
+ commands.append(
+ cmd + " " + name + " intra-zone-filtering " + izf[0].replace(".", " "),
+ )
+ elif not opr and not have:
+ commands.append(
+ cmd + " " + name + " intra-zone-filtering",
+ )
+ return commands
+
+ def _dict_diff(self, want, have, path=""):
+ """
+ Recursively find keys/values in `want` that differ or are missing in `have`.
+ Returns list of tuples: (full_path, value_in_want)
+ """
+ diffs = []
+
+ have = have or {}
+
+ for key, want_val in want.items():
+ current_path = f"{path}.{key.replace('_', '-')}" if path else key.replace("_", "-")
+
+ if key not in have:
+ if isinstance(want_val, dict):
+ diffs.extend(self._dict_diff(want_val, {}, current_path))
+ elif isinstance(want_val, list):
+ for i, item in enumerate(want_val):
+ if isinstance(item, dict):
+ diffs.extend(self._dict_diff(item, {}, f"{current_path}[{i}]"))
+ else:
+ diffs.append((f"{current_path}[{i}]", item))
+ else:
+ diffs.append((current_path, want_val))
+
+ else:
+ have_val = have[key]
+
+ if isinstance(want_val, dict) and isinstance(have_val, dict):
+ diffs.extend(self._dict_diff(want_val, have_val, current_path))
+
+ elif isinstance(want_val, list) and isinstance(have_val, list):
+ for i, item in enumerate(want_val):
+ if i >= len(have_val):
+ diffs.append((f"{current_path}[{i}]", item))
+ elif isinstance(item, dict) and isinstance(have_val[i], dict):
+ diffs.extend(
+ self._dict_diff(item, have_val[i], f"{current_path}[{i}]"),
+ )
+ elif item != have_val[i]:
+ diffs.append((f"{current_path}[{i}]", item))
+
+ elif want_val != have_val:
+ diffs.append((current_path, want_val))
+
+ return diffs
+
+ def _render_sources(self, attr, w, h, opr, cmd, name, type):
+ """
+ This function forms the commands for sources (from)
+ based on the 'opr'.
+ :param attr: attribute name.
+ :param w: the desired config.
+ :param h: the target config.
+ :param cmd: commands to be prepend.
+ :param name: name of group.
+ :param type: group type.
+ :return: generated list of commands.
+ """
+ commands = []
+ have = []
+ if w:
+ want = w.get(attr) or []
+ if h:
+ have = h.get(attr) or []
+
+ have_index = {item["zone"]: item for item in have}
+
+ for item1 in want:
+ zone = item1["zone"]
+
+ if zone in have_index:
+ item2 = have_index[zone]
+
+ wfw = item1.get("firewall", {})
+ hfw = item2.get("firewall", {})
+ if wfw:
+ if opr:
+ sources = self._dict_diff(wfw, hfw)
+ for source in sources:
+ commands.append(
+ cmd
+ + " "
+ + name
+ + " from "
+ + zone
+ + " firewall "
+ + source[0].replace("_", "-")
+ + " "
+ + source[1],
+ )
+ elif not opr and hfw:
+ sources = self._dict_diff(wfw, hfw)
+ for source in sources:
+ commands.append(
+ cmd
+ + " "
+ + name
+ + " from "
+ + zone
+ + " firewall "
+ + source[0].replace("_", "-"),
+ )
+ elif not opr and not hfw:
+ commands.append(
+ cmd + " " + name + " from " + zone,
+ )
+ elif opr:
+ wfw = item1.get("firewall", {})
+ for key, val in wfw.items():
+ if val:
+ commands.append(
+ cmd
+ + " "
+ + name
+ + " from "
+ + zone
+ + " firewall "
+ + key.replace("_", "-")
+ + " "
+ + val,
+ )
+ elif not opr:
+ commands.append(
+ cmd + " " + name + " from " + zone,
+ )
+ return commands
diff --git a/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py b/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py
index 85a8042f..ec1aaef1 100644
--- a/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/firewall_interfaces/firewall_interfaces.py
@@ -10,8 +10,8 @@ is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
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 2942b191..be36d3fe 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
@@ -10,14 +10,13 @@ is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -30,10 +29,13 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
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
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import (
+ get_os_version,
+ load_config,
+)
class Firewall_rules(ConfigBase):
@@ -78,6 +80,14 @@ class Firewall_rules(ConfigBase):
result = {"changed": False}
warnings = list()
commands = list()
+ diff = None
+
+ try:
+ self._module.params["comment"]
+ except KeyError:
+ comment = []
+ else:
+ comment = self._module.params["comment"]
if self.state in self.ACTION_STATES:
existing_firewall_rules_facts = self.get_firewall_rules_facts()
@@ -87,6 +97,12 @@ class Firewall_rules(ConfigBase):
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(deepcopy(existing_firewall_rules_facts)))
+ if commands and self._module._diff:
+ commit = not self._module.check_mode
+ diff = load_config(self._module, commands, commit=commit, comment=comment)
+ if diff:
+ result["diff"] = {"prepared": str(diff)}
+
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
@@ -213,13 +229,17 @@ class Firewall_rules(ConfigBase):
commands.append(self._compute_command(rs_id, remove=True))
# Blank out the only rule set that it is removed.
for entry in have:
- if entry['afi'] == rs_id['afi'] and rs_id['name']:
+ if entry["afi"] == rs_id["afi"] and rs_id["name"]:
entry["rule_sets"] = [
- rule_set for rule_set in entry["rule_sets"] if rule_set.get("name") != rs_id['name']
+ rule_set
+ for rule_set in entry["rule_sets"]
+ if rule_set.get("name") != rs_id["name"]
]
- elif entry['afi'] == rs_id['afi'] and rs_id['filter']:
+ elif entry["afi"] == rs_id["afi"] and rs_id["filter"]:
entry["rule_sets"] = [
- rule_set for rule_set in entry["rule_sets"] if rule_set.get("filter") != rs_id['filter']
+ rule_set
+ for rule_set in entry["rule_sets"]
+ if rule_set.get("filter") != rs_id["filter"]
]
commands.extend(self._state_merged(want, have))
return commands
@@ -264,7 +284,7 @@ class Firewall_rules(ConfigBase):
for h in have:
if h["afi"] == w["afi"]:
commands.append(
- self._compute_command(self._rs_id(None, w["afi"]), remove=True)
+ self._compute_command(self._rs_id(None, w["afi"]), remove=True),
)
elif have:
for h in have:
@@ -294,7 +314,7 @@ class Firewall_rules(ConfigBase):
h_rs = deepcopy(remove_empties(have))
h_rules = h_rs.pop("rules", None)
if w_rs:
- for key, val in iteritems(w_rs):
+ for key, val in w_rs.items():
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]):
@@ -338,6 +358,7 @@ class Firewall_rules(ConfigBase):
"disable",
"description",
"jump_target",
+ "offload_target",
)
if w_rules:
for w in w_rules:
@@ -345,7 +366,7 @@ class Firewall_rules(ConfigBase):
h = self.search_rules_in_have_rs(h_rules, w["number"])
if w != h and self.state == "replaced":
h = {}
- for key, val in iteritems(w):
+ for key, val in w.items():
if val:
if opr and key in l_set and not (h and self._is_w_same(w, h, key)):
if key == "disable":
@@ -443,7 +464,7 @@ class Firewall_rules(ConfigBase):
if w[attr]:
if h and attr in h.keys():
h_state = h.get(attr) or {}
- for item, val in iteritems(w[attr]):
+ for item, val in w[attr].items():
if (
opr
and item in l_set
@@ -452,7 +473,9 @@ class Firewall_rules(ConfigBase):
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)))
+ 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
@@ -504,7 +527,7 @@ class Firewall_rules(ConfigBase):
if w[attr]:
if h and attr in h.keys():
h_recent = h.get(attr) or {}
- for item, val in iteritems(w[attr]):
+ for item, val in w[attr].items():
if (
opr
and item in l_set
@@ -532,7 +555,7 @@ class Firewall_rules(ConfigBase):
if w[attr]:
if h and attr in h.keys():
h_icmp = h.get(attr) or {}
- for item, val in iteritems(w[attr]):
+ for item, val in w[attr].items():
if (
opr
and item in l_set
@@ -555,7 +578,9 @@ class Firewall_rules(ConfigBase):
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)))
+ commands.append(
+ cmd + (" " + attr + " " + item.replace("_", "-") + " " + str(val)),
+ )
return commands
def _add_interface(self, attr, w, h, cmd, opr):
@@ -573,15 +598,15 @@ class Firewall_rules(ConfigBase):
if w[attr]:
if h and attr in h.keys():
h_if = h.get(attr) or {}
- for item, val in iteritems(w[attr]):
+ for item, val in w[attr].items():
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)
+ + (" " + 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("_", "-"))
+ cmd + (" " + attr.replace("_", "-") + " " + item.replace("_", "-")),
)
return commands
@@ -608,7 +633,7 @@ class Firewall_rules(ConfigBase):
if w[attr]:
if h and attr in h.keys():
h_time = h.get(attr) or {}
- for item, val in iteritems(w[attr]):
+ for item, val in w[attr].items():
if (
opr
and item in l_set
@@ -654,14 +679,14 @@ class Firewall_rules(ConfigBase):
for flag in flags:
invert = flag.get("invert", False)
commands.append(
- cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"])
+ 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"])
+ cmd + (" " + attr + " flags " + ("not " if invert else "") + flag["flag"]),
)
return commands
@@ -837,7 +862,7 @@ class Firewall_rules(ConfigBase):
h_group = {}
if h and h.get(attr) and key in h[attr].keys():
h_group = h[attr].get(key)
- for item, val in iteritems(group):
+ for item, val in group.items():
if val:
if (
opr
@@ -969,7 +994,10 @@ class Firewall_rules(ConfigBase):
if number:
cmd += " rule " + str(number)
if attrib:
- if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4") and attrib == "enable_default_log":
+ if (
+ LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
+ and attrib == "enable_default_log"
+ ):
cmd += " " + "default-log"
else:
cmd += " " + attrib.replace("_", "-")
@@ -1107,15 +1135,23 @@ class Firewall_rules(ConfigBase):
for item in rs:
self._prune_stubs(item)
elif isinstance(rs, dict):
- keys_to_remove = [key for key, value in rs.items()
- if (
- (key == "disable" and value is False)
- or
- (key == "log" and value == "disable" and
- LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"))
- or
- (key in ["new", "invalid", "related", "established"] and value is False and
- LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")))]
+ keys_to_remove = [
+ key
+ for key, value in rs.items()
+ if (
+ (key == "disable" and value is False)
+ or (
+ key == "log"
+ and value == "disable"
+ and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
+ )
+ or (
+ key in ["new", "invalid", "related", "established"]
+ and value is False
+ and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
+ )
+ )
+ ]
for key in keys_to_remove:
del rs[key]
for key in rs:
@@ -1131,10 +1167,20 @@ class Firewall_rules(ConfigBase):
return True
elif isinstance(w, list) and isinstance(rs, list):
try:
- sorted_list1 = sorted(w, key=lambda x: str(x)) # pylint: disable=unnecessary-lambda
- sorted_list2 = sorted(rs, key=lambda x: str(x)) # pylint: disable=unnecessary-lambda
+
+ def comparison(x):
+ if "name" in x:
+ return x["name"]
+ if "number" in x:
+ return x["number"]
+ return str(x)
+
+ sorted_list1 = sorted(w, key=comparison)
+ sorted_list2 = sorted(rs, key=comparison)
except TypeError:
return False
+ if len(sorted_list1) != len(sorted_list2):
+ return False
return all(self._is_same_rs(x, y) for x, y in zip(sorted_list1, sorted_list2))
else:
return w == rs
diff --git a/plugins/module_utils/network/vyos/config/ha/__init__.py b/plugins/module_utils/network/vyos/config/ha/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/ha/__init__.py
diff --git a/plugins/module_utils/network/vyos/config/ha/ha.py b/plugins/module_utils/network/vyos/config/ha/ha.py
new file mode 100644
index 00000000..7fc96a35
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/ha/ha.py
@@ -0,0 +1,705 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The vyos_ha config file.
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to its desired end-state is
+created.
+"""
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
+ ResourceModule,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_empties,
+)
+
+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.ha import (
+ HaTemplate,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import combine
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
+
+
+class Ha(ResourceModule):
+ """
+ The vyos_ha config class
+ """
+
+ def __init__(self, module):
+ super(Ha, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="ha",
+ tmplt=HaTemplate(),
+ )
+ self.parsers = [
+ "disable",
+ ]
+
+ self._validate_template()
+
+ def _validate_template(self):
+ version = get_os_version(self._module)
+ if LooseVersion(version) >= LooseVersion("1.4"):
+ self._tmplt = HaTemplate()
+ else:
+ self._module.fail_json(msg="High Availability is not supported in this version of VyOS")
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+
+ if self.state not in ["parsed", "gathered", "purged"]:
+ self.generate_commands()
+ self.run_commands()
+
+ if self.state == "purged":
+ wantd = {"disable": False}
+ haved = deepcopy(self.have)
+ if wantd != haved:
+ self.commands = ["delete high-availability"]
+ self.run_commands()
+ if "before" in self.result:
+ self._normalize_lists(self.result["before"])
+ if "after" in self.result:
+ self._normalize_lists(self.result["after"])
+ if "parsed" in self.result:
+ self._normalize_lists(self.result["parsed"])
+ return self.result
+
+ def generate_commands(self):
+ """Generate configuration commands to send based on
+ want, have and desired state.
+ """
+ wantd = deepcopy(self.want)
+ haved = deepcopy(self.have)
+ for entry in wantd, haved:
+ self._list_to_named_dict(entry)
+ self._normalize_lists(entry)
+
+ if self.state in ["deleted"]:
+ wantd, haved, p = self._prune_stubs(self._module.params.get("config", {}), haved)
+
+ if self.state in ["overridden"]:
+ wo = deepcopy(wantd)
+ self._diff_w_h(wo, haved)
+
+ haved_disable = haved.get("disable")
+
+ for k1, v1 in wo.items():
+
+ if not isinstance(v1, dict):
+ continue
+
+ for name, obj in v1.items():
+ if isinstance(obj, dict) and not obj:
+ wi, hi, pi = self._prune_stubs({k1: {name: {}}}, haved)
+ haved = hi
+
+ for k2, v2 in v1.items():
+ if not isinstance(v2, dict):
+ continue
+
+ for name, obj in v2.items():
+ if isinstance(obj, dict) and not obj:
+ wi, hi, pi = self._prune_stubs({k1: {k2: {name: {}}}}, haved)
+ haved = hi
+
+ if haved_disable is not None:
+ haved["disable"] = haved_disable
+
+ keys = set(wantd) | set(haved)
+
+ for k in keys:
+
+ want = wantd.get(k, {})
+ have = haved.get(k, {})
+
+ if k == "vrrp":
+ if self.state in ["merged"]:
+ want = combine(have, want, recursive=True, list_merge="append_rp")
+ self._compare_vrrp(want, have)
+
+ if k == "virtual_servers":
+ if self.state in ["merged"]:
+ want = combine(have, want, recursive=True)
+ self._compare_vsrvs(want, have)
+
+ if self.state in ["deleted"] and k == "disable":
+ want = have
+ if self.state in ["overridden"] and k == "disable" and not want:
+ want = False
+ if self.state in ["rendered"]:
+ have = None
+
+ self.compare(
+ parsers=self.parsers,
+ want={k: want},
+ have={k: have},
+ )
+
+ self.commands = list(dict.fromkeys(self.commands))
+
+ def _compare_vsrvs(self, want, have):
+ """Compare virtual servers.
+
+ Pre-index both want and have by (name, attribute) signature so that
+ each lookup is O(1) instead of O(n). Groups that are identical
+ between want and have are skipped entirely via an equality
+ short-circuit before leaf decomposition.
+ """
+ vs_parsers = [
+ "virtual_servers.address",
+ "virtual_servers.algorithm",
+ "virtual_servers.delay_loop",
+ "virtual_servers.forward_method",
+ "virtual_servers.persistence_timeout",
+ "virtual_servers.fwmark",
+ "virtual_servers.port",
+ "virtual_servers.protocol",
+ "virtual_servers.real_server.port",
+ "virtual_servers.real_server.health_check_script",
+ "virtual_servers.real_server.connection_timeout",
+ ]
+
+ want_index = (
+ {vs["name"]: vs for vs in want.values() if isinstance(vs, dict) and vs.get("name")}
+ if isinstance(want, dict)
+ else {}
+ )
+ have_index = (
+ {vs["name"]: vs for vs in have.values() if isinstance(vs, dict) and vs.get("name")}
+ if isinstance(have, dict)
+ else {}
+ )
+
+ all_names = set(want_index) | set(have_index)
+
+ for name in all_names:
+ w = want_index.get(name, {})
+ h = have_index.get(name, {})
+
+ if w == h and self.state not in ["rendered"]:
+ continue
+
+ wlist = self._extract_named_leafs(w) if w else []
+ hlist = self._extract_named_leafs(h) if h else []
+
+ if self.state == "rendered":
+ hlist = []
+
+ def _vsrv_sig(item):
+ if not isinstance(item, dict):
+ return None
+ iname = item.get("name")
+ if not iname:
+ return None
+ if "real_server" in item:
+ rs = item["real_server"]
+ if not isinstance(rs, dict) or "address" not in rs:
+ return None
+ addr = rs["address"]
+ for k in rs:
+ if k != "address":
+ return ("real_server", iname, addr, k)
+ return ("real_server", iname, addr, None)
+ for k in item:
+ if k != "name":
+ return ("attr", iname, k)
+ return None
+
+ have_leaf_index = {}
+ for hdict in hlist:
+ sig = _vsrv_sig(hdict)
+ if sig is not None:
+ have_leaf_index[sig] = hdict
+
+ want_leaf_index = {}
+ for wdict in wlist:
+ sig = _vsrv_sig(wdict)
+ if sig is not None:
+ want_leaf_index[sig] = wdict
+
+ if self.state in ["replaced", "deleted"]:
+ for sig, hdict in have_leaf_index.items():
+ wdict = want_leaf_index.get(sig, {})
+ if self.state == "deleted" and wdict:
+ wdict = {}
+ elif not wdict:
+ hdict = {}
+ self.compare(
+ parsers=vs_parsers,
+ want={"virtual_servers": wdict},
+ have={"virtual_servers": hdict},
+ )
+
+ if self.state in ["merged", "replaced", "rendered", "overridden"]:
+ for sig, wdict in want_leaf_index.items():
+ hdict = have_leaf_index.get(sig, {})
+ self.compare(
+ parsers=vs_parsers,
+ want={"virtual_servers": wdict},
+ have={"virtual_servers": hdict},
+ )
+
+ def _compare_vrrp(self, want, have):
+ """Compare VRRP groups and sync-groups.
+
+ Pre-index groups by name so matching is O(1). Groups that are
+ identical between want and have are skipped via equality
+ short-circuit before any leaf decomposition occurs — this is the
+ dominant performance win for large idempotent runs.
+ """
+ vrrp_parsers = [
+ "vrrp.snmp",
+ "vrrp.global_parameters",
+ "vrrp.global_parameters.garp",
+ "vrrp.groups",
+ "vrrp.groups.disable",
+ "vrrp.groups.no_preempt",
+ "vrrp.groups.rfc3768_compatibility",
+ "vrrp.groups.address",
+ "vrrp.groups.excluded_address",
+ "vrrp.groups.garp",
+ "vrrp.groups.authentication",
+ "vrrp.groups.transition_script",
+ "vrrp.groups.health_check",
+ "vrrp.groups.track.interface",
+ "vrrp.groups.track.exclude_vrrp_interface",
+ "vrrp.sync_groups.member",
+ "vrrp.sync_groups.transition_script",
+ "vrrp.sync_groups.health_check",
+ ]
+
+ if (
+ have.get("snmp") == "enabled"
+ and want.get("snmp") != "enabled"
+ and self.state not in ["deleted", "overridden"]
+ and (self.state != "merged" or "snmp" in want)
+ ):
+ self.commands.append("delete high-availability vrrp snmp")
+
+ non_named = {k: v for k, v in (want or {}).items() if k not in ("groups", "sync_groups")}
+ non_named_have = {
+ k: v for k, v in (have or {}).items() if k not in ("groups", "sync_groups")
+ }
+
+ hlist_non = self._extract_leaf_items(non_named_have)
+ wlist_non = self._extract_leaf_items(non_named)
+
+ if self.state == "rendered":
+ hlist_non = []
+
+ have_non_index = {}
+ for hdict in hlist_non:
+ sig = self._vrrp_leaf_sig(hdict)
+ have_non_index[sig] = hdict
+
+ want_non_index = {}
+ for wdict in wlist_non:
+ sig = self._vrrp_leaf_sig(wdict)
+ want_non_index[sig] = wdict
+
+ if self.state in ["replaced", "deleted"]:
+ for sig, hdict in have_non_index.items():
+ wdict = want_non_index.get(sig, {})
+ if self.state == "deleted" and wdict:
+ wdict = {}
+ if self.state == "replaced" and wdict and wdict != hdict:
+ wdict = {}
+ elif not wdict:
+ hdict = {}
+ self.compare(parsers=vrrp_parsers, want={"vrrp": wdict}, have={"vrrp": hdict})
+
+ if self.state in ["merged", "replaced", "rendered", "overridden"]:
+ for sig, wdict in want_non_index.items():
+ hdict = have_non_index.get(sig, {})
+ self.compare(parsers=vrrp_parsers, want={"vrrp": wdict}, have={"vrrp": hdict})
+
+ for section in ("groups", "sync_groups"):
+ want_objs = (want or {}).get(section, {})
+ have_objs = (have or {}).get(section, {})
+
+ if not isinstance(want_objs, dict):
+ want_objs = {}
+ if not isinstance(have_objs, dict):
+ have_objs = {}
+
+ all_names = set(want_objs) | set(have_objs)
+
+ for name in all_names:
+ w = want_objs.get(name, {})
+ h = have_objs.get(name, {})
+
+ if w == h and self.state not in ["rendered"]:
+ continue
+
+ wlist = self._extract_leaf_items({section: {name: w}}) if w else []
+ hlist = self._extract_leaf_items({section: {name: h}}) if h else []
+
+ if self.state == "rendered":
+ hlist = []
+
+ have_leaf_index = {}
+ for hdict in hlist:
+ sig = self._vrrp_leaf_sig(hdict)
+ have_leaf_index[sig] = hdict
+
+ want_leaf_index = {}
+ for wdict in wlist:
+ sig = self._vrrp_leaf_sig(wdict)
+ want_leaf_index[sig] = wdict
+
+ if self.state in ["replaced", "deleted"]:
+ for sig, hdict in have_leaf_index.items():
+ wdict = want_leaf_index.get(sig, {})
+ if self.state == "deleted" and wdict:
+ wdict = {}
+ if self.state == "replaced" and wdict and wdict != hdict:
+ wdict = {}
+ elif not wdict:
+ hdict = {}
+ self.compare(
+ parsers=vrrp_parsers,
+ want={"vrrp": wdict},
+ have={"vrrp": hdict},
+ )
+
+ if self.state in ["merged", "replaced", "rendered", "overridden"]:
+ for sig, wdict in want_leaf_index.items():
+ hdict = have_leaf_index.get(sig, {})
+ self.compare(
+ parsers=vrrp_parsers,
+ want={"vrrp": wdict},
+ have={"vrrp": hdict},
+ )
+
+ def _vrrp_leaf_sig(self, item):
+ """Build a hashable signature for a VRRP leaf dict for O(1) indexing."""
+ if not isinstance(item, dict) or not item:
+ return ()
+
+ container = next(iter(item))
+ inner = item[container]
+
+ sig = [container]
+
+ if isinstance(inner, dict) and "name" in inner:
+ sig.append(("name", inner["name"]))
+
+ if isinstance(inner, dict):
+ for k, v in inner.items():
+ if k == "name":
+ continue
+ if not isinstance(v, dict):
+ sig.append(k)
+ break
+ sig.append(k)
+ for leaf in v:
+ sig.append(leaf)
+ break
+ break
+
+ return tuple(sig)
+
+ def _list_to_named_dict(self, data):
+ """Convert all named-object lists to name-keyed dicts in-place.
+
+ Replaces the three separate _vrrp_groups_list_to_dict,
+ _vrrp_sync_groups_list_to_dict, and _virtual_servers_list_to_dict
+ methods with a single helper. Also normalises real_server lists
+ inside virtual servers.
+ """
+ # VRRP groups and sync_groups
+ vrrp = data.get("vrrp", {})
+ for key in ("groups", "sync_groups"):
+ items = vrrp.get(key)
+ if isinstance(items, list):
+ vrrp[key] = {
+ item["name"]: item
+ for item in items
+ if isinstance(item, dict) and item.get("name")
+ }
+
+ # Virtual servers
+ vss = data.get("virtual_servers")
+ if isinstance(vss, list):
+ new_vss = {}
+ for vs in vss:
+ if not isinstance(vs, dict):
+ continue
+ name = vs.get("name")
+ if not name:
+ continue
+ rs = vs.get("real_server")
+ if isinstance(rs, list):
+ vs["real_server"] = {
+ item["address"]: item
+ for item in rs
+ if isinstance(item, dict) and item.get("address")
+ }
+ new_vss[name] = vs
+ data["virtual_servers"] = new_vss
+ elif isinstance(vss, dict):
+ for vs in vss.values():
+ if not isinstance(vs, dict):
+ continue
+ rs = vs.get("real_server")
+ if isinstance(rs, list):
+ vs["real_server"] = {
+ item["address"]: item
+ for item in rs
+ if isinstance(item, dict) and item.get("address")
+ }
+
+ return data
+
+ def _extract_leaf_items(self, data, path=None, parent_name=None):
+ path = path or []
+ results = []
+
+ if isinstance(data, dict):
+ current_name = data.get("name", parent_name)
+
+ for k, v in data.items():
+ if k == "name" or (k == "snmp" and v == "disabled"):
+ continue
+ results.extend(self._extract_leaf_items(v, path + [k], current_name))
+ return results
+
+ leaf_key = path[-1]
+ top_key = path[0]
+
+ if top_key in ["groups", "sync_groups"]:
+ subkeys = path[2:]
+ else:
+ subkeys = path[1:]
+
+ nested = {leaf_key: data}
+
+ for p in reversed(subkeys[:-1]):
+ nested = {p: nested}
+ if parent_name:
+ out = {top_key: {"name": parent_name}}
+ out[top_key].update(nested)
+ else:
+ out = {top_key: nested}
+
+ results.append(out)
+ return results
+
+ def _normalize_lists(self, node):
+ """
+ Recursively normalize all lists inside a dict or list.
+ All lists are sorted to ensure consistent ordering for comparison.
+ """
+ if isinstance(node, dict):
+ for k, v in node.items():
+ if isinstance(v, list):
+ if all(not isinstance(i, (dict, list)) for i in v):
+ node[k] = sorted(v)
+ else:
+ for item in v:
+ self._normalize_lists(item)
+ elif isinstance(v, dict):
+ self._normalize_lists(v)
+ elif isinstance(node, list):
+ for item in node:
+ self._normalize_lists(item)
+
+ def _extract_named_leafs(self, data, parent_name=None, prefix_key=None):
+ results = []
+
+ if prefix_key == "real_server" and isinstance(data, dict):
+ for d, server_data in data.items():
+ if not isinstance(server_data, dict):
+ continue
+
+ address = server_data.get("address")
+ if not address:
+ continue
+
+ for k, v in server_data.items():
+ if k == "address":
+ continue
+
+ results.append(
+ {
+ "name": parent_name,
+ "real_server": {
+ "address": address,
+ k: v,
+ },
+ },
+ )
+ return results
+
+ if isinstance(data, dict):
+ current_name = data.get("name", parent_name)
+
+ for k, v in data.items():
+ if k == "name":
+ continue
+
+ results.extend(
+ self._extract_named_leafs(v, current_name, k),
+ )
+
+ return results
+
+ return [
+ {
+ "name": parent_name,
+ prefix_key: data,
+ },
+ ]
+
+ def _prune_stubs(self, w, h, path=""):
+ wc = {}
+ hc = self._remove_defaults(h)
+
+ if not self._remove_defaults(w) and remove_empties(hc):
+ self.commands = ["delete high-availability"]
+ return {}, {}, path
+
+ for k, wg in (self._remove_defaults(w) or {}).items():
+ next_path = f"{path} {k}".strip()
+ stub = self._cli_path(next_path)
+ hg = remove_empties(hc).get(k)
+
+ if hg is None:
+ continue
+
+ if not isinstance(wg, (dict, list)):
+ self.commands.append(f"delete high-availability {stub}")
+ hc.pop(k, None)
+ wc.pop(k, None)
+ continue
+
+ if not wg:
+ self.commands.append(f"delete high-availability {stub}")
+ hc.pop(k, None)
+ wc.pop(k, None)
+ continue
+
+ if isinstance(wg, list) and isinstance(hg, dict):
+ for item in wg:
+ name = item.get("name")
+ if not name:
+ continue
+
+ if name in hg:
+ self.commands.append(
+ f"delete high-availability {stub} {name}",
+ )
+
+ hg.pop(name, None)
+
+ if hg:
+ hc[k] = hg
+ else:
+ hc.pop(k, None)
+
+ if self._remove_defaults(wg):
+ wc[k] = wg
+ else:
+ wc.pop(k, None)
+
+ continue
+
+ if isinstance(wg, dict) and isinstance(hg, dict):
+ wi, hi, p = self._prune_stubs(wg, hg, next_path)
+
+ if wi:
+ wc[k] = wi
+
+ if hi:
+ hc[k] = hi
+ else:
+ hc.pop(k, None)
+
+ return wc, hc, path
+
+ def _remove_defaults(self, data):
+ """Strip None and False from config dicts, but preserve "disabled".
+
+ False is the argspec default for boolean flags (disable, no_preempt,
+ rfc3768_compatibility) and carries no config intent — stripping it
+ prevents spurious `delete` commands for fields already at their
+ default state.
+
+ "disabled" is an explicit user choice for snmp and must be preserved
+ so that _prune_stubs can act on it. The original code stripped it,
+ which made `snmp: disabled` invisible to the deleted-state logic.
+ """
+ if isinstance(data, dict):
+ cleaned = {}
+ for k, v in data.items():
+ if v is None or v is False:
+ continue
+ v = self._remove_defaults(v)
+ cleaned[k] = v
+ return cleaned
+ return data
+
+ def _cli_path(self, path):
+ token_map = {
+ "groups": "group",
+ "sync_groups": "sync-group",
+ "virtual_servers": "virtual-server",
+ }
+
+ parts = []
+ for p in path.split():
+ p = token_map.get(p, p)
+ parts.append(p.replace("_", "-"))
+
+ return " ".join(parts)
+
+ def _diff_w_h(self, w, h):
+
+ NAMED_OBJECT_KEYS = {
+ "groups",
+ "sync_groups",
+ "virtual_servers",
+ "global_parameters",
+ }
+
+ if not isinstance(w, dict) or not isinstance(h, dict):
+ return w
+
+ for key in w.keys() & h.keys():
+ wv = w[key]
+ hv = h[key]
+
+ if key in NAMED_OBJECT_KEYS and isinstance(wv, dict) and isinstance(hv, dict):
+ for name in wv.keys() & hv.keys():
+ if wv[name] != hv[name] and isinstance(wv[name], (dict, list)):
+ wv[name] = {}
+ elif wv[name] != hv[name]:
+ wv[name] = None
+ continue
+ self._diff_w_h(wv, hv)
+ return w
diff --git a/plugins/module_utils/network/vyos/config/hostname/hostname.py b/plugins/module_utils/network/vyos/config/hostname/hostname.py
index 36aba74f..8b30a693 100644
--- a/plugins/module_utils/network/vyos/config/hostname/hostname.py
+++ b/plugins/module_utils/network/vyos/config/hostname/hostname.py
@@ -7,7 +7,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
diff --git a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
index a9d9307c..71e4c0d1 100644
--- a/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
+++ b/plugins/module_utils/network/vyos/config/interfaces/interfaces.py
@@ -11,12 +11,10 @@ created
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
from copy import deepcopy
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -76,6 +74,7 @@ class Interfaces(ConfigBase):
if self.state in self.ACTION_STATES:
existing_interfaces_facts = self.get_interfaces_facts()
+
else:
existing_interfaces_facts = []
@@ -227,7 +226,7 @@ class Interfaces(ConfigBase):
updates = dict_diff(have_copy, want_copy)
if updates:
- for key, value in iteritems(updates):
+ for key, value in updates.items():
commands.append(
self._compute_commands(key=key, value=value, interface=want_copy["name"]),
)
@@ -243,7 +242,7 @@ class Interfaces(ConfigBase):
vif_updates = dict_diff(have_vif, want_vif)
if vif_updates:
- for key, value in iteritems(vif_updates):
+ for key, value in vif_updates.items():
commands.append(
self._compute_commands(
key=key,
diff --git a/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py b/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py
index 6e0c005f..cfce6fee 100644
--- a/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/l3_interfaces/l3_interfaces.py
@@ -13,13 +13,11 @@ created
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
from copy import deepcopy
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -80,7 +78,7 @@ class L3_interfaces(ConfigBase):
commands = list()
if self.state in self.ACTION_STATES:
- existing_l3_interfaces_facts = self.get_l3_interfaces_facts()
+ existing_l3_interfaces_facts = self.mutate_autoconfig(self.get_l3_interfaces_facts())
else:
existing_l3_interfaces_facts = []
@@ -96,16 +94,19 @@ class L3_interfaces(ConfigBase):
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
- changed_l3_interfaces_facts = self.get_l3_interfaces_facts()
+ changed_l3_interfaces_facts = self.mutate_autoconfig(self.get_l3_interfaces_facts())
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
+
if not running_config:
self._module.fail_json(
msg="value of running_config parameter must not be empty for state parsed",
)
- result["parsed"] = self.get_l3_interfaces_facts(data=running_config)
+ result["parsed"] = self.mutate_autoconfig(
+ self.get_l3_interfaces_facts(data=running_config),
+ )
else:
changed_l3_interfaces_facts = []
@@ -129,6 +130,7 @@ class L3_interfaces(ConfigBase):
"""
want = self._module.params["config"]
have = existing_l3_interfaces_facts
+
resp = self.set_state(want, have)
return to_list(resp)
@@ -174,6 +176,7 @@ class L3_interfaces(ConfigBase):
elif state == "replaced":
commands.extend(self._state_replaced(item, obj_in_have))
+ commands = [command.replace("auto-config", "autoconf") for command in commands]
return commands
def _state_replaced(self, want, have):
@@ -226,7 +229,7 @@ class L3_interfaces(ConfigBase):
have_vifs = have_copy.pop("vifs", [])
for update in self._get_updates(want_copy, have_copy):
- for key, value in iteritems(update):
+ for key, value in update.items():
commands.append(
self._compute_commands(key=key, value=value, interface=want_copy["name"]),
)
@@ -238,7 +241,7 @@ class L3_interfaces(ConfigBase):
have_vif = {}
for update in self._get_updates(want_vif, have_vif):
- for key, value in iteritems(update):
+ for key, value in update.items():
commands.append(
self._compute_commands(
key=key,
@@ -247,12 +250,15 @@ class L3_interfaces(ConfigBase):
vif=want_vif["vlan_id"],
),
)
-
return commands
def _state_deleted(self, want, have):
"""The command generator when state is deleted
+ Deletes only the L3 address attributes (base interface and VIFs)
+ owned by this module, never the interface subtree, so L2 settings
+ are preserved.
+
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
@@ -261,51 +267,92 @@ class L3_interfaces(ConfigBase):
want_copy = deepcopy(remove_empties(want))
have_copy = deepcopy(have)
- want_vifs = want_copy.pop("vifs", [])
- have_vifs = have_copy.pop("vifs", [])
-
- for update in self._get_updates(have_copy, want_copy):
- for key, value in iteritems(update):
- commands.append(
- self._compute_commands(
- key=key,
- value=value,
- interface=want_copy["name"],
- remove=True,
- ),
- )
-
- if have_vifs:
- for have_vif in have_vifs:
- want_vif = search_obj_in_list(have_vif["vlan_id"], want_vifs, key="vlan_id")
- if not want_vif:
- want_vif = {"vlan_id": have_vif["vlan_id"]}
+ if have_copy is not None:
+ if all(v in (None, {}, []) for k, v in want_copy.items() if k != "name"):
+ # Only delete L3 attributes we own — do not touch L2 config
+ have_vifs = have_copy.pop("vifs", []) or []
- for update in self._get_updates(have_vif, want_vif):
- for key, value in iteritems(update):
+ for addr_family in ("ipv4", "ipv6"):
+ for addr in have_copy.get(addr_family) or []:
commands.append(
self._compute_commands(
- key=key,
+ key="address",
+ value=addr["address"],
interface=want_copy["name"],
- value=value,
- vif=want_vif["vlan_id"],
remove=True,
),
)
+ for have_vif in have_vifs:
+ for addr_family in ("ipv4", "ipv6"):
+ for addr in have_vif.get(addr_family) or []:
+ commands.append(
+ self._compute_commands(
+ key="address",
+ value=addr["address"],
+ interface=want_copy["name"],
+ vif=have_vif["vlan_id"],
+ remove=True,
+ ),
+ )
+
+ return commands
+
+ want_vifs = want_copy.pop("vifs", [])
+ have_vifs = have_copy.pop("vifs", [])
+
+ if have_vifs:
+ for have_vif in have_vifs:
+ want_vif = search_obj_in_list(have_vif["vlan_id"], want_vifs, key="vlan_id")
+ if not want_vif:
+ want_vif = {"vlan_id": have_vif["vlan_id"]}
+
+ for update in self._get_updates(have_vif, want_vif):
+ for key, value in update.items():
+ commands.append(
+ self._compute_commands(
+ key=key,
+ interface=want_copy["name"],
+ value=value,
+ vif=want_vif["vlan_id"],
+ remove=True,
+ ),
+ )
+
+ for update in self._get_updates(have_copy, want_copy):
+ for key, value in update.items():
+ commands.append(
+ self._compute_commands(
+ key=key,
+ value=value,
+ interface=want_copy["name"],
+ remove=True,
+ ),
+ )
+
return commands
def _compute_commands(self, interface, key, vif=None, value=None, remove=False):
- intf_context = "interfaces {0} {1}".format(get_interface_type(interface), interface)
+ if value == "auto-config" and vif is None:
+ intf_context = "interfaces {0} {1} ipv6".format(
+ get_interface_type(interface),
+ interface,
+ )
+ else:
+ intf_context = "interfaces {0} {1}".format(get_interface_type(interface), interface)
+
set_cmd = "set {0}".format(intf_context)
del_cmd = "delete {0}".format(intf_context)
if vif:
- set_cmd = set_cmd + (" vif {0}".format(vif))
- del_cmd = del_cmd + (" vif {0}".format(vif))
+ suffix = " ipv6" if value == "auto-config" else ""
+ set_cmd += f" vif {vif}{suffix}"
+ del_cmd += f" vif {vif}{suffix}"
- if remove:
+ if remove and key and value:
command = "{0} {1} '{2}'".format(del_cmd, key, value)
+ elif remove and not (key and value):
+ command = "{0}".format(del_cmd)
else:
command = "{0} {1} '{2}'".format(set_cmd, key, value)
@@ -318,3 +365,12 @@ class L3_interfaces(ConfigBase):
updates.extend(diff_list_of_dicts(want.get("ipv6", []), have.get("ipv6", [])))
return updates
+
+ def mutate_autoconfig(self, obj):
+ if isinstance(obj, dict):
+ return dict(map(lambda kv: (kv[0], self.mutate_autoconfig(kv[1])), obj.items()))
+ if isinstance(obj, list):
+ return list(map(self.mutate_autoconfig, obj))
+ if isinstance(obj, str):
+ return obj.replace("autoconf", "auto-config")
+ return obj
diff --git a/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py
index 6890fe0c..203519e4 100644
--- a/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/lag_interfaces/lag_interfaces.py
@@ -8,11 +8,11 @@ is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
-from ansible.module_utils.six import iteritems
+
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -245,7 +245,7 @@ class Lag_interfaces(ConfigBase):
commands.extend(self._add_bond_members(want, have))
if updates:
- for key, value in iteritems(updates):
+ for key, value in updates.items():
if value:
if key == "arp_monitor":
commands.extend(self._add_arp_monitor(updates, key, want, have))
diff --git a/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py b/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py
index 1dfd25e4..82a35a54 100644
--- a/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py
+++ b/plugins/module_utils/network/vyos/config/lldp_global/lldp_global.py
@@ -8,11 +8,11 @@ is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
-from ansible.module_utils.six import iteritems
+
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -241,7 +241,7 @@ class Lldp_global(ConfigBase):
commands.extend(self._add_management_addresses(want, have))
if updates:
- for key, value in iteritems(updates):
+ for key, value in updates.items():
if value is not None:
if key == "enable":
if value is False:
diff --git a/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py
index 2fd6a548..36bf1b2d 100644
--- a/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py
+++ b/plugins/module_utils/network/vyos/config/lldp_interfaces/lldp_interfaces.py
@@ -13,10 +13,8 @@ created
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -285,7 +283,7 @@ class Lldp_interfaces(ConfigBase):
have_dict = have_location_type.get("coordinate_based") or {}
location_type = "coordinate-based"
updates = dict_diff(have_dict, want_dict)
- for key, value in iteritems(updates):
+ for key, value in updates.items():
if value:
commands.append(self._compute_command(set_cmd + location_type, key, str(value)))
@@ -319,7 +317,7 @@ class Lldp_interfaces(ConfigBase):
if is_dict_element_present(have_location_type, "coordinate_based"):
have_dict = have_location_type.get("coordinate_based") or {}
location_type = "coordinate-based"
- for key, value in iteritems(have_dict):
+ for key, value in have_dict.items():
only_in_have = key_value_in_dict(key, value, want_dict)
if not only_in_have:
commands.append(
diff --git a/plugins/module_utils/network/vyos/config/logging_global/logging_global.py b/plugins/module_utils/network/vyos/config/logging_global/logging_global.py
index f94c9195..1724f338 100644
--- a/plugins/module_utils/network/vyos/config/logging_global/logging_global.py
+++ b/plugins/module_utils/network/vyos/config/logging_global/logging_global.py
@@ -7,7 +7,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -20,7 +19,6 @@ created.
from copy import deepcopy
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
@@ -33,6 +31,13 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.logging_global import (
Logging_globalTemplate,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.logging_global_15 import (
+ Logging_globalTemplate15,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Logging_global(ResourceModule):
@@ -48,22 +53,25 @@ class Logging_global(ResourceModule):
resource="logging_global",
tmplt=Logging_globalTemplate(),
)
- self.parsers = [
- "console.facilities",
- "global_params.archive.file_num",
- "global_params.archive.size",
- "global_params.marker_interval",
- "global_params.preserve_fqdn",
- "global_params.facilities",
- "files.archive.size",
- "files.archive.file_num",
- "files",
- "hosts.port",
- "hosts.facility.protocol", # 1.3 and below
- "hosts.protocol",
- "hosts",
- "users",
- ]
+
+ def _validate_template(self):
+ version = get_os_version(self._module)
+ if LooseVersion(version) >= LooseVersion("1.5"):
+ self._tmplt = Logging_globalTemplate15()
+ else:
+ self._tmplt = Logging_globalTemplate()
+
+ self.parsers = [p["name"] for p in self._tmplt.PARSERS if not p["name"].endswith(".state")]
+
+ 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
@@ -71,11 +79,31 @@ class Logging_global(ResourceModule):
: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()
return self.result
+ def _strip_unsupported_15(self, data):
+ """Remove 1.4-only keys from a list_to_dict result for 1.5 devices."""
+ if not data:
+ return data
+ warnings = []
+ for key in ("files", "users"):
+ if data.pop(key, None) is not None:
+ warnings.append(
+ "'{0}' is not supported on VyOS 1.5+, ignoring.".format(key),
+ )
+ if "global_params" in data:
+ if data["global_params"].pop("archive", None) is not None:
+ warnings.append(
+ "'global_params.archive' is not supported on VyOS 1.5+, ignoring.",
+ )
+ for warning in warnings:
+ self._module.warn(warning)
+ return data
+
def generate_commands(self):
"""Generate configuration commands to send based on
want, have and desired state.
@@ -89,10 +117,15 @@ class Logging_global(ResourceModule):
else:
haved = dict()
+ version = get_os_version(self._module)
+ if LooseVersion(version) >= LooseVersion("1.5"):
+ wantd = self._strip_unsupported_15(wantd)
+ haved = self._strip_unsupported_15(haved)
+
if self.state in ["overridden", "replaced"]:
if wantd != haved:
wantx, havex = self.call_op(wantd, haved, "overridden")
- for k, have in iteritems(havex):
+ for k, have in havex.items():
if k not in wantx:
self._compare(want={}, have=have)
@@ -102,7 +135,7 @@ class Logging_global(ResourceModule):
if self.state == "merged":
wantd = dict_merge(haved, wantd)
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _compare(self, want, have):
@@ -116,12 +149,12 @@ class Logging_global(ResourceModule):
def operation_rep(self, params):
op_val = dict()
- for k, val in iteritems(params):
+ for k, val in params.items():
if k in ["console", "global_params"]:
mod_val = deepcopy(val)
op_val.update(self.flatten_facility({k: mod_val}))
elif k in ["files", "hosts", "users"]:
- for m, n in iteritems(val):
+ for m, n in val.items():
mod_n = deepcopy(n)
if mod_n.get("archive"):
del mod_n["archive"]
@@ -160,18 +193,18 @@ class Logging_global(ResourceModule):
def flatten_facility(self, param):
temp_param = dict()
- for element, val in iteritems(param):
+ for element, val in param.items():
if element in ["console", "global_params", "syslog"]:
if element != "syslog" and val.get("facilities"):
- for k, v in iteritems(val.get("facilities")):
+ for k, v in val.get("facilities").items():
temp_param[k + element] = {element: {"facilities": v}}
del val["facilities"]
if val:
temp_param[element] = {element: val}
if element in ["files", "hosts", "users"]:
- for k, v in iteritems(val):
+ for k, v in val.items():
if v.get("facilities"):
- for pk, dat in iteritems(v.get("facilities")):
+ for pk, dat in v.get("facilities").items():
temp_param[pk + k] = {
element: {
"facilities": dat,
@@ -197,7 +230,7 @@ class Logging_global(ResourceModule):
"hosts": "hostname",
"users": "username",
}
- for element, val in iteritems(param):
+ for element, val in param.items():
if element == "facilities": # only with recursion call
_tem_par = {}
for par in val:
diff --git a/plugins/module_utils/network/vyos/config/nat/__init__.py b/plugins/module_utils/network/vyos/config/nat/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/nat/__init__.py
diff --git a/plugins/module_utils/network/vyos/config/nat/nat.py b/plugins/module_utils/network/vyos/config/nat/nat.py
new file mode 100644
index 00000000..a2800914
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/nat/nat.py
@@ -0,0 +1,577 @@
+# -*- coding: utf-8 -*-
+# 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
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
+ ResourceModule,
+)
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.nat import (
+ NatTemplate,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import combine
+
+
+class Nat(ResourceModule):
+ """The vyos_nat config class"""
+
+ def __init__(self, module):
+ super(Nat, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="nat",
+ tmplt=NatTemplate(),
+ )
+ self.parsers = []
+
+ def execute_module(self):
+ if self.state not in ["parsed", "gathered"]:
+ self.generate_commands()
+ self.run_commands()
+ return self.result
+
+ def generate_commands(self):
+ wantd = deepcopy(self.want)
+ haved = deepcopy(self.have)
+
+ if self.state == "merged":
+ wantd = combine(haved, wantd, recursive=True)
+
+ if self.state == "deleted":
+ if not wantd:
+ for nat_type in haved:
+ self.commands.append(f"delete {nat_type}")
+ return
+ self._list_to_dict(wantd)
+ self._list_to_dict(haved)
+ self._delete_nat_objects(wantd, haved, only_missing=False)
+ return
+
+ self._list_to_dict(wantd)
+ self._list_to_dict(haved)
+
+ if self.state == "replaced":
+ self._delete_nat_objects(wantd, haved, only_missing=False)
+ self._set_commands(wantd, haved)
+ elif self.state == "overridden":
+ self._delete_nat_objects(wantd, haved, only_missing=True)
+ self._delete_nat_objects(wantd, haved, only_missing=False)
+ self._set_commands(wantd, haved)
+ else:
+ self._set_commands(wantd, haved)
+
+ self.commands = list(dict.fromkeys(self.commands))
+
+ # -------------------------------------------------------------------------
+ # List → keyed dict conversion
+ # -------------------------------------------------------------------------
+
+ def _list_to_dict(self, config):
+ nat = config.get("nat", {})
+ cgnat = nat.get("cgnat", {})
+
+ pool = cgnat.get("pool", {})
+ for ptype in ("external", "internal"):
+ entries = pool.get(ptype)
+ if isinstance(entries, list):
+ pool[ptype] = {item["name"]: item for item in entries}
+
+ rules = cgnat.get("rule")
+ if isinstance(rules, list):
+ cgnat["rule"] = {r["id"]: r for r in rules}
+
+ for section in ("destination", "source", "static"):
+ rules = nat.get(section, {}).get("rule")
+ if isinstance(rules, list):
+ nat[section]["rule"] = {r["id"]: r for r in rules}
+
+ nat64 = config.get("nat64", {})
+ rules = nat64.get("source", {}).get("rule")
+ if isinstance(rules, list):
+ nat64["source"]["rule"] = {r["id"]: r for r in rules}
+ for rule in nat64["source"]["rule"].values():
+ pools = rule.get("translation", {}).get("pool")
+ if isinstance(pools, list):
+ rule["translation"]["pool"] = {p["id"]: p for p in pools}
+
+ nat66 = config.get("nat66", {})
+ for section in ("destination", "source"):
+ rules = nat66.get(section, {}).get("rule")
+ if isinstance(rules, list):
+ nat66[section]["rule"] = {r["id"]: r for r in rules}
+
+ # -------------------------------------------------------------------------
+ # Top-level dispatch
+ # -------------------------------------------------------------------------
+
+ def _set_commands(self, wantd, haved):
+ self._compare_cgnat_global(wantd, haved)
+ self._compare_cgnat_pools(wantd, haved)
+ self._compare_cgnat_rules(wantd, haved)
+
+ for section in ("destination", "source", "static"):
+ self._compare_nat_rules("nat", section, wantd, haved)
+
+ self._compare_nat_rules("nat64", "source", wantd, haved)
+
+ for section in ("destination", "source"):
+ self._compare_nat_rules("nat66", section, wantd, haved)
+
+ self.commands = list(dict.fromkeys(self.commands))
+
+ # -------------------------------------------------------------------------
+ # Delete helpers
+ # -------------------------------------------------------------------------
+
+ def _delete_nat_objects(self, wantd, haved, only_missing=False):
+ """
+ Generate delete commands for NAT objects.
+ only_missing=False: delete objects present in both want and have (when different)
+ only_missing=True: delete objects present in have but absent from want
+ """
+ for nat_type in haved:
+ want_nat = wantd.get(nat_type, {})
+ have_nat = haved[nat_type]
+
+ if only_missing and nat_type not in wantd:
+ self.commands.append(f"delete {nat_type}")
+ continue
+
+ for section in have_nat:
+ want_section = want_nat.get(section, {})
+ have_section = have_nat[section]
+
+ if only_missing and section not in want_nat:
+ self.commands.append(
+ f"delete {nat_type} {section.replace('_', '-')}",
+ )
+ continue
+
+ if section == "cgnat":
+ for pool_type in ("external", "internal"):
+ want_pools = want_section.get("pool", {}).get(pool_type, {})
+ have_pools = have_section.get("pool", {}).get(pool_type, {})
+ for name in have_pools:
+ if only_missing and name not in want_pools:
+ self.commands.append(
+ f"delete {nat_type} cgnat pool {pool_type} {name}",
+ )
+
+ elif not only_missing and name in want_pools:
+ if self.state == "deleted" or want_pools[name] != have_pools[name]:
+ self.commands.append(
+ f"delete {nat_type} cgnat pool {pool_type} {name}",
+ )
+ want_rules = want_section.get("rule", {})
+ have_rules = have_section.get("rule", {})
+ for rid in have_rules:
+ if only_missing and rid not in want_rules:
+ self.commands.append(f"delete {nat_type} cgnat rule {rid}")
+
+ elif not only_missing and rid in want_rules:
+ if self.state == "deleted" or want_rules[rid] != have_rules[rid]:
+ self.commands.append(f"delete {nat_type} cgnat rule {rid}")
+ else:
+ want_rules = want_section.get("rule", {})
+ have_rules = have_section.get("rule", {})
+ cli_section = section.replace("_", "-")
+ for rid in have_rules:
+ if only_missing and rid not in want_rules:
+ self.commands.append(
+ f"delete {nat_type} {cli_section} rule {rid}",
+ )
+
+ elif not only_missing and rid in want_rules:
+ if self.state == "deleted" or want_rules[rid] != have_rules[rid]:
+ self.commands.append(
+ f"delete {nat_type} {cli_section} rule {rid}",
+ )
+
+ # -------------------------------------------------------------------------
+ # CGNAT
+ # -------------------------------------------------------------------------
+
+ def _compare_cgnat_global(self, wantd, haved):
+ if self.state in ("replaced", "overridden") and not wantd.get("nat", {}).get("cgnat"):
+ return
+ w = wantd.get("nat", {}).get("cgnat", {}).get("log_allocation")
+ h = haved.get("nat", {}).get("cgnat", {}).get("log_allocation")
+ if bool(w) != bool(h):
+ self.addcmd(
+ {"nat": {"cgnat": {"log_allocation": True}}},
+ "cgnat_log_allocation",
+ not bool(w),
+ )
+
+ def _compare_cgnat_pools(self, wantd, haved):
+ want_ext = wantd.get("nat", {}).get("cgnat", {}).get("pool", {}).get("external", {})
+ have_ext = haved.get("nat", {}).get("cgnat", {}).get("pool", {}).get("external", {})
+ want_int = wantd.get("nat", {}).get("cgnat", {}).get("pool", {}).get("internal", {})
+ have_int = haved.get("nat", {}).get("cgnat", {}).get("pool", {}).get("internal", {})
+
+ scope = self.state in ("replaced", "overridden")
+ ext_names = set(want_ext) if scope else set(want_ext) | set(have_ext)
+ int_names = set(want_int) if scope else set(want_int) | set(have_int)
+
+ for name in ext_names:
+ w = want_ext.get(name, {})
+ h = have_ext.get(name, {})
+ if scope and w != h:
+ h = {}
+ self._compare_external_pool(name, w, h)
+
+ for name in int_names:
+ w = want_int.get(name, {})
+ h = have_int.get(name, {})
+ if scope and w != h:
+ h = {}
+ self._compare_internal_pool(name, w, h)
+
+ def _compare_external_pool(self, name, want, have):
+ w = want.get("external_port_range")
+ h = have.get("external_port_range")
+ if w != h:
+ if w:
+ self.addcmd({"name": name, "range": w}, "cgnat_pool_external_port_range", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd({"name": name, "range": h}, "cgnat_pool_external_port_range", True)
+
+ w = want.get("per_user_limit", {}).get("port")
+ h = have.get("per_user_limit", {}).get("port")
+ if w != h:
+ if w:
+ self.addcmd({"name": name, "limit": w}, "cgnat_pool_external_per_user", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd({"name": name, "limit": h}, "cgnat_pool_external_per_user", True)
+
+ want_ranges = {(r["value"] if isinstance(r, dict) else r): r for r in want.get("range", [])}
+ have_ranges = {(r["value"] if isinstance(r, dict) else r): r for r in have.get("range", [])}
+ for val, rng in want_ranges.items():
+ existing = have_ranges.get(val)
+ if existing is None or existing != rng:
+ seq = rng.get("seq") if isinstance(rng, dict) else None
+ self.addcmd(
+ {"name": name, "range": val, "seq": seq},
+ "cgnat_pool_external_range",
+ False,
+ )
+
+ if self.state in ("replaced", "overridden"):
+ for val in have_ranges:
+ if val not in want_ranges:
+ self.addcmd({"name": name, "range": val}, "cgnat_pool_external_range", True)
+
+ def _compare_internal_pool(self, name, want, have):
+ want_ranges = set(want.get("range", []))
+ have_ranges = set(have.get("range", []))
+
+ for rng in want_ranges - have_ranges:
+ self.addcmd({"name": name, "range": rng}, "cgnat_pool_internal_range", False)
+
+ if self.state in ("replaced", "overridden"):
+ for rng in have_ranges - want_ranges:
+ self.addcmd({"name": name, "range": rng}, "cgnat_pool_internal_range", True)
+
+ def _compare_cgnat_rules(self, wantd, haved):
+ want_rules = wantd.get("nat", {}).get("cgnat", {}).get("rule", {})
+ have_rules = haved.get("nat", {}).get("cgnat", {}).get("rule", {})
+
+ rids = (
+ set(want_rules)
+ if self.state in ("replaced", "overridden")
+ else set(want_rules) | set(have_rules)
+ )
+
+ for rid in rids:
+ w = want_rules.get(rid, {})
+ h = have_rules.get(rid, {})
+
+ if self.state in ("replaced", "overridden") and w != h:
+ h = {}
+
+ w_src = w.get("source", {}).get("pool")
+ h_src = h.get("source", {}).get("pool")
+ if w_src != h_src:
+ if w_src:
+ self.addcmd({"id": rid, "pool": w_src}, "cgnat_rule_source_pool", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd({"id": rid, "pool": h_src}, "cgnat_rule_source_pool", True)
+
+ w_tr = w.get("translation", {}).get("pool")
+ h_tr = h.get("translation", {}).get("pool")
+ if w_tr != h_tr:
+ if w_tr:
+ self.addcmd({"id": rid, "pool": w_tr}, "cgnat_rule_translation_pool", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd({"id": rid, "pool": h_tr}, "cgnat_rule_translation_pool", True)
+
+ # -------------------------------------------------------------------------
+ # NAT / NAT64 / NAT66 rules
+ # -------------------------------------------------------------------------
+
+ def _compare_nat_rules(self, nat_type, section, wantd, haved):
+ want_rules = wantd.get(nat_type, {}).get(section, {}).get("rule", {})
+ have_rules = haved.get(nat_type, {}).get(section, {}).get("rule", {})
+
+ rids = (
+ set(want_rules)
+ if self.state in ("replaced", "overridden")
+ else set(want_rules) | set(have_rules)
+ )
+
+ for rid in rids:
+ w = want_rules.get(rid, {})
+ h = have_rules.get(rid, {})
+ if self.state in ("replaced", "overridden") and w != h:
+ h = {}
+ if w == h and self.state != "rendered":
+ continue
+ self._compare_rule(nat_type, section, rid, w, h)
+
+ def _compare_rule(self, nat_type, section, rid, want, have):
+ ctx = {"nat": nat_type, "type": section, "id": rid}
+
+ want_lb = want.get("load_balance") or {}
+ have_lb = have.get("load_balance") or {}
+ want_trans_addr = (want.get("translation") or {}).get("address")
+ have_trans_addr = (have.get("translation") or {}).get("address")
+ if want_lb and want_trans_addr is not None:
+ self._module.fail_json(
+ msg="translation.address and load_balance are mutually exclusive",
+ )
+ if self.state == "merged":
+ if want_lb and have_trans_addr is not None:
+ self._module.fail_json(
+ msg=(
+ "Cannot add load_balance to a rule that already has translation.address with "
+ "state=merged; use state=replaced or state=overridden"
+ ),
+ )
+ if want_trans_addr is not None and have_lb:
+ self._module.fail_json(
+ msg=(
+ "Cannot add translation.address to a rule that already has load_balance with "
+ "state=merged; use state=replaced or state=overridden"
+ ),
+ )
+
+ for field in set(want) | set(have):
+ if field == "inbound_interface":
+ continue
+ val = want.get(field) if field in want else have.get(field)
+ if isinstance(val, bool):
+ self._cmp_bool(want, have, field, ctx, f"nat_type_{field}")
+ elif isinstance(val, str):
+ self._cmp_scalar(want, have, field, ctx, f"nat_type_{field}")
+
+ self._cmp_interface(want, have, ctx, nat_type, section)
+ self._cmp_outbound_interface(want, have, ctx)
+ for atype in ("destination", "source"):
+ self._cmp_addr_sub(want, have, atype, ctx)
+ self._cmp_translation(want, have, ctx)
+ self._cmp_match_mark(want, have, ctx)
+ self._cmp_nat64_pools(want, have, ctx)
+ self._cmp_load_balance(want, have, ctx)
+
+ # -------------------------------------------------------------------------
+ # Field-level helpers
+ # -------------------------------------------------------------------------
+
+ def _cmp_scalar(self, want, have, field, ctx, parser):
+ w = want.get(field)
+ h = have.get(field)
+ if w != h:
+ if w is not None:
+ self.addcmd(dict(ctx, **{field: w}), parser, False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, **{field: h}), parser, True)
+
+ def _cmp_bool(self, want, have, field, ctx, parser):
+ w = bool(want.get(field))
+ h = bool(have.get(field))
+ if w != h:
+ if w:
+ self.addcmd(dict(ctx), parser, False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx), parser, True)
+
+ def _cmp_interface(self, want, have, ctx, nat_type, section):
+ iface_w = want.get("inbound_interface")
+ iface_h = have.get("inbound_interface")
+ if iface_w == iface_h:
+ return
+
+ if nat_type == "nat" and section == "static":
+ if iface_w:
+ self.addcmd(dict(ctx, value=iface_w), "nat_static_inbound_interface", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, value=iface_h), "nat_static_inbound_interface", True)
+ return
+
+ iface_w = iface_w or {}
+ iface_h = iface_h or {}
+
+ if nat_type == "nat":
+ parser_name = "nat_inbound_interface_name"
+ parser_group = "nat_inbound_interface_group"
+ else:
+ parser_name = "nat6x_inbound_interface"
+ parser_group = "nat6x_inbound_interface"
+
+ if iface_w.get("name") != iface_h.get("name"):
+ if iface_w.get("name"):
+ self.addcmd(dict(ctx, value=iface_w["name"]), parser_name, False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, value=iface_h["name"]), parser_name, True)
+
+ if nat_type == "nat" and iface_w.get("group") != iface_h.get("group"):
+ if iface_w.get("group"):
+ self.addcmd(dict(ctx, value=iface_w["group"]), parser_group, False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, value=iface_h["group"]), parser_group, True)
+
+ def _cmp_outbound_interface(self, want, have, ctx):
+ iface_w = want.get("outbound_interface") or {}
+ iface_h = have.get("outbound_interface") or {}
+
+ if iface_w.get("name") != iface_h.get("name"):
+ if iface_w.get("name"):
+ self.addcmd(dict(ctx, value=iface_w["name"]), "nat_type_outbound_interface", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, value=iface_h["name"]), "nat_type_outbound_interface", True)
+
+ if iface_w.get("group") != iface_h.get("group"):
+ if iface_w.get("group"):
+ self.addcmd(
+ dict(ctx, value=iface_w["group"]),
+ "nat_type_outbound_interface_group",
+ False,
+ )
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(
+ dict(ctx, value=iface_h["group"]),
+ "nat_type_outbound_interface_group",
+ True,
+ )
+
+ def _cmp_addr_sub(self, want, have, atype, ctx):
+ sub_w = want.get(atype) or {}
+ sub_h = have.get(atype) or {}
+ if sub_w == sub_h:
+ return
+
+ changed = {k: v for k, v in sub_w.items() if sub_h.get(k) != v}
+ removed = {
+ k: v
+ for k, v in sub_h.items()
+ if k not in sub_w and self.state in ("replaced", "overridden")
+ }
+
+ if changed:
+ self.addcmd(dict(ctx, atype=atype, sub=changed), "nat_type_address", False)
+ if removed:
+ self.addcmd(dict(ctx, atype=atype, sub=removed), "nat_type_address", True)
+
+ def _cmp_translation(self, want, have, ctx):
+ trans_w = want.get("translation") or {}
+ trans_h = have.get("translation") or {}
+ if trans_w == trans_h:
+ return
+
+ changed = {k: v for k, v in trans_w.items() if k != "pool" and trans_h.get(k) != v}
+ removed = {
+ k: v
+ for k, v in trans_h.items()
+ if k != "pool" and k not in trans_w and self.state in ("replaced", "overridden")
+ }
+
+ if changed:
+ self.addcmd(dict(ctx, translation=changed), "nat_type_translation_address", False)
+ if removed:
+ self.addcmd(dict(ctx, translation=removed), "nat_type_translation_address", True)
+
+ def _cmp_match_mark(self, want, have, ctx):
+ w = want.get("match", {}).get("mark")
+ h = have.get("match", {}).get("mark")
+ if w != h:
+ if w is not None:
+ self.addcmd(dict(ctx, mark=w), "nat64_match_mark", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, mark=h), "nat64_match_mark", True)
+
+ def _cmp_nat64_pools(self, want, have, ctx):
+ want_pools = want.get("translation", {}).get("pool", {})
+ have_pools = have.get("translation", {}).get("pool", {})
+
+ if isinstance(want_pools, list):
+ want_pools = {p["id"]: p for p in want_pools}
+ if isinstance(have_pools, list):
+ have_pools = {p["id"]: p for p in have_pools}
+
+ for pid in set(want_pools) | set(have_pools):
+ wp = want_pools.get(pid, {})
+ hp = have_pools.get(pid, {})
+
+ if wp == hp:
+ continue
+
+ changed = {k: v for k, v in wp.items() if k != "id" and hp.get(k) != v}
+ removed = {
+ k: v
+ for k, v in hp.items()
+ if k != "id" and k not in wp and self.state in ("replaced", "overridden")
+ }
+
+ if changed:
+ self.addcmd(
+ dict(ctx, pool_id=pid, pool=changed),
+ "nat64_translation_pool",
+ False,
+ )
+ if removed:
+ self.addcmd(
+ dict(ctx, pool_id=pid, pool=removed),
+ "nat64_translation_pool",
+ True,
+ )
+
+ def _cmp_load_balance(self, want, have, ctx):
+ lb_w = want.get("load_balance") or {}
+ lb_h = have.get("load_balance") or {}
+
+ want_hash = set(lb_w.get("hash") or [])
+ have_hash = set(lb_h.get("hash") or [])
+
+ for h in want_hash - have_hash:
+ self.addcmd(dict(ctx, value=h), "nat_type_lb_hash", False)
+ if self.state in ("replaced", "overridden"):
+ for h in have_hash - want_hash:
+ self.addcmd(dict(ctx, value=h), "nat_type_lb_hash", True)
+
+ want_backends = lb_w.get("backend", [])
+ have_backends = lb_h.get("backend", [])
+ if isinstance(want_backends, list):
+ want_backends = {b["ip"]: b for b in want_backends}
+ if isinstance(have_backends, list):
+ have_backends = {b["ip"]: b for b in have_backends}
+
+ for ip in set(want_backends) | set(have_backends):
+ wb = want_backends.get(ip, {})
+ hb = have_backends.get(ip, {})
+ if wb == hb:
+ continue
+ if wb:
+ weight = wb.get("weight")
+ if weight is None:
+ self._module.fail_json(msg="load_balance.backend entries require 'weight'")
+ self.addcmd(dict(ctx, ip=ip, weight=weight), "nat_type_lb_backend", False)
+ elif self.state in ("replaced", "overridden"):
+ self.addcmd(dict(ctx, ip=ip, weight=hb.get("weight")), "nat_type_lb_backend", True)
diff --git a/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py b/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py
index 5d294063..78b7a545 100644
--- a/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py
+++ b/plugins/module_utils/network/vyos/config/ntp_global/ntp_global.py
@@ -7,7 +7,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -18,7 +17,6 @@ necessary to bring the current configuration to its desired end-state is
created.
"""
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
@@ -93,16 +91,16 @@ class Ntp_global(ResourceModule):
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
- haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd}
+ haved = {k: v for k, v in haved.items() if k in wantd or not wantd}
wantd = {}
commandlist = self._commandlist(haved)
servernames = self._servernames(haved)
# removing the servername and commandlist from the list after deleting it from haved
# iterate through the top-level items to delete
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
- for hk, hval in iteritems(have):
+ for hk, hval in have.items():
if hk == "allow_clients" and hk in commandlist:
self.commands.append(
self._tmplt.render({"": hk}, "allow_clients_delete", True),
@@ -130,7 +128,7 @@ class Ntp_global(ResourceModule):
commandlist = self._commandlist(haved)
servernames = self._servernames(haved)
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
if "server" not in have:
self._compareoverride(want={}, have=have)
@@ -139,7 +137,7 @@ class Ntp_global(ResourceModule):
self._compareoverride(want={}, have=have)
servernames.remove(have["server"])
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _compare(self, want, have):
@@ -155,7 +153,7 @@ class Ntp_global(ResourceModule):
def _compareoverride(self, want, have):
# do not delete configuration with options level
- for i, val in iteritems(have):
+ for i, val in have.items():
if i == "options":
pass
else:
@@ -163,12 +161,12 @@ class Ntp_global(ResourceModule):
def _ntp_list_to_dict(self, entry):
servers_dict = {}
- for k, data in iteritems(entry):
+ for k, data in entry.items():
if k == "servers":
for value in data:
if "options" in value:
result = self._serveroptions_list_to_dict(value)
- for res, resvalue in iteritems(result):
+ for res, resvalue in result.items():
servers_dict.update({res: resvalue})
else:
servers_dict.update({value["server"]: value})
@@ -179,7 +177,7 @@ class Ntp_global(ResourceModule):
def _serveroptions_list_to_dict(self, entry):
serveroptions_dict = {}
- for Opk, Op in iteritems(entry):
+ for Opk, Op in entry.items():
if Opk == "options":
for val in Op:
dict = {}
@@ -190,16 +188,16 @@ class Ntp_global(ResourceModule):
def _commandlist(self, haved):
commandlist = []
- for k, have in iteritems(haved):
- for ck, cval in iteritems(have):
+ for k, have in haved.items():
+ for ck, cval in have.items():
if ck != "options" and ck not in commandlist:
commandlist.append(ck)
return commandlist
def _servernames(self, haved):
servernames = []
- for k, have in iteritems(haved):
- for sk, sval in iteritems(have):
+ for k, have in haved.items():
+ for sk, sval in have.items():
if sk != "options" and sval not in servernames:
servernames.append(sval)
return servernames
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 51b47494..bc93ac24 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
@@ -7,7 +7,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
The vyos_ospf_interfaces config file.
@@ -17,7 +16,6 @@ necessary to bring the current configuration to its desired end-state is
created.
"""
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
@@ -27,17 +25,16 @@ 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
+ Ospf_interfacesTemplate14,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
)
-
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
-
class Ospf_interfaces(ResourceModule):
"""
@@ -77,7 +74,7 @@ class Ospf_interfaces(ResourceModule):
self._tmplt = Ospf_interfacesTemplate()
def parse(self):
- """ override parse to check template """
+ """override parse to check template"""
self._validate_template()
return super().parse()
@@ -119,12 +116,12 @@ class Ospf_interfaces(ResourceModule):
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
h_del = {}
- for k, v in iteritems(haved):
+ for k, v in haved.items():
if k in wantd or not wantd:
h_del.update({k: v})
haved = h_del
have_int = []
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k in wantd:
have_int.append(k)
self._remove_ospf_int(have)
@@ -132,7 +129,7 @@ class Ospf_interfaces(ResourceModule):
if self.state == "overridden":
have_int = []
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
have_int.append(k)
self._remove_ospf_int(have)
@@ -142,17 +139,17 @@ class Ospf_interfaces(ResourceModule):
# removing the interfaces from haved that are already negated
for interface in have_int:
haved.pop(interface)
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
self._compare(want={}, have=have)
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _remove_ospf_int(self, entry):
int_name = entry.get("name", {})
int_addr = entry.get("address_family", {})
- for k, addr in iteritems(int_addr):
+ for k, addr in int_addr.items():
rem_entry = {"name": int_name, "address_family": {"afi": k}}
self.addcmd(rem_entry, "ip_ospf", True)
@@ -169,8 +166,8 @@ class Ospf_interfaces(ResourceModule):
hdict = have.get("address_family", {})
wname = want.get("name")
hname = have.get("name")
- for name, entry in iteritems(wdict):
- for key, param in iteritems(entry):
+ for name, entry in wdict.items():
+ for key, param in entry.items():
w_addr = {"afi": name, key: param}
h_addr = {}
if hdict.get(name):
@@ -178,8 +175,8 @@ class Ospf_interfaces(ResourceModule):
w = {"name": wname, "address_family": w_addr}
h = {"name": hname, "address_family": h_addr}
self.compare(parsers=self.parsers, want=w, have=h)
- for name, entry in iteritems(hdict):
- for key, param in iteritems(entry):
+ for name, entry in hdict.items():
+ for key, param in entry.items():
h_addr = {"afi": name, key: param}
w_addr = {}
w = {"name": wname, "address_family": w_addr}
@@ -187,7 +184,7 @@ class Ospf_interfaces(ResourceModule):
self.compare(parsers=self.parsers, want=w, have=h)
def _ospf_int_list_to_dict(self, entry):
- for name, family in iteritems(entry):
+ for name, family in entry.items():
if "address_family" in family:
addr_dict = {}
for entry in family.get("address_family", []):
diff --git a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py
index a9c1de1b..20821980 100644
--- a/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py
+++ b/plugins/module_utils/network/vyos/config/ospfv2/ospfv2.py
@@ -10,14 +10,13 @@ is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -33,6 +32,10 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils
_is_w_same,
list_diff_want_only,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ospfv2(ConfigBase):
@@ -54,7 +57,7 @@ class Ospfv2(ConfigBase):
:returns: The current configuration as a dictionary
"""
- (facts, _warnings) = Facts(self._module).get_facts(
+ facts, _warnings = Facts(self._module).get_facts(
self.gather_subset,
self.gather_network_resources,
data=data,
@@ -124,7 +127,7 @@ class Ospfv2(ConfigBase):
want = self._module.params["config"]
have = existing_ospfv2_facts
- resp = self.set_state(want, have)
+ resp = self.set_state(remove_empties(want), remove_empties(have))
return to_list(resp)
def set_state(self, w, h):
@@ -136,7 +139,6 @@ class Ospfv2(ConfigBase):
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
-
commands = []
if self.state in ("merged", "replaced", "overridden", "rendered") and not w:
self._module.fail_json(
@@ -220,7 +222,7 @@ class Ospfv2(ConfigBase):
w = deepcopy(remove_empties(want))
leaf = ("default_metric", "log_adjacency_changes")
if w:
- for key, val in iteritems(w):
+ for key, val in w.items():
if opr and key in leaf and not _is_w_same(w, have, key):
commands.append(self._form_attr_cmd(attr=key, val=_bool_to_str(val), opr=opr))
elif not opr and key in leaf and not _in_target(have, key):
@@ -270,7 +272,6 @@ class Ospfv2(ConfigBase):
:param opr: True/False.
:return: generated list of commands.
"""
-
commands = []
h = {}
if have:
@@ -289,7 +290,7 @@ class Ospfv2(ConfigBase):
),
}
leaf = leaf_dict[attr]
- for item, value in iteritems(want[attr]):
+ for item, value in want[attr].items():
if opr and item in leaf and not _is_w_same(want[attr], h, item):
if item == "enabled":
item = "enable"
@@ -320,7 +321,6 @@ class Ospfv2(ConfigBase):
:param opr: True/False.
:return: generated list of commands.
"""
-
commands = []
h = []
if want:
@@ -336,6 +336,22 @@ class Ospfv2(ConfigBase):
command = cmd + attr.replace("_", "-") + " "
if attr == "network":
command += member["address"]
+ elif (
+ attr == "passive_interface"
+ and member != "default"
+ and LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
+ ):
+ command = (
+ command.replace("passive-interface", "interface") + member + " passive"
+ )
+ elif attr == "passive_interface_exclude" and LooseVersion(
+ get_os_version(self._module),
+ ) >= LooseVersion("1.4"):
+ command = (
+ command.replace("passive-interface-exclude", "interface")
+ + member
+ + " passive disable"
+ )
else:
command += member
commands.append(command)
@@ -348,7 +364,23 @@ class Ospfv2(ConfigBase):
cmd + attr.replace("_", "-") + " " + member["address"],
)
elif member not in h:
- commands.append(cmd + attr.replace("_", "-") + " " + member)
+ if (
+ attr == "passive_interface"
+ and member != "default"
+ and LooseVersion(get_os_version(self._module))
+ >= LooseVersion("1.4")
+ ):
+ commands.append(cmd + "interface" + " " + member + " passive")
+ elif attr == "passive_interface_exclude" and LooseVersion(
+ get_os_version(self._module),
+ ) >= LooseVersion("1.4"):
+ command = (
+ command.replace("passive-interface-exclude", "interface")
+ + member
+ + " passive disable"
+ )
+ else:
+ commands.append(cmd + attr.replace("_", "-") + " " + member)
else:
commands.append(cmd + " " + attr.replace("_", "-"))
return commands
@@ -385,7 +417,7 @@ class Ospfv2(ConfigBase):
commands.append(cmd + attr.replace("_", "-"))
elif w:
for w_item in w:
- for key, val in iteritems(w_item):
+ for key, val in w_item.items():
if not cmd:
cmd = self._compute_command(opr=opr)
h_item = self.search_obj_in_have(h, w_item, name[attr])
@@ -493,7 +525,7 @@ class Ospfv2(ConfigBase):
commands.append(self._compute_command(attr=attr, opr=opr))
elif w:
for w_item in w:
- for key, val in iteritems(w_item):
+ for key, val in w_item.items():
if not cmd:
cmd = self._compute_command(opr=opr)
h_item = self.search_obj_in_have(h, w_item, name[attr])
@@ -590,7 +622,7 @@ class Ospfv2(ConfigBase):
leaf = leaf_dict[attr]
if h and key in h.keys():
h_attrib = h.get(key) or {}
- for item, val in iteritems(w[key]):
+ for item, val in w[key].items():
if opr and item in leaf and not _is_w_same(w[key], h_attrib, item):
if item in ("administrative", "always") and val:
commands.append(
@@ -651,7 +683,7 @@ class Ospfv2(ConfigBase):
self._form_attr_cmd(key="area", attr=w_area["area_id"], opr=opr),
)
else:
- for key, val in iteritems(w_area):
+ for key, val in w_area.items():
if opr and key in l_set and not _is_w_same(w_area, h_area, key):
if key == "area_id":
commands.append(
@@ -724,7 +756,7 @@ class Ospfv2(ConfigBase):
if w_area:
if h_type and key in h_type.keys():
h_area = h_type.get(key) or {}
- for item, val in iteritems(w_type[key]):
+ for item, val in w_type[key].items():
if (
opr
and item in a_type[key]
diff --git a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py
index 25d9a0ea..a84899f5 100644
--- a/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py
+++ b/plugins/module_utils/network/vyos/config/ospfv3/ospfv3.py
@@ -10,14 +10,13 @@ is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
from copy import deepcopy
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -33,6 +32,10 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils
_in_target,
_is_w_same,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ospfv3(ConfigBase):
@@ -200,7 +203,7 @@ class Ospfv3(ConfigBase):
commands = []
w = deepcopy(remove_empties(want))
if w:
- for key, val in iteritems(w):
+ for key, val in w.items():
commands.extend(self._render_child_param(w, have, key, opr))
return commands
@@ -241,7 +244,7 @@ class Ospfv3(ConfigBase):
elif want[attr]:
leaf_dict = {"parameters": "router_id"}
leaf = leaf_dict[attr]
- for item, value in iteritems(want[attr]):
+ for item, value in want[attr].items():
if opr and item in leaf and not _is_w_same(want[attr], h, item):
commands.append(self._form_attr_cmd(key=attr, attr=item, val=value, opr=opr))
elif not opr and item in leaf and not _in_target(h, item):
@@ -264,10 +267,12 @@ class Ospfv3(ConfigBase):
name = {
"redistribute": "route_type",
"range": "address",
+ "interface": "name",
}
leaf_dict = {
"redistribute": ("route_map", "route_type"),
"range": ("address", "advertise", "not_advertise"),
+ "interface": ("name"),
}
leaf = leaf_dict[attr]
w = want.get(attr) or []
@@ -277,19 +282,31 @@ class Ospfv3(ConfigBase):
commands.append(self._compute_command(attr=attr, opr=opr))
elif w:
for w_item in w:
- for key, val in iteritems(w_item):
+ for key, val in w_item.items():
if not cmd:
cmd = self._compute_command(opr=opr)
h_item = search_obj_in_list(w_item[name[attr]], h, name[attr])
if opr and key in leaf and not _is_w_same(w_item, h_item, key):
- if key == "route_type" or (
+ if key in ["route_type", "name"] or (
key == "address"
and "advertise" not in w_item
and "not-advertise" not in w_item
):
if not val:
cmd = cmd.replace("set", "delete")
- commands.append(cmd + attr + " " + str(val))
+ if (
+ LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
+ and attr == "interface"
+ ):
+ words = cmd.split()
+ cmd14_list = []
+ for word in words:
+ cmd14_list.append(word)
+ if word == "ospfv3":
+ cmd14_list.append(attr + " " + str(val))
+ commands.append(" ".join(cmd14_list))
+ else:
+ commands.append(cmd + attr + " " + str(val))
elif key in leaf_dict["range"] and key != "address":
commands.append(
cmd + attr + " " + w_item[name[attr]] + " " + key.replace("_", "-"),
@@ -306,8 +323,20 @@ class Ospfv3(ConfigBase):
+ str(val),
)
elif not opr and key in leaf and not _in_target(h_item, key):
- if key in ("route_type", "address"):
- commands.append(cmd + attr + " " + str(val))
+ if key in ("route_type", "address", "name"):
+ if (
+ LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4")
+ and attr == "interface"
+ ):
+ words = cmd.split()
+ cmd14_list = []
+ for word in words:
+ cmd14_list.append(word)
+ if word == "ospfv3":
+ cmd14_list.append(attr + " " + str(val))
+ commands.append(" ".join(cmd14_list))
+ else:
+ commands.append(cmd + attr + " " + str(val))
else:
commands.append(cmd + (attr + " " + w_item[name[attr]] + " " + key))
return commands
@@ -346,7 +375,7 @@ class Ospfv3(ConfigBase):
self._form_attr_cmd(key="area", attr=w_area["area_id"], opr=opr),
)
else:
- for key, val in iteritems(w_area):
+ for key, val in w_area.items():
if opr and key in l_set and not _is_w_same(w_area, h_area, key):
if key == "area_id":
commands.append(
@@ -373,6 +402,10 @@ class Ospfv3(ConfigBase):
commands.extend(
self._render_list_dict_param(key, w_area, h_area, cmd, opr),
)
+ elif key == "interface":
+ commands.extend(
+ self._render_list_dict_param(key, w_area, h_area, cmd, opr),
+ )
return commands
def _form_attr_cmd(self, key=None, attr=None, val=None, opr=True):
diff --git a/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py b/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py
index 9da27c15..05164c31 100644
--- a/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py
+++ b/plugins/module_utils/network/vyos/config/prefix_lists/prefix_lists.py
@@ -7,7 +7,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -19,7 +18,6 @@ created.
"""
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
@@ -86,23 +84,23 @@ class Prefix_lists(ResourceModule):
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
- haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd}
- for key, hvalue in iteritems(haved):
+ haved = {k: v for k, v in haved.items() if k in wantd or not wantd}
+ for key, hvalue in haved.items():
wvalue = wantd.pop(key, {})
if wvalue:
wplists = wvalue.get("prefix_lists", {})
hplists = hvalue.get("prefix_lists", {})
hvalue["prefix_lists"] = {
- k: v for k, v in iteritems(hplists) if k in wplists or not wplists
+ k: v for k, v in hplists.items() if k in wplists or not wplists
}
# remove superfluous config for overridden and deleted
if self.state in ["overridden", "deleted"]:
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
self._compare(want={}, have=have)
- for k, want in iteritems(wantd):
+ for k, want in wantd.items():
self._compare(want=want, have=haved.pop(k, {}))
def _compare(self, want, have):
@@ -127,7 +125,7 @@ class Prefix_lists(ResourceModule):
)
def _compare_plists(self, want, have):
- for wk, wentry in iteritems(want):
+ for wk, wentry in want.items():
hentry = have.pop(wk, {})
# parser list for name and descriptions
@@ -143,7 +141,7 @@ class Prefix_lists(ResourceModule):
self._compare_rules(want=wplrules, have=hplrules)
def _compare_rules(self, want, have):
- for wr, wrule in iteritems(want):
+ for wr, wrule in want.items():
hrule = have.pop(wr, {})
# parser list for entries
@@ -164,7 +162,7 @@ class Prefix_lists(ResourceModule):
)
def _prefix_list_list_to_dict(self, entry):
- for afi, value in iteritems(entry):
+ for afi, value in entry.items():
if "prefix_lists" in value:
for pl in value["prefix_lists"]:
pl.update({"afi": afi})
diff --git a/plugins/module_utils/network/vyos/config/route_maps/route_maps.py b/plugins/module_utils/network/vyos/config/route_maps/route_maps.py
index 9b6c3e9d..948341d3 100644
--- a/plugins/module_utils/network/vyos/config/route_maps/route_maps.py
+++ b/plugins/module_utils/network/vyos/config/route_maps/route_maps.py
@@ -7,7 +7,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -19,7 +18,6 @@ created.
"""
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
@@ -31,6 +29,13 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps import (
Route_mapsTemplate,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps_14 import (
+ Route_mapsTemplate14,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Route_maps(ResourceModule):
@@ -59,6 +64,8 @@ class Route_maps(ResourceModule):
"set_bgp_extcommunity_rt",
"set_extcommunity_rt",
"set_extcommunity_soo",
+ "set_extcommunity_bandwidth",
+ "set_extcommunity_bandwidth_non_transitive",
"set_ip_next_hop",
"set_ipv6_next_hop",
"set_large_community",
@@ -70,6 +77,7 @@ class Route_maps(ResourceModule):
"set_src",
"set_tag",
"set_weight",
+ "set_table",
"set_comm_list",
"set_comm_list_delete",
"set_community",
@@ -89,15 +97,34 @@ class Route_maps(ResourceModule):
"on_match_next",
"match_ipv6_address",
"match_ipv6_nexthop",
+ "match_protocol",
"match_rpki",
]
+ def _validate_template(self):
+ version = get_os_version(self._module)
+ if LooseVersion(version) >= LooseVersion("1.4"):
+ self._tmplt = Route_mapsTemplate14()
+ else:
+ self._tmplt = Route_mapsTemplate()
+
+ 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()
@@ -116,16 +143,16 @@ class Route_maps(ResourceModule):
# if state is deleted, empty out wantd and set haved to wantd
if self.state == "deleted":
- haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd}
+ haved = {k: v for k, v in haved.items() if k in wantd or not wantd}
wantd = {}
# remove superfluous config for overridden and deleted
if self.state in ["overridden", "deleted"]:
- for k, have in iteritems(haved):
+ for k, have in haved.items():
if k not in wantd:
self.commands.append(self._tmplt.render({"route_map": k}, "route_map", True))
- for wk, want in iteritems(wantd):
+ for wk, want in wantd.items():
self._compare(want=want, have=haved.pop(wk, {}))
def _compare(self, want, have):
@@ -139,13 +166,13 @@ class Route_maps(ResourceModule):
self._compare_entries(want=w_entries, have=h_entries)
def _compare_entries(self, want, have):
- for wk, wentry in iteritems(want):
+ for wk, wentry in want.items():
hentry = have.pop(wk, {})
self.compare(parsers=self.parsers, want=wentry, have=hentry)
def _route_maps_list_to_dict(self, entry):
entry = {x["route_map"]: x for x in entry}
- for rmap, data in iteritems(entry):
+ for rmap, data in entry.items():
if "entries" in data:
for x in data["entries"]:
x.update({"route_map": rmap})
diff --git a/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py b/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py
index 9497d7fa..cdc1d6e1 100644
--- a/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py
+++ b/plugins/module_utils/network/vyos/config/snmp_server/snmp_server.py
@@ -7,7 +7,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -20,7 +19,6 @@ created.
import re
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
ResourceModule,
)
@@ -107,13 +105,13 @@ class Snmp_server(ResourceModule):
"""
self._compare_lists(want, have)
self._compare_snmp_v3(want, have)
- for key, value in iteritems(want):
+ for key, value in want.items():
self.compare(
parsers=self.parsers,
want={key: value},
have={key: have.pop(key, "")},
)
- for key, entry in iteritems(have):
+ for key, entry in have.items():
if entry:
self.compare(parsers=self.parsers, want={}, have={key: entry})
@@ -125,10 +123,10 @@ class Snmp_server(ResourceModule):
for attrib in parsers:
wdict = get_from_dict(want, attrib) or {}
hdict = get_from_dict(have, attrib) or {}
- for key, entry in iteritems(wdict):
+ for key, entry in wdict.items():
# self.addcmd(entry, attrib, False)
if attrib == "communities":
- for k, v in iteritems(entry):
+ for k, v in entry.items():
if k in ["clients", "networks"]:
v.sort()
h = {}
@@ -153,9 +151,9 @@ class Snmp_server(ResourceModule):
)
have.pop(attrib, {})
# remove remaining items in have for replaced
- for key, entry in iteritems(hdict):
+ for key, entry in hdict.items():
if attrib == "communities":
- for k, v in iteritems(entry):
+ for k, v in entry.items():
if k != "name":
self.compare(
parsers="communities",
@@ -188,9 +186,9 @@ class Snmp_server(ResourceModule):
for attrib in attribute_dict.keys():
wattrib = get_from_dict(wdict, attrib) or {}
hattrib = get_from_dict(hdict, attrib) or {}
- for key, entry in iteritems(wattrib):
+ for key, entry in wattrib.items():
self._compare_snmp_v3_auth_privacy(entry, hattrib.get(key, {}), attrib)
- for k, v in iteritems(entry):
+ for k, v in entry.items():
if k != attribute_dict[attrib]:
h = {}
if hattrib.get(key):
@@ -217,11 +215,11 @@ class Snmp_server(ResourceModule):
have=h,
)
# remove remaining items in have for replaced
- for key, entry in iteritems(hattrib):
+ for key, entry in hattrib.items():
self._compare_snmp_v3_auth_privacy({}, entry, attrib)
self.compare(parsers=parsers, want={}, have={"snmp_v3": {attrib: entry}})
hdict.pop(attrib, {})
- for key, entry in iteritems(wdict):
+ for key, entry in wdict.items():
# self.addcmd(entry, attrib, False)
self.compare(
parsers="snmp_v3.engine_id",
@@ -229,7 +227,7 @@ class Snmp_server(ResourceModule):
have={"snmp_v3": {key: hdict.pop(key, {})}},
)
# remove remaining items in have for replaced
- for key, entry in iteritems(hdict):
+ for key, entry in hdict.items():
self.compare(parsers=parsers, want={}, have={"snmp_v3": {key: entry}})
def _compare_snmp_v3_auth_privacy(self, wattrib, hattrib, attrib):
@@ -244,7 +242,7 @@ class Snmp_server(ResourceModule):
primary_key = "user"
else:
primary_key = "address"
- for key, entry in iteritems(wattrib):
+ for key, entry in wattrib.items():
if key != primary_key and key in ["authentication", "privacy"]:
self.compare(
parsers=parsers,
@@ -265,7 +263,7 @@ class Snmp_server(ResourceModule):
},
},
)
- for key, entry in iteritems(hattrib):
+ for key, entry in hattrib.items():
if key != primary_key and key in ["authentication", "privacy"]:
self.compare(
parsers=parsers,
@@ -291,13 +289,13 @@ class Snmp_server(ResourceModule):
"views": "view",
"trap_targets": "address",
}
- for k, v in iteritems(param_dict):
+ for k, v in param_dict.items():
if k in entry:
a_dict = {}
for el in entry[k]:
a_dict.update({el[v]: el})
entry[k] = a_dict
- for k, v in iteritems(v3_param_dict):
+ for k, v in v3_param_dict.items():
if entry.get("snmp_v3") and k in entry.get("snmp_v3"):
a_dict = {}
for el in entry["snmp_v3"][k]:
diff --git a/plugins/module_utils/network/vyos/config/static_routes/static_routes.py b/plugins/module_utils/network/vyos/config/static_routes/static_routes.py
index 8451e7da..2a09c0f0 100644
--- a/plugins/module_utils/network/vyos/config/static_routes/static_routes.py
+++ b/plugins/module_utils/network/vyos/config/static_routes/static_routes.py
@@ -13,11 +13,9 @@ created
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
from copy import deepcopy
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
ConfigBase,
)
@@ -182,7 +180,7 @@ class Static_routes(ConfigBase):
"""
commands = []
if have:
- for key, value in iteritems(want):
+ for key, value in want.items():
if value:
if key == "next_hops":
commands.extend(self._update_next_hop(want, have))
@@ -257,7 +255,7 @@ class Static_routes(ConfigBase):
"""
commands = []
have = {}
- for key, value in iteritems(want):
+ for key, value in want.items():
if value:
if key == "dest":
commands.append(self._compute_command(dest=want["dest"]))
@@ -287,7 +285,7 @@ class Static_routes(ConfigBase):
updates = dict_delete(want_blackhole, have_blackhole)
if updates:
- for attrib, value in iteritems(updates):
+ for attrib, value in updates.items():
if value:
if attrib == "distance":
commands.append(
@@ -394,7 +392,7 @@ class Static_routes(ConfigBase):
have_blackhole = have_copy.get(key) or {}
updates = dict_delete(have_blackhole, want_blackhole)
if updates:
- for attrib, value in iteritems(updates):
+ for attrib, value in updates.items():
if value:
if attrib == "distance":
commands.append(
@@ -508,7 +506,7 @@ class Static_routes(ConfigBase):
commands.extend(self._add_next_hop(want, have, opr=opr))
if opr and updates:
- for key, value in iteritems(updates):
+ for key, value in updates.items():
if value:
if key == "blackhole_config":
commands.extend(self._add_blackhole(key, want, have))
diff --git a/plugins/module_utils/network/vyos/config/vpn_ipsec/__init__.py b/plugins/module_utils/network/vyos/config/vpn_ipsec/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/vpn_ipsec/__init__.py
diff --git a/plugins/module_utils/network/vyos/config/vpn_ipsec/vpn_ipsec.py b/plugins/module_utils/network/vyos/config/vpn_ipsec/vpn_ipsec.py
new file mode 100644
index 00000000..2d986cb4
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/vpn_ipsec/vpn_ipsec.py
@@ -0,0 +1,665 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2026 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The vyos_vpn_ipsec config file.
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to its desired end-state is
+created.
+
+Follows the established per-module convention used by vyos_ha/vyos_nat
+(list-to-dict conversion + explicit per-state branching in
+generate_commands), rather than a shared generic engine.
+
+State semantics (standard Ansible RM convention, confirmed against a
+real device run that caught a bug in an earlier version of this file):
+ - merged: only items/fields named in `want` are touched. Nothing
+ absent from `want` is ever deleted.
+ - replaced: only items NAMED in `want` are touched (same item scope
+ as merged) -- but for each named item, its full state is
+ reconciled to exactly match `want` (fields present in
+ `have` but omitted from `want` ARE deleted). Items not
+ named in `want` at all are left completely alone.
+ - overridden: every item is in scope, including ones absent from
+ `want` entirely -- those get deleted wholesale. Named
+ items are reconciled the same way as `replaced`.
+
+This is implemented via two independent flags:
+ - select_all: whether item iteration considers have-only items too
+ (True only for overridden; False for merged/replaced).
+ - reconcile: whether omitted fields within an already-selected item
+ get deleted (True for replaced/overridden; False for
+ merged/rendered).
+"""
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
+ ResourceModule,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ dict_merge,
+)
+
+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.vpn_ipsec import (
+ Vpn_ipsecTemplate,
+)
+
+
+class Vpn_ipsec(ResourceModule):
+ """
+ The vyos_vpn_ipsec config class
+ """
+
+ def __init__(self, module):
+ super(Vpn_ipsec, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="vpn_ipsec",
+ tmplt=Vpn_ipsecTemplate(),
+ )
+ self.parsers = [
+ "esp_group",
+ "esp_group.proposal",
+ "esp_group.proposal.encryption",
+ "esp_group.proposal.hash",
+ "esp_group.compression",
+ "esp_group.disable_rekey",
+ "esp_group.life_bytes",
+ "esp_group.life_packets",
+ "esp_group.lifetime",
+ "esp_group.mode",
+ "esp_group.pfs",
+ "ike_group",
+ "ike_group.key_exchange",
+ "ike_group.proposal",
+ "ike_group.proposal.dh_group",
+ "ike_group.proposal.encryption",
+ "ike_group.proposal.hash",
+ "ike_group.close_action",
+ "ike_group.dead_peer_detection.action",
+ "ike_group.dead_peer_detection.interval",
+ "ike_group.dead_peer_detection.timeout",
+ "ike_group.disable_mobike",
+ "ike_group.ikev2_reauth",
+ "ike_group.lifetime",
+ "ike_group.mode",
+ "profile",
+ "profile.authentication.mode",
+ "profile.authentication.pre_shared_secret",
+ "profile.esp_group",
+ "profile.ike_group",
+ "profile.disable",
+ "authentication.psk.secret_type",
+ "authentication.psk.dhcp_interface",
+ "authentication.ppk",
+ "authentication.ppk.id",
+ "authentication.ppk.secret",
+ "authentication.ppk.secret_type",
+ "interface",
+ "log.level",
+ "log.subsystem",
+ "options.disable_route_autoinstall",
+ "options.flexvpn",
+ "options.interface",
+ "options.retransmission.attempts",
+ "options.retransmission.base",
+ "options.retransmission.timeout",
+ "options.virtual_ip",
+ "disable_uniqreqids",
+ ]
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ if self.state not in ["parsed", "gathered"]:
+ self.generate_commands()
+ self.run_commands()
+ return self.result
+
+ def generate_commands(self):
+ """Generate configuration commands to send based on
+ want, have and desired state.
+ """
+ wantd = deepcopy(self.want) or {}
+ haved = deepcopy(self.have) or {}
+
+ for entry in (wantd, haved):
+ self._list_to_dict(entry)
+
+ scoped_delete = None
+ if self.state == "deleted":
+ if wantd:
+ # user named specific items -- surgical removal of just
+ # those, everything else preserved (vyos_vrf precedent:
+ # deleted + instances:[{name: vrf-blue}] removes only
+ # vrf-blue). Capture what was named before wiping wantd.
+ scoped_delete = wantd
+ wantd = {}
+
+ if self.state == "merged":
+ # NOTE: list_to_dict() above must run BEFORE this. dict_merge
+ # concatenates lists rather than merging matching entries by
+ # key, so merging while ike_group/esp_group/etc are still
+ # lists would duplicate entries instead of filling in omitted
+ # fields from `have`. Once they're name-keyed dicts, dict_merge
+ # recurses per-key correctly, which is what lets a partial
+ # update (e.g. specifying only key_exchange) leave other
+ # existing fields on that same group untouched.
+ wantd = dict_merge(haved, wantd)
+
+ select_all = self.state in ("overridden", "deleted")
+ reconcile = self.state in ("replaced", "overridden", "deleted")
+
+ self._compare_esp_groups(wantd, haved, select_all, reconcile, scoped_delete)
+ self._compare_ike_groups(wantd, haved, select_all, reconcile, scoped_delete)
+ self._compare_profiles(wantd, haved, select_all, reconcile, scoped_delete)
+ self._compare_psks(wantd, haved, select_all, reconcile, scoped_delete)
+ self._compare_ppks(wantd, haved, select_all, reconcile, scoped_delete)
+ self._compare_top_level(wantd, haved, select_all, reconcile, scoped_delete)
+
+ self.commands = list(dict.fromkeys(self.commands))
+
+ # -------------------------------------------------------------------
+ # List -> name-keyed dict conversion (matches vyos_ha/vyos_nat style)
+ # -------------------------------------------------------------------
+
+ def _list_to_dict(self, config):
+ for key in ("ike_group", "esp_group", "profile"):
+ items = config.get(key)
+ if isinstance(items, list):
+ config[key] = {item["name"]: item for item in items}
+ for item in config[key].values():
+ if isinstance(item.get("proposal"), list):
+ item["proposal"] = {p["proposal_id"]: p for p in item["proposal"]}
+
+ auth = config.get("authentication", {})
+ for key in ("psk", "ppk"):
+ items = auth.get(key)
+ if isinstance(items, list):
+ auth[key] = {item["name"]: item for item in items}
+
+ # -------------------------------------------------------------------
+ # ESP groups
+ # -------------------------------------------------------------------
+
+ def _compare_esp_groups(self, wantd, haved, select_all, reconcile, scoped_delete=None):
+ have_groups = haved.get("esp_group", {})
+
+ if scoped_delete is not None:
+ for name in set(scoped_delete.get("esp_group", {})):
+ if name in have_groups:
+ self.commands.append("delete vpn ipsec esp-group {0}".format(name))
+ return
+
+ want_groups = wantd.get("esp_group", {})
+ names = set(want_groups) | set(have_groups) if select_all else set(want_groups)
+
+ for name in names:
+ w = want_groups.get(name, {})
+ h = have_groups.get(name, {})
+ if w == h:
+ continue
+
+ if name in have_groups and name not in want_groups:
+ # only reached when select_all (overridden): item entirely
+ # absent from want -> delete wholesale
+ self.commands.append("delete vpn ipsec esp-group {0}".format(name))
+ continue
+
+ if name not in have_groups:
+ self.addcmd({"name": name}, "esp_group", False)
+
+ for field in ("mode", "pfs", "lifetime", "life_bytes", "life_packets"):
+ self._cmp_scalar(
+ w,
+ h,
+ field,
+ {"name": name},
+ "esp_group.{0}".format(field),
+ reconcile,
+ )
+ for field in ("compression", "disable_rekey"):
+ self._cmp_bool(
+ w,
+ h,
+ field,
+ {"name": name},
+ "esp_group.{0}".format(field),
+ reconcile,
+ )
+
+ self._compare_proposals(
+ w.get("proposal", {}),
+ h.get("proposal", {}),
+ {"name": name},
+ "esp_group.proposal",
+ "esp_group.proposal.encryption",
+ "esp_group.proposal.hash",
+ None,
+ reconcile,
+ )
+
+ # -------------------------------------------------------------------
+ # IKE groups
+ # -------------------------------------------------------------------
+
+ def _compare_ike_groups(self, wantd, haved, select_all, reconcile, scoped_delete=None):
+ have_groups = haved.get("ike_group", {})
+
+ if scoped_delete is not None:
+ for name in set(scoped_delete.get("ike_group", {})):
+ if name in have_groups:
+ self.commands.append("delete vpn ipsec ike-group {0}".format(name))
+ return
+
+ want_groups = wantd.get("ike_group", {})
+ names = set(want_groups) | set(have_groups) if select_all else set(want_groups)
+
+ for name in names:
+ w = want_groups.get(name, {})
+ h = have_groups.get(name, {})
+ if w == h:
+ continue
+
+ if name in have_groups and name not in want_groups:
+ self.commands.append("delete vpn ipsec ike-group {0}".format(name))
+ continue
+
+ if name not in have_groups:
+ self.addcmd({"name": name}, "ike_group", False)
+
+ self._cmp_scalar(
+ w,
+ h,
+ "key_exchange",
+ {"name": name},
+ "ike_group.key_exchange",
+ reconcile,
+ )
+ for field in ("close_action", "lifetime", "mode"):
+ self._cmp_scalar(
+ w,
+ h,
+ field,
+ {"name": name},
+ "ike_group.{0}".format(field),
+ reconcile,
+ )
+ for field in ("disable_mobike", "ikev2_reauth"):
+ self._cmp_bool(
+ w,
+ h,
+ field,
+ {"name": name},
+ "ike_group.{0}".format(field),
+ reconcile,
+ )
+
+ w_dpd = w.get("dead_peer_detection", {})
+ h_dpd = h.get("dead_peer_detection", {})
+ for field in ("action", "interval", "timeout"):
+ self._cmp_scalar(
+ w_dpd,
+ h_dpd,
+ field,
+ {"name": name},
+ "ike_group.dead_peer_detection.{0}".format(field),
+ reconcile,
+ )
+
+ self._compare_proposals(
+ w.get("proposal", {}),
+ h.get("proposal", {}),
+ {"name": name},
+ "ike_group.proposal",
+ "ike_group.proposal.encryption",
+ "ike_group.proposal.hash",
+ "ike_group.proposal.dh_group",
+ reconcile,
+ )
+
+ # -------------------------------------------------------------------
+ # Proposals (shared by esp_group / ike_group)
+ # -------------------------------------------------------------------
+
+ def _compare_proposals(
+ self,
+ want_props,
+ have_props,
+ group_ctx,
+ bare_parser,
+ encryption_parser,
+ hash_parser,
+ dh_group_parser,
+ reconcile,
+ ):
+ # a proposal collection lives entirely inside an already-selected
+ # group -- once that group is in scope, its own proposals always
+ # get full reconciliation under replaced/overridden (never a
+ # separate select_all concern of their own).
+ ids = set(want_props) | set(have_props) if reconcile else set(want_props)
+ for pid in ids:
+ w = want_props.get(pid, {})
+ h = have_props.get(pid, {})
+ if w == h:
+ continue
+
+ if pid in have_props and pid not in want_props:
+ self.addcmd(dict(group_ctx, proposal_id=pid), bare_parser, True)
+ continue
+
+ if pid not in have_props:
+ self.addcmd(dict(group_ctx, proposal_id=pid), bare_parser, False)
+
+ ctx = dict(group_ctx, proposal_id=pid)
+ self._cmp_scalar(w, h, "encryption", ctx, encryption_parser, reconcile)
+ self._cmp_scalar(w, h, "hash", ctx, hash_parser, reconcile)
+ if dh_group_parser:
+ self._cmp_scalar(w, h, "dh_group", ctx, dh_group_parser, reconcile)
+
+ # -------------------------------------------------------------------
+ # Profiles
+ # -------------------------------------------------------------------
+
+ def _compare_profiles(self, wantd, haved, select_all, reconcile, scoped_delete=None):
+ have_profiles = haved.get("profile", {})
+
+ if scoped_delete is not None:
+ for name in set(scoped_delete.get("profile", {})):
+ if name in have_profiles:
+ self.commands.append("delete vpn ipsec profile {0}".format(name))
+ return
+
+ want_profiles = wantd.get("profile", {})
+ names = set(want_profiles) | set(have_profiles) if select_all else set(want_profiles)
+
+ for name in names:
+ w = want_profiles.get(name, {})
+ h = have_profiles.get(name, {})
+ if w == h:
+ continue
+
+ if name in have_profiles and name not in want_profiles:
+ self.commands.append("delete vpn ipsec profile {0}".format(name))
+ continue
+
+ if name not in have_profiles:
+ self.addcmd({"name": name}, "profile", False)
+
+ ctx = {"name": name}
+ w_auth = w.get("authentication", {})
+ h_auth = h.get("authentication", {})
+ self._cmp_scalar(
+ w_auth,
+ h_auth,
+ "mode",
+ ctx,
+ "profile.authentication.mode",
+ reconcile,
+ )
+ self._cmp_scalar(
+ w_auth,
+ h_auth,
+ "pre_shared_secret",
+ ctx,
+ "profile.authentication.pre_shared_secret",
+ reconcile,
+ )
+ self._cmp_scalar(w, h, "esp_group", ctx, "profile.esp_group", reconcile)
+ self._cmp_scalar(w, h, "ike_group", ctx, "profile.ike_group", reconcile)
+ self._cmp_bool(w, h, "disable", ctx, "profile.disable", reconcile)
+
+ w_tunnels = set(w.get("bind_tunnel") or [])
+ h_tunnels = set(h.get("bind_tunnel") or [])
+ for tun in w_tunnels - h_tunnels:
+ self.addcmd(dict(ctx, bind_tunnel=tun), "profile.bind_tunnel", False)
+ if reconcile:
+ for tun in h_tunnels - w_tunnels:
+ self.addcmd(dict(ctx, bind_tunnel=tun), "profile.bind_tunnel", True)
+
+ # -------------------------------------------------------------------
+ # PSKs
+ # -------------------------------------------------------------------
+
+ def _compare_psks(self, wantd, haved, select_all, reconcile, scoped_delete=None):
+ have_psks = haved.get("authentication", {}).get("psk", {})
+
+ if scoped_delete is not None:
+ for name in set(scoped_delete.get("authentication", {}).get("psk", {})):
+ if name in have_psks:
+ self.commands.append(
+ "delete vpn ipsec authentication psk {0}".format(name),
+ )
+ return
+
+ want_psks = wantd.get("authentication", {}).get("psk", {})
+ names = set(want_psks) | set(have_psks) if select_all else set(want_psks)
+
+ for name in names:
+ w = want_psks.get(name, {})
+ h = have_psks.get(name, {})
+ if w == h:
+ continue
+
+ if name in have_psks and name not in want_psks:
+ self.commands.append("delete vpn ipsec authentication psk {0}".format(name))
+ continue
+
+ if name not in have_psks:
+ self.addcmd({"name": name}, "authentication.psk", False)
+
+ ctx = {"name": name}
+ self._cmp_scalar(w, h, "secret", ctx, "authentication.psk.secret", reconcile)
+ self._cmp_scalar(
+ w,
+ h,
+ "secret_type",
+ ctx,
+ "authentication.psk.secret_type",
+ reconcile,
+ )
+
+ w_ids = set(w.get("id") or [])
+ h_ids = set(h.get("id") or [])
+ for i in w_ids - h_ids:
+ self.addcmd(dict(ctx, id=i), "authentication.psk.id", False)
+ if reconcile:
+ for i in h_ids - w_ids:
+ self.addcmd(dict(ctx, id=i), "authentication.psk.id", True)
+
+ w_dhcp = set(w.get("dhcp_interface") or [])
+ h_dhcp = set(h.get("dhcp_interface") or [])
+ for i in w_dhcp - h_dhcp:
+ self.addcmd(dict(ctx, dhcp_interface=i), "authentication.psk.dhcp_interface", False)
+ if reconcile:
+ for i in h_dhcp - w_dhcp:
+ self.addcmd(
+ dict(ctx, dhcp_interface=i),
+ "authentication.psk.dhcp_interface",
+ True,
+ )
+
+ def _compare_ppks(self, wantd, haved, select_all, reconcile, scoped_delete=None):
+ have_ppks = haved.get("authentication", {}).get("ppk", {})
+
+ if scoped_delete is not None:
+ for name in set(scoped_delete.get("authentication", {}).get("ppk", {})):
+ if name in have_ppks:
+ self.commands.append(
+ "delete vpn ipsec authentication ppk {0}".format(name),
+ )
+ return
+
+ want_ppks = wantd.get("authentication", {}).get("ppk", {})
+ names = set(want_ppks) | set(have_ppks) if select_all else set(want_ppks)
+
+ for name in names:
+ w = want_ppks.get(name, {})
+ h = have_ppks.get(name, {})
+ if w == h:
+ continue
+
+ if name in have_ppks and name not in want_ppks:
+ self.commands.append("delete vpn ipsec authentication ppk {0}".format(name))
+ continue
+
+ if name not in have_ppks:
+ self.addcmd({"name": name}, "authentication.ppk", False)
+
+ ctx = {"name": name}
+ self._cmp_scalar(w, h, "secret", ctx, "authentication.ppk.secret", reconcile)
+ self._cmp_scalar(
+ w,
+ h,
+ "secret_type",
+ ctx,
+ "authentication.ppk.secret_type",
+ reconcile,
+ )
+
+ w_ids = set(w.get("id") or [])
+ h_ids = set(h.get("id") or [])
+ for i in w_ids - h_ids:
+ self.addcmd(dict(ctx, id=i), "authentication.ppk.id", False)
+ if reconcile:
+ for i in h_ids - w_ids:
+ self.addcmd(dict(ctx, id=i), "authentication.ppk.id", True)
+
+ # -------------------------------------------------------------------
+ # Top-level scalar / list / bool fields
+ #
+ # NOTE: these are all direct fields of the single top-level config
+ # object, not named collections -- there is no "item entirely absent
+ # from want" concept here, only "field omitted from want". So only
+ # `reconcile` applies; `select_all` is irrelevant at this level (it's
+ # accepted for a consistent call signature but unused).
+ # -------------------------------------------------------------------
+
+ def _compare_top_level(self, wantd, haved, select_all, reconcile, scoped_delete=None):
+ if scoped_delete is not None:
+ # Principle: naming a parameter under scoped `deleted` means
+ # "delete this specific value" -- a scalar/bool key present
+ # (regardless of value) signals whole-field removal; a list
+ # value present means "delete exactly these elements", not
+ # the whole list, mirroring vyos_vrf's bind_to_all precedent
+ # extended consistently to list- and nested-dict-shaped
+ # fields.
+ if "disable_uniqreqids" in scoped_delete and haved.get("disable_uniqreqids"):
+ self.commands.append("delete vpn ipsec disable-uniqreqids")
+
+ h_ifaces = set(haved.get("interface") or [])
+ for i in set(scoped_delete.get("interface") or []) & h_ifaces:
+ self.addcmd({"interface": i}, "interface", True)
+
+ s_log = scoped_delete.get("log", {})
+ h_log = haved.get("log", {})
+ if "level" in s_log and "level" in h_log:
+ self.addcmd({"level": h_log["level"]}, "log.level", True)
+ h_sub = set(h_log.get("subsystem") or [])
+ for s in set(s_log.get("subsystem") or []) & h_sub:
+ self.addcmd({"subsystem": s}, "log.subsystem", True)
+
+ s_opt = scoped_delete.get("options", {})
+ h_opt = haved.get("options", {})
+ for field in ("disable_route_autoinstall", "flexvpn", "virtual_ip"):
+ if field in s_opt and h_opt.get(field):
+ self.addcmd({}, "options.{0}".format(field), True)
+ if "interface" in s_opt and "interface" in h_opt:
+ self.addcmd({"interface": h_opt["interface"]}, "options.interface", True)
+
+ s_retrans = s_opt.get("retransmission", {})
+ h_retrans = h_opt.get("retransmission", {})
+ for field in ("attempts", "base", "timeout"):
+ if field in s_retrans and field in h_retrans:
+ self.addcmd(
+ {field: h_retrans[field]},
+ "options.retransmission.{0}".format(field),
+ True,
+ )
+ return
+
+ self._cmp_bool(wantd, haved, "disable_uniqreqids", {}, "disable_uniqreqids", reconcile)
+
+ w_ifaces = set(wantd.get("interface") or [])
+ h_ifaces = set(haved.get("interface") or [])
+ for i in w_ifaces - h_ifaces:
+ self.addcmd({"interface": i}, "interface", False)
+ if reconcile:
+ for i in h_ifaces - w_ifaces:
+ self.addcmd({"interface": i}, "interface", True)
+
+ w_log = wantd.get("log", {})
+ h_log = haved.get("log", {})
+ self._cmp_scalar(w_log, h_log, "level", {}, "log.level", reconcile)
+ w_sub = set(w_log.get("subsystem") or [])
+ h_sub = set(h_log.get("subsystem") or [])
+ for s in w_sub - h_sub:
+ self.addcmd({"subsystem": s}, "log.subsystem", False)
+ if reconcile:
+ for s in h_sub - w_sub:
+ self.addcmd({"subsystem": s}, "log.subsystem", True)
+
+ w_opt = wantd.get("options", {})
+ h_opt = haved.get("options", {})
+ for field in ("disable_route_autoinstall", "flexvpn", "virtual_ip"):
+ self._cmp_bool(w_opt, h_opt, field, {}, "options.{0}".format(field), reconcile)
+ self._cmp_scalar(w_opt, h_opt, "interface", {}, "options.interface", reconcile)
+
+ w_retrans = w_opt.get("retransmission", {})
+ h_retrans = h_opt.get("retransmission", {})
+ for field in ("attempts", "base", "timeout"):
+ self._cmp_scalar(
+ w_retrans,
+ h_retrans,
+ field,
+ {},
+ "options.retransmission.{0}".format(field),
+ reconcile,
+ )
+
+ # -------------------------------------------------------------------
+ # Field-level helpers (mirrors vyos_nat's _cmp_scalar / _cmp_bool)
+ # -------------------------------------------------------------------
+
+ def _cmp_scalar(self, want, have, field, ctx, parser, reconcile=False):
+ w = want.get(field)
+ h = have.get(field)
+ if w != h:
+ if w is not None:
+ self.addcmd(dict(ctx, **{field: w}), parser, False)
+ elif reconcile and h is not None:
+ self.addcmd(dict(ctx, **{field: h}), parser, True)
+
+ def _cmp_bool(self, want, have, field, ctx, parser, reconcile=False):
+ # An explicitly-specified value (even False) is always enforced,
+ # regardless of state -- that's the user directly saying what
+ # they want. An OMITTED field is only enforced (i.e. deleted if
+ # currently True) under full reconciliation (replaced/overridden).
+ # Under merged, an omitted field is left alone -- protected
+ # further upstream by dict_merge backfilling `want` from `have`
+ # before this is ever reached, but this still needs to be correct
+ # in isolation (e.g. for a field nested inside a dict that wasn't
+ # part of the dict_merge'd top-level structure).
+ explicit = field in want
+ w = bool(want.get(field))
+ h = bool(have.get(field))
+ if w != h and (w or explicit or reconcile):
+ self.addcmd(dict(ctx), parser, not w)
diff --git a/plugins/module_utils/network/vyos/config/vpn_ipsec_s2s/__init__.py b/plugins/module_utils/network/vyos/config/vpn_ipsec_s2s/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/vpn_ipsec_s2s/__init__.py
diff --git a/plugins/module_utils/network/vyos/config/vpn_ipsec_s2s/vpn_ipsec_s2s.py b/plugins/module_utils/network/vyos/config/vpn_ipsec_s2s/vpn_ipsec_s2s.py
new file mode 100644
index 00000000..65232ed7
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/vpn_ipsec_s2s/vpn_ipsec_s2s.py
@@ -0,0 +1,355 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2026 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The vyos_vpn_ipsec_s2s config file.
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to its desired end-state is
+created.
+
+Mirrors vyos_vpn_ipsec's config.py exactly -- same list-to-dict
+conversion + explicit per-state branching, same select_all/reconcile
+two-flag design for the replaced/overridden distinction, same scoped
+deleted handling. See that file's own docstring for the full state
+semantics; the summary:
+
+ - merged: only items/fields named in `want` are touched.
+ - replaced: only items NAMED in `want` are touched, but each named
+ item is fully reconciled (omitted fields removed).
+ - overridden: every item is in scope, including ones absent from
+ `want` -- those get deleted wholesale. Named items
+ reconciled the same way as replaced.
+ - deleted: bare (no config) deletes everything; a scoped config
+ deletes only what's named, down to individual list
+ elements.
+"""
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
+ ResourceModule,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ dict_merge,
+)
+
+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.vpn_ipsec_s2s import (
+ Vpn_ipsec_s2sTemplate,
+)
+
+
+class Vpn_ipsec_s2s(ResourceModule):
+ """
+ The vyos_vpn_ipsec_s2s config class
+ """
+
+ def __init__(self, module):
+ super(Vpn_ipsec_s2s, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="vpn_ipsec_s2s",
+ tmplt=Vpn_ipsec_s2sTemplate(),
+ )
+
+ def execute_module(self):
+ if self.state not in ["parsed", "gathered"]:
+ self.generate_commands()
+ self.run_commands()
+ return self.result
+
+ def generate_commands(self):
+ wantd = deepcopy(self.want) or {}
+ haved = deepcopy(self.have) or {}
+
+ for entry in (wantd, haved):
+ self._list_to_dict(entry)
+
+ scoped_delete = None
+ if self.state == "deleted":
+ if wantd:
+ scoped_delete = wantd
+ wantd = {}
+
+ if self.state == "merged":
+ # list_to_dict() above must run BEFORE this -- dict_merge
+ # concatenates raw lists rather than merging matching items
+ # by key, so it only does the right thing once both sides
+ # are already name-keyed dicts.
+ wantd = dict_merge(haved, wantd)
+
+ select_all = self.state in ("overridden", "deleted")
+ reconcile = self.state in ("replaced", "overridden", "deleted")
+
+ self._compare_peers(wantd, haved, select_all, reconcile, scoped_delete)
+
+ self.commands = list(dict.fromkeys(self.commands))
+
+ # -------------------------------------------------------------------
+ # List -> name-keyed dict conversion
+ # -------------------------------------------------------------------
+
+ def _list_to_dict(self, config):
+ peers = config.get("peer")
+ if isinstance(peers, list):
+ config["peer"] = {p["name"]: p for p in peers}
+ for peer in config["peer"].values():
+ if isinstance(peer.get("tunnel"), list):
+ peer["tunnel"] = {t["tunnel_id"]: t for t in peer["tunnel"]}
+
+ # -------------------------------------------------------------------
+ # Peers
+ # -------------------------------------------------------------------
+
+ def _compare_peers(self, wantd, haved, select_all, reconcile, scoped_delete=None):
+ have_peers = haved.get("peer", {})
+
+ if scoped_delete is not None:
+ for name in set(scoped_delete.get("peer", {})):
+ if name in have_peers:
+ self.commands.append(
+ "delete vpn ipsec site-to-site peer {0}".format(name),
+ )
+ return
+
+ want_peers = wantd.get("peer", {})
+ names = set(want_peers) | set(have_peers) if select_all else set(want_peers)
+
+ for name in names:
+ w = want_peers.get(name, {})
+ h = have_peers.get(name, {})
+ if w == h:
+ continue
+
+ if name in have_peers and name not in want_peers:
+ self.commands.append(
+ "delete vpn ipsec site-to-site peer {0}".format(name),
+ )
+ continue
+
+ if name not in have_peers:
+ self.addcmd({"name": name}, "peer", False)
+
+ ctx = {"name": name}
+ self._cmp_bool(w, h, "disable", ctx, "peer.disable", reconcile)
+
+ w_auth = w.get("authentication", {})
+ h_auth = h.get("authentication", {})
+ for field in ("local_id", "remote_id", "mode"):
+ self._cmp_scalar(
+ w_auth,
+ h_auth,
+ field,
+ ctx,
+ "peer.authentication.{0}".format(field),
+ reconcile,
+ )
+ self._cmp_bool(
+ w_auth,
+ h_auth,
+ "use_x509_id",
+ ctx,
+ "peer.authentication.use_x509_id",
+ reconcile,
+ )
+
+ w_ppk = w_auth.get("ppk", {})
+ h_ppk = h_auth.get("ppk", {})
+ self._cmp_scalar(w_ppk, h_ppk, "id", ctx, "peer.authentication.ppk.id", reconcile)
+ self._cmp_bool(
+ w_ppk,
+ h_ppk,
+ "required",
+ ctx,
+ "peer.authentication.ppk.required",
+ reconcile,
+ )
+
+ w_rsa = w_auth.get("rsa", {})
+ h_rsa = h_auth.get("rsa", {})
+ for field in ("local_key", "remote_key", "passphrase"):
+ self._cmp_scalar(
+ w_rsa,
+ h_rsa,
+ field,
+ ctx,
+ "peer.authentication.rsa.{0}".format(field),
+ reconcile,
+ )
+
+ w_x509 = w_auth.get("x509", {})
+ h_x509 = h_auth.get("x509", {})
+ for field in ("certificate", "passphrase"):
+ self._cmp_scalar(
+ w_x509,
+ h_x509,
+ field,
+ ctx,
+ "peer.authentication.x509.{0}".format(field),
+ reconcile,
+ )
+ w_ca = set(w_x509.get("ca_certificate") or [])
+ h_ca = set(h_x509.get("ca_certificate") or [])
+ for cert in w_ca - h_ca:
+ self.addcmd(
+ dict(ctx, ca_certificate=cert),
+ "peer.authentication.x509.ca_certificate",
+ False,
+ )
+ if reconcile:
+ for cert in h_ca - w_ca:
+ self.addcmd(
+ dict(ctx, ca_certificate=cert),
+ "peer.authentication.x509.ca_certificate",
+ True,
+ )
+
+ for field in (
+ "childless",
+ "connection_type",
+ "default_esp_group",
+ "description",
+ "dhcp_interface",
+ "ike_group",
+ "ikev2_reauth",
+ "local_address",
+ ):
+ self._cmp_scalar(w, h, field, ctx, "peer.{0}".format(field), reconcile)
+ self._cmp_bool(
+ w,
+ h,
+ "force_udp_encapsulation",
+ ctx,
+ "peer.force_udp_encapsulation",
+ reconcile,
+ )
+ self._cmp_scalar(w, h, "replay_window", ctx, "peer.replay_window", reconcile)
+
+ w_remote_addr = set(w.get("remote_address") or [])
+ h_remote_addr = set(h.get("remote_address") or [])
+ for addr in w_remote_addr - h_remote_addr:
+ self.addcmd(dict(ctx, remote_address=addr), "peer.remote_address", False)
+ if reconcile:
+ for addr in h_remote_addr - w_remote_addr:
+ self.addcmd(dict(ctx, remote_address=addr), "peer.remote_address", True)
+
+ w_virt_addr = set(w.get("virtual_address") or [])
+ h_virt_addr = set(h.get("virtual_address") or [])
+ for addr in w_virt_addr - h_virt_addr:
+ self.addcmd(dict(ctx, virtual_address=addr), "peer.virtual_address", False)
+ if reconcile:
+ for addr in h_virt_addr - w_virt_addr:
+ self.addcmd(dict(ctx, virtual_address=addr), "peer.virtual_address", True)
+
+ self._compare_tunnels(w.get("tunnel", {}), h.get("tunnel", {}), ctx, reconcile)
+ self._compare_vti(w.get("vti", {}), h.get("vti", {}), ctx, reconcile)
+
+ # -------------------------------------------------------------------
+ # Tunnels (nested collection within a peer)
+ # -------------------------------------------------------------------
+
+ def _compare_tunnels(self, want_tunnels, have_tunnels, peer_ctx, reconcile):
+ # A tunnel collection lives entirely inside an already-selected
+ # peer -- once that peer is in scope, its own tunnels always get
+ # full reconciliation under replaced/overridden, matching how
+ # esp_group/ike_group's own nested proposals behave in the
+ # profile module.
+ ids = set(want_tunnels) | set(have_tunnels) if reconcile else set(want_tunnels)
+ for tid in ids:
+ w = want_tunnels.get(tid, {})
+ h = have_tunnels.get(tid, {})
+ if w == h:
+ continue
+
+ if tid in have_tunnels and tid not in want_tunnels:
+ self.addcmd(dict(peer_ctx, tunnel_id=tid), "peer.tunnel", True)
+ continue
+
+ if tid not in have_tunnels:
+ self.addcmd(dict(peer_ctx, tunnel_id=tid), "peer.tunnel", False)
+
+ ctx = dict(peer_ctx, tunnel_id=tid)
+ self._cmp_bool(w, h, "disable", ctx, "peer.tunnel.disable", reconcile)
+ for field in ("esp_group", "protocol"):
+ self._cmp_scalar(w, h, field, ctx, "peer.tunnel.{0}".format(field), reconcile)
+ self._cmp_scalar(w, h, "priority", ctx, "peer.tunnel.priority", reconcile)
+
+ for side in ("local", "remote"):
+ w_side = w.get(side, {})
+ h_side = h.get(side, {})
+ self._cmp_scalar(
+ w_side,
+ h_side,
+ "port",
+ ctx,
+ "peer.tunnel.{0}.port".format(side),
+ reconcile,
+ )
+ w_prefix = set(w_side.get("prefix") or [])
+ h_prefix = set(h_side.get("prefix") or [])
+ for p in w_prefix - h_prefix:
+ self.addcmd(
+ dict(ctx, prefix=p),
+ "peer.tunnel.{0}.prefix".format(side),
+ False,
+ )
+ if reconcile:
+ for p in h_prefix - w_prefix:
+ self.addcmd(
+ dict(ctx, prefix=p),
+ "peer.tunnel.{0}.prefix".format(side),
+ True,
+ )
+
+ # -------------------------------------------------------------------
+ # VTI (nested dict within a peer, not a collection)
+ # -------------------------------------------------------------------
+
+ def _compare_vti(self, w_vti, h_vti, peer_ctx, reconcile):
+ for field in ("bind", "esp_group"):
+ self._cmp_scalar(w_vti, h_vti, field, peer_ctx, "peer.vti.{0}".format(field), reconcile)
+
+ w_ts = w_vti.get("traffic_selector", {})
+ h_ts = h_vti.get("traffic_selector", {})
+ for side in ("local", "remote"):
+ w_prefix = set(w_ts.get(side, {}).get("prefix") or [])
+ h_prefix = set(h_ts.get(side, {}).get("prefix") or [])
+ parser = "peer.vti.traffic_selector.{0}.prefix".format(side)
+ for p in w_prefix - h_prefix:
+ self.addcmd(dict(peer_ctx, prefix=p), parser, False)
+ if reconcile:
+ for p in h_prefix - w_prefix:
+ self.addcmd(dict(peer_ctx, prefix=p), parser, True)
+
+ # -------------------------------------------------------------------
+ # Field-level helpers (mirrors vyos_vpn_ipsec's own)
+ # -------------------------------------------------------------------
+
+ def _cmp_scalar(self, want, have, field, ctx, parser, reconcile=False):
+ w = want.get(field)
+ h = have.get(field)
+ if w != h:
+ if w is not None:
+ self.addcmd(dict(ctx, **{field: w}), parser, False)
+ elif reconcile and h is not None:
+ self.addcmd(dict(ctx, **{field: h}), parser, True)
+
+ def _cmp_bool(self, want, have, field, ctx, parser, reconcile=False):
+ explicit = field in want
+ w = bool(want.get(field))
+ h = bool(have.get(field))
+ if w != h and (w or explicit or reconcile):
+ self.addcmd(dict(ctx), parser, not w)
diff --git a/plugins/module_utils/network/vyos/config/vrf/__init__.py b/plugins/module_utils/network/vyos/config/vrf/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/vrf/__init__.py
diff --git a/plugins/module_utils/network/vyos/config/vrf/vrf.py b/plugins/module_utils/network/vyos/config/vrf/vrf.py
new file mode 100644
index 00000000..a55073b0
--- /dev/null
+++ b/plugins/module_utils/network/vyos/config/vrf/vrf.py
@@ -0,0 +1,307 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+"""
+The vyos_vrf config file.
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to its desired end-state is
+created.
+"""
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
+ ResourceModule,
+)
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.bgp_global.bgp_global import (
+ Bgp_global,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv2.ospfv2 import (
+ Ospfv2,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.ospfv3.ospfv3 import (
+ Ospfv3,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.config.static_routes.static_routes import (
+ Static_routes,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.facts import Facts
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrf import (
+ VrfTemplate,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import combine
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
+
+
+class Vrf(ResourceModule):
+ """
+ The vyos_vrf config class
+ """
+
+ def __init__(self, module):
+ super(Vrf, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="vrf",
+ tmplt=VrfTemplate(),
+ )
+ self.parsers = [
+ "bind_to_all",
+ ]
+
+ def _validate_template(self):
+ version = get_os_version(self._module)
+ if LooseVersion(version) >= LooseVersion("1.4"):
+ self._tmplt = VrfTemplate()
+ else:
+ self._module.fail_json(msg="VRF is not supported in this version of VyOS")
+
+ def parse(self):
+ """override parse to check template"""
+ self._validate_template()
+ return super().parse()
+
+ def get_parser(self, name):
+ """get_parsers"""
+ self._validate_template()
+ return super().get_parser(name)
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ if self.state not in ["parsed", "gathered"]:
+ self.generate_commands()
+ self.run_commands()
+
+ return self.result
+
+ def generate_commands(self):
+ """Generate configuration commands to send based on
+ want, have and desired state.
+ """
+ wantd = {}
+ haved = {}
+ wantd = deepcopy(self.want)
+ haved = deepcopy(self.have)
+
+ # if state is merged, merge want onto have and then compare
+ if self.state in ["merged", "replaced"]:
+
+ wantd = combine(haved, wantd, recursive=True)
+
+ # if state is deleted, delete and empty out wantd
+ if self.state == "deleted":
+ w = deepcopy(wantd)
+ if w == {} and haved != {}:
+ self.commands = ["delete vrf"]
+ return
+ for k, want in w.items():
+ if not (k in haved and haved[k]):
+ del wantd[k]
+ else:
+ if isinstance(want, list):
+ for entry in want:
+ wname = entry.get("name")
+ haved["instances"] = [
+ i for i in haved.get("instances", []) if i.get("name") != wname
+ ]
+ self.commands.append("delete vrf name {}".format(wname))
+ else:
+ self.commands.append("delete vrf {}".format(k.replace("_", "-")))
+ del wantd[k]
+
+ if self.state == "overridden":
+ w = deepcopy(wantd)
+ h = deepcopy(haved)
+ for k, want in w.items():
+ if k in haved and haved[k] != want:
+ if isinstance(want, list):
+ for entry in want:
+ wname = entry.get("name")
+ hdict = next(
+ (inst for inst in haved["instances"] if inst["name"] == wname),
+ None,
+ )
+ wantc = self._canonicalise(entry)
+ havec = self._canonicalise(hdict or {})
+
+ if wantc != havec:
+ haved["instances"] = [
+ i for i in haved.get("instances", []) if i.get("name") != wname
+ ]
+ self.commands.append("delete vrf name {}".format(wname))
+ self.commands.append("commit")
+
+ for k, want in wantd.items():
+ if isinstance(want, list):
+ self._compare_instances(want=want, have=haved.pop(k, {}))
+ self.compare(
+ parsers=self.parsers,
+ want={k: want},
+ have={k: haved.pop(k, {})},
+ )
+
+ def _compare_instances(self, want, have):
+ """Compare the instances of the VRF"""
+ parsers = [
+ "table_id",
+ "vni",
+ "description",
+ "disable_vrf",
+ ]
+
+ for entry in want:
+ h = {}
+ wname = entry.get("name")
+ h = {
+ k: v
+ for vrf in have
+ if vrf.get("name") == wname
+ for k, v in vrf.items()
+ if k != "address_family"
+ }
+ self.compare(parsers=parsers, want=entry, have=h)
+
+ if "address_family" in entry:
+ wafi = {"name": wname, "address_family": entry.get("address_family", [])}
+ hdict = next((d for d in have if d.get("name") == wname), None)
+
+ hafi = {
+ "name": (hdict or {"name": wname})["name"],
+ "address_family": hdict.get("address_family", []) if hdict else [],
+ }
+
+ self._compare_addr_family(wafi, hafi)
+
+ if "protocols" in entry:
+ for protocol_name in entry["protocols"]:
+
+ w_p_dict = entry["protocols"][protocol_name]
+
+ h_p_dict = next(
+ (
+ v.get("protocols", {}).get(protocol_name, {})
+ for v in have
+ if v.get("name") == wname
+ ),
+ {},
+ )
+ if protocol_name == "bgp":
+ bgp_module = Bgp_global(self._module)
+ bgp_module._validate_template()
+ bgp_module.want = w_p_dict
+ bgp_module.have = h_p_dict
+ bgp_module.generate_commands()
+ protocol_commands = bgp_module.commands
+ elif protocol_name == "ospf":
+ ospfv2_module = Ospfv2(self._module)
+ ospfv2_module._module.params["config"] = w_p_dict
+ ospfv2_module.state = self.state
+ protocol_commands = ospfv2_module.set_config(h_p_dict)
+ elif protocol_name == "ospfv3":
+ ospfv3_module = Ospfv3(self._module)
+ ospfv3_module._module.params["config"] = w_p_dict
+ ospfv3_module.state = self.state
+ protocol_commands = ospfv3_module.set_config(h_p_dict)
+ elif protocol_name == "static":
+ static_routes_module = Static_routes(self._module)
+ static_routes_module._module.params["config"] = w_p_dict
+ static_routes_module.state = self.state
+ protocol_commands = static_routes_module.set_config(h_p_dict)
+ else:
+ self._module.fail_json(
+ msg="The protocol {} is not supported".format(protocol_name),
+ )
+ self.commands.extend(
+ [
+ cmd.replace("protocols", "vrf name " + wname + " protocols", 1)
+ for cmd in protocol_commands
+ ],
+ )
+
+ def _compare_addr_family(self, want, have):
+ """Compare the address families of the VRF"""
+ afi_parsers = [
+ "disable_forwarding",
+ "disable_nht",
+ ]
+
+ wafi = self.afi_to_list(want)
+ hafi = self.afi_to_list(have)
+
+ lookup = {(d["name"], d["afi"]): d for d in hafi}
+ pairs = [(d1, lookup.get((d1["name"], d1["afi"]), {})) for d1 in wafi]
+
+ for wafd, hafd in pairs:
+ if "route_maps" in wafd:
+ self._compare_route_maps(wafd, hafd)
+ self.compare(parsers=afi_parsers, want=wafd, have=hafd)
+
+ def afi_to_list(self, data):
+ """Convert address family dict to list"""
+
+ return [
+ {"name": data["name"], **{**af, "afi": "ip" if af["afi"] == "ipv4" else af["afi"]}}
+ for af in data["address_family"]
+ ]
+
+ def _compare_route_maps(self, wafd, hafd):
+ want_rms = wafd.get("route_maps", [])
+ have_rms = hafd.get("route_maps", [])
+
+ for want in want_rms:
+ match = next(
+ (
+ h
+ for h in have_rms
+ if h["rm_name"] == want["rm_name"] and h["protocol"] == want["protocol"]
+ ),
+ {},
+ )
+ base = {"name": wafd["name"], "afi": wafd["afi"]}
+
+ self.compare(
+ parsers="route_maps",
+ want={**base, "route_maps": want},
+ have={**base, "route_maps": match},
+ )
+
+ def _canonicalise(self, obj):
+ if isinstance(obj, dict):
+ return {k: self._canonicalise(v) for k, v in obj.items()}
+
+ if isinstance(obj, list):
+ if not obj:
+ return obj
+
+ if not isinstance(obj[0], dict):
+ return sorted(obj)
+
+ canon = [self._canonicalise(i) for i in obj]
+
+ def sort_key(d):
+ for k in ("name", "address", "afi", "area_id", "neighbor_id", "dest"):
+ if k in d:
+ return d[k]
+ return tuple(sorted(d.items()))
+
+ return sorted(canon, key=sort_key)
+
+ return obj
diff --git a/plugins/module_utils/network/vyos/facts/bgp_address_family/bgp_address_family.py b/plugins/module_utils/network/vyos/facts/bgp_address_family/bgp_address_family.py
index 3386bd66..dab92612 100644
--- a/plugins/module_utils/network/vyos/facts/bgp_address_family/bgp_address_family.py
+++ b/plugins/module_utils/network/vyos/facts/bgp_address_family/bgp_address_family.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -28,11 +27,11 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_template
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_address_family_14 import (
Bgp_address_familyTemplate14,
)
-
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
-
class Bgp_address_familyFacts(object):
"""The vyos bgp_address_family facts class"""
diff --git a/plugins/module_utils/network/vyos/facts/bgp_global/bgp_global.py b/plugins/module_utils/network/vyos/facts/bgp_global/bgp_global.py
index dd793681..cf2e4475 100644
--- a/plugins/module_utils/network/vyos/facts/bgp_global/bgp_global.py
+++ b/plugins/module_utils/network/vyos/facts/bgp_global/bgp_global.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -25,15 +24,14 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.bgp
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_global import (
Bgp_globalTemplate,
)
-
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.bgp_global_14 import (
Bgp_globalTemplate14,
)
-
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
-
class Bgp_globalFacts(object):
"""The vyos bgp_global facts class"""
diff --git a/plugins/module_utils/network/vyos/facts/facts.py b/plugins/module_utils/network/vyos/facts/facts.py
index 74bbda74..92c0f97a 100644
--- a/plugins/module_utils/network/vyos/facts/facts.py
+++ b/plugins/module_utils/network/vyos/facts/facts.py
@@ -6,8 +6,8 @@ The facts class for vyos
this file validates each subset of facts and selectively
calls the appropriate facts gathering function
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -30,6 +30,9 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firew
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.firewall_rules.firewall_rules import (
Firewall_rulesFacts,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ha.ha import (
+ HaFacts,
+)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.hostname.hostname import (
HostnameFacts,
)
@@ -56,6 +59,9 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.lldp_
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.logging_global.logging_global import (
Logging_globalFacts,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.nat.nat import (
+ NatFacts,
+)
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ntp_global.ntp_global import (
Ntp_globalFacts,
)
@@ -80,7 +86,13 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.snmp_
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import (
Static_routesFacts,
)
-
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vpn_ipsec.vpn_ipsec import (
+ Vpn_ipsecFacts,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vpn_ipsec_s2s.vpn_ipsec_s2s import (
+ Vpn_ipsec_s2sFacts,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.vrf.vrf import VrfFacts
FACT_LEGACY_SUBSETS = dict(default=Default, neighbors=Neighbors, config=Config)
FACT_RESOURCE_SUBSETS = dict(
@@ -93,6 +105,7 @@ FACT_RESOURCE_SUBSETS = dict(
firewall_rules=Firewall_rulesFacts,
firewall_global=Firewall_globalFacts,
firewall_interfaces=Firewall_interfacesFacts,
+ ha=HaFacts,
ospfv3=Ospfv3Facts,
ospfv2=Ospfv2Facts,
ospf_interfaces=Ospf_interfacesFacts,
@@ -102,8 +115,12 @@ FACT_RESOURCE_SUBSETS = dict(
prefix_lists=Prefix_listsFacts,
logging_global=Logging_globalFacts,
ntp_global=Ntp_globalFacts,
+ nat=NatFacts,
snmp_server=Snmp_serverFacts,
hostname=HostnameFacts,
+ vrf=VrfFacts,
+ vpn_ipsec=Vpn_ipsecFacts,
+ vpn_ipsec_s2s=Vpn_ipsec_s2sFacts,
)
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 a46f8563..e13c1939 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
@@ -9,8 +9,8 @@ It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -77,13 +77,17 @@ class Firewall_globalFacts(object):
:rtype: dictionary
:returns: The generated config
"""
+
conf = "\n".join(
filter(
- lambda x: ("firewall ipv6-name" and "firewall name" not in x),
+ lambda x: not (
+ x.startswith("set firewall name")
+ or x.startswith("set firewall ipv6-name")
+ or x.startswith("set firewall ipv6 name")
+ ),
conf,
),
)
-
a_lst = [
"config_trap",
"validation",
@@ -97,6 +101,7 @@ class Firewall_globalFacts(object):
"group": self.parse_group(conf),
"route_redirects": self.route_redirects(conf),
"state_policy": self.parse_state_policy(conf),
+ "zone": self.parse_zone(conf),
}
firewall.update(f_sub)
return firewall
@@ -179,7 +184,7 @@ class Firewall_globalFacts(object):
if policies:
rules_lst = []
for sp in set(policies):
- sp_regex = r" %s .+$" % sp
+ sp_regex = r"^set firewall (?:global-options )?state-policy %s .+$" % sp
cfg = "\n".join(findall(sp_regex, conf, M))
obj = self.parse_policies(cfg, sp)
obj["connection_type"] = sp
@@ -233,7 +238,7 @@ class Firewall_globalFacts(object):
if groups:
rules_lst = []
for gr in set(groups):
- gr_regex = r" %s .+$" % gr
+ gr_regex = r"^set firewall group " + type + " %s .+$" % gr
cfg = "\n".join(findall(gr_regex, conf, M))
if "ipv6" in type:
# fmt: off
@@ -400,3 +405,120 @@ class Firewall_globalFacts(object):
"twa_hazards_protection",
)
return True if attrib in bool_set else False
+
+ def parse_zone(self, conf):
+ """
+ This function triggers the parsing of 'zone' attributes.
+ :param conf: configuration.
+ :return: generated config dictionary.
+ """
+ cfg_dict = {}
+
+ KEY_MAP = {
+ "interface": "interfaces",
+ "intra-zone-filtering": "intra-zone-filtering",
+ "from": "sources",
+ }
+
+ LIST_ATTRS = {
+ "interfaces",
+ "intra_zone_filtering",
+ "sources",
+ }
+
+ for line in conf.splitlines():
+
+ m = search(
+ r"^set firewall zone (?P<zone>\S+)\s+(?P<attr>[a-z-]+)(?:\s+(?P<value>'[^']+'|[^\n]+))?$",
+ line,
+ )
+ if not m:
+ continue
+
+ zone_name = m.group("zone")
+ raw_attr = m.group("attr").replace("-", "_")
+ value = m.group("value")
+
+ if value is None:
+ value = True
+ else:
+ value = value.strip("'")
+
+ # VyOS 1.5.0 GA wraps 'interface' under a new 'member' node:
+ # "set firewall zone <name> member interface <ifname>". Unwrap
+ # it here so it lands in the same 'interfaces' list as the
+ # pre-1.5.0 bare "interface <ifname>" form. No version check
+ # needed -- 1.4.x/1.5-rolling configs never emit 'member'.
+ if raw_attr == "member" and isinstance(value, str) and value.startswith("interface "):
+ raw_attr = "interface"
+ value = value.split(None, 1)[1].strip("'")
+
+ zone = cfg_dict.setdefault(zone_name, {"name": zone_name})
+
+ attr = KEY_MAP.get(raw_attr, raw_attr)
+
+ if attr in LIST_ATTRS:
+ if attr == "intra_zone_filtering":
+ izf = zone.setdefault(attr, {})
+ izf_attr = self._parse_izf(value)
+ for k, v in izf_attr.items():
+ if isinstance(v, dict):
+ izf.setdefault(k, {}).update(v)
+ else:
+ izf[k] = v
+ elif attr == "sources":
+ self._parse_sources(zone, value)
+ else:
+ zone.setdefault(attr, []).append(value)
+ else:
+ zone[attr] = value
+
+ return list(cfg_dict.values())
+
+ def _parse_izf(self, value):
+
+ tokens = value.replace("'", "").split()
+
+ result = {}
+
+ key = tokens[0].replace("-", "_")
+
+ if len(tokens) == 2:
+ result[key] = tokens[1]
+
+ elif len(tokens) >= 3:
+ subkey = tokens[1].replace("-", "_")
+ result[key] = {subkey: tokens[2]}
+
+ return result
+
+ def _parse_sources(self, zone, value):
+
+ tokens = value.split()
+
+ if len(tokens) < 1:
+ return
+
+ src_zone = tokens[0]
+
+ sources = zone.setdefault("sources", [])
+
+ entry = None
+ for s in sources:
+ if s.get("zone") == src_zone:
+ entry = s
+ break
+
+ if entry is None:
+ entry = {"zone": src_zone}
+ sources.append(entry)
+
+ if len(tokens) == 1:
+ return
+
+ if tokens[1] == "firewall" and len(tokens) >= 4:
+ key = tokens[2].replace("-", "_")
+ val = tokens[3].strip("'")
+
+ firewall = entry.setdefault("firewall", {})
+ firewall[key] = val
diff --git a/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py b/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py
index bac31920..34235af7 100644
--- a/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/firewall_interfaces/firewall_interfaces.py
@@ -9,8 +9,8 @@ It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
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 a6b56345..31cc1fa5 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
@@ -9,8 +9,8 @@ It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -61,12 +61,20 @@ class Firewall_rulesFacts(object):
objs = []
# check 1.4+ first
new_rules = True
- v6_rules = findall(r"^set firewall ipv6 (name|forward|input|output) (?:\'*)(\S+)(?:\'*)", data, M)
+ 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)
+ 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:
@@ -171,12 +179,18 @@ class Firewall_rulesFacts(object):
:returns: The generated config
"""
conf = "\n".join(filter(lambda x: x, conf))
- a_lst = ["description", "default_action", "default_jump_target", "enable_default_log", "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')
+ if "default_log" in config:
+ config["enable_default_log"] = config.pop("default_log")
config["rules"] = self.parse_rules_lst(conf)
return config
@@ -219,6 +233,7 @@ class Firewall_rulesFacts(object):
"description",
"icmp",
"jump_target",
+ "offload_target",
"queue",
"queue_options",
]
@@ -263,8 +278,8 @@ class Firewall_rulesFacts(object):
found_lengths = findall(rule_regex, conf, M)
if found_lengths:
lengths = []
- for l in set(found_lengths):
- obj = {"length": l.strip("'")}
+ for pplen in set(found_lengths):
+ obj = {"length": pplen.strip("'")}
lengths.append(obj)
return lengths
@@ -389,21 +404,21 @@ class Firewall_rulesFacts(object):
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':
+ 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
+ (type_no, code) = val.split("/")
+ config["type"] = int(type_no)
+ config["code"] = int(code)
elif val.isnumeric():
- config['type'] = type_no
+ config["type"] = int(val)
else:
- config['type_name'] = val
+ config["type_name"] = val
return config
def parse_icmp(self, conf, attrib=None):
@@ -414,7 +429,7 @@ class Firewall_rulesFacts(object):
:return: generated config dictionary.
"""
cfg_dict = self.parse_icmp_attr(conf, "icmp")
- if (len(cfg_dict) == 0):
+ if len(cfg_dict) == 0:
cfg_dict = self.parse_icmp_attr(conf, "icmpv6")
return cfg_dict
@@ -458,7 +473,7 @@ class Firewall_rulesFacts(object):
if not out:
if attrib == "disable":
out = search(r"^.*\d+" + " (disable$)", conf, M)
- if attrib == 'log':
+ if attrib == "log":
out = search(r"^.*\d+" + " (log$)", conf, M)
if out:
@@ -523,7 +538,7 @@ class Firewall_rulesFacts(object):
if out:
val = out.group(1).strip("'")
if "/" in val: # number/unit
- (number, unit) = val.split("/")
- config['number'] = number
- config['unit'] = unit
+ number, unit = val.split("/")
+ config["number"] = number
+ config["unit"] = unit
return config
diff --git a/plugins/module_utils/network/vyos/facts/ha/__init__.py b/plugins/module_utils/network/vyos/facts/ha/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/ha/__init__.py
diff --git a/plugins/module_utils/network/vyos/facts/ha/ha.py b/plugins/module_utils/network/vyos/facts/ha/ha.py
new file mode 100644
index 00000000..fc559783
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/ha/ha.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The vyos_ha fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ha.ha import (
+ HaArgs,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.ha import (
+ HaTemplate,
+)
+
+
+class HaFacts(object):
+ """The vyos_ha facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = HaArgs.argument_spec
+
+ def get_config(self, connection):
+ return connection.get('show configuration commands | match "set high-availability"')
+
+ def get_config_set(self, data, connection):
+ """Classify config lines into per-object buckets for isolated parsing.
+
+ Each bucket is parsed by a single HaTemplate instance so that facts
+ from different objects (groups, sync-groups, virtual-servers) never
+ bleed into each other.
+
+ Keys are namespaced to avoid collisions between a VRRP group and a
+ sync-group that share the same name (e.g. both named "g1").
+ An elif chain ensures each line lands in exactly one bucket.
+ """
+ config_dict = {}
+ for config_line in data.splitlines():
+ vrrp_disable = re.search(r"set high-availability disable", config_line)
+ vrrp_snmp = re.search(r"set high-availability vrrp snmp", config_line)
+ vrrp_gp = re.search(
+ r"set high-availability vrrp global-parameters (\S+).*",
+ config_line,
+ )
+ vrrp_grp = re.search(r"set high-availability vrrp group (\S+).*", config_line)
+ vrrp_sg = re.search(r"set high-availability vrrp sync-group (\S+).*", config_line)
+ vrrp_vsrv = re.search(r"set high-availability virtual-server (\S+).*", config_line)
+
+ if vrrp_disable:
+ config_dict.setdefault("disable", []).append(config_line)
+ elif vrrp_snmp:
+ config_dict.setdefault("vrrp", []).append(config_line)
+ elif vrrp_gp:
+ config_dict.setdefault("global_parameters", []).append(config_line)
+ elif vrrp_grp:
+ key = "vrrp_group_{0}".format(vrrp_grp.group(1))
+ config_dict.setdefault(key, []).append(config_line)
+ elif vrrp_sg:
+ key = "vrrp_sg_{0}".format(vrrp_sg.group(1))
+ config_dict.setdefault(key, []).append(config_line)
+ elif vrrp_vsrv:
+ config_dict.setdefault(vrrp_vsrv.group(1), []).append(config_line)
+
+ return list(config_dict.values())
+
+ def deep_merge(self, dest, src):
+ for key, value in src.items():
+ if key in dest and isinstance(dest[key], dict) and isinstance(value, dict):
+ self.deep_merge(dest[key], value)
+ else:
+ dest[key] = value
+ return dest
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for vrrp network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+ objs = {}
+
+ if not data:
+ data = self.get_config(connection)
+ resources = self.get_config_set(data, connection)
+ vrrp_facts = {"disable": False, "virtual_servers": {}, "vrrp": {}}
+ for resource in resources:
+ vrrp_parser = HaTemplate(
+ lines=resource,
+ module=self._module,
+ )
+ objs = vrrp_parser.parse()
+ if "disable" in objs:
+ vrrp_facts["disable"] = objs["disable"]
+
+ for section in ("virtual_servers", "vrrp"):
+ if section in objs:
+ for name, data in objs[section].items():
+ if not isinstance(data, dict):
+ vrrp_facts[section][name] = data
+ continue
+ existing = vrrp_facts[section].get(name, {})
+ vrrp_facts[section][name] = self.deep_merge(existing, data)
+
+ ansible_facts["ansible_network_resources"].pop("ha", None)
+
+ vrrp_facts = self.normalize_config(vrrp_facts)
+
+ validate_parser = HaTemplate(lines=[], module=self._module)
+ params = utils.remove_empties(
+ validate_parser.validate_config(
+ self.argument_spec,
+ {"config": vrrp_facts},
+ redact=True,
+ ),
+ )
+
+ facts["ha"] = params.get("config", {})
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def normalize_config(self, config):
+ if not config:
+ return config
+
+ if isinstance(config.get("virtual_servers"), dict):
+ config["virtual_servers"] = list(config["virtual_servers"].values())
+
+ vrrp = config.get("vrrp", {})
+
+ if isinstance(vrrp.get("groups"), dict):
+ vrrp["groups"] = list(vrrp["groups"].values())
+
+ if isinstance(vrrp.get("sync_groups"), dict):
+ vrrp["sync_groups"] = list(vrrp["sync_groups"].values())
+
+ # Normalize real_server inside each virtual_server
+ for vs in config.get("virtual_servers", []):
+ if isinstance(vs.get("real_server"), dict):
+ vs["real_server"] = list(vs["real_server"].values())
+
+ for group in vrrp.get("groups", []):
+ if isinstance(group.get("address"), list):
+ group["address"] = sorted(group["address"])
+
+ if isinstance(group.get("excluded_address"), list):
+ group["excluded_address"] = sorted(group["excluded_address"])
+
+ if isinstance(group.get("track", {}).get("interface"), list):
+ group["track"]["interface"] = sorted(group["track"]["interface"])
+
+ for sg in vrrp.get("sync_groups", []):
+ if isinstance(sg.get("member"), list):
+ sg["member"] = sorted(sg["member"])
+
+ return config
diff --git a/plugins/module_utils/network/vyos/facts/hostname/hostname.py b/plugins/module_utils/network/vyos/facts/hostname/hostname.py
index b4f7c529..18a66357 100644
--- a/plugins/module_utils/network/vyos/facts/hostname/hostname.py
+++ b/plugins/module_utils/network/vyos/facts/hostname/hostname.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
diff --git a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
index cd8008c6..30d619a3 100644
--- a/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/interfaces/interfaces.py
@@ -12,7 +12,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
@@ -80,6 +79,7 @@ class InterfacesFacts(object):
facts["interfaces"].append(utils.remove_empties(cfg))
ansible_facts["ansible_network_resources"].update(facts)
+
return ansible_facts
def render_config(self, conf):
@@ -94,7 +94,7 @@ class InterfacesFacts(object):
"""
vif_conf = "\n".join(filter(lambda x: ("vif" in x), conf))
eth_conf = "\n".join(filter(lambda x: ("vif" not in x), conf))
- config = self.parse_attribs(["description", "speed", "mtu", "duplex"], eth_conf)
+ config = self.parse_attribs(["description", "speed", "mtu", "duplex", "vrf"], eth_conf)
config["vifs"] = self.parse_vifs(vif_conf)
return utils.remove_empties(config)
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 7d4d1a08..9e79e24d 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
@@ -12,7 +12,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
@@ -20,7 +19,6 @@ import re
from copy import deepcopy
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.l3_interfaces.l3_interfaces import (
@@ -139,7 +137,7 @@ class L3_interfacesFacts(object):
else:
config["ipv6"].append({"address": item})
- for key, value in iteritems(config):
+ for key, value in config.items():
if value == []:
config[key] = None
diff --git a/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py b/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py
index 8e1c8624..94c73416 100644
--- a/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/lag_interfaces/lag_interfaces.py
@@ -9,8 +9,8 @@ It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
diff --git a/plugins/module_utils/network/vyos/facts/legacy/base.py b/plugins/module_utils/network/vyos/facts/legacy/base.py
index 30978e0e..59666e19 100644
--- a/plugins/module_utils/network/vyos/facts/legacy/base.py
+++ b/plugins/module_utils/network/vyos/facts/legacy/base.py
@@ -11,7 +11,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
import platform
import re
diff --git a/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py b/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py
index 7a6e9b8e..40ff2ad3 100644
--- a/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py
+++ b/plugins/module_utils/network/vyos/facts/lldp_global/lldp_global.py
@@ -9,8 +9,8 @@ It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
diff --git a/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py b/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py
index e029b47a..72bdf154 100644
--- a/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py
+++ b/plugins/module_utils/network/vyos/facts/lldp_interfaces/lldp_interfaces.py
@@ -12,7 +12,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
diff --git a/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py b/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py
index 8b60bef9..243043bc 100644
--- a/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py
+++ b/plugins/module_utils/network/vyos/facts/logging_global/logging_global.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -15,7 +14,6 @@ for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.logging_global.logging_global import (
@@ -24,6 +22,13 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.log
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.logging_global import (
Logging_globalTemplate,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.logging_global_15 import (
+ Logging_globalTemplate15,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Logging_globalFacts(object):
@@ -38,7 +43,7 @@ class Logging_globalFacts(object):
def process_facts(self, objFinal):
if objFinal:
- for ke, vl in iteritems(objFinal):
+ for ke, vl in objFinal.items():
if ke == "files":
_files = []
for k, v in vl.items():
@@ -81,8 +86,17 @@ class Logging_globalFacts(object):
if not data:
data = self.get_logging_data(connection)
- # parse native config using the Logging_global template
- logging_global_parser = Logging_globalTemplate(lines=data.splitlines(), module=self._module)
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.5"):
+ logging_global_parser = Logging_globalTemplate15(
+ lines=data.splitlines(),
+ module=self._module,
+ )
+ else:
+ logging_global_parser = Logging_globalTemplate(
+ lines=data.splitlines(),
+ module=self._module,
+ )
+
objs = logging_global_parser.parse()
ansible_facts["ansible_network_resources"].pop("logging_global", None)
objs = self.process_facts(objs)
diff --git a/plugins/module_utils/network/vyos/facts/nat/__init__.py b/plugins/module_utils/network/vyos/facts/nat/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/nat/__init__.py
diff --git a/plugins/module_utils/network/vyos/facts/nat/nat.py b/plugins/module_utils/network/vyos/facts/nat/nat.py
new file mode 100644
index 00000000..73d6238f
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/nat/nat.py
@@ -0,0 +1,174 @@
+# -*- coding: utf-8 -*-
+# 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
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.nat.nat import (
+ NatArgs,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.nat import (
+ NatTemplate,
+)
+
+
+class NatFacts(object):
+ """The vyos nat facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = NatArgs.argument_spec
+
+ def get_config(self, connection):
+ return connection.get("show configuration commands | match 'set nat'")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ facts = {}
+ config_lines = []
+
+ if not data:
+ data = self.get_config(connection)
+
+ for resource in data.splitlines():
+ config_lines.append(re.sub(r"'([^']*)'", r"\1", resource))
+
+ nat_parser = NatTemplate(lines=config_lines, module=self._module)
+ objs = nat_parser.parse()
+ objs = self._normalise(objs)
+
+ ansible_facts["ansible_network_resources"].pop("nat", None)
+
+ params = utils.remove_empties(
+ nat_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
+ )
+
+ if params.get("config"):
+ facts["nat"] = params["config"]
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
+
+ def _deep_merge(self, base, override):
+ for k, v in override.items():
+ if k in base and isinstance(base[k], dict) and isinstance(v, dict):
+ self._deep_merge(base[k], v)
+ elif k in base and isinstance(base[k], list) and isinstance(v, list):
+ for entry in v:
+ if entry not in base[k]:
+ base[k].append(entry)
+ else:
+ base[k] = v
+ return base
+
+ def _merge_rule_list(self, rules):
+ merged = {}
+ for item in rules:
+ rid = item["id"]
+ if rid not in merged:
+ merged[rid] = {"id": rid}
+ for k, v in item.items():
+ if k == "id":
+ continue
+ if isinstance(v, list):
+ existing = merged[rid].setdefault(k, [])
+ for entry in v:
+ if entry not in existing:
+ existing.append(entry)
+ elif isinstance(v, dict):
+ merged[rid].setdefault(k, {})
+ self._deep_merge(merged[rid][k], v)
+ else:
+ merged[rid][k] = v
+ return list(merged.values())
+
+ def _merge_pool_list(self, pools):
+ merged = {}
+ for item in pools:
+ name = item["name"]
+ if name not in merged:
+ merged[name] = {"name": name}
+ for k, v in item.items():
+ if k == "name":
+ continue
+ if k == "range" and isinstance(v, list):
+ existing = merged[name].setdefault(k, [])
+ existing.extend(v)
+ if v and isinstance(v[0], dict):
+ merged[name][k] = self._merge_range_list(existing)
+ else:
+ merged[name][k] = list(dict.fromkeys(existing))
+ elif isinstance(v, list):
+ merged[name].setdefault(k, [])
+ for val in v:
+ if val not in merged[name][k]:
+ merged[name][k].append(val)
+ elif isinstance(v, dict):
+ merged[name].setdefault(k, {})
+ self._deep_merge(merged[name][k], v)
+ else:
+ merged[name][k] = v
+ return list(merged.values())
+
+ def _merge_range_list(self, ranges):
+ """Merge external pool range entries by value, preserving seq."""
+ merged = {}
+ for entry in ranges:
+ if isinstance(entry, dict):
+ key = entry.get("value") or entry.get("address", "")
+ if not key:
+ continue
+ if key not in merged:
+ merged[key] = {"value": key}
+ if entry.get("seq"):
+ merged[key]["seq"] = entry["seq"]
+ else:
+ if entry not in merged:
+ merged[entry] = {"value": entry}
+ return list(merged.values())
+
+ def _normalise(self, objs):
+ for nat_type in ["nat", "nat64", "nat66"]:
+ nat = objs.get(nat_type)
+ if not nat:
+ continue
+
+ for section in ["destination", "source", "static", "cgnat"]:
+ if section not in nat:
+ continue
+ rules = nat[section].get("rule")
+ if isinstance(rules, list):
+ nat[section]["rule"] = self._merge_rule_list(rules)
+ nat[section]["rule"].sort(key=lambda x: x.get("id", 0))
+
+ if "cgnat" in nat and "pool" in nat["cgnat"]:
+ pool = nat["cgnat"]["pool"]
+ for ptype in ["external", "internal"]:
+ if ptype in pool and isinstance(pool[ptype], list):
+ pool[ptype] = self._merge_pool_list(pool[ptype])
+
+ if nat_type == "nat64":
+ for rule in nat.get("source", {}).get("rule", []):
+ pools = rule.get("translation", {}).get("pool")
+ if pools and isinstance(pools, list):
+ rule["translation"]["pool"] = self._merge_rule_list(pools)
+ rule["translation"]["pool"].sort(key=lambda x: x.get("id", 0))
+
+ self._cast_ports(objs)
+ return objs
+
+ def _cast_ports(self, obj):
+ """Recursively cast known integer port/seq fields to str."""
+ if isinstance(obj, dict):
+ for k, v in obj.items():
+ if k in ("port", "seq") and isinstance(v, int):
+ obj[k] = str(v)
+ else:
+ self._cast_ports(v)
+ elif isinstance(obj, list):
+ for item in obj:
+ self._cast_ports(item)
diff --git a/plugins/module_utils/network/vyos/facts/ntp_global/ntp_global.py b/plugins/module_utils/network/vyos/facts/ntp_global/ntp_global.py
index 880bc79f..a6d6892f 100644
--- a/plugins/module_utils/network/vyos/facts/ntp_global/ntp_global.py
+++ b/plugins/module_utils/network/vyos/facts/ntp_global/ntp_global.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
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 852e1da7..1a9a687d 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
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -23,17 +22,16 @@ 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
+ Ospf_interfacesTemplate14,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
)
-
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
-from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import LooseVersion
-
class Ospf_interfacesFacts(object):
"""The vyos ospf_interfaces facts class"""
@@ -54,7 +52,9 @@ class Ospf_interfacesFacts(object):
for config_line in data.splitlines():
ospf_int = re.search(r"set protocols (?:ospf|ospfv3) interface (\S+).*", config_line)
if ospf_int:
- config_dict[ospf_int.group(1)] = config_dict.get(ospf_int.group(1), "") + config_line + "\n"
+ config_dict[ospf_int.group(1)] = (
+ config_dict.get(ospf_int.group(1), "") + config_line + "\n"
+ )
return list(config_dict.values())
def get_config_set_1_2(self, data):
@@ -63,12 +63,15 @@ class Ospf_interfacesFacts(object):
config_set = []
int_string = ""
for config_line in data.splitlines():
- ospf_int = re.search(r"set interfaces \S+ (\S+) .*", config_line)
+ ospf_int_raw = re.findall(r"^set interfaces \S+ (\S+)", config_line, re.M)
+ ospf_int_vif = re.findall(r"^set interfaces \S+ (\S+) vif (\d+)", config_line, re.M)
+
+ ospf_int = ospf_int_raw + ospf_int_vif
if ospf_int:
- if ospf_int.group(1) not in interface_list:
+ if ospf_int not in interface_list:
if int_string:
config_set.append(int_string)
- interface_list.append(ospf_int.group(1))
+ interface_list.append(ospf_int)
int_string = ""
int_string = int_string + config_line + "\n"
if int_string:
@@ -115,7 +118,6 @@ class Ospf_interfacesFacts(object):
if key in objs and objs[key]:
objs[key] = list(objs[key].values())
ospf_interfaces_facts.append(objs)
-
ansible_facts["ansible_network_resources"].pop("ospf_interfaces", None)
facts = {"ospf_interfaces": []}
params = utils.remove_empties(
@@ -123,11 +125,10 @@ class Ospf_interfacesFacts(object):
self.argument_spec,
{"config": ospf_interfaces_facts},
redact=True,
- )
+ ),
)
if params.get("config"):
for cfg in params["config"]:
facts["ospf_interfaces"].append(utils.remove_empties(cfg))
ansible_facts["ansible_network_resources"].update(facts)
-
return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py
index bdc7c9f8..056f949d 100644
--- a/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py
+++ b/plugins/module_utils/network/vyos/facts/ospfv2/ospfv2.py
@@ -9,8 +9,8 @@ It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -22,6 +22,10 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common i
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv2.ospfv2 import (
Ospfv2Args,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ospfv2Facts(object):
@@ -99,9 +103,9 @@ class Ospfv2Facts(object):
config["areas"] = self.parse_attrib_list(conf, "area", "area_id")
config["parameters"] = self.parse_attrib(conf, "parameters", "parameters")
config["neighbor"] = self.parse_attrib_list(conf, "neighbor", "neighbor_id")
- config["passive_interface"] = self.parse_leaf_list(conf, "passive-interface")
+ config["passive_interface"] = self.parse_passive(conf, "passive-interface")
config["redistribute"] = self.parse_attrib_list(conf, "redistribute", "route_type")
- config["passive_interface_exclude"] = self.parse_leaf_list(
+ config["passive_interface_exclude"] = self.parse_passive(
conf,
"passive-interface-exclude",
)
@@ -159,6 +163,29 @@ class Ospfv2Facts(object):
lst.sort()
return lst
+ def parse_passive(self, conf, attrib):
+ """
+ This function forms the regex to fetch the listed attributes
+ from the configuration data
+ :param conf: configuration data
+ :param attrib: attribute name
+ :return: generated rule list configuration
+ """
+ lst = []
+ items = []
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ if attrib == "passive-interface-exclude":
+ items = findall("^interface (?:'*)(\\S+)(?:'*) passive disable$", conf, M)
+ else:
+ items = findall("^interface (?:'*)(\\S+)(?:'*) passive$", conf, M)
+
+ items += findall(r"^" + attrib + " (?:'*)(\\S+)(?:'*)", conf, M)
+ if items:
+ for i in set(items):
+ lst.append(i.strip("'"))
+ lst.sort()
+ return lst
+
def parse_distance(self, conf, attrib=None):
"""
This function triggers the parsing of 'distance' attributes
@@ -389,13 +416,13 @@ class Ospfv2Facts(object):
:param match: parent node/attribute name.
:return: generated config dictionary.
"""
-
config = {}
for attrib in attr_list:
regex = self.map_regex(attrib)
if match:
regex = match.replace("_", "-") + " " + regex
+
if conf:
if self.is_bool(attrib):
out = conf.find(attrib.replace("_", "-"))
@@ -403,13 +430,13 @@ class Ospfv2Facts(object):
if match:
if attrib == "set" and conf.find(match) >= 1:
config[attrib] = True
- en = conf.find(match + " 'enable'")
+ en = conf.find(match + " enable") != -1
if out >= 1:
if dis >= 1:
config[attrib] = False
else:
config[attrib] = True
- elif match and en >= 1:
+ elif match and en:
config[attrib] = True
else:
out = search(r"^.*" + regex + " (.+)", conf, M)
diff --git a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py
index 547ff793..5fd9e627 100644
--- a/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py
+++ b/plugins/module_utils/network/vyos/facts/ospfv3/ospfv3.py
@@ -9,8 +9,8 @@ It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
-from __future__ import absolute_import, division, print_function
+from __future__ import absolute_import, division, print_function
__metaclass__ = type
@@ -22,6 +22,10 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common i
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.ospfv3.ospfv3 import (
Ospfv3Args,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Ospfv3Facts(object):
@@ -100,6 +104,9 @@ class Ospfv3Facts(object):
for item in set(items):
i_regex = r" %s .+$" % item
cfg = "\n".join(findall(i_regex, conf, M))
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ cfg14 = findall(r"(interface .+) area '%s'$" % item, conf, M)
+ cfg += "\n " + item + " " + " ".join(cfg14)
if attrib == "area":
obj = self.parse_area(cfg, item)
else:
@@ -121,6 +128,8 @@ class Ospfv3Facts(object):
rule = self.parse_attrib(conf, "area_id", match=area_id)
r_sub = {"range": self.parse_attrib_list(conf, "range", "address")}
rule.update(r_sub)
+ r_int = {"interface": self.parse_attrib_list(conf, "interface", "name")}
+ rule.update(r_int)
return rule
def parse_attrib(self, conf, param, match=None):
@@ -133,6 +142,7 @@ class Ospfv3Facts(object):
"area_id": ["export_list", "import_list"],
"redistribute": ["route_map"],
"range": ["advertise", "not_advertise"],
+ "interface": ["name"],
"parameters": ["router_id"],
}
cfg_dict = self.parse_attr(conf, param_lst[param], match)
diff --git a/plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py b/plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py
index 17f63fb2..4bdcbf8c 100644
--- a/plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py
+++ b/plugins/module_utils/network/vyos/facts/prefix_lists/prefix_lists.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
diff --git a/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py b/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py
index 2ad54e63..d4084fd7 100644
--- a/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py
+++ b/plugins/module_utils/network/vyos/facts/route_maps/route_maps.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -25,6 +24,13 @@ from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.rou
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps import (
Route_mapsTemplate,
)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.route_maps_14 import (
+ Route_mapsTemplate14,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.version import (
+ LooseVersion,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.vyos import get_os_version
class Route_mapsFacts(object):
@@ -59,11 +65,18 @@ class Route_mapsFacts(object):
"""
facts = {}
objs = []
+
+ if LooseVersion(get_os_version(self._module)) >= LooseVersion("1.4"):
+ route_maps_class = Route_mapsTemplate14
+ else:
+ route_maps_class = Route_mapsTemplate
+
if not data:
data = self.get_config(connection)
# parse native config using the Route_maps template
- route_maps_parser = Route_mapsTemplate(lines=data.splitlines())
+ route_maps_parser = route_maps_class(lines=data.splitlines())
+
if route_maps_parser.parse().get("route_maps"):
objs = list(route_maps_parser.parse().get("route_maps").values())
for item in objs:
diff --git a/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py b/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py
index e70a15f9..d3ff02e5 100644
--- a/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py
+++ b/plugins/module_utils/network/vyos/facts/snmp_server/snmp_server.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -17,7 +16,6 @@ based on the configuration.
import re
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.snmp_server.snmp_server import (
@@ -63,7 +61,7 @@ class Snmp_serverFacts(object):
if objs:
if "communities" in objs:
for k in objs["communities"].values():
- for param, val in iteritems(k):
+ for param, val in k.items():
if param in ["clients", "networks"]:
if None in val:
val.remove(None)
diff --git a/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py b/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py
index 99b3917b..1bce772c 100644
--- a/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py
+++ b/plugins/module_utils/network/vyos/facts/static_routes/static_routes.py
@@ -12,7 +12,6 @@ based on the configuration.
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
from copy import deepcopy
from re import M, findall, search
@@ -164,9 +163,9 @@ class Static_routesFacts(object):
elif dis >= 1:
nh_info["enabled"] = False
for element in nh_list:
- if element["forward_router_address"] == nh_info["forward_router_address"]:
- if "interface" in nh_info.keys():
- element["interface"] = nh_info["interface"]
+ if element.get("forward_router_address") == nh_info.get(
+ "forward_router_address",
+ ):
if "admin_distance" in nh_info.keys():
element["admin_distance"] = nh_info["admin_distance"]
if "enabled" in nh_info.keys():
@@ -174,4 +173,5 @@ class Static_routesFacts(object):
nh_info = None
if nh_info is not None:
nh_list.append(nh_info)
+ nh_info = {}
return nh_list
diff --git a/plugins/module_utils/network/vyos/facts/vpn_ipsec/__init__.py b/plugins/module_utils/network/vyos/facts/vpn_ipsec/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/vpn_ipsec/__init__.py
diff --git a/plugins/module_utils/network/vyos/facts/vpn_ipsec/vpn_ipsec.py b/plugins/module_utils/network/vyos/facts/vpn_ipsec/vpn_ipsec.py
new file mode 100644
index 00000000..695b9b8d
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/vpn_ipsec/vpn_ipsec.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+# Copyright 2026 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The vyos vpn_ipsec fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+
+Follows the established per-key conversion convention used by
+vyos_logging_global/vyos_ha (explicit process_facts() naming each
+name-keyed dict that needs converting to a list), matching the config.py
+convention for this module, rather than a generic argspec-driven walker.
+"""
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.vpn_ipsec.vpn_ipsec import (
+ Vpn_ipsecArgs,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vpn_ipsec import (
+ Vpn_ipsecTemplate,
+)
+
+
+class Vpn_ipsecFacts(object):
+ """The vyos vpn_ipsec facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Vpn_ipsecArgs.argument_spec
+
+ def get_vpn_ipsec_data(self, connection):
+ return connection.get('show configuration commands | match "vpn ipsec"')
+
+ def process_facts(self, objFinal):
+ """Convert the name-keyed dicts produced by the parser into the
+ lists the argspec expects. Each key handled explicitly, matching
+ the vyos_logging_global/vyos_ha convention.
+ """
+ if not objFinal:
+ return objFinal
+
+ for key in ("ike_group", "esp_group"):
+ if key in objFinal:
+ items = list(objFinal[key].values())
+ for item in items:
+ if "proposal" in item:
+ item["proposal"] = sorted(
+ item["proposal"].values(),
+ key=lambda p: int(p["proposal_id"]),
+ )
+ objFinal[key] = sorted(items, key=lambda item: item["name"])
+
+ if "profile" in objFinal:
+ objFinal["profile"] = sorted(
+ objFinal["profile"].values(),
+ key=lambda item: item["name"],
+ )
+
+ if "authentication" in objFinal:
+ auth = objFinal["authentication"]
+ for key in ("psk", "ppk"):
+ if key in auth:
+ auth[key] = sorted(
+ auth[key].values(),
+ key=lambda item: item["name"],
+ )
+
+ return objFinal
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Vpn_ipsec network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+
+ if not data:
+ data = self.get_vpn_ipsec_data(connection)
+
+ vpn_ipsec_parser = Vpn_ipsecTemplate(lines=data.splitlines(), module=self._module)
+ objs = vpn_ipsec_parser.parse()
+
+ ansible_facts["ansible_network_resources"].pop("vpn_ipsec", None)
+ objs = self.process_facts(objs)
+
+ params = utils.remove_empties(
+ vpn_ipsec_parser.validate_config(
+ self.argument_spec,
+ {"config": objs},
+ redact=True,
+ ),
+ )
+
+ facts["vpn_ipsec"] = params.get("config", {})
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/vpn_ipsec_s2s/__init__.py b/plugins/module_utils/network/vyos/facts/vpn_ipsec_s2s/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/vpn_ipsec_s2s/__init__.py
diff --git a/plugins/module_utils/network/vyos/facts/vpn_ipsec_s2s/vpn_ipsec_s2s.py b/plugins/module_utils/network/vyos/facts/vpn_ipsec_s2s/vpn_ipsec_s2s.py
new file mode 100644
index 00000000..6c9b5d8e
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/vpn_ipsec_s2s/vpn_ipsec_s2s.py
@@ -0,0 +1,115 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2026 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The vyos vpn_ipsec_s2s fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+
+Follows the established per-key conversion convention used by
+vyos_logging_global/vyos_ha/vyos_vpn_ipsec (explicit process_facts()
+naming each name-keyed dict that needs converting to a list), matching
+the config.py convention for this module, rather than a generic
+argspec-driven walker.
+"""
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import (
+ utils,
+)
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.vpn_ipsec_s2s.vpn_ipsec_s2s import (
+ Vpn_ipsec_s2sArgs,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vpn_ipsec_s2s import (
+ Vpn_ipsec_s2sTemplate,
+)
+
+
+class Vpn_ipsec_s2sFacts(object):
+ """The vyos vpn_ipsec_s2s facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Vpn_ipsec_s2sArgs.argument_spec
+
+ def get_vpn_ipsec_s2s_data(self, connection):
+ return connection.get(
+ 'show configuration commands | match "vpn ipsec site-to-site"',
+ )
+
+ def process_facts(self, objFinal):
+ """Convert the name-keyed dicts produced by the parser into the
+ lists the argspec expects.
+
+ NOTE: every PARSERS result template in rm_templates.py nests its
+ output under "site_to_site" -> "peer" (mirroring the CLI's own
+ tree: `vpn ipsec site-to-site peer <name> ...`), but the
+ argspec's `config` has `peer` directly at the top level -- there
+ is no `site_to_site` wrapper in the argspec, since that's the
+ one node wrap_docstring.py unwrapped when building the
+ docstring (its own children became config's children directly).
+ So this needs to strip that outer key, not just convert the
+ name-keyed dicts to lists.
+ """
+ if not objFinal:
+ return objFinal
+
+ site_to_site = objFinal.get("site_to_site", {})
+ peers = site_to_site.get("peer", {})
+
+ items = list(peers.values())
+ for item in items:
+ if "tunnel" in item:
+ item["tunnel"] = sorted(
+ item["tunnel"].values(),
+ key=lambda t: int(t["tunnel_id"]),
+ )
+
+ return {"peer": sorted(items, key=lambda item: item["name"])}
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Vpn_ipsec_s2s network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+
+ if not data:
+ data = self.get_vpn_ipsec_s2s_data(connection)
+
+ vpn_ipsec_s2s_parser = Vpn_ipsec_s2sTemplate(
+ lines=data.splitlines(),
+ module=self._module,
+ )
+ objs = vpn_ipsec_s2s_parser.parse()
+
+ ansible_facts["ansible_network_resources"].pop("vpn_ipsec_s2s", None)
+ objs = self.process_facts(objs)
+
+ params = utils.remove_empties(
+ vpn_ipsec_s2s_parser.validate_config(
+ self.argument_spec,
+ {"config": objs},
+ redact=True,
+ ),
+ )
+
+ facts["vpn_ipsec_s2s"] = params.get("config", {})
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
diff --git a/plugins/module_utils/network/vyos/facts/vrf/__init__.py b/plugins/module_utils/network/vyos/facts/vrf/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/vrf/__init__.py
diff --git a/plugins/module_utils/network/vyos/facts/vrf/vrf.py b/plugins/module_utils/network/vyos/facts/vrf/vrf.py
new file mode 100644
index 00000000..74f46b45
--- /dev/null
+++ b/plugins/module_utils/network/vyos/facts/vrf/vrf.py
@@ -0,0 +1,226 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+"""
+The vyos vrf fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.argspec.vrf.vrf import VrfArgs
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.bgp_global.bgp_global import (
+ Bgp_globalFacts,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv2.ospfv2 import (
+ Ospfv2Facts,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.ospfv3.ospfv3 import (
+ Ospfv3Facts,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.facts.static_routes.static_routes import (
+ Static_routesFacts,
+)
+from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.rm_templates.vrf import (
+ VrfTemplate,
+)
+
+
+class VrfFacts(object):
+ """The vyos vrf facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = VrfArgs.argument_spec
+
+ def get_config(self, connection):
+ return connection.get("show configuration commands | match 'set vrf'")
+
+ def get_config_set(self, data, connection):
+ """To classify the configurations beased on vrf"""
+ config_dict = {}
+ for config_line in data.splitlines():
+ vrf_inst = re.search(r"set vrf name (\S+).*", config_line)
+ vrf_bta = re.search(r"set vrf bind-to-all", config_line)
+ if vrf_bta:
+ config_dict["bind_to_all"] = config_dict.get("bind_to_all", "") + config_line + "\n"
+ if vrf_inst:
+ config_dict[vrf_inst.group(1)] = (
+ config_dict.get(vrf_inst.group(1), "") + config_line + "\n"
+ )
+ return list(config_dict.values())
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Vrf network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+ objs = []
+
+ if not data:
+ data = self.get_config(connection)
+
+ vrf_facts = {}
+ instances = []
+ vrf_parser = VrfTemplate(lines=[], module=self._module)
+ resources = self.get_config_set(data, connection)
+
+ for resource in resources:
+ vrf_parser = VrfTemplate(
+ lines=resource.split("\n"),
+ module=self._module,
+ )
+ objs = vrf_parser.parse()
+
+ if objs and "protocols" in resource:
+
+ protocol_lines = []
+ for line in resource.strip().split("\n"):
+ if "protocols" in line:
+ idx = line.index("protocols")
+ protocol_lines.append("set " + line[idx:])
+ objs["protocols"] = self._parse_protocols("\n".join(protocol_lines))
+
+ if objs:
+ if "bind_to_all" in objs:
+ vrf_facts.update(objs)
+ if "name" in objs:
+ instances.append(self._normalise_instance(objs))
+
+ if instances:
+ vrf_facts.update({"instances": instances})
+
+ ansible_facts["ansible_network_resources"].pop("vrf_facts", None)
+ facts = {"vrf": []}
+
+ params = utils.remove_empties(
+ vrf_parser.validate_config(
+ self.argument_spec,
+ {"config": vrf_facts},
+ redact=True,
+ ),
+ )
+
+ if not resources:
+ params["config"].pop("bind_to_all", None)
+
+ if params.get("config"):
+ facts["vrf"] = params["config"]
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def _normalise_instance(self, instance):
+ n_inst = instance.copy()
+ af_map = {}
+
+ for af in instance.get("address_family", []):
+ afi = af.get("afi")
+ if not afi:
+ continue
+
+ if afi not in af_map:
+ af_map[afi] = {"afi": afi}
+
+ for k, v in af.items():
+ if k == "afi":
+ continue
+ elif k == "route_maps":
+ if "route_maps" not in af_map[afi]:
+ af_map[afi]["route_maps"] = []
+ af_map[afi]["route_maps"].extend(v)
+ else:
+ af_map[afi][k] = v
+
+ for afi_data in af_map.values():
+ if "route_maps" in afi_data:
+ seen = []
+ deduped = []
+ for item in afi_data["route_maps"]:
+ if item not in seen:
+ seen.append(item)
+ deduped.append(item)
+ afi_data["route_maps"] = deduped
+
+ n_inst["address_family"] = list(af_map.values())
+ return n_inst
+
+ def _parse_protocols(self, protocols):
+ """Parse protocols and return a dictionary"""
+
+ protocol_chunks = {}
+ parsed_protocols = {}
+
+ for line in protocols.split("\n"):
+ parts = line.split()
+ if len(parts) > 2 and parts[0] == "set" and parts[1] == "protocols":
+ protocol = parts[2]
+ protocol_chunks.setdefault(protocol, []).append(line)
+
+ protocol_strings = {proto: "\n".join(lines) for proto, lines in protocol_chunks.items()}
+
+ for protocol_name, protocol_string in protocol_strings.items():
+ protocol_dict = {}
+
+ if protocol_name == "bgp":
+ bgp_module = Bgp_globalFacts(self._module)
+ protocol_dict = bgp_module.populate_facts(
+ connection=self._module._connection,
+ ansible_facts={"ansible_network_resources": {}},
+ data=protocol_string,
+ )
+ parsed_protocols[protocol_name] = list(
+ protocol_dict.get("ansible_network_resources").values(),
+ )[0]
+
+ elif protocol_name == "ospf":
+ ospf_module = Ospfv2Facts(self._module)
+ protocol_dict = ospf_module.populate_facts(
+ connection=self._module._connection,
+ ansible_facts={"ansible_network_resources": {}},
+ data=protocol_string,
+ )
+ parsed_protocols[protocol_name] = list(
+ protocol_dict.get("ansible_network_resources").values(),
+ )[0]
+
+ elif protocol_name == "ospfv3":
+ ospfv3_module = Ospfv3Facts(self._module)
+ protocol_dict = ospfv3_module.populate_facts(
+ connection=self._module._connection,
+ ansible_facts={"ansible_network_resources": {}},
+ data=protocol_string,
+ )
+ parsed_protocols[protocol_name] = list(
+ protocol_dict.get("ansible_network_resources").values(),
+ )[0]
+
+ elif protocol_name == "static":
+ static_routes_module = Static_routesFacts(self._module)
+ protocol_dict = static_routes_module.populate_facts(
+ connection=self._module._connection,
+ ansible_facts={"ansible_network_resources": {}},
+ data=protocol_string,
+ )
+ parsed_protocols[protocol_name] = list(
+ protocol_dict.get("ansible_network_resources").values(),
+ )[0]
+ else:
+ self._module.fail_json(msg="The protocol is not supported" + protocol_name)
+
+ return parsed_protocols
diff --git a/plugins/module_utils/network/vyos/rm_templates/bgp_address_family.py b/plugins/module_utils/network/vyos/rm_templates/bgp_address_family.py
index f8f86cd2..fe7889aa 100644
--- a/plugins/module_utils/network/vyos/rm_templates/bgp_address_family.py
+++ b/plugins/module_utils/network/vyos/rm_templates/bgp_address_family.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -293,8 +292,8 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+aggregate-address
\s+(?P<address>\S+)
- \s*(?P<as_set>as-set)*
- \s*(?P<summary_only>summary-only)*
+ \s*(?P<as_set>as-set)?
+ \s*(?P<summary_only>summary-only)?
$""",
re.VERBOSE,
),
@@ -397,8 +396,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+network
\s+(?P<address>\S+)
\s+path-limit
- \s+(?P<limit>\S+)
- *$""",
+ \s+(?P<limit>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_network,
@@ -432,8 +430,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+network
\s+(?P<address>\S+)
\s+route-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_network,
@@ -499,8 +496,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+redistribute
\s+(?P<proto>\S+)
\s+metric
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_redistribute,
@@ -534,8 +530,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+redistribute
\s+(?P<proto>\S+)
\s+route-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_redistribute,
@@ -568,8 +563,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+redistribute
\s+table
- \s+(?P<tab>\S+)
- *$""",
+ \s+(?P<tab>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_redistribute,
@@ -659,8 +653,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+allowas-in
\s+number
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -877,8 +870,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+capability
\s+prefix-list
- \s+(?P<orf>\S+)
- *$""",
+ \s+(?P<orf>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -915,8 +907,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+default-originate
\s+route-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -992,8 +983,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+prefix-list
\s+(?P<action>export|import)
- \s+(?P<list>\S+)
- *$""",
+ \s+(?P<list>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor_prefix_list,
@@ -1033,8 +1023,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+filter-list
\s+(?P<action>export|import)
- \s+(?P<list>\S+)
- *$""",
+ \s+(?P<list>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor_filter_list,
@@ -1073,8 +1062,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+address-family
\s+(?P<afi>\S+)-unicast
\s+maximum-prefix
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -1176,8 +1164,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+address-family
\s+(?P<afi>\S+)-unicast
\s+peer-group
- \s+(?P<name>\S+)
- *$""",
+ \s+(?P<name>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -1246,8 +1233,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+route-map
\s+(?P<action>export|import)
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor_route_map,
@@ -1389,8 +1375,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+address-family
\s+(?P<afi>\S+)-unicast
\s+unsuppress-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -1424,8 +1409,7 @@ class Bgp_address_familyTemplate(NetworkTemplate):
\s+address-family
\s+(?P<afi>\S+)-unicast
\s+weight
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
diff --git a/plugins/module_utils/network/vyos/rm_templates/bgp_address_family_14.py b/plugins/module_utils/network/vyos/rm_templates/bgp_address_family_14.py
index fd4c9de9..9936cf6d 100644
--- a/plugins/module_utils/network/vyos/rm_templates/bgp_address_family_14.py
+++ b/plugins/module_utils/network/vyos/rm_templates/bgp_address_family_14.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -309,8 +308,8 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+aggregate-address
\s+(?P<address>\S+)
- \s*(?P<as_set>as-set)*
- \s*(?P<summary_only>summary-only)*
+ \s*(?P<as_set>as-set)?
+ \s*(?P<summary_only>summary-only)?
$""",
re.VERBOSE,
),
@@ -410,8 +409,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+network
\s+(?P<address>\S+)
\s+path-limit
- \s+(?P<limit>\S+)
- *$""",
+ \s+(?P<limit>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_network,
@@ -444,8 +442,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+network
\s+(?P<address>\S+)
\s+route-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_network,
@@ -508,8 +505,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+redistribute
\s+(?P<proto>\S+)
- \s+metric\s+(?P<val>\S+)
- *$""",
+ \s+metric\s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_redistribute,
@@ -542,8 +538,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+redistribute
\s+(?P<proto>\S+)
\s+route-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_redistribute,
@@ -575,8 +570,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+redistribute
\s+table
- \s+(?P<tab>\S+)
- *$""",
+ \s+(?P<tab>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_redistribute,
@@ -663,8 +657,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+allowas-in
\s+number
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -875,8 +868,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+capability
\s+prefix-list
- \s+(?P<orf>\S+)
- *$""",
+ \s+(?P<orf>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -912,8 +904,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+default-originate
\s+route-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -987,8 +978,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+prefix-list
\s+(?P<action>export|import)
- \s+(?P<list>\S+)
- *$""",
+ \s+(?P<list>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor_prefix_list,
@@ -1027,8 +1017,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+filter-list
\s+(?P<action>export|import)
- \s+(?P<list>\S+)
- *$""",
+ \s+(?P<list>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor_filter_list,
@@ -1066,8 +1055,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+address-family
\s+(?P<afi>\S+)-unicast
\s+maximum-prefix
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -1166,8 +1154,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+address-family
\s+(?P<afi>\S+)-unicast
\s+peer-group
- \s+(?P<name>\S+)
- *$""",
+ \s+(?P<name>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -1234,8 +1221,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+(?P<afi>\S+)-unicast
\s+route-map
\s+(?P<action>export|import)
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor_route_map,
@@ -1373,8 +1359,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+address-family
\s+(?P<afi>\S+)-unicast
\s+unsuppress-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
@@ -1407,8 +1392,7 @@ class Bgp_address_familyTemplate14(NetworkTemplate):
\s+address-family
\s+(?P<afi>\S+)-unicast
\s+weight
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_af_neighbor,
diff --git a/plugins/module_utils/network/vyos/rm_templates/bgp_global.py b/plugins/module_utils/network/vyos/rm_templates/bgp_global.py
index 621f65ea..730fa5ee 100644
--- a/plugins/module_utils/network/vyos/rm_templates/bgp_global.py
+++ b/plugins/module_utils/network/vyos/rm_templates/bgp_global.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -17,7 +16,6 @@ the given network resource.
import re
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
NetworkTemplate,
)
@@ -26,7 +24,7 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.r
def _tmplt_bgp_params_confederation(config_data):
command = []
for list_el in config_data["bgp_params"]["confederation"]:
- for k, v in iteritems(list_el):
+ for k, v in list_el.items():
command.append(
"protocols bgp {as_number} parameters confederation ".format(**config_data)
+ k
@@ -65,7 +63,7 @@ def _tmplt_bgp_params_default(config_data):
def _tmplt_bgp_neighbor_timers(config_data):
command = []
- for k, v in iteritems(config_data["neighbor"]["timers"]):
+ for k, v in config_data["neighbor"]["timers"].items():
command.append(
"protocols bgp {as_number} neighbor ".format(**config_data)
+ config_data["neighbor"]["address"]
@@ -80,7 +78,7 @@ def _tmplt_bgp_neighbor_timers(config_data):
def _tmplt_bgp_timers(config_data):
command = []
- for k, v in iteritems(config_data["timers"]):
+ for k, v in config_data["timers"].items():
command.append(
"protocols bgp {as_number} ".format(**config_data) + "timers " + k + " " + str(v),
)
@@ -238,8 +236,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+advertisement-interval
- \s+(?P<interval>\S+)
- *$""",
+ \s+(?P<interval>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} advertisement-interval {{ neighbor.advertisement_interval }}",
@@ -438,8 +435,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+capability
\s+orf
\s+prefix-list
- \s+(?P<orf>\S+)
- *$""",
+ \s+(?P<orf>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} capability orf prefix-list {{ neighbor.capability.orf }}",
@@ -468,8 +464,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+(?P<address>\S+)
\s+default-originate
\s+route-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} default-originate route-map {{ neighbor.default_originate }}",
@@ -495,8 +490,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+description
- \s+(?P<desc>\S+)
- *$""",
+ \s+(?P<desc>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} description {{ neighbor.description }}",
@@ -574,8 +568,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+disable-send-community
- \s+(?P<comm>\S+)
- *$""",
+ \s+(?P<comm>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} disable-send-community {{ neighbor.disable_send_community }}",
@@ -634,8 +627,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+ebgp-multihop
- \s+(?P<hop>\S+)
- *$""",
+ \s+(?P<hop>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} ebgp-multihop {{ neighbor.ebgp_multihop }}",
@@ -662,8 +654,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+(?P<address>\S+)
\s+filter-list
\s+(?P<action>export|import)
- \s+(?P<list>\S+)
- *$""",
+ \s+(?P<list>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_filter_list,
@@ -722,8 +713,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+maximum-prefix
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} maximum-prefix {{ neighbor.maximum_prefix }}",
@@ -827,8 +817,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+password
- \s+(?P<pwd>\S+)
- *$""",
+ \s+(?P<pwd>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} password {{ neighbor.password }}",
@@ -854,8 +843,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+peer-group
- \s+(?P<name>\S+)
- *$""",
+ \s+(?P<name>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} peer-group {{ neighbor.peer_group_name }}",
@@ -881,8 +869,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+port
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} port {{ neighbor.port }}",
@@ -909,8 +896,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+(?P<address>\S+)
\s+prefix-list
\s+(?P<action>export|import)
- \s+(?P<list>\S+)
- *$""",
+ \s+(?P<list>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_prefix_list,
@@ -941,8 +927,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+remote-as
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} remote-as {{ neighbor.remote_as }}",
@@ -995,8 +980,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+(?P<address>\S+)
\s+route-map
\s+(?P<action>export|import)
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_route_map,
@@ -1158,8 +1142,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+unsuppress-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} unsuppress-map {{ neighbor.unsuppress_map }}",
@@ -1185,8 +1168,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+update-source
- \s+(?P<src>\S+)
- *$""",
+ \s+(?P<src>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} update-source {{ neighbor.update_source }}",
@@ -1212,8 +1194,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+weight
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} weight {{ neighbor.weight }}",
@@ -1239,8 +1220,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+ttl-security
- \s+(?P<ttl>\S+)
- *$""",
+ \s+(?P<ttl>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} neighbor {{ neighbor.address }} ttl-security {{ neighbor.ttl_security }}",
@@ -1267,8 +1247,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+(?P<address>\S+)
\s+timers
\s+(?P<type>connect|holdtime|keepalive)
- \s+(?P<sec>\S+)
- *$""",
+ \s+(?P<sec>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_timers,
@@ -1296,8 +1275,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+(?P<as_num>\d+)
\s+timers
\s+(?P<type>\S+)
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_timers,
@@ -1419,8 +1397,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+(?P<as_num>\d+)
\s+parameters
\s+cluster-id
- \s+(?P<id>\S+)
- *$""",
+ \s+(?P<id>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters cluster-id {{ bgp_params.cluster_id }}",
@@ -1443,8 +1420,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+parameters
\s+confederation
\s+(?P<type>identifier|peers)
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_confederation,
@@ -1472,8 +1448,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+parameters
\s+dampening
\s+half-life
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters dampening half-life {{ bgp_params.dampening.half_life}}",
@@ -1498,8 +1473,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+parameters
\s+dampening
\s+max-suppress-time
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters dampening max-suppress-time {{ bgp_params.dampening.max_suppress_time}}",
@@ -1524,8 +1498,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+parameters
\s+dampening
\s+re-use
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters dampening re-use {{ bgp_params.dampening.re_use}}",
@@ -1550,8 +1523,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+parameters
\s+dampening
\s+start-suppress-time
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters dampening start-suppress-time {{ bgp_params.dampening.start_suppress_time}}",
@@ -1575,9 +1547,8 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+(?P<as_num>\d+)
\s+parameters
\s+default
- \s*(?P<no_ipv4_unicast>no-ipv4-unicast)*
- \s*(?P<local_pref>local-pref\s\S+)
- *$""",
+ \s*(?P<no_ipv4_unicast>no-ipv4-unicast)?
+ \s*(?P<local_pref>local-pref\s\S+)?\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_default,
@@ -1649,8 +1620,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+distance\sprefix
\s+(?P<prefix>\S+)
\s+distance
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters distance prefix {{ bgp_params.distance.prefix }} distance {{ bgp_params.distance.value }}",
@@ -1679,8 +1649,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+parameters
\s+distance\sglobal
\s+(?P<type>\S+)
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_distance,
@@ -1730,8 +1699,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+(?P<as_num>\d+)
\s+parameters
\s+graceful-restart\s+stalepath-time
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters graceful-restart stalepath-time {{ bgp_params.graceful_restart }}",
@@ -1819,8 +1787,7 @@ class Bgp_globalTemplate(NetworkTemplate):
\s+(?P<as_num>\d+)
\s+parameters
\s+router-id
- \s+(?P<id>\S+)
- *$""",
+ \s+(?P<id>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp {{ as_number }} parameters router-id {{ bgp_params.router_id }}",
diff --git a/plugins/module_utils/network/vyos/rm_templates/bgp_global_14.py b/plugins/module_utils/network/vyos/rm_templates/bgp_global_14.py
index b8beb923..b86e233c 100644
--- a/plugins/module_utils/network/vyos/rm_templates/bgp_global_14.py
+++ b/plugins/module_utils/network/vyos/rm_templates/bgp_global_14.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -17,7 +16,6 @@ the given network resource.
import re
-from ansible.module_utils.six import iteritems
from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
NetworkTemplate,
)
@@ -26,12 +24,9 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.r
def _tmplt_bgp_params_confederation(config_data):
command = []
for list_el in config_data["bgp_params"]["confederation"]:
- for k, v in iteritems(list_el):
+ for k, v in list_el.items():
command.append(
- "protocols bgp parameters confederation ".format(**config_data)
- + k
- + " "
- + str(v),
+ "protocols bgp parameters confederation ".format(**config_data) + k + " " + str(v),
)
return command
@@ -65,7 +60,7 @@ def _tmplt_bgp_params_default(config_data):
def _tmplt_bgp_neighbor_timers(config_data):
command = []
- for k, v in iteritems(config_data["neighbor"]["timers"]):
+ for k, v in config_data["neighbor"]["timers"].items():
command.append(
"protocols bgp neighbor ".format(**config_data)
+ config_data["neighbor"]["address"]
@@ -80,7 +75,7 @@ def _tmplt_bgp_neighbor_timers(config_data):
def _tmplt_bgp_timers(config_data):
command = []
- for k, v in iteritems(config_data["timers"]):
+ for k, v in config_data["timers"].items():
command.append(
"protocols bgp ".format(**config_data) + "timers " + k + " " + str(v),
)
@@ -238,8 +233,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+advertisement-interval
- \s+(?P<interval>\S+)
- *$""",
+ \s+(?P<interval>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} advertisement-interval {{ neighbor.advertisement_interval }}",
@@ -431,8 +425,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+capability
\s+orf
\s+prefix-list
- \s+(?P<orf>\S+)
- *$""",
+ \s+(?P<orf>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} capability orf prefix-list {{ neighbor.capability.orf }}",
@@ -460,8 +453,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+(?P<address>\S+)
\s+default-originate
\s+route-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} default-originate route-map {{ neighbor.default_originate }}",
@@ -486,8 +478,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+description
- \s+(?P<desc>\S+)
- *$""",
+ \s+(?P<desc>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} description {{ neighbor.description }}",
@@ -562,8 +553,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+disable-send-community
- \s+(?P<comm>\S+)
- *$""",
+ \s+(?P<comm>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} disable-send-community {{ neighbor.disable_send_community }}",
@@ -620,8 +610,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+ebgp-multihop
- \s+(?P<hop>\S+)
- *$""",
+ \s+(?P<hop>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} ebgp-multihop {{ neighbor.ebgp_multihop }}",
@@ -647,8 +636,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+(?P<address>\S+)
\s+filter-list
\s+(?P<action>export|import)
- \s+(?P<list>\S+)
- *$""",
+ \s+(?P<list>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_filter_list,
@@ -705,8 +693,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+maximum-prefix
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} maximum-prefix {{ neighbor.maximum_prefix }}",
@@ -806,8 +793,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+password
- \s+(?P<pwd>\S+)
- *$""",
+ \s+(?P<pwd>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} password {{ neighbor.password }}",
@@ -832,8 +818,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+peer-group
- \s+(?P<name>\S+)
- *$""",
+ \s+(?P<name>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} peer-group {{ neighbor.peer_group_name }}",
@@ -858,8 +843,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+port
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} port {{ neighbor.port }}",
@@ -885,8 +869,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+(?P<address>\S+)
\s+prefix-list
\s+(?P<action>export|import)
- \s+(?P<list>\S+)
- *$""",
+ \s+(?P<list>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_prefix_list,
@@ -916,8 +899,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+remote-as
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} remote-as {{ neighbor.remote_as }}",
@@ -968,8 +950,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+(?P<address>\S+)
\s+route-map
\s+(?P<action>export|import)
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_route_map,
@@ -1125,8 +1106,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+unsuppress-map
- \s+(?P<map>\S+)
- *$""",
+ \s+(?P<map>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} unsuppress-map {{ neighbor.unsuppress_map }}",
@@ -1151,8 +1131,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+update-source
- \s+(?P<src>\S+)
- *$""",
+ \s+(?P<src>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} update-source {{ neighbor.update_source }}",
@@ -1177,8 +1156,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+weight
- \s+(?P<num>\S+)
- *$""",
+ \s+(?P<num>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} weight {{ neighbor.weight }}",
@@ -1203,8 +1181,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+neighbor
\s+(?P<address>\S+)
\s+ttl-security
- \s+(?P<ttl>\S+)
- *$""",
+ \s+(?P<ttl>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp neighbor {{ neighbor.address }} ttl-security {{ neighbor.ttl_security }}",
@@ -1230,8 +1207,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+(?P<address>\S+)
\s+timers
\s+(?P<type>connect|holdtime|keepalive)
- \s+(?P<sec>\S+)
- *$""",
+ \s+(?P<sec>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_neighbor_timers,
@@ -1258,8 +1234,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+bgp
\s+timers
\s+(?P<type>\S+)
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_timers,
@@ -1376,8 +1351,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+bgp
\s+parameters
\s+cluster-id
- \s+(?P<id>\S+)
- *$""",
+ \s+(?P<id>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters cluster-id {{ bgp_params.cluster_id }}",
@@ -1399,8 +1373,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+parameters
\s+confederation
\s+(?P<type>identifier|peers)
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_confederation,
@@ -1427,8 +1400,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+parameters
\s+dampening
\s+half-life
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters dampening half-life {{ bgp_params.dampening.half_life}}",
@@ -1452,8 +1424,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+parameters
\s+dampening
\s+max-suppress-time
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters dampening max-suppress-time {{ bgp_params.dampening.max_suppress_time}}",
@@ -1477,8 +1448,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+parameters
\s+dampening
\s+re-use
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters dampening re-use {{ bgp_params.dampening.re_use}}",
@@ -1502,8 +1472,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+parameters
\s+dampening
\s+start-suppress-time
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters dampening start-suppress-time {{ bgp_params.dampening.start_suppress_time}}",
@@ -1526,9 +1495,8 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+bgp
\s+parameters
\s+default
- \s*(?P<no_ipv4_unicast>no-ipv4-unicast)*
- \s*(?P<local_pref>local-pref\s\S+)
- *$""",
+ \s*(?P<no_ipv4_unicast>no-ipv4-unicast)?
+ \s*(?P<local_pref>local-pref\s\S+)?\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_default,
@@ -1597,8 +1565,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+distance\sprefix
\s+(?P<prefix>\S+)
\s+distance
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters distance prefix {{ bgp_params.distance.prefix }} distance {{ bgp_params.distance.value }}",
@@ -1626,8 +1593,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+parameters
\s+distance\sglobal
\s+(?P<type>\S+)
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_bgp_params_distance,
@@ -1675,8 +1641,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+bgp
\s+parameters
\s+graceful-restart\s+stalepath-time
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters graceful-restart stalepath-time {{ bgp_params.graceful_restart }}",
@@ -1760,8 +1725,7 @@ class Bgp_globalTemplate14(NetworkTemplate):
\s+bgp
\s+parameters
\s+router-id
- \s+(?P<id>\S+)
- *$""",
+ \s+(?P<id>\S+)\s*$""",
re.VERBOSE,
),
"setval": "protocols bgp parameters router-id {{ bgp_params.router_id }}",
diff --git a/plugins/module_utils/network/vyos/rm_templates/ha.py b/plugins/module_utils/network/vyos/rm_templates/ha.py
new file mode 100644
index 00000000..300b14a7
--- /dev/null
+++ b/plugins/module_utils/network/vyos/rm_templates/ha.py
@@ -0,0 +1,1011 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Ha 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 _tmplt_vsrvs(config_data):
+ config_data = config_data["virtual_servers"]
+ command = []
+
+ cmd = "high-availability virtual-server {name}".format(**config_data)
+ for key, value in config_data.items():
+ if key == "name" or isinstance(value, dict) or value is None:
+ continue
+ else:
+ command.append(f"{cmd} {key.replace('_', '-')} {value}")
+
+ return command
+
+
+def _tmplt_vsrvs_rsrv(config_data):
+ config_data = config_data["virtual_servers"]
+ command = []
+ cmd = "high-availability virtual-server {name}".format(**config_data)
+ config_data = config_data["real_server"]
+ address = config_data["address"]
+ for key, value in config_data.items():
+ if key == "address" or value is None:
+ continue
+ if value is not None and key == "health_check_script":
+ command.append(cmd + " real-server " + address + " health-check script " + value)
+ else:
+ command.append(cmd + " real-server " + f"{address} {key.replace('_', '-')} {value}")
+ return command
+
+
+def _tmplt_vrrp_sgroup_hc(config_data):
+ config_data = config_data["vrrp"]["sync_groups"]
+ command = []
+ cmd = "high-availability vrrp sync-group {name}".format(**config_data)
+ config_data = config_data["health_check"]
+ for key, value in config_data.items():
+ if value is not None:
+ command.append(cmd + " health-check " + f"{key.replace('_', '-')} {value}")
+ return command
+
+
+def _tmplt_vrrp_sgroup_ts(config_data):
+ config_data = config_data["vrrp"]["sync_groups"]
+ command = []
+ cmd = "high-availability vrrp sync-group {name}".format(**config_data)
+ config_data = config_data["transition_script"]
+ for key, value in config_data.items():
+ if value is not None:
+ command.append(cmd + " transition-script " + f"{key.replace('_', '-')} {value}")
+ return command
+
+
+def _tmplt_vrrp_gp(config_data):
+ config_data = config_data["vrrp"]["global_parameters"]
+ command = []
+
+ cmd = "high-availability vrrp global-parameters".format(**config_data)
+ for key, value in config_data.items():
+ if isinstance(value, dict) or value is None:
+ continue
+ else:
+ command.append(f"{cmd} {key.replace('_', '-')} {value}")
+ return command
+
+
+def _tmplt_vrrp_gp_garp(config_data):
+ config_data = config_data["vrrp"]["global_parameters"]["garp"]
+ command = []
+ cmd = "high-availability vrrp global-parameters garp"
+
+ for key, value in config_data.items():
+ if value is None:
+ continue
+ command.append(f"{cmd} {key.replace('_', '-')} {value}")
+
+ return command
+
+
+def _tmplt_vrrp_group(config_data):
+ config_data = config_data["vrrp"]["groups"]
+ command = []
+ cmd = "high-availability vrrp group {name}".format(**config_data)
+
+ for key, value in config_data.items():
+ if (
+ key == "name"
+ or isinstance(value, dict)
+ or isinstance(value, list)
+ or isinstance(value, bool)
+ or value is None
+ ):
+ continue
+ else:
+ if key == "description":
+ value = f"'{value}'"
+ command.append(f"{cmd} {key.replace('_', '-')} {value}")
+ return command
+
+
+def _tmplt_vrrp_group_bool(config_data):
+ config_data = config_data["vrrp"]["groups"]
+ command = []
+ cmd = "high-availability vrrp group {name}".format(**config_data)
+
+ for key, value in config_data.items():
+ if key != "name" and value is not None:
+ command.append(f"{cmd} {key.replace('_', '-')}")
+ return command
+
+
+def _tmplt_vrrp_group_garp(config_data):
+ config_data = config_data["vrrp"]["groups"]
+ command = []
+ cmd = "high-availability vrrp group {name}".format(**config_data)
+ config_data = config_data["garp"]
+ for key, value in config_data.items():
+ if value is not None:
+ command.append(cmd + " garp " + f"{key.replace('_', '-')} {value}")
+ return command
+
+
+def _tmplt_vrrp_group_auth(config_data):
+ config_data = config_data["vrrp"]["groups"]
+ command = []
+ cmd = "high-availability vrrp group {name}".format(**config_data)
+ config_data = config_data["authentication"]
+ for key, value in config_data.items():
+ if value is not None:
+ command.append(cmd + " authentication " + f"{key.replace('_', '-')} {value}")
+ return command
+
+
+def _tmplt_vrrp_group_ts(config_data):
+ config_data = config_data["vrrp"]["groups"]
+ command = []
+ cmd = "high-availability vrrp group {name}".format(**config_data)
+ config_data = config_data["transition_script"]
+ for key, value in config_data.items():
+ if value is not None:
+ command.append(cmd + " transition-script " + f"{key.replace('_', '-')} {value}")
+ return command
+
+
+def _tmplt_vrrp_sgroup_member(config_data):
+ sgroup = config_data["vrrp"]["sync_groups"]
+ command = []
+ cmd = "high-availability vrrp sync-group {name}".format(**sgroup)
+ members = sgroup.get("member", [])
+ for member in members:
+ if member is None:
+ continue
+ command.append(f"{cmd} member {member}")
+ return command
+
+
+def _tmplt_vrrp_group_exaddress(config_data):
+ group = config_data["vrrp"]["groups"]
+ command = []
+ cmd = "high-availability vrrp group {name}".format(**group)
+ exaddresses = group.get("excluded_address", [])
+ for exaddress in exaddresses:
+ if exaddress is None:
+ continue
+ command.append(f"{cmd} excluded-address {exaddress}")
+ return command
+
+
+def _tmplt_vrrp_group_address(config_data):
+ group = config_data["vrrp"]["groups"]
+ command = []
+ cmd = "high-availability vrrp group {name}".format(**group)
+ addresses = group.get("address", [])
+ for address in addresses:
+ if address is None:
+ continue
+ command.append(f"{cmd} address {address}")
+ return command
+
+
+def _tmplt_vrrp_group_hc(config_data):
+ config_data = config_data["vrrp"]["groups"]
+ command = []
+ cmd = "high-availability vrrp group {name}".format(**config_data)
+ config_data = config_data["health_check"]
+ for key, value in config_data.items():
+ if value is not None:
+ command.append(cmd + " health-check " + f"{key.replace('_', '-')} {value}")
+ return command
+
+
+def _tmplt_vrrp_group_track_list(config_data):
+ config_data = config_data["vrrp"]["groups"]
+ command = []
+ cmd = "high-availability vrrp group {name}".format(**config_data)
+ config_data = config_data["track"]
+ for key, value in config_data.items():
+ if isinstance(value, list) and value is not None and key != "name":
+ for item in value:
+ command.append(cmd + " track " + f"{key.replace('_', '-')} {item}")
+ return command
+
+
+def _tmplt_vrrp_group_track_bool(config_data):
+ config_data = config_data["vrrp"]["groups"]
+ command = []
+ cmd = "high-availability vrrp group {name}".format(**config_data)
+ config_data = config_data["track"]
+ for key, value in config_data.items():
+ if key != "name" and value is not None:
+ command.append(cmd + " track " + f"{key.replace('_', '-')}")
+ return command
+
+
+class HaTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ prefix = {"set": "set", "remove": "delete"}
+ super(HaTemplate, self).__init__(
+ lines=lines,
+ tmplt=self,
+ prefix=prefix,
+ module=module,
+ )
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "disable",
+ "getval": re.compile(
+ r"""
+ ^set
+ \shigh-availability
+ \s(?P<disable>disable)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "high-availability disable",
+ "result": {
+ "disable": "{{ True if disable is defined else False }}",
+ },
+ },
+ {
+ "name": "virtual_servers.address",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svirtual-server
+ \s+(?P<name>\S+)
+ (?:\s+address\s+(?P<address>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vsrvs,
+ "result": {
+ "virtual_servers": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "address": "{{ address if address is defined else None }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "virtual_servers.algorithm",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svirtual-server
+ \s+(?P<name>\S+)
+ (?:\s+algorithm\s+(?P<algorithm>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vsrvs,
+ "result": {
+ "virtual_servers": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "algorithm": "{{ algorithm if algorithm is defined else None }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "virtual_servers.delay_loop",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svirtual-server
+ \s+(?P<name>\S+)
+ (?:\s+delay-loop\s+(?P<delay_loop>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vsrvs,
+ "result": {
+ "virtual_servers": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "delay_loop": "{{ delay_loop if delay_loop is defined else None }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "virtual_servers.forward_method",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svirtual-server
+ \s+(?P<name>\S+)
+ (?:\s+forward-method\s+(?P<forward_method>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vsrvs,
+ "result": {
+ "virtual_servers": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "forward_method": "{{ forward_method if forward_method is defined else None }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "virtual_servers.fwmark",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svirtual-server
+ \s+(?P<name>\S+)
+ (?:\s+fwmark\s+(?P<fwmark>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vsrvs,
+ "result": {
+ "virtual_servers": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "fwmark": "{{ fwmark if fwmark is defined else None }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "virtual_servers.persistence_timeout",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svirtual-server
+ \s+(?P<name>\S+)
+ (?:\s+persistence-timeout\s+(?P<persistence_timeout>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vsrvs,
+ "result": {
+ "virtual_servers": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "persistence_timeout": "{{ persistence_timeout if persistence_timeout is defined else None }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "virtual_servers.port",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svirtual-server
+ \s+(?P<name>\S+)
+ (?:\s+port\s+(?P<port>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vsrvs,
+ "result": {
+ "virtual_servers": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "port": "{{ port if port is defined else None }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "virtual_servers.protocol",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svirtual-server
+ \s+(?P<name>\S+)
+ (?:\s+protocol\s+(?P<protocol>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vsrvs,
+ "result": {
+ "virtual_servers": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "protocol": "{{ protocol if protocol is defined else None }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "virtual_servers.real_server.port",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svirtual-server
+ \s+(?P<name>\S+)
+ \sreal-server
+ \s+(?P<address>\S+)
+ (?:\s+port\s+(?P<port>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vsrvs_rsrv,
+ "result": {
+ "virtual_servers": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "real_server": {
+ "{{ address }}": {
+ "address": "{{ address }}",
+ "port": "{{ port if port is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "virtual_servers.real_server.health_check_script",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svirtual-server
+ \s+(?P<name>\S+)
+ \sreal-server
+ \s+(?P<address>\S+)
+ (?:\s+health-check\sscript\s+(?P<hcscript>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vsrvs_rsrv,
+ "result": {
+ "virtual_servers": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "real_server": {
+ "{{ address }}": {
+ "address": "{{ address }}",
+ "health_check_script": "{{ hcscript if hcscript is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "virtual_servers.real_server.connection_timeout",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svirtual-server
+ \s+(?P<name>\S+)
+ \sreal-server
+ \s+(?P<address>\S+)
+ (?:\s+connection-timeout\s+(?P<cont>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vsrvs_rsrv,
+ "result": {
+ "virtual_servers": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "real_server": {
+ "{{ address }}": {
+ "address": "{{ address }}",
+ "connection_timeout": "{{ cont if cont is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.sync_groups.member",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\ssync-group
+ \s+(?P<sgname>\S+)
+ \smember
+ \s+(?P<member>\S+)
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_sgroup_member,
+ "result": {
+ "vrrp": {
+ "sync_groups": {
+ "{{ sgname }}": {
+ "name": "{{ sgname }}",
+ "member": [
+ "{{ member }}",
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.sync_groups.health_check",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\ssync-group
+ \s+(?P<sgname>\S+)
+ \shealth-check
+ (?:\s+failure-count\s+(?P<failure_count>\S+))?
+ (?:\s+interval\s+(?P<int>\S+))?
+ (?:\s+ping\s+(?P<ping>\S+))?
+ (?:\s+script\s+(?P<script>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_sgroup_hc,
+ "result": {
+ "vrrp": {
+ "sync_groups": {
+ "{{ sgname }}": {
+ "name": "{{ sgname }}",
+ "health_check": {
+ "failure_count": "{{ failure_count if failure_count is defined else None }}",
+ "interval": "{{ int if int is defined else None }}",
+ "ping": "{{ ping if ping is defined else None }}",
+ "script": "{{ script if script is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.sync_groups.transition_script",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\ssync-group
+ \s+(?P<sgname>\S+)
+ \stransition-script
+ (?:\s+backup\s+(?P<backup>\S+))?
+ (?:\s+fault\s+(?P<fault>\S+))?
+ (?:\s+master\s+(?P<master>\S+))?
+ (?:\s+stop\s+(?P<stop>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_sgroup_ts,
+ "result": {
+ "vrrp": {
+ "sync_groups": {
+ "{{ sgname }}": {
+ "name": "{{ sgname }}",
+ "transition_script": {
+ "backup": "{{ backup if backup is defined else None }}",
+ "fault": "{{ fault if fault is defined else None }}",
+ "master": "{{ master if master is defined else None }}",
+ "stop": "{{ stop if stop is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.global_parameters.garp",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\sglobal-parameters
+ \s+garp
+ (?:\s+interval\s+(?P<interval>\S+))?
+ (?:\s+master-delay\s+(?P<master_delay>\S+))?
+ (?:\s+master-refresh\s+(?P<master_refresh>\S+))?
+ (?:\s+master-refresh-repeat\s+(?P<master_refresh_repeat>\S+))?
+ (?:\s+master-repeat\s+(?P<master_repeat>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_gp_garp,
+ "result": {
+ "vrrp": {
+ "global_parameters": {
+ "garp": {
+ "interval": "{{ interval if interval is defined else None }}",
+ "master_delay": "{{ master_delay if master_delay is defined else None }}",
+ "master_refresh": "{{ master_refresh if master_refresh is defined else None }}",
+ "master_refresh_repeat": "{{ master_refresh_repeat if master_refresh_repeat is defined else None }}",
+ "master_repeat": "{{ master_repeat if master_repeat is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.global_parameters",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\sglobal-parameters
+ (?=\s+(?:startup-delay|version)\s)
+ (?:\s+startup-delay\s+(?P<startup_delay>\S+))?
+ (?:\s+version\s+(?P<version>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_gp,
+ "result": {
+ "vrrp": {
+ "global_parameters": {
+ "startup_delay": "{{ startup_delay if startup_delay is defined else None }}",
+ "version": "{{ version if version is defined else None }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\sgroup
+ \s+(?P<gname>\S+)
+ (?:\s+description\s+(?P<description>'.+?'|\S+))?
+ (?:\s+advertise-interval\s+(?P<advertise_interval>\S+))?
+ (?:\s+hello-source-address\s+(?P<hello_source>\S+))?
+ (?:\s+interface\s+(?P<interface>\S+))?
+ (?:\s+peer-address\s+(?P<peer_address>\S+))?
+ (?:\s+preempt-delay\s+(?P<preempt_delay>\S+))?
+ (?:\s+priority\s+(?P<priority>\S+))?
+ (?:\s+vrid\s+(?P<vrid>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "name": "{{ gname }}",
+ "description": "{{ description | replace(\"'\", \"\") if description is defined else None }}",
+ "advertise_interval": "{{ advertise_interval if advertise_interval is defined else None }}",
+ "hello_source_address": "{{ hello_source if hello_source is defined else None }}",
+ "interface": "{{ interface if interface is defined else None }}",
+ "peer_address": "{{ peer_address if peer_address is defined else None }}",
+ "preempt_delay": "{{ preempt_delay if preempt_delay is defined else None }}",
+ "priority": "{{ priority if priority is defined else None }}",
+ "vrid": "{{ vrid if vrid is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups.excluded_address",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\sgroup
+ \s+(?P<gname>\S+)
+ \sexcluded-address
+ \s+(?P<excluded_address>.*)
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group_exaddress,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "name": "{{ gname }}",
+ "excluded_address": [
+ "{{ excluded_address | replace(\"'\", \"\") }}",
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups.address",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\sgroup
+ \s+(?P<gname>\S+)
+ \saddress
+ \s+(?P<address>.*)
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group_address,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "name": "{{ gname }}",
+ "address": [
+ "{{ address | replace(\"'\", \"\") }}",
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups.garp",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\sgroup
+ \s+(?P<gname>\S+)
+ \s+garp
+ (?:\s+interval\s+(?P<interval>\S+))?
+ (?:\s+master-delay\s+(?P<master_delay>\S+))?
+ (?:\s+master-refresh\s+(?P<master_refresh>\S+))?
+ (?:\s+master-refresh-repeat\s+(?P<master_refresh_repeat>\S+))?
+ (?:\s+master-repeat\s+(?P<master_repeat>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group_garp,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "name": "{{ gname }}",
+ "garp": {
+ "interval": "{{ interval if interval is defined else None }}",
+ "master_delay": "{{ master_delay if master_delay is defined else None }}",
+ "master_refresh": "{{ master_refresh if master_refresh is defined else None }}",
+ "master_refresh_repeat": "{{ master_refresh_repeat if master_refresh_repeat is defined else None }}",
+ "master_repeat": "{{ master_repeat if master_repeat is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups.authentication",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\sgroup
+ \s+(?P<gname>\S+)
+ \s+authentication
+ (?:\s+password\s+(?P<password>\S+))?
+ (?:\s+type\s+(?P<type>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group_auth,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "name": "{{ gname }}",
+ "authentication": {
+ "password": "{{ password if password is defined else None }}",
+ "type": "{{ type if type is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups.transition_script",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\sgroup
+ \s+(?P<gname>\S+)
+ \stransition-script
+ (?:\s+backup\s+(?P<backup>\S+))?
+ (?:\s+fault\s+(?P<fault>\S+))?
+ (?:\s+master\s+(?P<master>\S+))?
+ (?:\s+stop\s+(?P<stop>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group_ts,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "name": "{{ gname }}",
+ "transition_script": {
+ "backup": "{{ backup if backup is defined else None }}",
+ "fault": "{{ fault if fault is defined else None }}",
+ "master": "{{ master if master is defined else None }}",
+ "stop": "{{ stop if stop is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups.health_check",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\sgroup
+ \s+(?P<gname>\S+)
+ \shealth-check
+ (?:\s+failure-count\s+(?P<failure_count>\S+))?
+ (?:\s+interval\s+(?P<int>\S+))?
+ (?:\s+ping\s+(?P<ping>\S+))?
+ (?:\s+script\s+(?P<script>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group_hc,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "name": "{{ gname }}",
+ "health_check": {
+ "failure_count": "{{ failure_count if failure_count is defined else None }}",
+ "interval": "{{ int if int is defined else None }}",
+ "ping": "{{ ping if ping is defined else None }}",
+ "script": "{{ script if script is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups.track.interface",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\sgroup
+ \s+(?P<gname>\S+)
+ \strack
+ (?:\s+interface\s+(?P<interface>\S+))?
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group_track_list,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "name": "{{ gname }}",
+ "track": {
+ "interface": "{{ [interface.strip(\"'\")] if interface is defined else [] }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.snmp",
+ "getval": re.compile(
+ r"""
+ ^set
+ \shigh-availability
+ \svrrp
+ \s(?P<snmp>snmp)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "high-availability vrrp snmp",
+ "result": {
+ "vrrp": {
+ "snmp": "{{ 'enabled' if snmp is defined else 'disabled' }}",
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups.disable",
+ "getval": re.compile(
+ r"""
+ ^set
+ \shigh-availability\svrrp\sgroup
+ \s(?P<gname>\S+)
+ \s(?P<disable>disable)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group_bool,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "disable": "{{ True if disable is defined else False }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups.no_preempt",
+ "getval": re.compile(
+ r"""
+ ^set
+ \shigh-availability\svrrp\sgroup
+ \s(?P<gname>\S+)
+ \s(?P<no_preempt>no-preempt)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group_bool,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "no_preempt": "{{ True if no_preempt is defined else False }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups.rfc3768_compatibility",
+ "getval": re.compile(
+ r"""
+ ^set
+ \shigh-availability\svrrp\sgroup
+ \s(?P<gname>\S+)
+ \s(?P<rfc3768_compatibility>rfc3768-compatibility)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group_bool,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "rfc3768_compatibility": "{{ True if rfc3768_compatibility is defined else False }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "vrrp.groups.track.exclude_vrrp_interface",
+ "getval": re.compile(
+ r"""
+ ^set\shigh-availability\svrrp\sgroup
+ \s+(?P<gname>\S+)
+ \strack
+ \s(?P<exclude_vrrp_inter>exclude-vrrp-interface)
+ $
+ """,
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_vrrp_group_track_bool,
+ "result": {
+ "vrrp": {
+ "groups": {
+ "{{ gname }}": {
+ "name": "{{ gname }}",
+ "track": {
+ "exclude_vrrp_interface": "{{ True if exclude_vrrp_inter is defined else False }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/plugins/module_utils/network/vyos/rm_templates/hostname.py b/plugins/module_utils/network/vyos/rm_templates/hostname.py
index 29ab00f6..b7e56310 100644
--- a/plugins/module_utils/network/vyos/rm_templates/hostname.py
+++ b/plugins/module_utils/network/vyos/rm_templates/hostname.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
diff --git a/plugins/module_utils/network/vyos/rm_templates/logging_global.py b/plugins/module_utils/network/vyos/rm_templates/logging_global.py
index 516e270b..07ad1f68 100644
--- a/plugins/module_utils/network/vyos/rm_templates/logging_global.py
+++ b/plugins/module_utils/network/vyos/rm_templates/logging_global.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
diff --git a/plugins/module_utils/network/vyos/rm_templates/logging_global_15.py b/plugins/module_utils/network/vyos/rm_templates/logging_global_15.py
new file mode 100644
index 00000000..3216747d
--- /dev/null
+++ b/plugins/module_utils/network/vyos/rm_templates/logging_global_15.py
@@ -0,0 +1,241 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def tmplt_params(config_data):
+ def templt_common(val, tmplt):
+ if val.get("facility"):
+ tmplt += " facility {facility}".format(facility=val["facility"])
+ if val.get("severity"):
+ tmplt += " level {level}".format(level=val["severity"])
+ return tmplt
+
+ tmplt = ""
+ if config_data.get("global_params"):
+ val = config_data.get("global_params")
+ tmplt += "system syslog local"
+ tmplt = templt_common(val.get("facilities", {}), tmplt)
+ elif config_data.get("console"):
+ val = config_data.get("console")
+ tmplt += "system syslog console"
+ tmplt = templt_common(val.get("facilities", {}), tmplt)
+ elif config_data.get("hosts"):
+ val = config_data.get("hosts")
+ if val.get("hostname") and not val.get("port") and not val.get("protocol"):
+ tmplt += "system syslog remote {hostname}".format(hostname=val["hostname"])
+ if val.get("facilities"):
+ tmplt = templt_common(val.get("facilities"), tmplt)
+ return tmplt
+
+
+class Logging_globalTemplate15(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ prefix = {"set": "set", "remove": "delete"}
+ super(Logging_globalTemplate15, self).__init__(
+ lines=lines,
+ tmplt=self,
+ prefix=prefix,
+ module=module,
+ )
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "syslog.state",
+ "getval": re.compile(
+ r"""
+ ^set\ssystem
+ (\s(?P<syslog>syslog))
+ $""", re.VERBOSE,
+ ),
+ "setval": "system syslog",
+ "result": {
+ "syslog": {
+ "state": "{{ 'enabled' if syslog is defined else 'disabled' }}",
+ },
+ },
+ },
+ {
+ "name": "console.state",
+ "getval": re.compile(
+ r"""
+ ^set\ssystem\ssyslog
+ (\s(?P<console>console))
+ $""", re.VERBOSE,
+ ),
+ "setval": "system syslog console",
+ "result": {
+ "console": {
+ "state": "{{ 'enabled' if console is defined else 'disabled' }}",
+ },
+ },
+ },
+ {
+ "name": "console.facilities",
+ "getval": re.compile(
+ r"""
+ ^set\ssystem\ssyslog\sconsole\sfacility
+ (\s(?P<facility>all|auth|authpriv|cron|daemon|kern|lpr|mail|mark|news|protocols|security|syslog|user|uucp|local[0-7]))?
+ (\slevel\s(?P<level>'(emerg|alert|crit|err|warning|notice|info|debug|all)'))?
+ $""", re.VERBOSE,
+ ),
+ "setval": tmplt_params,
+ "remval": "system syslog console facility {{ console.facilities.facility }}",
+ "result": {
+ "console": {
+ "facilities": [
+ {
+ "facility": "{{ facility }}",
+ "severity": "{{ level }}",
+ },
+ ],
+ },
+ },
+ },
+ {
+ "name": "global_params.state",
+ "getval": re.compile(
+ r"""
+ ^set\ssystem\ssyslog
+ (\s(?P<local>local))
+ $""", re.VERBOSE,
+ ),
+ "setval": "system syslog local",
+ "result": {
+ "global_params": {
+ "state": "{{ 'enabled' if local is defined else 'disabled' }}",
+ },
+ },
+ },
+ {
+ "name": "global_params.marker_interval",
+ "getval": re.compile(
+ r"""
+ ^set\ssystem\ssyslog\smarker\sinterval
+ (\s(?P<marker_interval>'(\d+)'))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "system syslog marker interval {{ global_params.marker_interval }}",
+ "remval": "system syslog marker",
+ "result": {
+ "global_params": {
+ "marker_interval": "{{ marker_interval }}",
+ },
+ },
+ },
+ {
+ "name": "global_params.preserve_fqdn",
+ "getval": re.compile(
+ r"""
+ ^set\ssystem\ssyslog
+ (\s(?P<preserve_fqdn>preserve-fqdn))
+ $""", re.VERBOSE,
+ ),
+ "setval": "system syslog preserve-fqdn",
+ "result": {
+ "global_params": {
+ "preserve_fqdn": "{{ True if preserve_fqdn is defined }}",
+ },
+ },
+ },
+ {
+ "name": "global_params.facilities",
+ "getval": re.compile(
+ r"""
+ ^set\ssystem\ssyslog\slocal\sfacility
+ (\s(?P<facility>all|auth|authpriv|cron|daemon|kern|lpr|mail|mark|news|protocols|security|syslog|user|uucp|local[0-7]))?
+ (\slevel\s(?P<level>'(emerg|alert|crit|err|warning|notice|info|debug|all)'))?
+ $""", re.VERBOSE,
+ ),
+ "setval": tmplt_params,
+ "remval": "system syslog local facility {{ global_params.facilities.facility }}",
+ "result": {
+ "global_params": {
+ "facilities": [
+ {
+ "facility": "{{ facility }}",
+ "severity": "{{ level }}",
+ },
+ ],
+ },
+ },
+ },
+ {
+ "name": "hosts.port",
+ "getval": re.compile(
+ r"""
+ ^set\ssystem\ssyslog\sremote
+ (\s(?P<hostname>\S+))
+ (\sport\s(?P<port>'(\d+)'))
+ $""", re.VERBOSE,
+ ),
+ "setval": "system syslog remote {{ hosts.hostname }} port {{ hosts.port }}",
+ "result": {
+ "hosts": {
+ "{{ hostname }}": {
+ "hostname": "{{ hostname }}",
+ "port": "{{ port }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "hosts.protocol",
+ "getval": re.compile(
+ r"""
+ ^set\ssystem\ssyslog\sremote
+ (\s(?P<hostname>\S+))
+ (\sprotocol\s(?P<protocol>'(udp|tcp)'))
+ $""", re.VERBOSE,
+ ),
+ "setval": "system syslog remote {{ hosts.hostname }} protocol {{ hosts.protocol }}",
+ "result": {
+ "hosts": {
+ "{{ hostname }}": {
+ "hostname": "{{ hostname }}",
+ "protocol": "{{ protocol }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "hosts",
+ "getval": re.compile(
+ r"""
+ ^set\ssystem\ssyslog\sremote
+ (\s(?P<hostname>\S+))
+ (\sfacility\s(?P<facility>all|auth|authpriv|cron|daemon|kern|lpr|mail|mark|news|protocols|security|syslog|user|uucp|local[0-7]))
+ (\slevel\s(?P<level>'(emerg|alert|crit|err|warning|notice|info|debug|all)'))?
+ $""", re.VERBOSE,
+ ),
+ "setval": tmplt_params,
+ "remval": "system syslog remote {{ hosts.hostname }}",
+ "result": {
+ "hosts": {
+ "{{ hostname }}": {
+ "hostname": "{{ hostname }}",
+ "facilities": [
+ {
+ "facility": "{{ facility }}",
+ "severity": "{{ level }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/plugins/module_utils/network/vyos/rm_templates/nat.py b/plugins/module_utils/network/vyos/rm_templates/nat.py
new file mode 100644
index 00000000..94af8696
--- /dev/null
+++ b/plugins/module_utils/network/vyos/rm_templates/nat.py
@@ -0,0 +1,1186 @@
+# -*- coding: utf-8 -*-
+# 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
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_nat_rule_addr_sub(config_data):
+ """Generate address/fqdn/prefix/port/group commands for destination or source sub-dict."""
+ nat = config_data["nat"]
+ type_ = config_data["type"]
+ rid = config_data["id"]
+ atype = config_data["atype"]
+ sub = config_data["sub"]
+
+ base = f"{nat} {type_} rule {rid} {atype}"
+ commands = []
+
+ for field in ("address", "fqdn", "prefix", "port"):
+ if sub.get(field) is not None:
+ commands.append(f"{base} {field} {sub[field]}")
+
+ for gtype in ("address_group", "domain_group", "mac_group", "network_group", "port_group"):
+ if sub.get(gtype) is not None:
+ commands.append(f"{base} group {gtype.replace('_', '-')} {sub[gtype]}")
+
+ return commands
+
+
+def _tmplt_nat_rule_translation(config_data):
+ """Generate translation commands."""
+ nat = config_data["nat"]
+ type_ = config_data["type"]
+ rid = config_data["id"]
+ trans = config_data["translation"]
+
+ base = f"{nat} {type_} rule {rid} translation"
+ commands = []
+
+ if trans.get("address") is not None:
+ commands.append(f"{base} address {trans['address']}")
+
+ if trans.get("port") is not None:
+ commands.append(f"{base} port {trans['port']}")
+
+ if trans.get("redirect_port") is not None:
+ commands.append(f"{base} redirect port {trans['redirect_port']}")
+
+ if trans.get("address_mapping") is not None:
+ commands.append(f"{base} options address-mapping {trans['address_mapping']}")
+
+ if trans.get("port_mapping") is not None:
+ commands.append(f"{base} options port-mapping {trans['port_mapping']}")
+
+ return commands
+
+
+def _tmplt_nat64_translation_pool(config_data):
+ """Generate all nat64 translation pool commands from a single call."""
+ nat = config_data["nat"]
+ type_ = config_data["type"]
+ rid = config_data["id"]
+ pool_id = config_data["pool_id"]
+ pool = config_data["pool"]
+
+ base = f"{nat} {type_} rule {rid} translation pool {pool_id}"
+ commands = []
+
+ if pool.get("address") is not None:
+ commands.append(f"{base} address {pool['address']}")
+ if pool.get("description") is not None:
+ commands.append(f"{base} description '{pool['description']}'")
+ if pool.get("port") is not None:
+ commands.append(f"{base} port {pool['port']}")
+ if pool.get("protocol") is not None:
+ commands.append(f"{base} protocol {pool['protocol']}")
+ if pool.get("disable"):
+ commands.append(f"{base} disable")
+
+ return commands
+
+
+class NatTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ prefix = {"set": "set", "remove": "delete"}
+ super(NatTemplate, self).__init__(lines=lines, tmplt=self, prefix=prefix, module=module)
+
+ # fmt: off
+ PARSERS = [
+
+ # -------------------------
+ # CGNAT
+ # -------------------------
+ {
+ "name": "cgnat_log_allocation",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+log-allocation
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat log-allocation",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "log_allocation": True,
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_pool_external_range",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+pool
+ \s+external
+ \s+(?P<name>\S+)
+ \s+range
+ \s+(?P<range>\S+)(?:\s+seq\s+(?P<seq>\d+))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat pool external {{ name }} range {{ range }}{% if seq is defined and seq %} seq {{ seq }}{% endif %}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "pool": {
+ "external": [
+ {
+ "name": "{{ name }}",
+ "range": [
+ {
+ "value": "{{ range }}",
+ "seq": "{{ seq }}",
+ },
+ ],
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_pool_external_port_range",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+pool
+ \s+external
+ \s+(?P<name>\S+)
+ \s+external-port-range
+ \s+(?P<range>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat pool external {{ name }} external-port-range {{ range }}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "pool": {
+ "external": [
+ {
+ "name": "{{ name }}",
+ "external_port_range": "{{ range }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_pool_external_per_user",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+pool
+ \s+external
+ \s+(?P<name>\S+)
+ \s+per-user-limit
+ \s+port
+ \s+(?P<limit>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat pool external {{ name }} per-user-limit port {{ limit }}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "pool": {
+ "external": [
+ {
+ "name": "{{ name }}",
+ "per_user_limit": {"port": "{{ limit }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_pool_internal_range",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+pool
+ \s+internal
+ \s+(?P<name>\S+)
+ \s+range
+ \s+(?P<range>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat pool internal {{ name }} range {{ range }}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "pool": {
+ "internal": [
+ {
+ "name": "{{ name }}",
+ "range": ["{{ range }}"],
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_rule_source_pool",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+rule
+ \s+(?P<id>\d+)
+ \s+source
+ \s+pool
+ \s+(?P<pool>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat rule {{ id }} source pool {{ pool }}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "source": {"pool": "{{ pool }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "cgnat_rule_translation_pool",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+cgnat
+ \s+rule
+ \s+(?P<id>\d+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat cgnat rule {{ id }} translation pool {{ pool }}",
+ "result": {
+ "nat": {
+ "cgnat": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {"pool": "{{ pool }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+
+ # -------------------------
+ # GENERIC NAT
+ # -------------------------
+ {
+ "name": "nat_type_description",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+description
+ \s+(?P<description>.+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} description '{{ description }}'",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "description": "{{ description }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_protocol",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+protocol
+ \s+(?P<protocol>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} protocol {{ protocol }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "protocol": "{{ protocol }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_disable",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+disable
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} disable",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "disable": True,
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_exclude",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+exclude
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} exclude",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "exclude": True,
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_log",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+log
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} log",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "log": True,
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_address",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+(?P<atype>destination|source)
+ \s+address
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_addr_sub,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "{{ atype }}": {"address": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_prefix",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+(?P<atype>destination|source)
+ \s+prefix
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_addr_sub,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "{{ atype }}": {"prefix": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_fqdn",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+(?P<atype>destination|source)
+ \s+fqdn
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_addr_sub,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "{{ atype }}": {"fqdn": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_port",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+(?P<atype>destination|source)
+ \s+port
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_addr_sub,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "{{ atype }}": {"port": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_address_group",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+(?P<atype>destination|source)
+ \s+group
+ \s+(?P<gtype>address-group|domain-group|mac-group|network-group|port-group)
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_addr_sub,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "{{ atype }}": {
+ "{{ gtype | replace('-', '_') }}": "{{ value }}",
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_translation_address",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+address
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_translation,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {"address": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_translation_port",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+port
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_translation,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {"port": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_translation_options",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+options
+ \s+(?P<opt>address-mapping|port-mapping)
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_translation,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {
+ "{{ opt | replace('-', '_') }}": "{{ value }}",
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_translation_redirect",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+redirect
+ \s+port
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_nat_rule_translation,
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {
+ "redirect_port": "{{ value }}",
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_inbound_interface_name",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+inbound-interface
+ \s+name
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat {{ type }} rule {{ id }} inbound-interface name {{ value }}",
+ "result": {
+ "nat": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "inbound_interface": {"name": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_inbound_interface_group",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+inbound-interface
+ \s+group
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat {{ type }} rule {{ id }} inbound-interface group {{ value }}",
+ "result": {
+ "nat": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "inbound_interface": {"group": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_static_inbound_interface",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat
+ \s+static
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+inbound-interface
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat static rule {{ id }} inbound-interface {{ value }}",
+ "result": {
+ "nat": {
+ "static": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "inbound_interface": "{{ value }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat6x_inbound_interface",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+inbound-interface
+ \s+name
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} inbound-interface name {{ value }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "inbound_interface": {"name": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_outbound_interface",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+outbound-interface
+ \s+name
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} outbound-interface name {{ value }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "outbound_interface": {"name": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_outbound_interface_group",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source|static)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+outbound-interface
+ \s+group
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} outbound-interface group {{ value }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "outbound_interface": {"group": "{{ value }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_packet_type",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+packet-type
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} packet-type {{ value }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "packet_type": "{{ value }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_lb_backend",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+load-balance
+ \s+backend
+ \s+(?P<ip>\S+)
+ \s+weight
+ \s+(?P<weight>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} load-balance backend {{ ip }} weight {{ weight }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "load_balance": {
+ "backend": [
+ {"ip": "{{ ip }}", "weight": "{{ weight }}"},
+ ],
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat_type_lb_hash",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+(?P<nat>nat|nat64|nat66)
+ \s+(?P<type>destination|source)
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+load-balance
+ \s+hash
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ nat }} {{ type }} rule {{ id }} load-balance hash {{ value }}",
+ "result": {
+ "{{ nat }}": {
+ "{{ type }}": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "load_balance": {"hash": ["{{ value }}"]},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat64_match_mark",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat64
+ \s+source
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+match
+ \s+mark
+ \s+(?P<mark>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat64 source rule {{ id }} match mark {{ mark }}",
+ "result": {
+ "nat64": {
+ "source": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "match": {"mark": "{{ mark }}"},
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat64_translation_pool",
+ "getval": re.compile(r"^$"), # never matches — setval only
+ "setval": _tmplt_nat64_translation_pool,
+ "result": {},
+ },
+ {
+ "name": "nat64_translation_pool_address",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat64
+ \s+source
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool_id>\d+)
+ \s+address
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} address {{ value }}",
+ "result": {
+ "nat64": {
+ "source": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {
+ "pool": [{"id": "{{ pool_id }}", "address": "{{ value }}"}],
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+
+ {
+ "name": "nat64_translation_pool_description",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat64
+ \s+source
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool_id>\d+)
+ \s+description
+ \s+(?P<value>.+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} description '{{ value }}'",
+ "result": {
+ "nat64": {
+ "source": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {
+ "pool": [{"id": "{{ pool_id }}", "description": "{{ value }}"}],
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat64_translation_pool_disable",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat64
+ \s+source
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool_id>\d+)
+ \s+disable
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} disable",
+ "result": {
+ "nat64": {
+ "source": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {
+ "pool": [{"id": "{{ pool_id }}", "disable": True}],
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat64_translation_pool_port",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat64
+ \s+source
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool_id>\d+)
+ \s+port
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} port {{ value }}",
+ "result": {
+ "nat64": {
+ "source": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {
+ "pool": [{"id": "{{ pool_id }}", "port": "{{ value }}"}],
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nat64_translation_pool_protocol",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+nat64
+ \s+source
+ \s+rule
+ \s+(?P<id>\S+)
+ \s+translation
+ \s+pool
+ \s+(?P<pool_id>\d+)
+ \s+protocol
+ \s+(?P<value>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nat64 source rule {{ id }} translation pool {{ pool_id }} protocol {{ value }}",
+ "result": {
+ "nat64": {
+ "source": {
+ "rule": [
+ {
+ "id": "{{ id }}",
+ "translation": {
+ "pool": [{"id": "{{ pool_id }}", "protocol": "{{ value }}"}],
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/plugins/module_utils/network/vyos/rm_templates/ntp_global.py b/plugins/module_utils/network/vyos/rm_templates/ntp_global.py
index e9d8a0cb..b83a3e1c 100644
--- a/plugins/module_utils/network/vyos/rm_templates/ntp_global.py
+++ b/plugins/module_utils/network/vyos/rm_templates/ntp_global.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
diff --git a/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces.py b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces.py
index 0d7eaf84..5183aec4 100644
--- a/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces.py
+++ b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -23,6 +22,7 @@ from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.r
from ansible_collections.vyos.vyos.plugins.module_utils.network.vyos.utils.utils import (
get_interface_type,
+ get_interface_with_vif,
)
@@ -36,21 +36,23 @@ def _get_parameters(data):
def _tmplt_ospf_int_delete(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
- command = (
- "interfaces " + int_type + " {name} ".format(**config_data) + params[1] + " " + params[0]
- )
+ command = "interfaces " + int_type + " " + name + " " + params[1] + " " + params[0]
return command
def _tmplt_ospf_int_cost(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -62,11 +64,14 @@ def _tmplt_ospf_int_cost(config_data):
def _tmplt_ospf_int_auth_password(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -79,11 +84,14 @@ def _tmplt_ospf_int_auth_password(config_data):
def _tmplt_ospf_int_auth_md5(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -98,11 +106,14 @@ def _tmplt_ospf_int_auth_md5(config_data):
def _tmplt_ospf_int_auth_md5_delete(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -114,11 +125,14 @@ def _tmplt_ospf_int_auth_md5_delete(config_data):
def _tmplt_ospf_int_bw(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -130,11 +144,14 @@ def _tmplt_ospf_int_bw(config_data):
def _tmplt_ospf_int_hello_interval(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -146,11 +163,14 @@ def _tmplt_ospf_int_hello_interval(config_data):
def _tmplt_ospf_int_dead_interval(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -162,15 +182,10 @@ def _tmplt_ospf_int_dead_interval(config_data):
def _tmplt_ospf_int_mtu_ignore(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
- "interfaces "
- + int_type
- + " {name} ".format(**config_data)
- + params[1]
- + " "
- + params[0]
- + " mtu-ignore"
+ "interfaces " + int_type + " " + name + " " + params[1] + " " + params[0] + " mtu-ignore"
)
return command
@@ -178,11 +193,14 @@ def _tmplt_ospf_int_mtu_ignore(config_data):
def _tmplt_ospf_int_network(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -194,11 +212,14 @@ def _tmplt_ospf_int_network(config_data):
def _tmplt_ospf_int_priority(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -210,11 +231,14 @@ def _tmplt_ospf_int_priority(config_data):
def _tmplt_ospf_int_retransmit_interval(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -226,11 +250,14 @@ def _tmplt_ospf_int_retransmit_interval(config_data):
def _tmplt_ospf_int_transmit_delay(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -242,11 +269,14 @@ def _tmplt_ospf_int_transmit_delay(config_data):
def _tmplt_ospf_int_ifmtu(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -258,11 +288,14 @@ def _tmplt_ospf_int_ifmtu(config_data):
def _tmplt_ospf_int_instance(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
command = (
"interfaces "
+ int_type
- + " {name} ".format(**config_data)
+ + " "
+ + name
+ + " "
+ params[1]
+ " "
+ params[0]
@@ -274,16 +307,9 @@ def _tmplt_ospf_int_instance(config_data):
def _tmplt_ospf_int_passive(config_data):
int_type = get_interface_type(config_data["name"])
+ name = get_interface_with_vif(config_data["name"])
params = _get_parameters(config_data["address_family"])
- command = (
- "interfaces "
- + int_type
- + " {name} ".format(**config_data)
- + params[1]
- + " "
- + params[0]
- + " passive"
- )
+ command = "interfaces " + int_type + " " + name + " " + params[1] + " " + params[0] + " passive"
return command
@@ -308,6 +334,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
*$""",
@@ -316,7 +343,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"remval": _tmplt_ospf_int_delete,
"compval": "address_family",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -332,18 +359,18 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+authentication
\s+plaintext-password
- \s+(?P<text>\S+)
- *$""",
+ \s+(?P<text>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_ospf_int_auth_password,
"compval": "address_family.authentication",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -362,6 +389,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+authentication
@@ -369,15 +397,14 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+key-id
\s+(?P<id>\d+)
\s+md5-key
- \s+(?P<text>\S+)
- *$""",
+ \s+(?P<text>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_ospf_int_auth_md5,
"remval": _tmplt_ospf_int_auth_md5_delete,
"compval": "address_family.authentication",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -399,6 +426,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+bandwidth
@@ -409,7 +437,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"setval": _tmplt_ospf_int_bw,
"compval": "address_family.bandwidth",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -426,6 +454,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+cost
@@ -436,7 +465,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"setval": _tmplt_ospf_int_cost,
"compval": "address_family.cost",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -453,6 +482,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+hello-interval
@@ -463,7 +493,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"setval": _tmplt_ospf_int_hello_interval,
"compval": "address_family.hello_interval",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -480,6 +510,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+dead-interval
@@ -490,7 +521,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"setval": _tmplt_ospf_int_dead_interval,
"compval": "address_family.dead_interval",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -507,6 +538,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+(?P<mtu>mtu-ignore)
@@ -516,7 +548,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"setval": _tmplt_ospf_int_mtu_ignore,
"compval": "address_family.mtu_ignore",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -533,17 +565,17 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+network
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_ospf_int_network,
"compval": "address_family.network",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -560,6 +592,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+priority
@@ -570,7 +603,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"setval": _tmplt_ospf_int_priority,
"compval": "address_family.priority",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -587,6 +620,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+retransmit-interval
@@ -597,7 +631,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"setval": _tmplt_ospf_int_retransmit_interval,
"compval": "address_family.retransmit_interval",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -614,6 +648,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+transmit-delay
@@ -624,7 +659,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"setval": _tmplt_ospf_int_transmit_delay,
"compval": "address_family.transmit_delay",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -641,6 +676,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+ifmtu
@@ -651,7 +687,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"setval": _tmplt_ospf_int_ifmtu,
"compval": "address_family.ifmtu",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -668,6 +704,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+instance-id
@@ -678,7 +715,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"setval": _tmplt_ospf_int_instance,
"compval": "address_family.instance",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -695,6 +732,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
\s+(?P<afi>ip|ipv6)
\s+(?P<proto>ospf|ospfv3)
\s+(?P<pass>passive)
@@ -704,7 +742,7 @@ class Ospf_interfacesTemplate(NetworkTemplate):
"setval": _tmplt_ospf_int_passive,
"compval": "address_family.passive",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
"address_family": {
"{{ afi }}": {
"afi": '{{ "ipv4" if afi == "ip" else "ipv6" }}',
@@ -721,12 +759,13 @@ class Ospf_interfacesTemplate(NetworkTemplate):
\s+interfaces
\s+(?P<type>\S+)
\s+(?P<name>\S+)
+ (?:\s+vif\s+(?P<vif>\d+))?
.*$""",
re.VERBOSE,
),
"setval": "set interface {{ type }} {{ name }}",
"result": {
- "name": "{{ name }}",
+ "name": "{{ name + '.' + vif if vif is defined else name }}",
},
},
]
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
index 43fae1e9..8d09011f 100644
--- a/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py
+++ b/plugins/module_utils/network/vyos/rm_templates/ospf_interfaces_14.py
@@ -5,6 +5,7 @@
from __future__ import absolute_import, division, print_function
+
__metaclass__ = type
"""
@@ -31,9 +32,7 @@ def _get_parameters(data):
def _tmplt_ospf_int_delete(config_data):
params = _get_parameters(config_data["address_family"])
- command = (
- "protocols " + params[0] + " interface {name}".format(**config_data)
- )
+ command = "protocols " + params[0] + " interface {name}".format(**config_data)
return command
@@ -81,10 +80,7 @@ def _tmplt_ospf_int_auth_md5(config_data):
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"
+ "protocols " + params[0] + " interface {name}".format(**config_data) + " authentication"
)
return command
@@ -128,12 +124,7 @@ def _tmplt_ospf_int_dead_interval(config_data):
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"
- )
+ command = "protocols " + params[0] + " interface {name}".format(**config_data) + " mtu-ignore"
return command
@@ -212,12 +203,7 @@ def _tmplt_ospf_int_instance(config_data):
def _tmplt_ospf_int_passive(config_data):
params = _get_parameters(config_data["address_family"])
- command = (
- "protocols "
- + params[0]
- + " interface {name}".format(**config_data)
- + " passive"
- )
+ command = "protocols " + params[0] + " interface {name}".format(**config_data) + " passive"
return command
@@ -239,8 +225,7 @@ class Ospf_interfacesTemplate14(NetworkTemplate):
\s+protocols
\s+(?P<proto>ospf|ospfv3)
\s+interface
- \s+(?P<name>\S+)
- *$""",
+ \s+(?P<name>\S+)\s*$""",
re.VERBOSE,
),
"remval": _tmplt_ospf_int_delete,
@@ -265,8 +250,7 @@ class Ospf_interfacesTemplate14(NetworkTemplate):
\s+(?P<name>\S+)
\s+authentication
\s+plaintext-password
- \s+(?P<text>\S+)
- *$""",
+ \s+(?P<text>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_ospf_int_auth_password,
@@ -297,8 +281,7 @@ class Ospf_interfacesTemplate14(NetworkTemplate):
\s+key-id
\s+(?P<id>\d+)
\s+md5-key
- \s+(?P<text>\S+)
- *$""",
+ \s+(?P<text>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_ospf_int_auth_md5,
@@ -458,8 +441,7 @@ class Ospf_interfacesTemplate14(NetworkTemplate):
\s+interface
\s+(?P<name>\S+)
\s+network
- \s+(?P<val>\S+)
- *$""",
+ \s+(?P<val>\S+)\s*$""",
re.VERBOSE,
),
"setval": _tmplt_ospf_int_network,
diff --git a/plugins/module_utils/network/vyos/rm_templates/prefix_lists.py b/plugins/module_utils/network/vyos/rm_templates/prefix_lists.py
index 0e99cfea..0e071199 100644
--- a/plugins/module_utils/network/vyos/rm_templates/prefix_lists.py
+++ b/plugins/module_utils/network/vyos/rm_templates/prefix_lists.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
diff --git a/plugins/module_utils/network/vyos/rm_templates/route_maps.py b/plugins/module_utils/network/vyos/rm_templates/route_maps.py
index 8f218a6b..fd8fdd9d 100644
--- a/plugins/module_utils/network/vyos/rm_templates/route_maps.py
+++ b/plugins/module_utils/network/vyos/rm_templates/route_maps.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -33,8 +32,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "route_map",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\s*$""",
re.VERBOSE,
),
"compval": "route_map",
@@ -51,8 +49,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "sequence",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\s*$""",
re.VERBOSE,
),
"compval": "sequence",
@@ -75,8 +72,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "call",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\scall\s(?P<call>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\scall\s(?P<call>\S+)\s*$""",
re.VERBOSE,
),
"setval": "policy route-map {{route_map}} rule {{sequence}} call {{call}}",
@@ -99,8 +95,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "description",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sdescription\s(?P<description>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sdescription\s(?P<description>\S+)\s*$""",
re.VERBOSE,
),
"setval": "policy route-map {{route_map}} rule {{sequence}} description {{description}}",
@@ -123,8 +118,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "action",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\saction\s(?P<action>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\saction\s(?P<action>\S+)\s*$""",
re.VERBOSE,
),
"setval": "policy route-map {{route_map}} rule {{sequence}} action {{action}}",
@@ -147,8 +141,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "continue_sequence",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\scontinue\s(?P<continue>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\scontinue\s(?P<continue>\S+)\s*$""",
re.VERBOSE,
),
"setval": "policy route-map {{route_map}} rule {{sequence}} continue {{continue_sequence}}",
@@ -171,8 +164,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "on_match_next",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\son-match\s(?P<next>next)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\son-match\s(?P<next>next)\s*$""",
re.VERBOSE,
),
"compval": "on_match.next",
@@ -198,8 +190,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "on_match_goto",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\son-match\sgoto\s(?P<goto>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\son-match\sgoto\s(?P<goto>\S+)\s*$""",
re.VERBOSE,
),
"compval": "on_match.goto",
@@ -225,8 +216,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_aggregator_ip",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\saggregator\sip\s(?P<ip>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\saggregator\sip\s(?P<ip>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.aggregator.ip",
@@ -254,8 +244,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_aggregator_as",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\saggregator\sas\s(?P<as>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\saggregator\sas\s(?P<as>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.aggregator.as",
@@ -283,8 +272,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_as_path_exclude",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sas-path-exclude\s(?P<as>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sas-path-exclude\s(?P<as>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.as_path_exclude",
@@ -310,12 +298,12 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_as_path_prepend",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sas-path-prepend\s(?P<as>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sas-path-prepend\s(?P<as>.*)
+ $""",
re.VERBOSE,
),
"compval": "set.as_path_prepend",
- "setval": "policy route-map {{route_map}} rule {{sequence}} set as-path-prepend {{set.as_path_prepend}}",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} set as-path-prepend '{{set.as_path_prepend}}'",
"result": {
"route_maps": {
"{{ route_map }}": {
@@ -337,10 +325,10 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_atomic_aggregate",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\satomic-aggregate(?P<as>)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\s(?P<as>atomic-aggregate)\s*$""",
re.VERBOSE,
),
+ "compval": "set.atomic_aggregate",
"setval": "policy route-map {{route_map}} rule {{sequence}} set atomic-aggregate",
"result": {
"route_maps": {
@@ -363,8 +351,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_bgp_extcommunity_rt",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sbgp-extcommunity-rt\s(?P<bgp>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sbgp-extcommunity-rt\s(?P<bgp>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.bgp_extcommunity_rt",
@@ -391,13 +378,12 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_comm_list",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\scomm-list\scomm-list\s(?P<comm_list>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\scommunity-list\s(?P<comm_list>\S+)\s*$""",
re.VERBOSE,
),
- "compval": "set.comm_list.comm_list",
+ "compval": "match.community.community_list",
"setval": "policy route-map {{route_map}} rule {{sequence}} "
- "set comm-list comm-list {{set.comm_list.comm_list}}",
+ "match community community-list {{set.comm_list.comm_list}}",
"result": {
"route_maps": {
"{{ route_map }}": {
@@ -406,8 +392,8 @@ class Route_mapsTemplate(NetworkTemplate):
"{{sequence}}":
{
"sequence": "{{sequence}}",
- "set": {
- "comm_list": {"comm_list": "{{comm_list}}"},
+ "match": {
+ "community": {"community_list": "{{comm_list}}"},
},
},
},
@@ -419,8 +405,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_comm_list_delete",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\scomm-list\sdelete(?P<delete>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\scomm-list\s(?P<delete>delete)\s*$""",
re.VERBOSE,
),
"compval": "set.comm_list.comm_list",
@@ -447,8 +432,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_extcommunity_rt",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity-rt\s(?P<extcommunity_rt>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity-rt\s(?P<extcommunity_rt>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.extcommunity_rt",
@@ -475,8 +459,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_extcommunity_soo",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity-soo\s(?P<extcommunity_soo>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity-soo\s(?P<extcommunity_soo>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.extcommunity_soo",
@@ -500,11 +483,65 @@ class Route_mapsTemplate(NetworkTemplate):
},
},
{
+ "name": "set_extcommunity_bandwidth",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity\sbandwidth\s(?P<extcommunity_bw>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.extcommunity_bandwidth",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set extcommunity bandwidth {{set.extcommunity_bandwidth}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "extcommunity_bandwidth": "{{extcommunity_bw}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_extcommunity_bandwidth_non_transitive",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)
+ \sset\sextcommunity\s(?P<extcommunity_bw_nt>bandwidth-non-transitive)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.extcommunity_bandwidth_non_transitive",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set extcommunity bandwidth-non-transitive",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "extcommunity_bandwidth_non_transitive": "{{True if extcommunity_bw_nt is defined}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
"name": "set_ip_next_hop",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sip-next-hop\s(?P<ip_next_hop>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sip-next-hop\s(?P<ip_next_hop>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.ip_next_hop",
@@ -533,8 +570,7 @@ class Route_mapsTemplate(NetworkTemplate):
r"""
^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sipv6-next-hop
\s(?P<type>global|local)
- \s(?P<value>\S+)
- *$""",
+ \s(?P<value>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.ipv6_next_hop",
@@ -564,8 +600,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_large_community",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\slarge-community\s(?P<large_community>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\slarge-community\s(?P<large_community>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.large_community",
@@ -592,8 +627,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_local_preference",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\slocal-preference\s(?P<local_preference>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\slocal-preference\s(?P<local_preference>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.local_preference",
@@ -620,8 +654,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_metric",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\smetric\s(?P<metric>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\smetric\s(?P<metric>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.metric",
@@ -648,8 +681,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_metric_type",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\smetric-type\s(?P<metric_type>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\smetric-type\s(?P<metric_type>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.metric_type",
@@ -676,8 +708,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_origin",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sorigin\s(?P<origin>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sorigin\s(?P<origin>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.origin",
@@ -704,8 +735,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_originator_id",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\soriginator-id\s(?P<originator_id>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\soriginator-id\s(?P<originator_id>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.originator_id",
@@ -732,8 +762,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_src",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\ssrc\s(?P<src>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\ssrc\s(?P<src>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.src",
@@ -760,8 +789,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_tag",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\stag\s(?P<tag>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\stag\s(?P<tag>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.tag",
@@ -788,8 +816,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "set_weight",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sweight\s(?P<weight>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sweight\s(?P<weight>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.weight",
@@ -813,11 +840,37 @@ class Route_mapsTemplate(NetworkTemplate):
},
},
{
+ "name": "set_table",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\stable\s(?P<table>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.weight",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set table {{set.table}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "table": "{{table}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
"name": "set_community",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\scommunity\s(?P<value>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\scommunity\s(?P<value>\S+)\s*$""",
re.VERBOSE,
),
"compval": "set.community.value",
@@ -846,8 +899,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "match_as_path",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sas-path\s(?P<as_path>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sas-path\s(?P<as_path>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.as_path",
@@ -874,8 +926,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "match_community_community_list",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\scommunity-list\s(?P<community_list>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\scommunity-list\s(?P<community_list>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.community.community_list",
@@ -902,8 +953,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "match_community_exact_match",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\sexact-match(?P<exact_match>)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\sexact-match(?P<exact_match>)\s*$""",
re.VERBOSE,
),
"compval": "match.community.exact_match",
@@ -930,8 +980,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "match_extcommunity",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sextcommunity\s(?P<extcommunity>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sextcommunity\s(?P<extcommunity>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.extcommunity",
@@ -958,8 +1007,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "match_interface",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sinterface\s(?P<interface>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sinterface\s(?P<interface>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.interface",
@@ -986,8 +1034,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "match_large_community_large_community_list",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\slarge-community\slarge-community-list\s(?P<lc>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\slarge-community\slarge-community-list\s(?P<lc>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.large_community_large_community_list",
@@ -1014,8 +1061,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "match_metric",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\smetric\s(?P<metric>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\smetric\s(?P<metric>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.metric",
@@ -1042,8 +1088,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "match_origin",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sorigin\s(?P<origin>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sorigin\s(?P<origin>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.origin",
@@ -1070,8 +1115,7 @@ class Route_mapsTemplate(NetworkTemplate):
"name": "match_peer",
"getval": re.compile(
r"""
- ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\speer\s(?P<peer>\S+)
- *$""",
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\speer\s(?P<peer>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.peer",
@@ -1100,8 +1144,7 @@ class Route_mapsTemplate(NetworkTemplate):
r"""
^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sip\saddress
\s(?P<list_type>access-list|prefix-list)
- \s(?P<value>\S+)
- *$""",
+ \s(?P<value>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.ip.address",
@@ -1134,8 +1177,7 @@ class Route_mapsTemplate(NetworkTemplate):
r"""
^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sip\snexthop
\s(?P<list_type>access-list|prefix-list)
- \s(?P<value>\S+)
- *$""",
+ \s(?P<value>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.ip.next_hop",
@@ -1168,8 +1210,7 @@ class Route_mapsTemplate(NetworkTemplate):
r"""
^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sip\sroute-source
\s(?P<list_type>access-list|prefix-list)
- \s(?P<value>\S+)
- *$""",
+ \s(?P<value>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.ip.route_source",
@@ -1202,8 +1243,7 @@ class Route_mapsTemplate(NetworkTemplate):
r"""
^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sipv6\saddress
\s(?P<list_type>access-list|prefix-list)
- \s(?P<value>\S+)
- *$""",
+ \s(?P<value>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.ipv6.address",
@@ -1235,8 +1275,7 @@ class Route_mapsTemplate(NetworkTemplate):
"getval": re.compile(
r"""
^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sipv6\snexthop
- \s(?P<value>\S+)
- *$""",
+ \s(?P<value>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.ipv6.next_hop",
@@ -1261,12 +1300,37 @@ class Route_mapsTemplate(NetworkTemplate):
},
},
{
+ "name": "match_protocol",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sprotocol\s(?P<value>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.protocol",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match protocol {{match.protocol}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}": {
+ "sequence": "{{sequence}}",
+ "match": {
+ "protocol": "{{value}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
"name": "match_rpki",
"getval": re.compile(
r"""
^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\srpki
- \s(?P<value>\S+)
- *$""",
+ \s(?P<value>\S+)\s*$""",
re.VERBOSE,
),
"compval": "match.rpki",
diff --git a/plugins/module_utils/network/vyos/rm_templates/route_maps_14.py b/plugins/module_utils/network/vyos/rm_templates/route_maps_14.py
new file mode 100644
index 00000000..cf2d6b67
--- /dev/null
+++ b/plugins/module_utils/network/vyos/rm_templates/route_maps_14.py
@@ -0,0 +1,1363 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+"""
+The Route_maps parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+class Route_mapsTemplate14(NetworkTemplate):
+ def __init__(self, lines=None):
+ prefix = {"set": "set", "remove": "delete"}
+ super(Route_mapsTemplate14, self).__init__(lines=lines, tmplt=self, prefix=prefix)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "route_map",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "route_map",
+ "setval": "policy route-map {{route_map}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ },
+ },
+ },
+ },
+ {
+ "name": "sequence",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)
+ *$""",
+ re.VERBOSE,
+ ),
+ "compval": "sequence",
+ "setval": "policy route-map {{route_map}} rule {{sequence}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "call",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\scall\s(?P<call>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "setval": "policy route-map {{route_map}} rule {{sequence}} call {{call}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "call": "{{call}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "description",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sdescription\s(?P<description>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "setval": "policy route-map {{route_map}} rule {{sequence}} description {{description}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "description": "{{description}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "action",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\saction\s(?P<action>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "setval": "policy route-map {{route_map}} rule {{sequence}} action {{action}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "action": "{{action}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "continue_sequence",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\scontinue\s(?P<continue>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "setval": "policy route-map {{route_map}} rule {{sequence}} continue {{continue_sequence}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "continue_sequence": "{{continue}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "on_match_next",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\son-match\s(?P<next>next)
+ *$""",
+ re.VERBOSE,
+ ),
+ "compval": "on_match.next",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} on-match next",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "on_match": {
+ "next": "{{True if next is defined}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "on_match_goto",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\son-match\sgoto\s(?P<goto>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "on_match.goto",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} on-match goto {{on_match.goto}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "on_match": {
+ "goto": "{{goto}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_aggregator_ip",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\saggregator\sip\s(?P<ip>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.aggregator.ip",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} set aggregator ip {{set.aggregator.ip}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "aggregator": {
+ "ip": "{{ip}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_aggregator_as",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\saggregator\sas\s(?P<as>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.aggregator.as",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} set aggregator as {{set.aggregator.as}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "aggregator": {
+ "as": "{{as}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_as_path_exclude",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sas-path\sexclude\s(?P<as>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.as_path_exclude",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} set as-path exclude {{set.as_path_exclude}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "as_path_exclude": "{{as}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_as_path_prepend",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sas-path\sprepend\s(?P<as>.*)
+ $""",
+ re.VERBOSE,
+ ),
+ "compval": "set.as_path_prepend",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} set as-path prepend '{{set.as_path_prepend}}'",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "as_path_prepend": "{{as}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_atomic_aggregate",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\s(?P<as>atomic-aggregate)
+ *$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.atomic_aggregate",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} set atomic-aggregate",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "atomic_aggregate": "{{True if as is defined}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_bgp_extcommunity_rt",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sbgp-extcommunity-rt\s(?P<bgp>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.bgp_extcommunity_rt",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set bgp-extcommunity-rt {{set.bgp_extcommunity_rt}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "bgp_extcommunity_rt": "{{bgp}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_comm_list",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\scommunity-list\s(?P<comm_list>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.community.community_list",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match community community-list {{set.comm_list.comm_list}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "match": {
+ "community": {"community_list": "{{comm_list}}"},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_comm_list_delete",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\scomm-list\s(?P<delete>delete)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.comm_list.comm_list",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set comm-list delete",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "comm_list": {"delete": "{{True if delete is defined}}"},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_extcommunity_rt",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity\srt\s(?P<extcommunity_rt>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.extcommunity_rt",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set extcommunity rt {{set.extcommunity_rt}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "extcommunity_rt": "{{extcommunity_rt}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_extcommunity_soo",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity\ssoo\s(?P<extcommunity_soo>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.extcommunity_soo",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set extcommunity soo {{set.extcommunity_soo}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "extcommunity_soo": "{{extcommunity_soo}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_extcommunity_bandwidth",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity\sbandwidth\s(?P<extcommunity_bw>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.extcommunity_bandwidth",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set extcommunity bandwidth {{set.extcommunity_bandwidth}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "extcommunity_bandwidth": "{{extcommunity_bw}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_extcommunity_bandwidth_non_transitive",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sextcommunity\s(?P<extcommunity_bw_nt>bandwidth-non-transitive)
+ *$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.extcommunity_bandwidth_non_transitive",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set extcommunity bandwidth-non-transitive",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "extcommunity_bandwidth_non_transitive": "{{True if extcommunity_bw_nt is defined}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_ip_next_hop",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sip-next-hop\s(?P<ip_next_hop>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.ip_next_hop",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set ip-next-hop {{set.ip_next_hop}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "ip_next_hop": "{{ip_next_hop}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_ipv6_next_hop",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sipv6-next-hop
+ \s(?P<type>global|local)
+ \s(?P<value>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.ipv6_next_hop",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set ipv6-next-hop {{set.ipv6_next_hop.ip_type}} {{set.ipv6_next_hop.value}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "ipv6_next_hop": {
+ "ip_type": "{{type}}",
+ "value": "{{value}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_large_community",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\slarge-community\s(?P<op>none|replace\s(?P<large_community>\S+))
+ $""",
+ re.VERBOSE,
+ ),
+ "compval": "set.large_community",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set large-community {{set.large_community if set.large_community == 'none' else 'replace ' + set.large_community}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "large_community": "{{op if op == 'none' else large_community}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_local_preference",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\slocal-preference\s(?P<local_preference>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.local_preference",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set local-preference {{set.local_preference}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "local_preference": "{{local_preference}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_metric",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\smetric\s(?P<metric>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.metric",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set metric {{set.metric}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "metric": "{{metric}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_metric_type",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\smetric-type\s(?P<metric_type>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.metric_type",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set metric-type {{set.metric_type}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "metric_type": "{{metric_type}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_origin",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sorigin\s(?P<origin>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.origin",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set origin {{set.origin}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "origin": "{{origin}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_originator_id",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\soriginator-id\s(?P<originator_id>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.originator_id",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set originator-id {{set.originator_id}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "originator_id": "{{originator_id}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_src",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\ssrc\s(?P<src>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.src",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set src {{set.src}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "src": "{{src}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_tag",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\stag\s(?P<tag>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.tag",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set tag {{set.tag}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "tag": "{{tag}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_weight",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\sweight\s(?P<weight>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.weight",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set weight {{set.weight}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "weight": "{{weight}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_table",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\stable\s(?P<table>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "set.weight",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set table {{set.table}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "table": "{{table}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set_community",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\sset\scommunity\s(?P<op>none|replace\s(?P<value>\S+))
+ $""",
+ re.VERBOSE,
+ ),
+ "compval": "set.community.value",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "set community {{set.community.value if set.community.value == 'none' else 'replace ' + set.community.value}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "set": {
+ "community": {
+ "value": "{{op if op == 'none' else value}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_as_path",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sas-path\s(?P<as_path>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.as_path",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match as-path {{match.as_path}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "match": {
+ "as_path": "{{as_path}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_community_community_list",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\scommunity-list\s(?P<community_list>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.community.community_list",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match community community-list {{match.community.community_list}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "match": {
+ "community": {"community_list": "{{community_list}}"},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_community_exact_match",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\scommunity\sexact-match(?P<exact_match>)
+ *$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.community.exact_match",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match community exact-match",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "match": {
+ "community": {"exact_match": "{{True if exact_match is defined}}"},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_extcommunity",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sextcommunity\s(?P<extcommunity>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.extcommunity",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match extcommunity {{match.extcommunity}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "match": {
+ "extcommunity": "{{extcommunity}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_interface",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sinterface\s(?P<interface>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.interface",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match interface {{match.interface}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "match": {
+ "interface": "{{interface}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_large_community_large_community_list",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\slarge-community\slarge-community-list\s(?P<lc>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.large_community_large_community_list",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match large-community large-community-list {{match.large_community_large_community_list}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "match": {
+ "large_community_large_community_list": "{{lc}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_metric",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\smetric\s(?P<metric>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.metric",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match metric {{match.metric}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "match": {
+ "metric": "{{metric}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_origin",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sorigin\s(?P<origin>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.origin",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match origin {{match.origin}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "match": {
+ "origin": "{{origin}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_peer",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\speer\s(?P<peer>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.peer",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match peer {{match.peer}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}":
+ {
+ "sequence": "{{sequence}}",
+ "match": {
+ "peer": "{{peer}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_ip_address",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sip\saddress
+ \s(?P<list_type>access-list|prefix-list)
+ \s(?P<value>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.ip.address",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match ip address {{match.ip.address.list_type}} {{match.ip.address.value}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}": {
+ "sequence": "{{sequence}}",
+ "match": {
+ "ip": {
+ "address": {
+ "list_type": "{{list_type}}",
+ "value": "{{value}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_ip_next_hop",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sip\snexthop
+ \s(?P<list_type>access-list|prefix-list)
+ \s(?P<value>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.ip.next_hop",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match ip nexthop {{match.ip.next_hop.list_type}} {{match.ip.next_hop.value}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}": {
+ "sequence": "{{sequence}}",
+ "match": {
+ "ip": {
+ "next_hop": {
+ "list_type": "{{list_type}}",
+ "value": "{{value}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_ip_route_source",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sip\sroute-source
+ \s(?P<list_type>access-list|prefix-list)
+ \s(?P<value>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.ip.route_source",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match ip route-source {{match.ip.route_source.list_type}} {{match.ip.route_source.value}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}": {
+ "sequence": "{{sequence}}",
+ "match": {
+ "ip": {
+ "route_source": {
+ "list_type": "{{list_type}}",
+ "value": "{{value}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_ipv6_address",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sipv6\saddress
+ \s(?P<list_type>access-list|prefix-list)
+ \s(?P<value>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.ipv6.address",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match ipv6 address {{match.ipv6.address.list_type}} {{match.ipv6.address.value}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}": {
+ "sequence": "{{sequence}}",
+ "match": {
+ "ipv6": {
+ "address": {
+ "list_type": "{{list_type}}",
+ "value": "{{value}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_ipv6_nexthop",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sipv6\snexthop
+ \s(?P<value>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.ipv6.next_hop",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match ipv6 nexthop {{match.ipv6.next_hop}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}": {
+ "sequence": "{{sequence}}",
+ "match": {
+ "ipv6": {
+ "next_hop": "{{value}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_protocol",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\sprotocol\s(?P<value>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.protocol",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match protocol {{match.protocol}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}": {
+ "sequence": "{{sequence}}",
+ "match": {
+ "protocol": "{{value}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match_rpki",
+ "getval": re.compile(
+ r"""
+ ^set\spolicy\sroute-map\s(?P<route_map>\S+)\srule\s(?P<sequence>\d+)\smatch\srpki
+ \s(?P<value>\S+)\s*$""",
+ re.VERBOSE,
+ ),
+ "compval": "match.rpki",
+ "setval": "policy route-map {{route_map}} rule {{sequence}} "
+ "match rpki {{match.rpki}}",
+ "result": {
+ "route_maps": {
+ "{{ route_map }}": {
+ "route_map": '{{ route_map }}',
+ "entries": {
+ "{{sequence}}": {
+ "sequence": "{{sequence}}",
+ "match": {
+ "rpki": "{{value}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+
+ ]
+ # fmt: on
diff --git a/plugins/module_utils/network/vyos/rm_templates/snmp_server.py b/plugins/module_utils/network/vyos/rm_templates/snmp_server.py
index 71753083..bd76a5ae 100644
--- a/plugins/module_utils/network/vyos/rm_templates/snmp_server.py
+++ b/plugins/module_utils/network/vyos/rm_templates/snmp_server.py
@@ -5,7 +5,6 @@
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
"""
@@ -140,9 +139,9 @@ class Snmp_serverTemplate(NetworkTemplate):
r"""
^set\sservice\ssnmp\scommunity
\s+(?P<name>\S+)
- \s*(?P<auth>authorization\srw|authorization\sro)*
- \s*(client\s(?P<client>\S+))*
- \s*(network\s(?P<network>\S+))*
+ \s*(?P<auth>authorization\srw|authorization\sro)?
+ \s*(client\s(?P<client>\S+))?
+ \s*(network\s(?P<network>\S+))?
$""",
re.VERBOSE,
),
@@ -164,8 +163,7 @@ class Snmp_serverTemplate(NetworkTemplate):
"getval": re.compile(
r"""
^set\sservice\ssnmp\scontact
- \s+(?P<name>\S+)
- *$""",
+ \s+(?P<name>\S+)\s*$""",
re.VERBOSE,
),
"setval": "service snmp contact {{ contact }}",
@@ -179,8 +177,7 @@ class Snmp_serverTemplate(NetworkTemplate):
"getval": re.compile(
r"""
^set\sservice\ssnmp\sdescription
- \s+(?P<name>\S+)
- *$""",
+ \s+(?P<name>\S+)\s*$""",
re.VERBOSE,
),
"setval": "service snmp description {{ description }}",
@@ -195,8 +192,8 @@ class Snmp_serverTemplate(NetworkTemplate):
r"""
^set\sservice\ssnmp\slisten-address
\s+(?P<addr>\S+)
- \s*(port)*
- \s*(?P<port>\d+)*
+ \s*(port)?
+ \s*(?P<port>\d+)?
$""",
re.VERBOSE,
),
@@ -232,8 +229,7 @@ class Snmp_serverTemplate(NetworkTemplate):
"getval": re.compile(
r"""
^set\sservice\ssnmp\ssmux-peer
- \s+(?P<name>\S+)
- *$""",
+ \s+(?P<name>\S+)\s*$""",
re.VERBOSE,
),
"setval": "service snmp smux-peer {{ smux_peer }}",
@@ -247,8 +243,7 @@ class Snmp_serverTemplate(NetworkTemplate):
"getval": re.compile(
r"""
^set\sservice\ssnmp\strap-source
- \s+(?P<name>\S+)
- *$""",
+ \s+(?P<name>\S+)\s*$""",
re.VERBOSE,
),
"setval": "service snmp trap-source {{ trap_source }}",
@@ -263,9 +258,8 @@ class Snmp_serverTemplate(NetworkTemplate):
r"""
^set\sservice\ssnmp\strap-target
\s+(?P<name>\S+)
- \s*(?P<comm>community\s\S+)*
- \s*(?P<port>port\s\d+)*
- $""",
+ \s*(?P<comm>community\s\S+)?
+ \s*(?P<port>port\s\d+)? $""",
re.VERBOSE,
),
"setval": _tmplt_snmp_server_trap_target,
@@ -283,8 +277,7 @@ class Snmp_serverTemplate(NetworkTemplate):
"getval": re.compile(
r"""
^set\sservice\ssnmp\sv3\sengineid
- \s+(?P<name>\S+)
- *$""",
+ \s+(?P<name>\S+)\s*$""",
re.VERBOSE,
),
"setval": "service snmp v3 engineid {{ snmp_v3.engine_id }}",
@@ -301,9 +294,9 @@ class Snmp_serverTemplate(NetworkTemplate):
r"""
^set\sservice\ssnmp\sv3\sgroup
\s+(?P<name>\S+)
- \s*(?P<mode>mode\s\S+)*
- \s*(?P<sec>seclevel\s\S+)*
- \s*(?P<view>view\s\S+)*
+ \s*(?P<mode>mode\s\S+)?
+ \s*(?P<sec>seclevel\s\S+)?
+ \s*(?P<view>view\s\S+)?
$""",
re.VERBOSE,
),
@@ -329,9 +322,9 @@ class Snmp_serverTemplate(NetworkTemplate):
^set\sservice\ssnmp\sv3\strap-target
\s+(?P<name>\S+)
\s+auth
- \s*(?P<enc>encrypted-password\s\S+)*
- \s*(?P<plain>plaintext-password\s\S+)*
- \s*(?P<type>type\s\S+)*
+ \s*(?P<enc>encrypted-password\s\S+)?
+ \s*(?P<plain>plaintext-password\s\S+)?
+ \s*(?P<type>type\s\S+)?
$""",
re.VERBOSE,
),
@@ -358,8 +351,7 @@ class Snmp_serverTemplate(NetworkTemplate):
r"""
^set\sservice\ssnmp\sv3\strap-target
\s+(?P<name>\S+)
- \s+(?P<port>port\s\d+)*
- $""",
+ \s+(?P<port>port\s\d+)? $""",
re.VERBOSE,
),
"setval": "service snmp v3 trap-target port {{ snmp_v3.trap_targets.port }}",
@@ -381,7 +373,7 @@ class Snmp_serverTemplate(NetworkTemplate):
r"""
^set\sservice\ssnmp\sv3\strap-target
\s+(?P<name>\S+)
- \s+(?P<protocol>protocol\s\S+)*
+ \s+(?P<protocol>protocol\s\S+)?
$""",
re.VERBOSE,
),
@@ -404,7 +396,7 @@ class Snmp_serverTemplate(NetworkTemplate):
r"""
^set\sservice\ssnmp\sv3\strap-target
\s+(?P<name>\S+)
- \s+(?P<type>type\s\S+)*
+ \s+(?P<type>type\s\S+)?
$""",
re.VERBOSE,
),
@@ -427,7 +419,7 @@ class Snmp_serverTemplate(NetworkTemplate):
r"""
^set\sservice\ssnmp\sv3\strap-target
\s+(?P<name>\S+)
- \s+(?P<user>user\s\S+)*
+ \s+(?P<user>user\s\S+)?
$""",
re.VERBOSE,
),
@@ -451,9 +443,9 @@ class Snmp_serverTemplate(NetworkTemplate):
^set\sservice\ssnmp\sv3\strap-target
\s+(?P<name>\S+)
\s+privacy
- \s*(?P<enc>encrypted-password\s\S+)*
- \s*(?P<plain>plaintext-password\s\S+)*
- \s*(?P<type>type\s\S+)*
+ \s*(?P<enc>encrypted-password\s\S+)?
+ \s*(?P<plain>plaintext-password\s\S+)?
+ \s*(?P<type>type\s\S+)?
$""",
re.VERBOSE,
),
@@ -481,9 +473,9 @@ class Snmp_serverTemplate(NetworkTemplate):
^set\sservice\ssnmp\sv3\suser
\s+(?P<name>\S+)
\s+auth
- \s*(?P<enc>encrypted-password\s\S+)*
- \s*(?P<plain>plaintext-password\s\S+)*
- \s*(?P<type>type\s\S+)*
+ \s*(?P<enc>encrypted-password\s\S+)?
+ \s*(?P<plain>plaintext-password\s\S+)?
+ \s*(?P<type>type\s\S+)?
$""",
re.VERBOSE,
),
@@ -511,9 +503,9 @@ class Snmp_serverTemplate(NetworkTemplate):
^set\sservice\ssnmp\sv3\suser
\s+(?P<name>\S+)
\s+privacy
- \s*(?P<enc>encrypted-password\s\S+)*
- \s*(?P<plain>plaintext-password\s\S+)*
- \s*(?P<type>type\s\S+)*
+ \s*(?P<enc>encrypted-password\s\S+)?
+ \s*(?P<plain>plaintext-password\s\S+)?
+ \s*(?P<type>type\s\S+)?
$""",
re.VERBOSE,
),
@@ -540,8 +532,7 @@ class Snmp_serverTemplate(NetworkTemplate):
r"""
^set\sservice\ssnmp\sv3\suser
\s+(?P<name>\S+)
- \s+(?P<group>group\s.+)*
- $""",
+ \s+(?P<group>group\s.+)? $""",
re.VERBOSE,
),
"setval": "service snmp v3 user {{ snmp_v3.users.user }} group {{ snmp_v3.users.group }}",
@@ -563,7 +554,7 @@ class Snmp_serverTemplate(NetworkTemplate):
r"""
^set\sservice\ssnmp\sv3\suser
\s+(?P<name>\S+)
- \s+(?P<mode>mode\s\S+)*
+ \s+(?P<mode>mode\s\S+)?
$""",
re.VERBOSE,
),
@@ -587,8 +578,8 @@ class Snmp_serverTemplate(NetworkTemplate):
^set\sservice\ssnmp\sv3\sview
\s+(?P<name>\S+)
\s+(?P<oid>oid\s\S+)
- \s*(?P<ex>exclude\s\S+)*
- \s*(?P<mask>mask\s\S+)*
+ \s*(?P<ex>exclude\s\S+)?
+ \s*(?P<mask>mask\s\S+)?
$""",
re.VERBOSE,
),
diff --git a/plugins/module_utils/network/vyos/rm_templates/vpn_ipsec.py b/plugins/module_utils/network/vyos/rm_templates/vpn_ipsec.py
new file mode 100644
index 00000000..ff889526
--- /dev/null
+++ b/plugins/module_utils/network/vyos/rm_templates/vpn_ipsec.py
@@ -0,0 +1,997 @@
+# -*- coding: utf-8 -*-
+# Copyright 2026 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 VPN IPSEC parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+class Vpn_ipsecTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ prefix = {"set": "set", "remove": "delete"}
+ super(Vpn_ipsecTemplate, self).__init__(
+ lines=lines,
+ tmplt=self,
+ prefix=prefix,
+ module=module,
+ )
+
+ # fmt: off
+ PARSERS = [
+ # ---------------------------------------------------------------
+ # esp-group
+ # ---------------------------------------------------------------
+ {
+ "name": "esp_group",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sesp-group\s(?P<esp_group>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec esp-group {{ name }}",
+ "result": {
+ "esp_group": {
+ "{{ esp_group }}": {
+ "name": "{{ esp_group }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "esp_group.proposal",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sesp-group\s(?P<esp_group>\S+)
+ \sproposal\s(?P<proposal_id>\d+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec esp-group {{ name }} proposal {{ proposal_id }}",
+ "result": {
+ "esp_group": {
+ "{{ esp_group }}": {
+ "name": "{{ esp_group }}",
+ "proposal": {
+ "{{ proposal_id }}": {
+ "proposal_id": "{{ proposal_id }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "esp_group.proposal.encryption",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sesp-group\s(?P<esp_group>\S+)
+ \sproposal\s(?P<proposal_id>\d+)
+ \sencryption\s'?(?P<encryption>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec esp-group {{ name }} proposal {{ proposal_id }} encryption {{ encryption }}",
+ "result": {
+ "esp_group": {
+ "{{ esp_group }}": {
+ "name": "{{ esp_group }}",
+ "proposal": {
+ "{{ proposal_id }}": {
+ "proposal_id": "{{ proposal_id }}",
+ "encryption": "{{ encryption }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "esp_group.proposal.hash",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sesp-group\s(?P<esp_group>\S+)
+ \sproposal\s(?P<proposal_id>\d+)
+ \shash\s'?(?P<hash>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec esp-group {{ name }} proposal {{ proposal_id }} hash {{ hash }}",
+ "result": {
+ "esp_group": {
+ "{{ esp_group }}": {
+ "name": "{{ esp_group }}",
+ "proposal": {
+ "{{ proposal_id }}": {
+ "proposal_id": "{{ proposal_id }}",
+ "hash": "{{ hash }}",
+ },
+ },
+ },
+ },
+ },
+ },
+
+ # ---------------------------------------------------------------
+ # ike-group
+ # ---------------------------------------------------------------
+ {
+ "name": "ike_group",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.key_exchange",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \skey-exchange\s'?(?P<key_exchange>\w+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} key-exchange {{ key_exchange }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "key_exchange": "{{ key_exchange }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.proposal",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \sproposal\s(?P<proposal_id>\d+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} proposal {{ proposal_id }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "proposal": {
+ "{{ proposal_id }}": {
+ "proposal_id": "{{ proposal_id }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.proposal.dh_group",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \sproposal\s(?P<proposal_id>\d+)
+ \sdh-group\s'?(?P<dh_group>\d+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} proposal {{ proposal_id }} dh-group {{ dh_group }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "proposal": {
+ "{{ proposal_id }}": {
+ "proposal_id": "{{ proposal_id }}",
+ "dh_group": "{{ dh_group }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.proposal.encryption",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \sproposal\s(?P<proposal_id>\d+)
+ \sencryption\s'?(?P<encryption>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} proposal {{ proposal_id }} encryption {{ encryption }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "proposal": {
+ "{{ proposal_id }}": {
+ "proposal_id": "{{ proposal_id }}",
+ "encryption": "{{ encryption }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.proposal.hash",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \sproposal\s(?P<proposal_id>\d+)
+ \shash\s'?(?P<hash>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} proposal {{ proposal_id }} hash {{ hash }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "proposal": {
+ "{{ proposal_id }}": {
+ "proposal_id": "{{ proposal_id }}",
+ "hash": "{{ hash }}",
+ },
+ },
+ },
+ },
+ },
+ },
+
+ # ---------------------------------------------------------------
+ # authentication psk
+ # ---------------------------------------------------------------
+ {
+ "name": "authentication.psk",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sauthentication\spsk\s(?P<psk>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec authentication psk {{ name }}",
+ "result": {
+ "authentication": {
+ "psk": {
+ "{{ psk }}": {
+ "name": "{{ psk }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication.psk.id",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sauthentication\spsk\s(?P<psk>\S+)
+ \sid\s'?(?P<id>\S+?)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec authentication psk {{ name }} id {{ id }}",
+ "result": {
+ "authentication": {
+ "psk": {
+ "{{ psk }}": {
+ "name": "{{ psk }}",
+ "id": ["{{ id }}"],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication.psk.secret",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sauthentication\spsk\s(?P<psk>\S+)
+ \ssecret\s'?(?P<secret>[^']+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec authentication psk {{ name }} secret '{{ secret }}'",
+ "result": {
+ "authentication": {
+ "psk": {
+ "{{ psk }}": {
+ "name": "{{ psk }}",
+ "secret": "{{ secret }}",
+ },
+ },
+ },
+ },
+ },
+
+ # ---------------------------------------------------------------
+ # profile
+ # ---------------------------------------------------------------
+ {
+ "name": "profile",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sprofile\s(?P<profile>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec profile {{ name }}",
+ "result": {
+ "profile": {
+ "{{ profile }}": {
+ "name": "{{ profile }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "profile.authentication.mode",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sprofile\s(?P<profile>\S+)
+ \sauthentication\smode\s'?(?P<mode>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec profile {{ name }} authentication mode {{ mode }}",
+ "result": {
+ "profile": {
+ "{{ profile }}": {
+ "name": "{{ profile }}",
+ "authentication": {
+ "mode": "{{ mode }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "profile.authentication.pre_shared_secret",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sprofile\s(?P<profile>\S+)
+ \sauthentication\spre-shared-secret\s'?(?P<pre_shared_secret>[^']+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec profile {{ name }} authentication pre-shared-secret '{{ pre_shared_secret }}'",
+ "result": {
+ "profile": {
+ "{{ profile }}": {
+ "name": "{{ profile }}",
+ "authentication": {
+ "pre_shared_secret": "{{ pre_shared_secret }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "profile.bind_tunnel",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sprofile\s(?P<profile>\S+)
+ \sbind\stunnel\s'?(?P<bind_tunnel>\S+?)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec profile {{ name }} bind tunnel {{ bind_tunnel }}",
+ "result": {
+ "profile": {
+ "{{ profile }}": {
+ "name": "{{ profile }}",
+ "bind_tunnel": ["{{ bind_tunnel }}"],
+ },
+ },
+ },
+ },
+ {
+ "name": "profile.esp_group",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sprofile\s(?P<profile>\S+)
+ \sesp-group\s'?(?P<esp_group>\S+?)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec profile {{ name }} esp-group {{ esp_group }}",
+ "result": {
+ "profile": {
+ "{{ profile }}": {
+ "name": "{{ profile }}",
+ "esp_group": "{{ esp_group }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "profile.ike_group",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sprofile\s(?P<profile>\S+)
+ \sike-group\s'?(?P<ike_group>\S+?)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec profile {{ name }} ike-group {{ ike_group }}",
+ "result": {
+ "profile": {
+ "{{ profile }}": {
+ "name": "{{ profile }}",
+ "ike_group": "{{ ike_group }}",
+ },
+ },
+ },
+ },
+
+ # ---------------------------------------------------------------
+ # ike-group: remaining fields
+ # ---------------------------------------------------------------
+ {
+ "name": "ike_group.close_action",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \sclose-action\s'?(?P<close_action>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} close-action {{ close_action }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "close_action": "{{ close_action }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.dead_peer_detection.action",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \sdead-peer-detection\saction\s'?(?P<action>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} dead-peer-detection action {{ action }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "dead_peer_detection": {"action": "{{ action }}"},
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.dead_peer_detection.interval",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \sdead-peer-detection\sinterval\s'?(?P<interval>\d+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} dead-peer-detection interval {{ interval }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "dead_peer_detection": {"interval": "{{ interval }}"},
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.dead_peer_detection.timeout",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \sdead-peer-detection\stimeout\s'?(?P<timeout>\d+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} dead-peer-detection timeout {{ timeout }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "dead_peer_detection": {"timeout": "{{ timeout }}"},
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.disable_mobike",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \sdisable-mobike
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} disable-mobike",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "disable_mobike": True,
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.ikev2_reauth",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \sikev2-reauth
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} ikev2-reauth",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "ikev2_reauth": True,
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.lifetime",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \slifetime\s'?(?P<lifetime>\d+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} lifetime {{ lifetime }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "lifetime": "{{ lifetime }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "ike_group.mode",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sike-group\s(?P<ike_group>\S+)
+ \smode\s'?(?P<mode>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec ike-group {{ name }} mode {{ mode }}",
+ "result": {
+ "ike_group": {
+ "{{ ike_group }}": {
+ "name": "{{ ike_group }}",
+ "mode": "{{ mode }}",
+ },
+ },
+ },
+ },
+
+ # ---------------------------------------------------------------
+ # esp-group: remaining fields
+ # ---------------------------------------------------------------
+ {
+ "name": "esp_group.compression",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sesp-group\s(?P<esp_group>\S+)
+ \scompression
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec esp-group {{ name }} compression",
+ "result": {
+ "esp_group": {
+ "{{ esp_group }}": {
+ "name": "{{ esp_group }}",
+ "compression": True,
+ },
+ },
+ },
+ },
+ {
+ "name": "esp_group.disable_rekey",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sesp-group\s(?P<esp_group>\S+)
+ \sdisable-rekey
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec esp-group {{ name }} disable-rekey",
+ "result": {
+ "esp_group": {
+ "{{ esp_group }}": {
+ "name": "{{ esp_group }}",
+ "disable_rekey": True,
+ },
+ },
+ },
+ },
+ {
+ "name": "esp_group.life_bytes",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sesp-group\s(?P<esp_group>\S+)
+ \slife-bytes\s'?(?P<life_bytes>\d+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec esp-group {{ name }} life-bytes {{ life_bytes }}",
+ "result": {
+ "esp_group": {
+ "{{ esp_group }}": {
+ "name": "{{ esp_group }}",
+ "life_bytes": "{{ life_bytes }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "esp_group.life_packets",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sesp-group\s(?P<esp_group>\S+)
+ \slife-packets\s'?(?P<life_packets>\d+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec esp-group {{ name }} life-packets {{ life_packets }}",
+ "result": {
+ "esp_group": {
+ "{{ esp_group }}": {
+ "name": "{{ esp_group }}",
+ "life_packets": "{{ life_packets }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "esp_group.lifetime",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sesp-group\s(?P<esp_group>\S+)
+ \slifetime\s'?(?P<lifetime>\d+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec esp-group {{ name }} lifetime {{ lifetime }}",
+ "result": {
+ "esp_group": {
+ "{{ esp_group }}": {
+ "name": "{{ esp_group }}",
+ "lifetime": "{{ lifetime }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "esp_group.mode",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sesp-group\s(?P<esp_group>\S+)
+ \smode\s'?(?P<mode>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec esp-group {{ name }} mode {{ mode }}",
+ "result": {
+ "esp_group": {
+ "{{ esp_group }}": {
+ "name": "{{ esp_group }}",
+ "mode": "{{ mode }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "esp_group.pfs",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sesp-group\s(?P<esp_group>\S+)
+ \spfs\s'?(?P<pfs>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec esp-group {{ name }} pfs {{ pfs }}",
+ "result": {
+ "esp_group": {
+ "{{ esp_group }}": {
+ "name": "{{ esp_group }}",
+ "pfs": "{{ pfs }}",
+ },
+ },
+ },
+ },
+
+ # ---------------------------------------------------------------
+ # authentication.psk: remaining fields
+ # ---------------------------------------------------------------
+ {
+ "name": "authentication.psk.secret_type",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sauthentication\spsk\s(?P<psk>\S+)
+ \ssecret-type\s'?(?P<secret_type>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec authentication psk {{ name }} secret-type {{ secret_type }}",
+ "result": {
+ "authentication": {
+ "psk": {
+ "{{ psk }}": {
+ "name": "{{ psk }}",
+ "secret_type": "{{ secret_type }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication.psk.dhcp_interface",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sauthentication\spsk\s(?P<psk>\S+)
+ \sdhcp-interface\s'?(?P<dhcp_interface>\S+?)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec authentication psk {{ name }} dhcp-interface {{ dhcp_interface }}",
+ "result": {
+ "authentication": {
+ "psk": {
+ "{{ psk }}": {
+ "name": "{{ psk }}",
+ "dhcp_interface": ["{{ dhcp_interface }}"],
+ },
+ },
+ },
+ },
+ },
+
+ # ---------------------------------------------------------------
+ # authentication.ppk
+ # ---------------------------------------------------------------
+ {
+ "name": "authentication.ppk",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sauthentication\sppk\s(?P<ppk>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec authentication ppk {{ name }}",
+ "result": {
+ "authentication": {
+ "ppk": {
+ "{{ ppk }}": {
+ "name": "{{ ppk }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication.ppk.id",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sauthentication\sppk\s(?P<ppk>\S+)
+ \sid\s'?(?P<id>\S+?)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec authentication ppk {{ name }} id {{ id }}",
+ "result": {
+ "authentication": {
+ "ppk": {
+ "{{ ppk }}": {
+ "name": "{{ ppk }}",
+ "id": ["{{ id }}"],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication.ppk.secret",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sauthentication\sppk\s(?P<ppk>\S+)
+ \ssecret\s'?(?P<secret>[^']+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec authentication ppk {{ name }} secret '{{ secret }}'",
+ "result": {
+ "authentication": {
+ "ppk": {
+ "{{ ppk }}": {
+ "name": "{{ ppk }}",
+ "secret": "{{ secret }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication.ppk.secret_type",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sauthentication\sppk\s(?P<ppk>\S+)
+ \ssecret-type\s'?(?P<secret_type>[\w-]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec authentication ppk {{ name }} secret-type {{ secret_type }}",
+ "result": {
+ "authentication": {
+ "ppk": {
+ "{{ ppk }}": {
+ "name": "{{ ppk }}",
+ "secret_type": "{{ secret_type }}",
+ },
+ },
+ },
+ },
+ },
+
+ # ---------------------------------------------------------------
+ # profile: remaining fields
+ # ---------------------------------------------------------------
+ {
+ "name": "profile.disable",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sprofile\s(?P<profile>\S+)
+ \sdisable
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec profile {{ name }} disable",
+ "result": {
+ "profile": {
+ "{{ profile }}": {
+ "name": "{{ profile }}",
+ "disable": True,
+ },
+ },
+ },
+ },
+
+ # ---------------------------------------------------------------
+ # top-level: interface, log, options, disable_uniqreqids
+ # ---------------------------------------------------------------
+ {
+ "name": "interface",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sinterface\s'?(?P<interface>\S+?)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec interface {{ interface }}",
+ "result": {
+ "interface": ["{{ interface }}"],
+ },
+ },
+ {
+ "name": "log.level",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\slog\slevel\s'?(?P<level>\d+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec log level {{ level }}",
+ "result": {
+ "log": {"level": "{{ level }}"},
+ },
+ },
+ {
+ "name": "log.subsystem",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\slog\ssubsystem\s'?(?P<subsystem>\S+?)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec log subsystem {{ subsystem }}",
+ "result": {
+ "log": {"subsystem": ["{{ subsystem }}"]},
+ },
+ },
+ {
+ "name": "options.disable_route_autoinstall",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\soptions\sdisable-route-autoinstall
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec options disable-route-autoinstall",
+ "result": {
+ "options": {"disable_route_autoinstall": True},
+ },
+ },
+ {
+ "name": "options.flexvpn",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\soptions\sflexvpn
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec options flexvpn",
+ "result": {
+ "options": {"flexvpn": True},
+ },
+ },
+ {
+ "name": "options.interface",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\soptions\sinterface\s'?(?P<interface>\S+?)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec options interface {{ interface }}",
+ "result": {
+ "options": {"interface": "{{ interface }}"},
+ },
+ },
+ {
+ "name": "options.retransmission.attempts",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\soptions\sretransmission\sattempts\s'?(?P<attempts>\d+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec options retransmission attempts {{ attempts }}",
+ "result": {
+ "options": {"retransmission": {"attempts": "{{ attempts }}"}},
+ },
+ },
+ {
+ "name": "options.retransmission.base",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\soptions\sretransmission\sbase\s'?(?P<base>[\d.]+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec options retransmission base {{ base }}",
+ "result": {
+ "options": {"retransmission": {"base": "{{ base }}"}},
+ },
+ },
+ {
+ "name": "options.retransmission.timeout",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\soptions\sretransmission\stimeout\s'?(?P<timeout>\d+)'?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec options retransmission timeout {{ timeout }}",
+ "result": {
+ "options": {"retransmission": {"timeout": "{{ timeout }}"}},
+ },
+ },
+ {
+ "name": "options.virtual_ip",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\soptions\svirtual-ip
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec options virtual-ip",
+ "result": {
+ "options": {"virtual_ip": True},
+ },
+ },
+ {
+ "name": "disable_uniqreqids",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\sdisable-uniqreqids
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "vpn ipsec disable-uniqreqids",
+ "result": {
+ "disable_uniqreqids": True,
+ },
+ },
+ ]
+ # fmt: on
diff --git a/plugins/module_utils/network/vyos/rm_templates/vpn_ipsec_s2s.py b/plugins/module_utils/network/vyos/rm_templates/vpn_ipsec_s2s.py
new file mode 100644
index 00000000..4319019c
--- /dev/null
+++ b/plugins/module_utils/network/vyos/rm_templates/vpn_ipsec_s2s.py
@@ -0,0 +1,670 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2026 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
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+class Vpn_ipsec_s2sTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ prefix = {"set": "set", "remove": "delete"}
+ super(Vpn_ipsec_s2sTemplate, self).__init__(
+ lines=lines,
+ tmplt=self,
+ module=module,
+ prefix=prefix,
+ )
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "peer",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }}",
+ "result": {
+ "site_to_site": {
+ "peer": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "peer.disable",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)\sdisable$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} disable",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "disable": True}}},
+ },
+ },
+ {
+ "name": "peer.authentication.local_id",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\slocal-id\s'(?P<local_id>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication local-id '{{ local_id }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"local_id": "{{ local_id }}"}}},
+ },
+ },
+ },
+ {
+ "name": "peer.authentication.remote_id",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\sremote-id\s'(?P<remote_id>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication remote-id '{{ remote_id }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"remote_id": "{{ remote_id }}"}}},
+ },
+ },
+ },
+ {
+ "name": "peer.authentication.mode",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\smode\s'(?P<mode>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication mode '{{ mode }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"mode": "{{ mode }}"}}},
+ },
+ },
+ },
+ {
+ "name": "peer.authentication.use_x509_id",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\suse-x509-id$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication use-x509-id",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"use_x509_id": True}}},
+ },
+ },
+ },
+ {
+ "name": "peer.authentication.ppk.id",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\sppk\sid\s'(?P<id>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication ppk id '{{ id }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"ppk": {"id": "{{ id }}"}}}},
+ },
+ },
+ },
+ {
+ "name": "peer.authentication.ppk.required",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\sppk\srequired$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication ppk required",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"ppk": {"required": True}}}},
+ },
+ },
+ },
+ {
+ "name": "peer.authentication.rsa.local_key",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\srsa\slocal-key\s'(?P<local_key>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication rsa local-key '{{ local_key }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"rsa": {"local_key": "{{ local_key }}"}}}},
+ },
+ },
+ },
+ {
+ "name": "peer.authentication.rsa.remote_key",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\srsa\sremote-key\s'(?P<remote_key>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication rsa remote-key '{{ remote_key }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"rsa": {"remote_key": "{{ remote_key }}"}}}},
+ },
+ },
+ },
+ {
+ "name": "peer.authentication.rsa.passphrase",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\srsa\spassphrase\s'(?P<passphrase>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication rsa passphrase '{{ passphrase }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"rsa": {"passphrase": "{{ passphrase }}"}}}},
+ },
+ },
+ },
+ {
+ "name": "peer.authentication.x509.certificate",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\sx509\scertificate\s'(?P<certificate>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication x509 certificate '{{ certificate }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"x509": {"certificate": "{{ certificate }}"}}}},
+ },
+ },
+ },
+ {
+ "name": "peer.authentication.x509.passphrase",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\sx509\spassphrase\s'(?P<passphrase>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication x509 passphrase '{{ passphrase }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"x509": {"passphrase": "{{ passphrase }}"}}}},
+ },
+ },
+ },
+ {
+ "name": "peer.authentication.x509.ca_certificate",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sauthentication\sx509\sca-certificate\s'(?P<ca_certificate>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} authentication x509 ca-certificate '{{ ca_certificate }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "authentication": {"x509": {"ca_certificate": ["{{ ca_certificate }}"]}}}},
+ },
+ },
+ },
+ {
+ "name": "peer.childless",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \schildless\s'(?P<childless>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} childless '{{ childless }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "childless": "{{ childless }}"}}},
+ },
+ },
+ {
+ "name": "peer.connection_type",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sconnection-type\s'(?P<connection_type>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} connection-type '{{ connection_type }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "connection_type": "{{ connection_type }}"}}},
+ },
+ },
+ {
+ "name": "peer.default_esp_group",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sdefault-esp-group\s'(?P<default_esp_group>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} default-esp-group '{{ default_esp_group }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "default_esp_group": "{{ default_esp_group }}"}}},
+ },
+ },
+ {
+ "name": "peer.description",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sdescription\s'(?P<description>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} description '{{ description }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "description": "{{ description }}"}}},
+ },
+ },
+ {
+ "name": "peer.dhcp_interface",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sdhcp-interface\s'(?P<dhcp_interface>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} dhcp-interface '{{ dhcp_interface }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "dhcp_interface": "{{ dhcp_interface }}"}}},
+ },
+ },
+ {
+ "name": "peer.force_udp_encapsulation",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sforce-udp-encapsulation$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} force-udp-encapsulation",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "force_udp_encapsulation": True}}},
+ },
+ },
+ {
+ "name": "peer.ike_group",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sike-group\s'(?P<ike_group>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} ike-group '{{ ike_group }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "ike_group": "{{ ike_group }}"}}},
+ },
+ },
+ {
+ "name": "peer.ikev2_reauth",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sikev2-reauth\s'(?P<ikev2_reauth>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} ikev2-reauth '{{ ikev2_reauth }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "ikev2_reauth": "{{ ikev2_reauth }}"}}},
+ },
+ },
+ {
+ "name": "peer.local_address",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \slocal-address\s'(?P<local_address>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} local-address '{{ local_address }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "local_address": "{{ local_address }}"}}},
+ },
+ },
+ {
+ "name": "peer.remote_address",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sremote-address\s'(?P<remote_address>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} remote-address '{{ remote_address }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "remote_address": ["{{ remote_address }}"]}}},
+ },
+ },
+ {
+ "name": "peer.replay_window",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \sreplay-window\s'(?P<replay_window>\d+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} replay-window '{{ replay_window }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "replay_window": "{{ replay_window }}"}}},
+ },
+ },
+ {
+ "name": "peer.virtual_address",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \svirtual-address\s'(?P<virtual_address>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} virtual-address '{{ virtual_address }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "virtual_address": ["{{ virtual_address }}"]}}},
+ },
+ },
+ {
+ "name": "peer.tunnel",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \stunnel\s(?P<tunnel_id>\d+)$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} tunnel {{ tunnel_id }}",
+ "result": {
+ "site_to_site": {
+ "peer": {"{{ name }}": {"name": "{{ name }}", "tunnel": {"{{ tunnel_id }}": {"tunnel_id": "{{ tunnel_id }}"}}}},
+ },
+ },
+ },
+ {
+ "name": "peer.tunnel.disable",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \stunnel\s(?P<tunnel_id>\d+)\sdisable$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} tunnel {{ tunnel_id }} disable",
+ "result": {
+ "site_to_site": {
+ "peer": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "tunnel": {
+ "{{ tunnel_id }}": {
+ "tunnel_id": "{{ tunnel_id }}",
+ "disable": True,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "peer.tunnel.esp_group",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \stunnel\s(?P<tunnel_id>\d+)\sesp-group\s'(?P<esp_group>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} tunnel {{ tunnel_id }} esp-group '{{ esp_group }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "tunnel": {
+ "{{ tunnel_id }}": {
+ "tunnel_id": "{{ tunnel_id }}",
+ "esp_group": "{{ esp_group }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "peer.tunnel.protocol",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \stunnel\s(?P<tunnel_id>\d+)\sprotocol\s'(?P<protocol>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} tunnel {{ tunnel_id }} protocol '{{ protocol }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "tunnel": {
+ "{{ tunnel_id }}": {
+ "tunnel_id": "{{ tunnel_id }}",
+ "protocol": "{{ protocol }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "peer.tunnel.priority",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \stunnel\s(?P<tunnel_id>\d+)\spriority\s'(?P<priority>\d+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} tunnel {{ tunnel_id }} priority '{{ priority }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "tunnel": {
+ "{{ tunnel_id }}": {
+ "tunnel_id": "{{ tunnel_id }}",
+ "priority": "{{ priority }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "peer.tunnel.local.port",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \stunnel\s(?P<tunnel_id>\d+)\slocal\sport\s'(?P<port>\d+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} tunnel {{ tunnel_id }} local port '{{ port }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "tunnel": {
+ "{{ tunnel_id }}": {
+ "tunnel_id": "{{ tunnel_id }}",
+ "local": {"port": "{{ port }}"},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "peer.tunnel.local.prefix",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \stunnel\s(?P<tunnel_id>\d+)\slocal\sprefix\s'(?P<prefix>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} tunnel {{ tunnel_id }} local prefix '{{ prefix }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "tunnel": {"{{ tunnel_id }}": {"tunnel_id": "{{ tunnel_id }}", "local": {"prefix": ["{{ prefix }}"]}}},
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "peer.tunnel.remote.port",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \stunnel\s(?P<tunnel_id>\d+)\sremote\sport\s'(?P<port>\d+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} tunnel {{ tunnel_id }} remote port '{{ port }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "tunnel": {
+ "{{ tunnel_id }}": {
+ "tunnel_id": "{{ tunnel_id }}",
+ "remote": {"port": "{{ port }}"},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "peer.tunnel.remote.prefix",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \stunnel\s(?P<tunnel_id>\d+)\sremote\sprefix\s'(?P<prefix>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} tunnel {{ tunnel_id }} remote prefix '{{ prefix }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "tunnel": {"{{ tunnel_id }}": {"tunnel_id": "{{ tunnel_id }}", "remote": {"prefix": ["{{ prefix }}"]}}},
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "peer.vti.bind",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \svti\sbind\s'(?P<bind>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} vti bind '{{ bind }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "vti": {"bind": "{{ bind }}"}}}},
+ },
+ },
+ {
+ "name": "peer.vti.esp_group",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \svti\sesp-group\s'(?P<esp_group>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} vti esp-group '{{ esp_group }}'",
+ "result": {
+ "site_to_site": {"peer": {"{{ name }}": {"name": "{{ name }}", "vti": {"esp_group": "{{ esp_group }}"}}}},
+ },
+ },
+ {
+ "name": "peer.vti.traffic_selector.local.prefix",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \svti\straffic-selector\slocal\sprefix\s'(?P<prefix>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} vti traffic-selector local prefix '{{ prefix }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "vti": {"traffic_selector": {"local": {"prefix": ["{{ prefix }}"]}}},
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "peer.vti.traffic_selector.remote.prefix",
+ "getval": re.compile(
+ r"""
+ ^set\svpn\sipsec\ssite-to-site\speer\s(?P<name>\S+)
+ \svti\straffic-selector\sremote\sprefix\s'(?P<prefix>[^']+)'$
+ """, re.VERBOSE,
+ ),
+ "setval": "vpn ipsec site-to-site peer {{ name }} vti traffic-selector remote prefix '{{ prefix }}'",
+ "result": {
+ "site_to_site": {
+ "peer": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "vti": {"traffic_selector": {"remote": {"prefix": ["{{ prefix }}"]}}},
+ },
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/plugins/module_utils/network/vyos/rm_templates/vrf.py b/plugins/module_utils/network/vyos/rm_templates/vrf.py
new file mode 100644
index 00000000..79928547
--- /dev/null
+++ b/plugins/module_utils/network/vyos/rm_templates/vrf.py
@@ -0,0 +1,248 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+"""
+The VRF parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+class VrfTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ prefix = {"set": "set", "remove": "delete"}
+ super(VrfTemplate, self).__init__(lines=lines, tmplt=self, prefix=prefix, module=module)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "table_id",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+vrf
+ \s+name
+ \s+(?P<name>\S+)
+ \s+table
+ \s+'(?P<tid>\S+)'
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "vrf name {{ name }} table {{ table_id }}",
+ "result": {
+ "name": "{{ name }}",
+ "table_id": "{{ tid }}",
+ },
+ },
+ {
+ "name": "bind_to_all",
+ "getval": re.compile(
+ r"""
+ ^set
+ \svrf
+ \s(?P<bta>bind-to-all)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "vrf bind-to-all",
+ "result": {
+ "bind_to_all": "{{ True if bta is defined }}",
+ },
+ },
+ {
+ "name": "vni",
+ "getval": re.compile(
+ r"""
+ ^set
+ \s+vrf
+ \s+name
+ \s+(?P<name>\S+)
+ \s+vni
+ \s'(?P<vni>\S+)'
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "vrf name {{name}} vni {{vni}}",
+ "result": {
+ "name": "{{ name }}",
+ "vni": "{{ vni }}",
+ },
+ },
+ {
+ "name": "description",
+ "getval": re.compile(
+ r"""
+ ^set
+ \svrf
+ \sname
+ \s(?P<name>\S+)
+ \sdescription
+ \s(?P<desc>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "vrf name {{name}} description {{description}}",
+ "result": {
+ "name": "{{ name }}",
+ "description": "{{ desc }}",
+ },
+ },
+ {
+ "name": "disable_vrf",
+ "getval": re.compile(
+ r"""
+ ^set
+ \svrf
+ \sname
+ \s(?P<name>\S+)
+ \s(?P<disable>disable)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "vrf name {{name}} disable",
+ "compval": "disable",
+ "result": {
+ "name": "{{ name }}",
+ "disable": "{{ True if disable is defined }}",
+ },
+ },
+ # {
+ # "name": "address_family",
+ # "getval": re.compile(
+ # r"""
+ # ^set
+ # \svrf
+ # \sname
+ # \s(?P<name>\S+)
+ # \s(?P<af>ip|ipv6)
+ # $""",
+ # re.VERBOSE,
+ # ),
+ # "setval": "vrf name {{name}} {{ af }}",
+ # 'compval': "address_family",
+ # "result": {
+ # "name": "{{ name }}",
+ # "address_family": {
+ # '{{ "ipv4" if af == "ip" else "ipv6" }}': {
+ # "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}',
+ # },
+ # },
+ # },
+ # },
+ # {
+ # "name": "address_family.disable_forwarding",
+ # "getval": re.compile(
+ # r"""
+ # ^set
+ # \svrf
+ # \sname
+ # \s(?P<name>\S+)
+ # \s(?P<af>ip|ipv6)
+ # \s(?P<df>disable-forwarding)
+ # $""",
+ # re.VERBOSE,
+ # ),
+ # "setval": "vrf name {{name}} {{ afi }} disable-forwarding",
+ # # "compval": "address_family.ipv6.disable_forwarding",
+ # "result": {
+ # "name": "{{ name }}",
+ # "address_family": {
+ # '{{ "ipv4" if af == "ip" else "ipv6" }}': {
+ # "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}',
+ # "disable_forwarding": "{{ True if df is defined }}",
+ # },
+ # },
+ # },
+ # },
+ {
+ "name": "disable_forwarding",
+ "getval": re.compile(
+ r"""
+ ^set
+ \svrf
+ \sname
+ \s(?P<name>\S+)
+ \s(?P<af>ip|ipv6)
+ \s(?P<df>disable-forwarding)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "vrf name {{name}} {{ afi }} disable-forwarding",
+ "compval": "disable_forwarding",
+ "result": {
+ "name": "{{ name }}",
+ 'address_family': [{
+ "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}',
+ "disable_forwarding": "{{ True if df is defined }}",
+ }],
+ },
+ },
+ {
+ "name": "disable_nht",
+ "getval": re.compile(
+ r"""
+ ^set
+ \svrf
+ \sname
+ \s(?P<name>\S+)
+ \s(?P<af>ip|ipv6)
+ \snht
+ \s(?P<nht>no-resolve-via-default)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "vrf name {{name}} {{ afi }} nht no-resolve-via-default",
+ "compval": "nht_no_resolve_via_default",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": [{
+ "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}',
+ "nht_no_resolve_via_default": "{{ True if nht is defined }}",
+ }],
+ },
+ },
+ {
+ "name": "route_maps",
+ "getval": re.compile(
+ r"""
+ ^set
+ \svrf
+ \sname
+ \s(?P<name>\S+)
+ \s(?P<af>ip|ipv6)
+ \sprotocol
+ \s(?P<proto>\S+)
+ \sroute-map
+ \s'(?P<rm>\S+)'
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "vrf name {{name}} {{ afi }} protocol {{ route_maps.protocol }} route-map {{ route_maps.rm_name }}",
+ "compval": "route_maps",
+ "remval": "vrf name {{name}} {{ afi }} protocol {{ route_maps.protocol }}",
+ "result": {
+ "name": "{{ name }}",
+ "address_family": [{
+ "afi": '{{ "ipv4" if af == "ip" else "ipv6" }}',
+ "route_maps": [{
+ "rm_name": "{{ rm }}",
+ "protocol": "{{ proto }}",
+ }],
+ }],
+ },
+ },
+ ]
+ # fmt: on
diff --git a/plugins/module_utils/network/vyos/utils/utils.py b/plugins/module_utils/network/vyos/utils/utils.py
index 8722251e..89f12773 100644
--- a/plugins/module_utils/network/vyos/utils/utils.py
+++ b/plugins/module_utils/network/vyos/utils/utils.py
@@ -6,11 +6,8 @@
# utils
from __future__ import absolute_import, division, print_function
-
__metaclass__ = type
from ansible.module_utils.basic import missing_required_lib
-from ansible.module_utils.six import iteritems
-
try:
import ipaddress
@@ -50,6 +47,18 @@ def get_interface_type(interface):
return "dummy"
+def get_interface_with_vif(interface):
+ """Gets virtual interface if any or return as is"""
+ vlan = None
+ interface_real = interface
+ if "." in interface:
+ interface_real, vlan = interface.split(".")
+
+ if vlan is not None:
+ interface_real = interface_real + " vif " + vlan
+ return interface_real
+
+
def dict_delete(base, comparable):
"""
This function generates a dict containing key, value pairs for keys
@@ -186,7 +195,7 @@ def key_value_in_dict(have_key, have_value, want_dict):
:param want_dict:
:return:
"""
- for key, value in iteritems(want_dict):
+ for key, value in want_dict.items():
if key == have_key and value == have_value:
return True
return False
@@ -258,9 +267,80 @@ def _is_w_same(w, h, key):
def _in_target(h, key):
"""
- This function checks whether the target exist and key present in target config.
+ This functi checks whether the target exist 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 in_target_not_none(h, key):
+ """
+ This function checks whether the target exist,key present in target config, and the value is not None.
+ :param h: target config.
+ :param key: attribute name.
+ :return: True/False.
+ """
+ return True if h and key in h and h[key] is not None else False
+
+
+def combine(a, b, recursive=False, list_merge="replace"):
+ """Merge dict ``b`` into dict ``a``, returning a new dict.
+
+ :param a: Base dictionary.
+ :param b: Dictionary whose values take precedence over ``a``.
+ :param recursive: When True, nested dicts are merged recursively rather
+ than replaced wholesale.
+ :param list_merge: Controls how list values are combined when the same key
+ exists in both dicts. Supported modes:
+
+ - ``"replace"`` *(default)* — ``b``'s list replaces ``a``'s list.
+ - ``"append"`` — ``b``'s list is appended to ``a``'s list (duplicates
+ kept).
+ - ``"prepend"`` — ``b``'s list is prepended to ``a``'s list
+ (duplicates kept).
+ - ``"append_rp"`` — like ``"append"`` but duplicates are removed,
+ preserving the first occurrence (rp = remove-preserve).
+ - ``"prepend_rp"`` — like ``"prepend"`` but duplicates are removed,
+ preserving the first occurrence.
+
+ Passing any other value raises ``ValueError``.
+ :returns: New merged dict.
+ :raises ValueError: If either argument is not a dict, or if an
+ unsupported ``list_merge`` mode is given.
+ """
+
+ if not isinstance(a, dict) or not isinstance(b, dict):
+ raise ValueError("combine expects two dictionaries")
+
+ result = a.copy()
+
+ for k, v in b.items():
+ if k in result:
+ # dict merge
+ if recursive and isinstance(result[k], dict) and isinstance(v, dict):
+ result[k] = combine(result[k], v, recursive=True, list_merge=list_merge)
+
+ # list merge
+ elif isinstance(result[k], list) and isinstance(v, list):
+ if list_merge == "replace":
+ result[k] = v
+ elif list_merge == "append":
+ result[k] = result[k] + v
+ elif list_merge == "prepend":
+ result[k] = v + result[k]
+ elif list_merge == "append_rp":
+ result[k] = list(dict.fromkeys(result[k] + v))
+ elif list_merge == "prepend_rp":
+ result[k] = list(dict.fromkeys(v + result[k]))
+ else:
+ raise ValueError(f"Unsupported list_merge mode: {list_merge}")
+
+ # everything else
+ else:
+ result[k] = v
+ else:
+ result[k] = v
+
+ return result
diff --git a/plugins/module_utils/network/vyos/utils/version.py b/plugins/module_utils/network/vyos/utils/version.py
index cc3028c3..6d84ef1c 100644
--- a/plugins/module_utils/network/vyos/utils/version.py
+++ b/plugins/module_utils/network/vyos/utils/version.py
@@ -7,6 +7,8 @@
"""Provide version object to compare version numbers."""
from __future__ import absolute_import, division, print_function
+
+
__metaclass__ = type
diff --git a/plugins/module_utils/network/vyos/vyos.py b/plugins/module_utils/network/vyos/vyos.py
index 1430b1b1..4983221e 100644
--- a/plugins/module_utils/network/vyos/vyos.py
+++ b/plugins/module_utils/network/vyos/vyos.py
@@ -34,6 +34,7 @@ import json
from ansible.module_utils._text import to_text
from ansible.module_utils.connection import Connection, ConnectionError
+
_DEVICE_CONFIGS = {}
@@ -68,8 +69,13 @@ def get_config(module, flags=None, format=None):
flags = [] if flags is None else flags
global _DEVICE_CONFIGS
- if _DEVICE_CONFIGS != {}:
- return _DEVICE_CONFIGS
+ # If _DEVICE_CONFIGS is non-empty and module.params["match"] is "none",
+ # return the cached device configurations. This avoids redundant calls
+ # to the connection when no specific match criteria are provided.
+ if _DEVICE_CONFIGS != {} and (
+ module.params["match"] is not None and module.params["match"] == "none"
+ ):
+ return to_text(_DEVICE_CONFIGS)
else:
connection = get_connection(module)
try:
@@ -81,6 +87,30 @@ def get_config(module, flags=None, format=None):
return cfg
+def copy_file(module, source, destination, proto="scp"):
+ """Copy a local file to the remote device over the existing network_cli
+ SSH session, using netcommon's generic connection-level file transfer
+ RPC (the same mechanism ansible.netcommon.net_put uses).
+
+ Requires the device to have SCP/SFTP reachable over the same SSH
+ session used for network_cli. Mirrors the calling convention of
+ cisco.iosxr's module_utils copy_file(module, source, destination, proto),
+ confirmed against cisco.iosxr's iosxr_config.py call site:
+ copy_file(module, src, dst, "sftp").
+ """
+ connection = get_connection(module)
+ try:
+ timeout = connection.get_option("persistent_command_timeout")
+ connection.copy_file(
+ source=source,
+ destination=destination,
+ proto=proto,
+ timeout=timeout,
+ )
+ except ConnectionError as exc:
+ module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+
+
def run_commands(module, commands, check_rc=True):
connection = get_connection(module)
try:
@@ -90,11 +120,16 @@ def run_commands(module, commands, check_rc=True):
return response
-def load_config(module, commands, commit=False, comment=None):
+def load_config(module, commands, commit=False, comment=None, confirm=None):
connection = get_connection(module)
try:
- response = connection.edit_config(candidate=commands, commit=commit, comment=comment)
+ response = connection.edit_config(
+ candidate=commands,
+ commit=commit,
+ comment=comment,
+ confirm=confirm,
+ )
except ConnectionError as exc:
module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
diff --git a/plugins/module_utils/network/vyos/vyos_file.py b/plugins/module_utils/network/vyos/vyos_file.py
new file mode 100644
index 00000000..2b946c20
--- /dev/null
+++ b/plugins/module_utils/network/vyos/vyos_file.py
@@ -0,0 +1,99 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2026, VyOS maintainers and contributors
+# 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
+
+import hashlib
+import re
+
+
+STAT_RE = re.compile(r"^(?P<mode>\d+)\s+(?P<owner>\S+)\s+(?P<group>\S+)\s+(?P<size>\d+)$")
+
+
+def parse_stat(output):
+ """Parse `stat --format='%a %U %G %s' <path>` output.
+ Returns None if the path doesn't exist (caller checks rc/stderr first).
+ """
+ m = STAT_RE.match(output.strip())
+ if not m:
+ return None
+ d = m.groupdict()
+ return {
+ "mode": d["mode"].zfill(4)[-4:],
+ "owner": d["owner"],
+ "group": d["group"],
+ "size": int(d["size"]),
+ }
+
+
+def _normalize_mode(mode):
+ if mode is None:
+ return None
+ return str(mode).zfill(4)[-4:]
+
+
+def build_want(params, local_content_hash=None):
+ return {
+ "dest": params["dest"],
+ "state": params.get("state", "present"),
+ "owner": params.get("owner"),
+ "group": params.get("group"),
+ "mode": _normalize_mode(params.get("mode")),
+ "content_hash": local_content_hash,
+ }
+
+
+def diff_want_have(want, have):
+ """Returns dict of {field: (have_val, want_val)} for fields that differ.
+ Identity is `dest`, not a config-tree path — this compares a stat-shaped
+ dict, not config lines.
+ """
+ diff = {}
+ if want["state"] == "absent":
+ if have is not None:
+ diff["state"] = (have, "absent")
+ return diff
+
+ if have is None:
+ diff["state"] = (None, "present")
+ for f in ("owner", "group", "mode"):
+ if want.get(f) is not None:
+ diff[f] = (None, want[f])
+ if want.get("content_hash"):
+ diff["content"] = (None, want["content_hash"])
+ return diff
+
+ for f in ("owner", "group"):
+ if want.get(f) is not None and want[f] != have.get(f):
+ diff[f] = (have.get(f), want[f])
+
+ if want.get("mode") is not None:
+ want_mode = want["mode"]
+ have_mode = have.get("mode")
+ if want_mode[0] == "0":
+ # Caller didn't request specific setuid/setgid/sticky bits —
+ # don't fight VyOS's own conventions (e.g. /config/auth is
+ # deliberately setgid vyattacfg; see vyos.dev T2713). Compare
+ # only the rwx digits unless the caller explicitly asked for a
+ # non-zero leading digit.
+ if want_mode[-3:] != have_mode[-3:]:
+ diff["mode"] = (have_mode, want_mode)
+ elif want_mode != have_mode:
+ diff["mode"] = (have_mode, want_mode)
+
+ if want.get("content_hash") and want["content_hash"] != have.get("content_hash"):
+ diff["content"] = (have.get("content_hash"), want["content_hash"])
+
+ return diff
+
+
+def local_sha256(path):
+ h = hashlib.sha256()
+ with open(path, "rb") as f:
+ for chunk in iter(lambda: f.read(65536), b""):
+ h.update(chunk)
+ return h.hexdigest()