From 7fec0b69a082aaeec72fee0612766aa42f6b1b4d Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 18 Apr 2024 07:52:35 +0200 Subject: Merging upstream version 9.4.0+dfsg. Signed-off-by: Daniel Baumann --- .../bgp_address_family/bgp_address_family.py | 18 +- .../network/ios/argspec/bgp_global/bgp_global.py | 1 + .../network/ios/argspec/evpn_evi/__init__.py | 0 .../network/ios/argspec/evpn_evi/evpn_evi.py | 91 + .../network/ios/argspec/evpn_global/__init__.py | 0 .../network/ios/argspec/evpn_global/evpn_global.py | 93 + .../module_utils/network/ios/argspec/lacp/lacp.py | 10 +- .../network/ios/argspec/lldp_global/lldp_global.py | 10 +- .../module_utils/network/ios/argspec/ping/ping.py | 1 + .../network/ios/argspec/service/service.py | 1 + .../network/ios/argspec/snmp_server/snmp_server.py | 440 ++++- .../network/ios/argspec/vlans/vlans.py | 8 + .../network/ios/argspec/vxlan_vtep/__init__.py | 0 .../network/ios/argspec/vxlan_vtep/vxlan_vtep.py | 102 ++ .../module_utils/network/ios/config/acls/acls.py | 105 +- .../bgp_address_family/bgp_address_family.py | 10 +- .../network/ios/config/bgp_global/bgp_global.py | 2 +- .../network/ios/config/evpn_evi/__init__.py | 0 .../network/ios/config/evpn_evi/evpn_evi.py | 103 ++ .../network/ios/config/evpn_global/__init__.py | 0 .../network/ios/config/evpn_global/evpn_global.py | 98 + .../module_utils/network/ios/config/lacp/lacp.py | 4 +- .../network/ios/config/lldp_global/lldp_global.py | 8 +- .../ios/config/logging_global/logging_global.py | 2 +- .../network/ios/config/ospfv2/ospfv2.py | 278 +-- .../module_utils/network/ios/config/ping/ping.py | 2 +- .../ios/config/prefix_lists/prefix_lists.py | 216 +-- .../network/ios/config/service/service.py | 6 +- .../network/ios/config/snmp_server/snmp_server.py | 179 +- .../ios/config/static_routes/static_routes.py | 4 +- .../module_utils/network/ios/config/vlans/vlans.py | 194 +- .../network/ios/config/vxlan_vtep/__init__.py | 0 .../network/ios/config/vxlan_vtep/vxlan_vtep.py | 190 ++ .../module_utils/network/ios/facts/acls/acls.py | 90 +- .../network/ios/facts/bgp_global/bgp_global.py | 11 +- .../network/ios/facts/evpn_evi/__init__.py | 0 .../network/ios/facts/evpn_evi/evpn_evi.py | 67 + .../network/ios/facts/evpn_global/__init__.py | 0 .../network/ios/facts/evpn_global/evpn_global.py | 68 + .../module_utils/network/ios/facts/facts.py | 12 + .../module_utils/network/ios/facts/legacy/base.py | 48 +- .../ios/facts/logging_global/logging_global.py | 8 +- .../network/ios/facts/ospfv2/ospfv2.py | 63 +- .../network/ios/facts/prefix_lists/prefix_lists.py | 55 +- .../network/ios/facts/snmp_server/snmp_server.py | 37 +- .../module_utils/network/ios/facts/vlans/vlans.py | 223 ++- .../network/ios/facts/vxlan_vtep/__init__.py | 0 .../network/ios/facts/vxlan_vtep/vxlan_vtep.py | 67 + .../module_utils/network/ios/rm_templates/acls.py | 158 +- .../network/ios/rm_templates/bgp_address_family.py | 28 +- .../network/ios/rm_templates/bgp_global.py | 363 +++- .../network/ios/rm_templates/evpn_evi.py | 121 ++ .../network/ios/rm_templates/evpn_global.py | 115 ++ .../network/ios/rm_templates/ospfv2.py | 1541 ++++++++-------- .../network/ios/rm_templates/ospfv3.py | 16 +- .../module_utils/network/ios/rm_templates/ping.py | 1 + .../network/ios/rm_templates/prefix_lists.py | 84 +- .../network/ios/rm_templates/route_maps.py | 33 +- .../network/ios/rm_templates/snmp_server.py | 1924 +++++++++++++++----- .../network/ios/rm_templates/static_routes.py | 4 +- .../network/ios/rm_templates/vxlan_vtep.py | 131 ++ .../module_utils/network/ios/utils/utils.py | 17 +- 62 files changed, 5336 insertions(+), 2125 deletions(-) create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_evi/__init__.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_evi/evpn_evi.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_global/__init__.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_global/evpn_global.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vxlan_vtep/__init__.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vxlan_vtep/vxlan_vtep.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_evi/__init__.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_evi/evpn_evi.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_global/__init__.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_global/evpn_global.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vxlan_vtep/__init__.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vxlan_vtep/vxlan_vtep.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_evi/__init__.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_evi/evpn_evi.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_global/__init__.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_global/evpn_global.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vxlan_vtep/__init__.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vxlan_vtep/vxlan_vtep.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/evpn_evi.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/evpn_global.py create mode 100644 ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/vxlan_vtep.py (limited to 'ansible_collections/cisco/ios/plugins/module_utils') diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_address_family/bgp_address_family.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_address_family/bgp_address_family.py index a52e3787c..18fed7891 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_address_family/bgp_address_family.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_address_family/bgp_address_family.py @@ -340,7 +340,7 @@ class Bgp_address_familyArgs(object): # pylint: disable=R0903 "type": "dict", "options": { "set": {"type": "bool"}, - "number": {"type": "int"}, + "number": {"type": "str"}, "dual_as": {"type": "bool"}, "no_prepend": { "type": "dict", @@ -367,7 +367,6 @@ class Bgp_address_familyArgs(object): # pylint: disable=R0903 "warning_only": {"type": "bool"}, }, }, - "next_hop_self": {"type": "bool"}, "nexthop_self": { "type": "dict", "options": {"set": {"type": "bool"}, "all": {"type": "bool"}}, @@ -440,7 +439,7 @@ class Bgp_address_familyArgs(object): # pylint: disable=R0903 "out": {"type": "bool"}, }, }, - "remote_as": {"type": "int"}, + "remote_as": {"type": "str"}, "remove_private_as": { "type": "dict", "options": { @@ -814,6 +813,19 @@ class Bgp_address_familyArgs(object): # pylint: disable=R0903 "type": "dict", "options": {"name": {"type": "str"}, "filter": {"type": "bool"}}, }, + "advertise": { + "type": "dict", + "options": { + "afi": { + "type": "str", + "choices": ["l2vpn"], + }, + "safi": { + "type": "str", + "choices": ["evpn"], + }, + }, + }, }, }, }, diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_global/bgp_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_global/bgp_global.py index 38c4e1ab3..21bb051c3 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_global/bgp_global.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/bgp_global/bgp_global.py @@ -940,6 +940,7 @@ class Bgp_globalArgs(object): # pylint: disable=R0903 "choices": [ "merged", "replaced", + "overridden", "deleted", "purged", "gathered", diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_evi/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_evi/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_evi/evpn_evi.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_evi/evpn_evi.py new file mode 100644 index 000000000..021e1ed81 --- /dev/null +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_evi/evpn_evi.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- +# Copyright 2023 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 +# ansible.content_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the documentation in the module file and re-run +# ansible.content_builder commenting out +# the path to external 'docstring' in build.yaml. +# +############################################## + +""" +The arg spec for the ios_evpn_evi module +""" + + +class Evpn_eviArgs(object): # pylint: disable=R0903 + """The arg spec for the ios_evpn_evi module""" + + argument_spec = { + "config": { + "type": "list", + "elements": "dict", + "options": { + "evi": {"type": "int", "required": True}, + "default_gateway": { + "type": "dict", + "options": { + "advertise": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "disable": {"type": "bool"}, + }, + }, + }, + }, + "ip": { + "type": "dict", + "options": { + "local_learning": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "disable": {"type": "bool"}, + }, + }, + }, + }, + "encapsulation": { + "type": "str", + "choices": ["vxlan"], + "default": "vxlan", + }, + "replication_type": { + "type": "str", + "choices": ["ingress", "static"], + }, + "route_distinguisher": {"type": "str"}, + }, + }, + "running_config": {"type": "str"}, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_global/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_global/evpn_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_global/evpn_global.py new file mode 100644 index 000000000..acdb0c759 --- /dev/null +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/evpn_global/evpn_global.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# Copyright 2023 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 +# ansible.content_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the documentation in the module file and re-run +# ansible.content_builder commenting out +# the path to external 'docstring' in build.yaml. +# +############################################## + +""" +The arg spec for the ios_evpn_global module +""" + + +class Evpn_globalArgs(object): # pylint: disable=R0903 + """The arg spec for the ios_evpn_global module""" + + argument_spec = { + "config": { + "type": "dict", + "options": { + "default_gateway": { + "type": "dict", + "options": {"advertise": {"type": "bool"}}, + }, + "flooding_suppression": { + "type": "dict", + "options": { + "address_resolution": { + "type": "dict", + "options": {"disable": {"type": "bool"}}, + }, + }, + }, + "ip": { + "type": "dict", + "options": { + "local_learning": { + "type": "dict", + "options": {"disable": {"type": "bool"}}, + }, + }, + }, + "replication_type": { + "type": "str", + "choices": ["ingress", "static"], + }, + "route_target": { + "type": "dict", + "options": { + "auto": { + "type": "dict", + "options": {"vni": {"type": "bool"}}, + }, + }, + }, + "router_id": { + "type": "str", + }, + }, + }, + "running_config": {"type": "str"}, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "gathered", + "parsed", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/lacp/lacp.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/lacp/lacp.py index 3e346db12..dff90d45f 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/lacp/lacp.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/lacp/lacp.py @@ -50,7 +50,15 @@ class LacpArgs(object): }, "running_config": {"type": "str"}, "state": { - "choices": ["merged", "replaced", "deleted", "rendered", "parsed", "gathered"], + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "parsed", + "gathered", + ], "default": "merged", "type": "str", }, diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/lldp_global/lldp_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/lldp_global/lldp_global.py index 841e34bed..f7b699826 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/lldp_global/lldp_global.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/lldp_global/lldp_global.py @@ -61,7 +61,15 @@ class Lldp_globalArgs(object): }, "running_config": {"type": "str"}, "state": { - "choices": ["merged", "replaced", "deleted", "rendered", "parsed", "gathered"], + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "parsed", + "gathered", + ], "default": "merged", "type": "str", }, diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/ping/ping.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/ping/ping.py index f89779d06..8de060b50 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/ping/ping.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/ping/ping.py @@ -37,6 +37,7 @@ class PingArgs(object): # pylint: disable=R0903 "dest": {"required": True, "type": "str"}, "df_bit": {"default": False, "type": "bool"}, "source": {"type": "str"}, + "size": {"type": "int"}, "ingress": {"type": "str"}, "egress": {"type": "str"}, "timeout": {"type": "int"}, diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/service/service.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/service/service.py index 76d2a3e34..238fc64a2 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/service/service.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/service/service.py @@ -110,6 +110,7 @@ class ServiceArgs(object): # pylint: disable=R0903 "choices": [ "merged", "replaced", + "overridden", "deleted", "gathered", "rendered", diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/snmp_server/snmp_server.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/snmp_server/snmp_server.py index d0473cc97..446a8225b 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/snmp_server/snmp_server.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/snmp_server/snmp_server.py @@ -13,15 +13,16 @@ __metaclass__ = type ############################################# # # This file is auto generated by the -# cli_rm_builder. +# ansible.content_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. +# in the documentation in the module file and re-run +# ansible.content_builder commenting out +# the path to external 'docstring' in build.yaml. # -############################################# +############################################## """ The arg spec for the ios_snmp_server module @@ -34,7 +35,10 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 argument_spec = { "config": { "options": { - "accounting": {"options": {"command": {"type": "str"}}, "type": "dict"}, + "accounting": { + "options": {"command": {"type": "str"}}, + "type": "dict", + }, "cache": {"type": "int"}, "chassis_id": {"type": "str"}, "communities": { @@ -52,7 +56,10 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 "contact": {"type": "str"}, "context": {"elements": "str", "type": "list"}, "drop": { - "options": {"unknown_user": {"type": "bool"}, "vrf_traffic": {"type": "bool"}}, + "options": { + "unknown_user": {"type": "bool"}, + "vrf_traffic": {"type": "bool"}, + }, "type": "dict", }, "engine_id": { @@ -82,7 +89,11 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 "elements": "dict", "options": { "context": {"type": "str"}, - "version_option": {"choices": ["auth", "noauth", "priv"], "type": "str"}, + "match": {"choices": ["exact", "prefix"], "type": "str"}, + "version_option": { + "choices": ["auth", "noauth", "priv"], + "type": "str", + }, "group": {"type": "str"}, "notify": {"type": "str"}, "read": {"type": "str"}, @@ -101,7 +112,10 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 "community_string": {"type": "str"}, "traps": {"type": "list", "elements": "str"}, "version": {"choices": ["1", "2c", "3"], "type": "str"}, - "version_option": {"choices": ["auth", "noauth", "priv"], "type": "str"}, + "version_option": { + "choices": ["auth", "noauth", "priv"], + "type": "str", + }, "vrf": {"type": "str"}, }, "type": "list", @@ -116,7 +130,10 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 "type": "dict", }, "ip": { - "options": {"dscp": {"type": "int"}, "precedence": {"type": "int"}}, + "options": { + "dscp": {"type": "int"}, + "precedence": {"type": "int"}, + }, "type": "dict", }, "location": {"type": "str"}, @@ -145,6 +162,7 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 "trap_timeout": {"type": "int"}, "traps": { "options": { + "aaa_server": {"type": "bool"}, "auth_framework": { "options": { "sec_violation": {"type": "bool"}, @@ -188,6 +206,22 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 }, "type": "dict", }, + "bulkstat": { + "options": { + "enable": {"type": "bool"}, + "collection": {"type": "bool"}, + "transfer": {"type": "bool"}, + }, + "type": "dict", + }, + "call_home": { + "options": { + "enable": {"type": "bool"}, + "message_send_fail": {"type": "bool"}, + "server_fail": {"type": "bool"}, + }, + "type": "dict", + }, "casa": {"type": "bool"}, "cef": { "options": { @@ -204,7 +238,10 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 "config_copy": {"type": "bool"}, "config_ctid": {"type": "bool"}, "cpu": { - "options": {"enable": {"type": "bool"}, "threshold": {"type": "bool"}}, + "options": { + "enable": {"type": "bool"}, + "threshold": {"type": "bool"}, + }, "type": "dict", }, "dhcp": {"type": "bool"}, @@ -218,19 +255,39 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 }, "eigrp": {"type": "bool"}, "entity": {"type": "bool"}, + "entity_diag": { + "options": { + "boot_up_fail": {"type": "bool"}, + "enable": {"type": "bool"}, + "hm_test_recover": {"type": "bool"}, + "hm_thresh_reached": {"type": "bool"}, + "scheduled_test_fail": {"type": "bool"}, + }, + "type": "dict", + }, + "entity_perf": { + "options": { + "enable": {"type": "bool"}, + "throughput_notif": {"type": "bool"}, + }, + "type": "dict", + }, + "entity_state": {"type": "bool"}, "energywise": {"type": "bool"}, "envmon": { "options": { + "enable": {"type": "bool"}, "fan": { "options": { - "shutdown": {"type": "bool"}, "enable": {"type": "bool"}, + "shutdown": {"type": "bool"}, "status": {"type": "bool"}, "supply": {"type": "bool"}, "temperature": {"type": "bool"}, }, "type": "dict", }, + "fan_enable": {"type": "bool"}, "shutdown": {"type": "bool"}, "status": {"type": "bool"}, "supply": {"type": "bool"}, @@ -238,6 +295,7 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 }, "type": "dict", }, + "errdisable": {"type": "bool"}, "ethernet": { "options": { "cfm": { @@ -275,8 +333,24 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 }, "type": "dict", }, + "ether_oam": {"type": "bool"}, "event_manager": {"type": "bool"}, - "flowmon": {"type": "bool"}, + "flash": { + "options": { + "enable": {"type": "bool"}, + "insertion": {"type": "bool"}, + "lowspace": {"type": "bool"}, + "removal": {"type": "bool"}, + }, + "type": "dict", + }, + "flex_links": { + "options": { + "enable": {"type": "bool"}, + "status": {"type": "bool"}, + }, + "type": "dict", + }, "firewall": { "options": { "enable": {"type": "bool"}, @@ -284,6 +358,7 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 }, "type": "dict", }, + "flowmon": {"type": "bool"}, "frame_relay": { "options": { "enable": {"type": "bool"}, @@ -343,6 +418,15 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 "type": "dict", }, "ipsla": {"type": "bool"}, + "isis": {"type": "bool"}, + "l2tc": { + "options": { + "enable": {"type": "bool"}, + "sys_threshold": {"type": "bool"}, + "threshold": {"type": "bool"}, + }, + "type": "dict", + }, "l2tun": { "options": { "pseudowire_status": {"type": "bool"}, @@ -350,9 +434,141 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 }, "type": "dict", }, + "license": {"type": "bool"}, + "lisp": {"type": "bool"}, + "local_auth": {"type": "bool"}, + "mac_notification": { + "options": { + "enable": {"type": "bool"}, + "change": {"type": "bool"}, + "move": {"type": "bool"}, + "threshold": {"type": "bool"}, + }, + "type": "dict", + }, + "memory": { + "options": { + "enable": {"type": "bool"}, + "bufferpeak": {"type": "bool"}, + }, + "type": "dict", + }, + "mpls": { + "options": { + "fast_reroute": { + "options": { + "enable": {"type": "bool"}, + "protected": {"type": "bool"}, + }, + "type": "dict", + }, + "ldp": { + "options": { + "enable": {"type": "bool"}, + "pv_limit": {"type": "bool"}, + "session_down": {"type": "bool"}, + "session_up": {"type": "bool"}, + "threshold": {"type": "bool"}, + }, + "type": "dict", + }, + "rfc": { + "options": { + "ldp": { + "options": { + "enable": {"type": "bool"}, + "pv_limit": {"type": "bool"}, + "session_down": {"type": "bool"}, + "session_up": {"type": "bool"}, + "threshold": {"type": "bool"}, + }, + "type": "dict", + }, + "traffic_eng": { + "options": { + "enable": {"type": "bool"}, + "down": {"type": "bool"}, + "reoptimized": {"type": "bool"}, + "reroute": {"type": "bool"}, + "up": {"type": "bool"}, + }, + "type": "dict", + }, + "vpn": { + "options": { + "enable": {"type": "bool"}, + "illegal_label": {"type": "bool"}, + "max_thresh_cleared": { + "type": "bool", + }, + "max_threshold": {"type": "bool"}, + "mid_threshold": {"type": "bool"}, + "vrf_down": {"type": "bool"}, + "vrf_up": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "traffic_eng": { + "options": { + "enable": {"type": "bool"}, + "down": {"type": "bool"}, + "reroute": {"type": "bool"}, + "up": {"type": "bool"}, + }, + "type": "dict", + }, + "vpn": { + "options": { + "enable": {"type": "bool"}, + "illegal_label": {"type": "bool"}, + "max_thresh_cleared": {"type": "bool"}, + "max_threshold": {"type": "bool"}, + "mid_threshold": {"type": "bool"}, + "vrf_down": {"type": "bool"}, + "vrf_up": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "mpls_vpn": {"type": "bool"}, "msdp": {"type": "bool"}, "mvpn": {"type": "bool"}, - "mpls_vpn": {"type": "bool"}, + "nhrp": { + "options": { + "enable": {"type": "bool"}, + "nhc": { + "options": { + "enable": {"type": "bool"}, + "down": {"type": "bool"}, + "up": {"type": "bool"}, + }, + "type": "dict", + }, + "nhp": { + "options": { + "enable": {"type": "bool"}, + "down": {"type": "bool"}, + "up": {"type": "bool"}, + }, + "type": "dict", + }, + "nhs": { + "options": { + "enable": {"type": "bool"}, + "down": {"type": "bool"}, + "up": {"type": "bool"}, + }, + "type": "dict", + }, + "quota_exceeded": {"type": "bool"}, + }, + "type": "dict", + }, "ospf": { "options": { "cisco_specific": { @@ -362,11 +578,17 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 "retransmit": {"type": "bool"}, "state_change": { "options": { - "nssa_trans_change": {"type": "bool"}, + "nssa_trans_change": { + "type": "bool", + }, "shamlink": { "options": { - "interface": {"type": "bool"}, - "neighbor": {"type": "bool"}, + "interface": { + "type": "bool", + }, + "neighbor": { + "type": "bool", + }, }, "type": "dict", }, @@ -383,6 +605,42 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 }, "type": "dict", }, + "ospfv3": { + "options": { + "errors": { + "options": { + "enable": {"type": "bool"}, + "bad_packet": {"type": "bool"}, + "config_error": {"type": "bool"}, + "virt_bad_packet": {"type": "bool"}, + "virt_config_error": {"type": "bool"}, + }, + "type": "dict", + }, + "rate_limit": {"type": "int"}, + "state_change": { + "options": { + "enable": {"type": "bool"}, + "if_state_change": {"type": "bool"}, + "neighbor_restart_helper_status_change": { + "type": "bool", + }, + "neighbor_state_change": {"type": "bool"}, + "nssa_translator_status_change": { + "type": "bool", + }, + "restart_status_change": {"type": "bool"}, + "virtif_state_change": {"type": "bool"}, + "vn_restart_helper_status_change": { + "type": "bool", + }, + "vn_state_change": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, "pim": { "options": { "invalid_pim_message": {"type": "bool"}, @@ -392,19 +650,35 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 }, "type": "dict", }, - "vrfmib": { + "pki": {"type": "bool"}, + "port_security": {"type": "bool"}, + "power_ethernet": { "options": { - "vrf_up": {"type": "bool"}, - "vrf_down": {"type": "bool"}, - "vnet_trunk_up": {"type": "bool"}, - "vnet_trunk_down": {"type": "bool"}, + "enable": {"type": "bool"}, + "group": { + "options": { + "slot_id": {"type": "int"}, + "threshold": {"type": "int"}, + }, + "elements": "dict", + "type": "list", + }, + "police": {"type": "bool"}, }, "type": "dict", }, - "pki": {"type": "bool"}, - "rsvp": {"type": "bool"}, - "isis": {"type": "bool"}, "pw_vc": {"type": "bool"}, + "rep": {"type": "bool"}, + "rsvp": {"type": "bool"}, + "rf": {"type": "bool"}, + "smart_license": { + "options": { + "enable": {"type": "bool"}, + "entitlement": {"type": "bool"}, + "global": {"type": "bool"}, + }, + "type": "dict", + }, "snmp": { "options": { "authentication": {"type": "bool"}, @@ -415,10 +689,103 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 }, "type": "dict", }, + "stackwise": {"type": "bool"}, + "stpx": { + "options": { + "enable": {"type": "bool"}, + "inconsistency": {"type": "bool"}, + "loop_inconsistency": {"type": "bool"}, + "root_inconsistency": {"type": "bool"}, + }, + "type": "dict", + }, "syslog": {"type": "bool"}, "transceiver_all": {"type": "bool"}, + "trustsec": { + "options": { + "authz_file_error": {"type": "bool"}, + "cache_file_error": {"type": "bool"}, + "enable": {"type": "bool"}, + "keystore_file_error": {"type": "bool"}, + "keystore_sync_fail": {"type": "bool"}, + "random_number_fail": {"type": "bool"}, + "src_entropy_fail": {"type": "bool"}, + }, + "type": "dict", + }, + "trustsec_interface": { + "options": { + "enable": {"type": "bool"}, + "authc_fail": {"type": "bool"}, + "authz_fail": {"type": "bool"}, + "sap_fail": {"type": "bool"}, + "supplicant_fail": {"type": "bool"}, + "unauthorized": {"type": "bool"}, + }, + "type": "dict", + }, + "trustsec_policy": { + "options": { + "enable": {"type": "bool"}, + "authz_sgacl_fail": {"type": "bool"}, + "peer_policy_updated": {"type": "bool"}, + }, + "type": "dict", + }, + "trustsec_server": { + "options": { + "enable": {"type": "bool"}, + "provision_secret": {"type": "bool"}, + "radius_server": {"type": "bool"}, + }, + "type": "dict", + }, + "trustsec_sxp": { + "options": { + "enable": {"type": "bool"}, + "binding_conflict": {"type": "bool"}, + "binding_err": {"type": "bool"}, + "binding_expn_fail": {"type": "bool"}, + "conn_config_err": {"type": "bool"}, + "conn_down": {"type": "bool"}, + "conn_srcaddr_err": {"type": "bool"}, + "conn_up": {"type": "bool"}, + "msg_parse_err": {"type": "bool"}, + "oper_nodeid_change": {"type": "bool"}, + }, + "type": "dict", + }, "tty": {"type": "bool"}, + "udld": { + "options": { + "enable": {"type": "bool"}, + "link_fail_rpt": {"type": "bool"}, + "status_change": {"type": "bool"}, + }, + "type": "dict", + }, + "vlan_membership": {"type": "bool"}, + "vlancreate": {"type": "bool"}, + "vlandelete": {"type": "bool"}, + "vrfmib": { + "options": { + "vrf_up": {"type": "bool"}, + "vrf_down": {"type": "bool"}, + "vnet_trunk_up": {"type": "bool"}, + "vnet_trunk_down": {"type": "bool"}, + }, + "type": "dict", + }, "vrrp": {"type": "bool"}, + "vswitch": { + "options": { + "dual_active": {"type": "bool"}, + "enable": {"type": "bool"}, + "vsl": {"type": "bool"}, + }, + "type": "dict", + }, + "vtp": {"type": "bool"}, }, "type": "dict", }, @@ -431,17 +798,29 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 "no_log": False, "type": "dict", "options": { - "algorithm": {"type": "str", "choices": ["md5", "sha"]}, - "password": {"type": "str", "no_log": True}, + "algorithm": { + "type": "str", + "choices": ["md5", "sha"], + }, + "password": { + "type": "str", + "no_log": True, + }, }, }, "encryption": { "no_log": False, "type": "dict", "options": { - "priv": {"type": "str", "choices": ["3des", "aes", "des"]}, + "priv": { + "type": "str", + "choices": ["3des", "aes", "des"], + }, "priv_option": {"type": "str"}, - "password": {"type": "str", "no_log": True}, + "password": { + "type": "str", + "no_log": True, + }, }, }, "group": {"type": "str"}, @@ -449,7 +828,10 @@ class Snmp_serverArgs(object): # pylint: disable=R0903 "udp_port": {"type": "int"}, "username": {"type": "str"}, "version": {"choices": ["v1", "v2c", "v3"], "type": "str"}, - "version_option": {"choices": ["encrypted"], "type": "str"}, + "version_option": { + "choices": ["encrypted"], + "type": "str", + }, "vrf": {"type": "str"}, }, "type": "list", diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vlans/vlans.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vlans/vlans.py index a3b0b6acd..9db593dcc 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vlans/vlans.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vlans/vlans.py @@ -55,9 +55,17 @@ class VlansArgs(object): "associated": {"type": "list", "elements": "int"}, }, }, + "member": { + "type": "dict", + "options": { + "vni": {"type": "int", "required": True}, + "evi": {"type": "int"}, + }, + }, }, "type": "list", }, + "configuration": {"type": "bool"}, "running_config": {"type": "str"}, "state": { "choices": [ diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vxlan_vtep/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vxlan_vtep/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vxlan_vtep/vxlan_vtep.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vxlan_vtep/vxlan_vtep.py new file mode 100644 index 000000000..b5eb35c07 --- /dev/null +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/argspec/vxlan_vtep/vxlan_vtep.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- +# Copyright 2023 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 +# ansible.content_builder. +# +# Manually editing this file is not advised. +# +# To update the argspec make the desired changes +# in the documentation in the module file and re-run +# ansible.content_builder commenting out +# the path to external 'docstring' in build.yaml. +# +############################################## + +""" +The arg spec for the ios_vxlan_vtep module +""" + + +class Vxlan_vtepArgs(object): # pylint: disable=R0903 + """The arg spec for the ios_vxlan_vtep module""" + + argument_spec = { + "config": { + "type": "list", + "elements": "dict", + "options": { + "interface": {"type": "str", "required": True}, + "source_interface": {"type": "str"}, + "host_reachability_bgp": { + "type": "bool", + }, + "member": { + "type": "dict", + "options": { + "vni": { + "type": "dict", + "options": { + "l2vni": { + "type": "list", + "elements": "dict", + "options": { + "vni": {"type": "int"}, + "replication": { + "type": "dict", + "options": { + "type": { + "type": "str", + "choices": ["ingress", "static"], + }, + "mcast_group": { + "type": "dict", + "options": { + "ipv4": {"type": "str"}, + "ipv6": {"type": "str"}, + }, + }, + }, + }, + }, + }, + "l3vni": { + "type": "list", + "elements": "dict", + "options": { + "vni": {"type": "int"}, + "vrf": {"type": "str"}, + }, + }, + }, + }, + }, + }, + }, + }, + "running_config": {"type": "str"}, + "state": { + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "rendered", + "gathered", + "parsed", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/acls/acls.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/acls/acls.py index 9d29555cc..164c75c40 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/acls/acls.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/acls/acls.py @@ -15,6 +15,7 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +from ansible.module_utils._text import to_text from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( ResourceModule, @@ -149,11 +150,28 @@ class Acls(ResourceModule): entry["afi"] = afi return entry + def pop_remark(r_entry, afi): + """Takes out remarks from ace entry as remarks not same + does not mean the ace entry to be re-introduced + """ + if r_entry.get("remarks"): + return r_entry.pop("remarks") + else: + return {} + for wseq, wentry in iteritems(want): hentry = have.pop(wseq, {}) + rem_hentry, rem_wentry = {}, {} + if hentry: hentry = self.sanitize_protocol_options(wentry, hentry) - if hentry != wentry: + + if hentry != wentry: # will let in if ace is same but remarks is not same + if hentry: + rem_hentry["remarks"] = pop_remark(hentry, afi) + if wentry: + rem_wentry["remarks"] = pop_remark(wentry, afi) + if hentry: if self.state == "merged": self._module.fail_json( @@ -164,24 +182,51 @@ class Acls(ResourceModule): ), ) else: # other action states - if hentry.get("remarks"): # remove remark if not in want - for rems in hentry.get("remarks"): - if rems not in wentry.get("remarks", {}): - self.addcmd({"remarks": rems}, "remarks", negate=True) - else: # remove ace if not in want + if rem_hentry.get("remarks"): # remove remark if not in want + for k_hrems, hrems in rem_hentry.get("remarks").items(): + if k_hrems not in rem_wentry.get("remarks", {}).keys(): + if self.state in ["replaced", "overridden"]: + self.addcmd( + { + "remarks": hrems, + "sequence": hentry.get("sequence", ""), + }, + "remarks_no_data", + negate=True, + ) + break + else: + self.addcmd( + { + "remarks": hrems, + "sequence": hentry.get("sequence", ""), + }, + "remarks", + negate=True, + ) + # remove ace if not in want + if hentry != wentry: self.addcmd(add_afi(hentry, afi), "aces", negate=True) - if wentry.get("remarks"): # add remark if not in have - for rems in wentry.get("remarks"): - if rems not in hentry.get("remarks", {}): - self.addcmd({"remarks": rems}, "remarks") - else: # add ace if not in have + if rem_wentry.get("remarks"): # add remark if not in have + for k_wrems, wrems in rem_wentry.get("remarks").items(): + if k_wrems not in rem_hentry.get("remarks", {}).keys(): + self.addcmd( + {"remarks": wrems, "sequence": hentry.get("sequence", "")}, + "remarks", + ) + # add ace if not in have + if hentry != wentry: self.addcmd(add_afi(wentry, afi), "aces") # remove remaining entries from have aces list for hseq in have.values(): if hseq.get("remarks"): # remove remarks that are extra in have - for rems in hseq.get("remarks"): - self.addcmd({"remarks": rems}, "remarks", negate=True) + for krems, rems in hseq.get("remarks").items(): + self.addcmd( + {"remarks": rems, "sequence": hseq.get("sequence", "")}, + "remarks", + negate=True, + ) else: # remove extra aces self.addcmd(add_afi(hseq, afi), "aces", negate=True) @@ -192,6 +237,7 @@ class Acls(ResourceModule): list(wace.get("protocol_options"))[0] == hace.get("protocol") ): hace.pop("protocol") + hace["protocol_options"] = wace.get("protocol_options") return hace def acl_name_cmd(self, name, afi, acl_type): @@ -224,11 +270,17 @@ class Acls(ResourceModule): for acl in each.get("acls"): # check each acl for aces temp_aces = {} if acl.get("aces"): - temp_rem = [] # remarks if defined in an ace + rem_idx = 0 # remarks if defined in an ace for ace in acl.get("aces"): # each ace turned to dict - if ace.get("destination") and ace.get("destination", {}).get( - "port_protocol", - {}, + if ( + ace.get("destination") + and ace.get("destination", {}).get( + "port_protocol", + {}, + ) + and not ace.get("destination", {}) + .get("port_protocol", {}) + .get("range") ): for k, v in ( ace.get("destination", {}).get("port_protocol", {}).items() @@ -251,18 +303,25 @@ class Acls(ResourceModule): ), ) - if ace.get("remarks"): - en_name = str(acl.get("name")) + "remark" - temp_rem.extend(ace.pop("remarks")) + if ace.get( + "remarks", + ): # index aces inside of each ace don't cluster them all + rem_ace = {} + # en_name = str(acl.get("name")) + "remark" + # temp_rem.extend(ace.pop("remarks")) + for remks in ace.get("remarks"): + rem_ace[remks.replace(" ", "_")] = remks + rem_idx += 1 + ace["remarks"] = rem_ace if ace.get("sequence"): temp_aces.update({ace.get("sequence"): ace}) elif ace: count += 1 - temp_aces.update({"_" + str(count): ace}) + temp_aces.update({"_" + to_text(count): ace}) - if temp_rem: # add remarks to the temp ace - temp_aces.update({en_name: {"remarks": temp_rem}}) + # if temp_rem: # add remarks to the temp ace + # temp_aces.update({en_name: {"remarks": temp_rem}}) if acl.get("acl_type"): # update acl dict with req info temp_acls.update( diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_address_family/bgp_address_family.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_address_family/bgp_address_family.py index 36ea3e963..17cdd11fc 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_address_family/bgp_address_family.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_address_family/bgp_address_family.py @@ -39,6 +39,7 @@ class Bgp_address_family(ResourceModule): """ parsers = [ + "advertise", "as_number", # generic "aggregate_addresses", "auto_summary", @@ -336,9 +337,6 @@ class Bgp_address_family(ResourceModule): neib["neighbor_address"] = neib.pop("ipv6_address") if neib.get("ipv6_adddress"): neib["neighbor_address"] = neib.pop("ipv6_adddress") - # next_hop_self deprecated added nexthop_self - if neib.get("next_hop_self"): - neib["nexthop_self"] = {"set": neib.pop("next_hop_self")} # prefix_list and prefix_lists if neib.get("prefix_list"): # deprecated made list neib["prefix_lists"] = [neib.pop("prefix_list")] @@ -356,6 +354,12 @@ class Bgp_address_family(ResourceModule): # slow_peer to slow_peer_options if neib.get("slow_peer"): # only one slow_peer is allowed neib["slow_peer_options"] = neib.pop("slow_peer")[0] + # we can skip deprecating these by handling the int to str conversion here + # int to float is not considered considering the size of as numbers + if neib.get("remote_as"): + neib["remote_as"] = str(neib.get("remote_as")) + if neib.get("local_as") and neib.get("local_as", {}).get("number"): + neib["local_as"]["number"] = str(neib["local_as"]["number"]) # make dict neighbors dict tmp[neib[p_key[k]]] = neib _af["neighbors"] = tmp diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_global/bgp_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_global/bgp_global.py index 59881d622..c48cac946 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_global/bgp_global.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/bgp_global/bgp_global.py @@ -148,7 +148,7 @@ class Bgp_global(ResourceModule): """Generate configuration commands to send based on want, have and desired state. """ - if self.state in ["merged", "replaced"]: + if self.state in ["merged", "replaced", "overridden"]: w_asn = self.want.get("as_number") h_asn = self.have.get("as_number") diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_evi/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_evi/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_evi/evpn_evi.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_evi/evpn_evi.py new file mode 100644 index 000000000..240906637 --- /dev/null +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_evi/evpn_evi.py @@ -0,0 +1,103 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2023 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 ios_evpn_evi config file. +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to its desired end-state is +created. +""" + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) + +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.facts import Facts +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.evpn_evi import ( + Evpn_eviTemplate, +) + + +class Evpn_evi(ResourceModule): + """ + The ios_evpn_evi config class + """ + + def __init__(self, module): + super(Evpn_evi, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="evpn_evi", + tmplt=Evpn_eviTemplate(), + ) + self.parsers = [ + "default_gateway.advertise.enable", + "default_gateway.advertise.disable", + "encapsulation", + "ip.local_learning.enable", + "ip.local_learning.disable", + "replication_type", + "route_distinguisher", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = {entry["evi"]: entry for entry in self.want} + haved = {entry["evi"]: entry for entry in self.have} + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} + wantd = {} + + # remove superfluous config for overridden and deleted + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + self.addcmd(have, "evi", negate=True) + + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {})) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Evpn_evi network resource. + """ + begin = len(self.commands) + self.compare(parsers=self.parsers, want=want, have=have) + if len(self.commands) != begin: + self.commands.insert(begin, self._tmplt.render(want or have, "evi", False)) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_global/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_global/evpn_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_global/evpn_global.py new file mode 100644 index 000000000..bd6e10321 --- /dev/null +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/evpn_global/evpn_global.py @@ -0,0 +1,98 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2023 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 ios_evpn_global config file. +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to its desired end-state is +created. +""" + +from ansible_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.cisco.ios.plugins.module_utils.network.ios.facts.facts import Facts +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.evpn_global import ( + Evpn_globalTemplate, +) + + +EVPN_GLOBAL_PARENT = "l2vpn evpn" + + +class Evpn_global(ResourceModule): + """ + The ios_evpn_global config class + """ + + def __init__(self, module): + super(Evpn_global, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="evpn_global", + tmplt=Evpn_globalTemplate(), + ) + self.parsers = [ + "default_gateway.advertise", + "flooding_suppression.address_resolution.disable", + "ip.local_learning.disable", + "replication_type", + "route_target.auto.vni", + "router_id", + ] + + 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 = self.want + haved = self.have + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # remove superfluous config for deleted + if self.state == "deleted": + if haved: + self.commands.append("no " + EVPN_GLOBAL_PARENT) + wantd, haved = {}, {} + + self._compare(want=wantd, have=haved) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Evpn_global network resource. + """ + begin = len(self.commands) + self.compare(parsers=self.parsers, want=want, have=have) + if len(self.commands) != begin: + self.commands.insert(begin, EVPN_GLOBAL_PARENT) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/lacp/lacp.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/lacp/lacp.py index 0420c347b..e09a53869 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/lacp/lacp.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/lacp/lacp.py @@ -126,7 +126,7 @@ class Lacp(ConfigBase): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - if self.state in ("merged", "replaced", "rendered") and not want: + if self.state in ("merged", "replaced", "overridden", "rendered") and not want: self._module.fail_json( msg="value of config parameter must not be empty for state {0}".format(self.state), ) @@ -135,7 +135,7 @@ class Lacp(ConfigBase): commands = self._state_deleted(want, have) elif self.state in ("merged", "rendered"): commands = self._state_merged(want, have) - elif self.state == "replaced": + elif self.state in ["replaced", "overridden"]: commands = self._state_replaced(want, have) return commands diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/lldp_global/lldp_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/lldp_global/lldp_global.py index c575bc7b0..34b466c52 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/lldp_global/lldp_global.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/lldp_global/lldp_global.py @@ -140,24 +140,22 @@ class Lldp_global(ConfigBase): to the desired configuration """ commands = [] - if self.state in ("merged", "replaced", "rendered") and not want: + if self.state in ("merged", "replaced", "overridden", "rendered") and not want: self._module.fail_json( msg="value of config parameter must not be empty for state {0}".format(self.state), ) - if self.state == "overridden": - commands = self._state_overridden(want, have) elif self.state == "deleted": commands = self._state_deleted(want, have) elif self.state in ("merged", "rendered"): commands = self._state_merged(want, have) - elif self.state == "replaced": + elif self.state in ["replaced", "overridden"]: commands = self._state_replaced(want, have) return commands def _state_replaced(self, want, have): - """The command generator when state is replaced + """The command generator when state is replaced/overridden :param want: the desired configuration as a dictionary :param have: the current configuration as a dictionary diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/logging_global/logging_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/logging_global/logging_global.py index 4eb442a0c..bfaab013b 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/logging_global/logging_global.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/logging_global/logging_global.py @@ -108,9 +108,9 @@ class Logging_global(ResourceModule): the `want` and `have` data with the `parsers` defined for the Logging_global network resource. """ + self._compare_complex_attrs(want, have) self.compare(parsers=self.parsers, want=want, have=have) self._compare_lists_attrs(want, have) - self._compare_complex_attrs(want, have) def _compare_lists_attrs(self, want, have): """Compare list of dict""" diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/ospfv2/ospfv2.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/ospfv2/ospfv2.py index 2cbc53e17..1dd26f356 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/ospfv2/ospfv2.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/ospfv2/ospfv2.py @@ -33,10 +33,6 @@ class Ospfv2(ResourceModule): The ios_ospfv2 class """ - gather_subset = ["!all", "!min"] - - gather_network_resources = ["ospfv2"] - def __init__(self, module): super(Ospfv2, self).__init__( empty_fact_val={}, @@ -46,39 +42,97 @@ class Ospfv2(ResourceModule): tmplt=Ospfv2Template(), ) + self.parsers = [ + "adjacency", + "address_family", + "auto_cost", + "bfd", + "capability.lls", + "capability.opaque", + "capability.transit", + "capability.vrf_lite", + "compatible", + "default_information", + "default_metric", + "discard_route", + "distance.admin_distance", + "distance.ospf", + "distribute_list.prefix", + "distribute_list.route_map", + "domain_id", + "domain_tag", + "event_log", + "help", + "ignore", + "interface_id", + "ispf", + "limit", + "local_rib_criteria", + "log_adjacency_changes", + "max_lsa", + "max_metric", + "maximum_paths", + "mpls.ldp.autoconfig", + "mpls.ldp.sync", + "mpls.traffic_eng", + "neighbor", + "nsf.cisco", + "nsf.ietf.disable", + "nsf.ietf.strict_lsa_checking", + "prefix_suppression", + "priority", + "queue_depth.hello.max_packets", + "queue_depth.hello.unlimited", + "queue_depth.update.max_packets", + "queue_depth.update.unlimited", + "router_id", + "shutdown", + "summary_address.not_advertise", + "summary_address.nssa_only", + "timers.throttle.lsa", + "timers.pacing.flood", + "timers.pacing.lsa_group", + "timers.pacing.retransmission", + "timers.throttle.spf", + "traffic_share", + "ttl_security", + ] + def execute_module(self): """Execute the module :rtype: A dictionary :returns: The result from module execution """ - self.gen_config() - self.run_commands() + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() return self.result - def gen_config(self): + def generate_commands(self): """Select the appropriate function based on the state provided :rtype: A list :returns: the commands necessary to migrate the current configuration to the desired configuration """ + haved, wantd = dict(), dict() + if self.want: - wantd = {} for entry in self.want.get("processes", []): + entry = self._handle_deprecated(entry) wantd.update({(entry["process_id"], entry.get("vrf")): entry}) - else: - wantd = {} + if self.have: - haved = {} for entry in self.have.get("processes", []): + # entry = self._handle_deprecated(entry) haved.update({(entry["process_id"], entry.get("vrf")): entry}) - else: - haved = {} # turn all lists of dicts into dicts prior to merge for each in wantd, haved: - self.list_to_dict(each) + if each: + self._list_to_dict(each) + # if state is merged, merge want onto have if self.state == "merged": wantd = dict_merge(haved, wantd) @@ -103,61 +157,32 @@ class Ospfv2(ResourceModule): self._compare(want=want, have=haved.pop(k, {})) def _compare(self, want, have): - parsers = [ - "adjacency", - "address_family", - "auto_cost", - "bfd", - "capability", - "compatible", - "default_information", - "default_metric", - "discard_route", - "distance.admin_distance", - "distance.ospf", - "distribute_list.acls", - "distribute_list.prefix", - "distribute_list.route_map", - "domain_id", - "domain_tag", - "event_log", - "help", - "ignore", - "interface_id", - "ispf", - "limit", - "local_rib_criteria", - "log_adjacency_changes", - "max_lsa", - "max_metric", - "maximum_paths", - "mpls.ldp", - "mpls.traffic_eng", - "neighbor", - "network", - "nsf.cisco", - "nsf.ietf", - "passive_interface", - "prefix_suppression", - "priority", - "queue_depth.hello", - "queue_depth.update", - "router_id", - "shutdown", - "summary_address", - "timers.throttle.lsa", - "timers.throttle.spf", - "traffic_share", - "ttl_security", - ] - if want != have: self.addcmd(want or have, "pid", False) - self.compare(parsers, want, have) + self.compare(self.parsers, want, have) self._areas_compare(want, have) + self._complex_compare(want, have) if want.get("passive_interfaces"): self._passive_interfaces_compare(want, have) + def _complex_compare(self, want, have): + complex_parsers = ["distribute_list.acls", "network"] + for _parser in complex_parsers: + if _parser == "distribute_list.acls": + wdist = want.get("distribute_list", {}).get("acls", {}) + hdist = have.get("distribute_list", {}).get("acls", {}) + else: + wdist = want.get(_parser, {}) + hdist = have.get(_parser, {}) + for key, wanting in iteritems(wdist): + haveing = hdist.pop(key, {}) + if wanting != haveing: + if haveing and self.state in ["overridden", "replaced"]: + self.addcmd(haveing, _parser, negate=True) + self.addcmd(wanting, _parser, False) + for key, haveing in iteritems(hdist): + self.addcmd(haveing, _parser, negate=True) + def _areas_compare(self, want, have): wareas = want.get("areas", {}) hareas = have.get("areas", {}) @@ -168,42 +193,40 @@ class Ospfv2(ResourceModule): def _area_compare(self, want, have): parsers = [ - "area.authentication", - "area.capability", - "area.default_cost", - "area.nssa", - "area.nssa.translate", - "area.ranges", - "area.sham_link", - "area.stub", + "authentication", + "capability", + "default_cost", + "nssa", + "nssa.translate", + "sham_link", + "stub", ] self.compare(parsers=parsers, want=want, have=have) - self._area_compare_filters(want, have) + self._area_complex_compare(want, have, want.get("area_id")) - def _area_compare_filters(self, wantd, haved): - for name, entry in iteritems(wantd): - h_item = haved.pop(name, {}) - if entry != h_item and name == "filter_list": - filter_list_entry = {} - filter_list_entry["area_id"] = wantd["area_id"] - if h_item: - li_diff = [ - item for item in entry + h_item if item not in entry or item not in h_item - ] - else: - li_diff = entry - filter_list_entry["filter_list"] = li_diff - self.addcmd(filter_list_entry, "area.filter_list", False) - for name, entry in iteritems(haved): - if name == "filter_list": - self.addcmd(entry, "area.filter_list", True) + def _area_complex_compare(self, want, have, area_id): + area_complex_parsers = ["filter_list", "ranges"] + for _parser in area_complex_parsers: + wantr = want.get(_parser, {}) + haver = have.get(_parser, {}) + for key, wanting in iteritems(wantr): + haveing = have.pop(key, {}) + haveing["area_id"] = area_id + wanting["area_id"] = area_id + if wanting != haveing: + if haveing and self.state in ["overridden", "replaced"]: + self.addcmd(haveing, _parser, negate=True) + self.addcmd(wanting, _parser, False) + for key, haveing in iteritems(haver): + haveing["area_id"] = area_id + self.addcmd(haveing, _parser, negate=True) def _passive_interfaces_compare(self, want, have): - parsers = ["passive_interfaces"] + parsers = ["passive_interfaces.default", "passive_interfaces.interface"] h_pi = None for k, v in iteritems(want["passive_interfaces"]): - h_pi = have.get("passive_interfaces", []) - if h_pi and h_pi.get(k) and h_pi.get(k) != v: + h_pi = have.get("passive_interfaces", {}) + if h_pi.get(k) and h_pi.get(k) != v: for each in v["name"]: h_interface_name = h_pi[k].get("name", []) if each not in h_interface_name: @@ -243,42 +266,51 @@ class Ospfv2(ResourceModule): } self.compare( parsers=parsers, - want={"passive_interface": temp}, + want={"passive_interfaces": temp}, have=dict(), ) elif k == "default": self.compare( parsers=parsers, want=dict(), - have={"passive_interface": {"default": True}}, + have={"passive_interfaces": {"default": True}}, ) - def list_to_dict(self, param): - if param: - for _pid, proc in iteritems(param): - for area in proc.get("areas", []): - ranges = {} - for entry in area.get("ranges", []): - ranges.update({entry["address"]: entry}) - if bool(ranges): - area["ranges"] = ranges - filter_list = {} - for entry in area.get("filter_list", []): - filter_list.update({entry["direction"]: entry}) - if bool(filter_list): - area["filter_list"] = filter_list - temp = {} - for entry in proc.get("areas", []): - temp.update({entry["area_id"]: entry}) - proc["areas"] = temp - if proc.get("distribute_list"): - if "acls" in proc.get("distribute_list"): - temp = {} - for entry in proc["distribute_list"].get("acls", []): - temp.update({entry["name"]: entry}) - proc["distribute_list"]["acls"] = temp - if proc.get("passive_interfaces") and proc["passive_interfaces"].get("interface"): - temp = {} - for entry in proc["passive_interfaces"]["interface"].get("name", []): - temp.update({entry: entry}) - proc["passive_interfaces"]["interface"]["name"] = temp + def _list_to_dict(self, param): + for _pid, proc in param.items(): + # convert list to dict for areas + for area in proc.get("areas", []): + area["ranges"] = {entry["address"]: entry for entry in area.get("ranges", [])} + area["filter_list"] = { + entry["direction"]: entry for entry in area.get("filter_list", []) + } + + proc["areas"] = {entry["area_id"]: entry for entry in proc.get("areas", [])} + + # list to dict for distribute_list + distribute_list = proc.get("distribute_list", {}) + if "acls" in distribute_list: + distribute_list["acls"] = { + entry["name"]: entry for entry in distribute_list["acls"] + } + + # list to dict for passive_interfaces + passive_interfaces = proc.get("passive_interfaces", {}).get("interface", {}) + if passive_interfaces.get("name"): + passive_interfaces["name"] = {entry: entry for entry in passive_interfaces["name"]} + + # list to dict for network + if proc.get("network"): + proc["network"] = {entry["address"]: entry for entry in proc["network"]} + + def _handle_deprecated(self, config): + if config.get("passive_interface"): + passive_interfaces = config.get("passive_interfaces", {}) + interface = passive_interfaces.get("interface", {}) + name_list = interface.get("name", []) + if not name_list: + name_list.append(config["passive_interface"]) + else: + name_list.extend(config["passive_interface"]) + del config["passive_interface"] + return config diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/ping/ping.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/ping/ping.py index f2417f596..138cd5715 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/ping/ping.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/ping/ping.py @@ -81,7 +81,7 @@ class Ping: Returns the percent of packet loss, received packets, transmitted packets, and RTT data. """ - if type(ping_results) == list: + if isinstance(ping_results, list): ping_results = ping_results[0] ping_data = PingTemplate(lines=ping_results.splitlines()) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/prefix_lists/prefix_lists.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/prefix_lists/prefix_lists.py index 7c4ebe5ec..210fb234d 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/prefix_lists/prefix_lists.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/prefix_lists/prefix_lists.py @@ -63,12 +63,10 @@ class Prefix_lists(ResourceModule): want, have and desired state. """ wantd = {entry["afi"]: entry for entry in self.want} - haved = {entry["afi"]: entry for entry in self.have} - # Convert each of config list to dict - for each in wantd, haved: - self.list_to_dict(each) + self._prefix_list_transform(wantd) + self._prefix_list_transform(haved) # if state is merged, merge want onto have and then compare if self.state == "merged": @@ -76,44 +74,24 @@ class Prefix_lists(ResourceModule): # if state is deleted, empty out wantd and set haved to wantd if self.state == "deleted": - temp = None - for k, v in iteritems(haved): - if k in wantd: - if wantd[k].get("prefix_lists"): - want_afi_name = wantd[k].get("prefix_lists", {}) - haved[k]["prefix_lists"] = { - key: val - for key, val in iteritems(v.get("prefix_lists")) - if key in want_afi_name - } - elif wantd: - temp = k - if temp: - haved.pop(k) - wantd = {} - for k, have in iteritems(haved): - for key, val in iteritems(have["prefix_lists"]): - if k == "ipv4": - k = "ip" - self.commands.append("no {0} prefix-list {1}".format(k, key)) + haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} + for key, hvalue in iteritems(haved): + wvalue = wantd.pop(key, {}) + if wvalue: + wplists = wvalue.get("prefix_lists", {}) + hplists = hvalue.get("prefix_lists", {}) + hvalue["prefix_lists"] = { + k: v for k, v in iteritems(hplists) if k in wplists or not wplists + } # remove superfluous config for overridden and deleted if self.state in ["overridden", "deleted"]: for k, have in iteritems(haved): - want_afi = wantd.get(k, {}) - for key, val in iteritems(have["prefix_lists"]): - if k == "ipv4": - k = "ip" - if want_afi and key not in want_afi.get("prefix_lists"): - self.commands.append("no {0} prefix-list {1}".format(k, key)) + if k not in wantd: + self._compare(want={}, have=have) for k, want in iteritems(wantd): self._compare(want=want, have=haved.pop(k, {})) - # alligning cmd with negate cmd 1st followed by config cmd - if self.state in ["overridden", "replaced"]: - self.commands = [each for each in self.commands if "no" in each] + [ - each for each in self.commands if "no" not in each - ] def _compare(self, want, have): """Leverages the base class `compare()` method and @@ -121,130 +99,48 @@ class Prefix_lists(ResourceModule): the `want` and `have` data with the `parsers` defined for the Prefix_lists network resource. """ - if want != have and self.state != "deleted": - for k, v in iteritems(want["prefix_lists"]): - if have.get("prefix_lists") and have["prefix_lists"].get(k): - have_prefix = have["prefix_lists"].pop(k, {}) - for key, val in iteritems(v.get("entries")): - if have_prefix.get("entries"): - have_prefix_param = have_prefix["entries"].pop(key, {}) - else: - have_prefix_param = None - if have_prefix.get("description"): - self.compare( - parsers=self.parsers, - want={ - "afi": want["afi"], - "name": k, - "prefix_list": {"description": v["description"]}, - }, - have={ - "afi": want["afi"], - "name": k, - "prefix_list": {"description": have_prefix.pop("description")}, - }, - ) - if have_prefix_param and val != have_prefix_param: - if key == "description": - # Code snippet should be removed when Description param is removed from - # entries level as this supports deprecated level of Description - self.compare( - parsers=self.parsers, - want={"afi": want["afi"], "name": k, "prefix_list": {key: val}}, - have={ - "afi": have["afi"], - "name": k, - "prefix_list": {key: have_prefix_param}, - }, - ) - else: - if self.state == "merged" and have_prefix_param.get( - "sequence", - ) == val.get("sequence"): - self._module.fail_json( - "Cannot update existing sequence {0} of Prefix Lists {1} with state merged.".format( - val.get("sequence"), - k, - ) - + " Please use state replaced or overridden.", - ) - self.compare( - parsers=self.parsers, - want=dict(), - have={ - "afi": have["afi"], - "name": k, - "prefix_list": have_prefix_param, - }, - ) - self.compare( - parsers=self.parsers, - want={"afi": want["afi"], "name": k, "prefix_list": val}, - have={ - "afi": have["afi"], - "name": k, - "prefix_list": have_prefix_param, - }, - ) - elif val and val != have_prefix_param: - self.compare( - parsers=self.parsers, - want={"afi": want["afi"], "name": k, "prefix_list": val}, - have=dict(), - ) - if have_prefix and (self.state == "replaced" or self.state == "overridden"): - if have_prefix.get("description"): - # Code snippet should be removed when Description param is removed from - # entries level as this supports deprecated level of Description - self.compare( - parsers=self.parsers, - want=dict(), - have={ - "afi": want["afi"], - "name": k, - "prefix_list": {"description": have_prefix["description"]}, - }, - ) - for key, val in iteritems(have_prefix.get("entries")): - self.compare( - parsers=self.parsers, - want=dict(), - have={"afi": have["afi"], "name": k, "prefix_list": val}, - ) - elif v: - if v.get("description"): - self.compare( - parsers=self.parsers, - want={ - "afi": want["afi"], - "name": k, - "prefix_list": {"description": v["description"]}, - }, - have=dict(), - ) - for key, val in iteritems(v.get("entries")): - self.compare( - parsers=self.parsers, - want={"afi": want["afi"], "name": k, "prefix_list": val}, - have=dict(), - ) + wplists = want.get("prefix_lists", {}) + hplists = have.get("prefix_lists", {}) + for wk, wentry in iteritems(wplists): + hentry = hplists.pop(wk, {}) + self.compare(["description"], want=wentry, have=hentry) + # compare sequences + self._compare_seqs(wentry.pop("entries", {}), hentry.pop("entries", {})) - def list_to_dict(self, param): - if param: - for key, val in iteritems(param): - if val.get("prefix_lists"): - temp_prefix_list = {} - for each in val["prefix_lists"]: - temp_entries = dict() - if each.get("entries"): - for every in each["entries"]: - temp_entries.update({str(every["sequence"]): every}) - temp_prefix_list.update( - { - each["name"]: { - "description": each.get("description"), - "entries": temp_entries, - }, - }, + if self.state in ["overridden", "deleted"]: + # remove remaining prefix lists + for h in hplists.values(): + self.commands.append( + "no {0} prefix-list {1}".format(h["afi"].replace("ipv4", "ip"), h["name"]), + ) + + def _compare_seqs(self, want, have): + for wseq, wentry in iteritems(want): + hentry = have.pop(wseq, {}) + if hentry != wentry: + if hentry: + if self.state == "merged": + self._module.fail_json( + msg="Cannot update existing sequence {0} of prefix list {1} with state merged." + " Please use state replaced or overridden.".format( + hentry["sequence"], + hentry["name"], + ), ) - val["prefix_lists"] = temp_prefix_list + else: + self.addcmd(hentry, "entry", negate=True) + self.addcmd(wentry, "entry") + # remove remaining entries from have prefix list + for hseq in have.values(): + self.addcmd(hseq, "entry", negate=True) + + def _prefix_list_transform(self, entry): + for afi, value in iteritems(entry): + if "prefix_lists" in value: + for plist in value["prefix_lists"]: + plist.update({"afi": afi}) + if "entries" in plist: + for seq in plist["entries"]: + seq.update({"afi": afi, "name": plist["name"]}) + plist["entries"] = {x["sequence"]: x for x in plist["entries"]} + value["prefix_lists"] = {entry["name"]: entry for entry in value["prefix_lists"]} diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/service/service.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/service/service.py index 23e3329be..6a4c8a1ff 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/service/service.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/service/service.py @@ -103,9 +103,11 @@ class Service(ResourceModule): "prompt": True, "slave_log": True, "password_recovery": True, - "private_config_encryption": True, } + if "private_config_encryption" in haved: + service_default["private_config_encryption"] = True + # if state is merged, merge want onto have and then compare if self.state == "merged": wantd = dict_merge(haved, wantd) @@ -115,7 +117,7 @@ class Service(ResourceModule): wantd = self._service_list_to_dict(service_default) # if state is replaced - elif self.state == "replaced": + elif self.state in ["replaced", "overridden"]: wantd = dict_merge(self._service_list_to_dict(service_default), wantd) self._compare(want=wantd, have=haved) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/snmp_server/snmp_server.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/snmp_server/snmp_server.py index ff45f7b5f..187d0779d 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/snmp_server/snmp_server.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/snmp_server/snmp_server.py @@ -77,72 +77,121 @@ class Snmp_server(ResourceModule): "views", ] self.complex_parsers = [ + "traps.aaa_server", "traps.auth_framework", "traps.bfd", "traps.bgp", + "traps.bgp.cbgp2", "traps.bridge", + "traps.bulkstat", + "traps.call_home", "traps.casa", + "traps.cef", "traps.cnpd", "traps.config", "traps.config_copy", "traps.config_ctid", + "traps.cpu", "traps.dhcp", + "traps.dlsw", "traps.eigrp", - "traps.entity", "traps.energywise", + "traps.entity", + "traps.entity_diag", + "traps.entity_perf", + "traps.entity_state", + "traps.envmon", + "traps.errdisable", + "traps.ether_oam", + "traps.ethernet.cfm.alarm", + "traps.ethernet.cfm.cc", + "traps.ethernet.cfm.crosscheck", + "traps.ethernet.evc", "traps.event_manager", + "traps.flash", + "traps.flex_links", + "traps.firewall", "traps.flowmon", + "traps.frame_relay", + "traps.frame_relay.subif", "traps.fru_ctrl", "traps.hsrp", - "traps.ipsla", - "traps.isis", - "traps.msdp", - "traps.mvpn", - "traps.mpls_vpn", - "traps.pki", - "traps.pw_vc", - "traps.rsvp", - "traps.syslog", - "traps.transceiver_all", - "traps.tty", - "traps.vrrp", - "traps.vrfmib", - "traps.ipmulticast", "traps.ike.policy.add", "traps.ike.policy.delete", "traps.ike.tunnel.start", "traps.ike.tunnel.stop", + "traps.ipmulticast", "traps.ipsec.cryptomap.add", - "traps.ipsec.cryptomap.delete", "traps.ipsec.cryptomap.attach", + "traps.ipsec.cryptomap.delete", "traps.ipsec.cryptomap.detach", + "traps.ipsec.too_many_sas", "traps.ipsec.tunnel.start", "traps.ipsec.tunnel.stop", - "traps.ipsec.too_many_sas", + "traps.ipsla", + "traps.isis", + "traps.l2tc", + "traps.l2tun.pseudowire_status", + "traps.l2tun.session", + "traps.lisp", + "traps.license", + "traps.local_auth", + "traps.mac_notification", + "traps.memory", + "traps.mpls.fast_reroute", + "traps.mpls.ldp", + "traps.mpls.rfc.ldp", + "traps.mpls.rfc.traffic_eng", + "traps.mpls.rfc.vpn", + "traps.mpls.traffic_eng", + "traps.mpls.vpn", + "traps.msdp", + "traps.mvpn", + "traps.nhrp.nhc", + "traps.nhrp.nhp", + "traps.nhrp.nhs", + "traps.nhrp.quota_exceeded", "traps.ospf.cisco_specific.error", - "traps.ospf.cisco_specific.retransmit", "traps.ospf.cisco_specific.lsa", + "traps.ospf.cisco_specific.retransmit", "traps.ospf.cisco_specific.state_change.nssa_trans_change", "traps.ospf.cisco_specific.state_change.shamlink.interface", "traps.ospf.cisco_specific.state_change.shamlink.neighbor", "traps.ospf.error", - "traps.ospf.retransmit", "traps.ospf.lsa", + "traps.ospf.retransmit", "traps.ospf.state_change", - "traps.l2tun.pseudowire_status", - "traps.l2tun.session", - "traps.cpu", - "traps.firewall", + "traps.ospfv3.errors", + "traps.ospfv3.rate_limit", + "traps.ospfv3.state_change", "traps.pim", + "traps.pki", + "traps.port_security", + "traps.power_ethernet", + "traps.pw_vc", + "traps.rep", + "traps.rsvp", + "traps.rf", + "traps.smart_license", "traps.snmp", - "traps.frame_relay", - "traps.frame_relay.subif", - "traps.cef", - "traps.dlsw", - "traps.ethernet.evc", - "traps.ethernet.cfm.alarm", - "traps.ethernet.cfm.cc", - "traps.ethernet.cfm.crosscheck", + "traps.stackwise", + "traps.stpx", + "traps.syslog", + "traps.transceiver_all", + "traps.trustsec", + "traps.trustsec_interface", + "traps.trustsec_policy", + "traps.trustsec_server", + "traps.trustsec_sxp", + "traps.tty", + "traps.udld", + "traps.vlan_membership", + "traps.vlancreate", + "traps.vlandelete", + "traps.vrfmib", + "traps.vrrp", + "traps.vswitch", + "traps.vtp", ] def execute_module(self): @@ -163,6 +212,8 @@ class Snmp_server(ResourceModule): wantd = self._snmp_list_to_dict(self.want) haved = self._snmp_list_to_dict(self.have) + wantd = self._handle_deprecates(want=wantd) + # if state is merged, merge want onto have and then compare if self.state == "merged": wantd = dict_merge(haved, wantd) @@ -186,16 +237,39 @@ class Snmp_server(ResourceModule): def _compare_lists_attrs(self, want, have): """Compare list of dict""" for _parser in self.list_parsers: - i_want = want.get(_parser, {}) - i_have = have.get(_parser, {}) - for key, wanting in iteritems(i_want): - haveing = i_have.pop(key, {}) - if wanting != haveing: - if haveing and self.state in ["overridden", "replaced"]: - self.addcmd(haveing, _parser, negate=True) - self.addcmd(wanting, _parser) - for key, haveing in iteritems(i_have): - self.addcmd(haveing, _parser, negate=True) + if _parser == "users": + i_want = want.get(_parser, {}) + i_have = have.get(_parser, {}) + for key, wanting in iteritems(i_want): + wanting_compare = deepcopy(wanting) + if ( + "authentication" in wanting_compare + and "password" in wanting_compare["authentication"] + ): + wanting_compare["authentication"].pop("password") + if ( + "encryption" in wanting_compare + and "password" in wanting_compare["encryption"] + ): + wanting_compare["encryption"].pop("password") + haveing = i_have.pop(key, {}) + if wanting_compare != haveing: + if haveing and self.state in ["overridden", "replaced"]: + self.addcmd(haveing, _parser, negate=True) + self.addcmd(wanting, _parser) + for key, haveing in iteritems(i_have): + self.addcmd(haveing, _parser, negate=True) + else: + i_want = want.get(_parser, {}) + i_have = have.get(_parser, {}) + for key, wanting in iteritems(i_want): + haveing = i_have.pop(key, {}) + if wanting != haveing: + if haveing and self.state in ["overridden", "replaced"]: + self.addcmd(haveing, _parser, negate=True) + self.addcmd(wanting, _parser) + for key, haveing in iteritems(i_have): + self.addcmd(haveing, _parser, negate=True) def _snmp_list_to_dict(self, data): """Convert all list of dicts to dicts of dicts""" @@ -242,7 +316,8 @@ class Snmp_server(ResourceModule): tmp_data[k]["protocol"] = tmp elif k == "groups": tmp_data[k] = { - str(i[p_key.get(k)] + i.get("version_option", "")): i for i in tmp_data[k] + str(i[p_key.get(k)] + i.get("version_option", "") + i.get("context", "")): i + for i in tmp_data[k] } elif k == "views": tmp_data[k] = { @@ -251,3 +326,23 @@ class Snmp_server(ResourceModule): else: tmp_data[k] = {str(i[p_key.get(k)]): i for i in tmp_data[k]} return tmp_data + + def _handle_deprecates(self, want): + """Remove deprecated attributes and set the replacment""" + + # Take in count the traps config mpls_vpn which is DEPRECATED and replaced by mpls.vpn + if "traps" in want: + if "mpls_vpn" in want["traps"]: + want["traps"] = dict_merge( + want["traps"], + {"mpls": {"vpn": {"enable": want["traps"]["mpls_vpn"]}}}, + ) + want["traps"].pop("mpls_vpn") + if "envmon" in want["traps"] and "fan" in want["traps"]["envmon"]: + want["traps"]["envmon"]["fan_enable"] = want["traps"]["envmon"]["fan"].get( + "enable", + False, + ) + want["traps"]["envmon"].pop("fan") + + return want diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/static_routes/static_routes.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/static_routes/static_routes.py index 79b63c8fe..e6c1336d6 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/static_routes/static_routes.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/static_routes/static_routes.py @@ -71,7 +71,7 @@ class Static_routes(ResourceModule): if delete_spcl and haved and self.state == "deleted": for pk, to_rem in delete_spcl.items(): if pk in ["ipv4", "ipv6"]: - _afis = haved.get("_afis_") + _afis = haved.get("(_afis_)") for k, v in _afis.get(pk, {}).items(): for each_dest in to_rem: if k.split("_")[0] == each_dest: @@ -184,5 +184,5 @@ class Static_routes(ResourceModule): _routes[_key] = dummy_sr _srts[_afi] = _routes - _static_rts[_vrf if _vrf else "_afis_"] = _srts + _static_rts[_vrf if _vrf else "(_afis_)"] = _srts return _static_rts, _delete_spc diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vlans/vlans.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vlans/vlans.py index 9d206f509..ad95b680a 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vlans/vlans.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vlans/vlans.py @@ -20,7 +20,10 @@ __metaclass__ = type from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import ( ConfigBase, ) -from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + remove_empties, + to_list, +) from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.facts import Facts from ansible_collections.cisco.ios.plugins.module_utils.network.ios.utils.utils import dict_to_set @@ -44,6 +47,7 @@ class Vlans(ConfigBase): :rtype: A dictionary :returns: The current configuration as a dictionary """ + facts, _warnings = Facts(self._module).get_facts( self.gather_subset, self.gather_network_resources, @@ -63,7 +67,12 @@ class Vlans(ConfigBase): result = {"changed": False} commands = list() warnings = list() - + self.have_now = list() + self.configuration = self._module.params["configuration"] + if not self.configuration: + self.vlan_parent = "vlan {0}" + else: + self.vlan_parent = "vlan configuration {0}" if self.state in self.ACTION_STATES: existing_vlans_facts = self.get_vlans_facts() else: @@ -110,7 +119,10 @@ class Vlans(ConfigBase): :returns: the commands necessary to migrate the current configuration to the desired configuration """ - want = self._module.params["config"] + want = [] + if self._module.params.get("config"): + for cfg in self._module.params["config"]: + want.append(remove_empties(cfg)) have = existing_vlans_facts resp = self.set_state(want, have) return to_list(resp) @@ -173,6 +185,7 @@ class Vlans(ConfigBase): commands = [] want_local = want + self.have_now = have.copy() for each in have: count = 0 for every in want_local: @@ -264,7 +277,8 @@ class Vlans(ConfigBase): def _set_config(self, want, have): # Set the interface config based on the want and have config commands = [] - vlan = "vlan {0}".format(want.get("vlan_id")) + + vlan = self.vlan_parent.format(want.get("vlan_id")) def negate_have_config(want_diff, have_diff, vlan, commands): name = dict(have_diff).get("name") @@ -294,8 +308,9 @@ class Vlans(ConfigBase): self.remove_command_from_config_list(vlan, "private-vlan association", commands) # Get the diff b/w want n have - want_dict = dict_to_set(want) - have_dict = dict_to_set(have) + + want_dict = dict_to_set(want, sort_dictionary=True) + have_dict = dict_to_set(have, sort_dictionary=True) diff = want_dict - have_dict have_diff = have_dict - want_dict @@ -303,69 +318,140 @@ class Vlans(ConfigBase): if have_diff and (self.state == "replaced" or self.state == "overridden"): negate_have_config(diff, have_diff, vlan, commands) - name = dict(diff).get("name") - state = dict(diff).get("state") - shutdown = dict(diff).get("shutdown") - mtu = dict(diff).get("mtu") - remote_span = dict(diff).get("remote_span") - private_vlan = dict(diff).get("private_vlan") - - if name: - self.add_command_to_config_list(vlan, "name {0}".format(name), commands) - if state: - self.add_command_to_config_list(vlan, "state {0}".format(state), commands) - if mtu: - self.add_command_to_config_list(vlan, "mtu {0}".format(mtu), commands) - if remote_span: - self.add_command_to_config_list(vlan, "remote-span", commands) - - if private_vlan: - private_vlan_type = dict(private_vlan).get("type") - private_vlan_associated = dict(private_vlan).get("associated") - if private_vlan_type: - self.add_command_to_config_list( - vlan, - "private-vlan {0}".format(private_vlan_type), - commands, + if not self.configuration: + name = dict(diff).get("name") + state = dict(diff).get("state") + shutdown = dict(diff).get("shutdown") + mtu = dict(diff).get("mtu") + remote_span = dict(diff).get("remote_span") + private_vlan = dict(diff).get("private_vlan") + + if name: + self.add_command_to_config_list(vlan, "name {0}".format(name), commands) + if state: + self.add_command_to_config_list(vlan, "state {0}".format(state), commands) + if mtu: + self.add_command_to_config_list(vlan, "mtu {0}".format(mtu), commands) + if remote_span: + self.add_command_to_config_list(vlan, "remote-span", commands) + + if private_vlan: + private_vlan_type = dict(private_vlan).get("type") + private_vlan_associated = dict(private_vlan).get("associated") + if private_vlan_type: + self.add_command_to_config_list( + vlan, + "private-vlan {0}".format(private_vlan_type), + commands, + ) + if private_vlan_associated: + associated_list = ",".join( + str(e) for e in private_vlan_associated + ) # Convert python list to string with elements separated by a comma + self.add_command_to_config_list( + vlan, + "private-vlan association {0}".format(associated_list), + commands, + ) + if shutdown == "enabled": + self.add_command_to_config_list(vlan, "shutdown", commands) + elif shutdown == "disabled": + self.add_command_to_config_list(vlan, "no shutdown", commands) + else: + member_dict = dict(diff).get("member") + if member_dict: + member_dict = dict(member_dict) + member_vni = member_dict.get("vni") + member_evi = member_dict.get("evi") + commands.extend( + self._remove_vlan_vni_evi_mapping( + want, + ), ) - if private_vlan_associated: - associated_list = ",".join( - str(e) for e in private_vlan_associated - ) # Convert python list to string with elements separated by a comma - self.add_command_to_config_list( - vlan, - "private-vlan association {0}".format(associated_list), - commands, + commands.extend( + [ + vlan, + self._get_member_cmds(member_dict), + ], ) - if shutdown == "enabled": - self.add_command_to_config_list(vlan, "shutdown", commands) - elif shutdown == "disabled": - self.add_command_to_config_list(vlan, "no shutdown", commands) elif have_diff and (self.state == "replaced" or self.state == "overridden"): negate_have_config(diff, have_diff, vlan, commands) - return commands def _clear_config(self, want, have): # Delete the interface config based on the want and have config commands = [] - vlan = "vlan {0}".format(have.get("vlan_id")) + vlan = self.vlan_parent.format(have.get("vlan_id")) if ( have.get("vlan_id") - and "default" not in have.get("name") + and "default" not in have.get("name", "") and (have.get("vlan_id") != want.get("vlan_id") or self.state == "deleted") ): self.remove_command_from_config_list(vlan, "vlan", commands) - elif "default" not in have.get("name"): - if have.get("mtu") != want.get("mtu"): - self.remove_command_from_config_list(vlan, "mtu", commands) - if have.get("remote_span") != want.get("remote_span") and want.get("remote_span"): - self.remove_command_from_config_list(vlan, "remote-span", commands) - if have.get("shutdown") != want.get("shutdown") and want.get("shutdown"): - self.remove_command_from_config_list(vlan, "shutdown", commands) - if have.get("state") != want.get("state") and want.get("state"): - self.remove_command_from_config_list(vlan, "state", commands) + if self.configuration and self.state == "overridden": + self.have_now.remove(have) + elif "default" not in have.get("name", ""): + if not self.configuration: + if have.get("mtu") != want.get("mtu"): + self.remove_command_from_config_list(vlan, "mtu", commands) + if have.get("remote_span") != want.get("remote_span") and want.get("remote_span"): + self.remove_command_from_config_list(vlan, "remote-span", commands) + if have.get("shutdown") != want.get("shutdown") and want.get("shutdown"): + self.remove_command_from_config_list(vlan, "shutdown", commands) + if have.get("state") != want.get("state") and want.get("state"): + self.remove_command_from_config_list(vlan, "state", commands) + return commands + def _remove_vlan_vni_evi_mapping(self, want_dict): + commands = [] + have_copy = self.have_now.copy() + vlan = want_dict["vlan_id"] + for vlan_dict in have_copy: + if vlan_dict["vlan_id"] == vlan: + if "member" in vlan_dict: + commands.extend( + [ + self.vlan_parent.format(vlan), + self._get_member_cmds( + vlan_dict.get("member", {}), + prefix="no", + ), + ], + ) + vlan_dict.pop("member") + if vlan_dict["vlan_id"] != vlan: + delete_member = False + have_vni = vlan_dict.get("member", {}).get("vni") + have_evi = vlan_dict.get("member", {}).get("evi") + if have_vni and (have_vni == want_dict["member"].get("vni")): + delete_member = True + if have_evi and (have_evi == want_dict["member"].get("evi")): + delete_member = True + if delete_member: + commands.extend( + [ + self.vlan_parent.format(vlan_dict["vlan_id"]), + self._get_member_cmds( + vlan_dict.get("member", {}), + prefix="no", + ), + ], + ) + self.have_now.remove(vlan_dict) return commands + + def _get_member_cmds(self, member_dict, prefix=""): + cmd = "" + if prefix: + prefix = prefix + " " + member_vni = member_dict.get("vni") + member_evi = member_dict.get("evi") + + if member_evi: + cmd = prefix + "member evpn-instance {0} vni {1}".format(member_evi, member_vni) + elif member_vni: + cmd = prefix + "member vni {0}".format(member_vni) + + return cmd diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vxlan_vtep/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vxlan_vtep/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vxlan_vtep/vxlan_vtep.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vxlan_vtep/vxlan_vtep.py new file mode 100644 index 000000000..bb35b04e4 --- /dev/null +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/config/vxlan_vtep/vxlan_vtep.py @@ -0,0 +1,190 @@ +# +# -*- coding: utf-8 -*- +# Copyright 2023 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 ios_vxlan_vtep config file. +It is in this file where the current configuration (as dict) +is compared to the provided configuration (as dict) and the command set +necessary to bring the current configuration to its desired end-state is +created. +""" + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, + param_list_to_dict, +) + +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.facts import Facts +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.vxlan_vtep import ( + Vxlan_vtepTemplate, +) + + +class Vxlan_vtep(ResourceModule): + """ + The ios_vxlan_vtep config class + """ + + def __init__(self, module): + super(Vxlan_vtep, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="vxlan_vtep", + tmplt=Vxlan_vtepTemplate(), + ) + self.parsers = [ + "source_interface", + "host_reachability_bgp", + ] + + 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 = self._interface_list_to_dict(self.want, self.have) + + # if state is merged, merge want onto have and then compare + if self.state == "merged": + wantd = dict_merge(haved, wantd) + + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + haved = {k: v for k, v in iteritems(haved) if k in wantd or not wantd} + wantd_copy = wantd.copy() + wantd = {} + + # remove superfluous config for deleted + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd: + have = self._filtered_dict(wantd_copy.get(k), have) + self._compare(want={}, have=have) + + for k, want in iteritems(wantd): + self._compare(want=want, have=haved.pop(k, {})) + + def _compare(self, want, have): + """Leverages the base class `compare()` method and + populates the list of commands to be run by comparing + the `want` and `have` data with the `parsers` defined + for the Vxlan_vtep network resource. + """ + + begin = len(self.commands) + self.compare(parsers=self.parsers, want=want, have=have) + + self._compare_member_vnis( + want.pop("member", {}).get("vni", {}), + have.pop("member", {}).get("vni", {}), + ) + + if len(self.commands) != begin: + self.commands.insert( + begin, + self._tmplt.render(want or have, "interface", False), + ) + + def _compare_member_vnis(self, wantmv, havemv): + """Compare member VNIs dict""" + + PARSER_DICT = { + "l2vni": "replication", + "l3vni": "vrf", + } + + for vni_type in ["l2vni", "l3vni"]: + wantd = wantmv.get(vni_type, {}) + haved = havemv.get(vni_type, {}) + undel_vnis = haved.copy() + + for wvni, want in wantd.items(): + have = haved.pop(wvni, {}) + if want != have: + # remove exiting config of the member VNI + self.addcmd(undel_vnis.pop(wvni, {}), PARSER_DICT[vni_type], True) + if vni_type == "l3vni": + undel_vnis = self._remove_existing_vnis_vrfs(want["vrf"], undel_vnis) + self.addcmd(want, PARSER_DICT[vni_type]) + + # remove remaining configs in have for replaced state + for hvni, have in haved.items(): + if hvni in undel_vnis: + self.addcmd(have, PARSER_DICT[vni_type], True) + + def _interface_list_to_dict(self, want, have): + """Convert all list of dicts to dicts of dicts""" + + wantd = {entry["interface"]: entry for entry in want} + haved = {entry["interface"]: entry for entry in have} + + for each in wantd, haved: + if each: + for nvi, nvid in each.items(): + member_vni = nvid.get("member", {}).get("vni") + if member_vni: + for vni_type in member_vni: + member_vni[vni_type] = param_list_to_dict( + member_vni[vni_type], + unique_key="vni", + remove_key=False, + ) + + return wantd, haved + + def _remove_existing_vnis_vrfs(self, want_vrf, haved): + """Remove member VNIs of corresponding VRF""" + + vrf_haved = next( + (h for h in haved.values() if h["vrf"] == want_vrf), + None, + ) + if vrf_haved: + self.addcmd(haved.pop(vrf_haved["vni"]), "vrf", True) + return haved + + def _filtered_dict(self, want, have): + """Remove other config from 'have' if 'member' key is present""" + + if "member" in want: + have_member = {} + want_vni_dict = want.get("member", {}).get("vni", {}) + have_vni_dict = have.get("member", {}).get("vni", {}) + + for vni_type, have_vnis in have_vni_dict.items(): + want_vnis = want_vni_dict.get(vni_type, {}) + have_member[vni_type] = { + vni: have_vni_dict[vni_type].get(vni) for vni in have_vnis if vni in want_vnis + } + have = { + "interface": have["interface"], + "member": {"vni": have_member}, + } + + return have diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/acls/acls.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/acls/acls.py index 6037d99e1..2be369a7a 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/acls/acls.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/acls/acls.py @@ -17,6 +17,7 @@ __metaclass__ = type import re +from ansible.module_utils._text import to_text from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( @@ -34,28 +35,49 @@ from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates class AclsFacts(object): """The ios_acls fact class""" - def __init__(self, module, subspec="config", options="options"): + def __init__(self, module): self._module = module self.argument_spec = AclsArgs.argument_spec def get_acl_data(self, connection): - # Get the access-lists from the ios router - # Get the remarks on access-lists from the ios router - # alternate command 'sh run partition access-list' but has a lot of ordering issues - # and incomplete ACLs are not viewed correctly - _acl_data = connection.get("show access-list") - _remarks_data = connection.get("show running-config | include ip(v6)* access-list|remark") - if _remarks_data: - _acl_data += "\n" + _remarks_data - return _acl_data + # Removed the show access-list + # Removed the show running-config | include ip(v6)* access-list|remark + return connection.get("show running-config | section access-list") + + def get_acl_names(self, connection): + # this information is required to scoop out the access lists which has no aces + return connection.get("show access-lists | include access list") + + def populate_empty_acls(self, raw_acls, raw_acls_name): + # this would update empty acls to the full acls entry + if raw_acls and raw_acls_name: + for aclnames, acldata in raw_acls_name.get("acls").items(): + if aclnames not in raw_acls.get("acls").keys(): + if not raw_acls.get("acls"): + raw_acls["acls"] = {} + raw_acls["acls"][aclnames] = acldata + elif raw_acls_name and not raw_acls: + for aclnames, acldata in raw_acls_name.get("acls").items(): + if not raw_acls.get("acls"): + raw_acls["acls"] = {} + raw_acls["acls"][aclnames] = acldata + return raw_acls def sanitize_data(self, data): """removes matches or extra config info that is added on acl match""" re_data = "" + remarks_idx = 0 for da in data.split("\n"): if "match" in da: mod_da = re.sub(r"\([^()]*\)", "", da) re_data += mod_da[:-1] + "\n" + elif re.match(r"\s*\d+\sremark.+", da, re.IGNORECASE) or re.match( + r"\s*remark.+", + da, + re.IGNORECASE, + ): + remarks_idx += 1 + re_data += to_text(remarks_idx) + " " + da + "\n" else: re_data += da + "\n" return re_data @@ -68,21 +90,30 @@ class AclsFacts(object): :rtype: dictionary :returns: facts """ + namedata = "" if not data: data = self.get_acl_data(connection) + namedata = self.get_acl_names(connection) if data: data = self.sanitize_data(data) - rmmod = NetworkTemplate(lines=data.splitlines(), tmplt=AclsTemplate()) - current = rmmod.parse() + # parse main information + templateObjMain = NetworkTemplate(lines=data.splitlines(), tmplt=AclsTemplate()) + raw_acls = templateObjMain.parse() + + if namedata: + # parse just names to update empty acls + templateObjName = NetworkTemplate(lines=namedata.splitlines(), tmplt=AclsTemplate()) + raw_acl_names = templateObjName.parse() + raw_acls = self.populate_empty_acls(raw_acls, raw_acl_names) temp_v4 = [] temp_v6 = [] - if current.get("acls"): - for k, v in iteritems(current.get("acls")): + if raw_acls.get("acls"): + for k, v in iteritems(raw_acls.get("acls")): if v.get("afi") == "ipv4" and v.get("acl_type") in ["standard", "extended"]: del v["afi"] temp_v4.append(v) @@ -99,6 +130,14 @@ class AclsFacts(object): _temp_addr = temp.get("address", "") ace[typ]["address"] = _temp_addr.split(" ")[0] ace[typ]["wildcard_bits"] = _temp_addr.split(" ")[1] + if temp.get("ipv6_address"): + _temp_addr = temp.get("ipv6_address", "") + if len(_temp_addr.split(" ")) == 2: + ipv6_add = ace[typ].pop("ipv6_address") + ace[typ]["address"] = ipv6_add.split(" ")[0] + ace[typ]["wildcard_bits"] = ipv6_add.split(" ")[1] + else: + ace[typ]["address"] = ace[typ].pop("ipv6_address") def process_protocol_options(each): for each_ace in each.get("aces"): @@ -131,14 +170,25 @@ class AclsFacts(object): def collect_remarks(aces): """makes remarks list per ace""" ace_entry = [] - rem = [] + ace_rem = [] + rem = {} for i in aces: - if i.get("remarks"): - rem.append(i.pop("remarks")) + if i.get("is_remark_for"): + if not rem.get(i.get("is_remark_for")): + rem[i.get("is_remark_for")] = {"remarks": []} + rem[i.get("is_remark_for")]["remarks"].append(i.get("the_remark")) + else: + rem[i.get("is_remark_for")]["remarks"].append(i.get("the_remark")) else: + if rem: + if rem.get(i.get("sequence")): + ace_rem = rem.pop(i.get("sequence")) + i["remarks"] = ace_rem.get("remarks") ace_entry.append(i) - if rem: - ace_entry.append({"remarks": rem}) + + if rem: # pending remarks + pending_rem = rem.get("remark") + ace_entry.append({"remarks": pending_rem.get("remarks")}) return ace_entry for each in temp_v4: @@ -148,7 +198,7 @@ class AclsFacts(object): for each in temp_v6: if each.get("aces"): - each["aces"] = collect_remarks(each.get("aces")) + # each["aces"] = collect_remarks(each.get("aces")) process_protocol_options(each) objs = [] diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/bgp_global/bgp_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/bgp_global/bgp_global.py index 68ff93636..37bbfabfd 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/bgp_global/bgp_global.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/bgp_global/bgp_global.py @@ -51,7 +51,10 @@ class Bgp_globalFacts(object): data = self.get_bgp_global_data(connection) # parse native config using the Bgp_global template - bgp_global_parser = Bgp_globalTemplate(lines=data.splitlines(), module=self._module) + bgp_global_parser = Bgp_globalTemplate( + lines=data.splitlines(), + module=self._module, + ) objs = bgp_global_parser.parse() neighbor_list = objs.get("neighbors", {}) if neighbor_list: @@ -64,7 +67,11 @@ class Bgp_globalFacts(object): ansible_facts["ansible_network_resources"].pop("bgp_global", None) params = utils.remove_empties( - bgp_global_parser.validate_config(self.argument_spec, {"config": obj}, redact=True), + bgp_global_parser.validate_config( + self.argument_spec, + {"config": obj}, + redact=True, + ), ) facts["bgp_global"] = params.get("config", {}) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_evi/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_evi/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_evi/evpn_evi.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_evi/evpn_evi.py new file mode 100644 index 000000000..be6cfb3bf --- /dev/null +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_evi/evpn_evi.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# Copyright 2023 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 ios evpn_evi fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.argspec.evpn_evi.evpn_evi import ( + Evpn_eviArgs, +) +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.evpn_evi import ( + Evpn_eviTemplate, +) + + +class Evpn_eviFacts(object): + """The ios evpn_evi facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Evpn_eviArgs.argument_spec + + def get_evpn_evi_data(self, connection): + return connection.get("show running-config | section ^l2vpn evpn instance .+$") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Evpn_evi 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_evpn_evi_data(connection) + + # parse native config using the Evpn_evi template + evpn_evi_parser = Evpn_eviTemplate(lines=data.splitlines(), module=self._module) + objs = list(evpn_evi_parser.parse().values()) + + ansible_facts["ansible_network_resources"].pop("evpn_evi", None) + + params = utils.remove_empties( + evpn_evi_parser.validate_config(self.argument_spec, {"config": objs}, redact=True), + ) + + facts["evpn_evi"] = params.get("config", []) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_global/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_global/evpn_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_global/evpn_global.py new file mode 100644 index 000000000..68ecfc711 --- /dev/null +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/evpn_global/evpn_global.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- +# Copyright 2023 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 ios evpn_global fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.argspec.evpn_global.evpn_global import ( + Evpn_globalArgs, +) +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.evpn_global import ( + Evpn_globalTemplate, +) + + +class Evpn_globalFacts(object): + """The ios evpn_global facts class""" + + def __init__(self, module): + self._module = module + self.argument_spec = Evpn_globalArgs.argument_spec + + def get_evpn_global_data(self, connection): + return connection.get("show running-config | section ^l2vpn evpn$") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Evpn_global 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_evpn_global_data(connection) + + # parse native config using the Evpn_global template + evpn_global_parser = Evpn_globalTemplate(lines=data.splitlines(), module=self._module) + objs = evpn_global_parser.parse() + obj = utils.remove_empties(objs) + + ansible_facts["ansible_network_resources"].pop("evpn_global", None) + + params = utils.remove_empties( + evpn_global_parser.validate_config(self.argument_spec, {"config": obj}, redact=True), + ) + + facts["evpn_global"] = params.get("config", {}) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/facts.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/facts.py index 6a28043bf..7718b474d 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/facts.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/facts.py @@ -29,6 +29,12 @@ from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.bgp_ad from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.bgp_global.bgp_global import ( Bgp_globalFacts, ) +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.evpn_evi.evpn_evi import ( + Evpn_eviFacts, +) +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.evpn_global.evpn_global import ( + Evpn_globalFacts, +) from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.hostname.hostname import ( HostnameFacts, ) @@ -93,6 +99,9 @@ from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.static from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.vlans.vlans import ( VlansFacts, ) +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.facts.vxlan_vtep.vxlan_vtep import ( + Vxlan_vtepFacts, +) FACT_LEGACY_SUBSETS = dict(default=Default, hardware=Hardware, interfaces=Interfaces, config=Config) @@ -122,6 +131,9 @@ FACT_RESOURCE_SUBSETS = dict( service=ServiceFacts, snmp_server=Snmp_serverFacts, hostname=HostnameFacts, + vxlan_vtep=Vxlan_vtepFacts, + evpn_global=Evpn_globalFacts, + evpn_evi=Evpn_eviFacts, ) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/legacy/base.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/legacy/base.py index de92f1ed5..5344ca627 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/legacy/base.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/legacy/base.py @@ -125,26 +125,31 @@ class Default(FactsBase): class Hardware(FactsBase): - COMMANDS = ["dir", "show memory statistics"] + COMMANDS = ["dir", "show memory statistics", "show processes cpu | include CPU utilization"] def populate(self): warnings = list() super(Hardware, self).populate() + data = self.responses[0] if data: self.facts["filesystems"] = self.parse_filesystems(data) self.facts["filesystems_info"] = self.parse_filesystems_info(data) + self.facts["cpu_utilization"] = self.parse_cpu_utilization(self.responses[2]) data = self.responses[1] if data: if "Invalid input detected" in data: warnings.append("Unable to gather memory statistics") else: - processor_line = [line for line in data.splitlines() if "Processor" in line].pop() - match = re.findall(r"\s(\d+)\s", processor_line) - if match: - self.facts["memtotal_mb"] = int(match[0]) / 1048576 - self.facts["memfree_mb"] = int(match[2]) / 1048576 + for line in data.splitlines(): + match = re.match( + r"Processor\s+(\S+|\d+)\s+(?P\d+)\s+\d+\s+(?P\d+)", + line, + ) + if match: + self.facts["memtotal_mb"] = int(match.group("total")) / 1048576 + self.facts["memfree_mb"] = int(match.group("free")) / 1048576 def parse_filesystems(self, data): return re.findall(r"^Directory of (\S+)/", data, re.M) @@ -164,6 +169,37 @@ class Hardware(FactsBase): facts[fs]["spacefree_kb"] = int(match.group(2)) / 1024 return facts + def parse_cpu_utilization(self, data): + facts = {} + regex_cpu_utilization = re.compile( + r""" + (^Core\s(?P\d+)?:)? + (^|\s)CPU\sutilization\sfor\sfive\sseconds: + (\s(?P\d+)?%)? + (\s(?P\d+)%/(?P\d+)%\)?)? + ;\sone\sminute:\s(?P\d+)?% + ;\sfive\sminutes:\s(?P\d+)?% + """, + re.VERBOSE, + ) + for line in data.split("\n"): + match_cpu_utilization = regex_cpu_utilization.match(line) + if match_cpu_utilization: + _core = "core" + if match_cpu_utilization.group("core"): + _core = "core_" + str(match_cpu_utilization.group("core")) + facts[_core] = {} + facts[_core]["five_seconds"] = int( + match_cpu_utilization.group("f_se_nom") or match_cpu_utilization.group("f_sec"), + ) + facts[_core]["one_minute"] = int(match_cpu_utilization.group("a_min")) + facts[_core]["five_minutes"] = int(match_cpu_utilization.group("f_min")) + if match_cpu_utilization.group("f_s_denom"): + facts[_core]["five_seconds_interrupt"] = int( + match_cpu_utilization.group("f_s_denom"), + ) + return facts + class Config(FactsBase): COMMANDS = ["show running-config"] diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/logging_global/logging_global.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/logging_global/logging_global.py index 639d43dba..5e955217c 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/logging_global/logging_global.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/logging_global/logging_global.py @@ -58,17 +58,17 @@ class Logging_globalFacts(object): if objFinal: for k, v in iteritems(objFinal): - if type(v) == list and k not in ["hosts", "source_interface", "filter"]: + if isinstance(v, list) and k not in ["hosts", "source_interface", "filter"]: v.sort() objFinal[k] = v - elif type(v) == list and k == "hosts": + elif isinstance(v, list) and k == "hosts": objFinal[k] = sorted( objFinal[k], key=lambda item: item["host"] if item.get("host") else item.get("ipv6"), ) - elif type(v) == list and k == "source_interface": + elif isinstance(v, list) and k == "source_interface": objFinal[k] = sorted(objFinal[k], key=lambda item: item["interface"]) - elif type(v) == list and k == "filter": + elif isinstance(v, list) and k == "filter": objFinal[k] = sorted(objFinal[k], key=lambda item: item["url"]) ansible_facts["ansible_network_resources"].pop("logging_global", None) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/ospfv2/ospfv2.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/ospfv2/ospfv2.py index 48a77de99..66fc35d2c 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/ospfv2/ospfv2.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/ospfv2/ospfv2.py @@ -37,6 +37,27 @@ class Ospfv2Facts(object): def get_ospfv2_data(self, connection): return connection.get("show running-config | section ^router ospf") + def dict_to_list(self, ospf_data): + """Converts areas, interfaces in each process to list + :param ospf_data: ospf data + :rtype: dictionary + :returns: facts_output + """ + + facts_output = {"processes": []} + + for process in ospf_data.get("processes", []): + if "passive_interfaces" in process and process["passive_interfaces"].get("default"): + if process.get("passive_interfaces", {}).get("interface"): + process["passive_interfaces"]["interface"]["name"] = [ + each for each in process["passive_interfaces"]["interface"]["name"] if each + ] + if "areas" in process: + process["areas"] = list(process["areas"].values()) + facts_output["processes"].append(process) + + return facts_output + def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for ospfv2 :param connection: the device connection @@ -45,42 +66,28 @@ class Ospfv2Facts(object): :rtype: dictionary :returns: facts """ + + facts = {} + if not data: data = self.get_ospfv2_data(connection) - ipv4 = {"processes": []} - rmmod = NetworkTemplate(lines=data.splitlines(), tmplt=Ospfv2Template()) - current = rmmod.parse() + ospf_temp_obj = NetworkTemplate(lines=data.splitlines(), tmplt=Ospfv2Template()) + ospf_parsed = ospf_temp_obj.parse() - # convert some of the dicts to lists - for key, sortv in [("processes", "process_id")]: - if key in current and current[key]: - current[key] = current[key].values() - current[key] = sorted(current[key], key=lambda k, sk=sortv: k[sk]) + # Convert dict to list + ospf_parsed["processes"] = ( + ospf_parsed["processes"].values() if "processes" in ospf_parsed else [] + ) - for process in current.get("processes", []): - if "passive_interfaces" in process and process["passive_interfaces"].get("default"): - if process["passive_interfaces"].get("interface"): - temp = [] - for each in process["passive_interfaces"]["interface"]["name"]: - if each: - temp.append(each) - process["passive_interfaces"]["interface"]["name"] = temp - if "areas" in process: - process["areas"] = list(process["areas"].values()) - process["areas"] = sorted(process["areas"], key=lambda k, sk="area_id": k[sk]) - for area in process["areas"]: - if "filters" in area: - area["filters"].sort() - ipv4["processes"].append(process) + # converts areas, interfaces in each process to list + facts_output = self.dict_to_list(ospf_parsed) ansible_facts["ansible_network_resources"].pop("ospfv2", None) - facts = {} - if current: - params = utils.validate_config(self.argument_spec, {"config": ipv4}) - params = utils.remove_empties(params) + if ospf_parsed["processes"]: + params = utils.validate_config(self.argument_spec, {"config": facts_output}) + params = utils.remove_empties(params) facts["ospfv2"] = params["config"] - ansible_facts["ansible_network_resources"].update(facts) return ansible_facts diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/prefix_lists/prefix_lists.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/prefix_lists/prefix_lists.py index 167f68c73..80a73e18c 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/prefix_lists/prefix_lists.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/prefix_lists/prefix_lists.py @@ -15,9 +15,6 @@ for a given resource, parsed, and the facts tree is populated based on the configuration. """ -from copy import copy - -from ansible.module_utils.six import iteritems from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils from ansible_collections.cisco.ios.plugins.module_utils.network.ios.argspec.prefix_lists.prefix_lists import ( @@ -59,50 +56,18 @@ class Prefix_listsFacts(object): objs = prefix_lists_parser.parse() final_objs = [] - temp = {} - temp["afi"] = None - temp["prefix_lists"] = [] + + _prefix_list = {"ipv4": [], "ipv6": []} if objs: - for k, v in iteritems(objs): - temp_prefix_list = {} - temp_prefix_list["entries"] = [] - if not temp["afi"] or v["afi"] != temp["afi"]: - if temp and temp["afi"]: - temp["prefix_lists"] = sorted( - temp["prefix_lists"], - key=lambda k, sk="name": str(k[sk]), - ) - # additional check for py3.5 - if len(final_objs) == 2: - for each in final_objs: - if v["afi"] == each["afi"]: - each["prefix_lists"].extend(temp["prefix_lists"]) - else: - final_objs.append(copy(temp)) - temp["prefix_lists"] = [] - temp["afi"] = v["afi"] - for each in v["prefix_lists"]: - if not temp_prefix_list.get("name"): - temp_prefix_list["name"] = each["name"] - if not temp_prefix_list.get("description") and each.get("description"): - temp_prefix_list["description"] = each["description"] - if each["entries"] and not each["entries"].get("description"): - temp_prefix_list["entries"].append(each["entries"]) - temp["prefix_lists"].append(temp_prefix_list) - if temp and temp["afi"]: - temp["prefix_lists"] = sorted( - temp["prefix_lists"], - key=lambda k, sk="name": str(k[sk]), + for prefixes in list(objs.values()): + _afi = prefixes.pop("afi") + _prefix_list[_afi].append( + prefixes, ) - # additional check for py3.5 - if len(final_objs) == 2: - for each in final_objs: - if v["afi"] == each["afi"]: - each["prefix_lists"].extend(temp["prefix_lists"]) - else: - final_objs.append(copy(temp)) - - final_objs = sorted(final_objs, key=lambda k, sk="afi": k[sk]) + if _prefix_list.get("ipv4"): + final_objs.append({"afi": "ipv4", "prefix_lists": _prefix_list.pop("ipv4")}) + if _prefix_list.get("ipv6"): + final_objs.append({"afi": "ipv6", "prefix_lists": _prefix_list.pop("ipv6")}) ansible_facts["ansible_network_resources"].pop("prefix_lists", None) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/snmp_server/snmp_server.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/snmp_server/snmp_server.py index 2fc1042e7..a153ac0a8 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/snmp_server/snmp_server.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/snmp_server/snmp_server.py @@ -15,6 +15,8 @@ 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.cisco.ios.plugins.module_utils.network.ios.argspec.snmp_server.snmp_server import ( @@ -33,7 +35,7 @@ class Snmp_serverFacts(object): self.argument_spec = Snmp_serverArgs.argument_spec def get_snmp_data(self, connection): - _get_snmp_data = connection.get("show running-config | section ^snmp-server") + _get_snmp_data = connection.get("show running-config | section ^snmp") return _get_snmp_data def get_snmpv3_user_data(self, connection): @@ -46,7 +48,12 @@ class Snmp_serverFacts(object): Note: The seperate method is needed because the snmpv3 user data is not returned within the snmp-server config """ - _get_snmpv3_user = connection.get("show snmp user") + try: + _get_snmpv3_user = connection.get("show snmp user") + except Exception as e: + if "agent not enabled" in str(e): + return "" + raise Exception("Unable to get snmp user data: %s" % str(e)) return _get_snmpv3_user def sort_list_dicts(self, objs): @@ -87,18 +94,36 @@ class Snmp_serverFacts(object): """ user_sets = snmpv3_user.split("User ") user_list = [] + re_snmp_auth = re.compile(r"^Authentication Protocol:\s*(MD5|SHA)") + re_snmp_priv = re.compile(r"^Privacy Protocol:\s*(3DES|AES|DES)([0-9]*)") + re_snmp_acl = re.compile(r"^.*active\s+(access-list: (\S+)|)\s*(IPv6 access-list: (\S+)|)") for user_set in user_sets: one_set = {} lines = user_set.splitlines() for line in lines: if line.startswith("name"): one_set["username"] = line.split(": ")[1] + continue if line.startswith("Group-name:"): one_set["group"] = line.split(": ")[1] - if "IPv6 access-list:" in line: - one_set["acl_v6"] = line.split(": ")[-1] - if "active\taccess-list:" in line: - one_set["acl_v4"] = line.split(": ")[-1] + continue + re_match = re_snmp_auth.search(line) + if re_match: + one_set["authentication"] = {"algorithm": re_match.group(1).lower()} + continue + re_match = re_snmp_priv.search(line) + if re_match: + one_set["encryption"] = {"priv": re_match.group(1).lower()} + if re_match.group(2): + one_set["encryption"]["priv_option"] = re_match.group(2) + continue + re_match = re_snmp_acl.search(line) + if re_match: + if re_match.group(2): + one_set["acl_v4"] = re_match.group(2) + if re_match.group(4): + one_set["acl_v6"] = re_match.group(4) + continue one_set["version"] = "v3" # defaults to version 3 data if len(one_set): user_list.append(one_set) diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vlans/vlans.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vlans/vlans.py index 73f73a5a2..9b506fa90 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vlans/vlans.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vlans/vlans.py @@ -16,6 +16,8 @@ from __future__ import absolute_import, division, print_function __metaclass__ = type +import re + from copy import deepcopy from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils @@ -42,14 +44,18 @@ class VlansFacts(object): self.generated_spec = utils.generate_dict(facts_argument_spec) - def get_vlans_data(self, connection): + def get_vlans_data(self, connection, configuration): """Checks device is L2/L3 and returns facts gracefully. Does not fail module. """ + if configuration: + cmd = "show running-config | sec ^vlan configuration .+" + else: + cmd = "show vlan" check_os_type = connection.get_device_info() if check_os_type.get("network_os_type") == "L3": return "" - return connection.get("show vlan") + return connection.get(cmd) def populate_facts(self, connection, ansible_facts, data=None): """Populate the facts for vlans @@ -59,14 +65,134 @@ class VlansFacts(object): :rtype: dictionary :returns: facts """ + configuration = self._module.params["configuration"] + objs = [] + + if not data: + data = self.get_vlans_data(connection, configuration) + if not configuration: + objs = self.parse_vlan(data) + else: + objs = self.parse_vlan_config(data) + + facts = {} + if objs: + facts["vlans"] = [] + params = utils.validate_config(self.argument_spec, {"config": objs}) + + for cfg in params["config"]: + facts["vlans"].append(utils.remove_empties(cfg)) + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def render_config(self, spec, conf, vlan_info): + """ + Render config as dictionary structure and delete keys + from spec for null values + + :param spec: The facts tree, generated from the argspec + :param conf: The configuration + :rtype: dictionary + :returns: The generated config + """ + config = deepcopy(spec) + + if vlan_info == "Name" and "VLAN Name" not in conf: + conf = list(filter(None, conf.split(" "))) + config["vlan_id"] = int(conf[0]) + config["name"] = conf[1] + state_idx = 2 + for i in range(2, len(conf)): # check for index where state starts + if conf[i] in ["suspended", "active"]: + state_idx = i + break + elif conf[i].split("/")[0] in ["sus", "act"]: + state_idx = i + break + config["name"] += " " + conf[i] + try: + if len(conf[state_idx].split("/")) > 1: + _state = conf[state_idx].split("/")[0] + if _state == "sus": + config["state"] = "suspend" + elif _state == "act": + config["state"] = "active" + config["shutdown"] = "enabled" + else: + if conf[state_idx] == "suspended": + config["state"] = "suspend" + elif conf[state_idx] == "active": + config["state"] = "active" + config["shutdown"] = "disabled" + except IndexError: + pass + elif vlan_info == "Type" and "VLAN Type" not in conf: + conf = list(filter(None, conf.split(" "))) + config["mtu"] = int(conf[3]) + elif vlan_info == "Remote": + if len(conf.split(",")) > 1 or conf.isdigit(): + remote_span_vlan = [] + if len(conf.split(",")) > 1: + remote_span_vlan = conf.split(",") + else: + remote_span_vlan.append(conf) + remote_span = [] + for each in remote_span_vlan: + split_sp_list = each.split("-") + if len(split_sp_list) > 1: # break range + for r_sp in range(int(split_sp_list[0]), int(split_sp_list[1]) + 1): + remote_span.append(r_sp) + else: + remote_span.append(int(each)) + config["remote_span"] = remote_span + + elif vlan_info == "Private" and "Primary Secondary" not in conf: + conf = list(filter(None, conf.split(" "))) + + pri_idx = 0 + sec_idx = 1 + priv_type_idx = 2 + + config["tmp_pvlans"] = { + "primary": conf[pri_idx], + "secondary": conf[sec_idx], + "sec_type": conf[priv_type_idx], + } + return utils.remove_empties(config) + + def parse_vlan_config(self, vlan_conf): + vlan_list = list() + + re1 = re.compile(r"^vlan configuration +(?P\d+)$") + re2 = re.compile(r"^member +(evpn\-instance +(?P\d+) )?vni (?P[\d\-]+)$") + + for line in vlan_conf.splitlines(): + line = line.strip() + m = re1.match(line) + if m: + vlan = m.groupdict()["vlan"] + vlan_dict = {"vlan_id": vlan} + continue + + m = re2.match(line) + if m: + group = m.groupdict() + vlan_dict.update({"member": {}}) + vlan_dict["member"].update({"vni": group["vni"]}) + if group["evi"]: + vlan_dict["member"].update({"evi": group["evi"]}) + vlan_list.append(vlan_dict) + + return vlan_list + + def parse_vlan(self, data): objs = [] mtu_objs = [] remote_objs = [] final_objs = [] pvlan_objs = [] - if not data: - data = self.get_vlans_data(connection) + # operate on a collection of resource x config = data.split("\n") # Get individual vlan configs separately @@ -124,6 +250,7 @@ class VlansFacts(object): pvlan_final = {} if len(pvlan_objs) > 0: # Sanitize and structure everything + for data in pvlan_objs: pvdata = data.get("tmp_pvlans") privlan = pvdata.get("primary") @@ -151,89 +278,7 @@ class VlansFacts(object): if vlan_id == every.get("vlan_id"): every.update(data) - facts = {} if final_objs: - facts["vlans"] = [] - params = utils.validate_config(self.argument_spec, {"config": objs}) - - for cfg in params["config"]: - facts["vlans"].append(utils.remove_empties(cfg)) - ansible_facts["ansible_network_resources"].update(facts) - - return ansible_facts - - def render_config(self, spec, conf, vlan_info): - """ - Render config as dictionary structure and delete keys - from spec for null values - - :param spec: The facts tree, generated from the argspec - :param conf: The configuration - :rtype: dictionary - :returns: The generated config - """ - config = deepcopy(spec) - - if vlan_info == "Name" and "VLAN Name" not in conf: - conf = list(filter(None, conf.split(" "))) - config["vlan_id"] = int(conf[0]) - config["name"] = conf[1] - state_idx = 2 - for i in range(2, len(conf)): # check for index where state starts - if conf[i] in ["suspended", "active"]: - state_idx = i - break - elif conf[i].split("/")[0] in ["sus", "act"]: - state_idx = i - break - config["name"] += " " + conf[i] - try: - if len(conf[state_idx].split("/")) > 1: - _state = conf[state_idx].split("/")[0] - if _state == "sus": - config["state"] = "suspend" - elif _state == "act": - config["state"] = "active" - config["shutdown"] = "enabled" - else: - if conf[state_idx] == "suspended": - config["state"] = "suspend" - elif conf[state_idx] == "active": - config["state"] = "active" - config["shutdown"] = "disabled" - except IndexError: - pass - elif vlan_info == "Type" and "VLAN Type" not in conf: - conf = list(filter(None, conf.split(" "))) - config["mtu"] = int(conf[3]) - elif vlan_info == "Remote": - if len(conf.split(",")) > 1 or conf.isdigit(): - remote_span_vlan = [] - if len(conf.split(",")) > 1: - remote_span_vlan = conf.split(",") - else: - remote_span_vlan.append(conf) - remote_span = [] - for each in remote_span_vlan: - split_sp_list = each.split("-") - if len(split_sp_list) > 1: # break range - for r_sp in range(int(split_sp_list[0]), int(split_sp_list[1]) + 1): - remote_span.append(r_sp) - else: - remote_span.append(int(each)) - config["remote_span"] = remote_span - - elif vlan_info == "Private" and "Primary Secondary" not in conf: - conf = list(filter(None, conf.split(" "))) - - pri_idx = 0 - sec_idx = 1 - priv_type_idx = 2 - - config["tmp_pvlans"] = { - "primary": conf[pri_idx], - "secondary": conf[sec_idx], - "sec_type": conf[priv_type_idx], - } - - return utils.remove_empties(config) + return objs + else: + return {} diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vxlan_vtep/__init__.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vxlan_vtep/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vxlan_vtep/vxlan_vtep.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vxlan_vtep/vxlan_vtep.py new file mode 100644 index 000000000..0027504e4 --- /dev/null +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/facts/vxlan_vtep/vxlan_vtep.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# Copyright 2023 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 ios vxlan_vtep fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils + +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.argspec.vxlan_vtep.vxlan_vtep import ( + Vxlan_vtepArgs, +) +from ansible_collections.cisco.ios.plugins.module_utils.network.ios.rm_templates.vxlan_vtep import ( + Vxlan_vtepTemplate, +) + + +class Vxlan_vtepFacts(object): + """The ios vxlan_vtep facts class""" + + def __init__(self, module): + self._module = module + self.argument_spec = Vxlan_vtepArgs.argument_spec + + def get_vxlan_vtep_data(self, connection): + return connection.get("show running-config | section ^interface nve") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Vxlan_vtep 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_vxlan_vtep_data(connection) + + # parse native config using the Vxlan_vtep template + vxlan_vtep_parser = Vxlan_vtepTemplate(lines=data.splitlines(), module=self._module) + objs = list(vxlan_vtep_parser.parse().values()) + + ansible_facts["ansible_network_resources"].pop("vxlan_vtep", None) + + params = utils.remove_empties( + vxlan_vtep_parser.validate_config(self.argument_spec, {"config": objs}, redact=True), + ) + + facts["vxlan_vtep"] = params.get("config", {}) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/acls.py b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/acls.py index 164c93caf..b3afd65f9 100644 --- a/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/acls.py +++ b/ansible_collections/cisco/ios/plugins/module_utils/network/ios/rm_templates/acls.py @@ -16,11 +16,21 @@ the given network resource. """ import re +from ansible.module_utils._text import to_text from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import ( NetworkTemplate, ) +def remarks_with_sequence(remarks_data): + cmd = "remark " + if remarks_data.get("remarks"): + cmd += remarks_data.get("remarks") + if remarks_data.get("sequence"): + cmd = to_text(remarks_data.get("sequence")) + " " + cmd + return cmd + + def _tmplt_access_list_entries(aces): def source_destination_common_config(config_data, command, attr): if config_data[attr].get("address"): @@ -63,7 +73,9 @@ def _tmplt_access_list_entries(aces): command += " {protocol_number}".format(**aces["protocol_options"]) else: command += " {0}".format(list(aces["protocol_options"])[0]) - proto_option = aces["protocol_options"].get(list(aces["protocol_options"])[0]) + proto_option = aces["protocol_options"].get( + list(aces["protocol_options"])[0], + ) elif aces.get("protocol"): command += " {protocol}".format(**aces) if aces.get("source"): @@ -120,7 +132,7 @@ class AclsTemplate(NetworkTemplate): PARSERS = [ { - "name": "acls_name", + "name": "only_acls_name", "getval": re.compile( r"""^(?PStandard|Extended|Reflexive)* \s*(?PIP|IPv6)* @@ -130,8 +142,7 @@ class AclsTemplate(NetworkTemplate): $""", re.VERBOSE, ), - "compval": "name", - "setval": "name", + "setval": "", "result": { "acls": { "{{ acl_name|d() }}": { @@ -141,80 +152,95 @@ class AclsTemplate(NetworkTemplate): }, }, }, - "shared": True, }, { - "name": "_acls_name", + "name": "acls_name", "getval": re.compile( - r"""^(ip|ipv6) + r"""^(?Pip|ipv6|mac) (\s(access-list)) - (\s(standard|extended)) - (\s(?P\S+))? + (\s(?Pstandard|extended|reflexive))? + (\s(?P\S+)) $""", re.VERBOSE, ), - "compval": "name", - "setval": "ip access-list", - "result": {}, + "setval": "name", + "result": { + "acls": { + "{{ acl_name|d() }}": { + "name": "{{ acl_name }}", + "acl_type": "{{ acl_type.lower() if acl_type is defined }}", + "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}", + }, + }, + }, "shared": True, }, { - "name": "_mac_acls_name", # + "name": "remarks", "getval": re.compile( - r"""^(?PStandard|Extended|Reflexive)* - \s*(?PMAC)* - \s*access - \s*list* - \s*(?P.+)* + r"""((?P^\d+)) + (\s*(?P\d+)) + (\sremark\s(?P.+)) $""", re.VERBOSE, ), - "compval": "name", - "setval": "", + "setval": remarks_with_sequence, "result": { "acls": { "{{ acl_name|d() }}": { "name": "{{ acl_name }}", - "acl_type": "{{ acl_type.lower() if acl_type is defined }}", - "afi": "{{ afi }}", + "aces": [ + { + "the_remark": "{{ remarks }}", + "order": "{{ order }}", + "is_remark_for": "{{ sequence }}", + }, + ], }, }, }, - "shared": True, }, { - "name": "remarks", + "name": "remarks_no_data", "getval": re.compile( - r"""\s+remark - (\s(?P.+))? - $""", + r"""(?P^\d+)\s*remark\s(?P.+)$""", re.VERBOSE, ), - "setval": "remark {{ remarks }}", + "setval": "{{ sequence }} remark", "result": { "acls": { - "{{ acl_name_r|d() }}": { - "name": "{{ acl_name_r }}", - "aces": [{"remarks": "{{ remarks }}"}], + "{{ acl_name|d() }}": { + "name": "{{ acl_name }}", + "aces": [ + { + "the_remark": "{{ remarks }}", + "order": "{{ order }}", + "is_remark_for": "remark", + }, + ], }, }, }, }, { - "name": "remarks_type_linear", + "name": "remarks_ipv6", "getval": re.compile( - r"""^(access-list) - (\s(?P\S+))? - (\sremark\s(?P.+))? + r"""\s*(sequence\s(?P\d+)) + (\sremark\s(?P.+)) $""", re.VERBOSE, ), "setval": "remark {{ remarks }}", "result": { "acls": { - "{{ acl_name_linear|d() }}": { - "name": "{{ acl_name_linear }}", - "aces": [{"remarks": "{{ remarks }}"}], + "{{ acl_name|d() }}": { + "name": "{{ acl_name }}", + "aces": [ + { + "sequence": "{{ sequence }}", + "remarks": ["{{ remarks }}"], + }, + ], }, }, }, @@ -222,12 +248,11 @@ class AclsTemplate(NetworkTemplate): { "name": "aces_ipv4_standard", "getval": re.compile( - r"""\s*(?P\d+)* - \s(?Pdeny|permit)? - (\s+(?P
(?!ahp|eigrp|esp|gre|icmp|igmp|ipv6|ipinip|ip|nos|object-group|ospf|pcp|pim|sctp|tcp|udp)\S+|\S+,))? + r"""(\s*(?P\d+))? + (\s(?Pdeny|permit)) + (\s+(?P
((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))? + (\s(?P((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)))? (\s*(?Pany))? - (\swildcard\sbits\s(?P\S+))? - (\shost\s(?P\S+))? (\s(?Plog))? $""", re.VERBOSE, @@ -245,7 +270,6 @@ class AclsTemplate(NetworkTemplate): "address": "{{ address }}", "wildcard_bits": "{{ wildcard }}", "any": "{{ not not any }}", - "host": "{{ host }}", }, "log": {"set": "{{ not not log }}"}, }, @@ -257,28 +281,39 @@ class AclsTemplate(NetworkTemplate): { "name": "aces", "getval": re.compile( - r"""\s*((?P\d+))? + r"""(\s*(?P\d+))? + (\s*sequence\s(?P\d+))? (\s*(?Pdeny|permit)) (\sevaluate\s(?P\S+))? (\s(?P\d+))? - (\s(?Pahp|eigrp|esp|gre|icmp|igmp|ipv6|ipinip|ip|nos|ospf|pcp|pim|sctp|tcp|udp))? + (\s*(?Pahp|eigrp|esp|gre|icmp|igmp|ipinip|ipv6|ip|nos|ospf|pcp|pim|sctp|tcp|ip|udp))? ((\s(?Pany))| (\sobject-group\s(?P\S+))| (\shost\s(?P\S+))| + (\s(?P\S+/\d+))| (\s(?P(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\s\S+)))? - (\s(?P(eq|gts|gt|lt|neq)\s(\S+|\d+)))? + (\seq\s(?P(\S+|\d+)))? + (\sgt\s(?P(\S+|\d+)))? + (\slt\s(?P(\S+|\d+)))? + (\sneq\s(?P(\S+|\d+)))? (\srange\s(?P\d+)\s(?P\d+))? (\s(?Pany))? (\sobject-group\s(?P\S+))? (\shost\s(?P\S+))? + (\s(?P\S+/\d+))? (\s(?P(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\s\S+))? - (\s(?P(eq|gts|lt|neq)\s(\S+|\d+)))? + (\seq\s(?P(\S+|\d+)))? + (\sgt\s(?P(\S+|\d+)))? + (\slt\s(?P(\S+|\d+)))? + (\sneq\s(?P(\S+|\d+)))? (\srange\s(?P\d+)\s(?P\d+))? (\s(?Padministratively-prohibited|alternate-address|conversion-error|dod-host-prohibited|dod-net-prohibited|echo-reply|echo|general-parameter-problem|host-isolated|host-precedence-unreachable|host-redirect|host-tos-redirect|host-tos-unreachable|host-unknown|host-unreachable|information-reply|information-request|mask-reply|mask-request|mobile-redirect|net-redirect|net-tos-redirect|net-tos-unreachable|net-unreachable|network-unknown|no-room-for-option|option-missing|packet-too-big|parameter-problem|port-unreachable|precedence-unreachable|protocol-unreachable|reassembly-timeout|redirect|router-advertisement|router-solicitation|source-quench|source-route-failed|time-exceeded|timestamp-reply|timestamp-request|traceroute|ttl-exceeded|unreachable|dvmrp|host-query|mtrace-resp|mtrace-route|pim|trace|v1host-report|v2host-report|v2leave-group|v3host-report|ack|established|fin|psh|rst|syn|urg))? (\sdscp\s(?P\S+))? (\s(?Pfragments))? - (\s(?Plog-input\s\(tag\s=\s\S+\)|log-input))? - (\s(?Plog\s\(tag\s=\s\S+\)|log))? + (\slog-input\s\(tag\s=\s(?P\S+\)|log-input))? + (\s(?Plog-input))? + (\slog\s\(tag\s=\s(?P\S+\)|log))? + (\s(?Plog))? (\soption\s(?P