summaryrefslogtreecommitdiffstats
path: root/ansible_collections/cisco/nxos/plugins
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 16:03:42 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-28 16:03:42 +0000
commit66cec45960ce1d9c794e9399de15c138acb18aed (patch)
tree59cd19d69e9d56b7989b080da7c20ef1a3fe2a5a /ansible_collections/cisco/nxos/plugins
parentInitial commit. (diff)
downloadansible-66cec45960ce1d9c794e9399de15c138acb18aed.tar.xz
ansible-66cec45960ce1d9c794e9399de15c138acb18aed.zip
Adding upstream version 7.3.0+dfsg.upstream/7.3.0+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/cisco/nxos/plugins')
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/aaa_server.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/aaa_server_host.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/acl.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/acl_interface.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/acl_interfaces.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/acls.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/banner.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/bfd_global.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/bfd_interfaces.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/bgp.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/bgp_address_family.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/bgp_af.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/bgp_global.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/bgp_neighbor.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_address_family.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_af.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/command.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/config.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/devicealias.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/evpn_global.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/evpn_vni.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/facts.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/feature.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/file_copy.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/gir.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/gir_profile_management.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/hostname.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/hsrp.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/hsrp_interfaces.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/igmp.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/igmp_interface.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/igmp_snooping.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/install_os.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/interface.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/interface_ospf.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/interfaces.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/l2_interface.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/l2_interfaces.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/l3_interface.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/l3_interfaces.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/lacp.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/lacp_interfaces.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/lag_interfaces.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/linkagg.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/lldp.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/lldp_global.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/lldp_interfaces.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/logging.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/logging_global.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/ntp.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/ntp_auth.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/ntp_global.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/ntp_options.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/nxapi.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/nxos.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/ospf.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/ospf_interfaces.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/ospf_vrf.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/ospfv2.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/ospfv3.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/overlay_global.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/pim.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/pim_interface.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/pim_rp_address.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/ping.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/prefix_lists.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/reboot.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/rollback.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/route_maps.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/rpm.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/smu.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/snapshot.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/snmp_community.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/snmp_contact.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/snmp_host.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/snmp_location.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/snmp_server.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/snmp_traps.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/snmp_user.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/static_route.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/static_routes.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/system.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/telemetry.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/udld.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/udld_interface.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/user.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vlan.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vlans.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vpc.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vpc_interface.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vrf.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vrf_af.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vrf_interface.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vrrp.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vsan.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vtp_domain.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vtp_password.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vtp_version.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vxlan_vtep.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/vxlan_vtep_vni.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/action/zone_zoneset.py125
-rw-r--r--ansible_collections/cisco/nxos/plugins/cliconf/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/cliconf/nxos.py424
-rw-r--r--ansible_collections/cisco/nxos/plugins/doc_fragments/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/doc_fragments/nxos.py21
-rw-r--r--ansible_collections/cisco/nxos/plugins/filter/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/httpapi/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/httpapi/nxos.py266
-rw-r--r--ansible_collections/cisco/nxos/plugins/inventory/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/acl_interfaces.py82
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/acls.py314
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/bfd_interfaces.py61
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/bgp_address_family.py250
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/bgp_global.py549
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/bgp_neighbor_address_family.py377
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/facts.py25
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/hostname.py50
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/hsrp_interfaces.py60
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/interfaces.py67
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/l2_interfaces.py73
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/l3_interfaces.py83
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/lacp.py71
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/lacp_interfaces.py76
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/lag_interfaces.py70
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/lldp_global.py88
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/lldp_interfaces.py67
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/logging_global.py276
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/ntp_global.py139
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/ospf_interfaces.py142
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/ospfv2.py622
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/ospfv3.py488
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/prefix_lists.py80
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/route_maps.py412
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/snmp_server.py411
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/static_routes.py89
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/telemetry.py115
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/vlans.py64
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/telemetry.py147
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/acl_interfaces.py321
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/acls.py674
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/bfd_interfaces.py311
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/bgp_address_family.py253
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/bgp_global.py410
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/bgp_neighbor_address_family.py232
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/hostname.py75
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/hsrp_interfaces.py286
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/interfaces.py492
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/l2_interfaces.py351
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/l3_interfaces.py545
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/lacp.py234
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/lacp_interfaces.py323
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py318
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/lldp_global.py277
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/lldp_interfaces.py312
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/logging_global.py199
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/ntp_global.py161
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/ospf_interfaces.py204
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/ospfv2.py216
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/ospfv3.py230
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/prefix_lists.py146
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/route_maps.py192
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/snmp_server.py243
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/static_routes.py567
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/telemetry.py593
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/vlans.py334
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/acl_interfaces.py129
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/acls.py327
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/bfd_interfaces.py104
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/bgp_address_family.py142
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/bgp_global.py130
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/bgp_neighbor_address_family.py133
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/facts.py188
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/hostname.py70
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/hsrp_interfaces.py96
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/interfaces.py110
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/l2_interfaces.py104
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/l3_interfaces.py135
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/lacp.py89
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/lacp_interfaces.py115
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/lag_interfaces.py104
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/base.py793
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/lldp_global.py107
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/lldp_interfaces.py128
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/logging_global.py91
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/ntp_global.py89
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/ospf_interfaces.py94
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/ospfv2.py94
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/ospfv3.py91
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/prefix_lists.py79
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/route_maps.py74
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/snmp_server.py85
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/static_routes.py230
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/telemetry.py185
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/vlans.py197
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/nxos.py1031
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_address_family.py798
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_global.py1536
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_neighbor_address_family.py894
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/hostname.py44
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/logging_global.py480
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ntp_global.py320
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospf_interfaces.py510
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv2.py1101
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv3.py945
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/prefix_lists.py102
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/route_maps.py1367
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/snmp_server.py1550
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/telemetry.py264
-rw-r--r--ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/utils.py214
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server.py336
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server_host.py370
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_acl_interfaces.py440
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_acls.py915
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_banner.py224
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_global.py333
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_interfaces.py302
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp.py761
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_address_family.py1031
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_af.py877
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_global.py1694
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor.py569
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_address_family.py1155
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_af.py781
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_command.py231
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_config.py579
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_devicealias.py550
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_global.py103
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_vni.py299
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_facts.py268
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_feature.py308
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_file_copy.py497
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_gir.py342
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_gir_profile_management.py214
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_hostname.py229
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp.py506
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp_interfaces.py264
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_igmp.py162
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_interface.py650
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_snooping.py319
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_install_os.py594
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_interfaces.py455
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_l2_interfaces.py408
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_l3_interfaces.py411
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_lacp.py276
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_lacp_interfaces.py381
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_lag_interfaces.py368
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_global.py345
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_interfaces.py263
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_logging.py940
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_logging_global.py735
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ntp.py446
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_auth.py336
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_global.py736
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_options.py173
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_nxapi.py424
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ospf_interfaces.py1455
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv2.py1984
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv3.py1702
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_overlay_global.py194
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_pim.py216
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py604
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_pim_rp_address.py248
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_ping.py255
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_prefix_lists.py842
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_reboot.py89
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_rollback.py130
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_route_maps.py1648
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_rpm.py396
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snapshot.py413
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_community.py254
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_contact.py151
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_host.py513
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_location.py156
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_server.py1480
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_traps.py319
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_user.py412
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_static_routes.py477
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_system.py399
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_telemetry.py339
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_udld.py254
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_udld_interface.py301
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_user.py473
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vlans.py439
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vpc.py470
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vpc_interface.py342
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vrf.py616
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_af.py274
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_interface.py267
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vrrp.py432
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vsan.py354
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_domain.py215
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_password.py277
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_version.py210
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep.py458
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep_vni.py452
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/nxos_zone_zoneset.py888
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/storage/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/storage/nxos_devicealias.py550
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/storage/nxos_vsan.py354
-rw-r--r--ansible_collections/cisco/nxos/plugins/modules/storage/nxos_zone_zoneset.py888
-rw-r--r--ansible_collections/cisco/nxos/plugins/netconf/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/netconf/nxos.py46
-rw-r--r--ansible_collections/cisco/nxos/plugins/terminal/__init__.py0
-rw-r--r--ansible_collections/cisco/nxos/plugins/terminal/nxos.py139
400 files changed, 88627 insertions, 0 deletions
diff --git a/ansible_collections/cisco/nxos/plugins/action/__init__.py b/ansible_collections/cisco/nxos/plugins/action/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/action/aaa_server.py b/ansible_collections/cisco/nxos/plugins/action/aaa_server.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/aaa_server.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/aaa_server_host.py b/ansible_collections/cisco/nxos/plugins/action/aaa_server_host.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/aaa_server_host.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/acl.py b/ansible_collections/cisco/nxos/plugins/action/acl.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/acl.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/acl_interface.py b/ansible_collections/cisco/nxos/plugins/action/acl_interface.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/acl_interface.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/acl_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/acl_interfaces.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/acl_interfaces.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/acls.py b/ansible_collections/cisco/nxos/plugins/action/acls.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/acls.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/banner.py b/ansible_collections/cisco/nxos/plugins/action/banner.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/banner.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/bfd_global.py b/ansible_collections/cisco/nxos/plugins/action/bfd_global.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/bfd_global.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/bfd_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/bfd_interfaces.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/bfd_interfaces.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp.py b/ansible_collections/cisco/nxos/plugins/action/bgp.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/bgp.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/action/bgp_address_family.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/bgp_address_family.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_af.py b/ansible_collections/cisco/nxos/plugins/action/bgp_af.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/bgp_af.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_global.py b/ansible_collections/cisco/nxos/plugins/action/bgp_global.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/bgp_global.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor.py b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_address_family.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_address_family.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_af.py b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_af.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/bgp_neighbor_af.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/command.py b/ansible_collections/cisco/nxos/plugins/action/command.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/command.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/config.py b/ansible_collections/cisco/nxos/plugins/action/config.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/config.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/devicealias.py b/ansible_collections/cisco/nxos/plugins/action/devicealias.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/devicealias.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/evpn_global.py b/ansible_collections/cisco/nxos/plugins/action/evpn_global.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/evpn_global.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/evpn_vni.py b/ansible_collections/cisco/nxos/plugins/action/evpn_vni.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/evpn_vni.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/facts.py b/ansible_collections/cisco/nxos/plugins/action/facts.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/facts.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/feature.py b/ansible_collections/cisco/nxos/plugins/action/feature.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/feature.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/file_copy.py b/ansible_collections/cisco/nxos/plugins/action/file_copy.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/file_copy.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/gir.py b/ansible_collections/cisco/nxos/plugins/action/gir.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/gir.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/gir_profile_management.py b/ansible_collections/cisco/nxos/plugins/action/gir_profile_management.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/gir_profile_management.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/hostname.py b/ansible_collections/cisco/nxos/plugins/action/hostname.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/hostname.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/hsrp.py b/ansible_collections/cisco/nxos/plugins/action/hsrp.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/hsrp.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/hsrp_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/hsrp_interfaces.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/hsrp_interfaces.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/igmp.py b/ansible_collections/cisco/nxos/plugins/action/igmp.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/igmp.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/igmp_interface.py b/ansible_collections/cisco/nxos/plugins/action/igmp_interface.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/igmp_interface.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/igmp_snooping.py b/ansible_collections/cisco/nxos/plugins/action/igmp_snooping.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/igmp_snooping.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/install_os.py b/ansible_collections/cisco/nxos/plugins/action/install_os.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/install_os.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/interface.py b/ansible_collections/cisco/nxos/plugins/action/interface.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/interface.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/interface_ospf.py b/ansible_collections/cisco/nxos/plugins/action/interface_ospf.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/interface_ospf.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/interfaces.py b/ansible_collections/cisco/nxos/plugins/action/interfaces.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/interfaces.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/l2_interface.py b/ansible_collections/cisco/nxos/plugins/action/l2_interface.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/l2_interface.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/l2_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/l2_interfaces.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/l2_interfaces.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/l3_interface.py b/ansible_collections/cisco/nxos/plugins/action/l3_interface.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/l3_interface.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/l3_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/l3_interfaces.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/l3_interfaces.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/lacp.py b/ansible_collections/cisco/nxos/plugins/action/lacp.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/lacp.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/lacp_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/lacp_interfaces.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/lacp_interfaces.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/lag_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/lag_interfaces.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/lag_interfaces.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/linkagg.py b/ansible_collections/cisco/nxos/plugins/action/linkagg.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/linkagg.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/lldp.py b/ansible_collections/cisco/nxos/plugins/action/lldp.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/lldp.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/lldp_global.py b/ansible_collections/cisco/nxos/plugins/action/lldp_global.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/lldp_global.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/lldp_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/lldp_interfaces.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/lldp_interfaces.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/logging.py b/ansible_collections/cisco/nxos/plugins/action/logging.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/logging.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/logging_global.py b/ansible_collections/cisco/nxos/plugins/action/logging_global.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/logging_global.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/ntp.py b/ansible_collections/cisco/nxos/plugins/action/ntp.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/ntp.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/ntp_auth.py b/ansible_collections/cisco/nxos/plugins/action/ntp_auth.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/ntp_auth.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/ntp_global.py b/ansible_collections/cisco/nxos/plugins/action/ntp_global.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/ntp_global.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/ntp_options.py b/ansible_collections/cisco/nxos/plugins/action/ntp_options.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/ntp_options.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/nxapi.py b/ansible_collections/cisco/nxos/plugins/action/nxapi.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/nxapi.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/nxos.py b/ansible_collections/cisco/nxos/plugins/action/nxos.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/nxos.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/ospf.py b/ansible_collections/cisco/nxos/plugins/action/ospf.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/ospf.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/action/ospf_interfaces.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/ospf_interfaces.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/ospf_vrf.py b/ansible_collections/cisco/nxos/plugins/action/ospf_vrf.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/ospf_vrf.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/ospfv2.py b/ansible_collections/cisco/nxos/plugins/action/ospfv2.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/ospfv2.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/ospfv3.py b/ansible_collections/cisco/nxos/plugins/action/ospfv3.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/ospfv3.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/overlay_global.py b/ansible_collections/cisco/nxos/plugins/action/overlay_global.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/overlay_global.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/pim.py b/ansible_collections/cisco/nxos/plugins/action/pim.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/pim.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/pim_interface.py b/ansible_collections/cisco/nxos/plugins/action/pim_interface.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/pim_interface.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/pim_rp_address.py b/ansible_collections/cisco/nxos/plugins/action/pim_rp_address.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/pim_rp_address.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/ping.py b/ansible_collections/cisco/nxos/plugins/action/ping.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/ping.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/prefix_lists.py b/ansible_collections/cisco/nxos/plugins/action/prefix_lists.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/prefix_lists.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/reboot.py b/ansible_collections/cisco/nxos/plugins/action/reboot.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/reboot.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/rollback.py b/ansible_collections/cisco/nxos/plugins/action/rollback.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/rollback.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/route_maps.py b/ansible_collections/cisco/nxos/plugins/action/route_maps.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/route_maps.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/rpm.py b/ansible_collections/cisco/nxos/plugins/action/rpm.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/rpm.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/smu.py b/ansible_collections/cisco/nxos/plugins/action/smu.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/smu.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/snapshot.py b/ansible_collections/cisco/nxos/plugins/action/snapshot.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/snapshot.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_community.py b/ansible_collections/cisco/nxos/plugins/action/snmp_community.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/snmp_community.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_contact.py b/ansible_collections/cisco/nxos/plugins/action/snmp_contact.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/snmp_contact.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_host.py b/ansible_collections/cisco/nxos/plugins/action/snmp_host.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/snmp_host.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_location.py b/ansible_collections/cisco/nxos/plugins/action/snmp_location.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/snmp_location.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_server.py b/ansible_collections/cisco/nxos/plugins/action/snmp_server.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/snmp_server.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_traps.py b/ansible_collections/cisco/nxos/plugins/action/snmp_traps.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/snmp_traps.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/snmp_user.py b/ansible_collections/cisco/nxos/plugins/action/snmp_user.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/snmp_user.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/static_route.py b/ansible_collections/cisco/nxos/plugins/action/static_route.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/static_route.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/static_routes.py b/ansible_collections/cisco/nxos/plugins/action/static_routes.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/static_routes.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/system.py b/ansible_collections/cisco/nxos/plugins/action/system.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/system.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/telemetry.py b/ansible_collections/cisco/nxos/plugins/action/telemetry.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/telemetry.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/udld.py b/ansible_collections/cisco/nxos/plugins/action/udld.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/udld.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/udld_interface.py b/ansible_collections/cisco/nxos/plugins/action/udld_interface.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/udld_interface.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/user.py b/ansible_collections/cisco/nxos/plugins/action/user.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/user.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vlan.py b/ansible_collections/cisco/nxos/plugins/action/vlan.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vlan.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vlans.py b/ansible_collections/cisco/nxos/plugins/action/vlans.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vlans.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vpc.py b/ansible_collections/cisco/nxos/plugins/action/vpc.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vpc.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vpc_interface.py b/ansible_collections/cisco/nxos/plugins/action/vpc_interface.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vpc_interface.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vrf.py b/ansible_collections/cisco/nxos/plugins/action/vrf.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vrf.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vrf_af.py b/ansible_collections/cisco/nxos/plugins/action/vrf_af.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vrf_af.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vrf_interface.py b/ansible_collections/cisco/nxos/plugins/action/vrf_interface.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vrf_interface.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vrrp.py b/ansible_collections/cisco/nxos/plugins/action/vrrp.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vrrp.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vsan.py b/ansible_collections/cisco/nxos/plugins/action/vsan.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vsan.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vtp_domain.py b/ansible_collections/cisco/nxos/plugins/action/vtp_domain.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vtp_domain.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vtp_password.py b/ansible_collections/cisco/nxos/plugins/action/vtp_password.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vtp_password.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vtp_version.py b/ansible_collections/cisco/nxos/plugins/action/vtp_version.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vtp_version.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep.py b/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep_vni.py b/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep_vni.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/vxlan_vtep_vni.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/action/zone_zoneset.py b/ansible_collections/cisco/nxos/plugins/action/zone_zoneset.py
new file mode 100644
index 00000000..3602c407
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/action/zone_zoneset.py
@@ -0,0 +1,125 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible import constants as C
+from ansible.module_utils.connection import Connection
+from ansible.utils.display import Display
+from ansible_collections.ansible.netcommon.plugins.action.network import (
+ ActionModule as ActionNetworkModule,
+)
+
+
+display = Display()
+
+
+class ActionModule(ActionNetworkModule):
+ def run(self, tmp=None, task_vars=None):
+ del tmp # tmp no longer has any effect
+
+ module_name = self._task.action.split(".")[-1]
+ self._config_module = True if module_name in ["nxos_config", "config"] else False
+ persistent_connection = self._play_context.connection.split(".")[-1]
+
+ warnings = []
+
+ if (self._play_context.connection == "httpapi") and module_name in (
+ "nxos_file_copy",
+ "nxos_nxapi",
+ ):
+ return {
+ "failed": True,
+ "msg": "Connection httpapi is not valid for '%s' module." % (module_name),
+ }
+
+ if module_name == "nxos_file_copy":
+ # when file_pull is enabled, the file_pull_timeout and connect_ssh_port options
+ # will override persistent_command_timeout and port
+ # this has been kept for backwards compatibility till these options are removed
+ if persistent_connection != "network_cli":
+ return {
+ "failed": True,
+ "msg": "Connection type must be fully qualified name for network_cli connection type, got %s"
+ % self._play_context.connection,
+ }
+
+ conn = Connection(self._connection.socket_path)
+ file_pull = self._task.args.get("file_pull", False)
+ file_pull_timeout = self._task.args.get("file_pull_timeout", 300)
+ connect_ssh_port = self._task.args.get("connect_ssh_port", 22)
+
+ if file_pull:
+ conn.set_option("persistent_command_timeout", file_pull_timeout)
+ conn.set_option("port", connect_ssh_port)
+
+ if module_name == "nxos_install_os":
+ connection = self._connection
+ if connection.transport == "local":
+ persistent_command_timeout = C.PERSISTENT_COMMAND_TIMEOUT
+ persistent_connect_timeout = C.PERSISTENT_CONNECT_TIMEOUT
+ else:
+ persistent_command_timeout = connection.get_option("persistent_command_timeout")
+ persistent_connect_timeout = connection.get_option("persistent_connect_timeout")
+
+ display.vvvv(
+ "PERSISTENT_COMMAND_TIMEOUT is %s" % str(persistent_command_timeout),
+ self._play_context.remote_addr,
+ )
+ display.vvvv(
+ "PERSISTENT_CONNECT_TIMEOUT is %s" % str(persistent_connect_timeout),
+ self._play_context.remote_addr,
+ )
+ if persistent_command_timeout < 600 or persistent_connect_timeout < 600:
+ msg = "PERSISTENT_COMMAND_TIMEOUT and PERSISTENT_CONNECT_TIMEOUT"
+ msg += " must be set to 600 seconds or higher when using nxos_install_os module."
+ msg += " Current persistent_command_timeout setting:" + str(
+ persistent_command_timeout,
+ )
+ msg += " Current persistent_connect_timeout setting:" + str(
+ persistent_connect_timeout,
+ )
+ return {"failed": True, "msg": msg}
+
+ if persistent_connection in ("network_cli", "httpapi"):
+ if module_name == "nxos_gir":
+ conn = Connection(self._connection.socket_path)
+ persistent_command_timeout = conn.get_option("persistent_command_timeout")
+ gir_timeout = 200
+ if persistent_command_timeout < gir_timeout:
+ conn.set_option("persistent_command_timeout", gir_timeout)
+ msg = "timeout value extended to %ss for nxos_gir" % gir_timeout
+ display.warning(msg)
+
+ else:
+ return {
+ "failed": True,
+ "msg": "Connection type %s is not valid for this module"
+ % self._play_context.connection,
+ }
+
+ result = super(ActionModule, self).run(task_vars=task_vars)
+ if warnings:
+ if "warnings" in result:
+ result["warnings"].extend(warnings)
+ else:
+ result["warnings"] = warnings
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/cliconf/__init__.py b/ansible_collections/cisco/nxos/plugins/cliconf/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/cliconf/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/cliconf/nxos.py b/ansible_collections/cisco/nxos/plugins/cliconf/nxos.py
new file mode 100644
index 00000000..1b3bd909
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/cliconf/nxos.py
@@ -0,0 +1,424 @@
+#
+# (c) 2017 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+author: Ansible Networking Team (@ansible-network)
+name: nxos
+short_description: Use NX-OS cliconf to run commands on Cisco NX-OS platform
+description:
+- This nxos plugin provides low level abstraction apis for sending and receiving CLI
+ commands from Cisco NX-OS network devices.
+version_added: 1.0.0
+options:
+ config_commands:
+ description:
+ - Specifies a list of commands that can make configuration changes
+ to the target device.
+ - When `ansible_network_single_user_mode` is enabled, if a command sent
+ to the device is present in this list, the existing cache is invalidated.
+ version_added: 2.0.0
+ type: list
+ elements: str
+ default: []
+ vars:
+ - name: ansible_nxos_config_commands
+"""
+
+import json
+import re
+
+from ansible.errors import AnsibleConnectionFailure
+from ansible.module_utils._text import to_bytes, to_text
+from ansible.module_utils.common._collections_compat import Mapping
+from ansible.module_utils.connection import ConnectionError
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ NetworkConfig,
+ dumps,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
+from ansible_collections.ansible.netcommon.plugins.plugin_utils.cliconf_base import CliconfBase
+
+
+class Cliconf(CliconfBase):
+ def __init__(self, *args, **kwargs):
+ self._module_context = {}
+ self._device_info = {}
+ super(Cliconf, self).__init__(*args, **kwargs)
+
+ def read_module_context(self, module_key):
+ if self._module_context.get(module_key):
+ return self._module_context[module_key]
+
+ return None
+
+ def save_module_context(self, module_key, module_context):
+ self._module_context[module_key] = module_context
+
+ return None
+
+ def get_device_info(self):
+ if not self._device_info:
+ device_info = {}
+
+ device_info["network_os"] = "nxos"
+ reply = self.get("show version")
+ platform_reply = self.get("show inventory")
+
+ match_sys_ver = re.search(r"\s+system:\s+version\s*(\S+)", reply, re.M)
+ if match_sys_ver:
+ device_info["network_os_version"] = match_sys_ver.group(1)
+ else:
+ match_kick_ver = re.search(r"\s+kickstart:\s+version\s*(\S+)", reply, re.M)
+ if match_kick_ver:
+ device_info["network_os_version"] = match_kick_ver.group(1)
+
+ if "network_os_version" not in device_info:
+ match_sys_ver = re.search(r"\s+NXOS:\s+version\s*(\S+)", reply, re.M)
+ if match_sys_ver:
+ device_info["network_os_version"] = match_sys_ver.group(1)
+
+ match_chassis_id = re.search(r"Hardware\n\s+cisco(.+)$", reply, re.M)
+ if match_chassis_id:
+ device_info["network_os_model"] = match_chassis_id.group(1).strip()
+
+ match_host_name = re.search(r"\s+Device name:\s*(\S+)", reply, re.M)
+ if match_host_name:
+ device_info["network_os_hostname"] = match_host_name.group(1)
+
+ match_isan_file_name = re.search(r"\s+system image file is:\s*(\S+)", reply, re.M)
+ if match_isan_file_name:
+ device_info["network_os_image"] = match_isan_file_name.group(1)
+ else:
+ match_kick_file_name = re.search(
+ r"\s+kickstart image file is:\s*(\S+)",
+ reply,
+ re.M,
+ )
+ if match_kick_file_name:
+ device_info["network_os_image"] = match_kick_file_name.group(1)
+
+ if "network_os_image" not in device_info:
+ match_isan_file_name = re.search(r"\s+NXOS image file is:\s*(\S+)", reply, re.M)
+ if match_isan_file_name:
+ device_info["network_os_image"] = match_isan_file_name.group(1)
+
+ match_os_platform = re.search(
+ r'NAME: "Chassis",\s*DESCR:.*\nPID:\s*(\S+)',
+ platform_reply,
+ re.M,
+ )
+ if match_os_platform:
+ device_info["network_os_platform"] = match_os_platform.group(1)
+
+ self._device_info = device_info
+
+ return self._device_info
+
+ def get_diff(
+ self,
+ candidate=None,
+ running=None,
+ diff_match="line",
+ diff_ignore_lines=None,
+ path=None,
+ diff_replace="line",
+ ):
+ diff = {}
+ device_operations = self.get_device_operations()
+ option_values = self.get_option_values()
+
+ if candidate is None and device_operations["supports_generate_diff"]:
+ raise ValueError("candidate configuration is required to generate diff")
+
+ if diff_match not in option_values["diff_match"]:
+ raise ValueError(
+ "'match' value %s in invalid, valid values are %s"
+ % (diff_match, ", ".join(option_values["diff_match"])),
+ )
+
+ if diff_replace not in option_values["diff_replace"]:
+ raise ValueError(
+ "'replace' value %s in invalid, valid values are %s"
+ % (diff_replace, ", ".join(option_values["diff_replace"])),
+ )
+
+ # prepare candidate configuration
+ candidate_obj = NetworkConfig(indent=2)
+ candidate_obj.load(candidate)
+
+ if running and diff_match != "none" and diff_replace != "config":
+ # running configuration
+ running_obj = NetworkConfig(indent=2, contents=running, ignore_lines=diff_ignore_lines)
+ configdiffobjs = candidate_obj.difference(
+ running_obj,
+ path=path,
+ match=diff_match,
+ replace=diff_replace,
+ )
+
+ else:
+ configdiffobjs = candidate_obj.items
+
+ diff["config_diff"] = dumps(configdiffobjs, "commands") if configdiffobjs else ""
+ return diff
+
+ def get_config(self, source="running", format="text", flags=None):
+ options_values = self.get_option_values()
+ if format not in options_values["format"]:
+ raise ValueError(
+ "'format' value %s is invalid. Valid values are %s"
+ % (format, ",".join(options_values["format"])),
+ )
+
+ lookup = {"running": "running-config", "startup": "startup-config"}
+ if source not in lookup:
+ raise ValueError("fetching configuration from %s is not supported" % source)
+
+ cmd = "show {0} ".format(lookup[source])
+ if format and format != "text":
+ cmd += "| %s " % format
+
+ if flags:
+ cmd += " ".join(to_list(flags))
+ cmd = cmd.strip()
+
+ return self.send_command(cmd)
+
+ def edit_config(self, candidate=None, commit=True, replace=None, comment=None):
+ resp = {}
+ operations = self.get_device_operations()
+ self.check_edit_config_capability(operations, candidate, commit, replace, comment)
+ results = []
+ requests = []
+
+ if replace:
+ device_info = self.get_device_info()
+ # not all NX-OS versions support `config replace`
+ # we let the device throw the invalid command error
+ candidate = "config replace {0}".format(replace)
+
+ if commit:
+ self.send_command("configure terminal")
+
+ for line in to_list(candidate):
+ if not isinstance(line, Mapping):
+ line = {"command": line}
+
+ cmd = line["command"]
+ if cmd != "end":
+ results.append(self.send_command(**line))
+ requests.append(cmd)
+
+ self.send_command("end")
+ else:
+ raise ValueError("check mode is not supported")
+
+ resp["request"] = requests
+ resp["response"] = results
+ return resp
+
+ def get(
+ self,
+ command,
+ prompt=None,
+ answer=None,
+ sendonly=False,
+ output=None,
+ newline=True,
+ check_all=False,
+ ):
+ if output:
+ command = self._get_command_with_output(command, output)
+ return self.send_command(
+ command=command,
+ prompt=prompt,
+ answer=answer,
+ sendonly=sendonly,
+ newline=newline,
+ check_all=check_all,
+ )
+
+ def run_commands(self, commands=None, check_rc=True):
+ if commands is None:
+ raise ValueError("'commands' value is required")
+
+ responses = list()
+ for cmd in to_list(commands):
+ if not isinstance(cmd, Mapping):
+ cmd = {"command": cmd}
+
+ output = cmd.pop("output", None)
+ if output:
+ cmd["command"] = self._get_command_with_output(cmd["command"], output)
+
+ try:
+ out = self.send_command(**cmd)
+ except AnsibleConnectionFailure as e:
+ if check_rc is True:
+ raise
+ out = getattr(e, "err", e)
+
+ if out is not None:
+ try:
+ out = to_text(out, errors="surrogate_or_strict").strip()
+ except UnicodeError:
+ raise ConnectionError(
+ message="Failed to decode output from %s: %s" % (cmd, to_text(out)),
+ )
+
+ try:
+ out = json.loads(out)
+ except ValueError:
+ pass
+
+ responses.append(out)
+ return responses
+
+ def get_device_operations(self):
+ return {
+ "supports_diff_replace": True,
+ "supports_commit": False,
+ "supports_rollback": False,
+ "supports_defaults": True,
+ "supports_onbox_diff": False,
+ "supports_commit_comment": False,
+ "supports_multiline_delimiter": False,
+ "supports_diff_match": True,
+ "supports_diff_ignore_lines": True,
+ "supports_generate_diff": True,
+ "supports_replace": True,
+ }
+
+ def get_option_values(self):
+ return {
+ "format": ["text", "json"],
+ "diff_match": ["line", "strict", "exact", "none"],
+ "diff_replace": ["line", "block", "config"],
+ "output": ["text", "json"],
+ }
+
+ def get_capabilities(self):
+ result = super(Cliconf, self).get_capabilities()
+ result["rpc"] += ["get_diff", "run_commands"]
+ result["device_operations"] = self.get_device_operations()
+ result.update(self.get_option_values())
+
+ return json.dumps(result)
+
+ def pull_file(self, command, remotepassword=None):
+ possible_errors_re = [
+ re.compile(rb"timed out"),
+ re.compile(rb"(?i)No space.*#"),
+ re.compile(rb"(?i)Permission denied.*#"),
+ re.compile(rb"(?i)No such file.*#"),
+ re.compile(rb"Compaction is not supported on this platform.*#"),
+ re.compile(rb"Compact of.*failed.*#"),
+ re.compile(rb"(?i)Could not resolve hostname"),
+ re.compile(rb"(?i)Too many authentication failures"),
+ re.compile(rb"Access Denied"),
+ re.compile(rb"(?i)Copying to\/from this server name is not permitted"),
+ ]
+
+ # set error regex for copy command
+ current_stderr_re = self._connection._get_terminal_std_re("terminal_stderr_re")
+ current_stderr_re.extend(possible_errors_re)
+
+ # do not change the ordering of this list
+ possible_prompts_re = [
+ re.compile(rb"file existing with this name"),
+ re.compile(rb"sure you want to continue connecting"),
+ re.compile(rb"(?i)Password:.*"),
+ ]
+
+ # set stdout regex for copy command to handle optional user prompts
+ # based on different match conditions
+ current_stdout_re = self._connection._get_terminal_std_re("terminal_stdout_re")
+ current_stdout_re.extend(possible_prompts_re)
+
+ retry = 1
+ file_pulled = False
+
+ try:
+ while not file_pulled and retry <= 6:
+ retry += 1
+ output = self.send_command(command=command, strip_prompt=False)
+
+ if possible_prompts_re[0].search(to_bytes(output)):
+ output = self.send_command(command="y", strip_prompt=False)
+
+ if possible_prompts_re[1].search(to_bytes(output)):
+ output = self.send_command(command="yes", strip_prompt=False)
+
+ if possible_prompts_re[2].search(to_bytes(output)):
+ output = self.send_command(command=remotepassword, strip_prompt=False)
+ if "Copy complete" in output:
+ file_pulled = True
+ return file_pulled
+ finally:
+ # always reset terminal regexes to default
+ for x in possible_prompts_re:
+ current_stdout_re.remove(x)
+ for x in possible_errors_re:
+ current_stderr_re.remove(x)
+
+ def set_cli_prompt_context(self):
+ """
+ Make sure we are in the operational cli context
+ :return: None
+ """
+ if self._connection.connected:
+ out = self._connection.get_prompt()
+ if out is None:
+ raise AnsibleConnectionFailure(
+ message="cli prompt is not identified from the last received"
+ " response window: %s" % self._connection._last_recv_window,
+ )
+ # Match prompts ending in )# except those with (maint-mode)#
+ config_prompt = re.compile(r"^.*\((?!maint-mode).*\)#$")
+
+ while config_prompt.match(to_text(out, errors="surrogate_then_replace").strip()):
+ self._connection.queue_message("vvvv", "wrong context, sending exit to device")
+ self._connection.send_command("exit")
+ out = self._connection.get_prompt()
+
+ def _get_command_with_output(self, command, output):
+ options_values = self.get_option_values()
+ if output not in options_values["output"]:
+ raise ValueError(
+ "'output' value %s is invalid. Valid values are %s"
+ % (output, ",".join(options_values["output"])),
+ )
+
+ if output == "json" and not command.endswith("| json"):
+ device_info = self.get_device_info()
+ model = device_info.get("network_os_model", "")
+ platform = device_info.get("network_os_platform", "")
+ if platform.startswith("DS-") and "MDS" in model:
+ cmd = "%s | json native" % command
+ else:
+ cmd = "%s | json" % command
+ elif output == "text" and command.endswith("| json"):
+ cmd = command.rsplit("|", 1)[0]
+ else:
+ cmd = command
+ return cmd
diff --git a/ansible_collections/cisco/nxos/plugins/doc_fragments/__init__.py b/ansible_collections/cisco/nxos/plugins/doc_fragments/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/doc_fragments/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/doc_fragments/nxos.py b/ansible_collections/cisco/nxos/plugins/doc_fragments/nxos.py
new file mode 100644
index 00000000..d3c6afe5
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/doc_fragments/nxos.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# Copyright: (c) 2015, Peter Sprygada <psprygada@ansible.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+class ModuleDocFragment(object):
+ # Standard files documentation fragment
+ DOCUMENTATION = r"""options: {}
+notes:
+ - For information on using CLI and NX-API see the :ref:`NXOS Platform Options guide
+ <nxos_platform_options>`
+ - For more information on using Ansible to manage network devices see the :ref:`Ansible
+ Network Guide <network_guide>`
+ - For more information on using Ansible to manage Cisco devices see the `Cisco integration
+ page <https://www.ansible.com/integrations/networks/cisco>`_.
+"""
diff --git a/ansible_collections/cisco/nxos/plugins/filter/__init__.py b/ansible_collections/cisco/nxos/plugins/filter/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/filter/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/httpapi/__init__.py b/ansible_collections/cisco/nxos/plugins/httpapi/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/httpapi/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/httpapi/nxos.py b/ansible_collections/cisco/nxos/plugins/httpapi/nxos.py
new file mode 100644
index 00000000..57421e83
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/httpapi/nxos.py
@@ -0,0 +1,266 @@
+# (c) 2018 Red Hat Inc.
+# 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
+
+DOCUMENTATION = """
+author: Ansible Networking Team (@ansible-network)
+name: nxos
+short_description: Use NX-API to run commands on Cisco NX-OS platform
+description:
+- This plugin provides low level abstraction APIs for sending and receiving
+ commands using NX-API with devices running Cisco NX-OS.
+version_added: 1.0.0
+"""
+
+import collections
+import json
+import re
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.connection import ConnectionError
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
+from ansible_collections.ansible.netcommon.plugins.plugin_utils.httpapi_base import HttpApiBase
+
+
+OPTIONS = {
+ "format": ["text", "json"],
+ "diff_match": ["line", "strict", "exact", "none"],
+ "diff_replace": ["line", "block", "config"],
+ "output": ["text", "json"],
+}
+
+
+class HttpApi(HttpApiBase):
+ def __init__(self, *args, **kwargs):
+ super(HttpApi, self).__init__(*args, **kwargs)
+ self._device_info = None
+ self._module_context = {}
+
+ def read_module_context(self, module_key):
+ if self._module_context.get(module_key):
+ return self._module_context[module_key]
+
+ return None
+
+ def save_module_context(self, module_key, module_context):
+ self._module_context[module_key] = module_context
+
+ return None
+
+ def send_request(self, data, **message_kwargs):
+ output = None
+ queue = list()
+ responses = list()
+
+ for item in to_list(data):
+ cmd_output = message_kwargs.get("output") or "text"
+ if isinstance(item, dict):
+ command = item["command"]
+ if "output" in item:
+ cmd_output = item["output"]
+ else:
+ command = item
+
+ # Emulate '| json' from CLI
+ if command.endswith("| json"):
+ command = command.rsplit("|", 1)[0]
+ cmd_output = "json"
+
+ if output and output != cmd_output:
+ responses.extend(self._run_queue(queue, output))
+ queue = list()
+
+ output = cmd_output
+ queue.append(command)
+
+ if queue:
+ responses.extend(self._run_queue(queue, output))
+
+ if len(responses) == 1:
+ return responses[0]
+ return responses
+
+ def _run_queue(self, queue, output):
+ if self._become:
+ self.connection.queue_message(
+ "warning",
+ "become has no effect over httpapi. Use network_cli if privilege escalation is required",
+ )
+
+ request = request_builder(queue, output)
+ headers = {"Content-Type": "application/json"}
+
+ response, response_data = self.connection.send(
+ "/ins",
+ request,
+ headers=headers,
+ method="POST",
+ )
+
+ try:
+ response_data = json.loads(to_text(response_data.getvalue()))
+ except ValueError:
+ raise ConnectionError(
+ "Response was not valid JSON, got {0}".format(to_text(response_data.getvalue())),
+ )
+
+ results = handle_response(response_data)
+ return results
+
+ def get_device_info(self):
+ if self._device_info:
+ return self._device_info
+
+ device_info = {}
+
+ device_info["network_os"] = "nxos"
+ reply, platform_reply = self.send_request(["show version", "show inventory"])
+
+ find_os_version = [
+ r"\s+system:\s+version\s*(\S+)",
+ r"\s+kickstart:\s+version\s*(\S+)",
+ r"\s+NXOS:\s+version\s*(\S+)",
+ ]
+ for regex in find_os_version:
+ match_ver = re.search(regex, reply, re.M)
+ if match_ver:
+ device_info["network_os_version"] = match_ver.group(1)
+ break
+
+ match_chassis_id = re.search(r"Hardware\n\s+cisco\s*(\S+\s+\S+)", reply, re.M)
+ if match_chassis_id:
+ device_info["network_os_model"] = match_chassis_id.group(1)
+
+ match_host_name = re.search(r"\s+Device name:\s*(\S+)", reply, re.M)
+ if match_host_name:
+ device_info["network_os_hostname"] = match_host_name.group(1)
+
+ find_os_image = [
+ r"\s+system image file is:\s*(\S+)",
+ r"\s+kickstart image file is:\s*(\S+)",
+ r"\s+NXOS image file is:\s*(\S+)",
+ ]
+ for regex in find_os_image:
+ match_file_name = re.search(regex, reply, re.M)
+ if match_file_name:
+ device_info["network_os_image"] = match_file_name.group(1)
+ break
+
+ match_os_platform = re.search(
+ r'NAME: (?:"Chassis"| Chassis ),\s*DESCR:.*\nPID:\s*(\S+)',
+ platform_reply,
+ re.M,
+ )
+ if match_os_platform:
+ device_info["network_os_platform"] = match_os_platform.group(1)
+
+ self._device_info = device_info
+ return self._device_info
+
+ def get_device_operations(self):
+ platform = self.get_device_info().get("network_os_platform", "")
+ return {
+ "supports_diff_replace": True,
+ "supports_commit": False,
+ "supports_rollback": False,
+ "supports_defaults": True,
+ "supports_onbox_diff": False,
+ "supports_commit_comment": False,
+ "supports_multiline_delimiter": False,
+ "supports_diff_match": True,
+ "supports_diff_ignore_lines": True,
+ "supports_generate_diff": True,
+ "supports_replace": True if "9K" in platform else False,
+ }
+
+ def get_capabilities(self):
+ result = {}
+ result["rpc"] = []
+ result["device_info"] = self.get_device_info()
+ result["device_operations"] = self.get_device_operations()
+ result.update(OPTIONS)
+ result["network_api"] = "nxapi"
+
+ return json.dumps(result)
+
+ # Shims for resource module support
+ def get(self, command, output=None):
+ # This method is ONLY here to support resource modules. Therefore most
+ # arguments are unsupported and not present.
+
+ return self.send_request(data=command, output=output)
+
+ def edit_config(self, candidate):
+ # This method is ONLY here to support resource modules. Therefore most
+ # arguments are unsupported and not present.
+
+ responses = self.send_request(candidate, output="config")
+ return [resp for resp in to_list(responses) if resp != "{}"]
+
+
+def handle_response(response):
+ results = []
+
+ if response["ins_api"].get("outputs"):
+ for output in to_list(response["ins_api"]["outputs"]["output"]):
+ if output["code"] != "200":
+ # Best effort messages: some API output keys may not exist on some platforms
+ input_data = output.get("input", "")
+ msg = output.get("msg", "")
+ clierror = output.get("clierror", "")
+ raise ConnectionError(
+ "%s: %s: %s" % (input_data, msg, clierror),
+ code=output["code"],
+ )
+ elif "body" in output:
+ result = output["body"]
+ if isinstance(result, dict):
+ result = json.dumps(result)
+
+ results.append(result.strip())
+
+ return results
+
+
+def request_builder(commands, output, version="1.0", chunk="0", sid=None):
+ """Encodes a NXAPI JSON request message"""
+ output_to_command_type = {
+ "text": "cli_show_ascii",
+ "json": "cli_show",
+ "bash": "bash",
+ "config": "cli_conf",
+ }
+
+ maybe_output = commands[0].split("|")[-1].strip()
+ if maybe_output in output_to_command_type:
+ command_type = output_to_command_type[maybe_output]
+ commands = [command.split("|")[0].strip() for command in commands]
+ else:
+ try:
+ command_type = output_to_command_type[output]
+ except KeyError:
+ msg = "invalid format, received %s, expected one of %s" % (
+ output,
+ ",".join(output_to_command_type.keys()),
+ )
+ raise ConnectionError(msg)
+
+ if isinstance(commands, (list, set, tuple)):
+ commands = " ;".join(commands)
+
+ # Order should not matter but some versions of NX-OS software fail
+ # to process the payload properly if 'input' gets serialized before
+ # 'type' and the payload of 'input' contains the word 'type'.
+ msg = collections.OrderedDict()
+ msg["version"] = version
+ msg["type"] = command_type
+ msg["chunk"] = chunk
+ msg["sid"] = sid
+ msg["input"] = commands
+ msg["output_format"] = "json"
+
+ return json.dumps(dict(ins_api=msg))
diff --git a/ansible_collections/cisco/nxos/plugins/inventory/__init__.py b/ansible_collections/cisco/nxos/plugins/inventory/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/inventory/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/acl_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/acl_interfaces.py
new file mode 100644
index 00000000..ad2d59d3
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acl_interfaces/acl_interfaces.py
@@ -0,0 +1,82 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The arg spec for the nxos_acl_interfaces module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class Acl_interfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_acl_interfaces module"""
+
+ argument_spec = {
+ "config": {
+ "elements": "dict",
+ "options": {
+ "access_groups": {
+ "elements": "dict",
+ "options": {
+ "acls": {
+ "elements": "dict",
+ "options": {
+ "direction": {
+ "choices": ["in", "out"],
+ "required": True,
+ "type": "str",
+ },
+ "name": {"required": True, "type": "str"},
+ "port": {"type": "bool"},
+ },
+ "type": "list",
+ },
+ "afi": {
+ "choices": ["ipv4", "ipv6"],
+ "required": True,
+ "type": "str",
+ },
+ },
+ "type": "list",
+ },
+ "name": {"required": True, "type": "str"},
+ },
+ "type": "list",
+ },
+ "running_config": {"type": "str"},
+ "state": {
+ "choices": [
+ "deleted",
+ "gathered",
+ "merged",
+ "overridden",
+ "rendered",
+ "replaced",
+ "parsed",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/acls.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/acls.py
new file mode 100644
index 00000000..3618248b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/acls/acls.py
@@ -0,0 +1,314 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The arg spec for the nxos_acls module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class AclsArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_acls module"""
+
+ argument_spec = {
+ "config": {
+ "elements": "dict",
+ "options": {
+ "acls": {
+ "elements": "dict",
+ "options": {
+ "aces": {
+ "elements": "dict",
+ "mutually_exclusive": [["grant", "remark"]],
+ "options": {
+ "destination": {
+ "mutually_exclusive": [
+ ["address", "any", "host", "prefix"],
+ [
+ "wildcard_bits",
+ "any",
+ "host",
+ "prefix",
+ ],
+ ],
+ "options": {
+ "address": {"type": "str"},
+ "any": {"type": "bool"},
+ "host": {"type": "str"},
+ "port_protocol": {
+ "mutually_exclusive": [
+ [
+ "eq",
+ "lt",
+ "neq",
+ "gt",
+ "range",
+ ],
+ ],
+ "options": {
+ "eq": {"type": "str"},
+ "gt": {"type": "str"},
+ "lt": {"type": "str"},
+ "neq": {"type": "str"},
+ "range": {
+ "options": {
+ "end": {"type": "str"},
+ "start": {"type": "str"},
+ },
+ "required_together": [["start", "end"]],
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "prefix": {"type": "str"},
+ "wildcard_bits": {"type": "str"},
+ },
+ "required_together": [["address", "wildcard_bits"]],
+ "type": "dict",
+ },
+ "dscp": {"type": "str"},
+ "fragments": {"type": "bool"},
+ "grant": {
+ "choices": ["permit", "deny"],
+ "type": "str",
+ },
+ "log": {"type": "bool"},
+ "precedence": {"type": "str"},
+ "protocol": {"type": "str"},
+ "protocol_options": {
+ "mutually_exclusive": [["icmp", "igmp", "tcp"]],
+ "options": {
+ "icmp": {
+ "options": {
+ "administratively_prohibited": {"type": "bool"},
+ "alternate_address": {"type": "bool"},
+ "conversion_error": {"type": "bool"},
+ "dod_host_prohibited": {"type": "bool"},
+ "dod_net_prohibited": {"type": "bool"},
+ "echo": {"type": "bool"},
+ "echo_reply": {"type": "bool"},
+ "echo_request": {"type": "bool"},
+ "general_parameter_problem": {"type": "bool"},
+ "host_isolated": {"type": "bool"},
+ "host_precedence_unreachable": {"type": "bool"},
+ "host_redirect": {"type": "bool"},
+ "host_tos_redirect": {"type": "bool"},
+ "host_tos_unreachable": {"type": "bool"},
+ "host_unknown": {"type": "bool"},
+ "host_unreachable": {"type": "bool"},
+ "information_reply": {"type": "bool"},
+ "information_request": {"type": "bool"},
+ "mask_reply": {"type": "bool"},
+ "mask_request": {"type": "bool"},
+ "message_code": {"type": "int"},
+ "message_type": {"type": "int"},
+ "mobile_redirect": {"type": "bool"},
+ "net_redirect": {"type": "bool"},
+ "net_tos_redirect": {"type": "bool"},
+ "net_tos_unreachable": {"type": "bool"},
+ "net_unreachable": {"type": "bool"},
+ "network_unknown": {"type": "bool"},
+ "no_room_for_option": {"type": "bool"},
+ "option_missing": {"type": "bool"},
+ "packet_too_big": {"type": "bool"},
+ "parameter_problem": {"type": "bool"},
+ "port_unreachable": {"type": "bool"},
+ "precedence_unreachable": {"type": "bool"},
+ "protocol_unreachable": {"type": "bool"},
+ "reassembly_timeout": {"type": "bool"},
+ "redirect": {"type": "bool"},
+ "router_advertisement": {"type": "bool"},
+ "router_solicitation": {"type": "bool"},
+ "source_quench": {"type": "bool"},
+ "source_route_failed": {"type": "bool"},
+ "time_exceeded": {"type": "bool"},
+ "timestamp_reply": {"type": "bool"},
+ "timestamp_request": {"type": "bool"},
+ "traceroute": {"type": "bool"},
+ "ttl_exceeded": {"type": "bool"},
+ "unreachable": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "icmpv6": {
+ "type": "dict",
+ "options": {
+ "beyond_scope": {"type": "bool"},
+ "destination_unreachable": {
+ "type": "bool",
+ },
+ "echo_reply": {"type": "bool"},
+ "echo_request": {"type": "bool"},
+ "fragments": {"type": "bool"},
+ "header": {"type": "bool"},
+ "hop_limit": {"type": "bool"},
+ "mld_query": {"type": "bool"},
+ "mld_reduction": {"type": "bool"},
+ "mld_report": {"type": "bool"},
+ "mldv2": {"type": "bool"},
+ "nd_na": {"type": "bool"},
+ "nd_ns": {"type": "bool"},
+ "next_header": {"type": "bool"},
+ "no_admin": {"type": "bool"},
+ "no_route": {"type": "bool"},
+ "packet_too_big": {"type": "bool"},
+ "parameter_option": {
+ "type": "bool",
+ },
+ "parameter_problem": {
+ "type": "bool",
+ },
+ "port_unreachable": {
+ "type": "bool",
+ },
+ "reassembly_timeout": {
+ "type": "bool",
+ },
+ "renum_command": {"type": "bool"},
+ "renum_result": {"type": "bool"},
+ "renum_seq_number": {
+ "type": "bool",
+ },
+ "router_advertisement": {
+ "type": "bool",
+ },
+ "router_renumbering": {
+ "type": "bool",
+ },
+ "router_solicitation": {
+ "type": "bool",
+ },
+ "time_exceeded": {"type": "bool"},
+ "unreachable": {"type": "bool"},
+ "telemetry_path": {"type": "bool"},
+ "telemetry_queue": {
+ "type": "bool",
+ },
+ },
+ },
+ "igmp": {
+ "mutually_exclusive": [
+ [
+ "dvmrp",
+ "host_query",
+ "host_report",
+ ],
+ ],
+ "options": {
+ "dvmrp": {"type": "bool"},
+ "host_query": {"type": "bool"},
+ "host_report": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "tcp": {
+ "options": {
+ "ack": {"type": "bool"},
+ "established": {"type": "bool"},
+ "fin": {"type": "bool"},
+ "psh": {"type": "bool"},
+ "rst": {"type": "bool"},
+ "syn": {"type": "bool"},
+ "urg": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "remark": {"type": "str"},
+ "sequence": {"type": "int"},
+ "source": {
+ "mutually_exclusive": [
+ ["address", "any", "host", "prefix"],
+ [
+ "wildcard_bits",
+ "host",
+ "any",
+ "prefix",
+ ],
+ ],
+ "options": {
+ "address": {"type": "str"},
+ "any": {"type": "bool"},
+ "host": {"type": "str"},
+ "port_protocol": {
+ "mutually_exclusive": [
+ ["eq", "lt", "neq", "range"],
+ ["eq", "gt", "neq", "range"],
+ ],
+ "options": {
+ "eq": {"type": "str"},
+ "gt": {"type": "str"},
+ "lt": {"type": "str"},
+ "neq": {"type": "str"},
+ "range": {
+ "options": {
+ "end": {"type": "str"},
+ "start": {"type": "str"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "prefix": {"type": "str"},
+ "wildcard_bits": {"type": "str"},
+ },
+ "required_together": [["address", "wildcard_bits"]],
+ "type": "dict",
+ },
+ },
+ "type": "list",
+ },
+ "name": {"required": True, "type": "str"},
+ },
+ "type": "list",
+ },
+ "afi": {
+ "choices": ["ipv4", "ipv6"],
+ "required": True,
+ "type": "str",
+ },
+ },
+ "type": "list",
+ },
+ "running_config": {"type": "str"},
+ "state": {
+ "choices": [
+ "deleted",
+ "gathered",
+ "merged",
+ "overridden",
+ "rendered",
+ "replaced",
+ "parsed",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/bfd_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/bfd_interfaces.py
new file mode 100644
index 00000000..6c47d2e5
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bfd_interfaces/bfd_interfaces.py
@@ -0,0 +1,61 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The arg spec for the nxos_bfd_interfaces module
+"""
+
+
+class Bfd_interfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_bfd_interfaces module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str"},
+ "bfd": {"choices": ["enable", "disable"], "type": "str"},
+ "echo": {"choices": ["enable", "disable"], "type": "str"},
+ },
+ "type": "list",
+ },
+ "state": {
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "gathered",
+ "rendered",
+ "parsed",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/bgp_address_family.py
new file mode 100644
index 00000000..f77843d7
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_address_family/bgp_address_family.py
@@ -0,0 +1,250 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the nxos_bgp_address_family module
+"""
+
+
+class Bgp_address_familyArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_bgp_address_family module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "type": "dict",
+ "options": {
+ "as_number": {"type": "str"},
+ "address_family": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "afi": {
+ "type": "str",
+ "choices": [
+ "ipv4",
+ "ipv6",
+ "link-state",
+ "vpnv4",
+ "vpnv6",
+ "l2vpn",
+ ],
+ "required": True,
+ },
+ "safi": {
+ "type": "str",
+ "choices": [
+ "unicast",
+ "multicast",
+ "mvpn",
+ "evpn",
+ ],
+ },
+ "additional_paths": {
+ "type": "dict",
+ "options": {
+ "install_backup": {"type": "bool"},
+ "receive": {"type": "bool"},
+ "selection": {
+ "type": "dict",
+ "options": {"route_map": {"type": "str"}},
+ },
+ "send": {"type": "bool"},
+ },
+ },
+ "advertise_l2vpn_evpn": {"type": "bool"},
+ "advertise_pip": {"type": "bool"},
+ "advertise_system_mac": {"type": "bool"},
+ "allow_vni_in_ethertag": {"type": "bool"},
+ "aggregate_address": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "prefix": {"type": "str"},
+ "advertise_map": {"type": "str"},
+ "as_set": {"type": "bool"},
+ "attribute_map": {"type": "str"},
+ "summary_only": {"type": "bool"},
+ "suppress_map": {"type": "str"},
+ },
+ },
+ "client_to_client": {
+ "type": "dict",
+ "options": {"no_reflection": {"type": "bool"}},
+ },
+ "dampen_igp_metric": {"type": "int"},
+ "dampening": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "decay_half_life": {"type": "int"},
+ "start_reuse_route": {"type": "int"},
+ "start_suppress_route": {"type": "int"},
+ "max_suppress_time": {"type": "int"},
+ "route_map": {"type": "str"},
+ },
+ },
+ "default_information": {
+ "type": "dict",
+ "options": {"originate": {"type": "bool"}},
+ },
+ "default_metric": {"type": "int"},
+ "distance": {
+ "type": "dict",
+ "options": {
+ "ebgp_routes": {"type": "int"},
+ "ibgp_routes": {"type": "int"},
+ "local_routes": {"type": "int"},
+ },
+ },
+ "export_gateway_ip": {"type": "bool"},
+ "inject_map": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "route_map": {"type": "str"},
+ "exist_map": {"type": "str"},
+ "copy_attributes": {"type": "bool"},
+ },
+ },
+ "maximum_paths": {
+ "type": "dict",
+ "options": {
+ "parallel_paths": {"type": "int"},
+ "ibgp": {
+ "type": "dict",
+ "options": {"parallel_paths": {"type": "int"}},
+ },
+ "eibgp": {
+ "type": "dict",
+ "options": {"parallel_paths": {"type": "int"}},
+ },
+ "local": {
+ "type": "dict",
+ "options": {"parallel_paths": {"type": "int"}},
+ },
+ "mixed": {
+ "type": "dict",
+ "options": {"parallel_paths": {"type": "int"}},
+ },
+ },
+ },
+ "networks": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "prefix": {"type": "str"},
+ "route_map": {"type": "str"},
+ },
+ },
+ "nexthop": {
+ "type": "dict",
+ "options": {
+ "route_map": {"type": "str"},
+ "trigger_delay": {
+ "type": "dict",
+ "options": {
+ "critical_delay": {"type": "int"},
+ "non_critical_delay": {"type": "int"},
+ },
+ },
+ },
+ },
+ "redistribute": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "protocol": {
+ "type": "str",
+ "choices": [
+ "am",
+ "direct",
+ "eigrp",
+ "isis",
+ "lisp",
+ "ospf",
+ "ospfv3",
+ "rip",
+ "static",
+ "hmm",
+ ],
+ "required": True,
+ },
+ "id": {"type": "str"},
+ "route_map": {"type": "str", "required": True},
+ },
+ },
+ "retain": {
+ "type": "dict",
+ "options": {
+ "route_target": {
+ "type": "dict",
+ "options": {
+ "retain_all": {"type": "bool"},
+ "route_map": {"type": "str"},
+ },
+ },
+ },
+ },
+ "suppress_inactive": {"type": "bool"},
+ "table_map": {
+ "type": "dict",
+ "options": {
+ "name": {"type": "str", "required": True},
+ "filter": {"type": "bool"},
+ },
+ },
+ "timers": {
+ "type": "dict",
+ "options": {
+ "bestpath_defer": {
+ "type": "dict",
+ "options": {
+ "defer_time": {"type": "int"},
+ "maximum_defer_time": {"type": "int"},
+ },
+ },
+ },
+ },
+ "wait_igp_convergence": {"type": "bool"},
+ "vrf": {"type": "str"},
+ },
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "parsed",
+ "gathered",
+ "rendered",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/bgp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/bgp_global.py
new file mode 100644
index 00000000..d47f7e42
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_global/bgp_global.py
@@ -0,0 +1,549 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the nxos_bgp_global module
+"""
+
+
+class Bgp_globalArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_bgp_global module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "type": "dict",
+ "options": {
+ "as_number": {"type": "str"},
+ "affinity_group": {
+ "type": "dict",
+ "options": {"group_id": {"type": "int"}},
+ },
+ "bestpath": {
+ "type": "dict",
+ "options": {
+ "always_compare_med": {"type": "bool"},
+ "as_path": {
+ "type": "dict",
+ "options": {
+ "ignore": {"type": "bool"},
+ "multipath_relax": {"type": "bool"},
+ },
+ },
+ "compare_neighborid": {"type": "bool"},
+ "compare_routerid": {"type": "bool"},
+ "cost_community_ignore": {"type": "bool"},
+ "igp_metric_ignore": {"type": "bool"},
+ "med": {
+ "type": "dict",
+ "options": {
+ "confed": {"type": "bool"},
+ "missing_as_worst": {"type": "bool"},
+ "non_deterministic": {"type": "bool"},
+ },
+ },
+ },
+ },
+ "cluster_id": {"type": "str"},
+ "confederation": {
+ "type": "dict",
+ "options": {
+ "identifier": {"type": "str"},
+ "peers": {"type": "list", "elements": "str"},
+ },
+ },
+ "disable_policy_batching": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "ipv4": {
+ "type": "dict",
+ "options": {"prefix_list": {"type": "str"}},
+ },
+ "ipv6": {
+ "type": "dict",
+ "options": {"prefix_list": {"type": "str"}},
+ },
+ "nexthop": {"type": "bool"},
+ },
+ },
+ "dynamic_med_interval": {"type": "int"},
+ "enforce_first_as": {"type": "bool"},
+ "enhanced_error": {"type": "bool"},
+ "fabric_soo": {"type": "str"},
+ "fast_external_fallover": {"type": "bool"},
+ "flush_routes": {"type": "bool"},
+ "graceful_restart": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "restart_time": {"type": "int"},
+ "stalepath_time": {"type": "int"},
+ "helper": {"type": "bool"},
+ },
+ },
+ "graceful_shutdown": {
+ "type": "dict",
+ "options": {
+ "activate": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "route_map": {"type": "str"},
+ },
+ },
+ "aware": {"type": "bool"},
+ },
+ },
+ "isolate": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "include_local": {"type": "bool"},
+ },
+ },
+ "log_neighbor_changes": {"type": "bool"},
+ "maxas_limit": {"type": "int"},
+ "neighbors": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "neighbor_address": {"type": "str", "required": True},
+ "bfd": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "singlehop": {"type": "bool"},
+ "multihop": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "interval": {
+ "type": "dict",
+ "options": {
+ "tx_interval": {"type": "int"},
+ "min_rx_interval": {"type": "int"},
+ "multiplier": {"type": "int"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "neighbor_affinity_group": {
+ "type": "dict",
+ "options": {"group_id": {"type": "int"}},
+ },
+ "bmp_activate_server": {"type": "int"},
+ "capability": {
+ "type": "dict",
+ "options": {"suppress_4_byte_as": {"type": "bool"}},
+ },
+ "description": {"type": "str"},
+ "disable_connected_check": {"type": "bool"},
+ "dont_capability_negotiate": {"type": "bool"},
+ "dscp": {"type": "str"},
+ "dynamic_capability": {"type": "bool"},
+ "ebgp_multihop": {"type": "int"},
+ "graceful_shutdown": {
+ "type": "dict",
+ "options": {
+ "activate": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "route_map": {"type": "str"},
+ },
+ },
+ },
+ },
+ "inherit": {
+ "type": "dict",
+ "options": {
+ "peer": {"type": "str"},
+ "peer_session": {"type": "str"},
+ },
+ },
+ "local_as": {"type": "str"},
+ "log_neighbor_changes": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "disable": {"type": "bool"},
+ },
+ },
+ "low_memory": {
+ "type": "dict",
+ "options": {"exempt": {"type": "bool"}},
+ },
+ "password": {
+ "type": "dict",
+ "no_log": False,
+ "options": {
+ "encryption": {"type": "int"},
+ "key": {"type": "str", "no_log": True},
+ },
+ },
+ "path_attribute": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "action": {
+ "type": "str",
+ "choices": [
+ "discard",
+ "treat-as-withdraw",
+ ],
+ },
+ "type": {"type": "int"},
+ "range": {
+ "type": "dict",
+ "options": {
+ "start": {"type": "int"},
+ "end": {"type": "int"},
+ },
+ },
+ },
+ },
+ "peer_type": {
+ "type": "str",
+ "choices": [
+ "fabric-border-leaf",
+ "fabric-external",
+ ],
+ },
+ "remote_as": {"type": "str"},
+ "remove_private_as": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "replace_as": {"type": "bool"},
+ "all": {"type": "bool"},
+ },
+ },
+ "shutdown": {"type": "bool"},
+ "timers": {
+ "type": "dict",
+ "options": {
+ "keepalive": {"type": "int"},
+ "holdtime": {"type": "int"},
+ },
+ },
+ "transport": {
+ "type": "dict",
+ "options": {
+ "connection_mode": {
+ "type": "dict",
+ "options": {"passive": {"type": "bool"}},
+ },
+ },
+ },
+ "ttl_security": {
+ "type": "dict",
+ "options": {"hops": {"type": "int"}},
+ },
+ "update_source": {"type": "str"},
+ },
+ },
+ "neighbor_down": {
+ "type": "dict",
+ "options": {"fib_accelerate": {"type": "bool"}},
+ },
+ "nexthop": {
+ "type": "dict",
+ "options": {"suppress_default_resolution": {"type": "bool"}},
+ },
+ "rd": {
+ "type": "dict",
+ "options": {
+ "dual": {"type": "bool"},
+ "id": {"type": "int"},
+ },
+ },
+ "reconnect_interval": {"type": "int"},
+ "router_id": {"type": "str"},
+ "shutdown": {"type": "bool"},
+ "suppress_fib_pending": {"type": "bool"},
+ "timers": {
+ "type": "dict",
+ "options": {
+ "bestpath_limit": {
+ "type": "dict",
+ "options": {
+ "timeout": {"type": "int"},
+ "always": {"type": "bool"},
+ },
+ },
+ "bgp": {
+ "type": "dict",
+ "options": {
+ "keepalive": {"type": "int"},
+ "holdtime": {"type": "int"},
+ },
+ },
+ "prefix_peer_timeout": {"type": "int"},
+ "prefix_peer_wait": {"type": "int"},
+ },
+ },
+ "vrfs": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "vrf": {"type": "str"},
+ "allocate_index": {"type": "int"},
+ "bestpath": {
+ "type": "dict",
+ "options": {
+ "always_compare_med": {"type": "bool"},
+ "as_path": {
+ "type": "dict",
+ "options": {
+ "ignore": {"type": "bool"},
+ "multipath_relax": {"type": "bool"},
+ },
+ },
+ "compare_neighborid": {"type": "bool"},
+ "compare_routerid": {"type": "bool"},
+ "cost_community_ignore": {"type": "bool"},
+ "igp_metric_ignore": {"type": "bool"},
+ "med": {
+ "type": "dict",
+ "options": {
+ "confed": {"type": "bool"},
+ "missing_as_worst": {"type": "bool"},
+ "non_deterministic": {"type": "bool"},
+ },
+ },
+ },
+ },
+ "cluster_id": {"type": "str"},
+ "confederation": {
+ "type": "dict",
+ "options": {
+ "identifier": {"type": "str"},
+ "peers": {"type": "list", "elements": "str"},
+ },
+ },
+ "graceful_restart": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "restart_time": {"type": "int"},
+ "stalepath_time": {"type": "int"},
+ "helper": {"type": "bool"},
+ },
+ },
+ "local_as": {"type": "str"},
+ "log_neighbor_changes": {"type": "bool"},
+ "maxas_limit": {"type": "int"},
+ "neighbors": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "neighbor_address": {
+ "type": "str",
+ "required": True,
+ },
+ "bfd": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "singlehop": {"type": "bool"},
+ "multihop": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "interval": {
+ "type": "dict",
+ "options": {
+ "tx_interval": {"type": "int"},
+ "min_rx_interval": {"type": "int"},
+ "multiplier": {"type": "int"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "neighbor_affinity_group": {
+ "type": "dict",
+ "options": {"group_id": {"type": "int"}},
+ },
+ "bmp_activate_server": {"type": "int"},
+ "capability": {
+ "type": "dict",
+ "options": {"suppress_4_byte_as": {"type": "bool"}},
+ },
+ "description": {"type": "str"},
+ "disable_connected_check": {"type": "bool"},
+ "dont_capability_negotiate": {"type": "bool"},
+ "dscp": {"type": "str"},
+ "dynamic_capability": {"type": "bool"},
+ "ebgp_multihop": {"type": "int"},
+ "graceful_shutdown": {
+ "type": "dict",
+ "options": {
+ "activate": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "route_map": {"type": "str"},
+ },
+ },
+ },
+ },
+ "inherit": {
+ "type": "dict",
+ "options": {
+ "peer": {"type": "str"},
+ "peer_session": {"type": "str"},
+ },
+ },
+ "local_as": {"type": "str"},
+ "log_neighbor_changes": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "disable": {"type": "bool"},
+ },
+ },
+ "low_memory": {
+ "type": "dict",
+ "options": {"exempt": {"type": "bool"}},
+ },
+ "password": {
+ "type": "dict",
+ "no_log": False,
+ "options": {
+ "encryption": {"type": "int"},
+ "key": {"type": "str", "no_log": True},
+ },
+ },
+ "path_attribute": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "action": {
+ "type": "str",
+ "choices": [
+ "discard",
+ "treat-as-withdraw",
+ ],
+ },
+ "type": {"type": "int"},
+ "range": {
+ "type": "dict",
+ "options": {
+ "start": {"type": "int"},
+ "end": {"type": "int"},
+ },
+ },
+ },
+ },
+ "peer_type": {
+ "type": "str",
+ "choices": [
+ "fabric-border-leaf",
+ "fabric-external",
+ ],
+ },
+ "remote_as": {"type": "str"},
+ "remove_private_as": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "replace_as": {"type": "bool"},
+ "all": {"type": "bool"},
+ },
+ },
+ "shutdown": {"type": "bool"},
+ "timers": {
+ "type": "dict",
+ "options": {
+ "keepalive": {"type": "int"},
+ "holdtime": {"type": "int"},
+ },
+ },
+ "transport": {
+ "type": "dict",
+ "options": {
+ "connection_mode": {
+ "type": "dict",
+ "options": {"passive": {"type": "bool"}},
+ },
+ },
+ },
+ "ttl_security": {
+ "type": "dict",
+ "options": {"hops": {"type": "int"}},
+ },
+ "update_source": {"type": "str"},
+ },
+ },
+ "neighbor_down": {
+ "type": "dict",
+ "options": {"fib_accelerate": {"type": "bool"}},
+ },
+ "reconnect_interval": {"type": "int"},
+ "router_id": {"type": "str"},
+ "timers": {
+ "type": "dict",
+ "options": {
+ "bestpath_limit": {
+ "type": "dict",
+ "options": {
+ "timeout": {"type": "int"},
+ "always": {"type": "bool"},
+ },
+ },
+ "bgp": {
+ "type": "dict",
+ "options": {
+ "keepalive": {"type": "int"},
+ "holdtime": {"type": "int"},
+ },
+ },
+ "prefix_peer_timeout": {"type": "int"},
+ "prefix_peer_wait": {"type": "int"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "deleted",
+ "purged",
+ "parsed",
+ "gathered",
+ "rendered",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/bgp_neighbor_address_family.py
new file mode 100644
index 00000000..4c97d294
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/bgp_neighbor_address_family/bgp_neighbor_address_family.py
@@ -0,0 +1,377 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the nxos_bgp_neighbor_address_family module
+"""
+
+
+class Bgp_neighbor_address_familyArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_bgp_neighbor_address_family module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "type": "dict",
+ "options": {
+ "as_number": {"type": "str"},
+ "neighbors": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "neighbor_address": {"type": "str", "required": True},
+ "address_family": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "afi": {
+ "type": "str",
+ "choices": [
+ "ipv4",
+ "ipv6",
+ "link-state",
+ "vpnv4",
+ "vpnv6",
+ "l2vpn",
+ ],
+ "required": True,
+ },
+ "safi": {
+ "type": "str",
+ "choices": [
+ "unicast",
+ "multicast",
+ "mvpn",
+ "evpn",
+ ],
+ },
+ "advertise_map": {
+ "type": "dict",
+ "options": {
+ "route_map": {
+ "type": "str",
+ "required": True,
+ },
+ "exist_map": {"type": "str"},
+ "non_exist_map": {"type": "str"},
+ },
+ },
+ "advertisement_interval": {"type": "int"},
+ "allowas_in": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "max_occurences": {"type": "int"},
+ },
+ },
+ "as_override": {"type": "bool"},
+ "capability": {
+ "type": "dict",
+ "options": {
+ "additional_paths": {
+ "type": "dict",
+ "options": {
+ "receive": {
+ "type": "str",
+ "choices": [
+ "enable",
+ "disable",
+ ],
+ },
+ "send": {
+ "type": "str",
+ "choices": [
+ "enable",
+ "disable",
+ ],
+ },
+ },
+ },
+ },
+ },
+ "default_originate": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "route_map": {"type": "str"},
+ },
+ },
+ "disable_peer_as_check": {"type": "bool"},
+ "filter_list": {
+ "type": "dict",
+ "options": {
+ "inbound": {"type": "str"},
+ "outbound": {"type": "str"},
+ },
+ },
+ "inherit": {
+ "type": "dict",
+ "options": {
+ "template": {"type": "str"},
+ "sequence": {"type": "int"},
+ },
+ },
+ "maximum_prefix": {
+ "type": "dict",
+ "options": {
+ "max_prefix_limit": {"type": "int"},
+ "generate_warning_threshold": {"type": "int"},
+ "restart_interval": {"type": "int"},
+ "warning_only": {"type": "bool"},
+ },
+ },
+ "next_hop_self": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "all_routes": {"type": "bool"},
+ },
+ },
+ "next_hop_third_party": {"type": "bool"},
+ "prefix_list": {
+ "type": "dict",
+ "options": {
+ "inbound": {"type": "str"},
+ "outbound": {"type": "str"},
+ },
+ },
+ "rewrite_evpn_rt_asn": {"type": "bool"},
+ "route_map": {
+ "type": "dict",
+ "options": {
+ "inbound": {"type": "str"},
+ "outbound": {"type": "str"},
+ },
+ },
+ "route_reflector_client": {"type": "bool"},
+ "send_community": {
+ "type": "dict",
+ "mutually_exclusive": [
+ ["both", "set"],
+ ["extended", "both"],
+ ["standard", "both"],
+ ["standard", "set"],
+ ],
+ "options": {
+ "set": {"type": "bool"},
+ "extended": {"type": "bool"},
+ "standard": {"type": "bool"},
+ "both": {"type": "bool"},
+ },
+ },
+ "soft_reconfiguration_inbound": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "always": {"type": "bool"},
+ },
+ },
+ "soo": {"type": "str"},
+ "suppress_inactive": {"type": "bool"},
+ "unsuppress_map": {"type": "str"},
+ "weight": {"type": "int"},
+ },
+ },
+ },
+ },
+ "vrfs": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "vrf": {"type": "str"},
+ "neighbors": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "neighbor_address": {
+ "type": "str",
+ "required": True,
+ },
+ "address_family": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "afi": {
+ "type": "str",
+ "choices": [
+ "ipv4",
+ "ipv6",
+ "link-state",
+ "vpnv4",
+ "vpnv6",
+ "l2vpn",
+ ],
+ "required": True,
+ },
+ "safi": {
+ "type": "str",
+ "choices": [
+ "unicast",
+ "multicast",
+ "mvpn",
+ "evpn",
+ ],
+ },
+ "advertise_map": {
+ "type": "dict",
+ "options": {
+ "route_map": {
+ "type": "str",
+ "required": True,
+ },
+ "exist_map": {"type": "str"},
+ "non_exist_map": {"type": "str"},
+ },
+ },
+ "advertisement_interval": {"type": "int"},
+ "allowas_in": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "max_occurences": {"type": "int"},
+ },
+ },
+ "as_override": {"type": "bool"},
+ "capability": {
+ "type": "dict",
+ "options": {
+ "additional_paths": {
+ "type": "dict",
+ "options": {
+ "receive": {
+ "type": "str",
+ "choices": [
+ "enable",
+ "disable",
+ ],
+ },
+ "send": {
+ "type": "str",
+ "choices": [
+ "enable",
+ "disable",
+ ],
+ },
+ },
+ },
+ },
+ },
+ "default_originate": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "route_map": {"type": "str"},
+ },
+ },
+ "disable_peer_as_check": {"type": "bool"},
+ "filter_list": {
+ "type": "dict",
+ "options": {
+ "inbound": {"type": "str"},
+ "outbound": {"type": "str"},
+ },
+ },
+ "inherit": {
+ "type": "dict",
+ "options": {
+ "template": {"type": "str"},
+ "sequence": {"type": "int"},
+ },
+ },
+ "maximum_prefix": {
+ "type": "dict",
+ "options": {
+ "max_prefix_limit": {"type": "int"},
+ "generate_warning_threshold": {"type": "int"},
+ "restart_interval": {"type": "int"},
+ "warning_only": {"type": "bool"},
+ },
+ },
+ "next_hop_self": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "all_routes": {"type": "bool"},
+ },
+ },
+ "next_hop_third_party": {"type": "bool"},
+ "prefix_list": {
+ "type": "dict",
+ "options": {
+ "inbound": {"type": "str"},
+ "outbound": {"type": "str"},
+ },
+ },
+ "rewrite_evpn_rt_asn": {"type": "bool"},
+ "route_map": {
+ "type": "dict",
+ "options": {
+ "inbound": {"type": "str"},
+ "outbound": {"type": "str"},
+ },
+ },
+ "route_reflector_client": {"type": "bool"},
+ "send_community": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "extended": {"type": "bool"},
+ "standard": {"type": "bool"},
+ "both": {"type": "bool"},
+ },
+ },
+ "soft_reconfiguration_inbound": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "always": {"type": "bool"},
+ },
+ },
+ "soo": {"type": "str"},
+ "suppress_inactive": {"type": "bool"},
+ "unsuppress_map": {"type": "str"},
+ "weight": {"type": "int"},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "parsed",
+ "gathered",
+ "rendered",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/facts.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/facts.py
new file mode 100644
index 00000000..ae12a8d6
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/facts/facts.py
@@ -0,0 +1,25 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+"""
+The arg spec for the nxos facts module.
+"""
+
+
+class FactsArgs(object):
+ """The arg spec for the nxos facts module"""
+
+ def __init__(self, **kwargs):
+ pass
+
+ argument_spec = {
+ "gather_subset": dict(default=["min"], type="list", elements="str"),
+ "gather_network_resources": dict(type="list", elements="str"),
+ "available_network_resources": {"type": "bool", "default": False},
+ }
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/hostname.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/hostname.py
new file mode 100644
index 00000000..3a712071
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hostname/hostname.py
@@ -0,0 +1,50 @@
+# -*- coding: utf-8 -*-
+# Copyright 2022 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the nxos_hostname module
+"""
+
+
+class HostnameArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_hostname module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {"type": "dict", "options": {"hostname": {"type": "str"}}},
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "parsed",
+ "gathered",
+ "rendered",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/hsrp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/hsrp_interfaces.py
new file mode 100644
index 00000000..ac302636
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/hsrp_interfaces/hsrp_interfaces.py
@@ -0,0 +1,60 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The arg spec for the nxos_hsrp_interfaces module
+"""
+
+
+class Hsrp_interfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_hsrp_interfaces module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str"},
+ "bfd": {"choices": ["enable", "disable"], "type": "str"},
+ },
+ },
+ "state": {
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "rendered",
+ "gathered",
+ "parsed",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/interfaces.py
new file mode 100644
index 00000000..ba8d7923
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/interfaces/interfaces.py
@@ -0,0 +1,67 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The arg spec for the nxos_interfaces module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class InterfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_interfaces module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "elements": "dict",
+ "options": {
+ "description": {"type": "str"},
+ "duplex": {"choices": ["full", "half", "auto"], "type": "str"},
+ "enabled": {"type": "bool"},
+ "fabric_forwarding_anycast_gateway": {"type": "bool"},
+ "ip_forward": {"type": "bool"},
+ "mode": {"choices": ["layer2", "layer3"], "type": "str"},
+ "mtu": {"type": "str"},
+ "name": {"required": True, "type": "str"},
+ "speed": {"type": "str"},
+ },
+ "type": "list",
+ },
+ "state": {
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "gathered",
+ "rendered",
+ "parsed",
+ "purged",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/l2_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/l2_interfaces.py
new file mode 100644
index 00000000..046511c7
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l2_interfaces/l2_interfaces.py
@@ -0,0 +1,73 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The arg spec for the nxos_l2_interfaces module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class L2_interfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_l2_interfaces module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "elements": "dict",
+ "options": {
+ "access": {
+ "options": {"vlan": {"type": "int"}},
+ "type": "dict",
+ },
+ "mode": {
+ "type": "str",
+ "choices": ["access", "dot1q-tunnel", "trunk", "fex-fabric", "fabricpath"],
+ },
+ "name": {"required": True, "type": "str"},
+ "trunk": {
+ "options": {
+ "allowed_vlans": {"type": "str"},
+ "native_vlan": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "list",
+ },
+ "state": {
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "rendered",
+ "parsed",
+ "gathered",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/l3_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/l3_interfaces.py
new file mode 100644
index 00000000..7a5163e0
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/l3_interfaces/l3_interfaces.py
@@ -0,0 +1,83 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The arg spec for the nxos_l3_interfaces module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class L3_interfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_l3_interfaces module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "elements": "dict",
+ "options": {
+ "dot1q": {"type": "int"},
+ "ipv4": {
+ "elements": "dict",
+ "options": {
+ "address": {"type": "str"},
+ "secondary": {"type": "bool"},
+ "tag": {"type": "int"},
+ },
+ "type": "list",
+ },
+ "ipv6": {
+ "elements": "dict",
+ "options": {
+ "address": {"type": "str"},
+ "tag": {"type": "int"},
+ },
+ "type": "list",
+ },
+ "name": {"required": True, "type": "str"},
+ "redirects": {"type": "bool"},
+ "ipv6_redirects": {"type": "bool"},
+ "unreachables": {"type": "bool"},
+ "evpn_multisite_tracking": {
+ "type": "str",
+ "choices": ["fabric-tracking", "dci-tracking"],
+ },
+ },
+ "type": "list",
+ },
+ "state": {
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "gathered",
+ "rendered",
+ "parsed",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/lacp.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/lacp.py
new file mode 100644
index 00000000..3c2ac06a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp/lacp.py
@@ -0,0 +1,71 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The arg spec for the nxos_lacp module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class LacpArgs(object):
+ """The arg spec for the nxos_lacp module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "options": {
+ "system": {
+ "options": {
+ "mac": {
+ "options": {
+ "address": {"type": "str"},
+ "role": {
+ "choices": ["primary", "secondary"],
+ "type": "str",
+ },
+ },
+ "type": "dict",
+ },
+ "priority": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "state": {
+ "choices": [
+ "merged",
+ "replaced",
+ "deleted",
+ "gathered",
+ "rendered",
+ "parsed",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ }
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/lacp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/lacp_interfaces.py
new file mode 100644
index 00000000..119432bf
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lacp_interfaces/lacp_interfaces.py
@@ -0,0 +1,76 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The arg spec for the nxos_lacp_interfaces module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class Lacp_interfacesArgs(object):
+ """The arg spec for the nxos_lacp_interfaces module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "elements": "dict",
+ "options": {
+ "convergence": {
+ "options": {
+ "graceful": {"type": "bool"},
+ "vpc": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "links": {
+ "options": {
+ "max": {"type": "int"},
+ "min": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ "mode": {"choices": ["delay"], "type": "str"},
+ "name": {"required": True, "type": "str"},
+ "port_priority": {"type": "int"},
+ "rate": {"choices": ["fast", "normal"], "type": "str"},
+ "suspend_individual": {"type": "bool"},
+ },
+ "type": "list",
+ },
+ "state": {
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "rendered",
+ "gathered",
+ "parsed",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ }
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/lag_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/lag_interfaces.py
new file mode 100644
index 00000000..12330b84
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lag_interfaces/lag_interfaces.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the nxos_lag_interfaces module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class Lag_interfacesArgs(object):
+ """The arg spec for the nxos_lag_interfaces module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "elements": "dict",
+ "options": {
+ "members": {
+ "elements": "dict",
+ "options": {
+ "member": {"type": "str"},
+ "mode": {
+ "type": "str",
+ "choices": ["active", "on", "passive"],
+ },
+ "force": {"type": "bool"},
+ },
+ "type": "list",
+ },
+ "name": {"required": True, "type": "str"},
+ },
+ "type": "list",
+ },
+ "state": {
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "rendered",
+ "parsed",
+ "gathered",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ }
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/lldp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/lldp_global.py
new file mode 100644
index 00000000..8fad5eba
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_global/lldp_global.py
@@ -0,0 +1,88 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The arg spec for the nxos_lldp_global module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class Lldp_globalArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_lldp_global module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "options": {
+ "holdtime": {"type": "int"},
+ "port_id": {"choices": [0, 1], "type": "int"},
+ "reinit": {"type": "int"},
+ "timer": {"type": "int"},
+ "tlv_select": {
+ "options": {
+ "dcbxp": {"type": "bool"},
+ "management_address": {
+ "options": {
+ "v4": {"type": "bool"},
+ "v6": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "port": {
+ "options": {
+ "description": {"type": "bool"},
+ "vlan": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "power_management": {"type": "bool"},
+ "system": {
+ "options": {
+ "capabilities": {"type": "bool"},
+ "description": {"type": "bool"},
+ "name": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "state": {
+ "choices": [
+ "merged",
+ "replaced",
+ "deleted",
+ "gathered",
+ "parsed",
+ "rendered",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ }
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/lldp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/lldp_interfaces.py
new file mode 100644
index 00000000..0552901e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/lldp_interfaces/lldp_interfaces.py
@@ -0,0 +1,67 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The arg spec for the nxos_lldp_interfaces module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class Lldp_interfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_lldp_interfaces module"""
+
+ argument_spec = {
+ "config": {
+ "elements": "dict",
+ "options": {
+ "name": {"required": True, "type": "str"},
+ "receive": {"type": "bool"},
+ "tlv_set": {
+ "options": {
+ "management_address": {"type": "str"},
+ "vlan": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ "transmit": {"type": "bool"},
+ },
+ "type": "list",
+ },
+ "running_config": {"type": "str"},
+ "state": {
+ "choices": [
+ "deleted",
+ "gathered",
+ "merged",
+ "overridden",
+ "rendered",
+ "replaced",
+ "parsed",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/logging_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/logging_global.py
new file mode 100644
index 00000000..58a9052f
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/logging_global/logging_global.py
@@ -0,0 +1,276 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the nxos_logging_global module
+"""
+
+
+class Logging_globalArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_logging_global module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "type": "dict",
+ "options": {
+ "console": {
+ "type": "dict",
+ "options": {
+ "state": {
+ "type": "str",
+ "choices": ["enabled", "disabled"],
+ },
+ "severity": {
+ "type": "str",
+ "choices": [
+ "emergency",
+ "alert",
+ "critical",
+ "error",
+ "warning",
+ "notification",
+ "informational",
+ "debugging",
+ ],
+ },
+ },
+ },
+ "event": {
+ "type": "dict",
+ "options": {
+ "link_status": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "default": {"type": "bool"},
+ },
+ },
+ "trunk_status": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "default": {"type": "bool"},
+ },
+ },
+ },
+ },
+ "history": {
+ "type": "dict",
+ "options": {
+ "severity": {
+ "type": "str",
+ "choices": [
+ "emergency",
+ "alert",
+ "critical",
+ "error",
+ "warning",
+ "notification",
+ "informational",
+ "debugging",
+ ],
+ },
+ "size": {"type": "int"},
+ },
+ },
+ "ip": {
+ "type": "dict",
+ "options": {
+ "access_list": {
+ "type": "dict",
+ "options": {
+ "cache": {
+ "type": "dict",
+ "options": {
+ "entries": {"type": "int"},
+ "interval": {"type": "int"},
+ "threshold": {"type": "int"},
+ },
+ },
+ "detailed": {"type": "bool"},
+ "include": {
+ "type": "dict",
+ "options": {"sgt": {"type": "bool"}},
+ },
+ },
+ },
+ },
+ },
+ "facilities": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "facility": {"type": "str"},
+ "severity": {
+ "type": "str",
+ "choices": [
+ "emergency",
+ "alert",
+ "critical",
+ "error",
+ "warning",
+ "notification",
+ "informational",
+ "debugging",
+ ],
+ },
+ },
+ },
+ "logfile": {
+ "type": "dict",
+ "options": {
+ "state": {
+ "type": "str",
+ "choices": ["enabled", "disabled"],
+ },
+ "name": {"type": "str"},
+ "severity": {
+ "type": "str",
+ "choices": [
+ "emergency",
+ "alert",
+ "critical",
+ "error",
+ "warning",
+ "notification",
+ "informational",
+ "debugging",
+ ],
+ },
+ "persistent_threshold": {"type": "int"},
+ "size": {"type": "int"},
+ },
+ },
+ "module": {
+ "type": "dict",
+ "options": {
+ "state": {
+ "type": "str",
+ "choices": ["enabled", "disabled"],
+ },
+ "severity": {
+ "type": "str",
+ "choices": [
+ "emergency",
+ "alert",
+ "critical",
+ "error",
+ "warning",
+ "notification",
+ "informational",
+ "debugging",
+ ],
+ },
+ },
+ },
+ "monitor": {
+ "type": "dict",
+ "options": {
+ "state": {
+ "type": "str",
+ "choices": ["enabled", "disabled"],
+ },
+ "severity": {
+ "type": "str",
+ "choices": [
+ "emergency",
+ "alert",
+ "critical",
+ "error",
+ "warning",
+ "notification",
+ "informational",
+ "debugging",
+ ],
+ },
+ },
+ },
+ "origin_id": {
+ "type": "dict",
+ "options": {
+ "hostname": {"type": "bool"},
+ "ip": {"type": "str"},
+ "string": {"type": "str"},
+ },
+ },
+ "rate_limit": {
+ "type": "str",
+ "choices": ["enabled", "disabled"],
+ },
+ "rfc_strict": {"type": "bool"},
+ "hosts": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "host": {"type": "str"},
+ "severity": {
+ "type": "str",
+ "choices": [
+ "emergency",
+ "alert",
+ "critical",
+ "error",
+ "warning",
+ "notification",
+ "informational",
+ "debugging",
+ ],
+ },
+ "facility": {"type": "str"},
+ "port": {"type": "int"},
+ "secure": {
+ "type": "dict",
+ "options": {
+ "trustpoint": {
+ "type": "dict",
+ "options": {"client_identity": {"type": "str"}},
+ },
+ },
+ },
+ "use_vrf": {"type": "str"},
+ },
+ },
+ "source_interface": {"type": "str"},
+ "timestamp": {
+ "type": "str",
+ "choices": ["microseconds", "milliseconds", "seconds"],
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "parsed",
+ "gathered",
+ "rendered",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/ntp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/ntp_global.py
new file mode 100644
index 00000000..a680e58d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ntp_global/ntp_global.py
@@ -0,0 +1,139 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the nxos_ntp_global module
+"""
+
+
+class Ntp_globalArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_ntp_global module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "type": "dict",
+ "options": {
+ "access_group": {
+ "type": "dict",
+ "options": {
+ "match_all": {"type": "bool"},
+ "peer": {
+ "type": "list",
+ "elements": "dict",
+ "options": {"access_list": {"type": "str"}},
+ },
+ "query_only": {
+ "type": "list",
+ "elements": "dict",
+ "options": {"access_list": {"type": "str"}},
+ },
+ "serve": {
+ "type": "list",
+ "elements": "dict",
+ "options": {"access_list": {"type": "str"}},
+ },
+ "serve_only": {
+ "type": "list",
+ "elements": "dict",
+ "options": {"access_list": {"type": "str"}},
+ },
+ },
+ },
+ "allow": {
+ "type": "dict",
+ "options": {
+ "control": {
+ "type": "dict",
+ "options": {"rate_limit": {"type": "int"}},
+ },
+ "private": {"type": "bool"},
+ },
+ },
+ "authenticate": {"type": "bool"},
+ "authentication_keys": {
+ "type": "list",
+ "elements": "dict",
+ "no_log": False,
+ "options": {
+ "id": {"type": "int"},
+ "key": {"type": "str", "no_log": True},
+ "encryption": {"type": "int"},
+ },
+ },
+ "logging": {"type": "bool"},
+ "master": {
+ "type": "dict",
+ "options": {"stratum": {"type": "int"}},
+ },
+ "passive": {"type": "bool"},
+ "peers": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "peer": {"type": "str"},
+ "key_id": {"type": "int"},
+ "maxpoll": {"type": "int"},
+ "minpoll": {"type": "int"},
+ "prefer": {"type": "bool"},
+ "vrf": {"type": "str", "aliases": ["use_vrf"]},
+ },
+ },
+ "servers": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "server": {"type": "str"},
+ "key_id": {"type": "int"},
+ "maxpoll": {"type": "int"},
+ "minpoll": {"type": "int"},
+ "prefer": {"type": "bool"},
+ "vrf": {"type": "str", "aliases": ["use_vrf"]},
+ },
+ },
+ "source": {"type": "str"},
+ "source_interface": {"type": "str"},
+ "trusted_keys": {
+ "type": "list",
+ "elements": "dict",
+ "no_log": False,
+ "options": {"key_id": {"type": "int"}},
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "parsed",
+ "gathered",
+ "rendered",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/ospf_interfaces.py
new file mode 100644
index 00000000..30d65035
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospf_interfaces/ospf_interfaces.py
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the nxos_ospf_interfaces module
+"""
+
+
+class Ospf_interfacesArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_ospf_interfaces module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str", "required": True},
+ "address_family": {
+ "type": "list",
+ "elements": "dict",
+ "mutually_exclusive": [["passive_interface", "default_passive_interface"]],
+ "options": {
+ "afi": {
+ "type": "str",
+ "choices": ["ipv4", "ipv6"],
+ "required": True,
+ },
+ "processes": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "process_id": {
+ "type": "str",
+ "required": True,
+ },
+ "area": {
+ "type": "dict",
+ "options": {
+ "area_id": {
+ "type": "str",
+ "required": True,
+ },
+ "secondaries": {"type": "bool"},
+ },
+ },
+ "multi_areas": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "multi_areas": {"type": "list", "elements": "str"},
+ "authentication": {
+ "type": "dict",
+ "options": {
+ "key_chain": {"type": "str", "no_log": False},
+ "message_digest": {"type": "bool"},
+ "enable": {"type": "bool"},
+ "null_auth": {"type": "bool"},
+ },
+ },
+ "authentication_key": {
+ "type": "dict",
+ "no_log": False,
+ "options": {
+ "encryption": {"type": "int"},
+ "key": {
+ "type": "str",
+ "required": True,
+ "no_log": True,
+ },
+ },
+ },
+ "message_digest_key": {
+ "type": "dict",
+ "no_log": False,
+ "options": {
+ "key_id": {"type": "int", "required": True},
+ "encryption": {"type": "int"},
+ "key": {
+ "type": "str",
+ "required": True,
+ "no_log": True,
+ },
+ },
+ },
+ "cost": {"type": "int"},
+ "dead_interval": {"type": "int"},
+ "hello_interval": {"type": "int"},
+ "instance": {"type": "int"},
+ "mtu_ignore": {"type": "bool"},
+ "network": {
+ "type": "str",
+ "choices": ["broadcast", "point-to-point"],
+ },
+ "default_passive_interface": {"type": "bool"},
+ "passive_interface": {"type": "bool"},
+ "priority": {"type": "int"},
+ "retransmit_interval": {"type": "int"},
+ "shutdown": {"type": "bool"},
+ "transmit_delay": {"type": "int"},
+ },
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "gathered",
+ "parsed",
+ "rendered",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/ospfv2.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/ospfv2.py
new file mode 100644
index 00000000..2e5e62f1
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv2/ospfv2.py
@@ -0,0 +1,622 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The arg spec for the nxos_ospfv2 module
+"""
+
+
+class Ospfv2Args(object): # pylint: disable=R0903
+ """The arg spec for the nxos_ospfv2 module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "options": {
+ "processes": {
+ "elements": "dict",
+ "options": {
+ "areas": {
+ "required_one_of": [
+ [
+ "authentication",
+ "default_cost",
+ "filter_list",
+ "nssa",
+ "ranges",
+ "stub",
+ ],
+ ],
+ "elements": "dict",
+ "options": {
+ "area_id": {"type": "str", "required": True},
+ "authentication": {
+ "options": {
+ "set": {"type": "bool"},
+ "message_digest": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "default_cost": {"type": "int"},
+ "filter_list": {
+ "options": {
+ "direction": {
+ "choices": ["in", "out"],
+ "type": "str",
+ "required": True,
+ },
+ "route_map": {
+ "type": "str",
+ "required": True,
+ },
+ },
+ "type": "list",
+ "elements": "dict",
+ },
+ "nssa": {
+ "options": {
+ "default_information_originate": {"type": "bool"},
+ "no_redistribution": {"type": "bool"},
+ "no_summary": {"type": "bool"},
+ "set": {"type": "bool"},
+ "translate": {
+ "options": {
+ "type7": {
+ "mutually_exclusive": [["always", "never"]],
+ "options": {
+ "always": {"type": "bool"},
+ "never": {"type": "bool"},
+ "supress_fa": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "ranges": {
+ "elements": "dict",
+ "options": {
+ "cost": {"type": "int"},
+ "not_advertise": {"type": "bool"},
+ "prefix": {
+ "type": "str",
+ "required": True,
+ },
+ },
+ "type": "list",
+ },
+ "stub": {
+ "options": {
+ "no_summary": {"type": "bool"},
+ "set": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "list",
+ },
+ "auto_cost": {
+ "options": {
+ "reference_bandwidth": {
+ "type": "int",
+ "required": True,
+ },
+ "unit": {
+ "choices": ["Gbps", "Mbps"],
+ "type": "str",
+ "required": True,
+ },
+ },
+ "type": "dict",
+ },
+ "bfd": {"type": "bool"},
+ "default_information": {
+ "options": {
+ "originate": {
+ "options": {
+ "always": {"type": "bool"},
+ "route_map": {"type": "str"},
+ "set": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "default_metric": {"type": "int"},
+ "distance": {"type": "int"},
+ "flush_routes": {"type": "bool"},
+ "graceful_restart": {
+ "options": {
+ "grace_period": {"type": "int"},
+ "helper_disable": {"type": "bool"},
+ "set": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "isolate": {"type": "bool"},
+ "log_adjacency_changes": {
+ "options": {
+ "detail": {"type": "bool"},
+ "log": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "max_lsa": {
+ "options": {
+ "ignore_count": {"type": "int"},
+ "ignore_time": {"type": "int"},
+ "max_non_self_generated_lsa": {
+ "type": "int",
+ "required": True,
+ },
+ "reset_time": {"type": "int"},
+ "threshold": {"type": "int"},
+ "warning_only": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "max_metric": {
+ "options": {
+ "router_lsa": {
+ "options": {
+ "external_lsa": {
+ "options": {
+ "max_metric_value": {"type": "int"},
+ "set": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "include_stub": {"type": "bool"},
+ "on_startup": {
+ "options": {
+ "set": {"type": "bool"},
+ "wait_for_bgp_asn": {"type": "int"},
+ "wait_period": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ "set": {"type": "bool"},
+ "summary_lsa": {
+ "options": {
+ "max_metric_value": {"type": "int"},
+ "set": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "maximum_paths": {"type": "int"},
+ "mpls": {
+ "options": {
+ "traffic_eng": {
+ "options": {
+ "areas": {
+ "type": "list",
+ "elements": "dict",
+ "options": {"area_id": {"type": "str"}},
+ },
+ "multicast_intact": {"type": "bool"},
+ "router_id": {"type": "str"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "name_lookup": {"type": "bool"},
+ "passive_interface": {
+ "options": {"default": {"type": "bool"}},
+ "type": "dict",
+ },
+ "process_id": {"required": True, "type": "str"},
+ "redistribute": {
+ "elements": "dict",
+ "options": {
+ "id": {"type": "str"},
+ "protocol": {
+ "choices": [
+ "bgp",
+ "direct",
+ "eigrp",
+ "isis",
+ "lisp",
+ "ospf",
+ "rip",
+ "static",
+ ],
+ "required": True,
+ "type": "str",
+ },
+ "route_map": {"type": "str", "required": True},
+ },
+ "type": "list",
+ },
+ "rfc1583compatibility": {"type": "bool"},
+ "router_id": {"type": "str"},
+ "shutdown": {"type": "bool"},
+ "summary_address": {
+ "elements": "dict",
+ "mutually_exclusive": [["not_advertise", "tag"]],
+ "options": {
+ "not_advertise": {"type": "bool"},
+ "prefix": {"type": "str", "required": True},
+ "tag": {"type": "int"},
+ },
+ "type": "list",
+ },
+ "table_map": {
+ "options": {
+ "filter": {"type": "bool"},
+ "name": {"type": "str", "required": True},
+ },
+ "type": "dict",
+ },
+ "timers": {
+ "options": {
+ "lsa_arrival": {"type": "int"},
+ "lsa_group_pacing": {"type": "int"},
+ "throttle": {
+ "options": {
+ "lsa": {
+ "options": {
+ "hold_interval": {"type": "int"},
+ "max_interval": {"type": "int"},
+ "start_interval": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ "spf": {
+ "options": {
+ "initial_spf_delay": {"type": "int"},
+ "max_wait_time": {"type": "int"},
+ "min_hold_time": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "vrfs": {
+ "elements": "dict",
+ "options": {
+ "areas": {
+ "required_one_of": [
+ [
+ "authentication",
+ "default_cost",
+ "filter_list",
+ "nssa",
+ "ranges",
+ "stub",
+ ],
+ ],
+ "elements": "dict",
+ "options": {
+ "area_id": {
+ "type": "str",
+ "required": True,
+ },
+ "authentication": {
+ "options": {
+ "set": {"type": "bool"},
+ "message_digest": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "default_cost": {"type": "int"},
+ "filter_list": {
+ "options": {
+ "direction": {
+ "choices": ["in", "out"],
+ "type": "str",
+ "required": True,
+ },
+ "route_map": {
+ "type": "str",
+ "required": True,
+ },
+ },
+ "type": "list",
+ "elements": "dict",
+ },
+ "nssa": {
+ "options": {
+ "default_information_originate": {"type": "bool"},
+ "no_redistribution": {"type": "bool"},
+ "no_summary": {"type": "bool"},
+ "set": {"type": "bool"},
+ "translate": {
+ "options": {
+ "type7": {
+ "mutually_exclusive": [
+ [
+ "always",
+ "never",
+ ],
+ ],
+ "options": {
+ "always": {"type": "bool"},
+ "never": {"type": "bool"},
+ "supress_fa": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "ranges": {
+ "elements": "dict",
+ "options": {
+ "cost": {"type": "int"},
+ "not_advertise": {"type": "bool"},
+ "prefix": {
+ "type": "str",
+ "required": True,
+ },
+ },
+ "type": "list",
+ },
+ "stub": {
+ "options": {
+ "no_summary": {"type": "bool"},
+ "set": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "list",
+ },
+ "auto_cost": {
+ "options": {
+ "reference_bandwidth": {
+ "type": "int",
+ "required": True,
+ },
+ "unit": {
+ "choices": ["Gbps", "Mbps"],
+ "type": "str",
+ "required": True,
+ },
+ },
+ "type": "dict",
+ },
+ "bfd": {"type": "bool"},
+ "capability": {
+ "type": "dict",
+ "options": {
+ "vrf_lite": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "evpn": {"type": "bool"},
+ },
+ },
+ },
+ },
+ "default_information": {
+ "options": {
+ "originate": {
+ "options": {
+ "always": {"type": "bool"},
+ "route_map": {"type": "str"},
+ "set": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "default_metric": {"type": "int"},
+ "distance": {"type": "int"},
+ "down_bit_ignore": {"type": "bool"},
+ "graceful_restart": {
+ "options": {
+ "grace_period": {"type": "int"},
+ "helper_disable": {"type": "bool"},
+ "set": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "log_adjacency_changes": {
+ "options": {
+ "detail": {"type": "bool"},
+ "log": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "max_lsa": {
+ "options": {
+ "ignore_count": {"type": "int"},
+ "ignore_time": {"type": "int"},
+ "max_non_self_generated_lsa": {
+ "type": "int",
+ "required": True,
+ },
+ "reset_time": {"type": "int"},
+ "threshold": {"type": "int"},
+ "warning_only": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "max_metric": {
+ "options": {
+ "router_lsa": {
+ "options": {
+ "external_lsa": {
+ "options": {
+ "max_metric_value": {"type": "int"},
+ "set": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ "include_stub": {"type": "bool"},
+ "on_startup": {
+ "options": {
+ "set": {"type": "bool"},
+ "wait_for_bgp_asn": {"type": "int"},
+ "wait_period": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ "set": {"type": "bool"},
+ "summary_lsa": {
+ "options": {
+ "max_metric_value": {"type": "int"},
+ "set": {"type": "bool"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "maximum_paths": {"type": "int"},
+ "name_lookup": {"type": "bool"},
+ "passive_interface": {
+ "options": {"default": {"type": "bool"}},
+ "type": "dict",
+ },
+ "redistribute": {
+ "elements": "dict",
+ "options": {
+ "id": {"type": "str"},
+ "protocol": {
+ "choices": [
+ "bgp",
+ "direct",
+ "eigrp",
+ "isis",
+ "lisp",
+ "ospf",
+ "rip",
+ "static",
+ ],
+ "required": True,
+ "type": "str",
+ },
+ "route_map": {
+ "type": "str",
+ "required": True,
+ },
+ },
+ "type": "list",
+ },
+ "rfc1583compatibility": {"type": "bool"},
+ "router_id": {"type": "str"},
+ "shutdown": {"type": "bool"},
+ "summary_address": {
+ "elements": "dict",
+ "options": {
+ "not_advertise": {"type": "bool"},
+ "prefix": {
+ "type": "str",
+ "required": True,
+ },
+ "tag": {"type": "int"},
+ },
+ "type": "list",
+ },
+ "table_map": {
+ "options": {
+ "filter": {"type": "bool"},
+ "name": {
+ "type": "str",
+ "required": True,
+ },
+ },
+ "type": "dict",
+ },
+ "timers": {
+ "options": {
+ "lsa_arrival": {"type": "int"},
+ "lsa_group_pacing": {"type": "int"},
+ "throttle": {
+ "options": {
+ "lsa": {
+ "options": {
+ "hold_interval": {"type": "int"},
+ "max_interval": {"type": "int"},
+ "start_interval": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ "spf": {
+ "options": {
+ "initial_spf_delay": {"type": "int"},
+ "max_wait_time": {"type": "int"},
+ "min_hold_time": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ },
+ "type": "dict",
+ },
+ "vrf": {"required": True, "type": "str"},
+ },
+ "type": "list",
+ },
+ },
+ "type": "list",
+ },
+ },
+ "type": "dict",
+ },
+ "state": {
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "gathered",
+ "rendered",
+ "parsed",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/ospfv3.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/ospfv3.py
new file mode 100644
index 00000000..601bb618
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/ospfv3/ospfv3.py
@@ -0,0 +1,488 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the nxos_ospfv3 module
+"""
+
+
+class Ospfv3Args(object): # pylint: disable=R0903
+ """The arg spec for the nxos_ospfv3 module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "type": "dict",
+ "options": {
+ "processes": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "address_family": {
+ "type": "dict",
+ "options": {
+ "afi": {"type": "str", "choices": ["ipv6"]},
+ "safi": {
+ "type": "str",
+ "choices": ["unicast"],
+ },
+ "areas": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "area_id": {
+ "type": "str",
+ "required": True,
+ },
+ "default_cost": {"type": "int"},
+ "filter_list": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "route_map": {
+ "type": "str",
+ "required": True,
+ },
+ "direction": {
+ "type": "str",
+ "choices": ["in", "out"],
+ "required": True,
+ },
+ },
+ },
+ "ranges": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "prefix": {
+ "type": "str",
+ "required": True,
+ },
+ "cost": {"type": "int"},
+ "not_advertise": {"type": "bool"},
+ },
+ },
+ },
+ },
+ "default_information": {
+ "type": "dict",
+ "options": {
+ "originate": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "always": {"type": "bool"},
+ "route_map": {"type": "str"},
+ },
+ },
+ },
+ },
+ "distance": {"type": "int"},
+ "maximum_paths": {"type": "int"},
+ "redistribute": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "protocol": {
+ "type": "str",
+ "choices": [
+ "bgp",
+ "direct",
+ "eigrp",
+ "isis",
+ "lisp",
+ "ospfv3",
+ "rip",
+ "static",
+ ],
+ "required": True,
+ },
+ "id": {"type": "str"},
+ "route_map": {
+ "type": "str",
+ "required": True,
+ },
+ },
+ },
+ "summary_address": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "prefix": {
+ "type": "str",
+ "required": True,
+ },
+ "not_advertise": {"type": "bool"},
+ "tag": {"type": "int"},
+ },
+ },
+ "table_map": {
+ "type": "dict",
+ "options": {
+ "name": {
+ "type": "str",
+ "required": True,
+ },
+ "filter": {"type": "bool"},
+ },
+ },
+ "timers": {
+ "type": "dict",
+ "options": {
+ "throttle": {
+ "type": "dict",
+ "options": {
+ "spf": {
+ "type": "dict",
+ "options": {
+ "initial_spf_delay": {"type": "int"},
+ "min_hold_time": {"type": "int"},
+ "max_wait_time": {"type": "int"},
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "areas": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "area_id": {"type": "str", "required": True},
+ "nssa": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "default_information_originate": {"type": "bool"},
+ "no_redistribution": {"type": "bool"},
+ "no_summary": {"type": "bool"},
+ "route_map": {"type": "str"},
+ "translate": {
+ "type": "dict",
+ "options": {
+ "type7": {
+ "type": "dict",
+ "options": {
+ "always": {"type": "bool"},
+ "never": {"type": "bool"},
+ "supress_fa": {"type": "bool"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "stub": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "no_summary": {"type": "bool"},
+ },
+ },
+ },
+ },
+ "auto_cost": {
+ "type": "dict",
+ "options": {
+ "reference_bandwidth": {
+ "type": "int",
+ "required": True,
+ },
+ "unit": {
+ "type": "str",
+ "required": True,
+ "choices": ["Gbps", "Mbps"],
+ },
+ },
+ },
+ "flush_routes": {"type": "bool"},
+ "graceful_restart": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "grace_period": {"type": "int"},
+ "helper_disable": {"type": "bool"},
+ "planned_only": {"type": "bool"},
+ },
+ },
+ "isolate": {"type": "bool"},
+ "log_adjacency_changes": {
+ "type": "dict",
+ "options": {
+ "log": {"type": "bool"},
+ "detail": {"type": "bool"},
+ },
+ },
+ "max_lsa": {
+ "type": "dict",
+ "options": {
+ "max_non_self_generated_lsa": {
+ "type": "int",
+ "required": True,
+ },
+ "threshold": {"type": "int"},
+ "ignore_count": {"type": "int"},
+ "ignore_time": {"type": "int"},
+ "reset_time": {"type": "int"},
+ "warning_only": {"type": "bool"},
+ },
+ },
+ "max_metric": {
+ "type": "dict",
+ "options": {
+ "router_lsa": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "external_lsa": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "max_metric_value": {"type": "int"},
+ },
+ },
+ "stub_prefix_lsa": {"type": "bool"},
+ "on_startup": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "wait_period": {"type": "int"},
+ "wait_for_bgp_asn": {"type": "int"},
+ },
+ },
+ "inter_area_prefix_lsa": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "max_metric_value": {"type": "int"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "name_lookup": {"type": "bool"},
+ "passive_interface": {
+ "type": "dict",
+ "options": {"default": {"type": "bool"}},
+ },
+ "process_id": {"type": "str", "required": True},
+ "router_id": {"type": "str"},
+ "shutdown": {"type": "bool"},
+ "timers": {
+ "type": "dict",
+ "options": {
+ "lsa_arrival": {"type": "int"},
+ "lsa_group_pacing": {"type": "int"},
+ "throttle": {
+ "type": "dict",
+ "options": {
+ "lsa": {
+ "type": "dict",
+ "options": {
+ "start_interval": {"type": "int"},
+ "hold_interval": {"type": "int"},
+ "max_interval": {"type": "int"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "vrfs": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "areas": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "area_id": {
+ "type": "str",
+ "required": True,
+ },
+ "nssa": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "default_information_originate": {"type": "bool"},
+ "no_redistribution": {"type": "bool"},
+ "no_summary": {"type": "bool"},
+ "route_map": {"type": "str"},
+ "translate": {
+ "type": "dict",
+ "options": {
+ "type7": {
+ "type": "dict",
+ "options": {
+ "always": {"type": "bool"},
+ "never": {"type": "bool"},
+ "supress_fa": {"type": "bool"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "stub": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "no_summary": {"type": "bool"},
+ },
+ },
+ },
+ },
+ "auto_cost": {
+ "type": "dict",
+ "options": {
+ "reference_bandwidth": {
+ "type": "int",
+ "required": True,
+ },
+ "unit": {
+ "type": "str",
+ "required": True,
+ "choices": ["Gbps", "Mbps"],
+ },
+ },
+ },
+ "graceful_restart": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "grace_period": {"type": "int"},
+ "helper_disable": {"type": "bool"},
+ "planned_only": {"type": "bool"},
+ },
+ },
+ "log_adjacency_changes": {
+ "type": "dict",
+ "options": {
+ "log": {"type": "bool"},
+ "detail": {"type": "bool"},
+ },
+ },
+ "max_lsa": {
+ "type": "dict",
+ "options": {
+ "max_non_self_generated_lsa": {
+ "type": "int",
+ "required": True,
+ },
+ "threshold": {"type": "int"},
+ "ignore_count": {"type": "int"},
+ "ignore_time": {"type": "int"},
+ "reset_time": {"type": "int"},
+ "warning_only": {"type": "bool"},
+ },
+ },
+ "max_metric": {
+ "type": "dict",
+ "options": {
+ "router_lsa": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "external_lsa": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "max_metric_value": {"type": "int"},
+ },
+ },
+ "stub_prefix_lsa": {"type": "bool"},
+ "on_startup": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "wait_period": {"type": "int"},
+ "wait_for_bgp_asn": {"type": "int"},
+ },
+ },
+ "inter_area_prefix_lsa": {
+ "type": "dict",
+ "options": {
+ "set": {"type": "bool"},
+ "max_metric_value": {"type": "int"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "name_lookup": {"type": "bool"},
+ "passive_interface": {
+ "type": "dict",
+ "options": {"default": {"type": "bool"}},
+ },
+ "router_id": {"type": "str"},
+ "shutdown": {"type": "bool"},
+ "timers": {
+ "type": "dict",
+ "options": {
+ "lsa_arrival": {"type": "int"},
+ "lsa_group_pacing": {"type": "int"},
+ "throttle": {
+ "type": "dict",
+ "options": {
+ "lsa": {
+ "type": "dict",
+ "options": {
+ "start_interval": {"type": "int"},
+ "hold_interval": {"type": "int"},
+ "max_interval": {"type": "int"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "vrf": {"type": "str", "required": True},
+ },
+ },
+ },
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "gathered",
+ "parsed",
+ "rendered",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/prefix_lists.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/prefix_lists.py
new file mode 100644
index 00000000..c29e0375
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/prefix_lists/prefix_lists.py
@@ -0,0 +1,80 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the nxos_prefix_lists module
+"""
+
+
+class Prefix_listsArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_prefix_lists module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "afi": {"type": "str", "choices": ["ipv4", "ipv6"]},
+ "prefix_lists": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str"},
+ "description": {"type": "str"},
+ "entries": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "sequence": {"type": "int"},
+ "action": {
+ "type": "str",
+ "choices": ["permit", "deny"],
+ },
+ "prefix": {"type": "str"},
+ "eq": {"type": "int"},
+ "ge": {"type": "int"},
+ "le": {"type": "int"},
+ "mask": {"type": "str"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "parsed",
+ "gathered",
+ "rendered",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/route_maps.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/route_maps.py
new file mode 100644
index 00000000..25e40d6b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/route_maps/route_maps.py
@@ -0,0 +1,412 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the nxos_route_maps module
+"""
+
+
+class Route_mapsArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_route_maps module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "route_map": {"type": "str"},
+ "entries": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "sequence": {"type": "int"},
+ "action": {
+ "type": "str",
+ "choices": ["deny", "permit"],
+ },
+ "continue_sequence": {"type": "int"},
+ "description": {"type": "str"},
+ "match": {
+ "type": "dict",
+ "options": {
+ "as_number": {
+ "type": "dict",
+ "options": {
+ "asn": {
+ "type": "list",
+ "elements": "str",
+ },
+ "as_path_list": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "as_path": {"type": "list", "elements": "str"},
+ "community": {
+ "type": "dict",
+ "options": {
+ "community_list": {
+ "type": "list",
+ "elements": "str",
+ },
+ "exact_match": {"type": "bool"},
+ },
+ },
+ "evpn": {
+ "type": "dict",
+ "options": {
+ "route_types": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "extcommunity": {
+ "type": "dict",
+ "options": {
+ "extcommunity_list": {
+ "type": "list",
+ "elements": "str",
+ },
+ "exact_match": {"type": "bool"},
+ },
+ },
+ "interfaces": {
+ "type": "list",
+ "elements": "str",
+ },
+ "ip": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "dict",
+ "options": {
+ "access_list": {"type": "str"},
+ "prefix_lists": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "multicast": {
+ "type": "dict",
+ "options": {
+ "source": {"type": "str"},
+ "group": {
+ "type": "dict",
+ "options": {"prefix": {"type": "str"}},
+ },
+ "group_range": {
+ "type": "dict",
+ "options": {
+ "first": {"type": "str"},
+ "last": {"type": "str"},
+ },
+ },
+ "rp": {
+ "type": "dict",
+ "options": {
+ "prefix": {"type": "str"},
+ "rp_type": {
+ "type": "str",
+ "choices": [
+ "ASM",
+ "Bidir",
+ ],
+ },
+ },
+ },
+ },
+ },
+ "next_hop": {
+ "type": "dict",
+ "options": {
+ "prefix_lists": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "route_source": {
+ "type": "dict",
+ "options": {
+ "prefix_lists": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ },
+ },
+ "ipv6": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "dict",
+ "options": {
+ "access_list": {"type": "str"},
+ "prefix_lists": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "multicast": {
+ "type": "dict",
+ "options": {
+ "source": {"type": "str"},
+ "group": {
+ "type": "dict",
+ "options": {"prefix": {"type": "str"}},
+ },
+ "group_range": {
+ "type": "dict",
+ "options": {
+ "first": {"type": "str"},
+ "last": {"type": "str"},
+ },
+ },
+ "rp": {
+ "type": "dict",
+ "options": {
+ "prefix": {"type": "str"},
+ "rp_type": {
+ "type": "str",
+ "choices": [
+ "ASM",
+ "Bidir",
+ ],
+ },
+ },
+ },
+ },
+ },
+ "next_hop": {
+ "type": "dict",
+ "options": {
+ "prefix_lists": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "route_source": {
+ "type": "dict",
+ "options": {
+ "prefix_lists": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ },
+ },
+ "mac_list": {
+ "type": "list",
+ "elements": "str",
+ },
+ "metric": {"type": "list", "elements": "int"},
+ "ospf_area": {
+ "type": "list",
+ "elements": "int",
+ },
+ "route_types": {
+ "type": "list",
+ "elements": "str",
+ "choices": [
+ "external",
+ "inter-area",
+ "internal",
+ "intra-area",
+ "level-1",
+ "level-2",
+ "local",
+ "nssa-external",
+ "type-1",
+ "type-2",
+ ],
+ },
+ "source_protocol": {
+ "type": "list",
+ "elements": "str",
+ },
+ "tags": {"type": "list", "elements": "int"},
+ },
+ },
+ "set": {
+ "type": "dict",
+ "options": {
+ "as_path": {
+ "type": "dict",
+ "options": {
+ "prepend": {
+ "type": "dict",
+ "options": {
+ "as_number": {
+ "type": "list",
+ "elements": "str",
+ },
+ "last_as": {"type": "int"},
+ },
+ },
+ "tag": {"type": "bool"},
+ },
+ },
+ "comm_list": {"type": "str"},
+ "community": {
+ "type": "dict",
+ "options": {
+ "additive": {"type": "bool"},
+ "graceful_shutdown": {"type": "bool"},
+ "internet": {"type": "bool"},
+ "local_as": {"type": "bool"},
+ "no_advertise": {"type": "bool"},
+ "no_export": {"type": "bool"},
+ "number": {
+ "type": "list",
+ "elements": "str",
+ },
+ },
+ },
+ "dampening": {
+ "type": "dict",
+ "options": {
+ "half_life": {"type": "int"},
+ "start_reuse_route": {"type": "int"},
+ "start_suppress_route": {"type": "int"},
+ "max_suppress_time": {"type": "int"},
+ },
+ },
+ "distance": {
+ "type": "dict",
+ "options": {
+ "igp_ebgp_routes": {"type": "int"},
+ "internal_routes": {"type": "int"},
+ "local_routes": {"type": "int"},
+ },
+ },
+ "evpn": {
+ "type": "dict",
+ "options": {
+ "gateway_ip": {
+ "type": "dict",
+ "mutually_exclusive": [["ip", "use_nexthop"]],
+ "options": {
+ "ip": {"type": "str"},
+ "use_nexthop": {"type": "bool"},
+ },
+ },
+ },
+ },
+ "extcomm_list": {"type": "str"},
+ "forwarding_address": {"type": "bool"},
+ "null_interface": {"type": "str"},
+ "ip": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "dict",
+ "options": {"prefix_list": {"type": "str"}},
+ },
+ "precedence": {"type": "str"},
+ },
+ },
+ "ipv6": {
+ "type": "dict",
+ "options": {
+ "address": {
+ "type": "dict",
+ "options": {"prefix_list": {"type": "str"}},
+ },
+ "precedence": {"type": "str"},
+ },
+ },
+ "label_index": {"type": "int"},
+ "level": {
+ "type": "str",
+ "choices": [
+ "level-1",
+ "level-1-2",
+ "level-2",
+ ],
+ },
+ "local_preference": {"type": "int"},
+ "metric": {
+ "type": "dict",
+ "options": {
+ "bandwidth": {"type": "int"},
+ "igrp_delay_metric": {"type": "int"},
+ "igrp_reliability_metric": {"type": "int"},
+ "igrp_effective_bandwidth_metric": {"type": "int"},
+ "igrp_mtu": {"type": "int"},
+ },
+ },
+ "metric_type": {
+ "type": "str",
+ "choices": [
+ "external",
+ "internal",
+ "type-1",
+ "type-2",
+ ],
+ },
+ "nssa_only": {"type": "bool"},
+ "origin": {
+ "type": "str",
+ "choices": ["egp", "igp", "incomplete"],
+ },
+ "path_selection": {
+ "type": "str",
+ "choices": [
+ "all",
+ "backup",
+ "best2",
+ "multipaths",
+ ],
+ },
+ "tag": {"type": "int"},
+ "weight": {"type": "int"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "parsed",
+ "gathered",
+ "rendered",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/snmp_server.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/snmp_server.py
new file mode 100644
index 00000000..a6b3d420
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/snmp_server/snmp_server.py
@@ -0,0 +1,411 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the
+# cli_rm_builder.
+#
+# Manually editing this file is not advised.
+#
+# To update the argspec make the desired changes
+# in the module docstring and re-run
+# cli_rm_builder.
+#
+#############################################
+
+"""
+The arg spec for the nxos_snmp_server module
+"""
+
+
+class Snmp_serverArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_snmp_server module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "type": "dict",
+ "options": {
+ "aaa_user": {
+ "type": "dict",
+ "options": {"cache_timeout": {"type": "int"}},
+ },
+ "communities": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "name": {"type": "str", "aliases": ["community"]},
+ "group": {"type": "str"},
+ "ro": {"type": "bool"},
+ "rw": {"type": "bool"},
+ "use_ipv4acl": {"type": "str"},
+ "use_ipv6acl": {"type": "str"},
+ },
+ },
+ "contact": {"type": "str"},
+ "context": {
+ "type": "dict",
+ "options": {
+ "name": {"type": "str"},
+ "instance": {"type": "str"},
+ "topology": {"type": "str"},
+ "vrf": {"type": "str"},
+ },
+ },
+ "counter": {
+ "type": "dict",
+ "options": {
+ "cache": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "timeout": {"type": "int"},
+ },
+ },
+ },
+ },
+ "drop": {
+ "type": "dict",
+ "options": {
+ "unknown_engine_id": {"type": "bool"},
+ "unknown_user": {"type": "bool"},
+ },
+ },
+ "traps": {
+ "type": "dict",
+ "options": {
+ "aaa": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "server_state_change": {"type": "bool"},
+ },
+ },
+ "bgp": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ },
+ },
+ "bridge": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "newroot": {"type": "bool"},
+ "topologychange": {"type": "bool"},
+ },
+ },
+ "callhome": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "event_notify": {"type": "bool"},
+ "smtp_send_fail": {"type": "bool"},
+ },
+ },
+ "cfs": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "merge_failure": {"type": "bool"},
+ "state_change_notif": {"type": "bool"},
+ },
+ },
+ "config": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "ccmCLIRunningConfigChanged": {"type": "bool"},
+ },
+ },
+ "entity": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "cefcMIBEnableStatusNotification": {"type": "bool"},
+ "entity_fan_status_change": {"type": "bool"},
+ "entity_mib_change": {"type": "bool"},
+ "entity_module_inserted": {"type": "bool"},
+ "entity_module_removed": {"type": "bool"},
+ "entity_module_status_change": {"type": "bool"},
+ "entity_power_out_change": {"type": "bool"},
+ "entity_power_status_change": {"type": "bool"},
+ "entity_sensor": {"type": "bool"},
+ "entity_unrecognised_module": {"type": "bool"},
+ },
+ },
+ "feature_control": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "featureOpStatusChange": {"type": "bool"},
+ "ciscoFeatOpStatusChange": {"type": "bool"},
+ },
+ },
+ "generic": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "coldStart": {"type": "bool"},
+ "warmStart": {"type": "bool"},
+ },
+ },
+ "license": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "notify_license_expiry": {"type": "bool"},
+ "notify_license_expiry_warning": {"type": "bool"},
+ "notify_licensefile_missing": {"type": "bool"},
+ "notify_no_license_for_feature": {"type": "bool"},
+ },
+ },
+ "link": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "cErrDisableInterfaceEventRev1": {"type": "bool"},
+ "cieLinkDown": {"type": "bool"},
+ "cieLinkUp": {"type": "bool"},
+ "cisco_xcvr_mon_status_chg": {"type": "bool"},
+ "cmn_mac_move_notification": {"type": "bool"},
+ "delayed_link_state_change": {"type": "bool"},
+ "extended_linkDown": {"type": "bool"},
+ "extended_linkUp": {"type": "bool"},
+ "linkDown": {"type": "bool"},
+ "linkUp": {"type": "bool"},
+ },
+ },
+ "mmode": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "cseMaintModeChangeNotify": {"type": "bool"},
+ "cseNormalModeChangeNotify": {"type": "bool"},
+ },
+ },
+ "ospf": {
+ "type": "dict",
+ "options": {"enable": {"type": "bool"}},
+ },
+ "ospfv3": {
+ "type": "dict",
+ "options": {"enable": {"type": "bool"}},
+ },
+ "rf": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "redundancy_framework": {"type": "bool"},
+ },
+ },
+ "rmon": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "fallingAlarm": {"type": "bool"},
+ "hcFallingAlarm": {"type": "bool"},
+ "hcRisingAlarm": {"type": "bool"},
+ "risingAlarm": {"type": "bool"},
+ },
+ },
+ "snmp": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "authentication": {"type": "bool"},
+ },
+ },
+ "storm_control": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "cpscEventRev1": {"type": "bool"},
+ "trap_rate": {"type": "bool"},
+ },
+ },
+ "stpx": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "inconsistency": {"type": "bool"},
+ "loop_inconsistency": {"type": "bool"},
+ "root_inconsistency": {"type": "bool"},
+ },
+ },
+ "syslog": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "message_generated": {"type": "bool"},
+ },
+ },
+ "sysmgr": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "cseFailSwCoreNotifyExtended": {"type": "bool"},
+ },
+ },
+ "system": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "clock_change_notification": {"type": "bool"},
+ },
+ },
+ "upgrade": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "upgradeJobStatusNotify": {"type": "bool"},
+ "upgradeOpNotifyOnCompletion": {"type": "bool"},
+ },
+ },
+ "vtp": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "notifs": {"type": "bool"},
+ "vlancreate": {"type": "bool"},
+ "vlandelete": {"type": "bool"},
+ },
+ },
+ },
+ },
+ "engine_id": {
+ "type": "dict",
+ "options": {"local": {"type": "str"}},
+ },
+ "global_enforce_priv": {"type": "bool"},
+ "hosts": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "host": {"type": "str"},
+ "community": {"type": "str"},
+ "filter_vrf": {"type": "str"},
+ "informs": {"type": "bool"},
+ "source_interface": {"type": "str"},
+ "traps": {"type": "bool"},
+ "use_vrf": {"type": "str"},
+ "version": {
+ "type": "str",
+ "choices": ["1", "2c", "3"],
+ },
+ "auth": {"type": "str"},
+ "priv": {"type": "str"},
+ "udp_port": {"type": "int"},
+ },
+ },
+ "location": {"type": "str"},
+ "mib": {
+ "type": "dict",
+ "options": {
+ "community_map": {
+ "type": "dict",
+ "options": {
+ "community": {"type": "str"},
+ "context": {"type": "str"},
+ },
+ },
+ },
+ },
+ "packetsize": {"type": "int"},
+ "protocol": {
+ "type": "dict",
+ "options": {"enable": {"type": "bool"}},
+ },
+ "source_interface": {
+ "type": "dict",
+ "options": {
+ "informs": {"type": "str"},
+ "traps": {"type": "str"},
+ },
+ },
+ "system_shutdown": {"type": "bool"},
+ "tcp_session": {
+ "type": "dict",
+ "options": {
+ "enable": {"type": "bool"},
+ "auth": {"type": "bool"},
+ },
+ },
+ "users": {
+ "type": "dict",
+ "options": {
+ "auth": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "user": {"type": "str"},
+ "group": {"type": "str"},
+ "authentication": {
+ "type": "dict",
+ "options": {
+ "algorithm": {
+ "type": "str",
+ "choices": [
+ "md5",
+ "sha",
+ "sha-256",
+ ],
+ },
+ "password": {
+ "type": "str",
+ "no_log": False,
+ },
+ "engine_id": {"type": "str"},
+ "localized_key": {"type": "bool"},
+ "localizedv2_key": {"type": "bool"},
+ "priv": {
+ "type": "dict",
+ "options": {
+ "privacy_password": {
+ "type": "str",
+ "no_log": False,
+ },
+ "aes_128": {"type": "bool"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "use_acls": {
+ "type": "list",
+ "elements": "dict",
+ "options": {
+ "user": {"type": "str"},
+ "ipv4": {"type": "str"},
+ "ipv6": {"type": "str"},
+ },
+ },
+ },
+ },
+ },
+ },
+ "state": {
+ "type": "str",
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "parsed",
+ "gathered",
+ "rendered",
+ ],
+ "default": "merged",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/static_routes.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/static_routes.py
new file mode 100644
index 00000000..58a669bd
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/static_routes/static_routes.py
@@ -0,0 +1,89 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The arg spec for the nxos_static_routes module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class Static_routesArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_static_routes module"""
+
+ argument_spec = {
+ "config": {
+ "elements": "dict",
+ "options": {
+ "address_families": {
+ "elements": "dict",
+ "options": {
+ "afi": {
+ "choices": ["ipv4", "ipv6"],
+ "required": True,
+ "type": "str",
+ },
+ "routes": {
+ "elements": "dict",
+ "options": {
+ "dest": {"required": True, "type": "str"},
+ "next_hops": {
+ "elements": "dict",
+ "options": {
+ "admin_distance": {"type": "int"},
+ "dest_vrf": {"type": "str"},
+ "forward_router_address": {"type": "str"},
+ "interface": {"type": "str"},
+ "route_name": {"type": "str"},
+ "tag": {"type": "int"},
+ "track": {"type": "int"},
+ },
+ "type": "list",
+ },
+ },
+ "type": "list",
+ },
+ },
+ "type": "list",
+ },
+ "vrf": {"type": "str"},
+ },
+ "type": "list",
+ },
+ "running_config": {"type": "str"},
+ "state": {
+ "choices": [
+ "deleted",
+ "merged",
+ "overridden",
+ "replaced",
+ "gathered",
+ "rendered",
+ "parsed",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/telemetry.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/telemetry.py
new file mode 100644
index 00000000..7da72979
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/telemetry/telemetry.py
@@ -0,0 +1,115 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the nxos_telemetry module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class TelemetryArgs(object): # pylint: disable=R0903
+ """The arg spec for the nxos_telemetry module"""
+
+ argument_spec = {
+ "config": {
+ "options": {
+ "certificate": {
+ "options": {
+ "hostname": {"type": "str"},
+ "key": {"type": "str", "no_log": False},
+ },
+ "type": "dict",
+ },
+ "compression": {"choices": ["gzip"], "type": "str"},
+ "source_interface": {"type": "str"},
+ "vrf": {"type": "str"},
+ "destination_groups": {
+ "options": {
+ "destination": {
+ "options": {
+ "encoding": {
+ "choices": ["GPB", "JSON"],
+ "type": "str",
+ },
+ "ip": {"type": "str"},
+ "port": {"type": "int"},
+ "protocol": {
+ "choices": ["HTTP", "TCP", "UDP", "gRPC"],
+ "type": "str",
+ },
+ },
+ "type": "dict",
+ },
+ "id": {"type": "str"},
+ },
+ "type": "list",
+ "elements": "raw",
+ },
+ "sensor_groups": {
+ "options": {
+ "data_source": {
+ "choices": ["NX-API", "DME", "YANG"],
+ "type": "str",
+ },
+ "id": {"type": "str"},
+ "path": {
+ "options": {
+ "depth": {"type": "str"},
+ "filter_condition": {"type": "str"},
+ "name": {"type": "str"},
+ "query_condition": {"type": "str"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "list",
+ "elements": "raw",
+ },
+ "subscriptions": {
+ "options": {
+ "destination_group": {"type": "str"},
+ "id": {"type": "str"},
+ "sensor_group": {
+ "options": {
+ "id": {"type": "str"},
+ "sample_interval": {"type": "int"},
+ },
+ "type": "dict",
+ },
+ },
+ "type": "list",
+ "elements": "raw",
+ },
+ },
+ "type": "dict",
+ },
+ "state": {
+ "choices": ["merged", "replaced", "deleted", "gathered"],
+ "default": "merged",
+ "type": "str",
+ },
+ } # pylint: disable=C0301
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/vlans.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/vlans.py
new file mode 100644
index 00000000..4f915f09
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/argspec/vlans/vlans.py
@@ -0,0 +1,64 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The arg spec for the nxos_vlans module
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+class VlansArgs(object):
+ """The arg spec for the nxos_vlans module"""
+
+ argument_spec = {
+ "running_config": {"type": "str"},
+ "config": {
+ "elements": "dict",
+ "options": {
+ "enabled": {"type": "bool"},
+ "mapped_vni": {"type": "int"},
+ "mode": {"choices": ["ce", "fabricpath"], "type": "str"},
+ "name": {"type": "str"},
+ "vlan_id": {"required": True, "type": "int"},
+ "state": {"choices": ["active", "suspend"], "type": "str"},
+ },
+ "type": "list",
+ },
+ "state": {
+ "choices": [
+ "merged",
+ "replaced",
+ "overridden",
+ "deleted",
+ "rendered",
+ "gathered",
+ "parsed",
+ ],
+ "default": "merged",
+ "type": "str",
+ },
+ }
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/telemetry.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/telemetry.py
new file mode 100644
index 00000000..f5844538
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/cmdref/telemetry/telemetry.py
@@ -0,0 +1,147 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+# Telemetry Command Reference File
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+TMS_GLOBAL = """
+# The cmd_ref is a yaml formatted list of module commands.
+# A leading underscore denotes a non-command variable; e.g. _template.
+# TMS does not have convenient global json data so this cmd_ref uses raw cli configs.
+---
+_template: # _template holds common settings for all commands
+ # Enable feature telemetry if disabled
+ feature: telemetry
+ # Common get syntax for TMS commands
+ get_command: show run telemetry all
+ # Parent configuration for TMS commands
+ context:
+ - telemetry
+certificate:
+ _exclude: ['N3K', 'N5K', 'N6k', 'N7k']
+ kind: dict
+ getval: certificate (?P<key>\\S+) (?P<hostname>\\S+)$
+ setval: certificate {key} {hostname}
+ default:
+ key: ~
+ hostname: ~
+compression:
+ _exclude: ['N3K', 'N5K', 'N6k', 'N7k']
+ kind: str
+ getval: use-compression (\\S+)$
+ setval: 'use-compression {0}'
+ default: ~
+ context: &dpcontext
+ - telemetry
+ - destination-profile
+source_interface:
+ _exclude: ['N3K', 'N5K', 'N6k', 'N7k']
+ kind: str
+ getval: source-interface (\\S+)$
+ setval: 'source-interface {0}'
+ default: ~
+ context: *dpcontext
+vrf:
+ _exclude: ['N3K', 'N5K', 'N6k', 'N7k']
+ kind: str
+ getval: use-vrf (\\S+)$
+ setval: 'use-vrf {0}'
+ default: ~
+ context: *dpcontext
+"""
+
+TMS_DESTGROUP = """
+# The cmd_ref is a yaml formatted list of module commands.
+# A leading underscore denotes a non-command variable; e.g. _template.
+# TBD: Use Structured Where Possible
+---
+_template: # _template holds common settings for all commands
+ # Enable feature telemetry if disabled
+ feature: telemetry
+ # Common get syntax for TMS commands
+ get_command: show run telemetry all
+ # Parent configuration for TMS commands
+ context:
+ - telemetry
+destination:
+ _exclude: ['N3K', 'N5K', 'N6k', 'N7k']
+ multiple: true
+ kind: dict
+ getval: ip address (?P<ip>\\S+) port (?P<port>\\S+) protocol (?P<protocol>\\S+) encoding (?P<encoding>\\S+)
+ setval: ip address {ip} port {port} protocol {protocol} encoding {encoding}
+ default:
+ ip: ~
+ port: ~
+ protocol: ~
+ encoding: ~
+"""
+
+TMS_SENSORGROUP = """
+# The cmd_ref is a yaml formatted list of module commands.
+# A leading underscore denotes a non-command variable; e.g. _template.
+# TBD: Use Structured Where Possible
+---
+_template: # _template holds common settings for all commands
+ # Enable feature telemetry if disabled
+ feature: telemetry
+ # Common get syntax for TMS commands
+ get_command: show run telemetry all
+ # Parent configuration for TMS commands
+ context:
+ - telemetry
+data_source:
+ _exclude: ['N3K', 'N5K', 'N6k', 'N7k']
+ kind: str
+ getval: data-source (\\S+)$
+ setval: 'data-source {0}'
+ default: ~
+path:
+ _exclude: ['N3K', 'N5K', 'N6k', 'N7k']
+ multiple: true
+ kind: dict
+ getval: path (?P<name>(\\S+|".*"))( depth (?P<depth>\\S+))?( query-condition (?P<query_condition>\\S+))?( filter-condition (?P<filter_condition>\\S+))?$
+ setval: path {name} depth {depth} query-condition {query_condition} filter-condition {filter_condition}
+ default:
+ name: ~
+ depth: ~
+ query_condition: ~
+ filter_condition: ~
+"""
+
+TMS_SUBSCRIPTION = """
+# The cmd_ref is a yaml formatted list of module commands.
+# A leading underscore denotes a non-command variable; e.g. _template.
+# TBD: Use Structured Where Possible
+---
+_template: # _template holds common settings for all commands
+ # Enable feature telemetry if disabled
+ feature: telemetry
+ # Common get syntax for TMS commands
+ get_command: show run telemetry all
+ # Parent configuration for TMS commands
+ context:
+ - telemetry
+destination_group:
+ _exclude: ['N3K', 'N5K', 'N6k', 'N7k']
+ multiple: true
+ kind: str
+ getval: dst-grp (\\S+)$
+ setval: 'dst-grp {0}'
+ default: ~
+sensor_group:
+ _exclude: ['N3K', 'N5K', 'N6k', 'N7k']
+ multiple: true
+ kind: dict
+ getval: snsr-grp (?P<id>\\S+) sample-interval (?P<sample_interval>\\S+)$
+ setval: snsr-grp {id} sample-interval {sample_interval}
+ default:
+ id: ~
+ sample_interval: ~
+"""
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/acl_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/acl_interfaces.py
new file mode 100644
index 00000000..5bd15062
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acl_interfaces/acl_interfaces.py
@@ -0,0 +1,321 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_acl_interfaces class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_empties,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ get_interface_type,
+ normalize_interface,
+ search_obj_in_list,
+)
+
+
+class Acl_interfaces(ConfigBase):
+ """
+ The nxos_acl_interfaces class
+ """
+
+ gather_subset = ["!all", "!min"]
+
+ gather_network_resources = ["acl_interfaces"]
+
+ def __init__(self, module):
+ super(Acl_interfaces, self).__init__(module)
+
+ def get_acl_interfaces_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ acl_interfaces_facts = facts["ansible_network_resources"].get("acl_interfaces")
+ if not acl_interfaces_facts:
+ return []
+ return acl_interfaces_facts
+
+ def edit_config(self, commands):
+ """Wrapper method for `_connection.edit_config()`
+ This exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return self._connection.edit_config(commands)
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ warnings = list()
+ commands = list()
+ self.state = self._module.params["state"]
+ action_states = ["merged", "replaced", "deleted", "overridden"]
+
+ if self.state == "gathered":
+ result["gathered"] = self.get_acl_interfaces_facts()
+ elif self.state == "rendered":
+ result["rendered"] = self.set_config({})
+ # no need to fetch facts for rendered
+ elif self.state == "parsed":
+ running_config = self._module.params["running_config"]
+ if not running_config:
+ self._module.fail_json(
+ msg="value of running_config parameter must not be empty for state parsed",
+ )
+ result["parsed"] = self.set_config({})
+ # no need to fetch facts for parsed
+ else:
+ existing_acl_interfaces_facts = self.get_acl_interfaces_facts()
+ commands.extend(self.set_config(existing_acl_interfaces_facts))
+ if commands and self.state in action_states:
+ if not self._module.check_mode:
+ self._connection.edit_config(commands)
+ result["changed"] = True
+ result["before"] = existing_acl_interfaces_facts
+ result["commands"] = commands
+
+ changed_acl_interfaces_facts = self.get_acl_interfaces_facts()
+ if result["changed"]:
+ result["after"] = changed_acl_interfaces_facts
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_acl_interfaces_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ config = self._module.params["config"]
+ want = []
+ if config:
+ for w in config:
+ if get_interface_type(w["name"]) == "loopback":
+ self._module.fail_json(
+ msg="This module works with ethernet, management or port-channe",
+ )
+ w.update({"name": normalize_interface(w["name"])})
+ want.append(remove_empties(w))
+ have = existing_acl_interfaces_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ if self.state in ("overridden", "merged", "replaced", "rendered") and not want:
+ self._module.fail_json(
+ msg="value of config parameter must not be empty for state {0}".format(self.state),
+ )
+
+ commands = []
+ if self.state == "overridden":
+ commands = self._state_overridden(want, have)
+ elif self.state == "deleted":
+ commands = self._state_deleted(want, have)
+ elif self.state == "rendered":
+ commands = self._state_rendered(want)
+ elif self.state == "parsed":
+ want = self._module.params["running_config"]
+ commands = self._state_parsed(want)
+ else:
+ for w in want:
+ if self.state == "merged":
+ commands.extend(self._state_merged(w, have))
+ elif self.state == "replaced":
+ commands.extend(self._state_replaced(w, have))
+ return commands
+
+ def _state_parsed(self, want):
+ return self.get_acl_interfaces_facts(want)
+
+ def _state_rendered(self, want):
+ commands = []
+ for w in want:
+ commands.extend(self.set_commands(w, {}))
+ return commands
+
+ def _state_replaced(self, want, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ new_commands = []
+ del_dict = {"name": want["name"], "access_groups": []}
+ obj_in_have = search_obj_in_list(want["name"], have, "name")
+ if obj_in_have != want:
+ commands = []
+ if obj_in_have and "access_groups" in obj_in_have.keys():
+ for ag in obj_in_have["access_groups"]:
+ want_afi = []
+ if want.get("access_groups"):
+ want_afi = search_obj_in_list(ag["afi"], want["access_groups"], "afi")
+ if not want_afi:
+ # whatever in have is not in want
+ del_dict["access_groups"].append(ag)
+ else:
+ del_acl = []
+ for acl in ag["acls"]:
+ if want_afi.get("acls"):
+ if acl not in want_afi["acls"]:
+ del_acl.append(acl)
+ else:
+ del_acl.append(acl)
+ afi = want_afi["afi"]
+ del_dict["access_groups"].append({"afi": afi, "acls": del_acl})
+
+ commands.extend(self._state_deleted([del_dict], have))
+ commands.extend(self._state_merged(want, have))
+ new_commands.append(commands[0])
+ commands = [commands[i] for i in range(1, len(commands)) if commands[i] != commands[0]]
+ new_commands.extend(commands)
+ return new_commands
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ want_intf = [w["name"] for w in want]
+ for h in have:
+ if h["name"] not in want_intf:
+ commands.extend(self._state_deleted([h], have))
+ for w in want:
+ commands.extend(self._state_replaced(w, have))
+ return commands
+
+ def _state_merged(self, want, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(want, have)
+
+ def set_commands(self, want, have, deleted=False):
+ commands = []
+ have_name = search_obj_in_list(want["name"], have, "name")
+ if have_name and have_name.get("access_groups"):
+ if want.get("access_groups"):
+ for w_afi in want["access_groups"]:
+ ip = "ipv6"
+ if w_afi["afi"] == "ipv4":
+ ip = "ip"
+ have_afi = search_obj_in_list(w_afi["afi"], have_name["access_groups"], "afi")
+ if have_afi:
+ new_acls = []
+ if deleted:
+ if w_afi.get("acls") and have_afi.get("acls"):
+ new_acls = [
+ acl for acl in w_afi.get("acls") if acl in have_afi.get("acls")
+ ]
+ elif "acls" not in w_afi.keys():
+ new_acls = have_afi.get("acls")
+ else:
+ if w_afi.get("acls"):
+ new_acls = [
+ acl for acl in w_afi["acls"] if acl not in have_afi["acls"]
+ ]
+ commands.extend(self.process_acl(new_acls, ip, deleted))
+ else:
+ if not deleted:
+ if w_afi.get("acls"):
+ commands.extend(self.process_acl(w_afi["acls"], ip))
+ else:
+ # only name is given to delete
+ if deleted and "access_groups" in have_name.keys():
+ commands.extend(self.process_access_group(have_name, True))
+ else:
+ if not deleted: # and 'access_groups' in have_name.keys():
+ commands.extend(self.process_access_group(want))
+
+ if len(commands) > 0:
+ commands.insert(0, "interface " + want["name"])
+ return commands
+
+ def process_access_group(self, item, deleted=False):
+ commands = []
+ for ag in item["access_groups"]:
+ ip = "ipv6"
+ if ag["afi"] == "ipv4":
+ ip = "ip"
+ if ag.get("acls"):
+ commands.extend(self.process_acl(ag["acls"], ip, deleted))
+ return commands
+
+ def process_acl(self, acls, ip, deleted=False):
+ commands = []
+ no = ""
+ if deleted:
+ no = "no "
+ for acl in acls:
+ port = ""
+ if acl.get("port"):
+ port = " port"
+ ag = " access-group "
+ if ip == "ipv6":
+ ag = " traffic-filter "
+ commands.append(no + ip + port + ag + acl["name"] + " " + acl["direction"])
+ return commands
+
+ def _state_deleted(self, main_want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if main_want:
+ if self.state == "deleted":
+ for w in main_want:
+ h = search_obj_in_list(w["name"], have, "name") or {}
+ commands.extend(self.set_commands(h, have, deleted=True))
+ else:
+ for want in main_want:
+ commands.extend(self.set_commands(want, have, deleted=True))
+ else:
+ for h in have:
+ commands.extend(self.set_commands(h, have, deleted=True))
+
+ return commands
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/acls.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/acls.py
new file mode 100644
index 00000000..5e6f3c34
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/acls/acls.py
@@ -0,0 +1,674 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_acls class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_empties,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acls.acls import (
+ AclsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ search_obj_in_list,
+)
+
+
+class Acls(ConfigBase):
+ """
+ The nxos_acls class
+ """
+
+ gather_subset = ["!all", "!min"]
+
+ gather_network_resources = ["acls"]
+
+ def __init__(self, module):
+ super(Acls, self).__init__(module)
+ self.state = self._module.params["state"]
+
+ def get_acls_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ acls_facts = facts["ansible_network_resources"].get("acls")
+ if not acls_facts:
+ return []
+ return acls_facts
+
+ def edit_config(self, commands):
+ """Wrapper method for `_connection.edit_config()`
+ This exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return self._connection.edit_config(commands)
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ warnings = list()
+ commands = list()
+ state = self.state
+ action_states = ["merged", "replaced", "deleted", "overridden"]
+
+ if state == "gathered":
+ result["gathered"] = self.get_acls_facts()
+ elif state == "rendered":
+ result["rendered"] = self.set_config({})
+ elif state == "parsed":
+ result["parsed"] = self.set_config({})
+ else:
+ existing_acls_facts = self.get_acls_facts()
+ commands.extend(self.set_config(existing_acls_facts))
+ result["before"] = existing_acls_facts
+ if commands and state in action_states:
+ if not self._module.check_mode:
+ self._connection.edit_config(commands)
+ result["changed"] = True
+ result["commands"] = commands
+
+ changed_acls_facts = self.get_acls_facts()
+ if result["changed"]:
+ result["after"] = changed_acls_facts
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_acls_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ config = self._module.params["config"]
+ want = []
+ if config:
+ for w in config:
+ want.append(remove_empties(w))
+ have = existing_acls_facts
+ if want:
+ want = self.convert_values(want)
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def convert_values(self, want):
+ """
+ This method is used to map and convert the user given values with what will actually be present in the device configuation
+ """
+ port_protocol = {
+ 515: "lpd",
+ 517: "talk",
+ 7: "echo",
+ 9: "discard",
+ 12: "exec",
+ 13: "login",
+ 14: "cmd",
+ 109: "pop2",
+ 19: "chargen",
+ 20: "ftp-data",
+ 21: "ftp",
+ 23: "telnet",
+ 25: "smtp",
+ 540: "uucp",
+ 543: "klogin",
+ 544: "kshell",
+ 37: "time",
+ 43: "whois",
+ 49: "tacacs",
+ 179: "bgp",
+ 53: "domain",
+ 194: "irc",
+ 70: "gopher",
+ 79: "finger",
+ 80: "www",
+ 101: "hostname",
+ 3949: "drip",
+ 110: "pop3",
+ 111: "sunrpc",
+ 496: "pim-auto-rp",
+ 113: "ident",
+ 119: "nntp",
+ }
+ protocol = {
+ 1: "icmp",
+ 2: "igmp",
+ 4: "ip",
+ 6: "tcp",
+ 103: "pim",
+ 108: "pcp",
+ 47: "gre",
+ 17: "udp",
+ 50: "esp",
+ 51: "ahp",
+ 88: "eigrp",
+ 89: "ospf",
+ 94: "nos",
+ }
+ precedence = {
+ 0: "routine",
+ 1: "priority",
+ 2: "immediate",
+ 3: "flash",
+ 4: "flash-override",
+ 5: "critical",
+ 6: "internet",
+ 7: "network",
+ }
+ dscp = {
+ 10: "AF11",
+ 12: "AF12",
+ 14: "AF13",
+ 18: "AF21",
+ 20: "AF22",
+ 22: "AF23",
+ 26: "AF31",
+ 28: "AF32",
+ 30: "AF33",
+ 34: "AF41",
+ 36: "AF42",
+ 38: "AF43",
+ 8: "CS1",
+ 16: "CS2",
+ 24: "CS3",
+ 32: "CS4",
+ 40: "CS5",
+ 48: "CS6",
+ 56: "CS7",
+ 0: "Default",
+ 46: "EF",
+ }
+ # port_pro_num = list(protocol.keys())
+ for afi in want:
+ if "acls" in afi.keys():
+ for acl in afi["acls"]:
+ if "aces" in acl.keys():
+ for ace in acl["aces"]:
+ if "dscp" in ace.keys():
+ if ace["dscp"] in dscp:
+ ace["dscp"] = dscp[int(ace["dscp"])]
+ if not ace["dscp"].isdigit():
+ ace["dscp"] = ace["dscp"].lower()
+ if "precedence" in ace.keys():
+ if ace["precedence"].isdigit():
+ ace["precedence"] = precedence[int(ace["precedence"])]
+ if (
+ "protocol" in ace.keys()
+ and ace["protocol"].isdigit()
+ and int(ace["protocol"]) in protocol.keys()
+ ):
+ ace["protocol"] = protocol[int(ace["protocol"])]
+ # convert number to name
+ if "protocol" in ace.keys() and ace["protocol"] in ["tcp", "udp"]:
+ for x in ["source", "destination"]:
+ if "port_protocol" in ace[x].keys():
+ key = list(ace[x]["port_protocol"].keys())[0]
+ # key could be eq,gt,lt,neq or range
+ if key != "range":
+ val = ace[x]["port_protocol"][key]
+ if val.isdigit() and int(val) in port_protocol.keys():
+ ace[x]["port_protocol"][key] = port_protocol[
+ int(val)
+ ]
+ else:
+ st = int(ace[x]["port_protocol"]["range"]["start"])
+ end = int(ace[x]["port_protocol"]["range"]["end"])
+
+ if st in port_protocol.keys():
+ ace[x]["port_protocol"]["range"][
+ "start"
+ ] = port_protocol[st]
+ if end in port_protocol.keys():
+ ace[x]["port_protocol"]["range"][
+ "end"
+ ] = port_protocol[end]
+ return want
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self.state
+ commands = []
+ if state == "overridden":
+ commands = self._state_overridden(want, have)
+ elif state == "deleted":
+ commands = self._state_deleted(want, have)
+ elif state == "rendered":
+ commands = self._state_rendered(want)
+ elif state == "parsed":
+ want = self._module.params["running_config"]
+ commands = self._state_parsed(want)
+ else:
+ for w in want:
+ if state == "merged":
+ commands.extend(self._state_merged(w, have))
+ elif state == "replaced":
+ commands.extend(self._state_replaced(w, have))
+ if state != "parsed":
+ commands = [c.strip() for c in commands]
+ return commands
+
+ def _state_parsed(self, want):
+ return self.get_acls_facts(want)
+
+ def _state_rendered(self, want):
+ commands = []
+ for w in want:
+ commands.extend(self.set_commands(w, {}))
+ return commands
+
+ def _state_replaced(self, want, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ have_afi = search_obj_in_list(want["afi"], have, "afi")
+ del_dict = {"acls": []}
+ want_names = []
+ if have_afi != want:
+ if have_afi:
+ del_dict.update({"afi": have_afi["afi"], "acls": []})
+ if want.get("acls"):
+ want_names = [w["name"] for w in want["acls"]]
+ have_names = [h["name"] for h in have_afi["acls"]]
+ want_acls = want.get("acls")
+ for w in want_acls:
+ acl_commands = []
+ if w["name"] not in have_names:
+ # creates new ACL in replaced state
+ merge_dict = {"afi": want["afi"], "acls": [w]}
+ commands.extend(self._state_merged(merge_dict, have))
+ else:
+ # acl in want exists in have
+ have_name = search_obj_in_list(w["name"], have_afi["acls"], "name")
+ have_aces = have_name.get("aces") if have_name.get("aces") else []
+ merge_aces = []
+ del_aces = []
+ w_aces = w.get("aces") if w.get("aces") else []
+
+ for ace in have_aces:
+ if ace not in w_aces:
+ del_aces.append(ace)
+ for ace in w_aces:
+ if ace not in have_aces:
+ merge_aces.append(ace)
+ merge_dict = {
+ "afi": want["afi"],
+ "acls": [{"name": w["name"], "aces": merge_aces}],
+ }
+ del_dict = {
+ "afi": want["afi"],
+ "acls": [{"name": w["name"], "aces": del_aces}],
+ }
+ if del_dict["acls"]:
+ acl_commands.extend(self._state_deleted([del_dict], have))
+ acl_commands.extend(self._state_merged(merge_dict, have))
+
+ for i in range(1, len(acl_commands)):
+ if acl_commands[i] == acl_commands[0]:
+ acl_commands[i] = ""
+ commands.extend(acl_commands)
+ else:
+ acls = []
+ # no acls given in want, so delete all have acls
+ for acl in have_afi["acls"]:
+ acls.append({"name": acl["name"]})
+ del_dict["acls"] = acls
+ if del_dict["acls"]:
+ commands.extend(self._state_deleted([del_dict], have))
+
+ else:
+ # want_afi is not present in have
+ commands.extend(self._state_merged(want, have))
+
+ commands = list(filter(None, commands))
+ return commands
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ want_afi = [w["afi"] for w in want]
+ for h in have:
+ if h["afi"] in want_afi:
+ w = search_obj_in_list(h["afi"], want, "afi")
+ for h_acl in h["acls"]:
+ w_acl = search_obj_in_list(h_acl["name"], w["acls"], "name")
+ if not w_acl:
+ del_dict = {
+ "afi": h["afi"],
+ "acls": [{"name": h_acl["name"]}],
+ }
+ commands.extend(self._state_deleted([del_dict], have))
+ else:
+ # if afi is not in want
+ commands.extend(self._state_deleted([{"afi": h["afi"]}], have))
+ for w in want:
+ commands.extend(self._state_replaced(w, have))
+ return commands
+
+ def _state_merged(self, want, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(want, have)
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if want: # and have != want:
+ for w in want:
+ ip = "ipv6" if w["afi"] == "ipv6" else "ip"
+ acl_names = []
+ have_afi = search_obj_in_list(w["afi"], have, "afi")
+ # if want['afi] not in have, ignore
+ if have_afi:
+ if w.get("acls"):
+ for acl in w["acls"]:
+ if "aces" in acl.keys() and self.state != "deleted":
+ have_name = search_obj_in_list(
+ acl["name"],
+ have_afi["acls"],
+ "name",
+ )
+ if have_name:
+ ace_commands = []
+ flag = 0
+ for ace in acl["aces"]:
+ if list(ace.keys()) == ["sequence"]:
+ # only sequence number is specified to be deleted
+ if "aces" in have_name.keys():
+ for h_ace in have_name["aces"]:
+ if h_ace["sequence"] == ace["sequence"]:
+ ace_commands.append(
+ "no " + str(ace["sequence"]),
+ )
+ flag = 1
+ else:
+ if "aces" in have_name.keys():
+ for h_ace in have_name["aces"]:
+ # when want['ace'] does not have seq number
+ if "sequence" not in ace.keys():
+ del h_ace["sequence"]
+ if ace == h_ace:
+ ace_commands.append(
+ "no " + self.process_ace(ace),
+ )
+ flag = 1
+ if flag:
+ ace_commands.insert(
+ 0,
+ ip + " access-list " + acl["name"],
+ )
+ commands.extend(ace_commands)
+ else:
+ # only name given
+ for h in have_afi["acls"]:
+ if h["name"] == acl["name"]:
+ acl_names.append(acl["name"])
+ for name in acl_names:
+ commands.append("no " + ip + " access-list " + name)
+
+ else:
+ # 'only afi is given'
+ if have_afi.get("acls"):
+ for h in have_afi["acls"]:
+ acl_names.append(h["name"])
+ for name in acl_names:
+ commands.append("no " + ip + " access-list " + name)
+ else:
+ v6 = []
+ v4 = []
+ v6_local = v4_local = None
+ for h in have:
+ if h["afi"] == "ipv6":
+ v6 = (acl["name"] for acl in h["acls"])
+ if "match_local_traffic" in h.keys():
+ v6_local = True
+ else:
+ v4 = (acl["name"] for acl in h["acls"])
+ if "match_local_traffic" in h.keys():
+ v4_local = True
+
+ self.no_commands(v4, commands, v4_local, "ip")
+ self.no_commands(v6, commands, v6_local, "ipv6")
+
+ for name in v6:
+ commands.append("no ipv6 access-list " + name)
+ if v4_local:
+ commands.append("no ipv6 access-list match-local-traffic")
+
+ return commands
+
+ def no_commands(self, v_list, commands, match_local, ip):
+ for name in v_list:
+ commands.append("no " + ip + " access-list " + name)
+ if match_local:
+ commands.append("no " + ip + " access-list match-local-traffic")
+
+ def set_commands(self, want, have):
+ commands = []
+ have_afi = search_obj_in_list(want["afi"], have, "afi")
+ ip = ""
+ if "v6" in want["afi"]:
+ ip = "ipv6 "
+ else:
+ ip = "ip "
+
+ if have_afi:
+ if want.get("acls"):
+ for w_acl in want["acls"]:
+ have_acl = search_obj_in_list(w_acl["name"], have_afi["acls"], "name")
+ name = w_acl["name"]
+ flag = 0
+ ace_commands = []
+ if have_acl != w_acl:
+ if have_acl:
+ ace_list = []
+ if w_acl.get("aces") and have_acl.get("aces"):
+ # case 1 --> sequence number not given in want --> new ace
+ # case 2 --> new sequence number in want --> new ace
+ # case 3 --> existing sequence number given --> update rule (only for merged state.
+ # For replaced and overridden, rule is deleted in the state's config)
+
+ ace_list = [
+ item for item in w_acl["aces"] if "sequence" not in item.keys()
+ ] # case 1
+
+ want_seq = [
+ item["sequence"]
+ for item in w_acl["aces"]
+ if "sequence" in item.keys()
+ ]
+
+ have_seq = [item["sequence"] for item in have_acl["aces"]]
+
+ new_seq = list(set(want_seq) - set(have_seq))
+ common_seq = list(set(want_seq).intersection(set(have_seq)))
+
+ temp_list = [
+ item
+ for item in w_acl["aces"]
+ if "sequence" in item.keys() and item["sequence"] in new_seq
+ ] # case 2
+ ace_list.extend(temp_list)
+ for w in w_acl["aces"]:
+ self.argument_spec = AclsArgs.argument_spec
+ params = utils.validate_config(
+ self.argument_spec,
+ {
+ "config": [
+ {
+ "afi": want["afi"],
+ "acls": [
+ {
+ "name": name,
+ "aces": ace_list,
+ },
+ ],
+ },
+ ],
+ },
+ )
+ if "sequence" in w.keys() and w["sequence"] in common_seq:
+ temp_obj = search_obj_in_list(
+ w["sequence"],
+ have_acl["aces"],
+ "sequence",
+ ) # case 3
+ if temp_obj != w:
+ ace_list.append(w)
+ if self.state == "merged":
+ # merged will never negate commands
+ self._module.fail_json(
+ msg="Cannot update existing ACE {0} of ACL {1} with state merged."
+ " Please use state replaced or overridden.".format(
+ name,
+ w["sequence"],
+ ),
+ )
+ elif w_acl.get("aces"):
+ # 'have' has ACL defined without any ACE
+ ace_list = list(w_acl["aces"])
+ for w_ace in ace_list:
+ ace_commands.append(self.process_ace(w_ace).strip())
+ flag = 1
+
+ if flag:
+ ace_commands.insert(0, ip + "access-list " + name)
+
+ else:
+ commands.append(ip + "access-list " + name)
+ if "aces" in w_acl.keys():
+ for w_ace in w_acl["aces"]:
+ commands.append(self.process_ace(w_ace).strip())
+ commands.extend(ace_commands)
+ else:
+ if want.get("acls"):
+ for w_acl in want["acls"]:
+ name = w_acl["name"]
+ commands.append(ip + "access-list " + name)
+ if "aces" in w_acl.keys():
+ for w_ace in w_acl["aces"]:
+ commands.append(self.process_ace(w_ace).strip())
+
+ return commands
+
+ def process_ace(self, w_ace):
+ command = ""
+ ace_keys = w_ace.keys()
+ if "remark" in ace_keys:
+ command += "remark " + w_ace["remark"] + " "
+ else:
+ command += w_ace["grant"] + " "
+ if "protocol" in ace_keys:
+ if w_ace["protocol"] == "icmpv6":
+ command += "icmp" + " "
+ else:
+ command += w_ace["protocol"] + " "
+ src = self.get_address(w_ace["source"], w_ace["protocol"])
+ dest = self.get_address(w_ace["destination"], w_ace["protocol"])
+ command += src + dest
+ if "protocol_options" in ace_keys:
+ pro = list(w_ace["protocol_options"].keys())[0]
+ if pro != w_ace["protocol"]:
+ self._module.fail_json(msg="protocol and protocol_options mismatch")
+ flags = ""
+ for k in w_ace["protocol_options"][pro].keys():
+ if k not in ["telemetry_queue", "telemetry_path"]:
+ k = re.sub("_", "-", k)
+ flags += k + " "
+ command += flags
+ if "dscp" in ace_keys:
+ command += "dscp " + w_ace["dscp"] + " "
+ if "fragments" in ace_keys:
+ command += "fragments "
+ if "precedence" in ace_keys:
+ command += "precedence " + w_ace["precedence"] + " "
+ if "log" in ace_keys:
+ command += "log "
+ if "sequence" in ace_keys:
+ command = str(w_ace["sequence"]) + " " + command
+ return command
+
+ def get_address(self, endpoint, pro=""):
+ ret_addr = ""
+ keys = list(endpoint.keys())
+ if "address" in keys:
+ if "wildcard_bits" not in keys:
+ self._module.fail_json(msg="wildcard bits not specified for address")
+ else:
+ ret_addr = endpoint["address"] + " " + endpoint["wildcard_bits"] + " "
+ elif "any" in keys:
+ ret_addr = "any "
+ elif "host" in keys:
+ ret_addr = "host " + endpoint["host"] + " "
+ elif "prefix" in keys:
+ ret_addr = endpoint["prefix"] + " "
+
+ if pro in ["tcp", "udp"]:
+ if "port_protocol" in keys:
+ options = self.get_options(endpoint["port_protocol"])
+ ret_addr += options
+ return ret_addr
+
+ def get_options(self, item):
+ com = ""
+ subkey = list(item.keys())
+ if "range" in subkey:
+ com = "range " + item["range"]["start"] + " " + item["range"]["end"] + " "
+ else:
+ com = subkey[0] + " " + item[subkey[0]] + " "
+ return com
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/bfd_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/bfd_interfaces.py
new file mode 100644
index 00000000..a9dc51fd
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bfd_interfaces/bfd_interfaces.py
@@ -0,0 +1,311 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+nxos_bfd_interfaces class
+This class creates a command set to bring the current device configuration
+to a desired end-state. The command set is based on a comparison of the
+current configuration (as dict) and the provided configuration (as dict).
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+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 (
+ dict_diff,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ flatten_dict,
+ search_obj_in_list,
+)
+
+
+class Bfd_interfaces(ConfigBase):
+ """
+ The nxos_bfd_interfaces class
+ """
+
+ gather_subset = ["min"]
+ gather_network_resources = ["bfd_interfaces"]
+ # exclude_params = []
+
+ def __init__(self, module):
+ super(Bfd_interfaces, self).__init__(module)
+
+ def get_bfd_interfaces_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :returns: A list of interface configs and a platform string
+ """
+ if self.state not in self.ACTION_STATES:
+ self.gather_subset = ["!all", "!min"]
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ bfd_interfaces_facts = facts["ansible_network_resources"].get("bfd_interfaces", [])
+
+ platform = facts.get("ansible_net_platform", "")
+ return bfd_interfaces_facts, platform
+
+ def edit_config(self, commands):
+ return self._connection.edit_config(commands)
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ warnings = list()
+ commands = list()
+
+ if self.state in self.ACTION_STATES:
+ (
+ existing_bfd_interfaces_facts,
+ platform,
+ ) = self.get_bfd_interfaces_facts()
+ else:
+ existing_bfd_interfaces_facts, platform = [], ""
+
+ if self.state in self.ACTION_STATES or self.state == "rendered":
+ commands.extend(self.set_config(existing_bfd_interfaces_facts, platform))
+
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ self.edit_config(commands)
+ result["changed"] = True
+
+ if self.state in self.ACTION_STATES:
+ result["commands"] = commands
+
+ if self.state in self.ACTION_STATES or self.state == "gathered":
+ (
+ changed_bfd_interfaces_facts,
+ platform,
+ ) = self.get_bfd_interfaces_facts()
+
+ elif self.state == "rendered":
+ result["rendered"] = commands
+
+ elif self.state == "parsed":
+ running_config = self._module.params["running_config"]
+ if not running_config:
+ self._module.fail_json(
+ msg="value of running_config parameter must not be empty for state parsed",
+ )
+ result["parsed"], platform = self.get_bfd_interfaces_facts(data=running_config)
+
+ if self.state in self.ACTION_STATES:
+ result["before"] = existing_bfd_interfaces_facts
+ if result["changed"]:
+ result["after"] = changed_bfd_interfaces_facts
+
+ elif self.state == "gathered":
+ result["gathered"] = changed_bfd_interfaces_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_bfd_interfaces_facts, platform):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ if re.search("N[56]K", platform):
+ # Some platforms do not support the 'bfd' interface keyword;
+ # remove the 'bfd' key from each want/have interface.
+ orig_want = self._module.params["config"]
+ want = []
+ for w in orig_want:
+ del w["bfd"]
+ want.append(w)
+ orig_have = existing_bfd_interfaces_facts
+ have = []
+ for h in orig_have:
+ del h["bfd"]
+ have.append(h)
+ else:
+ want = self._module.params["config"]
+ have = existing_bfd_interfaces_facts
+
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+ if state in ("overridden", "merged", "replaced", "rendered") and not want:
+ self._module.fail_json(
+ msg="value of config parameter must not be empty for state {0}".format(state),
+ )
+
+ cmds = list()
+ if state == "overridden":
+ cmds.extend(self._state_overridden(want, have))
+ elif state == "deleted":
+ cmds.extend(self._state_deleted(want, have))
+ else:
+ for w in want:
+ if state in ["merged", "rendered"]:
+ cmds.extend(self._state_merged(flatten_dict(w), have))
+ elif state == "replaced":
+ cmds.extend(self._state_replaced(flatten_dict(w), have))
+ return cmds
+
+ def _state_replaced(self, want, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ cmds = []
+ obj_in_have = search_obj_in_list(want["name"], have, "name")
+ if obj_in_have:
+ diff = dict_diff(want, obj_in_have)
+ else:
+ diff = want
+ merged_cmds = self.set_commands(want, have)
+ if "name" not in diff:
+ diff["name"] = want["name"]
+
+ replaced_cmds = []
+ if obj_in_have:
+ replaced_cmds = self.del_attribs(diff)
+ if replaced_cmds or merged_cmds:
+ for cmd in set(replaced_cmds).intersection(set(merged_cmds)):
+ merged_cmds.remove(cmd)
+ cmds.extend(replaced_cmds)
+ cmds.extend(merged_cmds)
+ return cmds
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ cmds = []
+ for h in have:
+ # Clean up bfd attrs for any interfaces not listed in the play
+ h = flatten_dict(h)
+ obj_in_want = flatten_dict(search_obj_in_list(h["name"], want, "name"))
+ if obj_in_want:
+ # Let the 'want' loop handle all vals for this interface
+ continue
+ cmds.extend(self.del_attribs(h))
+ for w in want:
+ # Update any want attrs if needed. The overridden state considers
+ # the play as the source of truth for the entire device, therefore
+ # set any unspecified attrs to their default state.
+ w = self.set_none_vals_to_defaults(flatten_dict(w))
+ cmds.extend(self.set_commands(w, have))
+ return cmds
+
+ def _state_merged(self, want, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(want, have)
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ if not (want or have):
+ return []
+ cmds = []
+ if want:
+ for w in want:
+ obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name"))
+ cmds.extend(self.del_attribs(obj_in_have))
+ else:
+ for h in have:
+ cmds.extend(self.del_attribs(flatten_dict(h)))
+ return cmds
+
+ def del_attribs(self, obj):
+ if not obj or len(obj.keys()) == 1:
+ return []
+ cmds = []
+ # 'bfd' and 'bfd echo' are enabled by default so the handling is
+ # counter-intuitive; we are enabling them to remove them. The end result
+ # is that they are removed from the interface config on the device.
+ if "bfd" in obj and "disable" in obj["bfd"]:
+ cmds.append("bfd")
+ if "echo" in obj and "disable" in obj["echo"]:
+ cmds.append("bfd echo")
+ if cmds:
+ cmds.insert(0, "interface " + obj["name"])
+ return cmds
+
+ def set_none_vals_to_defaults(self, want):
+ # Set dict None values to default states
+ if "bfd" in want and want["bfd"] is None:
+ want["bfd"] = "enable"
+ if "echo" in want and want["echo"] is None:
+ want["echo"] = "enable"
+ return want
+
+ def diff_of_dicts(self, want, obj_in_have):
+ diff = set(want.items()) - set(obj_in_have.items())
+ diff = dict(diff)
+ if diff and want["name"] == obj_in_have["name"]:
+ diff.update({"name": want["name"]})
+ return diff
+
+ def add_commands(self, want):
+ if not want:
+ return []
+ cmds = []
+ if "bfd" in want and want["bfd"] is not None:
+ cmd = "bfd" if want["bfd"] == "enable" else "no bfd"
+ cmds.append(cmd)
+ if "echo" in want and want["echo"] is not None:
+ cmd = "bfd echo" if want["echo"] == "enable" else "no bfd echo"
+ cmds.append(cmd)
+
+ if cmds:
+ cmds.insert(0, "interface " + want["name"])
+ return cmds
+
+ def set_commands(self, want, have):
+ cmds = []
+ obj_in_have = flatten_dict(search_obj_in_list(want["name"], have, "name"))
+ if not obj_in_have:
+ cmds = self.add_commands(want)
+ else:
+ diff = self.diff_of_dicts(want, obj_in_have)
+ cmds = self.add_commands(diff)
+ return cmds
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/bgp_address_family.py
new file mode 100644
index 00000000..4443039c
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_address_family/bgp_address_family.py
@@ -0,0 +1,253 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos_bgp_address_family config file.
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to its desired end-state is
+created.
+"""
+
+from copy import deepcopy
+
+from ansible.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,
+ remove_empties,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_address_family import (
+ Bgp_address_familyTemplate,
+)
+
+
+class Bgp_address_family(ResourceModule):
+ """
+ The nxos_bgp_address_family config class
+ """
+
+ def __init__(self, module):
+ super(Bgp_address_family, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="bgp_address_family",
+ tmplt=Bgp_address_familyTemplate(),
+ )
+ self.parsers = [
+ "additional_paths.install_backup",
+ "additional_paths.receive",
+ "additional_paths.selection.route_map",
+ "additional_paths.send",
+ "advertise_l2vpn_evpn",
+ "advertise_pip",
+ "advertise_system_mac",
+ "allow_vni_in_ethertag",
+ "client_to_client.no_reflection",
+ "dampen_igp_metric",
+ "dampening",
+ "default_information.originate",
+ "default_metric",
+ "distance",
+ "export_gateway_ip",
+ "maximum_paths.parallel_paths",
+ "maximum_paths.ibgp.parallel_paths",
+ "maximum_paths.eibgp.parallel_paths",
+ "maximum_paths.local.parallel_paths",
+ "maximum_paths.mixed.parallel_paths",
+ "nexthop.route_map",
+ "nexthop.trigger_delay",
+ "retain.route_target.retain_all",
+ "retain.route_target.route_map",
+ "suppress_inactive",
+ "table_map",
+ "timers.bestpath_defer",
+ "wait_igp_convergence",
+ ]
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ if self.state not in ["parsed", "gathered"]:
+ self.generate_commands()
+ self.run_commands()
+ return self.result
+
+ def generate_commands(self):
+ """Generate configuration commands to send based on
+ want, have and desired state.
+ """
+ wantd = deepcopy(self.want)
+ haved = deepcopy(self.have)
+
+ self._bgp_af_list_to_dict(wantd)
+ self._bgp_af_list_to_dict(haved)
+
+ # if state is merged, merge want onto have and then compare
+ if self.state == "merged":
+ wantd = dict_merge(haved, wantd)
+
+ wantd = remove_empties(wantd)
+ haved = remove_empties(haved)
+
+ have_af = haved.get("address_family", {})
+ want_af = wantd.get("address_family", {})
+ wvrfs = wantd.get("vrfs", {})
+ hvrfs = haved.get("vrfs", {})
+
+ # if state is overridden or deleted, remove superfluos config
+ if self.state in ["deleted", "overridden"]:
+ if (haved and haved["as_number"] == wantd.get("as_number")) or not wantd:
+ remove = True if self.state == "deleted" else False
+ purge = True if not wantd else False
+ self._remove_af(want_af, have_af, remove=remove, purge=purge)
+
+ for k, hvrf in iteritems(hvrfs):
+ wvrf = wvrfs.get(k, {})
+ self._remove_af(wvrf, hvrf, vrf=k, remove=remove, purge=purge)
+
+ if self.state in ["merged", "replaced", "overridden", "rendered"]:
+ for k, want in iteritems(want_af):
+ self._compare(want=want, have=have_af.pop(k, {}))
+
+ # handle vrf->af
+ for wk, wvrf in iteritems(wvrfs):
+ cur_ptr = len(self.commands)
+
+ hvrf = hvrfs.pop(wk, {})
+ for k, want in iteritems(wvrf):
+ self._compare(want=want, have=hvrf.pop(k, {}))
+
+ # add VRF command at correct position once
+ if cur_ptr != len(self.commands):
+ self.commands.insert(cur_ptr, "vrf {0}".format(wk))
+
+ if self.commands:
+ self.commands.insert(0, "router bgp {as_number}".format(**haved or wantd))
+
+ 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 Bgp_address_family network resource.
+ """
+ begin = len(self.commands)
+
+ self.compare(parsers=self.parsers, want=want, have=have)
+ self._compare_lists(want=want, have=have)
+
+ if len(self.commands) != begin or (not have and want):
+ self.commands.insert(
+ begin,
+ self._tmplt.render(want or have, "address_family", False),
+ )
+
+ def _compare_lists(self, want, have):
+ for attrib in [
+ "aggregate_address",
+ "inject_map",
+ "networks",
+ "redistribute",
+ ]:
+ wdict = want.get(attrib, {})
+ hdict = have.get(attrib, {})
+ for key, entry in iteritems(wdict):
+ if entry != hdict.pop(key, {}):
+ self.addcmd(entry, attrib.format(attrib), False)
+
+ # remove remaining items in have for replaced
+ for entry in hdict.values():
+ self.addcmd(entry, attrib.format(attrib), True)
+
+ def _bgp_af_list_to_dict(self, entry):
+ def _build_key(data):
+ """Build primary key for each dict
+
+ :params x: dictionary
+ :returns: primary key as tuple
+ """
+ # afi should always be present
+ # safi and vrf are optional
+ # a combination of these 3 uniquely
+ # identifies an AF context
+ afi = "afi_" + data["afi"]
+ safi = "safi_" + data.get("safi", "")
+ vrf = "vrf_" + data.get("vrf", "")
+
+ return (afi, safi, vrf)
+
+ # transform parameters which are
+ # list of dicts to dict of dicts
+ for item in entry.get("address_family", []):
+ item["aggregate_address"] = {x["prefix"]: x for x in item.get("aggregate_address", [])}
+ item["inject_map"] = {
+ (x["route_map"], x["exist_map"]): x for x in item.get("inject_map", [])
+ }
+ item["networks"] = {x["prefix"]: x for x in item.get("networks", [])}
+ item["redistribute"] = {
+ (x.get("id"), x["protocol"]): x for x in item.get("redistribute", [])
+ }
+
+ # transform all entries under
+ # config->address_family to dict of dicts
+ af = {_build_key(x): x for x in entry.get("address_family", [])}
+
+ temp = {}
+ entry["vrfs"] = {}
+ entry["address_family"] = {}
+
+ # group AFs by VRFs
+ # vrf_ denotes global AFs
+ for k in af.keys():
+ for x in k:
+ if x.startswith("vrf_"):
+ if x not in temp:
+ temp[x] = {}
+ temp[x][k] = af[k]
+
+ for k in temp.keys():
+ if k == "vrf_":
+ # populate global AFs
+ entry["address_family"][k] = temp[k]
+ else:
+ # populate VRF AFs
+ entry["vrfs"][k.replace("vrf_", "", 1)] = temp[k]
+
+ entry["address_family"] = entry["address_family"].get("vrf_", {})
+
+ # final structure: https://gist.github.com/NilashishC/628dae5fe39a4908e87c9e833bfbe57d
+
+ def _remove_af(self, want_af, have_af, vrf=None, remove=False, purge=False):
+ cur_ptr = len(self.commands)
+ for k, v in iteritems(have_af):
+ # first conditional is for deleted with config provided
+ # second conditional is for overridden
+ # third condition is for deleted with empty config
+ if any(
+ (
+ (remove and k in want_af),
+ (not remove and k not in want_af),
+ purge,
+ ),
+ ):
+ self.addcmd(v, "address_family", True)
+ if cur_ptr < len(self.commands) and vrf:
+ self.commands.insert(cur_ptr, "vrf {0}".format(vrf))
+ self.commands.append("exit")
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/bgp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/bgp_global.py
new file mode 100644
index 00000000..edb6e59b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_global/bgp_global.py
@@ -0,0 +1,410 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos_bgp_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.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.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_global import (
+ Bgp_globalTemplate,
+)
+
+
+class Bgp_global(ResourceModule):
+ """
+ The nxos_bgp_global config class
+ """
+
+ def __init__(self, module):
+ super(Bgp_global, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="bgp_global",
+ tmplt=Bgp_globalTemplate(),
+ )
+ # VRF parsers = 29
+ self.parsers = [
+ "allocate_index",
+ "affinity_group.group_id",
+ "bestpath.always_compare_med",
+ "bestpath.as_path.ignore",
+ "bestpath.as_path.multipath_relax",
+ "bestpath.compare_neighborid",
+ "bestpath.compare_routerid",
+ "bestpath.cost_community_ignore",
+ "bestpath.igp_metric_ignore",
+ "bestpath.med.confed",
+ "bestpath.med.missing_as_worst",
+ "bestpath.med.non_deterministic",
+ "cluster_id",
+ "local_as",
+ "confederation.identifier",
+ "graceful_restart",
+ "graceful_restart.restart_time",
+ "graceful_restart.stalepath_time",
+ "graceful_restart.helper",
+ "log_neighbor_changes",
+ "maxas_limit",
+ "neighbor_down.fib_accelerate",
+ "reconnect_interval",
+ "router_id",
+ "timers.bestpath_limit",
+ "timers.bgp",
+ "timers.prefix_peer_timeout",
+ "timers.prefix_peer_wait",
+ # end VRF parsers
+ "disable_policy_batching",
+ "disable_policy_batching.ipv4.prefix_list",
+ "disable_policy_batching.ipv6.prefix_list",
+ "disable_policy_batching.nexthop",
+ "dynamic_med_interval",
+ "enforce_first_as",
+ "enhanced_error",
+ "fast_external_fallover",
+ "flush_routes",
+ "graceful_shutdown.activate",
+ "graceful_shutdown.aware",
+ "isolate",
+ "nexthop.suppress_default_resolution",
+ "shutdown",
+ "suppress_fib_pending",
+ "fabric_soo",
+ "rd",
+ ]
+ self._af_data = {}
+
+ 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.
+ """
+ # we fail early if state is merged or
+ # replaced and want ASN != have ASN
+ if self.state in ["merged", "replaced"]:
+ w_asn = self.want.get("as_number")
+ h_asn = self.have.get("as_number")
+
+ if h_asn and w_asn != h_asn:
+ self._module.fail_json(
+ msg="BGP is already configured with ASN {0}. "
+ "Please remove it with state purged before "
+ "configuring new ASN".format(h_asn),
+ )
+
+ if self.state in ["deleted", "replaced"]:
+ self._build_af_data()
+
+ for entry in self.want, self.have:
+ self._bgp_list_to_dict(entry)
+
+ # if state is deleted, clean up global params
+ if self.state == "deleted":
+ if not self.want or (self.have.get("as_number") == self.want.get("as_number")):
+ self._compare(want={}, have=self.have)
+
+ elif self.state == "purged":
+ if not self.want or (self.have.get("as_number") == self.want.get("as_number")):
+ self.addcmd(self.have or {}, "as_number", True)
+
+ else:
+ wantd = self.want
+ # if state is merged, merge want onto have and then compare
+ if self.state == "merged":
+ wantd = dict_merge(self.have, self.want)
+
+ self._compare(want=wantd, have=self.have)
+
+ def _compare(self, want, have, vrf=None):
+ """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 Bgp_global network resource.
+ """
+ begin = len(self.commands)
+ self.compare(parsers=self.parsers, want=want, have=have)
+ self._compare_confederation_peers(want, have)
+ self._compare_neighbors(want, have, vrf=vrf)
+ self._vrfs_compare(want=want, have=have)
+
+ if len(self.commands) != begin or (not have and want):
+ self.commands.insert(
+ begin,
+ self._tmplt.render(
+ want or have,
+ "vrf" if "vrf" in (want.keys() or have.keys()) else "as_number",
+ False,
+ ),
+ )
+
+ def _compare_confederation_peers(self, want, have):
+ """Custom handling of confederation.peers option
+
+ :params want: the want BGP dictionary
+ :params have: the have BGP dictionary
+ """
+ w_cpeers = want.get("confederation", {}).get("peers", [])
+ h_cpeers = have.get("confederation", {}).get("peers", [])
+
+ if set(w_cpeers) != set(h_cpeers):
+ if self.state in ["replaced", "deleted"]:
+ # if there are peers already configured
+ # we need to remove those before we pass
+ # the new ones otherwise the device appends
+ # them to the existing ones
+ if h_cpeers:
+ self.addcmd(have, "confederation.peers", True)
+ if w_cpeers:
+ self.addcmd(want, "confederation.peers", False)
+
+ def _compare_neighbors(self, want, have, vrf=None):
+ """Custom handling of neighbors option
+
+ :params want: the want BGP dictionary
+ :params have: the have BGP dictionary
+ """
+ nbr_parsers = [
+ "remote_as",
+ "bfd",
+ "bfd.multihop.interval",
+ "neighbor_affinity_group.group_id",
+ "bmp_activate_server",
+ "capability",
+ "description",
+ "disable_connected_check",
+ "dont_capability_negotiate",
+ "dscp",
+ "dynamic_capability",
+ "ebgp_multihop",
+ "graceful_shutdown",
+ "inherit.peer",
+ "inherit.peer_session",
+ "local_as",
+ "log_neighbor_changes",
+ "low_memory",
+ "password",
+ "peer_type",
+ "remove_private_as",
+ "shutdown",
+ "timers",
+ "transport",
+ "ttl_security",
+ "update_source",
+ ]
+ wnbrs = want.get("neighbors", {})
+ hnbrs = have.get("neighbors", {})
+
+ # neighbors have separate contexts in NX-OS
+ for name, entry in iteritems(wnbrs):
+ begin = len(self.commands)
+ have_nbr = hnbrs.pop(name, {})
+
+ self.compare(parsers=nbr_parsers, want=entry, have=have_nbr)
+ self._compare_path_attribute(entry, have_nbr)
+
+ if len(self.commands) != begin:
+ self.commands.insert(begin, self._tmplt.render(entry, "neighbor_address", False))
+
+ # cleanup remaining neighbors
+ # but do not negate it entirely
+ # instead remove only those attributes
+ # that this module manages
+ for name, entry in iteritems(hnbrs):
+ if self._has_af(vrf=vrf, neighbor=name):
+ self._module.fail_json(
+ msg="Neighbor {0} has address-family configurations. "
+ "Please use the nxos_bgp_neighbor_af module to remove those first.".format(
+ name,
+ ),
+ )
+ else:
+ self.addcmd(entry, "neighbor_address", True)
+
+ def _compare_path_attribute(self, want, have):
+ """Custom handling of neighbor path_attribute
+ option.
+
+ :params want: the want neighbor dictionary
+ :params have: the have neighbor dictionary
+ """
+ w_p_attr = want.get("path_attribute", {})
+ h_p_attr = have.get("path_attribute", {})
+
+ for wkey, wentry in iteritems(w_p_attr):
+ if wentry != h_p_attr.pop(wkey, {}):
+ self.addcmd(wentry, "path_attribute", False)
+
+ # remove remaining items in have for replaced
+ for hkey, hentry in iteritems(h_p_attr):
+ self.addcmd(hentry, "path_attribute", True)
+
+ def _vrfs_compare(self, want, have):
+ """Custom handling of VRFs option
+
+ :params want: the want BGP dictionary
+ :params have: the have BGP dictionary
+ """
+ wvrfs = want.get("vrfs", {})
+ hvrfs = have.get("vrfs", {})
+ for name, entry in iteritems(wvrfs):
+ self._compare(want=entry, have=hvrfs.pop(name, {}), vrf=name)
+ # cleanup remaining VRFs
+ # but do not negate it entirely
+ # instead remove only those attributes
+ # that this module manages
+ for name, entry in iteritems(hvrfs):
+ if self._has_af(vrf=name):
+ self._module.fail_json(
+ msg="VRF {0} has address-family configurations. "
+ "Please use the nxos_bgp_af module to remove those first.".format(name),
+ )
+ else:
+ self.addcmd(entry, "vrf", True)
+
+ def _bgp_list_to_dict(self, entry):
+ """Convert list of items to dict of items
+ for efficient diff calculation.
+
+ :params entry: data dictionary
+ """
+
+ def _build_key(x):
+ """Build primary key for path_attribute
+ option.
+ :params x: path_attribute dictionary
+ :returns: primary key as tuple
+ """
+ key_1 = "start_{0}".format(x.get("range", {}).get("start", ""))
+ key_2 = "end_{0}".format(x.get("range", {}).get("end", ""))
+ key_3 = "type_{0}".format(x.get("type", ""))
+ key_4 = x["action"]
+
+ return (key_1, key_2, key_3, key_4)
+
+ if "neighbors" in entry:
+ for x in entry["neighbors"]:
+ if "path_attribute" in x:
+ x["path_attribute"] = {
+ _build_key(item): item for item in x.get("path_attribute", [])
+ }
+
+ entry["neighbors"] = {x["neighbor_address"]: x for x in entry.get("neighbors", [])}
+
+ if "vrfs" in entry:
+ entry["vrfs"] = {x["vrf"]: x for x in entry.get("vrfs", [])}
+ for _k, vrf in iteritems(entry["vrfs"]):
+ self._bgp_list_to_dict(vrf)
+
+ def _get_config(self):
+ return self._connection.get("show running-config | section '^router bgp'")
+
+ def _build_af_data(self):
+ """Build a dictionary with AF related information
+ from fetched BGP config.
+ _af_data = {
+ gbl_data = {'192.168.1.100', '192.168.1.101'},
+ vrf_data = {
+ 'vrf_1': {
+ 'has_af': True,
+ 'nbrs': {'192.0.1.1', '192.8.1.1'}
+ },
+ 'vrf_2': {
+ 'has_af': False,
+ 'nbrs': set()
+ }
+ }
+ }
+ """
+ data = self._get_config().split("\n")
+ cur_nbr = None
+ cur_vrf = None
+ gbl_data = set()
+ vrf_data = {}
+
+ for x in data:
+ if x.strip().startswith("vrf"):
+ cur_nbr = None
+ cur_vrf = x.split(" ")[-1]
+ vrf_data[cur_vrf] = {"nbrs": set(), "has_af": False}
+
+ elif x.strip().startswith("neighbor"):
+ cur_nbr = x.split(" ")[-1]
+
+ elif x.strip().startswith("address-family"):
+ if cur_nbr:
+ if cur_vrf:
+ vrf_data[cur_vrf]["nbrs"].add(cur_nbr)
+ else:
+ gbl_data.add(cur_nbr)
+ else:
+ if cur_vrf:
+ vrf_data[cur_vrf]["has_af"] = True
+
+ self._af_data["global"] = gbl_data
+ self._af_data["vrf"] = vrf_data
+
+ def _has_af(self, vrf=None, neighbor=None):
+ """Determine if the given vrf + neighbor
+ combination has AF configurations.
+
+ :params vrf: vrf name
+ :params neighbor: neighbor name
+ :returns: bool
+ """
+ has_af = False
+
+ if self._af_data:
+ vrf_af_data = self._af_data.get("vrf", {})
+ global_af_data = self._af_data.get("global", set())
+ if vrf:
+ vrf_nbr_has_af = vrf_af_data.get(vrf, {}).get("nbrs", set())
+ vrf_has_af = vrf_af_data.get(vrf, {}).get("has_af", False)
+ if neighbor and neighbor in vrf_nbr_has_af:
+ # we are inspecting neighbor within a VRF
+ # if the given neighbor has AF we return True
+ has_af = True
+ else:
+ # we are inspecting VRF as a whole
+ # if there is at least one neighbor
+ # with AF or VRF has AF itself return True
+ if vrf_nbr_has_af or vrf_has_af:
+ has_af = True
+ else:
+ # we are inspecting top level neighbors
+ # if the given neighbor has AF we return True
+ if neighbor and neighbor in global_af_data:
+ has_af = True
+
+ return has_af
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/bgp_neighbor_address_family.py
new file mode 100644
index 00000000..96902987
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/bgp_neighbor_address_family/bgp_neighbor_address_family.py
@@ -0,0 +1,232 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos_bgp_neighbor_address_family config file.
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to its desired end-state is
+created.
+"""
+from copy import deepcopy
+
+from ansible.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.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_neighbor_address_family import (
+ Bgp_neighbor_address_familyTemplate,
+)
+
+
+class Bgp_neighbor_address_family(ResourceModule):
+ """
+ The nxos_bgp_neighbor_address_family config class
+ """
+
+ def __init__(self, module):
+ super(Bgp_neighbor_address_family, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="bgp_neighbor_address_family",
+ tmplt=Bgp_neighbor_address_familyTemplate(),
+ )
+ self.parsers = [
+ "advertise_map.exist_map",
+ "advertise_map.non_exist_map",
+ "advertisement_interval",
+ "allowas_in",
+ "as_override",
+ "capability.additional_paths.receive",
+ "capability.additional_paths.send",
+ "default_originate",
+ "disable_peer_as_check",
+ "filter_list.inbound",
+ "filter_list.outbound",
+ "inherit",
+ "maximum_prefix",
+ "next_hop_self",
+ "next_hop_third_party",
+ "prefix_list.inbound",
+ "prefix_list.outbound",
+ "rewrite_evpn_rt_asn",
+ "route_map.inbound",
+ "route_map.outbound",
+ "route_reflector_client",
+ "send_community.extended",
+ "send_community.standard",
+ "soft_reconfiguration_inbound",
+ "soo",
+ "suppress_inactive",
+ "unsuppress_map",
+ "weight",
+ ]
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ if self.state not in ["parsed", "gathered"]:
+ self.generate_commands()
+ self.run_commands()
+ return self.result
+
+ def generate_commands(self):
+ """Generate configuration commands to send based on
+ want, have and desired state.
+ """
+ wantd = deepcopy(self.want)
+ haved = deepcopy(self.have)
+
+ for entry in wantd, haved:
+ self._bgp_list_to_dict(entry)
+
+ # 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 elements to delete
+ if self.state == "deleted":
+ if wantd:
+ to_del = {
+ "neighbors": self._set_to_delete(haved, wantd),
+ "vrfs": {},
+ }
+
+ for k, hvrf in iteritems(haved.get("vrfs", {})):
+ wvrf = wantd.get("vrfs", {}).get(k, {})
+ to_del["vrfs"][k] = {
+ "neighbors": self._set_to_delete(hvrf, wvrf),
+ "vrf": k,
+ }
+ haved.update(to_del)
+
+ wantd = {}
+
+ self._compare(want=wantd, have=haved)
+
+ if self.commands:
+ self.commands.insert(0, "router bgp {as_number}".format(**haved or wantd))
+
+ def _compare(self, want, have, vrf=""):
+ """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 Bgp_neighbor_address_family network resource.
+ """
+ w_nbrs = want.get("neighbors", {})
+ h_nbrs = have.get("neighbors", {})
+
+ if vrf:
+ begin_vrf = len(self.commands)
+
+ for k, w_nbr in iteritems(w_nbrs):
+ begin = len(self.commands)
+ h_nbr = h_nbrs.pop(k, {})
+ want_afs = w_nbr.get("address_family", {})
+ have_afs = h_nbr.get("address_family", {})
+
+ for k, want_af in iteritems(want_afs):
+ begin_af = len(self.commands)
+ have_af = have_afs.pop(k, {})
+
+ # swap `both` and `set` for idempotence
+ if "send_community" in want_af:
+ if want_af["send_community"].get("both"):
+ want_af["send_community"] = {
+ "extended": True,
+ "standard": True,
+ }
+ elif want_af["send_community"].get("set"):
+ want_af["send_community"].update({"standard": True})
+
+ self.compare(parsers=self.parsers, want=want_af, have=have_af)
+
+ if len(self.commands) != begin_af or (not have_af and want_af):
+ self.commands.insert(
+ begin_af,
+ self._tmplt.render(want_af, "address_family", False),
+ )
+
+ # remove remaining items in have for replaced
+ for k, have_af in iteritems(have_afs):
+ self.addcmd(have_af, "address_family", True)
+
+ if len(self.commands) != begin:
+ self.commands.insert(begin, "neighbor {0}".format(w_nbr["neighbor_address"]))
+
+ if self.state in ["overridden", "deleted"]:
+ for k, h_nbr in iteritems(h_nbrs):
+ begin = len(self.commands)
+ if not w_nbrs.pop(k, {}):
+ have_afs = h_nbr.get("address_family", {})
+ for k, have_af in iteritems(have_afs):
+ self.addcmd(have_af, "address_family", True)
+ if len(self.commands) != begin:
+ self.commands.insert(begin, "neighbor {0}".format(h_nbr["neighbor_address"]))
+
+ if vrf:
+ if len(self.commands) != begin_vrf:
+ self.commands.insert(begin_vrf, "vrf {0}".format(vrf))
+ else:
+ self._vrfs_compare(want, have)
+
+ def _vrfs_compare(self, want, have):
+ wvrfs = want.get("vrfs", {})
+ hvrfs = have.get("vrfs", {})
+ for k, wvrf in iteritems(wvrfs):
+ h_vrf = hvrfs.pop(k, {})
+ self._compare(want=wvrf, have=h_vrf, vrf=k)
+ # remove remaining items in have
+ for k, h_vrf in iteritems(hvrfs):
+ self._compare(want={}, have=h_vrf, vrf=k)
+
+ def _bgp_list_to_dict(self, data):
+ if "neighbors" in data:
+ for nbr in data["neighbors"]:
+ if "address_family" in nbr:
+ nbr["address_family"] = {
+ (x["afi"], x.get("safi")): x for x in nbr["address_family"]
+ }
+ data["neighbors"] = {x["neighbor_address"]: x for x in data["neighbors"]}
+
+ if "vrfs" in data:
+ for vrf in data["vrfs"]:
+ self._bgp_list_to_dict(vrf)
+ data["vrfs"] = {x["vrf"]: x for x in data["vrfs"]}
+
+ def _set_to_delete(self, haved, wantd):
+ neighbors = {}
+ h_nbrs = haved.get("neighbors", {})
+ w_nbrs = wantd.get("neighbors", {})
+
+ for k, h_nbr in iteritems(h_nbrs):
+ w_nbr = w_nbrs.pop(k, {})
+ if w_nbr:
+ neighbors[k] = h_nbr
+ afs_to_del = {}
+ h_addrs = h_nbr.get("address_family", {})
+ w_addrs = w_nbr.get("address_family", {})
+ for af, h_addr in iteritems(h_addrs):
+ if af in w_addrs:
+ afs_to_del[af] = h_addr
+ neighbors[k]["address_family"] = afs_to_del
+
+ return neighbors
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/hostname.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/hostname.py
new file mode 100644
index 00000000..42ed0694
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hostname/hostname.py
@@ -0,0 +1,75 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2022 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 nxos_hostname 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.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.hostname import (
+ HostnameTemplate,
+)
+
+
+class Hostname(ResourceModule):
+ """
+ The nxos_hostname config class
+ """
+
+ def __init__(self, module):
+ super(Hostname, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="hostname",
+ tmplt=HostnameTemplate(),
+ )
+ self.parsers = ["hostname"]
+
+ 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.
+ """
+ want = self.want
+ have = self.have
+
+ if self.state == "deleted":
+ want = {}
+
+ self._compare(want, have)
+
+ 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 Bgp_global network resource.
+ """
+ self.compare(parsers=self.parsers, want=want, have=have)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/hsrp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/hsrp_interfaces.py
new file mode 100644
index 00000000..757505e7
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/hsrp_interfaces/hsrp_interfaces.py
@@ -0,0 +1,286 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos hsrp_interfaces class
+This class creates a command set to bring the current device configuration
+to a desired end-state. The command set is based on a comparison of the
+current configuration (as dict) and the provided configuration (as dict).
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__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 (
+ dict_diff,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ flatten_dict,
+ normalize_interface,
+ search_obj_in_list,
+)
+
+
+class Hsrp_interfaces(ConfigBase):
+ """
+ The nxos_hsrp_interfaces class
+ """
+
+ gather_subset = ["!all", "!min"]
+
+ gather_network_resources = ["hsrp_interfaces"]
+
+ def __init__(self, module):
+ super(Hsrp_interfaces, self).__init__(module)
+
+ def get_hsrp_interfaces_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ hsrp_interfaces_facts = facts["ansible_network_resources"].get("hsrp_interfaces", [])
+ return hsrp_interfaces_facts
+
+ def edit_config(self, commands):
+ return self._connection.edit_config(commands)
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ warnings = []
+ commands = []
+
+ if self.state in self.ACTION_STATES:
+ existing_hsrp_interfaces_facts = self.get_hsrp_interfaces_facts()
+ else:
+ existing_hsrp_interfaces_facts = []
+
+ if self.state in self.ACTION_STATES or self.state == "rendered":
+ commands.extend(self.set_config(existing_hsrp_interfaces_facts))
+
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ self.edit_config(commands)
+ result["changed"] = True
+
+ if self.state in self.ACTION_STATES:
+ result["commands"] = commands
+
+ if self.state in self.ACTION_STATES or self.state == "gathered":
+ changed_hsrp_interfaces_facts = self.get_hsrp_interfaces_facts()
+
+ elif self.state == "rendered":
+ result["rendered"] = commands
+
+ elif self.state == "parsed":
+ running_config = self._module.params["running_config"]
+ if not running_config:
+ self._module.fail_json(
+ msg="value of running_config parameter must not be empty for state parsed",
+ )
+ result["parsed"] = self.get_hsrp_interfaces_facts(data=running_config)
+
+ if self.state in self.ACTION_STATES:
+ result["before"] = existing_hsrp_interfaces_facts
+ if result["changed"]:
+ result["after"] = changed_hsrp_interfaces_facts
+
+ elif self.state == "gathered":
+ result["gathered"] = changed_hsrp_interfaces_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_hsrp_interfaces_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ config = self._module.params["config"]
+ want = []
+ if config:
+ for w in config:
+ w.update({"name": normalize_interface(w["name"])})
+ want.append(w)
+ have = existing_hsrp_interfaces_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+ # check for 'config' keyword in play
+ if state in ("overridden", "merged", "replaced", "rendered") and not want:
+ self._module.fail_json(
+ msg="value of config parameter must not be empty for state {0}".format(state),
+ )
+
+ cmds = list()
+ if state == "overridden":
+ cmds.extend(self._state_overridden(want, have))
+ elif state == "deleted":
+ cmds.extend(self._state_deleted(want, have))
+ else:
+ for w in want:
+ if state in ["merged", "rendered"]:
+ cmds.extend(self._state_merged(flatten_dict(w), have))
+ elif state == "replaced":
+ cmds.extend(self._state_replaced(flatten_dict(w), have))
+ return cmds
+
+ def _state_replaced(self, want, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ cmds = []
+ obj_in_have = search_obj_in_list(want["name"], have, "name")
+ if obj_in_have:
+ diff = dict_diff(want, obj_in_have)
+ else:
+ diff = want
+ merged_cmds = self.set_commands(want, have)
+ if "name" not in diff:
+ diff["name"] = want["name"]
+
+ replaced_cmds = []
+ if obj_in_have:
+ replaced_cmds = self.del_attribs(diff)
+ if replaced_cmds or merged_cmds:
+ for cmd in set(replaced_cmds).intersection(set(merged_cmds)):
+ merged_cmds.remove(cmd)
+ cmds.extend(replaced_cmds)
+ cmds.extend(merged_cmds)
+ return cmds
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ cmds = []
+ for h in have:
+ # Check existing states, set to default if not in want or different than want
+ h = flatten_dict(h)
+ obj_in_want = search_obj_in_list(h["name"], want, "name")
+ if obj_in_want:
+ # Let the 'want' loop handle all vals for this interface
+ continue
+ cmds.extend(self.del_attribs(h))
+ for w in want:
+ # Update any want attrs if needed. The overridden state considers
+ # the play as the source of truth for the entire device, therefore
+ # set any unspecified attrs to their default state.
+ w = self.set_none_vals_to_defaults(flatten_dict(w))
+ cmds.extend(self.set_commands(w, have))
+ return cmds
+
+ def _state_merged(self, want, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(want, have)
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ if not (want or have):
+ return []
+ cmds = []
+ if want:
+ for w in want:
+ obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name"))
+ cmds.extend(self.del_attribs(obj_in_have))
+ else:
+ for h in have:
+ cmds.extend(self.del_attribs(flatten_dict(h)))
+ return cmds
+
+ def del_attribs(self, obj):
+ if not obj or len(obj.keys()) == 1:
+ return []
+ cmds = []
+ if "bfd" in obj:
+ cmds.append("no hsrp bfd")
+ if cmds:
+ cmds.insert(0, "interface " + obj["name"])
+ return cmds
+
+ def set_none_vals_to_defaults(self, want):
+ # Set dict None values to default states
+ if "bfd" in want and want["bfd"] is None:
+ want["bfd"] = "disable"
+ return want
+
+ def diff_of_dicts(self, want, obj_in_have):
+ diff = set(want.items()) - set(obj_in_have.items())
+ diff = dict(diff)
+ if diff and want["name"] == obj_in_have["name"]:
+ diff.update({"name": want["name"]})
+ return diff
+
+ def add_commands(self, want, obj_in_have):
+ if not want:
+ return []
+ cmds = []
+ if "bfd" in want and want["bfd"] is not None:
+ if want["bfd"] == "enable":
+ cmd = "hsrp bfd"
+ cmds.append(cmd)
+ elif want["bfd"] == "disable" and obj_in_have and obj_in_have.get("bfd") == "enable":
+ cmd = "no hsrp bfd"
+ cmds.append(cmd)
+
+ if cmds:
+ cmds.insert(0, "interface " + want["name"])
+ return cmds
+
+ def set_commands(self, want, have):
+ cmds = []
+ obj_in_have = search_obj_in_list(want["name"], have, "name")
+ if not obj_in_have:
+ cmds = self.add_commands(want, obj_in_have)
+ else:
+ diff = self.diff_of_dicts(want, obj_in_have)
+ cmds = self.add_commands(diff, obj_in_have)
+ return cmds
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/interfaces.py
new file mode 100644
index 00000000..32c5f6fe
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/interfaces/interfaces.py
@@ -0,0 +1,492 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_interfaces class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ dict_diff,
+ remove_empties,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ default_intf_enabled,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ normalize_interface,
+ search_obj_in_list,
+)
+
+
+class Interfaces(ConfigBase):
+ """
+ The nxos_interfaces class
+ """
+
+ gather_subset = ["min"]
+
+ gather_network_resources = ["interfaces"]
+
+ exclude_params = ["description", "mtu", "speed", "duplex"]
+
+ def __init__(self, module):
+ super(Interfaces, self).__init__(module)
+
+ def get_interfaces_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :data: Mocked running-config data for state `parsed`
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ self.facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ interfaces_facts = self.facts["ansible_network_resources"].get("interfaces")
+
+ return interfaces_facts
+
+ def get_platform(self):
+ """Wrapper method for getting platform info
+ This method exists solely to allow the unit test framework to mock calls.
+ """
+ return self.facts.get("ansible_net_platform", "")
+
+ def get_system_defaults(self):
+ """Wrapper method for `_connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return self._connection.get("show running-config all | incl 'system default switchport'")
+
+ def edit_config(self, commands):
+ """Wrapper method for `_connection.edit_config()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return self._connection.edit_config(commands)
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ commands = []
+ warnings = []
+
+ if self.state in self.ACTION_STATES:
+ existing_interfaces_facts = self.get_interfaces_facts()
+ else:
+ existing_interfaces_facts = []
+
+ if self.state in self.ACTION_STATES:
+ self.intf_defs = self.render_interface_defaults(
+ self.get_system_defaults(),
+ existing_interfaces_facts,
+ )
+ commands.extend(self.set_config(existing_interfaces_facts))
+
+ if self.state == "rendered":
+ # Hardcode the system defaults for "rendered"
+ # This can be made a configurable option in the future
+ self.intf_defs = {
+ "sysdefs": {
+ "L2_enabled": False,
+ "L3_enabled": False,
+ "mode": "layer3",
+ },
+ }
+ commands.extend(self.set_config(existing_interfaces_facts))
+
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ self.edit_config(commands)
+ result["changed"] = True
+
+ if self.state in self.ACTION_STATES:
+ result["commands"] = commands
+
+ if self.state in self.ACTION_STATES or self.state == "gathered":
+ changed_interfaces_facts = self.get_interfaces_facts()
+
+ elif self.state == "rendered":
+ result["rendered"] = commands
+
+ elif self.state == "parsed":
+ running_config = self._module.params["running_config"]
+ if not running_config:
+ self._module.fail_json(
+ msg="value of running_config parameter must not be empty for state parsed",
+ )
+ result["parsed"] = self.get_interfaces_facts(data=running_config)
+
+ if self.state in self.ACTION_STATES:
+ result["before"] = existing_interfaces_facts
+ if result["changed"]:
+ result["after"] = changed_interfaces_facts
+
+ elif self.state == "gathered":
+ result["gathered"] = changed_interfaces_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_interfaces_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ config = self._module.params.get("config")
+ want = []
+ if config:
+ for w in config:
+ w.update({"name": normalize_interface(w["name"])})
+ want.append(remove_empties(w))
+ have = deepcopy(existing_interfaces_facts)
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+ if state in ("overridden", "merged", "replaced", "rendered") and not want:
+ self._module.fail_json(
+ msg="value of config parameter must not be empty for state {0}".format(state),
+ )
+
+ commands = list()
+ if state == "overridden":
+ commands.extend(self._state_overridden(want, have))
+ elif state == "deleted":
+ commands.extend(self._state_deleted(want, have))
+ elif state == "purged":
+ commands.extend(self._state_purged(want, have))
+ else:
+ for w in want:
+ if state in ["merged", "rendered"]:
+ commands.extend(self._state_merged(w, have))
+ elif state == "replaced":
+ commands.extend(self._state_replaced(w, have))
+ return commands
+
+ def _state_replaced(self, w, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ name = w["name"]
+ obj_in_have = search_obj_in_list(name, have, "name")
+ if obj_in_have:
+ # If 'w' does not specify mode then intf may need to change to its
+ # default mode, however default mode may depend on sysdef.
+ if not w.get("mode") and re.search("Ethernet|port-channel", name):
+ sysdefs = self.intf_defs["sysdefs"]
+ sysdef_mode = sysdefs["mode"]
+ if obj_in_have.get("mode") != sysdef_mode:
+ w["mode"] = sysdef_mode
+ diff = dict_diff(w, obj_in_have)
+ else:
+ diff = w
+
+ merged_commands = self.set_commands(w, have)
+ # merged_commands:
+ # - These commands are changes specified by the playbook.
+ # - merged_commands apply to both existing and new objects
+ # replaced_commands:
+ # - These are the unspecified commands, used to reset any params
+ # that are not already set to default states
+ # - replaced_commands should only be used on 'have' objects
+ # (interfaces that already exist)
+ if obj_in_have:
+ if "name" not in diff:
+ diff["name"] = name
+ wkeys = w.keys()
+ dkeys = diff.keys()
+ for k in wkeys:
+ if k in self.exclude_params and k in dkeys:
+ del diff[k]
+ replaced_commands = self.del_attribs(diff)
+ cmds = set(replaced_commands).intersection(set(merged_commands))
+ for cmd in cmds:
+ merged_commands.remove(cmd)
+ commands.extend(replaced_commands)
+
+ commands.extend(merged_commands)
+ return commands
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ # overridden is the same as replaced behavior except for the scope.
+ cmds = []
+ existing_interfaces = []
+ for h in have:
+ existing_interfaces.append(h["name"])
+ obj_in_want = search_obj_in_list(h["name"], want, "name")
+ if obj_in_want:
+ if h != obj_in_want:
+ replaced_cmds = self._state_replaced(obj_in_want, [h])
+ if replaced_cmds:
+ cmds.extend(replaced_cmds)
+ else:
+ cmds.extend(self.del_attribs(h))
+
+ for w in want:
+ if w["name"] not in existing_interfaces:
+ # This is an object that was excluded from the 'have' list
+ # because all of its params are currently set to default states
+ # -OR- it's a new object that does not exist on the device yet.
+ cmds.extend(self.add_commands(w))
+ return cmds
+
+ def _state_merged(self, w, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(w, have)
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if want:
+ for w in want:
+ obj_in_have = search_obj_in_list(w["name"], have, "name")
+ commands.extend(self.del_attribs(obj_in_have))
+ else:
+ if not have:
+ return commands
+ for h in have:
+ commands.extend(self.del_attribs(h))
+ return commands
+
+ def _state_purged(self, want, have):
+ """The command generator when state is purged
+
+ :rtype: A list
+ :returns: the commands necessary to purge interfaces from running
+ configuration
+ """
+ commands = []
+ if want:
+ for w in want:
+ obj_in_have = search_obj_in_list(w["name"], have, "name")
+ if obj_in_have:
+ commands.append("no interface {0}".format(w["name"]))
+ return commands
+
+ def default_enabled(self, want=None, have=None, action=""):
+ # 'enabled' default state depends on the interface type and L2 state.
+ # Note that the current default could change when changing L2/L3 modes.
+ if self.state == "rendered":
+ # For "rendered", we always assume that
+ # the default enabled state is False
+ return False
+ if want is None:
+ want = {}
+ if have is None:
+ have = {}
+ name = have.get("name")
+ if name is None:
+ return None
+
+ sysdefs = self.intf_defs["sysdefs"]
+ sysdef_mode = sysdefs["mode"]
+
+ # Get the default enabled state for this interface. This was collected
+ # during Facts gathering.
+ intf_def_enabled = self.intf_defs.get(name)
+
+ have_mode = have.get("mode", sysdef_mode)
+ if action == "delete" and not want:
+ want_mode = sysdef_mode
+ else:
+ want_mode = want.get("mode", have_mode)
+ if (
+ (want_mode and have_mode) is None
+ or (want_mode != have_mode)
+ or intf_def_enabled is None
+ ):
+ # L2-L3 is changing or this is a new virtual intf. Get new default.
+ intf_def_enabled = default_intf_enabled(name=name, sysdefs=sysdefs, mode=want_mode)
+ return intf_def_enabled
+
+ def del_attribs(self, obj):
+ commands = []
+ if not obj or len(obj.keys()) == 1:
+ return commands
+ # mode/switchport changes should occur before other changes
+ sysdef_mode = self.intf_defs["sysdefs"]["mode"]
+ if "mode" in obj and obj["mode"] != sysdef_mode:
+ no_cmd = "no " if sysdef_mode == "layer3" else ""
+ commands.append(no_cmd + "switchport")
+ if "description" in obj:
+ commands.append("no description")
+ if "speed" in obj:
+ commands.append("no speed")
+ if "duplex" in obj:
+ commands.append("no duplex")
+ if "enabled" in obj:
+ sysdef_enabled = self.default_enabled(have=obj, action="delete")
+ if obj["enabled"] is False and sysdef_enabled is True:
+ commands.append("no shutdown")
+ elif obj["enabled"] is True and sysdef_enabled is False:
+ commands.append("shutdown")
+ if "mtu" in obj:
+ commands.append("no mtu")
+ if "ip_forward" in obj and obj["ip_forward"] is True:
+ commands.append("no ip forward")
+ if (
+ "fabric_forwarding_anycast_gateway" in obj
+ and obj["fabric_forwarding_anycast_gateway"] is True
+ ):
+ commands.append("no fabric forwarding mode anycast-gateway")
+ if commands:
+ commands.insert(0, "interface " + obj["name"])
+
+ return commands
+
+ def diff_of_dicts(self, w, obj):
+ diff = set(w.items()) - set(obj.items())
+ diff = dict(diff)
+ if diff and w["name"] == obj["name"]:
+ diff.update({"name": w["name"]})
+ return diff
+
+ def add_commands(self, d, obj_in_have=None):
+ commands = []
+ if obj_in_have is None:
+ obj_in_have = {}
+ # mode/switchport changes should occur before other changes
+ if "mode" in d:
+ sysdef_mode = self.intf_defs["sysdefs"]["mode"]
+ have_mode = obj_in_have.get("mode", sysdef_mode)
+ want_mode = d["mode"]
+ if have_mode == "layer2":
+ if want_mode == "layer3":
+ commands.append("no switchport")
+ elif want_mode == "layer2":
+ commands.append("switchport")
+ if "description" in d:
+ commands.append("description " + d["description"])
+ if "speed" in d:
+ commands.append("speed " + str(d["speed"]))
+ if "duplex" in d:
+ commands.append("duplex " + d["duplex"])
+ if "enabled" in d:
+ have_enabled = obj_in_have.get("enabled", self.default_enabled(d, obj_in_have)) or False
+ if d["enabled"] is False and have_enabled is True:
+ commands.append("shutdown")
+ elif d["enabled"] is True and have_enabled is False:
+ commands.append("no shutdown")
+ if "mtu" in d:
+ commands.append("mtu " + str(d["mtu"]))
+ if "ip_forward" in d:
+ if d["ip_forward"] is True:
+ commands.append("ip forward")
+ else:
+ commands.append("no ip forward")
+ if "fabric_forwarding_anycast_gateway" in d:
+ if d["fabric_forwarding_anycast_gateway"] is True:
+ commands.append("fabric forwarding mode anycast-gateway")
+ else:
+ commands.append("no fabric forwarding mode anycast-gateway")
+ if commands or not obj_in_have:
+ commands.insert(0, "interface" + " " + d["name"])
+ return commands
+
+ def set_commands(self, w, have):
+ commands = []
+ obj_in_have = search_obj_in_list(w["name"], have, "name")
+ if not obj_in_have:
+ commands = self.add_commands(w)
+ else:
+ diff = self.diff_of_dicts(w, obj_in_have)
+ commands = self.add_commands(diff, obj_in_have)
+ return commands
+
+ def render_interface_defaults(self, config, intfs):
+ """Collect user-defined-default states for 'system default switchport'
+ configurations. These configurations determine default L2/L3 modes
+ and enabled/shutdown states. The default values for user-defined-default
+ configurations may be different for legacy platforms.
+ Notes:
+ - L3 enabled default state is False on N9K,N7K but True for N3K,N6K
+ - Changing L2-L3 modes may change the default enabled value.
+ - '(no) system default switchport shutdown' only applies to L2 interfaces.
+ Run through the gathered interfaces and tag their default enabled state.
+ """
+ intf_defs = {}
+ L3_enabled = True if re.search("N[356]K", self.get_platform()) else False
+ intf_defs = {
+ "sysdefs": {
+ "mode": None,
+ "L2_enabled": None,
+ "L3_enabled": L3_enabled,
+ },
+ }
+ pat = "(no )*system default switchport$"
+ m = re.search(pat, config, re.MULTILINE)
+ if m:
+ intf_defs["sysdefs"]["mode"] = "layer3" if "no " in m.groups() else "layer2"
+
+ pat = "(no )*system default switchport shutdown$"
+ m = re.search(pat, config, re.MULTILINE)
+ if m:
+ intf_defs["sysdefs"]["L2_enabled"] = True if "no " in m.groups() else False
+
+ for item in intfs:
+ intf_defs[item["name"]] = default_intf_enabled(
+ name=item["name"],
+ sysdefs=intf_defs["sysdefs"],
+ mode=item.get("mode"),
+ )
+
+ return intf_defs
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/l2_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/l2_interfaces.py
new file mode 100644
index 00000000..e7d91498
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l2_interfaces/l2_interfaces.py
@@ -0,0 +1,351 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_l2_interfaces class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+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 (
+ dict_diff,
+ remove_empties,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ flatten_dict,
+ normalize_interface,
+ search_obj_in_list,
+ vlan_list_to_range,
+ vlan_range_to_list,
+)
+
+
+class L2_interfaces(ConfigBase):
+ """
+ The nxos_l2_interfaces class
+ """
+
+ gather_subset = ["!all", "!min"]
+
+ gather_network_resources = ["l2_interfaces"]
+
+ exclude_params = ["vlan", "allowed_vlans", "native_vlans"]
+
+ def __init__(self, module):
+ super(L2_interfaces, self).__init__(module)
+
+ def get_l2_interfaces_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ l2_interfaces_facts = facts["ansible_network_resources"].get("l2_interfaces")
+ if not l2_interfaces_facts:
+ return []
+ return l2_interfaces_facts
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ commands = []
+ warnings = []
+
+ if self.state in self.ACTION_STATES:
+ existing_l2_interfaces_facts = self.get_l2_interfaces_facts()
+ else:
+ existing_l2_interfaces_facts = []
+
+ if self.state in self.ACTION_STATES or self.state == "rendered":
+ commands.extend(self.set_config(existing_l2_interfaces_facts))
+
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ self._connection.edit_config(commands)
+ result["changed"] = True
+
+ if self.state in self.ACTION_STATES:
+ result["commands"] = commands
+
+ if self.state in self.ACTION_STATES or self.state == "gathered":
+ changed_l2_interfaces_facts = self.get_l2_interfaces_facts()
+
+ elif self.state == "rendered":
+ result["rendered"] = commands
+
+ elif self.state == "parsed":
+ running_config = self._module.params["running_config"]
+ if not running_config:
+ self._module.fail_json(
+ msg="value of running_config parameter must not be empty for state parsed",
+ )
+ result["parsed"] = self.get_l2_interfaces_facts(data=running_config)
+
+ if self.state in self.ACTION_STATES:
+ result["before"] = existing_l2_interfaces_facts
+ if result["changed"]:
+ result["after"] = changed_l2_interfaces_facts
+
+ elif self.state == "gathered":
+ result["gathered"] = changed_l2_interfaces_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_l2_interfaces_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ config = self._module.params.get("config")
+ want = []
+ if config:
+ for w in config:
+ w.update({"name": normalize_interface(w["name"])})
+ self.expand_trunk_allowed_vlans(w)
+ want.append(remove_empties(w))
+ have = existing_l2_interfaces_facts
+ for h in have:
+ self.expand_trunk_allowed_vlans(h)
+ resp = self.set_state(want, have) or []
+ self._reconstruct_commands(resp)
+
+ return resp
+
+ def expand_trunk_allowed_vlans(self, d):
+ if not d:
+ return None
+ if "trunk" in d and d["trunk"]:
+ if "allowed_vlans" in d["trunk"]:
+ allowed_vlans = vlan_range_to_list(d["trunk"]["allowed_vlans"])
+ vlans_list = [str(l) for l in sorted(allowed_vlans)]
+ d["trunk"]["allowed_vlans"] = ",".join(vlans_list)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+ if state in ("overridden", "merged", "replaced", "rendered") and not want:
+ self._module.fail_json(
+ msg="value of config parameter must not be empty for state {0}".format(state),
+ )
+
+ commands = list()
+ if state == "overridden":
+ commands.extend(self._state_overridden(want, have))
+ elif state == "deleted":
+ commands.extend(self._state_deleted(want, have))
+ else:
+ for w in want:
+ if state in ["merged", "rendered"]:
+ commands.extend(self._state_merged(flatten_dict(w), have))
+ elif state == "replaced":
+ commands.extend(self._state_replaced(flatten_dict(w), have))
+ return commands
+
+ def _state_replaced(self, w, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name"))
+ if obj_in_have:
+ diff = dict_diff(w, obj_in_have)
+ else:
+ diff = w
+ merged_commands = self.set_commands(w, have, True)
+ if "name" not in diff:
+ diff["name"] = w["name"]
+
+ dkeys = diff.keys()
+ for k in w.copy():
+ if k in self.exclude_params and k in dkeys:
+ del diff[k]
+ replaced_commands = self.del_attribs(diff)
+
+ if merged_commands or replaced_commands:
+ cmds = set(replaced_commands).intersection(set(merged_commands))
+ for cmd in cmds:
+ merged_commands.remove(cmd)
+ commands.extend(replaced_commands)
+ commands.extend(merged_commands)
+ return commands
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ for h in have:
+ h = flatten_dict(h)
+ obj_in_want = flatten_dict(search_obj_in_list(h["name"], want, "name"))
+ if h == obj_in_want:
+ continue
+ for w in want:
+ w = flatten_dict(w)
+ if h["name"] == w["name"]:
+ wkeys = w.keys()
+ hkeys = h.keys()
+ for k in wkeys:
+ if k in self.exclude_params and k in hkeys:
+ del h[k]
+ commands.extend(self.del_attribs(h))
+ for w in want:
+ commands.extend(self.set_commands(flatten_dict(w), have, True))
+ return commands
+
+ def _state_merged(self, w, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(w, have)
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if want:
+ for w in want:
+ obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name"))
+ commands.extend(self.del_attribs(obj_in_have))
+ else:
+ if not have:
+ return commands
+ for h in have:
+ commands.extend(self.del_attribs(flatten_dict(h)))
+ return commands
+
+ def del_attribs(self, obj):
+ commands = []
+ if not obj or len(obj.keys()) == 1:
+ return commands
+
+ cmd = "no switchport "
+ if "vlan" in obj:
+ commands.append(cmd + "access vlan")
+ if "mode" in obj:
+ commands.append(cmd + "mode")
+ if "allowed_vlans" in obj:
+ commands.append(cmd + "trunk allowed vlan")
+ if "native_vlan" in obj:
+ commands.append(cmd + "trunk native vlan")
+ if commands:
+ commands.insert(0, "interface " + obj["name"])
+ return commands
+
+ def diff_of_dicts(self, w, obj):
+ diff = set(w.items()) - set(obj.items())
+ diff = dict(diff)
+ if diff and w["name"] == obj["name"]:
+ diff.update({"name": w["name"]})
+ return diff
+
+ def add_commands(self, d, vlan_exists=False):
+ commands = []
+ if not d:
+ return commands
+
+ cmd = "switchport "
+ if "mode" in d:
+ commands.append(cmd + "mode {0}".format(d["mode"]))
+ if "vlan" in d:
+ commands.append(cmd + "access vlan " + str(d["vlan"]))
+ if "allowed_vlans" in d:
+ if vlan_exists:
+ commands.append(cmd + "trunk allowed vlan add " + str(d["allowed_vlans"]))
+ else:
+ commands.append(cmd + "trunk allowed vlan " + str(d["allowed_vlans"]))
+ if "native_vlan" in d:
+ commands.append(cmd + "trunk native vlan " + str(d["native_vlan"]))
+ if commands:
+ commands.insert(0, "interface " + d["name"])
+ return commands
+
+ def set_commands(self, w, have, replace=False):
+ commands = []
+
+ obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name"))
+ if not obj_in_have:
+ commands = self.add_commands(w)
+ else:
+ diff = self.diff_of_dicts(w, obj_in_have)
+ if diff and not replace:
+ if "mode" in diff.keys() and diff["mode"]:
+ commands = self.add_commands(diff)
+ if "allowed_vlans" in diff.keys() and diff["allowed_vlans"]:
+ vlan_tobe_added = diff["allowed_vlans"].split(",")
+ vlan_list = vlan_tobe_added[:]
+ if obj_in_have.get("allowed_vlans"):
+ have_vlans = obj_in_have["allowed_vlans"].split(",")
+ else:
+ have_vlans = []
+ for w_vlans in vlan_list:
+ if w_vlans in have_vlans:
+ vlan_tobe_added.pop(vlan_tobe_added.index(w_vlans))
+ if vlan_tobe_added:
+ diff.update({"allowed_vlans": ",".join(vlan_tobe_added)})
+ if have_vlans:
+ commands = self.add_commands(diff, True)
+ else:
+ commands = self.add_commands(diff)
+ return commands
+ commands = self.add_commands(diff)
+ return commands
+
+ def _reconstruct_commands(self, cmds):
+ for idx, cmd in enumerate(cmds):
+ match = re.search(
+ r"^(?P<cmd>(no\s)?switchport trunk allowed vlan(\sadd)?)\s(?P<vlans>.+)",
+ cmd,
+ )
+ if match:
+ data = match.groupdict()
+ unparsed = vlan_list_to_range(data["vlans"].split(","))
+ cmds[idx] = data["cmd"] + " " + unparsed
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/l3_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/l3_interfaces.py
new file mode 100644
index 00000000..55ac2266
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/l3_interfaces/l3_interfaces.py
@@ -0,0 +1,545 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_l3_interfaces class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_empties,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ normalize_interface,
+ search_obj_in_list,
+)
+
+
+class L3_interfaces(ConfigBase):
+ """
+ The nxos_l3_interfaces class
+ """
+
+ gather_subset = ["min"]
+
+ gather_network_resources = ["l3_interfaces"]
+
+ exclude_params = []
+
+ def __init__(self, module):
+ super(L3_interfaces, self).__init__(module)
+
+ def get_l3_interfaces_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ if self.state not in self.ACTION_STATES:
+ self.gather_subset = ["!all", "!min"]
+
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+
+ l3_interfaces_facts = facts["ansible_network_resources"].get("l3_interfaces")
+ self.platform = facts.get("ansible_net_platform", "")
+
+ return l3_interfaces_facts
+
+ def edit_config(self, commands):
+ return self._connection.edit_config(commands)
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ commands = []
+ warnings = []
+
+ if self.state in self.ACTION_STATES:
+ existing_l3_interfaces_facts = self.get_l3_interfaces_facts()
+ else:
+ existing_l3_interfaces_facts = []
+
+ if self.state in self.ACTION_STATES or self.state == "rendered":
+ commands.extend(self.set_config(existing_l3_interfaces_facts))
+
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ self.edit_config(commands)
+ result["changed"] = True
+
+ if self.state in self.ACTION_STATES:
+ result["commands"] = commands
+
+ if self.state in self.ACTION_STATES or self.state == "gathered":
+ changed_l3_interfaces_facts = self.get_l3_interfaces_facts()
+
+ elif self.state == "rendered":
+ result["rendered"] = commands
+
+ elif self.state == "parsed":
+ running_config = self._module.params["running_config"]
+ if not running_config:
+ self._module.fail_json(
+ msg="value of running_config parameter must not be empty for state parsed",
+ )
+ result["parsed"] = self.get_l3_interfaces_facts(data=running_config)
+
+ if self.state in self.ACTION_STATES:
+ result["before"] = existing_l3_interfaces_facts
+ if result["changed"]:
+ result["after"] = changed_l3_interfaces_facts
+
+ elif self.state == "gathered":
+ result["gathered"] = changed_l3_interfaces_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_l3_interfaces_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ config = self._module.params.get("config")
+ want = []
+ if config:
+ for w in config:
+ w.update({"name": normalize_interface(w["name"])})
+ want.append(remove_empties(w))
+ have = deepcopy(existing_l3_interfaces_facts)
+ self.init_check_existing(have)
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+ if state in ("overridden", "merged", "replaced", "rendered") and not want:
+ self._module.fail_json(
+ msg="value of config parameter must not be empty for state {0}".format(state),
+ )
+
+ commands = []
+ if state == "overridden":
+ commands.extend(self._state_overridden(want, have))
+ elif state == "deleted":
+ commands.extend(self._state_deleted(want, have))
+ else:
+ for w in want:
+ if state in ["merged", "rendered"]:
+ commands.extend(self._state_merged(w, have))
+ elif state == "replaced":
+ commands.extend(self._state_replaced(w, have))
+ return commands
+
+ def _state_replaced(self, want, have):
+ """The command generator when state is replaced
+ Scope is limited to interface objects defined in the playbook.
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ cmds = []
+ name = want["name"]
+ obj_in_have = search_obj_in_list(want["name"], have, "name")
+
+ have_v4 = obj_in_have.pop("ipv4", []) if obj_in_have else []
+ have_v6 = obj_in_have.pop("ipv6", []) if obj_in_have else []
+
+ # Process lists of dicts separately
+ v4_cmds = self._v4_cmds(want.pop("ipv4", []), have_v4, state="replaced")
+ v6_cmds = self._v6_cmds(want.pop("ipv6", []), have_v6, state="replaced")
+
+ # Process remaining attrs
+ if obj_in_have:
+ # Find 'want' changes first
+ diff = self.diff_of_dicts(want, obj_in_have)
+ rmv = {"name": name}
+ haves_not_in_want = set(obj_in_have.keys()) - set(want.keys()) - set(diff.keys())
+ for i in haves_not_in_want:
+ rmv[i] = obj_in_have[i]
+ cmds.extend(self.generate_delete_commands(rmv))
+ else:
+ diff = want
+
+ cmds.extend(self.add_commands(diff, name=name))
+ cmds.extend(v4_cmds)
+ cmds.extend(v6_cmds)
+ self.cmd_order_fixup(cmds, name)
+ return cmds
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden
+ Scope includes all interface objects on the device.
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ # overridden behavior is the same as replaced except for scope.
+ cmds = []
+ for i in have:
+ obj_in_want = search_obj_in_list(i["name"], want, "name")
+ if obj_in_want:
+ if i != obj_in_want:
+ v4_cmds = self._v4_cmds(
+ obj_in_want.pop("ipv4", []),
+ i.pop("ipv4", []),
+ state="overridden",
+ )
+ replaced_cmds = self._state_replaced(obj_in_want, [i])
+ replaced_cmds.extend(v4_cmds)
+ self.cmd_order_fixup(replaced_cmds, obj_in_want["name"])
+ cmds.extend(replaced_cmds)
+ else:
+ deleted_cmds = self.generate_delete_commands(i)
+ self.cmd_order_fixup(deleted_cmds, i["name"])
+ cmds.extend(deleted_cmds)
+
+ for i in want:
+ if [item for item in have if i["name"] == item["name"]]:
+ continue
+ cmds.extend(self.add_commands(i, name=i["name"]))
+
+ return cmds
+
+ def _state_merged(self, w, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(w, have)
+
+ def _v4_cmds(self, want, have, state=None):
+ """Helper method for processing ipv4 changes.
+ This is needed to handle primary/secondary address changes, which require a specific sequence when changing.
+ """
+ # The ip address cli does not allow removing primary addresses while
+ # secondaries are present, but it does allow changing a primary to a
+ # new address as long as the address is not a current secondary.
+ # Be aware of scenarios where a secondary is taking over
+ # the role of the primary, which must be changed in sequence.
+ # In general, primaries/secondaries should change in this order:
+ # Step 1. Remove secondaries that are being changed or removed
+ # Step 2. Change the primary if needed
+ # Step 3. Merge secondaries
+
+ # Normalize inputs (add tag key if not present)
+ for i in want:
+ i["tag"] = i.get("tag")
+ for i in have:
+ i["tag"] = i.get("tag")
+
+ merged = True if state == "merged" else False
+ replaced = True if state == "replaced" else False
+ overridden = True if state == "overridden" else False
+
+ # Create secondary and primary wants/haves
+ sec_w = [i for i in want if i.get("secondary")]
+ sec_h = [i for i in have if i.get("secondary")]
+ pri_w = [i for i in want if not i.get("secondary")]
+ pri_h = [i for i in have if not i.get("secondary")]
+ pri_w = pri_w[0] if pri_w else {}
+ pri_h = pri_h[0] if pri_h else {}
+ cmds = []
+
+ # Remove all addrs when no primary is specified in want (pri_w)
+ if pri_h and not pri_w and (replaced or overridden):
+ cmds.append("no ip address")
+ return cmds
+
+ # 1. Determine which secondaries are changing and remove them. Need a have/want
+ # diff instead of want/have because a have sec addr may be changing to a pri.
+ sec_to_rmv = []
+ sec_diff = self.diff_list_of_dicts(sec_h, sec_w)
+ for i in sec_diff:
+ if overridden or [w for w in sec_w if w["address"] == i["address"]]:
+ sec_to_rmv.append(i["address"])
+
+ # Check if new primary is currently a secondary
+ if pri_w and [h for h in sec_h if h["address"] == pri_w["address"]]:
+ if not overridden:
+ sec_to_rmv.append(pri_w["address"])
+
+ # Remove the changing secondaries
+ cmds.extend(["no ip address %s secondary" % i for i in sec_to_rmv])
+
+ # 2. change primary
+ if pri_w:
+ diff = dict(set(pri_w.items()) - set(pri_h.items()))
+ if diff:
+ addr = diff.get("address") or pri_w.get("address")
+ cmd = "ip address %s" % addr
+ tag = diff.get("tag")
+ cmd += " tag %s" % tag if tag else ""
+ cmds.append(cmd)
+
+ # 3. process remaining secondaries last
+ sec_w_to_chg = self.diff_list_of_dicts(sec_w, sec_h)
+ for i in sec_w_to_chg:
+ cmd = "ip address %s secondary" % i["address"]
+ cmd += " tag %s" % i["tag"] if i["tag"] else ""
+ cmds.append(cmd)
+
+ return cmds
+
+ def _v6_cmds(self, want, have, state=""):
+ """Helper method for processing ipv6 changes.
+ This is needed to avoid unnecessary churn on the device when removing or changing multiple addresses.
+ """
+ # Normalize inputs (add tag key if not present)
+ for i in want:
+ i["tag"] = i.get("tag")
+ for i in have:
+ i["tag"] = i.get("tag")
+
+ cmds = []
+ # items to remove (items in 'have' only)
+ if state == "replaced":
+ for i in self.diff_list_of_dicts(have, want):
+ want_addr = [w for w in want if w["address"] == i["address"]]
+ if not want_addr:
+ cmds.append("no ipv6 address %s" % i["address"])
+ elif i["tag"] and not want_addr[0]["tag"]:
+ # Must remove entire cli when removing tag
+ cmds.append("no ipv6 address %s" % i["address"])
+
+ # items to merge/add
+ for i in self.diff_list_of_dicts(want, have):
+ addr = i["address"]
+ tag = i["tag"]
+ if not tag and state == "merged":
+ # When want is IP-no-tag and have is IP+tag it will show up in diff,
+ # but for merged nothing has changed, so ignore it for idempotence.
+ have_addr = [h for h in have if h["address"] == addr]
+ if have_addr and have_addr[0].get("tag"):
+ continue
+ cmd = "ipv6 address %s" % i["address"]
+ cmd += " tag %s" % tag if tag else ""
+ cmds.append(cmd)
+
+ return cmds
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if want:
+ for w in want:
+ obj_in_have = search_obj_in_list(w["name"], have, "name")
+ commands.extend(self.del_all_attribs(obj_in_have))
+ else:
+ if not have:
+ return commands
+ for h in have:
+ commands.extend(self.del_all_attribs(h))
+ return commands
+
+ def del_all_attribs(self, obj):
+ commands = []
+ if not obj or len(obj.keys()) == 1:
+ return commands
+ commands = self.generate_delete_commands(obj)
+ self.cmd_order_fixup(commands, obj["name"])
+ return commands
+
+ def generate_delete_commands(self, obj):
+ """Generate CLI commands to remove non-default settings.
+ obj: dict of attrs to remove
+ """
+ commands = []
+ name = obj.get("name")
+ if "dot1q" in obj:
+ commands.append("no encapsulation dot1q")
+ if "redirects" in obj:
+ if not self.check_existing(name, "has_secondary") or re.match(
+ "N[35679]",
+ self.platform,
+ ):
+ # device auto-enables redirects when secondaries are removed;
+ # auto-enable may fail on legacy platforms so always do explicit enable
+ commands.append("ip redirects")
+ if "ipv6_redirects" in obj:
+ if not self.check_existing(name, "has_secondary") or re.match(
+ "N[35679]",
+ self.platform,
+ ):
+ # device auto-enables redirects when secondaries are removed;
+ # auto-enable may fail on legacy platforms so always do explicit enable
+ commands.append("ipv6 redirects")
+ if "unreachables" in obj:
+ commands.append("no ip unreachables")
+ if "ipv4" in obj:
+ commands.append("no ip address")
+ if "ipv6" in obj:
+ commands.append("no ipv6 address")
+ if "evpn_multisite_tracking" in obj:
+ have = self.existing_facts.get(name, {})
+ if have.get("evpn_multisite_tracking", False) is not False:
+ cmd = "no evpn multisite %s" % have.get("evpn_multisite_tracking")
+ commands.append(cmd)
+ return commands
+
+ def init_check_existing(self, have):
+ """Creates a class var dict for easier access to existing states"""
+ self.existing_facts = dict()
+ have_copy = deepcopy(have)
+ for intf in have_copy:
+ name = intf["name"]
+ self.existing_facts[name] = intf
+ # Check for presence of secondaries; used for ip redirects logic
+ if [i for i in intf.get("ipv4", []) if i.get("secondary")]:
+ self.existing_facts[name]["has_secondary"] = True
+
+ def check_existing(self, name, query):
+ """Helper method to lookup existing states on an interface.
+ This is needed for attribute changes that have additional dependencies;
+ e.g. 'ip redirects' may auto-enable when all secondary ip addrs are removed.
+ """
+ if name:
+ have = self.existing_facts.get(name, {})
+ if "has_secondary" in query:
+ return have.get("has_secondary", False)
+ if "redirects" in query:
+ return have.get("redirects", True)
+ if "unreachables" in query:
+ return have.get("unreachables", False)
+ return None
+
+ def diff_of_dicts(self, w, obj):
+ diff = set(w.items()) - set(obj.items())
+ diff = dict(diff)
+ if diff and w["name"] == obj["name"]:
+ diff.update({"name": w["name"]})
+ return diff
+
+ def diff_list_of_dicts(self, w, h):
+ diff = []
+ set_w = set(tuple(sorted(d.items())) for d in w) if w else set()
+ set_h = set(tuple(sorted(d.items())) for d in h) if h else set()
+ difference = set_w.difference(set_h)
+ for element in difference:
+ diff.append(dict((x, y) for x, y in element))
+ return diff
+
+ def add_commands(self, diff, name=""):
+ commands = []
+ if not diff:
+ return commands
+ if "dot1q" in diff:
+ commands.append("encapsulation dot1q " + str(diff["dot1q"]))
+ if "redirects" in diff:
+ # Note: device will auto-disable redirects when secondaries are present
+ if diff["redirects"] != self.check_existing(name, "redirects"):
+ no_cmd = "no " if diff["redirects"] is False else ""
+ commands.append(no_cmd + "ip redirects")
+ self.cmd_order_fixup(commands, name)
+ if "ipv6_redirects" in diff:
+ # Note: device will auto-disable redirects when secondaries are present
+ if diff["ipv6_redirects"] != self.check_existing(name, "ipv6_redirects"):
+ no_cmd = "no " if diff["ipv6_redirects"] is False else ""
+ commands.append(no_cmd + "ipv6 redirects")
+ self.cmd_order_fixup(commands, name)
+ if "unreachables" in diff:
+ if diff["unreachables"] != self.check_existing(name, "unreachables"):
+ no_cmd = "no " if diff["unreachables"] is False else ""
+ commands.append(no_cmd + "ip unreachables")
+ if "evpn_multisite_tracking" in diff:
+ commands.append("evpn multisite " + str(diff["evpn_multisite_tracking"]))
+ if "ipv4" in diff:
+ commands.extend(self.generate_afi_commands(diff["ipv4"]))
+ if "ipv6" in diff:
+ commands.extend(self.generate_afi_commands(diff["ipv6"]))
+ self.cmd_order_fixup(commands, name)
+
+ return commands
+
+ def generate_afi_commands(self, diff):
+ cmds = []
+ for i in diff:
+ cmd = "ipv6 address " if re.search("::", i["address"]) else "ip address "
+ cmd += i["address"]
+ if i.get("secondary"):
+ cmd += " secondary"
+ if i.get("tag"):
+ cmd += " tag " + str(i["tag"])
+ cmds.append(cmd)
+ return cmds
+
+ def set_commands(self, w, have):
+ commands = []
+ name = w["name"]
+ obj_in_have = search_obj_in_list(name, have, "name")
+ if not obj_in_have:
+ commands = self.add_commands(w, name=name)
+ else:
+ # lists of dicts must be processed separately from non-list attrs
+ v4_cmds = self._v4_cmds(w.pop("ipv4", []), obj_in_have.pop("ipv4", []), state="merged")
+ v6_cmds = self._v6_cmds(w.pop("ipv6", []), obj_in_have.pop("ipv6", []), state="merged")
+
+ # diff remaining attrs
+ diff = self.diff_of_dicts(w, obj_in_have)
+ commands = self.add_commands(diff, name=name)
+ commands.extend(v4_cmds)
+ commands.extend(v6_cmds)
+
+ self.cmd_order_fixup(commands, name)
+ return commands
+
+ def cmd_order_fixup(self, cmds, name):
+ """Inserts 'interface <name>' config at the beginning of populated command list; reorders dependent commands that must process after others."""
+ if cmds:
+ if name and not [item for item in cmds if item.startswith("interface")]:
+ cmds.insert(0, "interface " + name)
+
+ redirects = [item for item in cmds if re.match("(no )*ip(v6)* redirects", item)]
+ if redirects:
+ # redirects should occur after ipv4 commands, just move to end of list
+ redirects = redirects.pop()
+ cmds.remove(redirects)
+ cmds.append(redirects)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/lacp.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/lacp.py
new file mode 100644
index 00000000..4d334bad
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp/lacp.py
@@ -0,0 +1,234 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_lacp class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from 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 (
+ dict_diff,
+ remove_empties,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+
+
+class Lacp(ConfigBase):
+ """
+ The nxos_lacp class
+ """
+
+ gather_subset = ["!all", "!min"]
+
+ gather_network_resources = ["lacp"]
+
+ exclude_params = ["priority", "mac"]
+
+ def __init__(self, module):
+ super(Lacp, self).__init__(module)
+
+ def get_lacp_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ lacp_facts = facts["ansible_network_resources"].get("lacp", {})
+
+ return lacp_facts
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ commands = list()
+ warnings = list()
+
+ if self.state in self.ACTION_STATES:
+ existing_lacp_facts = self.get_lacp_facts()
+ else:
+ existing_lacp_facts = {}
+
+ if self.state in self.ACTION_STATES or self.state == "rendered":
+ commands.extend(self.set_config(existing_lacp_facts))
+
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ self._connection.edit_config(commands)
+ result["changed"] = True
+
+ if self.state in self.ACTION_STATES:
+ result["commands"] = commands
+
+ if self.state in self.ACTION_STATES or self.state == "gathered":
+ changed_lacp_facts = self.get_lacp_facts()
+
+ elif self.state == "rendered":
+ result["rendered"] = commands
+
+ elif self.state == "parsed":
+ running_config = self._module.params["running_config"]
+ if not running_config:
+ self._module.fail_json(
+ msg="value of running_config parameter must not be empty for state parsed",
+ )
+ result["parsed"] = self.get_lacp_facts(data=running_config)
+
+ if self.state in self.ACTION_STATES:
+ result["before"] = existing_lacp_facts
+ if result["changed"]:
+ result["after"] = changed_lacp_facts
+
+ elif self.state == "gathered":
+ result["gathered"] = changed_lacp_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_lacp_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = remove_empties(self._module.params["config"])
+ have = existing_lacp_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+ if state in ("merged", "replaced", "rendered") and not want:
+ self._module.fail_json(
+ msg="value of config parameter must not be empty for state {0}".format(state),
+ )
+
+ commands = list()
+
+ if state == "deleted":
+ commands.extend(self._state_deleted(want, have))
+ elif state in ["merged", "rendered"]:
+ commands.extend(self._state_merged(want, have))
+ elif state == "replaced":
+ commands.extend(self._state_replaced(want, have))
+ return commands
+
+ def _state_replaced(self, want, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ diff = dict_diff(want, have)
+ wkeys = want.keys()
+ dkeys = diff.keys()
+ for k in wkeys:
+ if k in self.exclude_params and k in dkeys:
+ del diff[k]
+ deleted_commands = self.del_all(diff)
+ merged_commands = self._state_merged(want, have)
+
+ commands.extend(deleted_commands)
+ if merged_commands:
+ commands.extend(merged_commands)
+
+ return commands
+
+ def _state_merged(self, want, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(want, have)
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if not have:
+ return commands
+ commands.extend(self.del_all(have))
+ return commands
+
+ def get_diff(self, comparable, base):
+ diff = {}
+ if not base:
+ diff = comparable
+ else:
+ diff = dict_diff(base, comparable)
+ return diff
+
+ def del_all(self, diff):
+ commands = []
+ base = "no lacp system-"
+ diff = diff.get("system")
+ if diff:
+ if "priority" in diff:
+ commands.append(base + "priority")
+ if "mac" in diff:
+ commands.append(base + "mac")
+ return commands
+
+ def add_commands(self, diff):
+ commands = []
+ base = "lacp system-"
+ diff = diff.get("system")
+ if diff and "priority" in diff:
+ cmd = base + "priority" + " " + str(diff["priority"])
+ commands.append(cmd)
+ if diff and "mac" in diff:
+ cmd = ""
+ if "address" in diff["mac"]:
+ cmd += base + "mac" + " " + diff["mac"]["address"]
+ if "role" in diff["mac"]:
+ cmd += " " + "role" + " " + diff["mac"]["role"]
+ if cmd:
+ commands.append(cmd)
+
+ return commands
+
+ def set_commands(self, want, have):
+ if not want:
+ return []
+ diff = self.get_diff(want, have)
+ return self.add_commands(diff)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/lacp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/lacp_interfaces.py
new file mode 100644
index 00000000..eaad0763
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lacp_interfaces/lacp_interfaces.py
@@ -0,0 +1,323 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_lacp_interfaces class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from 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 (
+ dict_diff,
+ remove_empties,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ flatten_dict,
+ get_interface_type,
+ normalize_interface,
+ search_obj_in_list,
+)
+
+
+class Lacp_interfaces(ConfigBase):
+ """
+ The nxos_lacp_interfaces class
+ """
+
+ gather_subset = ["!all", "!min"]
+
+ gather_network_resources = ["lacp_interfaces"]
+
+ exclude_params = ["port_priority", "rate", "min", "max"]
+
+ def __init__(self, module):
+ super(Lacp_interfaces, self).__init__(module)
+
+ def get_lacp_interfaces_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ lacp_interfaces_facts = facts["ansible_network_resources"].get("lacp_interfaces")
+ if not lacp_interfaces_facts:
+ return []
+ return lacp_interfaces_facts
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ commands = list()
+ warnings = list()
+
+ if self.state in self.ACTION_STATES:
+ existing_lacp_interfaces_facts = self.get_lacp_interfaces_facts()
+ else:
+ existing_lacp_interfaces_facts = []
+
+ if self.state in self.ACTION_STATES or self.state == "rendered":
+ commands.extend(self.set_config(existing_lacp_interfaces_facts))
+
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ self._connection.edit_config(commands)
+ result["changed"] = True
+
+ if self.state in self.ACTION_STATES:
+ result["commands"] = commands
+
+ if self.state in self.ACTION_STATES or self.state == "gathered":
+ changed_lacp_interfaces_facts = self.get_lacp_interfaces_facts()
+
+ elif self.state == "rendered":
+ result["rendered"] = commands
+
+ elif self.state == "parsed":
+ running_config = self._module.params["running_config"]
+ if not running_config:
+ self._module.fail_json(
+ msg="value of running_config parameter must not be empty for state parsed",
+ )
+ result["parsed"] = self.get_lacp_interfaces_facts(data=running_config)
+
+ if self.state in self.ACTION_STATES:
+ result["before"] = existing_lacp_interfaces_facts
+ if result["changed"]:
+ result["after"] = changed_lacp_interfaces_facts
+
+ elif self.state == "gathered":
+ result["gathered"] = changed_lacp_interfaces_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_lacp_interfaces_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ config = self._module.params.get("config")
+ want = []
+ if config:
+ for w in config:
+ if get_interface_type(w["name"]) not in (
+ "portchannel",
+ "ethernet",
+ ):
+ self._module.fail_json(
+ msg="This module works with either portchannel or ethernet",
+ )
+ w.update({"name": normalize_interface(w["name"])})
+ want.append(remove_empties(w))
+ have = existing_lacp_interfaces_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+ if state in ("overridden", "merged", "replaced", "rendered") and not want:
+ self._module.fail_json(
+ msg="value of config parameter must not be empty for state {0}".format(state),
+ )
+ commands = list()
+
+ if state == "overridden":
+ commands.extend(self._state_overridden(want, have))
+ elif state == "deleted":
+ commands.extend(self._state_deleted(want, have))
+ else:
+ for w in want:
+ if state in ["merged", "rendered"]:
+ commands.extend(self._state_merged(flatten_dict(w), have))
+ elif state == "replaced":
+ commands.extend(self._state_replaced(flatten_dict(w), have))
+ return commands
+
+ def _state_replaced(self, w, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name"))
+ diff = dict_diff(w, obj_in_have)
+ merged_commands = self.set_commands(w, have)
+ if "name" not in diff:
+ diff["name"] = w["name"]
+ wkeys = w.keys()
+ dkeys = diff.keys()
+ for k in wkeys:
+ if k in self.exclude_params and k in dkeys:
+ del diff[k]
+ replaced_commands = self.del_attribs(diff)
+
+ if merged_commands:
+ cmds = set(replaced_commands).intersection(set(merged_commands))
+ for cmd in cmds:
+ merged_commands.remove(cmd)
+ commands.extend(replaced_commands)
+ commands.extend(merged_commands)
+ return commands
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ for h in have:
+ h = flatten_dict(h)
+ obj_in_want = flatten_dict(search_obj_in_list(h["name"], want, "name"))
+ if h == obj_in_want:
+ continue
+ for w in want:
+ w = flatten_dict(w)
+ if h["name"] == w["name"]:
+ wkeys = w.keys()
+ hkeys = h.keys()
+ for k in wkeys:
+ if k in self.exclude_params and k in hkeys:
+ del h[k]
+ commands.extend(self.del_attribs(h))
+ for w in want:
+ commands.extend(self.set_commands(flatten_dict(w), have))
+ return commands
+
+ def _state_merged(self, w, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(w, have)
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if want:
+ for w in want:
+ obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name"))
+ commands.extend(self.del_attribs(obj_in_have))
+ else:
+ if not have:
+ return commands
+ for h in have:
+ commands.extend(self.del_attribs(flatten_dict(h)))
+ return commands
+
+ def del_attribs(self, obj):
+ commands = []
+ if not obj or len(obj.keys()) == 1:
+ return commands
+ commands.append("interface " + obj["name"])
+ if "graceful" in obj:
+ commands.append("lacp graceful-convergence")
+ if "vpc" in obj:
+ commands.append("no lacp vpn-convergence")
+ if "suspend_individual" in obj:
+ commands.append("lacp suspend_individual")
+ if "mode" in obj:
+ commands.append("no lacp mode " + obj["mode"])
+ if "max" in obj:
+ commands.append("no lacp max-bundle")
+ if "min" in obj:
+ commands.append("no lacp min-links")
+ if "port_priority" in obj:
+ commands.append("no lacp port-priority")
+ if "rate" in obj:
+ commands.append("no lacp rate")
+ return commands
+
+ def diff_of_dicts(self, w, obj):
+ diff = set(w.items()) - set(obj.items())
+ diff = dict(diff)
+ if diff and w["name"] == obj["name"]:
+ diff.update({"name": w["name"]})
+ return diff
+
+ def add_commands(self, d):
+ commands = []
+ if not d:
+ return commands
+ commands.append("interface" + " " + d["name"])
+
+ if "port_priority" in d:
+ commands.append("lacp port-priority " + str(d["port_priority"]))
+ if "rate" in d:
+ commands.append("lacp rate " + str(d["rate"]))
+ if "min" in d:
+ commands.append("lacp min-links " + str(d["min"]))
+ if "max" in d:
+ commands.append("lacp max-bundle " + str(d["max"]))
+ if "mode" in d:
+ commands.append("lacp mode " + d["mode"])
+ if "suspend_individual" in d:
+ if d["suspend_individual"] is True:
+ commands.append("lacp suspend-individual")
+ else:
+ commands.append("no lacp suspend-individual")
+ if "graceful" in d:
+ if d["graceful"] is True:
+ commands.append("lacp graceful-convergence")
+ else:
+ commands.append("no lacp graceful-convergence")
+ if "vpc" in d:
+ if d["vpc"] is True:
+ commands.append("lacp vpc-convergence")
+ else:
+ commands.append("no lacp vpc-convergence")
+ return commands
+
+ def set_commands(self, w, have):
+ commands = []
+ obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name"))
+ if not obj_in_have:
+ commands = self.add_commands(w)
+ else:
+ diff = self.diff_of_dicts(w, obj_in_have)
+ commands = self.add_commands(diff)
+ return commands
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py
new file mode 100644
index 00000000..495244b4
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lag_interfaces/lag_interfaces.py
@@ -0,0 +1,318 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_lag_interfaces class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from 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 (
+ dict_diff,
+ remove_empties,
+ search_obj_in_list,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ normalize_interface,
+)
+
+
+class Lag_interfaces(ConfigBase):
+ """
+ The nxos_lag_interfaces class
+ """
+
+ gather_subset = ["!all", "!min"]
+
+ gather_network_resources = ["lag_interfaces"]
+
+ def __init__(self, module):
+ super(Lag_interfaces, self).__init__(module)
+
+ def get_lag_interfaces_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ lag_interfaces_facts = facts["ansible_network_resources"].get("lag_interfaces")
+ if not lag_interfaces_facts:
+ return []
+ return lag_interfaces_facts
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ commands = list()
+ warnings = list()
+
+ if self.state in self.ACTION_STATES:
+ existing_lag_interfaces_facts = self.get_lag_interfaces_facts()
+ else:
+ existing_lag_interfaces_facts = []
+
+ if self.state in self.ACTION_STATES or self.state == "rendered":
+ commands.extend(self.set_config(existing_lag_interfaces_facts))
+
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ resp = self._connection.edit_config(commands)
+ if "response" in resp:
+ for item in resp["response"]:
+ if item:
+ err_str = item
+ if err_str.lower().startswith("cannot add"):
+ self._module.fail_json(msg=err_str)
+ result["changed"] = True
+
+ if self.state in self.ACTION_STATES:
+ result["commands"] = commands
+
+ if self.state in self.ACTION_STATES or self.state == "gathered":
+ changed_lag_interfaces_facts = self.get_lag_interfaces_facts()
+
+ elif self.state == "rendered":
+ result["rendered"] = commands
+
+ elif self.state == "parsed":
+ running_config = self._module.params["running_config"]
+ if not running_config:
+ self._module.fail_json(
+ msg="value of running_config parameter must not be empty for state parsed",
+ )
+ result["parsed"] = self.get_lag_interfaces_facts(data=running_config)
+
+ if self.state in self.ACTION_STATES:
+ result["before"] = existing_lag_interfaces_facts
+ if result["changed"]:
+ result["after"] = changed_lag_interfaces_facts
+
+ elif self.state == "gathered":
+ result["gathered"] = changed_lag_interfaces_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_lag_interfaces_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params.get("config")
+ if want:
+ for w in want:
+ w.update(remove_empties(w))
+ if "members" in w and w["members"]:
+ for item in w["members"]:
+ item.update({"member": normalize_interface(item["member"])})
+ have = existing_lag_interfaces_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+ if state in ("overridden", "merged", "replaced", "rendered") and not want:
+ self._module.fail_json(
+ msg="value of config parameter must not be empty for state {0}".format(state),
+ )
+ commands = list()
+
+ if state == "overridden":
+ commands.extend(self._state_overridden(want, have))
+ elif state == "deleted":
+ commands.extend(self._state_deleted(want, have))
+ else:
+ for w in want:
+ if state in ["merged", "rendered"]:
+ commands.extend(self._state_merged(w, have))
+ if state == "replaced":
+ commands.extend(self._state_replaced(w, have))
+ return commands
+
+ def _state_replaced(self, w, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ merged_commands = self.set_commands(w, have)
+ replaced_commands = self.del_intf_commands(w, have)
+ if merged_commands:
+ commands.extend(replaced_commands)
+ commands.extend(merged_commands)
+ return commands
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ for h in have:
+ obj_in_want = search_obj_in_list(h["name"], want, "name")
+ if obj_in_want:
+ diff = self.diff_list_of_dicts(h.get("members", []), obj_in_want["members"])
+ if not diff:
+ continue
+ commands.extend(self.del_all_commands(h))
+ for w in want:
+ commands.extend(self.set_commands(w, have))
+ return commands
+
+ def _state_merged(self, w, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(w, have)
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if want:
+ for w in want:
+ obj_in_have = search_obj_in_list(w["name"], have, "name")
+ commands.extend(self.del_all_commands(obj_in_have))
+ else:
+ if not have:
+ return commands
+ for h in have:
+ commands.extend(self.del_all_commands(h))
+ return commands
+
+ def diff_list_of_dicts(self, want, have):
+ if not want:
+ want = []
+
+ if not have:
+ have = []
+
+ diff = []
+ for w_item in want:
+ h_item = search_obj_in_list(w_item["member"], have, key="member") or {}
+ delta = dict_diff(h_item, w_item)
+ if delta:
+ if h_item:
+ if (
+ "mode" in delta.keys()
+ and delta["mode"] == "on"
+ and "mode" not in h_item.keys()
+ ):
+ # mode = on will not be displayed in running-config
+ continue
+ if "member" not in delta.keys():
+ delta["member"] = w_item["member"]
+ diff.append(delta)
+
+ return diff
+
+ def intersect_list_of_dicts(self, w, h):
+ intersect = []
+ wmem = []
+ hmem = []
+ for d in w:
+ wmem.append({"member": d["member"]})
+ for d in h:
+ hmem.append({"member": d["member"]})
+ set_w = set(tuple(sorted(d.items())) for d in wmem)
+ set_h = set(tuple(sorted(d.items())) for d in hmem)
+ intersection = set_w.intersection(set_h)
+ for element in intersection:
+ intersect.append(dict((x, y) for x, y in element))
+ return intersect
+
+ def add_commands(self, diff, name):
+ commands = []
+ name = name.strip("port-channel")
+ for d in diff:
+ commands.append("interface" + " " + d["member"])
+ cmd = ""
+ group_cmd = "channel-group {0}".format(name)
+ if d.get("force"):
+ cmd = group_cmd + " force "
+ if "mode" in d:
+ if cmd:
+ cmd = cmd + " mode " + d["mode"]
+ else:
+ cmd = group_cmd + " mode " + d["mode"]
+ if not cmd:
+ cmd = group_cmd
+ commands.append(cmd)
+ return commands
+
+ def set_commands(self, w, have):
+ commands = []
+ obj_in_have = search_obj_in_list(w["name"], have, "name")
+ if not obj_in_have:
+ commands = self.add_commands(w["members"], w["name"])
+ else:
+ if "members" not in obj_in_have:
+ obj_in_have["members"] = None
+ diff = self.diff_list_of_dicts(w["members"], obj_in_have["members"])
+ commands = self.add_commands(diff, w["name"])
+ return commands
+
+ def del_all_commands(self, obj_in_have):
+ commands = []
+ if not obj_in_have:
+ return commands
+ for m in obj_in_have.get("members", []):
+ commands.append("interface" + " " + m["member"])
+ commands.append("no channel-group")
+ return commands
+
+ def del_intf_commands(self, w, have):
+ commands = []
+ obj_in_have = search_obj_in_list(w["name"], have, "name")
+ if obj_in_have:
+ lst_to_del = self.intersect_list_of_dicts(w["members"], obj_in_have["members"])
+ if lst_to_del:
+ for item in lst_to_del:
+ commands.append("interface" + " " + item["member"])
+ commands.append("no channel-group")
+ return commands
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/lldp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/lldp_global.py
new file mode 100644
index 00000000..68d98621
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_global/lldp_global.py
@@ -0,0 +1,277 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_lldp_global class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from 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 (
+ dict_diff,
+ remove_empties,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+
+
+class Lldp_global(ConfigBase):
+ """
+ The nxos_lldp_global class
+ """
+
+ gather_subset = ["!all", "!min"]
+
+ gather_network_resources = ["lldp_global"]
+
+ def __init__(self, module):
+ super(Lldp_global, self).__init__(module)
+
+ def get_lldp_global_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ lldp_global_facts = facts["ansible_network_resources"].get("lldp_global")
+ if not lldp_global_facts:
+ return {}
+ return lldp_global_facts
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ commands = list()
+ warnings = list()
+
+ if self.state in self.ACTION_STATES:
+ existing_lldp_global_facts = self.get_lldp_global_facts()
+ else:
+ existing_lldp_global_facts = {}
+
+ if self.state in self.ACTION_STATES or self.state == "rendered":
+ commands.extend(self.set_config(existing_lldp_global_facts))
+
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ self._connection.edit_config(commands)
+ result["changed"] = True
+
+ if self.state in self.ACTION_STATES:
+ result["commands"] = commands
+
+ if self.state in self.ACTION_STATES or self.state == "gathered":
+ changed_lldp_global_facts = self.get_lldp_global_facts()
+
+ elif self.state == "rendered":
+ result["rendered"] = commands
+
+ elif self.state == "parsed":
+ running_config = self._module.params["running_config"]
+ if not running_config:
+ self._module.fail_json(
+ msg="value of running_config parameter must not be empty for state parsed",
+ )
+ result["parsed"] = self.get_lldp_global_facts(data=running_config)
+
+ if self.state in self.ACTION_STATES:
+ result["before"] = existing_lldp_global_facts
+ if result["changed"]:
+ result["after"] = changed_lldp_global_facts
+
+ elif self.state == "gathered":
+ result["gathered"] = changed_lldp_global_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_lldp_global_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params["config"]
+ have = existing_lldp_global_facts
+ resp = self.set_state(remove_empties(want), have)
+ return resp
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+ if state in ("merged", "replaced", "rendered") and not want:
+ self._module.fail_json(
+ msg="value of config parameter must not be empty for state {0}".format(state),
+ )
+ commands = list()
+
+ if state == "deleted":
+ commands = self._state_deleted(have)
+ elif state in ["merged", "rendered"]:
+ commands = self._state_merged(want, have)
+ elif state == "replaced":
+ commands = self._state_replaced(want, have)
+ return commands
+
+ def _state_replaced(self, want, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ merge_dict = dict_diff(have, want)
+ # merge_dict will contain new and unique values in want
+ delete_dict = self.find_delete_params(have, want)
+ self._module.params["state"] = "deleted"
+ commands.extend(self._state_deleted(delete_dict)) # delete
+ self._module.params["state"] = "merged"
+ commands.extend(self.set_commands(merge_dict)) # merge
+ self._module.params["state"] = "replaced"
+ return commands
+
+ def delete_nested_dict(self, have, want):
+ """
+ Returns tlv_select nested dict that needs to be defaulted
+ """
+ outer_dict = {}
+ for key, val in have.items():
+ inner_dict = {}
+ if not isinstance(val, dict):
+ if key not in want.keys():
+ inner_dict.update({key: val})
+ return inner_dict
+ else:
+ if key in want.keys():
+ outer_dict.update({key: self.delete_nested_dict(val, want[key])})
+ else:
+ outer_dict.update({key: val})
+ return outer_dict
+
+ def find_delete_params(self, have, want):
+ """
+ Returns parameters that are present in have and not in want, that need to be defaulted
+ """
+ delete_dict = {}
+ for key, val in have.items():
+ if key not in want.keys():
+ delete_dict.update({key: val})
+ else:
+ if key == "tlv_select":
+ delete_dict.update(
+ {key: self.delete_nested_dict(have["tlv_select"], want["tlv_select"])},
+ )
+ return delete_dict
+
+ def _state_merged(self, want, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = []
+ diff = dict_diff(have, want)
+ commands.extend(self.set_commands(diff))
+ return commands
+
+ def _state_deleted(self, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if have:
+ for key, val in have.items():
+ if "tlv_select" in key:
+ commands.extend(self.process_nested_dict(val))
+ else:
+ if key == "port_id":
+ key = "portid-subtype"
+ commands.append("no lldp " + key + " " + str(val))
+
+ return commands
+
+ def set_commands(self, diff):
+ commands = []
+ for key, val in diff.items():
+ commands.extend(self.add_commands(key, val))
+ return commands
+
+ def add_commands(self, key, val):
+ command = []
+ if "port_id" in key:
+ command.append("lldp portid-subtype " + str(val))
+ elif "tlv_select" in key:
+ command.extend(self.process_nested_dict(val))
+ else:
+ if val:
+ command.append("lldp " + key + " " + str(val))
+ return command
+
+ def process_nested_dict(self, val):
+ nested_commands = []
+ for k, v in val.items():
+ if isinstance(v, dict):
+ for k1, v1 in v.items():
+ com1 = "lldp tlv-select "
+ com2 = ""
+ if "system" in k:
+ com2 = "system-" + k1
+ elif "management_address" in k:
+ com2 = "management-address " + k1
+ elif "port" in k:
+ com2 = "port-" + k1
+
+ com1 += com2
+ com1 = self.negate_command(com1, v1)
+ nested_commands.append(com1)
+ else:
+ com1 = "lldp tlv-select "
+ if "power_management" in k:
+ com1 += "power-management"
+ else:
+ com1 += k
+
+ com1 = self.negate_command(com1, v)
+ nested_commands.append(com1)
+ return nested_commands
+
+ def negate_command(self, command, val):
+ # for merged, replaced vals need to be checked to add 'no'
+ if self._module.params["state"] == "merged":
+ if not val:
+ command = "no " + command
+ return command
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/lldp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/lldp_interfaces.py
new file mode 100644
index 00000000..528f7497
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/lldp_interfaces/lldp_interfaces.py
@@ -0,0 +1,312 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_lldp_interfaces class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from 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 (
+ dict_diff,
+ remove_empties,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ flatten_dict,
+ get_interface_type,
+ normalize_interface,
+ search_obj_in_list,
+)
+
+
+class Lldp_interfaces(ConfigBase):
+ """
+ The nxos_lldp_interfaces class
+ """
+
+ gather_subset = ["!all", "!min"]
+
+ gather_network_resources = ["lldp_interfaces"]
+
+ def __init__(self, module):
+ super(Lldp_interfaces, self).__init__(module)
+
+ def get_lldp_interfaces_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ lldp_interfaces_facts = facts["ansible_network_resources"].get("lldp_interfaces")
+ if not lldp_interfaces_facts:
+ return []
+ return lldp_interfaces_facts
+
+ def edit_config(self, commands):
+ """Wrapper method for `_connection.edit_config()`
+ This exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return self._connection.edit_config(commands)
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ commands = list()
+ warnings = list()
+ state = self._module.params["state"]
+ action_states = ["merged", "replaced", "deleted", "overridden"]
+
+ if state == "gathered":
+ result["gathered"] = self.get_lldp_interfaces_facts()
+ elif state == "rendered":
+ result["rendered"] = self.set_config({})
+ elif state == "parsed":
+ result["parsed"] = self.set_config({})
+ else:
+ existing_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
+ commands.extend(self.set_config(existing_lldp_interfaces_facts))
+ if commands and state in action_states:
+ if not self._module.check_mode:
+ self._connection.edit_config(commands)
+ result["changed"] = True
+ result["before"] = existing_lldp_interfaces_facts
+ result["commands"] = commands
+ result["commands"] = commands
+
+ changed_lldp_interfaces_facts = self.get_lldp_interfaces_facts()
+
+ if result["changed"]:
+ result["after"] = changed_lldp_interfaces_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_lldp_interfaces_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ config = self._module.params["config"]
+ want = []
+ if config:
+ for w in config:
+ if get_interface_type(w["name"]) not in (
+ "management",
+ "ethernet",
+ ):
+ self._module.fail_json(
+ msg="This module works with either management or ethernet",
+ )
+ w.update({"name": normalize_interface(w["name"])})
+ want.append(remove_empties(w))
+ have = existing_lldp_interfaces_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ state = self._module.params["state"]
+ if state == "overridden":
+ commands = self._state_overridden(want, have)
+ elif state == "deleted":
+ commands = self._state_deleted(want, have)
+ elif state == "rendered":
+ commands = self._state_rendered(want)
+ elif state == "parsed":
+ want = self._module.params["running_config"]
+ commands = self._state_parsed(want)
+ else:
+ for w in want:
+ if state == "merged":
+ commands.extend(self._state_merged(flatten_dict(w), have))
+ elif state == "replaced":
+ commands.extend(self._state_replaced(flatten_dict(w), have))
+ return commands
+
+ def _state_parsed(self, want):
+ return self.get_lldp_interfaces_facts(want)
+
+ def _state_rendered(self, want):
+ commands = []
+ for w in want:
+ commands.extend(self.set_commands(w, {}))
+ return commands
+
+ def _state_gathered(self, have):
+ """The command generator when state is gathered
+
+ :rtype: A list
+ :returns: the commands necessary to reproduce the current configuration
+ """
+ commands = []
+ want = {}
+ commands.append(self.set_commands(want, have))
+ return commands
+
+ def _state_replaced(self, want, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ del_commands = []
+ delete_dict = {}
+ obj_in_have = flatten_dict(search_obj_in_list(want["name"], have, "name"))
+ for k1 in obj_in_have.keys():
+ if k1 not in want.keys():
+ delete_dict.update({k1: obj_in_have[k1]})
+
+ if delete_dict:
+ delete_dict.update({"name": want["name"]})
+ del_commands = self.del_commands(delete_dict)
+ merged_commands = self.set_commands(want, have)
+
+ if merged_commands:
+ cmds = set(del_commands).intersection(set(merged_commands))
+ for cmd in cmds:
+ merged_commands.remove(cmd)
+
+ commands.extend(del_commands)
+ commands.extend(merged_commands)
+ return commands
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ want_intfs = [w["name"] for w in want]
+ for h in have:
+ h = flatten_dict(h)
+ delete_dict = {}
+ if h["name"] in want_intfs:
+ for w in want:
+ if w["name"] == h["name"]:
+ delete_keys = list(set(h) - set(flatten_dict(w)))
+ for k in delete_keys:
+ delete_dict.update({k: h[k]})
+ delete_dict.update({"name": h["name"]})
+ break
+ else:
+ delete_dict.update(h)
+ commands.extend(self.del_commands(delete_dict))
+ for w in want:
+ commands.extend(self.set_commands(flatten_dict(w), have))
+ return commands
+
+ def _state_merged(self, want, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(want, have)
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if want:
+ for w in want:
+ obj_in_have = flatten_dict(search_obj_in_list(w["name"], have, "name"))
+ commands.extend(self.del_commands(obj_in_have))
+ else:
+ if not have:
+ return commands
+ for h in have:
+ commands.extend(self.del_commands(flatten_dict(h)))
+ return commands
+
+ def set_commands(self, want, have):
+ commands = []
+ obj_in_have = flatten_dict(search_obj_in_list(want["name"], have, "name"))
+ if not obj_in_have:
+ commands = self.add_commands(flatten_dict(want))
+ else:
+ diff = dict_diff(obj_in_have, want)
+ if diff:
+ diff.update({"name": want["name"]})
+ commands = self.add_commands(diff)
+ return commands
+
+ def add_commands(self, d):
+ commands = []
+ if not d:
+ return commands
+ commands.append("interface " + d["name"])
+ if "transmit" in d:
+ if d["transmit"]:
+ commands.append("lldp transmit")
+ else:
+ commands.append("no lldp transmit")
+ if "receive" in d:
+ if d["receive"]:
+ commands.append("lldp receive")
+ else:
+ commands.append("no lldp receive")
+ if "management_address" in d:
+ commands.append("lldp tlv-set management-address " + d["management_address"])
+ if "vlan" in d:
+ commands.append("lldp tlv-set vlan " + str(d["vlan"]))
+
+ return commands
+
+ def del_commands(self, obj):
+ commands = []
+ if not obj or len(obj.keys()) == 1:
+ return commands
+ commands.append("interface " + obj["name"])
+ if "transmit" in obj:
+ commands.append("lldp transmit")
+ if "receive" in obj:
+ commands.append("lldp receive")
+ if "management_address" in obj:
+ commands.append("no lldp tlv-set management-address " + obj["management_address"])
+ if "vlan" in obj:
+ commands.append("no lldp tlv-set vlan " + str(obj["vlan"]))
+
+ return commands
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/logging_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/logging_global.py
new file mode 100644
index 00000000..6e493ad3
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/logging_global/logging_global.py
@@ -0,0 +1,199 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos_logging_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 copy import deepcopy
+
+from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
+ ResourceModule,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ dict_merge,
+ get_from_dict,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.logging_global import (
+ Logging_globalTemplate,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ get_logging_sevmap,
+)
+
+
+class Logging_global(ResourceModule):
+ """
+ The nxos_logging_global config class
+ """
+
+ def __init__(self, module):
+ super(Logging_global, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="logging_global",
+ tmplt=Logging_globalTemplate(),
+ )
+ self._sev_map = get_logging_sevmap(invert=True)
+ self._state_set = ("replaced", "deleted", "overridden")
+ self.parsers = [
+ "console",
+ "module",
+ "monitor",
+ "logfile",
+ "event.link_status.enable",
+ "event.link_status.default",
+ "event.trunk_status.enable",
+ "event.trunk_status.default",
+ "history.severity",
+ "history.size",
+ "ip.access_list.cache.entries",
+ "ip.access_list.cache.interval",
+ "ip.access_list.cache.threshold",
+ "ip.access_list.detailed",
+ "ip.access_list.include.sgt",
+ "origin_id.hostname",
+ "origin_id.ip",
+ "origin_id.string",
+ "rate_limit",
+ "rfc_strict",
+ "source_interface",
+ "timestamp",
+ ]
+
+ 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._logging_list_to_dict(self.want)
+ haved = self._logging_list_to_dict(self.have)
+
+ if self.state == "deleted":
+ # empty out want (in case something was specified)
+ # some items are populated later on for correct removal
+ wantd = {}
+
+ # pre-process `event.x.y` keys
+ for x in self.parsers[4:7]:
+ have_k = get_from_dict(haved, x)
+ want_k = get_from_dict(wantd, x)
+ if have_k is None and want_k is not None:
+ # set have to True to mimic default state
+ # this allows negate commands to be issued
+ self.__update_dict(haved, x)
+ if all(
+ (
+ self.state in self._state_set,
+ have_k is False,
+ want_k is None,
+ ),
+ ):
+ # if want is missing and have is negated
+ # set want to True in order to revert to default state
+ self.__update_dict(wantd, x)
+
+ # if state is merged, merge want onto have and then compare
+ if self.state == "merged":
+ for x in self.parsers[0:4]:
+ hstate = haved.get(x, {}).get("state", "")
+ wstate = wantd.get(x, {}).get("state", "")
+ if hstate == "disabled" and not wstate:
+ # this ensures that updates are done
+ # with correct `state`
+ if wantd.get(x, {}):
+ wantd[x].update({"state": "enabled"})
+ wantd = dict_merge(haved, wantd)
+
+ if self.state in self._state_set:
+ # set default states for keys that appear in negated form
+ for x in self.parsers[0:3]:
+ if x in haved and x not in wantd:
+ wantd[x] = {"state": "enabled"}
+ if "rate_limit" in haved and "rate_limit" not in wantd:
+ wantd["rate_limit"] = "enabled"
+ if "logfile" in haved and "logfile" not in wantd:
+ wantd["logfile"] = {"name": "messages", "severity": 5}
+
+ 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 Logging_global network resource.
+ """
+ self.compare(parsers=self.parsers, want=want, have=have)
+ self._compare_lists(want, have)
+
+ def _compare_lists(self, want, have):
+ """Compare list of dictionaries"""
+ for x in ["facilities", "hosts"]:
+ wantx = want.get(x, {})
+ havex = have.get(x, {})
+ for key, wentry in iteritems(wantx):
+ hentry = havex.pop(key, {})
+ if wentry != hentry:
+ if x == "hosts" and self.state in self._state_set:
+ # remove have config for hosts
+ # else want gets appended
+ self.addcmd(hentry, x, negate=True)
+ self.addcmd(wentry, x)
+ for key, hentry in iteritems(havex):
+ self.addcmd(hentry, x, negate=True)
+
+ def _logging_list_to_dict(self, data):
+ """Convert all list to dicts to dicts
+ of dicts and substitute severity values
+ """
+ tmp = deepcopy(data)
+ pkey = {"hosts": "host", "facilities": "facility"}
+ for k in ("hosts", "facilities"):
+ if k in tmp:
+ for x in tmp[k]:
+ if "severity" in x:
+ x["severity"] = self._sev_map[x["severity"]]
+ tmp[k] = {i[pkey[k]]: i for i in tmp[k]}
+ for k in ("console", "history", "logfile", "module", "monitor"):
+ if "severity" in tmp.get(k, {}):
+ tmp[k]["severity"] = self._sev_map[tmp[k]["severity"]]
+ return tmp
+
+ def __update_dict(self, datadict, key, nval=True):
+ """Utility method that updates last subkey of
+ `datadict` as identified by `key` to `nval`.
+ """
+ keys = key.split(".")
+ if keys[0] not in datadict:
+ datadict[keys[0]] = {}
+ if keys[1] not in datadict[keys[0]]:
+ datadict[keys[0]][keys[1]] = {}
+ datadict[keys[0]][keys[1]].update({keys[2]: nval})
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/ntp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/ntp_global.py
new file mode 100644
index 00000000..57a1a58d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ntp_global/ntp_global.py
@@ -0,0 +1,161 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos_ntp_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 copy import deepcopy
+
+from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import (
+ ResourceModule,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ dict_merge,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ntp_global import (
+ Ntp_globalTemplate,
+)
+
+
+class Ntp_global(ResourceModule):
+ """
+ The nxos_ntp_global config class
+ """
+
+ def __init__(self, module):
+ super(Ntp_global, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="ntp_global",
+ tmplt=Ntp_globalTemplate(),
+ )
+ self.parsers = [
+ "access_group.match_all",
+ "allow.control.rate_limit",
+ "allow.private",
+ "authenticate",
+ "logging",
+ "master.stratum",
+ "passive",
+ "source",
+ "source_interface",
+ ]
+
+ 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._ntp_list_to_dict(self.want)
+ haved = self._ntp_list_to_dict(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
+ if self.state == "deleted":
+ wantd = {}
+
+ 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 Ntp_global network resource.
+ """
+ self.compare(parsers=self.parsers, want=want, have=have)
+ self._compare_lists(want=want, have=have)
+ self._compare_access_group(want=want, have=have)
+
+ def _compare_lists(self, want, have):
+ keys = ["authentication_keys", "peers", "servers", "trusted_keys"]
+ for x in keys:
+ wantx = want.get(x, {})
+ havex = have.get(x, {})
+
+ for wkey, wentry in iteritems(wantx):
+ hentry = havex.pop(wkey, {})
+
+ # pop aliased keys to preserve idempotence
+ if x in ["peers", "servers"]:
+ wentry.pop("use_vrf", None)
+
+ if wentry != hentry:
+ if x in keys[1:3] and self.state in [
+ "overridden",
+ "replaced",
+ ]:
+ # remove existing config else it gets appeneded
+ self.addcmd(hentry, x, negate=True)
+ self.addcmd(wentry, x)
+
+ # remove superfluos config
+ for _hkey, hentry in iteritems(havex):
+ self.addcmd(hentry, x, negate=True)
+
+ def _compare_access_group(self, want, have):
+ want_ag = want.get("access_group", {})
+ have_ag = have.get("access_group", {})
+
+ for x in ["peer", "query_only", "serve", "serve_only"]:
+ wx = want_ag.get(x, {})
+ hx = have_ag.get(x, {})
+
+ for wkey, wentry in iteritems(wx):
+ hentry = hx.pop(wkey, {})
+ if wentry != hentry:
+ self.addcmd(wentry, x)
+
+ # remove superfluos config
+ for hentry in hx.values():
+ self.addcmd(hentry, x, negate=True)
+
+ def _ntp_list_to_dict(self, data):
+ """Convert all list to dicts to dicts
+ of dicts
+ """
+ tmp = deepcopy(data)
+ if "access_group" in tmp:
+ for x in ["peer", "query_only", "serve", "serve_only"]:
+ if x in tmp["access_group"]:
+ tmp["access_group"][x] = {i["access_list"]: i for i in tmp["access_group"][x]}
+ pkey = {
+ "authentication_keys": "id",
+ "peers": "peer",
+ "servers": "server",
+ "trusted_keys": "key_id",
+ }
+ for k in pkey.keys():
+ if k in tmp:
+ tmp[k] = {i[pkey[k]]: i for i in tmp[k]}
+ return tmp
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/ospf_interfaces.py
new file mode 100644
index 00000000..0a1bc580
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospf_interfaces/ospf_interfaces.py
@@ -0,0 +1,204 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos_ospf_interfaces 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.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospf_interfaces import (
+ Ospf_interfacesTemplate,
+)
+
+
+class Ospf_interfaces(ResourceModule):
+ """
+ The nxos_ospf_interfaces config class
+ """
+
+ def __init__(self, module):
+ super(Ospf_interfaces, self).__init__(
+ empty_fact_val=[],
+ facts_module=Facts(module),
+ module=module,
+ resource="ospf_interfaces",
+ tmplt=Ospf_interfacesTemplate(),
+ )
+ self.parsers = [
+ "authentication",
+ "authentication_key",
+ "message_digest_key",
+ "cost",
+ "dead_interval",
+ "hello_interval",
+ "instance",
+ "mtu_ignore",
+ "network",
+ "passive_interface",
+ "priority",
+ "retransmit_interval",
+ "shutdown",
+ "transmit_delay",
+ ]
+
+ 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["name"]: entry for entry in self.want}
+ haved = {entry["name"]: entry for entry in self.have}
+
+ # turn all lists of dicts into dicts prior to merge
+ for entry in wantd, haved:
+ self._list_to_dict(entry)
+
+ # 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._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 Ospf_interfaces network resource.
+ """
+ begin = len(self.commands)
+ self._compare_ospf_interfaces(want, have)
+ if len(self.commands) != begin:
+ self.commands.insert(begin, self._tmplt.render(want or have, "interface", False))
+
+ def _compare_ospf_interfaces(self, want, have):
+ waf = want.get("address_family", {})
+ haf = have.get("address_family", {})
+
+ for afi in ("ipv4", "ipv6"):
+ witem = waf.pop(afi, {})
+ hitem = haf.pop(afi, {})
+
+ # this key needs to be compared separately and
+ # popped from `authentication` dict to
+ # preserve idempotence for other keys in this dict
+ self.compare(["authentication.key_chain"], want=witem, have=hitem)
+ witem.get("authentication", {}).pop("key_chain", None)
+ hitem.get("authentication", {}).pop("key_chain", None)
+
+ # this ensures that the "no" form of "ip ospf passive-interface"
+ # command is executed even when there is no existing config
+ if witem.get("passive_interface") is False and "passive_interface" not in hitem:
+ hitem["passive_interface"] = True
+
+ if "passive_interface" in hitem and witem.get("default_passive_interface"):
+ self.commands.append(self._generate_passive_intf(witem))
+
+ self.compare(parsers=self.parsers, want=witem, have=hitem)
+
+ # compare top-level `multi_areas` config
+ for area in witem.get("multi_areas", []):
+ if area not in hitem.get("multi_areas", []):
+ self.addcmd({"afi": afi, "area": area}, "multi_areas", negate=False)
+ # remove superfluous top-level `multi_areas` config
+ for area in hitem.get("multi_areas", []):
+ if area not in witem.get("multi_areas", []):
+ self.addcmd({"afi": afi, "area": area}, "multi_areas", negate=True)
+
+ # compare config->address_family->processes
+ self._compare_processes(afi, witem.get("processes", {}), hitem.get("processes", {}))
+
+ def _compare_processes(self, afi, want, have):
+ # add and update config->address_family->processes
+
+ for w_id, wproc in want.items():
+ hproc = have.pop(w_id, {})
+ hproc["afi"] = wproc["afi"] = afi
+
+ # compare config->address_family->processes->area
+ self.compare(["area"], wproc, hproc)
+
+ # compare config->address_family->processes->multi_areas
+ marea_dict = {"afi": afi, "process_id": wproc["process_id"]}
+ for area in wproc.get("multi_areas", []):
+ if area not in hproc.get("multi_areas", []):
+ marea_dict["area"] = area
+ self.addcmd(marea_dict, "processes_multi_areas", negate=False)
+ # remove superfluous processes->multi_areas config
+ for area in hproc.get("multi_areas", []):
+ if area not in wproc.get("multi_areas", []):
+ marea_dict["area"] = area
+ self.addcmd(marea_dict, "processes_multi_areas", negate=True)
+
+ # remove superflous config->address_family->processes
+ for hproc in have.values():
+ hproc["afi"] = afi
+
+ # remove config->address_family->processes->area
+ self.addcmd(hproc, "area", negate=True)
+
+ # remove superfluous processes->multi_areas config
+ marea_dict = {"afi": afi, "process_id": hproc["process_id"]}
+ for area in hproc.get("multi_areas", []):
+ marea_dict["area"] = area
+ self.addcmd(marea_dict, "processes_multi_areas", negate=True)
+
+ def _list_to_dict(self, entry):
+ for item in entry.values():
+ for ag in item.get("address_family", []):
+ ag["processes"] = {
+ subentry["process_id"]: subentry for subentry in ag.get("processes", [])
+ }
+ item["address_family"] = {
+ subentry["afi"]: subentry for subentry in item.get("address_family", [])
+ }
+
+ def _generate_passive_intf(self, data):
+ cmd = "default "
+ if data["afi"] == "ipv4":
+ cmd += "ip ospf passive-interface"
+ else:
+ cmd += "ospfv3 passive-interface"
+ return cmd
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/ospfv2.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/ospfv2.py
new file mode 100644
index 00000000..8a6f42e7
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv2/ospfv2.py
@@ -0,0 +1,216 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos_ospfv2 class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from 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,
+ get_from_dict,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospfv2 import (
+ Ospfv2Template,
+)
+
+
+class Ospfv2(ResourceModule):
+ """
+ The nxos_ospfv2 class
+ """
+
+ def __init__(self, module):
+ super(Ospfv2, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="ospfv2",
+ tmplt=Ospfv2Template(),
+ )
+ self.parsers = [
+ "router_id",
+ "auto_cost",
+ "graceful_restart.set",
+ "graceful_restart.helper_disable",
+ "isolate",
+ "log_adjacency_changes",
+ "max_lsa",
+ "mpls.traffic_eng.router_id",
+ "mpls.traffic_eng.multicast_intact",
+ "name_lookup",
+ "passive_interface.default",
+ "rfc1583compatibility",
+ "shutdown",
+ "default_information.originate",
+ "default_metric",
+ "distance",
+ "table_map",
+ "timers.lsa_arrival",
+ "timers.lsa_group_pacing",
+ "timers.throttle.lsa",
+ "timers.throttle.spf",
+ "maximum_paths",
+ "max_metric",
+ "down_bit_ignore",
+ "capability.vrf_lite",
+ "bfd",
+ ]
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ if self.state not in ["parsed", "gathered"]:
+ self.gen_config()
+ self.run_commands()
+ return self.result
+
+ def gen_config(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
+ """
+ wantd = {(entry["process_id"]): entry for entry in self.want.get("processes", [])}
+ haved = {(entry["process_id"]): entry for entry in self.have.get("processes", [])}
+
+ # turn all lists of dicts into dicts prior to merge
+ for entry in wantd, haved:
+ self._ospf_list_to_dict(entry)
+
+ # 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 = {}
+
+ # if state is overridden, first remove processes that are in have but not in want
+ if self.state in ["overridden", "deleted"]:
+ for k, have in iteritems(haved):
+ if k not in wantd:
+ self.addcmd(have, "process_id", True)
+
+ for k, want in iteritems(wantd):
+ self._compare(want=want, have=haved.pop(k, {}))
+
+ def _compare(self, want, have):
+ begin = len(self.commands)
+ self.compare(self.parsers, want=want, have=have)
+ self._compare_lists(want=want, have=have)
+ self._areas_compare(want=want, have=have)
+ self._vrfs_compare(want=want, have=have)
+
+ if len(self.commands) != begin or (not have and want):
+ self.commands.insert(
+ begin,
+ self._tmplt.render(
+ want or have,
+ "vrf" if "vrf" in (want.keys() or have.keys()) else "process_id",
+ False,
+ ),
+ )
+
+ def _areas_compare(self, want, have):
+ wareas = want.get("areas", {})
+ hareas = have.get("areas", {})
+ for name, entry in iteritems(wareas):
+ self._area_compare(want=entry, have=hareas.pop(name, {}))
+ for name, entry in iteritems(hareas):
+ self._area_compare(want={}, have=entry)
+
+ def _area_compare(self, want, have):
+ parsers = [
+ "area.default_cost",
+ "area.authentication",
+ "area.nssa",
+ "area.nssa.translate",
+ "area.stub",
+ ]
+ self.compare(parsers=parsers, want=want, have=have)
+ self._area_compare_lists(want=want, have=have)
+
+ def _area_compare_lists(self, want, have):
+ for attrib in ["filter_list", "ranges"]:
+ wdict = want.get(attrib, {})
+ hdict = have.get(attrib, {})
+ for key, entry in iteritems(wdict):
+ if entry != hdict.pop(key, {}):
+ entry["area_id"] = want["area_id"]
+ self.addcmd(entry, "area.{0}".format(attrib), False)
+ # remove remaining items in have for replaced
+ for entry in hdict.values():
+ entry["area_id"] = have["area_id"]
+ self.addcmd(entry, "area.{0}".format(attrib), True)
+
+ def _compare_lists(self, want, have):
+ for attrib in [
+ "summary_address",
+ "redistribute",
+ "mpls.traffic_eng.areas",
+ ]:
+ wdict = get_from_dict(want, attrib) or {}
+ hdict = get_from_dict(have, attrib) or {}
+
+ for key, entry in iteritems(wdict):
+ if entry != hdict.pop(key, {}):
+ self.addcmd(entry, attrib, False)
+ # remove remaining items in have for replaced
+ for entry in hdict.values():
+ self.addcmd(entry, attrib, True)
+
+ def _vrfs_compare(self, want, have):
+ wvrfs = want.get("vrfs", {})
+ hvrfs = have.get("vrfs", {})
+ for name, entry in iteritems(wvrfs):
+ self._compare(want=entry, have=hvrfs.pop(name, {}))
+ # remove remaining items in have for replaced
+ for name, entry in iteritems(hvrfs):
+ self.addcmd(entry, "vrf", True)
+
+ def _ospf_list_to_dict(self, entry):
+ for _pid, proc in iteritems(entry):
+ for area in proc.get("areas", []):
+ area["ranges"] = {entry["prefix"]: entry for entry in area.get("ranges", [])}
+ area["filter_list"] = {
+ entry["direction"]: entry for entry in area.get("filter_list", [])
+ }
+ mpls_areas = {
+ entry["area_id"]: entry
+ for entry in proc.get("mpls", {}).get("traffic_eng", {}).get("areas", [])
+ }
+ if mpls_areas:
+ proc["mpls"]["traffic_eng"]["areas"] = mpls_areas
+ proc["areas"] = {entry["area_id"]: entry for entry in proc.get("areas", [])}
+ proc["summary_address"] = {
+ entry["prefix"]: entry for entry in proc.get("summary_address", [])
+ }
+ proc["redistribute"] = {
+ (entry.get("id"), entry["protocol"]): entry
+ for entry in proc.get("redistribute", [])
+ }
+ if "vrfs" in proc:
+ proc["vrfs"] = {entry["vrf"]: entry for entry in proc.get("vrfs", [])}
+ self._ospf_list_to_dict(proc["vrfs"])
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/ospfv3.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/ospfv3.py
new file mode 100644
index 00000000..e60fc31a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/ospfv3/ospfv3.py
@@ -0,0 +1,230 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos_ospfv3 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,
+ get_from_dict,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospfv3 import (
+ Ospfv3Template,
+)
+
+
+class Ospfv3(ResourceModule):
+ """
+ The nxos_ospfv3 config class
+ """
+
+ def __init__(self, module):
+ super(Ospfv3, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="ospfv3",
+ tmplt=Ospfv3Template(),
+ )
+ self.parsers = [
+ "auto_cost",
+ "flush_routes",
+ "graceful_restart.set",
+ "graceful_restart.helper_disable",
+ "graceful_restart.grace_period",
+ "graceful_restart.planned_only",
+ "isolate",
+ "log_adjacency_changes",
+ "max_lsa",
+ "max_metric",
+ "name_lookup",
+ "passive_interface.default",
+ "router_id",
+ "shutdown",
+ "timers.lsa_arrival",
+ "timers.lsa_group_pacing",
+ "timers.throttle.lsa",
+ ]
+
+ 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["process_id"]): entry for entry in self.want.get("processes", [])}
+ haved = {(entry["process_id"]): entry for entry in self.have.get("processes", [])}
+
+ # turn all lists of dicts into dicts prior to merge
+ for entry in wantd, haved:
+ self._ospfv3_list_to_dict(entry)
+
+ # 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 = {}
+
+ # if state is overridden, first remove processes that are in have but not in want
+ if self.state in ["overridden", "deleted"]:
+ for k, have in iteritems(haved):
+ if k not in wantd:
+ self.addcmd(have, "process_id", 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 Ospfv3 network resource.
+ """
+ begin = len(self.commands)
+ self.compare(parsers=self.parsers, want=want, have=have)
+ self._areas_compare(want=want, have=have)
+ self._vrfs_compare(want=want, have=have)
+ self._af_compare(want=want, have=have)
+
+ if len(self.commands) != begin or (not have and want):
+ self.commands.insert(
+ begin,
+ self._tmplt.render(
+ want or have,
+ "vrf" if "vrf" in (want.keys() or have.keys()) else "process_id",
+ False,
+ ),
+ )
+
+ def _areas_compare(self, want, have):
+ wareas = want.get("areas", {})
+ hareas = have.get("areas", {})
+ for name, entry in iteritems(wareas):
+ self._area_compare(want=entry, have=hareas.pop(name, {}))
+ for name, entry in iteritems(hareas):
+ self._area_compare(want={}, have=entry)
+
+ def _area_compare(self, want, have):
+ parsers = ["area.nssa", "area.nssa.translate", "area.stub"]
+ self.compare(parsers=parsers, want=want, have=have)
+
+ def _vrfs_compare(self, want, have):
+ wvrfs = want.get("vrfs", {})
+ hvrfs = have.get("vrfs", {})
+ for name, entry in iteritems(wvrfs):
+ self._compare(want=entry, have=hvrfs.pop(name, {}))
+ # remove remaining items in have for replaced
+ for name, entry in iteritems(hvrfs):
+ self.addcmd(entry, "vrf", True)
+
+ def _af_compare(self, want, have):
+ parsers = [
+ "default_information.originate",
+ "distance",
+ "maximum_paths",
+ "table_map",
+ "timers.throttle.spf",
+ ]
+ waf = want.get("address_family", {})
+ haf = have.get("address_family", {})
+
+ cmd_ptr = len(self.commands)
+
+ self._af_areas_compare(want=waf, have=haf)
+ self._af_compare_lists(want=waf, have=haf)
+ self.compare(parsers=parsers, want=waf, have=haf)
+
+ cmd_ptr_nxt = len(self.commands)
+ if cmd_ptr < cmd_ptr_nxt:
+ self.commands.insert(cmd_ptr, "address-family ipv6 unicast")
+
+ def _af_areas_compare(self, want, have):
+ wareas = want.get("areas", {})
+ hareas = have.get("areas", {})
+ for name, entry in iteritems(wareas):
+ self._af_area_compare(want=entry, have=hareas.pop(name, {}))
+ for name, entry in iteritems(hareas):
+ self._af_area_compare(want={}, have=entry)
+
+ def _af_area_compare(self, want, have):
+ self.compare(parsers=["area.default_cost"], want=want, have=have)
+ self._af_area_compare_lists(want=want, have=have)
+
+ def _af_area_compare_lists(self, want, have):
+ for attrib in ["filter_list", "ranges"]:
+ wdict = want.get(attrib, {})
+ hdict = have.get(attrib, {})
+ for key, entry in iteritems(wdict):
+ if entry != hdict.pop(key, {}):
+ entry["area_id"] = want["area_id"]
+ self.addcmd(entry, "area.{0}".format(attrib), False)
+ # remove remaining items in have for replaced
+ for entry in hdict.values():
+ entry["area_id"] = have["area_id"]
+ self.addcmd(entry, "area.{0}".format(attrib), True)
+
+ def _af_compare_lists(self, want, have):
+ for attrib in ["summary_address", "redistribute"]:
+ wdict = get_from_dict(want, attrib) or {}
+ hdict = get_from_dict(have, attrib) or {}
+
+ for key, entry in iteritems(wdict):
+ if entry != hdict.pop(key, {}):
+ self.addcmd(entry, attrib, False)
+ # remove remaining items in have for replaced
+ for entry in hdict.values():
+ self.addcmd(entry, attrib, True)
+
+ def _ospfv3_list_to_dict(self, entry):
+ for _pid, proc in iteritems(entry):
+ proc["areas"] = {entry["area_id"]: entry for entry in proc.get("areas", [])}
+ af = proc.get("address_family")
+ if af:
+ for area in af.get("areas", []):
+ area["ranges"] = {entry["prefix"]: entry for entry in area.get("ranges", [])}
+ area["filter_list"] = {
+ entry["direction"]: entry for entry in area.get("filter_list", [])
+ }
+ af["areas"] = {entry["area_id"]: entry for entry in af.get("areas", [])}
+ af["summary_address"] = {
+ entry["prefix"]: entry for entry in af.get("summary_address", [])
+ }
+ af["redistribute"] = {
+ (entry.get("id"), entry["protocol"]): entry
+ for entry in af.get("redistribute", [])
+ }
+ if "vrfs" in proc:
+ proc["vrfs"] = {entry["vrf"]: entry for entry in proc.get("vrfs", [])}
+ self._ospfv3_list_to_dict(proc["vrfs"])
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/prefix_lists.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/prefix_lists.py
new file mode 100644
index 00000000..2c6bb814
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/prefix_lists/prefix_lists.py
@@ -0,0 +1,146 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos_prefix_lists 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.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.prefix_lists import (
+ Prefix_listsTemplate,
+)
+
+
+class Prefix_lists(ResourceModule):
+ """
+ The nxos_prefix_lists config class
+ """
+
+ def __init__(self, module):
+ super(Prefix_lists, self).__init__(
+ empty_fact_val=[],
+ facts_module=Facts(module),
+ module=module,
+ resource="prefix_lists",
+ tmplt=Prefix_listsTemplate(),
+ )
+ self.parsers = []
+
+ 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["afi"]: entry for entry in self.want}
+ haved = {entry["afi"]: entry for entry in self.have}
+
+ 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":
+ 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}
+ 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):
+ if k not in wantd:
+ 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 Prefix_lists network resource.
+ """
+ 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", {}))
+
+ 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"],
+ ),
+ )
+ 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/nxos/plugins/module_utils/network/nxos/config/route_maps/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/route_maps.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/route_maps.py
new file mode 100644
index 00000000..b098b065
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/route_maps/route_maps.py
@@ -0,0 +1,192 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos_route_maps 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,
+ get_from_dict,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.route_maps import (
+ Route_mapsTemplate,
+)
+
+
+class Route_maps(ResourceModule):
+ """
+ The nxos_route_maps config class
+ """
+
+ def __init__(self, module):
+ super(Route_maps, self).__init__(
+ empty_fact_val=[],
+ facts_module=Facts(module),
+ module=module,
+ resource="route_maps",
+ tmplt=Route_mapsTemplate(),
+ )
+ self.linear_parsers = [
+ "description",
+ "continue_sequence",
+ "set.as_path.prepend.last_as",
+ "set.as_path.tag",
+ "set.comm_list",
+ "set.dampening",
+ "set.extcomm_list",
+ "set.forwarding_address",
+ "set.null_interface",
+ "set.ip.address.prefix_list",
+ "set.ip.precedence",
+ "set.ipv6.address.prefix_list",
+ "set.ipv6.precedence",
+ "set.label_index",
+ "set.level",
+ "set.local_preference",
+ "set.metric",
+ "set.metric_type",
+ "set.nssa_only",
+ "set.origin",
+ "set.path_selection",
+ "set.tag",
+ "set.weight",
+ ]
+ self.complex_parsers = [
+ "match.as_number.asn",
+ "match.as_number.as_path_list",
+ "match.as_path",
+ "match.community.community_list",
+ "match.evpn.route_types",
+ "match.extcommunity.extcommunity_list",
+ "match.interfaces",
+ "match.ip.address.access_list",
+ "match.ip.address.prefix_lists",
+ "match.ip.multicast",
+ "match.ip.next_hop.prefix_lists",
+ "match.ip.route_source.prefix_lists",
+ "match.ipv6.address.access_list",
+ "match.ipv6.address.prefix_lists",
+ "match.ipv6.multicast",
+ "match.ipv6.next_hop.prefix_lists",
+ "match.ipv6.route_source.prefix_lists",
+ "match.mac_list",
+ "match.metric",
+ "match.ospf_area",
+ "match.route_types",
+ "match.source_protocol",
+ "match.tags",
+ "set.as_path.prepend.as_number",
+ "set.distance",
+ "set.evpn.gateway_ip",
+ "set.community",
+ ]
+
+ 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._route_maps_list_to_dict(self.want)
+ haved = self._route_maps_list_to_dict(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:
+ for _hk, hentry in iteritems(have.get("entries", {})):
+ self.commands.append(self._tmplt.render(hentry, "route_map", True))
+
+ for wk, want in iteritems(wantd):
+ self._compare(want=want, have=haved.pop(wk, {}))
+
+ 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 Route_maps network resource.
+ """
+ w_entries = want.get("entries", {})
+ h_entries = have.get("entries", {})
+ self._compare_entries(want=w_entries, have=h_entries)
+
+ def _compare_entries(self, want, have):
+ for wk, wentry in iteritems(want):
+ hentry = have.pop(wk, {})
+ begin = len(self.commands)
+
+ self._compare_lists(wentry, hentry)
+ self.compare(parsers=self.linear_parsers, want=wentry, have=hentry)
+
+ if len(self.commands) != begin:
+ self.commands.insert(begin, self._tmplt.render(wentry, "route_map", False))
+ # remove superfluos entries from have
+ for _hk, hentry in iteritems(have):
+ self.commands.append(self._tmplt.render(hentry, "route_map", True))
+
+ def _compare_lists(self, want, have):
+ for x in self.complex_parsers:
+ wx = get_from_dict(want, x) or []
+ hx = get_from_dict(have, x) or []
+
+ if isinstance(wx, list):
+ wx = set(wx)
+ if isinstance(hx, list):
+ hx = set(hx)
+
+ if wx != hx:
+ # negate existing config so that want is not appended
+ # in case of replaced or overridden
+ if self.state in ["replaced", "overridden"] and hx:
+ self.addcmd(have, x, negate=True)
+ self.addcmd(want, x)
+
+ def _route_maps_list_to_dict(self, entry):
+ entry = {x["route_map"]: x for x in entry}
+ for rmap, data in iteritems(entry):
+ if "entries" in data:
+ for x in data["entries"]:
+ x.update({"route_map": rmap})
+ data["entries"] = {
+ (rmap, entry["action"], entry.get("sequence")): entry
+ for entry in data["entries"]
+ }
+ return entry
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/snmp_server.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/snmp_server.py
new file mode 100644
index 00000000..472e7285
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/snmp_server/snmp_server.py
@@ -0,0 +1,243 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos_snmp_server config file.
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to its desired end-state is
+created.
+"""
+
+from copy import deepcopy
+
+from ansible.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,
+ get_from_dict,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.snmp_server import (
+ Snmp_serverTemplate,
+)
+
+
+class Snmp_server(ResourceModule):
+ """
+ The nxos_snmp_server config class
+ """
+
+ def __init__(self, module):
+ super(Snmp_server, self).__init__(
+ empty_fact_val={},
+ facts_module=Facts(module),
+ module=module,
+ resource="snmp_server",
+ tmplt=Snmp_serverTemplate(),
+ )
+ self.parsers = [
+ "aaa_user.cache_timeout",
+ "contact",
+ "context",
+ "counter.enable",
+ "counter.cache.timeout",
+ "drop.unknown_engine_id",
+ "drop.unknown_user",
+ "traps.aaa",
+ "traps.bgp",
+ "traps.bridge.newroot",
+ "traps.bridge.topologychange",
+ "traps.callhome.event_notify",
+ "traps.callhome.smtp_send_fail",
+ "traps.cfs.merge_failure",
+ "traps.cfs.state_change_notif",
+ "traps.config.ccmCLIRunningConfigChanged",
+ "traps.entity.cefcMIBEnableStatusNotification",
+ "traps.entity.entity_fan_status_change",
+ "traps.entity.entity_mib_change",
+ "traps.entity.entity_module_inserted",
+ "traps.entity.entity_module_status_change",
+ "traps.entity.entity_power_out_change",
+ "traps.entity.entity_power_status_change",
+ "traps.entity.entity_sensor",
+ "traps.entity.entity_unrecognised_module",
+ "traps.feature_control.featureOpStatusChange",
+ "traps.feature_control.ciscoFeatOpStatusChange",
+ "traps.generic.coldStart",
+ "traps.generic.warmStart",
+ "traps.license.notify_license_expiry",
+ "traps.license.notify_license_expiry_warning",
+ "traps.license.notify_licensefile_missing",
+ "traps.license.notify_no_license_for_feature",
+ "traps.link.cErrDisableInterfaceEventRev1",
+ "traps.link.cieLinkDown",
+ "traps.link.cieLinkUp",
+ "traps.link.cisco_xcvr_mon_status_chg",
+ "traps.link.cmn_mac_move_notification",
+ "traps.link.delayed_link_state_change",
+ "traps.link.extended_linkDown",
+ "traps.link.extended_linkUp",
+ "traps.link.linkDown",
+ "traps.link.linkUp",
+ "traps.mmode.cseMaintModeChangeNotify",
+ "traps.mmode.cseNormalModeChangeNotify",
+ "traps.ospf",
+ "traps.ospfv3",
+ "traps.rf.redundancy_framework",
+ "traps.rmon.fallingAlarm",
+ "traps.rmon.hcFallingAlarm",
+ "traps.rmon.hcRisingAlarm",
+ "traps.rmon.risingAlarm",
+ "traps.snmp.authentication",
+ "traps.storm_control.cpscEventRev1",
+ "traps.storm_control.trap_rate",
+ "traps.stpx.inconsistency",
+ "traps.stpx.root_inconsistency",
+ "traps.stpx.loop_inconsistency",
+ "traps.syslog.message_generated",
+ "traps.sysmgr.cseFailSwCoreNotifyExtended",
+ "traps.system.clock_change_notification",
+ "traps.upgrade.upgradeJobStatusNotify",
+ "traps.upgrade.upgradeOpNotifyOnCompletion",
+ "traps.vtp.notifs",
+ "traps.vtp.vlancreate",
+ "traps.vtp.vlandelete",
+ "engine_id.local",
+ "global_enforce_priv",
+ "location",
+ "mib.community_map",
+ "packetsize",
+ "protocol.enable",
+ "source_interface.informs",
+ "source_interface.traps",
+ "system_shutdown",
+ "tcp_session",
+ ]
+
+ 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._list_to_dict(self.want)
+ haved = self._list_to_dict(self.have)
+
+ # if state is merged, merge want onto have and then compare
+ if self.state == "merged":
+ wantd = dict_merge(haved, wantd)
+
+ # this ensures that if user sets `enable: True` for a trap
+ # all suboptions for that trap are set to True
+ for x in [
+ "traps.aaa",
+ "traps.bridge",
+ "traps.callhome",
+ "traps.cfs",
+ "traps.config",
+ "traps.entity",
+ "traps.feature_control",
+ "traps.generic",
+ "traps.license",
+ "traps.link",
+ "traps.mmode",
+ "traps.rf",
+ "traps.rmon",
+ "traps.snmp",
+ "traps.storm_control",
+ "traps.stpx",
+ "traps.syslog",
+ "traps.sysmgr",
+ "traps.system",
+ "traps.upgrade",
+ "traps.vtp",
+ ]:
+ entry = get_from_dict(wantd, x)
+ if entry and entry.get("enable", False):
+ key = x.split(".")
+ wantd[key[0]][key[1]].pop("enable")
+ for i in self.parsers:
+ if i.startswith(x):
+ key = i.split(".")
+ wantd[key[0]][key[1]][key[2]] = True
+
+ # if state is deleted, empty out wantd and set haved to wantd
+ if self.state == "deleted":
+ wantd = {}
+
+ 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 Snmp_server network resource.
+ """
+ self.compare(parsers=self.parsers, want=want, have=have)
+ self._compare_lists(want=want, have=have)
+
+ def _compare_lists(self, want, have):
+ """
+ Compare list of dictionaries
+ """
+ for x in ["users.auth", "users.use_acls", "hosts", "communities"]:
+ wantx = get_from_dict(want, x) or {}
+ havex = get_from_dict(have, x) or {}
+ for wkey, wentry in iteritems(wantx):
+ hentry = havex.pop(wkey, {})
+ if wentry != hentry:
+ self.addcmd(wentry, x)
+ # remove superfluous items
+ for _k, hv in iteritems(havex):
+ self.addcmd(hv, x, negate=True)
+
+ def _list_to_dict(self, data):
+ def _build_key(x):
+ key = set()
+ for k, v in iteritems(x):
+ if isinstance(v, dict):
+ for sk, sv in iteritems(v):
+ if isinstance(sv, dict):
+ for ssk, ssv in iteritems(sv):
+ key.add(sk + "_" + ssk + "_" + str(ssv))
+ else:
+ key.add(sk + "_" + str(sv))
+ else:
+ key.add(k + "_" + str(v))
+ return tuple(sorted(key))
+
+ tmp = deepcopy(data)
+ if "communities" in tmp:
+ tmp["communities"] = {_build_key(entry): entry for entry in tmp["communities"]}
+ if "users" in tmp:
+ if "auth" in tmp["users"]:
+ tmp["users"]["auth"] = {_build_key(entry): entry for entry in tmp["users"]["auth"]}
+ if "use_acls" in tmp["users"]:
+ tmp["users"]["use_acls"] = {
+ entry["user"]: entry for entry in tmp["users"]["use_acls"]
+ }
+ if "hosts" in tmp:
+ tmp["hosts"] = {_build_key(entry): entry for entry in tmp["hosts"]}
+ return tmp
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/static_routes.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/static_routes.py
new file mode 100644
index 00000000..721ff82c
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/static_routes/static_routes.py
@@ -0,0 +1,567 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_static_routes class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.cfg.base import (
+ ConfigBase,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_empties,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ normalize_interface,
+ search_obj_in_list,
+)
+
+
+class Static_routes(ConfigBase):
+ """
+ The nxos_xstatic_routes class
+ """
+
+ gather_subset = ["!all", "!min"]
+
+ gather_network_resources = ["static_routes"]
+
+ def __init__(self, module):
+ super(Static_routes, self).__init__(module)
+
+ def get_static_routes_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ static_routes_facts = facts["ansible_network_resources"].get("static_routes")
+ if not static_routes_facts:
+ return []
+
+ return static_routes_facts
+
+ def edit_config(self, commands):
+ """Wrapper method for `_connection.edit_config()`
+ This exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return self._connection.edit_config(commands)
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ warnings = list()
+ commands = list()
+ state = self._module.params["state"]
+ action_states = ["merged", "replaced", "deleted", "overridden"]
+
+ if state == "gathered":
+ result["gathered"] = self.get_static_routes_facts()
+ elif state == "rendered":
+ result["rendered"] = self.set_config({})
+ # no need to fetch facts for rendered
+ elif state == "parsed":
+ result["parsed"] = self.set_config({})
+ # no need to fetch facts for parsed
+ else:
+ existing_static_routes_facts = self.get_static_routes_facts()
+ commands.extend(self.set_config(existing_static_routes_facts))
+ if commands and state in action_states:
+ if not self._module.check_mode:
+ self._connection.edit_config(commands)
+ result["changed"] = True
+ result["before"] = existing_static_routes_facts
+ result["commands"] = commands
+
+ changed_static_routes_facts = self.get_static_routes_facts()
+ if result["changed"]:
+ result["after"] = changed_static_routes_facts
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_static_routes_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ config = self._module.params["config"]
+ want = []
+ if config:
+ for w in config:
+ want.append(remove_empties(w))
+ have = existing_static_routes_facts
+ want = self.add_default_vrf(deepcopy(want))
+ have = self.add_default_vrf(deepcopy(have))
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+ commands = []
+ if state == "overridden":
+ commands = self._state_overridden(want, have)
+ elif state == "deleted":
+ commands = self._state_deleted(want, have)
+ elif state == "rendered":
+ commands = self._state_rendered(want, have=[])
+ elif state == "parsed":
+ want = self._module.params["running_config"]
+ commands = self._state_parsed(want)
+ else:
+ for w in want:
+ if state == "merged":
+ commands.extend(self._state_merged(w, have))
+ elif state == "replaced":
+ commands.extend(self._state_replaced(w, have))
+ return commands
+
+ def _state_parsed(self, want):
+ return self.get_static_routes_facts(want)
+
+ def _state_rendered(self, want, have):
+ commands = []
+ for w in want:
+ commands.extend(self.set_commands(w, {}))
+ return commands
+
+ def _state_replaced(self, want, have):
+ """The command generator when state is replaced
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ delete_commands = []
+ state = self._module.params["state"]
+ merged_commands = []
+ obj_in_have = search_obj_in_list(want["vrf"], have, "vrf")
+ # in replaced, we check if whatever in have is in want, unlike merged. This is because we need to apply deleted on have config
+ if obj_in_have and obj_in_have != {"vrf": "default"}:
+ want_afi_list = []
+ if "address_families" in want.keys():
+ want_afi_list = [w["afi"] for w in want["address_families"]]
+ if len(want_afi_list) > 0:
+ for h in obj_in_have["address_families"]:
+ if h["afi"] in want_afi_list:
+ want_afi = search_obj_in_list(h["afi"], want["address_families"], "afi")
+ want_dest_list = []
+ if "routes" in want_afi.keys():
+ want_dest_list = [w["dest"] for w in want_afi["routes"]]
+ if len(want_dest_list) > 0:
+ for ro in h["routes"]:
+ if ro["dest"] in want_dest_list:
+ want_dest = search_obj_in_list(
+ ro["dest"],
+ want_afi["routes"],
+ "dest",
+ )
+ want_next_hops = []
+ if "next_hops" in want_dest.keys():
+ want_next_hops = list(want_dest["next_hops"])
+ if len(want_next_hops) > 0:
+ for next_hop in ro["next_hops"]:
+ if next_hop not in want_next_hops:
+ # have's next hop not in want, so delete it
+ delete_dict = {
+ "vrf": obj_in_have["vrf"],
+ "address_families": [
+ {
+ "afi": h["afi"],
+ "routes": [
+ {
+ "dest": ro["dest"],
+ "next_hops": [next_hop],
+ },
+ ],
+ },
+ ],
+ }
+ delete_commands.extend(
+ self.del_commands([delete_dict]),
+ )
+ else:
+ # want has no next_hops, so delete all next_hops under that dest
+ if state == "overridden":
+ delete_dict = {
+ "vrf": obj_in_have["vrf"],
+ "address_families": [
+ {
+ "afi": h["afi"],
+ "routes": [
+ {
+ "dest": ro["dest"],
+ "next_hops": ro["next_hops"],
+ },
+ ],
+ },
+ ],
+ }
+ delete_commands.extend(self.del_commands([delete_dict]))
+ else:
+ if state == "overridden":
+ delete_dict = {
+ "vrf": obj_in_have["vrf"],
+ "address_families": [
+ {
+ "afi": h["afi"],
+ "routes": [
+ {
+ "dest": ro["dest"],
+ "next_hops": ro["next_hops"],
+ },
+ ],
+ },
+ ],
+ }
+ delete_commands.extend(self.del_commands([delete_dict]))
+
+ else:
+ if (
+ state == "overridden"
+ ): # want has no 'routes' key, so delete all routes under that afi
+ if "routes" in h.keys():
+ delete_dict = {
+ "vrf": obj_in_have["vrf"],
+ "address_families": [
+ {
+ "afi": h["afi"],
+ "routes": h["routes"],
+ },
+ ],
+ }
+ delete_commands.extend(self.del_commands([delete_dict]))
+ else:
+ if (
+ state == "overridden"
+ ): # want has 'vrf' key only. So delete all address families in it
+ delete_commands.extend(
+ self.del_commands(
+ [
+ {
+ "address_families": list(obj_in_have["address_families"]),
+ "vrf": obj_in_have["vrf"],
+ },
+ ],
+ ),
+ )
+ final_delete_commands = []
+ for d in delete_commands:
+ if d not in final_delete_commands:
+ final_delete_commands.append(d)
+ # if there are two afis, 'vrf context..' is added twice fom del_commands. The above code removes the redundant 'vrf context ..'
+ merged_commands = self.set_commands(want, have)
+ if merged_commands:
+ cmds = set(final_delete_commands).intersection(set(merged_commands))
+ for c in cmds:
+ merged_commands.remove(c)
+
+ # set_commands adds a 'vrf context..' line. The above code removes the redundant 'vrf context ..'
+ commands.extend(final_delete_commands)
+ commands.extend(merged_commands)
+ return commands
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ want_vrfs = [w["vrf"] for w in want]
+ for h in have:
+ if h["vrf"] not in want_vrfs and h["vrf"] != "management":
+ commands.extend(self._state_deleted([h], have))
+ for w in want:
+ commands.extend(self._state_replaced(w, have))
+ return commands
+
+ def _state_merged(self, want, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ return self.set_commands(want, have)
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if want:
+ for w in want:
+ delete_dict = {}
+ obj_in_have = search_obj_in_list(w["vrf"], have, "vrf")
+ if obj_in_have:
+ if "address_families" in w.keys():
+ o1 = obj_in_have["address_families"]
+ afi_list = [o["afi"] for o in o1] # have's afi list
+ for w1 in w["address_families"]:
+ if w1["afi"] in afi_list:
+ o2 = search_obj_in_list(w1["afi"], o1, "afi")
+ state = self._module.params["state"]
+ if state != "deleted":
+ # Deleted scope is till afi only. Part below is for use by overridden state.
+ if "routes" in w1.keys():
+ for w2 in w1["routes"]:
+ o3 = search_obj_in_list(
+ w2["dest"],
+ o2["routes"],
+ "dest",
+ )
+ hops = []
+ if "next_hops" in w2.keys():
+ for nh in w2["next_hops"]:
+ if nh in o3["next_hops"]:
+ hops.append(nh)
+ else:
+ # if next hops not given
+ hops = o3["next_hops"]
+
+ delete_dict = {
+ "vrf": obj_in_have["vrf"],
+ "address_families": [
+ {
+ "afi": w1["afi"],
+ "routes": [
+ {
+ "dest": w2["dest"],
+ "next_hops": hops,
+ },
+ ],
+ },
+ ],
+ }
+ commands.extend(self.del_commands([delete_dict]))
+ else:
+ # case when only afi given for delete
+ delete_dict = {
+ "vrf": obj_in_have["vrf"],
+ "address_families": [
+ {
+ "afi": o2["afi"],
+ "routes": o2["routes"],
+ },
+ ],
+ }
+ commands.extend(self.del_commands([delete_dict]))
+ else:
+ commands.extend(
+ self.del_commands(
+ [
+ {
+ "vrf": obj_in_have["vrf"],
+ "address_families": [o2],
+ },
+ ],
+ ),
+ )
+ else:
+ # only vrf given to delete
+ commands.extend(self.del_commands([obj_in_have]))
+ else:
+ if have:
+ # delete everything
+ del_have = []
+ for h in have:
+ if h["vrf"] != "management": # protect management vrf
+ del_have.append(h)
+ commands = self.del_commands(del_have)
+
+ final_delete_commands = []
+ # del_commands might add 'vrf context..' twice for two routes in the same vrf. This removes it
+ for c in commands:
+ if c not in final_delete_commands:
+ final_delete_commands.append(c)
+ return final_delete_commands
+
+ def del_commands(self, have):
+ commands = []
+ for h in have:
+ if h != {"vrf": "default"}:
+ vrf = h["vrf"]
+ if "default" not in vrf:
+ commands.append("vrf context " + vrf)
+ else:
+ # Default static routes are configured in global context.
+ # "vrf context default" command throws error 9.X release onwards.
+ # Changing the context to global is achieved by "configure terminal"
+ commands.append("configure terminal")
+ for af in h["address_families"]:
+ for route in af["routes"]:
+ for next_hop in route["next_hops"]:
+ command = self.del_next_hop(af, route, next_hop)
+ commands.append(command.strip())
+ return commands
+
+ def del_next_hop(self, af, route, next_hop):
+ command = ""
+ if af["afi"] == "ipv4":
+ command = "no ip route " + route["dest"] + " " + self.add_commands(next_hop)
+ else:
+ command = "no ipv6 route " + route["dest"] + " " + self.add_commands(next_hop)
+ return command
+
+ def add_commands(self, want):
+ command = ""
+ params = want.keys()
+ pref = vrf = ip = intf = name = tag = track = ""
+ if "admin_distance" in params:
+ pref = str(want["admin_distance"]) + " "
+ if "track" in params:
+ track = "track " + str(want["track"]) + " "
+ if "dest_vrf" in params:
+ vrf = "vrf " + str(want["dest_vrf"]) + " "
+ if "forward_router_address" in params:
+ ip = want["forward_router_address"] + " "
+ if "interface" in params:
+ intf = normalize_interface(want["interface"]) + " "
+ if "null0" in intf:
+ ip = ""
+ intf = "null0 "
+ if "route_name" in params:
+ name = "name " + str(want["route_name"]) + " "
+ if "tag" in params:
+ tag = "tag " + str(want["tag"]) + " "
+ command = intf + ip + vrf + name + tag + track + pref
+ if intf != "Null0 " and ip == "":
+ self._module.fail_json(msg="forward_router_address error")
+ return command.strip()
+
+ def set_commands(self, want, have):
+ commands = []
+ h1 = h2 = h3 = {}
+ want = remove_empties(want)
+ vrf_list = []
+ if have:
+ vrf_list = [h["vrf"] for h in have]
+ if want["vrf"] in vrf_list and have != [{"vrf": "default"}]:
+ for x in have:
+ if x["vrf"] == want["vrf"]:
+ h1 = x # this has the 'have' dict with same vrf as want
+ if "address_families" in h1.keys():
+ afi_list = [h["afi"] for h in h1["address_families"]]
+ if "address_families" in want.keys():
+ for af in want["address_families"]:
+ if af["afi"] in afi_list:
+ for x in h1["address_families"]:
+ if x["afi"] == af["afi"]:
+ h2 = x # this has the have dict with same vrf and afi as want
+ dest_list = [h["dest"] for h in h2["routes"]]
+ if "routes" in af.keys():
+ for ro in af["routes"]:
+ if ro["dest"] in dest_list:
+ for x in h2["routes"]:
+ if x["dest"] == ro["dest"]:
+ h3 = x # this has the have dict with same vrf, afi and dest as want
+ next_hop_list = list(h3["next_hops"])
+ if "next_hops" in ro.keys():
+ for nh in ro["next_hops"]:
+ if "interface" in nh.keys():
+ nh["interface"] = normalize_interface(
+ nh["interface"],
+ )
+ if nh not in next_hop_list:
+ # no match for next hop in have
+ commands = self.set_next_hop(
+ want,
+ h2,
+ nh,
+ ro,
+ commands,
+ )
+ vrf_list.append(want["vrf"])
+ else:
+ # no match for dest
+ if "next_hops" in ro.keys():
+ for nh in ro["next_hops"]:
+ commands = self.set_next_hop(
+ want,
+ h2,
+ nh,
+ ro,
+ commands,
+ )
+ else:
+ # no match for afi
+ if "routes" in af.keys():
+ for ro in af["routes"]:
+ for nh in ro["next_hops"]:
+ commands = self.set_next_hop(want, af, nh, ro, commands)
+ else:
+ # no match for vrf
+ vrf_list.append(want["vrf"])
+ for af in want["address_families"]:
+ for ro in af["routes"]:
+ for nh in ro["next_hops"]:
+ commands = self.set_next_hop(want, af, nh, ro, commands)
+ return commands
+
+ def set_next_hop(self, want, h2, nh, ro, commands):
+ vrf = want["vrf"]
+ if h2["afi"] == "ipv4":
+ com = "ip route " + ro["dest"] + " " + self.add_commands(nh)
+ else:
+ com = "ipv6 route " + ro["dest"] + " " + self.add_commands(nh)
+ commands.append(com.strip())
+ if "default" not in vrf:
+ string = "vrf context " + str(vrf)
+ else:
+ # Default static routes are configured in global context.
+ # "vrf context default" command throws error 9.X release onwards.
+ # Changing the context to global is achieved by "configure terminal"
+ string = "configure terminal"
+ if string not in commands:
+ commands.insert(0, string)
+ return commands
+
+ def add_default_vrf(self, dictionary):
+ """
+ This method is used to add 'default' vrf to the facts collected as global/default vrf
+ is not shown in facts. vrf key exists for all vrfs except global.
+ """
+ for d in dictionary:
+ if "vrf" not in d.keys():
+ d.update({"vrf": "default"})
+ return dictionary
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/telemetry.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/telemetry.py
new file mode 100644
index 00000000..da3f7743
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/telemetry/telemetry.py
@@ -0,0 +1,593 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_telemetry class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+from 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.cisco.nxos.plugins.module_utils.network.nxos.cmdref.telemetry.telemetry import (
+ TMS_DESTGROUP,
+ TMS_GLOBAL,
+ TMS_SENSORGROUP,
+ TMS_SUBSCRIPTION,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import NxosCmdRef
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.telemetry.telemetry import (
+ get_module_params_subsection,
+ get_setval_path,
+ massage_data,
+ normalize_data,
+ remove_duplicate_commands,
+ remove_duplicate_context,
+ valiate_input,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ normalize_interface,
+)
+
+
+class Telemetry(ConfigBase):
+ """
+ The nxos_telemetry class
+ """
+
+ gather_subset = ["!all", "!min"]
+
+ gather_network_resources = ["telemetry"]
+
+ def __init__(self, module):
+ super(Telemetry, self).__init__(module)
+
+ def get_telemetry_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ telemetry_facts = facts["ansible_network_resources"].get("telemetry")
+ if not telemetry_facts:
+ return {}
+ return telemetry_facts
+
+ def edit_config(self, commands):
+ return self._connection.edit_config(commands)
+
+ def execute_module(self):
+ """Execute the module
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ commands = list()
+ warnings = list()
+
+ state = self._module.params["state"]
+ if "overridden" in state:
+ self._module.fail_json(msg="State <overridden> is invalid for this module.")
+ # When state is 'deleted', the module_params should not contain data
+ # under the 'config' key
+ if "deleted" in state and self._module.params.get("config"):
+ self._module.fail_json(msg="Remove config key from playbook when state is <deleted>")
+
+ if self._module.params["config"] is None:
+ self._module.params["config"] = {}
+ # Normalize interface name.
+ int = self._module.params["config"].get("source_interface")
+ if int:
+ self._module.params["config"]["source_interface"] = normalize_interface(int)
+
+ if self.state in self.ACTION_STATES:
+ existing_telemetry_facts = self.get_telemetry_facts()
+ else:
+ existing_telemetry_facts = []
+
+ if self.state in self.ACTION_STATES:
+ commands.extend(self.set_config(existing_telemetry_facts))
+
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ self.edit_config(commands)
+ result["changed"] = True
+
+ if self.state in self.ACTION_STATES:
+ result["commands"] = commands
+
+ if self.state in self.ACTION_STATES or self.state == "gathered":
+ changed_telemetry_facts = self.get_telemetry_facts()
+
+ if self.state in self.ACTION_STATES:
+ result["before"] = existing_telemetry_facts
+ if result["changed"]:
+ result["after"] = changed_telemetry_facts
+
+ elif self.state == "gathered":
+ result["gathered"] = changed_telemetry_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_tms_global_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ config = self._module.params["config"]
+ want = dict((k, v) for k, v in config.items() if v is not None)
+ have = existing_tms_global_facts
+ resp = self.set_state(want, have)
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+
+ # The deleted case is very simple since we purge all telemetry config
+ # and does not require any processing using NxosCmdRef objects.
+ if state == "deleted":
+ return self._state_deleted(want, have)
+ elif state == "replaced":
+ if want == have:
+ return []
+ return self._state_replaced(want, have)
+
+ # Save off module params
+ ALL_MP = self._module.params["config"]
+
+ cmd_ref = {}
+ cmd_ref["TMS_GLOBAL"] = {}
+ cmd_ref["TMS_DESTGROUP"] = {}
+ cmd_ref["TMS_SENSORGROUP"] = {}
+ cmd_ref["TMS_SUBSCRIPTION"] = {}
+
+ # Build Telemetry Global NxosCmdRef Object
+ cmd_ref["TMS_GLOBAL"]["ref"] = []
+ self._module.params["config"] = get_module_params_subsection(ALL_MP, "TMS_GLOBAL")
+ cmd_ref["TMS_GLOBAL"]["ref"].append(NxosCmdRef(self._module, TMS_GLOBAL))
+ ref = cmd_ref["TMS_GLOBAL"]["ref"][0]
+ ref.set_context()
+ ref.get_existing()
+ ref.get_playvals()
+ device_cache = ref.cache_existing
+
+ def build_cmdref_objects(td):
+ cmd_ref[td["type"]]["ref"] = []
+ saved_ids = []
+ if want.get(td["name"]):
+ for playvals in want[td["name"]]:
+ valiate_input(playvals, td["name"], self._module)
+ if playvals["id"] in saved_ids:
+ continue
+ saved_ids.append(playvals["id"])
+ resource_key = td["cmd"].format(playvals["id"])
+ # Only build the NxosCmdRef object for the td['name'] module parameters.
+ self._module.params["config"] = get_module_params_subsection(
+ ALL_MP,
+ td["type"],
+ playvals["id"],
+ )
+ cmd_ref[td["type"]]["ref"].append(NxosCmdRef(self._module, td["obj"]))
+ ref = cmd_ref[td["type"]]["ref"][-1]
+ ref.set_context([resource_key])
+ if td["type"] == "TMS_SENSORGROUP" and get_setval_path(self._module):
+ # Sensor group path setting can contain optional values.
+ # Call get_setval_path helper function to process any
+ # optional setval keys.
+ ref._ref["path"]["setval"] = get_setval_path(self._module)
+ ref.get_existing(device_cache)
+ ref.get_playvals()
+ if td["type"] == "TMS_DESTGROUP":
+ normalize_data(ref)
+
+ # Build Telemetry Destination Group NxosCmdRef Objects
+ td = {
+ "name": "destination_groups",
+ "type": "TMS_DESTGROUP",
+ "obj": TMS_DESTGROUP,
+ "cmd": "destination-group {0}",
+ }
+ build_cmdref_objects(td)
+
+ # Build Telemetry Sensor Group NxosCmdRef Objects
+ td = {
+ "name": "sensor_groups",
+ "type": "TMS_SENSORGROUP",
+ "obj": TMS_SENSORGROUP,
+ "cmd": "sensor-group {0}",
+ }
+ build_cmdref_objects(td)
+
+ # Build Telemetry Subscription NxosCmdRef Objects
+ td = {
+ "name": "subscriptions",
+ "type": "TMS_SUBSCRIPTION",
+ "obj": TMS_SUBSCRIPTION,
+ "cmd": "subscription {0}",
+ }
+ build_cmdref_objects(td)
+
+ if state == "merged":
+ if want == have:
+ return []
+ commands = self._state_merged(cmd_ref)
+ return commands
+
+ @staticmethod
+ def _state_replaced(want, have):
+ """The command generator when state is replaced
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ commands = []
+ massaged_have = massage_data(have)
+ massaged_want = massage_data(want)
+
+ ref = {}
+ ref["tms_global"] = NxosCmdRef([], TMS_GLOBAL, ref_only=True)
+ ref["tms_destgroup"] = NxosCmdRef([], TMS_DESTGROUP, ref_only=True)
+ ref["tms_sensorgroup"] = NxosCmdRef([], TMS_SENSORGROUP, ref_only=True)
+ ref["tms_subscription"] = NxosCmdRef([], TMS_SUBSCRIPTION, ref_only=True)
+
+ # Order matters for state replaced.
+ # First remove all subscriptions, followed by sensor-groups and destination-groups.
+ # Second add all destination-groups, followed by sensor-groups and subscriptions
+ add = {
+ "TMS_GLOBAL": [],
+ "TMS_DESTGROUP": [],
+ "TMS_SENSORGROUP": [],
+ "TMS_SUBSCRIPTION": [],
+ }
+ delete = {
+ "TMS_DESTGROUP": [],
+ "TMS_SENSORGROUP": [],
+ "TMS_SUBSCRIPTION": [],
+ }
+
+ # Process Telemetry Global Want and Have Values
+ # Possible states:
+ # - want and have are (set) (equal: no action, not equal: replace with want)
+ # - want (set) have (not set) (add want)
+ # - want (not set) have (set) (delete have)
+ # - want (not set) have (not set) (no action)
+ # global_ctx = ref['tms_global']._ref['_template']['context']
+ # property_ctx = ref['tms_global']._ref['certificate'].get('context')
+ # setval = ref['tms_global']._ref['certificate']['setval']
+ #
+ all_global_properties = [
+ "certificate",
+ "compression",
+ "source_interface",
+ "vrf",
+ ]
+ dest_profile_properties = ["compression", "source_interface", "vrf"]
+ dest_profile_remote_commands = []
+ for property in all_global_properties:
+ cmd = None
+ global_ctx = ref["tms_global"]._ref["_template"]["context"]
+ property_ctx = ref["tms_global"]._ref[property].get("context")
+ setval = ref["tms_global"]._ref[property]["setval"]
+ kind = ref["tms_global"]._ref[property]["kind"]
+ if want.get(property) is not None:
+ if have.get(property) is not None:
+ if want.get(property) != have.get(property):
+ if kind == "dict":
+ cmd = [setval.format(**want.get(property))]
+ else:
+ cmd = [setval.format(want.get(property))]
+ elif have.get(property) is None:
+ if kind == "dict":
+ cmd = [setval.format(**want.get(property))]
+ else:
+ cmd = [setval.format(want.get(property))]
+ elif want.get(property) is None:
+ if have.get(property) is not None:
+ if kind == "dict":
+ cmd = ["no " + setval.format(**have.get(property))]
+ else:
+ cmd = ["no " + setval.format(have.get(property))]
+ if property in dest_profile_properties:
+ dest_profile_remote_commands.extend(cmd)
+
+ if cmd is not None:
+ ctx = global_ctx
+ if property_ctx is not None:
+ ctx.extend(property_ctx)
+ add["TMS_GLOBAL"].extend(ctx)
+ add["TMS_GLOBAL"].extend(cmd)
+
+ add["TMS_GLOBAL"] = remove_duplicate_commands(add["TMS_GLOBAL"])
+ # If all destination profile commands are being removed then just
+ # remove the config context instead.
+ if len(dest_profile_remote_commands) == 3:
+ for item in dest_profile_remote_commands:
+ add["TMS_GLOBAL"].remove(item)
+ add["TMS_GLOBAL"].remove("destination-profile")
+ add["TMS_GLOBAL"].extend(["no destination-profile"])
+
+ # Process Telemetry destination_group, sensor_group and subscription Want and Have Values
+ # Possible states:
+ # - want (not set) have (set) (delete have)
+ # - want and have are (set) (equal: no action, not equal: replace with want)
+ # - want (set) have (not set) (add want)
+ # - want (not set) have (not set) (no action)
+ tms_resources = [
+ "TMS_DESTGROUP",
+ "TMS_SENSORGROUP",
+ "TMS_SUBSCRIPTION",
+ ]
+ for resource in tms_resources:
+ if resource == "TMS_DESTGROUP":
+ name = "destination-group"
+ cmd_property = "destination"
+ global_ctx = ref["tms_destgroup"]._ref["_template"]["context"]
+ setval = ref["tms_destgroup"]._ref["destination"]["setval"]
+ want_resources = massaged_want.get("destination_groups")
+ have_resources = massaged_have.get("destination_groups")
+ if resource == "TMS_SENSORGROUP":
+ name = "sensor-group"
+ global_ctx = ref["tms_sensorgroup"]._ref["_template"]["context"]
+ setval = {}
+ setval["data_source"] = ref["tms_sensorgroup"]._ref["data_source"]["setval"]
+ setval["path"] = ref["tms_sensorgroup"]._ref["path"]["setval"]
+ want_resources = massaged_want.get("sensor_groups")
+ have_resources = massaged_have.get("sensor_groups")
+ if resource == "TMS_SUBSCRIPTION":
+ name = "subscription"
+ global_ctx = ref["tms_subscription"]._ref["_template"]["context"]
+ setval = {}
+ setval["destination_group"] = ref["tms_subscription"]._ref["destination_group"][
+ "setval"
+ ]
+ setval["sensor_group"] = ref["tms_subscription"]._ref["sensor_group"]["setval"]
+ want_resources = massaged_want.get("subscriptions")
+ have_resources = massaged_have.get("subscriptions")
+
+ if not want_resources and have_resources:
+ # want not and have not set so delete have
+ for key in have_resources.keys():
+ remove_context = ["{0} {1} {2}".format("no", name, key)]
+ delete[resource].extend(global_ctx)
+ if remove_context[0] not in delete[resource]:
+ delete[resource].extend(remove_context)
+ else:
+ # want and have are set.
+ # process wants:
+ for want_key in want_resources.keys():
+ if want_key not in have_resources.keys():
+ # Want resource key not in have resource key so add it
+ property_ctx = ["{0} {1}".format(name, want_key)]
+ for item in want_resources[want_key]:
+ if resource == "TMS_DESTGROUP":
+ cmd = [setval.format(**item[cmd_property])]
+ add[resource].extend(global_ctx)
+ if property_ctx[0] not in add[resource]:
+ add[resource].extend(property_ctx)
+ add[resource].extend(cmd)
+ if resource == "TMS_SENSORGROUP":
+ cmd = {}
+ if item.get("data_source"):
+ cmd["data_source"] = [
+ setval["data_source"].format(item["data_source"]),
+ ]
+ if item.get("path"):
+ setval["path"] = get_setval_path(item.get("path"))
+ cmd["path"] = [setval["path"].format(**item["path"])]
+ add[resource].extend(global_ctx)
+ if property_ctx[0] not in add[resource]:
+ add[resource].extend(property_ctx)
+ if cmd.get("data_source"):
+ add[resource].extend(cmd["data_source"])
+ if cmd.get("path"):
+ add[resource].extend(cmd["path"])
+ if resource == "TMS_SUBSCRIPTION":
+ cmd = {}
+ if item.get("destination_group"):
+ cmd["destination_group"] = [
+ setval["destination_group"].format(
+ item["destination_group"],
+ ),
+ ]
+ if item.get("sensor_group"):
+ cmd["sensor_group"] = [
+ setval["sensor_group"].format(**item["sensor_group"]),
+ ]
+ add[resource].extend(global_ctx)
+ if property_ctx[0] not in add[resource]:
+ add[resource].extend(property_ctx)
+ if cmd.get("destination_group"):
+ add[resource].extend(cmd["destination_group"])
+ if cmd.get("sensor_group"):
+ add[resource].extend(cmd["sensor_group"])
+
+ elif want_key in have_resources.keys():
+ # Want resource key exists in have resource keys but we need to
+ # inspect the individual items under the resource key
+ # for differences
+ for item in want_resources[want_key]:
+ if item not in have_resources[want_key]:
+ if item is None:
+ continue
+ # item wanted but does not exist so add it
+ property_ctx = ["{0} {1}".format(name, want_key)]
+ if resource == "TMS_DESTGROUP":
+ cmd = [setval.format(**item[cmd_property])]
+ add[resource].extend(global_ctx)
+ if property_ctx[0] not in add[resource]:
+ add[resource].extend(property_ctx)
+ add[resource].extend(cmd)
+ if resource == "TMS_SENSORGROUP":
+ cmd = {}
+ if item.get("data_source"):
+ cmd["data_source"] = [
+ setval["data_source"].format(item["data_source"]),
+ ]
+ if item.get("path"):
+ setval["path"] = get_setval_path(item.get("path"))
+ cmd["path"] = [setval["path"].format(**item["path"])]
+ add[resource].extend(global_ctx)
+ if property_ctx[0] not in add[resource]:
+ add[resource].extend(property_ctx)
+ if cmd.get("data_source"):
+ add[resource].extend(cmd["data_source"])
+ if cmd.get("path"):
+ add[resource].extend(cmd["path"])
+ if resource == "TMS_SUBSCRIPTION":
+ cmd = {}
+ if item.get("destination_group"):
+ cmd["destination_group"] = [
+ setval["destination_group"].format(
+ item["destination_group"],
+ ),
+ ]
+ if item.get("sensor_group"):
+ cmd["sensor_group"] = [
+ setval["sensor_group"].format(**item["sensor_group"]),
+ ]
+ add[resource].extend(global_ctx)
+ if property_ctx[0] not in add[resource]:
+ add[resource].extend(property_ctx)
+ if cmd.get("destination_group"):
+ add[resource].extend(cmd["destination_group"])
+ if cmd.get("sensor_group"):
+ add[resource].extend(cmd["sensor_group"])
+
+ # process haves:
+ for have_key in have_resources.keys():
+ if have_key not in want_resources.keys():
+ # Want resource key is not in have resource keys so remove it
+ cmd = ["no " + "{0} {1}".format(name, have_key)]
+ delete[resource].extend(global_ctx)
+ delete[resource].extend(cmd)
+ elif have_key in want_resources.keys():
+ # Have resource key exists in want resource keys but we need to
+ # inspect the individual items under the resource key
+ # for differences
+ for item in have_resources[have_key]:
+ if item not in want_resources[have_key]:
+ if item is None:
+ continue
+ # have item not wanted so remove it
+ property_ctx = ["{0} {1}".format(name, have_key)]
+ if resource == "TMS_DESTGROUP":
+ cmd = ["no " + setval.format(**item[cmd_property])]
+ delete[resource].extend(global_ctx)
+ if property_ctx[0] not in delete[resource]:
+ delete[resource].extend(property_ctx)
+ delete[resource].extend(cmd)
+ if resource == "TMS_SENSORGROUP":
+ cmd = {}
+ if item.get("data_source"):
+ cmd["data_source"] = [
+ "no "
+ + setval["data_source"].format(item["data_source"]),
+ ]
+ if item.get("path"):
+ setval["path"] = get_setval_path(item.get("path"))
+ cmd["path"] = [
+ "no " + setval["path"].format(**item["path"]),
+ ]
+ delete[resource].extend(global_ctx)
+ if property_ctx[0] not in delete[resource]:
+ delete[resource].extend(property_ctx)
+ if cmd.get("data_source"):
+ delete[resource].extend(cmd["data_source"])
+ if cmd.get("path"):
+ delete[resource].extend(cmd["path"])
+ if resource == "TMS_SUBSCRIPTION":
+ cmd = {}
+ if item.get("destination_group"):
+ cmd["destination_group"] = [
+ "no "
+ + setval["destination_group"].format(
+ item["destination_group"],
+ ),
+ ]
+ if item.get("sensor_group"):
+ cmd["sensor_group"] = [
+ "no "
+ + setval["sensor_group"].format(**item["sensor_group"]),
+ ]
+ delete[resource].extend(global_ctx)
+ if property_ctx[0] not in delete[resource]:
+ delete[resource].extend(property_ctx)
+ if cmd.get("destination_group"):
+ delete[resource].extend(cmd["destination_group"])
+ if cmd.get("sensor_group"):
+ delete[resource].extend(cmd["sensor_group"])
+
+ add[resource] = remove_duplicate_context(add[resource])
+ delete[resource] = remove_duplicate_context(delete[resource])
+
+ commands.extend(delete["TMS_SUBSCRIPTION"])
+ commands.extend(delete["TMS_SENSORGROUP"])
+ commands.extend(delete["TMS_DESTGROUP"])
+ commands.extend(add["TMS_DESTGROUP"])
+ commands.extend(add["TMS_SENSORGROUP"])
+ commands.extend(add["TMS_SUBSCRIPTION"])
+ commands.extend(add["TMS_GLOBAL"])
+ commands = remove_duplicate_context(commands)
+
+ return commands
+
+ @staticmethod
+ def _state_merged(cmd_ref):
+ """The command generator when state is merged
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ commands = cmd_ref["TMS_GLOBAL"]["ref"][0].get_proposed()
+
+ if cmd_ref["TMS_DESTGROUP"].get("ref"):
+ for cr in cmd_ref["TMS_DESTGROUP"]["ref"]:
+ commands.extend(cr.get_proposed())
+
+ if cmd_ref["TMS_SENSORGROUP"].get("ref"):
+ for cr in cmd_ref["TMS_SENSORGROUP"]["ref"]:
+ commands.extend(cr.get_proposed())
+
+ if cmd_ref["TMS_SUBSCRIPTION"].get("ref"):
+ for cr in cmd_ref["TMS_SUBSCRIPTION"]["ref"]:
+ commands.extend(cr.get_proposed())
+
+ return remove_duplicate_context(commands)
+
+ @staticmethod
+ def _state_deleted(want, have):
+ """The command generator when state is deleted
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if want != have:
+ commands = ["no telemetry"]
+ return commands
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/vlans.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/vlans.py
new file mode 100644
index 00000000..515618ad
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/config/vlans/vlans.py
@@ -0,0 +1,334 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos_vlans class
+It is in this file where the current configuration (as dict)
+is compared to the provided configuration (as dict) and the command set
+necessary to bring the current configuration to it's desired end-state is
+created
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+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 (
+ dict_diff,
+ remove_empties,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ search_obj_in_list,
+)
+
+
+class Vlans(ConfigBase):
+ """
+ The nxos_vlans class
+ """
+
+ gather_subset = ["min"]
+
+ gather_network_resources = ["vlans"]
+
+ def __init__(self, module):
+ super(Vlans, self).__init__(module)
+
+ def get_platform(self):
+ """Wrapper method for getting platform info
+ This method exists solely to allow the unit test framework to mock calls.
+ """
+ return self.facts.get("ansible_net_platform", "")
+
+ def get_vlans_facts(self, data=None):
+ """Get the 'facts' (the current configuration)
+
+ :rtype: A dictionary
+ :returns: The current configuration as a dictionary
+ """
+ self.facts, _warnings = Facts(self._module).get_facts(
+ self.gather_subset,
+ self.gather_network_resources,
+ data=data,
+ )
+ vlans_facts = self.facts["ansible_network_resources"].get("vlans")
+ if not vlans_facts:
+ return []
+
+ return vlans_facts
+
+ def edit_config(self, commands):
+ """Wrapper method for `_connection.edit_config()`
+ This exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return self._connection.edit_config(commands)
+
+ def execute_module(self):
+ """Execute the module
+
+ :rtype: A dictionary
+ :returns: The result from module execution
+ """
+ result = {"changed": False}
+ commands = []
+ warnings = []
+
+ if self.state in self.ACTION_STATES:
+ existing_vlans_facts = self.get_vlans_facts()
+ self._platform = self.get_platform()
+ else:
+ existing_vlans_facts = []
+ self._platform = ""
+
+ if self.state in self.ACTION_STATES or self.state == "rendered":
+ commands.extend(self.set_config(existing_vlans_facts))
+
+ if commands and self.state in self.ACTION_STATES:
+ if not self._module.check_mode:
+ self.edit_config(commands)
+ result["changed"] = True
+
+ if self.state in self.ACTION_STATES:
+ result["commands"] = commands
+
+ if self.state in self.ACTION_STATES or self.state == "gathered":
+ changed_vlans_facts = self.get_vlans_facts()
+
+ elif self.state == "rendered":
+ result["rendered"] = commands
+
+ elif self.state == "parsed":
+ running_config = self._module.params["running_config"]
+ if not running_config:
+ self._module.fail_json(
+ msg="value of running_config parameter must not be empty for state parsed",
+ )
+ result["parsed"] = self.get_vlans_facts(data=running_config)
+
+ if self.state in self.ACTION_STATES:
+ result["before"] = existing_vlans_facts
+ if result["changed"]:
+ result["after"] = changed_vlans_facts
+
+ elif self.state == "gathered":
+ result["gathered"] = changed_vlans_facts
+
+ result["warnings"] = warnings
+ return result
+
+ def set_config(self, existing_vlans_facts):
+ """Collect the configuration from the args passed to the module,
+ collect the current configuration (as a dict from facts)
+
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ want = self._module.params.get("config") or []
+ have = existing_vlans_facts
+ resp = self.set_state(self._sanitize(want), self._sanitize(have))
+ return to_list(resp)
+
+ def set_state(self, want, have):
+ """Select the appropriate function based on the state provided
+
+ :param want: the desired configuration as a dictionary
+ :param have: the current configuration as a dictionary
+ :rtype: A list
+ :returns: the commands necessary to migrate the current configuration
+ to the desired configuration
+ """
+ state = self._module.params["state"]
+ if state in ("overridden", "merged", "replaced", "rendered") and not want:
+ self._module.fail_json(
+ msg="value of config parameter must not be empty for state {0}".format(state),
+ )
+
+ commands = list()
+ if state == "overridden":
+ commands.extend(self._state_overridden(want, have))
+ elif state == "deleted":
+ commands.extend(self._state_deleted(want, have))
+ else:
+ for w in want:
+ if state in ["merged", "rendered"]:
+ commands.extend(self._state_merged(w, have))
+ elif state == "replaced":
+ commands.extend(self._state_replaced(w, have))
+
+ return commands
+
+ def remove_default_states(self, obj):
+ """Removes non-empty but default states from the obj."""
+ default_states = {"enabled": True, "state": "active", "mode": "ce"}
+ for k in default_states.keys():
+ if obj.get(k) == default_states[k]:
+ obj.pop(k, None)
+ return obj
+
+ def _state_replaced(self, want, have):
+ """The command generator when state is replaced.
+ Scope is limited to vlan objects defined in the playbook.
+ :rtype: A list
+ :returns: The minimum command set required to migrate the current
+ configuration to the desired configuration.
+ """
+ obj_in_have = search_obj_in_list(want["vlan_id"], have, "vlan_id")
+ if obj_in_have:
+ # Diff the want and have
+ diff = dict_diff(want, obj_in_have)
+ # Remove merge items from diff; what's left will be used to
+ # remove states not specified in the playbook
+ for k in dict(set(want.items()) - set(obj_in_have.items())).keys():
+ diff.pop(k, None)
+ else:
+ diff = want
+
+ # Remove default states from resulting diff
+ diff = self.remove_default_states(diff)
+
+ # merged_cmds: 'want' cmds to update 'have' states that don't match
+ # replaced_cmds: remaining 'have' cmds that need to be reset to default
+ merged_cmds = self.set_commands(want, have)
+ replaced_cmds = []
+ if obj_in_have:
+ # Remaining diff items are used to reset states to default
+ replaced_cmds = self.del_attribs(diff)
+ cmds = []
+ if replaced_cmds or merged_cmds:
+ cmds += ["vlan %s" % str(want["vlan_id"])]
+ cmds += merged_cmds + replaced_cmds
+ return cmds
+
+ def _state_overridden(self, want, have):
+ """The command generator when state is overridden.
+ Scope includes all vlan objects on the device.
+ :rtype: A list
+ :returns: the minimum command set required to migrate the current
+ configuration to the desired configuration.
+ """
+ # overridden behavior is the same as replaced except for scope.
+ cmds = []
+ existing_vlans = []
+ for h in have:
+ existing_vlans.append(h["vlan_id"])
+ obj_in_want = search_obj_in_list(h["vlan_id"], want, "vlan_id")
+ if obj_in_want:
+ if h != obj_in_want:
+ replaced_cmds = self._state_replaced(obj_in_want, [h])
+ if replaced_cmds:
+ cmds.extend(replaced_cmds)
+ else:
+ cmds.append("no vlan %s" % h["vlan_id"])
+
+ # Add wanted vlans that don't exist on the device yet
+ for w in want:
+ if w["vlan_id"] not in existing_vlans:
+ new_vlan = ["vlan %s" % w["vlan_id"]]
+ cmds.extend(new_vlan + self.add_commands(w))
+ return cmds
+
+ def _state_merged(self, w, have):
+ """The command generator when state is merged
+
+ :rtype: A list
+ :returns: the commands necessary to merge the provided into
+ the current configuration
+ """
+ cmds = self.set_commands(w, have)
+ if cmds:
+ cmds.insert(0, "vlan %s" % str(w["vlan_id"]))
+ return cmds
+
+ def _state_deleted(self, want, have):
+ """The command generator when state is deleted
+
+ :rtype: A list
+ :returns: the commands necessary to remove the current configuration
+ of the provided objects
+ """
+ commands = []
+ if want:
+ for w in want:
+ obj_in_have = search_obj_in_list(w["vlan_id"], have, "vlan_id")
+ if obj_in_have:
+ commands.append("no vlan " + str(obj_in_have["vlan_id"]))
+ else:
+ if not have:
+ return commands
+ for h in have:
+ commands.append("no vlan " + str(h["vlan_id"]))
+ return commands
+
+ def del_attribs(self, obj):
+ """Returns a list of commands to reset states to default"""
+ commands = []
+ if not obj:
+ return commands
+ default_cmds = {
+ "name": "no name",
+ "state": "no state",
+ "enabled": "no shutdown",
+ "mode": "mode ce",
+ "mapped_vni": "no vn-segment",
+ }
+ for k in obj:
+ commands.append(default_cmds[k])
+ return commands
+
+ def diff_of_dicts(self, w, obj):
+ diff = set(w.items()) - set(obj.items())
+ diff = dict(diff)
+ if diff and w["vlan_id"] == obj["vlan_id"]:
+ diff.update({"vlan_id": w["vlan_id"]})
+ return diff
+
+ def add_commands(self, d):
+ commands = []
+ if not d:
+ return commands
+ if "name" in d:
+ commands.append("name " + d["name"])
+ if "state" in d:
+ commands.append("state " + d["state"])
+ if "enabled" in d:
+ if d["enabled"] is True:
+ commands.append("no shutdown")
+ else:
+ commands.append("shutdown")
+ if "mode" in d:
+ commands.append("mode " + d["mode"])
+ if "mapped_vni" in d:
+ commands.append("vn-segment %s" % d["mapped_vni"])
+
+ return commands
+
+ def set_commands(self, w, have):
+ commands = []
+ obj_in_have = search_obj_in_list(w["vlan_id"], have, "vlan_id")
+ if not obj_in_have:
+ commands = self.add_commands(w)
+ else:
+ diff = self.diff_of_dicts(w, obj_in_have)
+ commands = self.add_commands(diff)
+ return commands
+
+ def _sanitize(self, vlans):
+ sanitized_vlans = []
+ for vlan in vlans:
+ if not re.search("N[567][7K]", self._platform):
+ if "mode" in vlan:
+ del vlan["mode"]
+ sanitized_vlans.append(remove_empties(vlan))
+ return sanitized_vlans
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/acl_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/acl_interfaces.py
new file mode 100644
index 00000000..053b56a9
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acl_interfaces/acl_interfaces.py
@@ -0,0 +1,129 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The nxos acl_interfaces fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acl_interfaces.acl_interfaces import (
+ Acl_interfacesArgs,
+)
+
+
+class Acl_interfacesFacts(object):
+ """The nxos acl_interfaces fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Acl_interfacesArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def get_device_data(self, connection):
+ return connection.get("show running-config | section ^interface")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for acl_interfaces
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ data = self.get_device_data(connection)
+ data = data.split("interface")
+
+ resources = []
+ for i in range(len(data)):
+ intf = data[i].split("\n")
+ for l in range(1, len(intf)):
+ if not re.search("ip(v6)?( port)? (access-group|traffic-filter)", intf[l]):
+ intf[l] = ""
+ intf = list(filter(None, intf))
+ resources.append(intf)
+
+ objs = []
+ for resource in resources:
+ if resource:
+ obj = self.render_config(self.generated_spec, resource)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts["ansible_network_resources"].pop("acl_interfaces", None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ params = utils.remove_empties(params)
+ facts["acl_interfaces"] = params["config"]
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+ config["name"] = conf[0].strip()
+ config["access_groups"] = []
+ v4 = {"afi": "ipv4", "acls": []}
+ v6 = {"afi": "ipv6", "acls": []}
+ for c in conf[1:]:
+ if c:
+ acl4 = re.search(
+ r"ip(?P<port>\sport)?\saccess-group\s(?P<name>\S+)\s(?P<dir>in|out)",
+ c,
+ )
+ acl6 = re.search(
+ r"ipv6(?P<port>\sport)?\straffic-filter\s(?P<name>\S+)\s(?P<dir>in|out)",
+ c,
+ )
+ if acl4:
+ v4["acls"].append(self._parse(acl4))
+ elif acl6:
+ v6["acls"].append(self._parse(acl6))
+
+ if len(v4["acls"]) > 0:
+ config["access_groups"].append(v4)
+ if len(v6["acls"]) > 0:
+ config["access_groups"].append(v6)
+
+ return utils.remove_empties(config)
+
+ def _parse(self, data):
+ return {
+ "name": data.group("name").strip(),
+ "direction": data.group("dir").strip(),
+ "port": True if data.group("port") else None,
+ }
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/acls.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/acls.py
new file mode 100644
index 00000000..70ebfcdd
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/acls/acls.py
@@ -0,0 +1,327 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos acls fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acls.acls import (
+ AclsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ validate_ipv4_addr,
+ validate_ipv6_addr,
+)
+
+
+class AclsFacts(object):
+ """The nxos acls fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = AclsArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def get_device_data(self, connection):
+ data = connection.get("show running-config | section 'ip(v6)* access-list'")
+ if data == "{}":
+ return ""
+ return data
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for acls
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ data = self.get_device_data(connection)
+ data = re.split("\nip", data)
+ v6 = []
+ v4 = []
+
+ for i in range(len(data)):
+ if str(data[i]):
+ if "v6" in str(data[i]).split()[0]:
+ v6.append(data[i])
+ else:
+ v4.append(data[i])
+
+ resources = []
+ resources.append(v6)
+ resources.append(v4)
+ objs = []
+ for resource in resources:
+ if resource:
+ obj = self.render_config(self.generated_spec, resource)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts["ansible_network_resources"].pop("acls", None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ params = utils.remove_empties(params)
+ facts["acls"] = params["config"]
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def get_endpoint(self, ace, pro):
+ ret_dict = {}
+ option = ace.split()[0]
+ if option == "any":
+ ret_dict.update({"any": True})
+ else:
+ # it could be a.b.c.d or a.b.c.d/x or a.b.c.d/32
+ if "/" in option: # or 'host' in option:
+ prefix = re.search(r"(.*)/(\d+)", option)
+ ip = prefix.group(1)
+ cidr = prefix.group(2)
+ if (validate_ipv4_addr(option) and int(cidr) == 32) or (
+ validate_ipv6_addr(option) and int(cidr) == 128
+ ):
+ ret_dict.update({"host": ip})
+ else:
+ ret_dict.update({"prefix": option})
+ else:
+ ret_dict.update({"address": option})
+ wb = ace.split()[1]
+ ret_dict.update({"wildcard_bits": wb})
+ ace = re.sub("{0}".format(wb), "", ace, 1)
+ ace = re.sub(option, "", ace, 1)
+ if pro in ["tcp", "udp"]:
+ keywords = ["eq", "lt", "gt", "neq", "range"]
+ if len(ace.split()) and ace.split()[0] in keywords:
+ port_protocol = {}
+ port_pro = re.search(r"(eq|lt|gt|neq) (\S+)", ace)
+ if port_pro:
+ port_protocol.update({port_pro.group(1): port_pro.group(2)})
+ ace = re.sub(port_pro.group(1), "", ace, 1)
+ ace = re.sub(port_pro.group(2), "", ace, 1)
+ else:
+ limit = re.search(r"(range) (\w*) (\w*)", ace)
+ if limit:
+ port_protocol.update(
+ {
+ "range": {
+ "start": limit.group(2),
+ "end": limit.group(3),
+ },
+ },
+ )
+ ace = re.sub(limit.group(2), "", ace, 1)
+ ace = re.sub(limit.group(3), "", ace, 1)
+ if port_protocol:
+ ret_dict.update({"port_protocol": port_protocol})
+ return ace, ret_dict
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+ protocol_options = {
+ "tcp": ["fin", "established", "psh", "rst", "syn", "urg", "ack"],
+ "icmp": [
+ "administratively_prohibited",
+ "alternate_address",
+ "conversion_error",
+ "dod_host_prohibited",
+ "dod_net_prohibited",
+ "echo_request",
+ "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",
+ "unreachable",
+ "reassembly_timeout",
+ "redirect",
+ "router_advertisement",
+ "router_solicitation",
+ "source_quench",
+ "source_route_failed",
+ "time_exceeded",
+ "timestamp_reply",
+ "timestamp_request",
+ "traceroute",
+ "ttl_exceeded",
+ ],
+ "icmpv6": [
+ "beyond_scope",
+ "destination_unreachable",
+ "echo_reply",
+ "echo_request",
+ "fragments",
+ "header",
+ "hop_limit",
+ "mld_query",
+ "mld_reduction",
+ "mld_report",
+ "mldv2",
+ "nd_na",
+ "nd_ns",
+ "next_header",
+ "no_admin",
+ "no_route",
+ "packet_too_big",
+ "parameter_option",
+ "parameter_problem",
+ "port_unreachable",
+ "reassembly_timeout",
+ "renum_command",
+ "renum_result",
+ "renum_seq_number",
+ "router_advertisement",
+ "router_renumbering",
+ "router_solicitation",
+ "time_exceeded",
+ "unreachable",
+ "telemetry_path",
+ "telemetry_queue",
+ ],
+ "igmp": ["dvmrp", "host_query", "host_report"],
+ }
+ if conf:
+ if "v6" in conf[0].split()[0]:
+ config["afi"] = "ipv6"
+ else:
+ config["afi"] = "ipv4"
+ config["acls"] = []
+ for acl in conf:
+ acls = {}
+ if "match-local-traffic" in acl:
+ config["match_local_traffic"] = True
+ continue
+ acl = acl.split("\n")
+ acl = [a.strip() for a in acl]
+ acl = list(filter(None, acl))
+ acls["name"] = re.match(r"(ip)?(v6)?\s?access-list (.*)", acl[0]).group(3)
+ acls["aces"] = []
+ for ace in list(filter(None, acl[1:])):
+ if re.search(r"^ip(.*)access-list.*", ace):
+ break
+ ace = ace.strip()
+ seq = re.match(r"(\d+)", ace)
+ rem = ""
+ entry = {}
+ if seq:
+ seq = seq.group(0)
+ entry.update({"sequence": seq})
+ ace = re.sub(seq, "", ace, 1)
+ grant = ace.split()[0]
+ if grant != "remark":
+ entry.update({"grant": grant})
+ else:
+ rem = re.match(".*remark (.*)", ace).group(1)
+ entry.update({"remark": rem})
+
+ if not rem and seq:
+ ace = re.sub(grant, "", ace, 1)
+
+ pro = ace.split()[0]
+ if pro == "icmp" and config["afi"] == "ipv6":
+ entry.update({"protocol": "icmpv6"})
+ else:
+ entry.update({"protocol": pro})
+
+ ace = re.sub(pro, "", ace, 1)
+ ace, source = self.get_endpoint(ace, pro)
+ entry.update({"source": source})
+ ace, dest = self.get_endpoint(ace, pro)
+ entry.update({"destination": dest})
+
+ dscp = re.search(r"dscp (\w*)", ace)
+ if dscp:
+ entry.update({"dscp": dscp.group(1)})
+
+ frag = re.search(r"fragments", ace)
+ if frag:
+ entry.update({"fragments": True})
+
+ prec = re.search(r"precedence (\w*)", ace)
+ if prec:
+ entry.update({"precedence": prec.group(1)})
+
+ log = re.search("log", ace)
+ if log:
+ entry.update({"log": True})
+
+ pro = entry.get("protocol", "")
+ if pro in ["tcp", "icmp", "icmpv6", "igmp"]:
+ pro_options = {}
+ options = {}
+ for option in protocol_options[pro]:
+ if option not in ["telemetry_path", "telemetry_queue"]:
+ option = re.sub("_", "-", option)
+ if option in ace:
+ if option == "echo" and (
+ "echo_request" in options or "echo_reply" in options
+ ):
+ continue
+ elif option == "unreachable" and "port_unreachable" in options:
+ continue
+ option = re.sub("-", "_", option)
+ options.update({option: True})
+ if options:
+ pro_options.update({pro: options})
+ if pro_options:
+ entry.update({"protocol_options": pro_options})
+ if entry:
+ acls["aces"].append(entry)
+ config["acls"].append(acls)
+ return utils.remove_empties(config)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/bfd_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/bfd_interfaces.py
new file mode 100644
index 00000000..9d7cceac
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bfd_interfaces/bfd_interfaces.py
@@ -0,0 +1,104 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# 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 nxos bfd_interfaces fact class
+Populate the facts tree based on the current device configuration.
+"""
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bfd_interfaces.bfd_interfaces import (
+ Bfd_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ get_interface_type,
+)
+
+
+class Bfd_interfacesFacts(object):
+ """The nxos_bfd_interfaces fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Bfd_interfacesArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for bfd_interfaces
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+
+ if not data:
+ data = connection.get("show running-config | section '^interface|^feature bfd'")
+
+ # Some of the bfd attributes
+ if "feature bfd" in data.split("\n"):
+ resources = data.split("interface ")
+ resources.pop(0)
+ else:
+ resources = []
+ for resource in resources:
+ if resource:
+ obj = self.render_config(self.generated_spec, resource)
+ if obj and len(obj.keys()) > 1:
+ objs.append(obj)
+
+ ansible_facts["ansible_network_resources"].pop("bfd_interfaces", None)
+ facts = {}
+ if objs:
+ facts["bfd_interfaces"] = []
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ for cfg in params["config"]:
+ facts["bfd_interfaces"].append(utils.remove_empties(cfg))
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+
+ match = re.search(r"^(\S+)", conf)
+ intf = match.group(1)
+ if get_interface_type(intf) == "unknown":
+ return {}
+ config["name"] = intf
+ # 'bfd'/'bfd echo' do not nvgen when enabled thus set to 'enable' when None.
+ # 'bfd' is not supported on some platforms
+ config["bfd"] = utils.parse_conf_cmd_arg(conf, "bfd", "enable", "disable") or "enable"
+ config["echo"] = utils.parse_conf_cmd_arg(conf, "bfd echo", "enable", "disable") or "enable"
+
+ return utils.remove_empties(config)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/bgp_address_family.py
new file mode 100644
index 00000000..a1b0bc22
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_address_family/bgp_address_family.py
@@ -0,0 +1,142 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos bgp_address_family 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.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_address_family.bgp_address_family import (
+ Bgp_address_familyArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_address_family import (
+ Bgp_address_familyTemplate,
+)
+
+
+class Bgp_address_familyFacts(object):
+ """The nxos bgp_address_family facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Bgp_address_familyArgs.argument_spec
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config | section '^router bgp'")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Bgp_address_family network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+ objs = []
+
+ if not data:
+ data = self.get_config(connection)
+
+ data = self._flatten_config(data)
+
+ # parse native config using the Bgp_address_family template
+ bgp_address_family_parser = Bgp_address_familyTemplate(lines=data.splitlines())
+ objs = bgp_address_family_parser.parse()
+ if objs:
+ nbr = []
+ if "address_family" in objs:
+ # remove neighbor AF entries
+ for k, v in iteritems(objs["address_family"]):
+ if not k.startswith("nbr_"):
+ nbr.append(k)
+ for x in nbr:
+ del objs["address_family"][x]
+
+ objs["address_family"] = list(objs["address_family"].values())
+ # sort list of dictionaries
+ for x in objs["address_family"]:
+ if "aggregate_address" in x:
+ x["aggregate_address"] = sorted(
+ x["aggregate_address"],
+ key=lambda k, s="prefix": k[s],
+ )
+ if "networks" in x:
+ x["networks"] = sorted(x["networks"], key=lambda k, s="prefix": k[s])
+ if "redistribute" in x:
+ x["redistribute"] = sorted(
+ x["redistribute"],
+ key=lambda k: (k.get("id", -1), k["protocol"]),
+ )
+ objs["address_family"] = sorted(
+ objs["address_family"],
+ key=lambda k: (
+ k.get("afi", ""),
+ k.get("safi", ""),
+ k.get("vrf", ""),
+ ),
+ )
+
+ ansible_facts["ansible_network_resources"].pop("bgp_address_family", None)
+
+ params = utils.remove_empties(utils.validate_config(self.argument_spec, {"config": objs}))
+
+ facts["bgp_address_family"] = params.get("config", {})
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
+
+ def _flatten_config(self, data):
+ """Flatten contexts in the BGP
+ running-config for easier parsing.
+ :param obj: dict
+ :returns: flattened running config
+ """
+ data = data.split("\n")
+ in_vrf_cxt = False
+ in_nbr_cxt = False
+ cur_vrf = {}
+
+ for x in data:
+ cur_indent = len(x) - len(x.lstrip())
+ if x.strip().startswith("vrf"):
+ in_vrf_cxt = True
+ in_nbr_cxt = False
+ cur_vrf["vrf"] = x
+ cur_vrf["indent"] = cur_indent
+ elif cur_vrf and (cur_indent <= cur_vrf["indent"]):
+ in_vrf_cxt = False
+ elif x.strip().startswith("neighbor"):
+ # we entered a neighbor context which
+ # also has address-family lines
+ in_nbr_cxt = True
+ nbr = x
+ elif x.strip().startswith("address-family"):
+ if in_vrf_cxt or in_nbr_cxt:
+ prepend = ""
+ if in_vrf_cxt:
+ prepend += cur_vrf["vrf"]
+ if in_nbr_cxt:
+ if in_vrf_cxt:
+ nbr = " " + nbr.strip()
+ prepend += nbr
+ data[data.index(x)] = prepend + " " + x.strip()
+
+ return "\n".join(data)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/bgp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/bgp_global.py
new file mode 100644
index 00000000..621e499f
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_global/bgp_global.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos bgp_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.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_global.bgp_global import (
+ Bgp_globalArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_global import (
+ Bgp_globalTemplate,
+)
+
+
+class Bgp_globalFacts(object):
+ """The nxos bgp_global facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Bgp_globalArgs.argument_spec
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config | section '^router bgp'")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Bgp_global network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+
+ if not data:
+ data = self.get_config(connection)
+
+ data = self._flatten_config(data)
+
+ # parse native config using the Bgp_global template
+ bgp_global_parser = Bgp_globalTemplate(lines=data.splitlines(), module=self._module)
+ obj = bgp_global_parser.parse()
+
+ vrfs = obj.get("vrfs", {})
+
+ # move global vals to their correct position in facts tree
+ # this is only needed for keys that are valid for both global
+ # and VRF contexts
+ global_vals = vrfs.pop("vrf_", {})
+ for key, value in iteritems(global_vals):
+ obj[key] = value
+
+ # transform vrfs into a list
+ if vrfs:
+ obj["vrfs"] = sorted(list(obj["vrfs"].values()), key=lambda k, sk="vrf": k[sk])
+ for vrf in obj["vrfs"]:
+ self._post_parse(vrf)
+
+ self._post_parse(obj)
+
+ obj = utils.remove_empties(obj)
+
+ 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),
+ )
+
+ facts["bgp_global"] = params.get("config", {})
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
+
+ def _flatten_config(self, data):
+ """Flatten neighbor contexts in
+ the running-config for easier parsing.
+ :param obj: dict
+ :returns: flattened running config
+ """
+ data = data.split("\n")
+ in_nbr_cxt = False
+ cur_nbr = {}
+
+ for x in data:
+ cur_indent = len(x) - len(x.lstrip())
+ if x.strip().startswith("neighbor"):
+ in_nbr_cxt = True
+ cur_nbr["nbr"] = x
+ cur_nbr["indent"] = cur_indent
+ elif cur_nbr and (cur_indent <= cur_nbr["indent"]):
+ in_nbr_cxt = False
+ elif in_nbr_cxt:
+ data[data.index(x)] = cur_nbr["nbr"] + " " + x.strip()
+
+ return "\n".join(data)
+
+ def _post_parse(self, obj):
+ """Converts the intermediate data structure
+ to valid format as per argspec.
+ :param obj: dict
+ """
+ conf_peers = obj.get("confederation", {}).get("peers")
+ if conf_peers:
+ obj["confederation"]["peers"] = conf_peers.split()
+ obj["confederation"]["peers"].sort()
+
+ neighbors = obj.get("neighbors", {})
+ if neighbors:
+ obj["neighbors"] = sorted(
+ list(neighbors.values()),
+ key=lambda k, sk="neighbor_address": k[sk],
+ )
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/bgp_neighbor_address_family.py
new file mode 100644
index 00000000..e26c1826
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/bgp_neighbor_address_family/bgp_neighbor_address_family.py
@@ -0,0 +1,133 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos bgp_neighbor_address_family 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.nxos.plugins.module_utils.network.nxos.argspec.bgp_neighbor_address_family.bgp_neighbor_address_family import (
+ Bgp_neighbor_address_familyArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.bgp_neighbor_address_family import (
+ Bgp_neighbor_address_familyTemplate,
+)
+
+
+class Bgp_neighbor_address_familyFacts(object):
+ """The nxos bgp_neighbor_address_family facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Bgp_neighbor_address_familyArgs.argument_spec
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config | section '^router bgp'")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Bgp_neighbor_address_family network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+ objs = {}
+
+ if not data:
+ data = self.get_config(connection)
+
+ data = self._flatten_config(data)
+
+ # parse native config using the Bgp_neighbor_address_family template
+ bgp_neighbor_address_family_parser = Bgp_neighbor_address_familyTemplate(lines=data)
+ objs = bgp_neighbor_address_family_parser.parse()
+
+ if objs:
+ top_lvl_nbrs = objs.get("vrfs", {}).pop("vrf_", {})
+ objs["neighbors"] = self._post_parse(top_lvl_nbrs).get("neighbors", [])
+
+ if "vrfs" in objs:
+ for vrf in objs["vrfs"].values():
+ vrf["neighbors"] = self._post_parse(vrf)["neighbors"]
+ objs["vrfs"] = list(objs["vrfs"].values())
+
+ ansible_facts["ansible_network_resources"].pop("bgp_neighbor_address_family", None)
+
+ params = utils.remove_empties(utils.validate_config(self.argument_spec, {"config": objs}))
+
+ facts["bgp_neighbor_address_family"] = params.get("config", {})
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
+
+ def _post_parse(self, data):
+ if "neighbors" in data:
+ data["neighbors"] = sorted(
+ list(data["neighbors"].values()),
+ key=lambda k, s="neighbor_address": k[s],
+ )
+ for nbr in data["neighbors"]:
+ nbr["address_family"] = sorted(
+ list(nbr["address_family"].values()),
+ key=lambda k: (k["afi"], k.get("safi", "")),
+ )
+ return data
+
+ def _flatten_config(self, data):
+ """Flatten contexts in the BGP
+ running-config for easier parsing.
+ Only neighbor AF contexts are returned.
+ :param data: str
+ :returns: flattened running config
+ """
+ data = data.split("\n")
+ nbr_af_cxt = []
+ context = ""
+ cur_vrf = ""
+ cur_nbr_indent = None
+ in_nbr_cxt = False
+ in_af = False
+
+ # this is the "router bgp <asn>" line
+ nbr_af_cxt.append(data[0])
+ for x in data:
+ cur_indent = len(x) - len(x.lstrip())
+ x = x.strip()
+ if x.startswith("vrf"):
+ cur_vrf = x + " "
+ in_nbr_cxt = False
+ elif x.startswith("neighbor"):
+ in_nbr_cxt = True
+ in_af = False
+ cur_nbr_indent = cur_indent
+ context = x
+ if cur_vrf:
+ context = cur_vrf + context
+ elif in_nbr_cxt and cur_indent > cur_nbr_indent:
+ if x.startswith("address-family"):
+ in_af = True
+ x = context + " " + x
+ if in_af:
+ nbr_af_cxt.append(x)
+ else:
+ in_nbr_cxt = False
+
+ return nbr_af_cxt
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/facts.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/facts.py
new file mode 100644
index 00000000..b70cb590
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/facts.py
@@ -0,0 +1,188 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+"""
+The facts class for nxos
+this file validates each subset of facts and selectively
+calls the appropriate facts gathering function
+"""
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import (
+ FactsBase,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.acl_interfaces.acl_interfaces import (
+ Acl_interfacesFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.acls.acls import (
+ AclsFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.bfd_interfaces.bfd_interfaces import (
+ Bfd_interfacesFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.bgp_address_family.bgp_address_family import (
+ Bgp_address_familyFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.bgp_global.bgp_global import (
+ Bgp_globalFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.bgp_neighbor_address_family.bgp_neighbor_address_family import (
+ Bgp_neighbor_address_familyFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.hostname.hostname import (
+ HostnameFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.hsrp_interfaces.hsrp_interfaces import (
+ Hsrp_interfacesFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.interfaces.interfaces import (
+ InterfacesFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.l2_interfaces.l2_interfaces import (
+ L2_interfacesFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.l3_interfaces.l3_interfaces import (
+ L3_interfacesFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.lacp.lacp import (
+ LacpFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.lacp_interfaces.lacp_interfaces import (
+ Lacp_interfacesFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.lag_interfaces.lag_interfaces import (
+ Lag_interfacesFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.legacy.base import (
+ Config,
+ Default,
+ Features,
+ Hardware,
+ Interfaces,
+ Legacy,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.lldp_global.lldp_global import (
+ Lldp_globalFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.lldp_interfaces.lldp_interfaces import (
+ Lldp_interfacesFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.logging_global.logging_global import (
+ Logging_globalFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.ntp_global.ntp_global import (
+ Ntp_globalFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.ospf_interfaces.ospf_interfaces import (
+ Ospf_interfacesFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.ospfv2.ospfv2 import (
+ Ospfv2Facts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.ospfv3.ospfv3 import (
+ Ospfv3Facts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.prefix_lists.prefix_lists import (
+ Prefix_listsFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.route_maps.route_maps import (
+ Route_mapsFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.snmp_server.snmp_server import (
+ Snmp_serverFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.static_routes.static_routes import (
+ Static_routesFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.telemetry.telemetry import (
+ TelemetryFacts,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.vlans.vlans import (
+ VlansFacts,
+)
+
+
+FACT_LEGACY_SUBSETS = dict(
+ default=Default,
+ legacy=Legacy,
+ hardware=Hardware,
+ interfaces=Interfaces,
+ config=Config,
+ features=Features,
+)
+NX_FACT_RESOURCE_SUBSETS = dict(
+ bfd_interfaces=Bfd_interfacesFacts,
+ hsrp_interfaces=Hsrp_interfacesFacts,
+ lag_interfaces=Lag_interfacesFacts,
+ lldp_global=Lldp_globalFacts,
+ telemetry=TelemetryFacts,
+ vlans=VlansFacts,
+ lacp=LacpFacts,
+ lacp_interfaces=Lacp_interfacesFacts,
+ interfaces=InterfacesFacts,
+ l3_interfaces=L3_interfacesFacts,
+ l2_interfaces=L2_interfacesFacts,
+ lldp_interfaces=Lldp_interfacesFacts,
+ acl_interfaces=Acl_interfacesFacts,
+ acls=AclsFacts,
+ static_routes=Static_routesFacts,
+ ospfv2=Ospfv2Facts,
+ ospfv3=Ospfv3Facts,
+ ospf_interfaces=Ospf_interfacesFacts,
+ bgp_global=Bgp_globalFacts,
+ bgp_address_family=Bgp_address_familyFacts,
+ bgp_neighbor_address_family=Bgp_neighbor_address_familyFacts,
+ route_maps=Route_mapsFacts,
+ prefix_lists=Prefix_listsFacts,
+ logging_global=Logging_globalFacts,
+ ntp_global=Ntp_globalFacts,
+ snmp_server=Snmp_serverFacts,
+ hostname=HostnameFacts,
+)
+MDS_FACT_RESOURCE_SUBSETS = dict(
+ logging_global=Logging_globalFacts,
+ ntp_global=Ntp_globalFacts,
+ snmp_server=Snmp_serverFacts,
+)
+
+
+class Facts(FactsBase):
+ """The fact class for nxos"""
+
+ VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys())
+
+ def __init__(self, module, chassis_type="nexus"):
+ super(Facts, self).__init__(module)
+ self.chassis_type = chassis_type
+
+ def get_resource_subsets(self):
+ """Return facts resource subsets based on
+ target device model.
+ """
+ facts_resource_subsets = NX_FACT_RESOURCE_SUBSETS
+ if self.chassis_type == "mds":
+ facts_resource_subsets = MDS_FACT_RESOURCE_SUBSETS
+ return facts_resource_subsets
+
+ def get_facts(self, legacy_facts_type=None, resource_facts_type=None, data=None):
+ """Collect the facts for nxos
+ :param legacy_facts_type: List of legacy facts types
+ :param resource_facts_type: List of resource fact types
+ :param data: previously collected conf
+ :rtype: dict
+ :return: the facts gathered
+ """
+ VALID_RESOURCE_SUBSETS = self.get_resource_subsets()
+
+ if frozenset(VALID_RESOURCE_SUBSETS.keys()):
+ self.get_network_resources_facts(VALID_RESOURCE_SUBSETS, resource_facts_type, data)
+
+ if self.VALID_LEGACY_GATHER_SUBSETS:
+ self.get_network_legacy_facts(FACT_LEGACY_SUBSETS, legacy_facts_type)
+
+ return self.ansible_facts, self._warnings
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/hostname.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/hostname.py
new file mode 100644
index 00000000..981648c0
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hostname/hostname.py
@@ -0,0 +1,70 @@
+# -*- coding: utf-8 -*-
+# Copyright 2022 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 nxos hostname 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.nxos.plugins.module_utils.network.nxos.argspec.hostname.hostname import (
+ HostnameArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.hostname import (
+ HostnameTemplate,
+)
+
+
+class HostnameFacts(object):
+ """The nxos hostname facts class"""
+
+ def __init__(self, module):
+ self._module = module
+ self.argument_spec = HostnameArgs.argument_spec
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config | section ^hostname")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Hostname network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+ objs = []
+
+ if not data:
+ data = self.get_config(connection)
+
+ # parse native config using the Hostname template
+ hostname_parser = HostnameTemplate(lines=data.splitlines(), module=self._module)
+ objs = hostname_parser.parse()
+
+ ansible_facts["ansible_network_resources"].pop("hostname", None)
+
+ params = utils.remove_empties(
+ hostname_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
+ )
+
+ facts["hostname"] = params.get("config", {})
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/hsrp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/hsrp_interfaces.py
new file mode 100644
index 00000000..d12e3223
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/hsrp_interfaces/hsrp_interfaces.py
@@ -0,0 +1,96 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# 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 nxos hsrp_interfaces fact class
+Populate the facts tree based on the current device configuration.
+"""
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.hsrp_interfaces.hsrp_interfaces import (
+ Hsrp_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ get_interface_type,
+)
+
+
+class Hsrp_interfacesFacts(object):
+ """The nxos hsrp_interfaces fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Hsrp_interfacesArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for hsrp_interfaces
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+
+ if not data:
+ data = connection.get("show running-config | section ^interface")
+
+ resources = data.split("interface ")
+ for resource in resources:
+ if resource:
+ obj = self.render_config(self.generated_spec, resource)
+ if obj and len(obj.keys()) > 1:
+ objs.append(obj)
+
+ ansible_facts["ansible_network_resources"].pop("hsrp_interfaces", None)
+ facts = {}
+ if objs:
+ facts["hsrp_interfaces"] = []
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ for cfg in params["config"]:
+ facts["hsrp_interfaces"].append(utils.remove_empties(cfg))
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+
+ match = re.search(r"^(\S+)", conf)
+ intf = match.group(1)
+ if get_interface_type(intf) == "unknown":
+ return {}
+ config["name"] = intf
+ config["bfd"] = utils.parse_conf_cmd_arg(conf, "hsrp bfd", "enable", "disable")
+
+ return utils.remove_empties(config)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/interfaces.py
new file mode 100644
index 00000000..92592b07
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/interfaces/interfaces.py
@@ -0,0 +1,110 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)#!/usr/bin/python
+"""
+The nxos interfaces fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.interfaces.interfaces import (
+ InterfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ get_interface_type,
+)
+
+
+class InterfacesFacts(object):
+ """The nxos interfaces fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = InterfacesArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for interfaces
+ :param connection: the device connection
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+ if not data:
+ data = connection.get("show running-config | section ^interface")
+
+ config = ("\n" + data).split("\ninterface ")
+ for conf in config:
+ conf = conf.strip()
+ if conf:
+ obj = self.render_config(self.generated_spec, conf)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts["ansible_network_resources"].pop("interfaces", None)
+ facts = {}
+ facts["interfaces"] = []
+ if objs:
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ for cfg in params["config"]:
+ facts["interfaces"].append(utils.remove_empties(cfg))
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+
+ match = re.search(r"^(\S+)", conf)
+ intf = match.group(1)
+ if get_interface_type(intf) == "unknown":
+ return {}
+ config["name"] = intf
+ config["description"] = utils.parse_conf_arg(conf, "description")
+ config["speed"] = utils.parse_conf_arg(conf, "speed")
+ config["mtu"] = utils.parse_conf_arg(conf, "mtu")
+ config["duplex"] = utils.parse_conf_arg(conf, "duplex")
+ config["mode"] = utils.parse_conf_cmd_arg(conf, "switchport", "layer2", "layer3")
+
+ config["enabled"] = utils.parse_conf_cmd_arg(conf, "shutdown", False, True)
+
+ config["fabric_forwarding_anycast_gateway"] = utils.parse_conf_cmd_arg(
+ conf,
+ "fabric forwarding mode anycast-gateway",
+ True,
+ )
+ config["ip_forward"] = utils.parse_conf_cmd_arg(conf, "ip forward", True)
+
+ interfaces_cfg = utils.remove_empties(config)
+ return interfaces_cfg
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/l2_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/l2_interfaces.py
new file mode 100644
index 00000000..cf55552b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l2_interfaces/l2_interfaces.py
@@ -0,0 +1,104 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)#!/usr/bin/python
+"""
+The nxos l2_interfaces fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.l2_interfaces.l2_interfaces import (
+ L2_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ get_interface_type,
+)
+
+
+class L2_interfacesFacts(object):
+ """The nxos l2_interfaces fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = L2_interfacesArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for l2_interfaces
+ :param connection: the device connection
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+ if not data:
+ data = connection.get("show running-config | section ^interface")
+
+ config = ("\n" + data).split("\ninterface ")
+ for conf in config:
+ conf = conf.strip()
+ if conf:
+ obj = self.render_config(self.generated_spec, conf)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts["ansible_network_resources"].pop("l2_interfaces", None)
+ facts = {}
+ if objs:
+ facts["l2_interfaces"] = []
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ for cfg in params["config"]:
+ facts["l2_interfaces"].append(utils.remove_empties(cfg))
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+
+ match = re.search(r"^(\S+)", conf)
+ intf = match.group(1)
+ if get_interface_type(intf) == "unknown":
+ return {}
+
+ config["name"] = intf
+ config["mode"] = utils.parse_conf_arg(conf, "switchport mode")
+ config["ip_forward"] = utils.parse_conf_arg(conf, "ip forward")
+ config["access"]["vlan"] = utils.parse_conf_arg(conf, "switchport access vlan")
+ config["trunk"]["allowed_vlans"] = utils.parse_conf_arg(
+ conf,
+ "switchport trunk allowed vlan",
+ )
+ config["trunk"]["native_vlan"] = utils.parse_conf_arg(conf, "switchport trunk native vlan")
+
+ return utils.remove_empties(config)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/l3_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/l3_interfaces.py
new file mode 100644
index 00000000..ad36e17c
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/l3_interfaces/l3_interfaces.py
@@ -0,0 +1,135 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)#!/usr/bin/python
+"""
+The nxos l3_interfaces fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.l3_interfaces.l3_interfaces import (
+ L3_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ get_interface_type,
+)
+
+
+class L3_interfacesFacts(object):
+ """The nxos l3_interfaces fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = L3_interfacesArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for l3_interfaces
+ :param connection: the device connection
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+ if not data:
+ data = connection.get("show running-config | section ^interface")
+
+ config = ("\n" + data).split("\ninterface ")
+ for conf in config:
+ conf = conf.strip()
+ if conf:
+ obj = self.render_config(self.generated_spec, conf)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts["ansible_network_resources"].pop("l3_interfaces", None)
+ facts = {}
+ if objs:
+ facts["l3_interfaces"] = []
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ for cfg in params["config"]:
+ facts["l3_interfaces"].append(utils.remove_empties(cfg))
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+ match = re.search(r"^(\S+)", conf)
+ intf = match.group(1)
+ if get_interface_type(intf) == "unknown":
+ return {}
+ config["name"] = intf
+ config["dot1q"] = utils.parse_conf_arg(conf, "encapsulation dot1[qQ]")
+ config["redirects"] = utils.parse_conf_cmd_arg(conf, "no ip redirects", False, True)
+ config["ipv6_redirects"] = utils.parse_conf_cmd_arg(conf, "no ipv6 redirects", False, True)
+ config["unreachables"] = utils.parse_conf_cmd_arg(conf, "ip unreachables", True, False)
+ config["evpn_multisite_tracking"] = utils.parse_conf_arg(conf, "evpn multisite")
+ ipv4_match = re.compile(r"\n ip address (.*)")
+ matches = ipv4_match.findall(conf)
+ if matches:
+ if matches[0]:
+ config["ipv4"] = []
+ for m in matches:
+ ipv4_conf = m.split()
+ addr = ipv4_conf[0]
+ if addr:
+ config_dict = {"address": addr}
+ if len(ipv4_conf) > 1:
+ d = ipv4_conf[1]
+ if d == "secondary":
+ config_dict.update({"secondary": True})
+ if len(ipv4_conf) == 4:
+ if ipv4_conf[2] == "tag":
+ config_dict.update({"tag": int(ipv4_conf[-1])})
+ elif d == "tag":
+ config_dict.update({"tag": int(ipv4_conf[-1])})
+ config["ipv4"].append(config_dict)
+
+ ipv6_match = re.compile(r"\n ipv6 address (.*)")
+ matches = ipv6_match.findall(conf)
+ if matches:
+ if matches[0]:
+ config["ipv6"] = []
+ for m in matches:
+ ipv6_conf = m.split()
+ addr = ipv6_conf[0]
+ if addr:
+ config_dict = {"address": addr}
+ if len(ipv6_conf) > 1:
+ d = ipv6_conf[1]
+ if d == "tag":
+ config_dict.update({"tag": int(ipv6_conf[-1])})
+ config["ipv6"].append(config_dict)
+
+ return utils.remove_empties(config)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/lacp.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/lacp.py
new file mode 100644
index 00000000..51b1413d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp/lacp.py
@@ -0,0 +1,89 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos lacp fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lacp.lacp import (
+ LacpArgs,
+)
+
+
+class LacpFacts(object):
+ """The nxos lacp fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = LacpArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for lacp
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ data = connection.get("show running-config | include lacp")
+ resources = data.strip()
+ objs = self.render_config(self.generated_spec, resources)
+ ansible_facts["ansible_network_resources"].pop("lacp", None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ facts["lacp"] = utils.remove_empties(params["config"])
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+
+ p_match = re.search(r"lacp system-priority (\d+)", conf, re.M)
+ if p_match:
+ config["system"]["priority"] = p_match.group(1)
+
+ a_match = re.search(r"lacp system-mac (\S+)", conf, re.M)
+ if a_match:
+ address = a_match.group(1)
+ config["system"]["mac"]["address"] = address
+ r_match = re.search(r"lacp system-mac {0} role (\S+)".format(address), conf, re.M)
+ if r_match:
+ config["system"]["mac"]["role"] = r_match.group(1)
+
+ return utils.remove_empties(config)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/lacp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/lacp_interfaces.py
new file mode 100644
index 00000000..c60d8abd
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lacp_interfaces/lacp_interfaces.py
@@ -0,0 +1,115 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos lacp_interfaces fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lacp_interfaces.lacp_interfaces import (
+ Lacp_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ get_interface_type,
+)
+
+
+class Lacp_interfacesFacts(object):
+ """The nxos lacp_interfaces fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Lacp_interfacesArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for lacp_interfaces
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+
+ if not data:
+ data = connection.get("show running-config | section ^interface")
+
+ resources = ("\n" + data).split("\ninterface ")
+ for resource in resources:
+ if resource and re.search(r"lacp", resource):
+ obj = self.render_config(self.generated_spec, resource)
+ if obj and len(obj.keys()) > 1:
+ objs.append(obj)
+
+ ansible_facts["ansible_network_resources"].pop("lacp_interfaces", None)
+ facts = {}
+ if objs:
+ facts["lacp_interfaces"] = []
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ for cfg in params["config"]:
+ facts["lacp_interfaces"].append(utils.remove_empties(cfg))
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+
+ match = re.search(r"^(\S+)", conf)
+ intf = match.group(1)
+ if get_interface_type(intf) == "unknown":
+ return {}
+ config["name"] = intf
+ config["port_priority"] = utils.parse_conf_arg(conf, "lacp port-priority")
+ config["rate"] = utils.parse_conf_arg(conf, "lacp rate")
+ config["mode"] = utils.parse_conf_arg(conf, "lacp mode")
+ suspend_individual = re.search(r"no lacp suspend-individual", conf)
+ if suspend_individual:
+ config["suspend_individual"] = False
+ max_links = utils.parse_conf_arg(conf, "lacp max-bundle")
+ if max_links:
+ config["links"]["max"] = max_links
+ min_links = utils.parse_conf_arg(conf, "lacp min-links")
+ if min_links:
+ config["links"]["min"] = min_links
+ graceful = re.search(r"no lacp graceful-convergence", conf)
+ if graceful:
+ config["convergence"]["graceful"] = False
+ vpc = re.search(r"lacp vpc-convergence", conf)
+ if vpc:
+ config["convergence"]["vpc"] = True
+
+ return utils.remove_empties(config)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/lag_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/lag_interfaces.py
new file mode 100644
index 00000000..b94c57e2
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lag_interfaces/lag_interfaces.py
@@ -0,0 +1,104 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)#!/usr/bin/python
+"""
+The nxos lag_interfaces fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lag_interfaces.lag_interfaces import (
+ Lag_interfacesArgs,
+)
+
+
+class Lag_interfacesFacts(object):
+ """The nxos lag_interfaces fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Lag_interfacesArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for lag_interfaces
+ :param connection: the device connection
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+ if not data:
+ data = connection.get("show running-config | section ^interface")
+
+ objs = self.render_config(self.generated_spec, data, connection)
+
+ ansible_facts["ansible_network_resources"].pop("lag_interfaces", None)
+ facts = {}
+ if objs:
+ facts["lag_interfaces"] = []
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ for cfg in params["config"]:
+ facts["lag_interfaces"].append(utils.remove_empties(cfg))
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def render_config(self, spec, conf, connection):
+ """
+ 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
+ """
+ result = []
+ match = re.findall(r"interface (port-channel\d+)", conf)
+
+ for item in match:
+ result.append({"name": item, "members": []})
+
+ for intf in conf.split("interface "):
+ member = {}
+ match_intf = re.search(r"(port-channel|Ethernet)(\S+)", intf)
+ if match_intf:
+ member["member"] = match_intf.group(0)
+
+ match_line = re.search(
+ r"channel-group\s(?P<port_channel>\d+)(\smode\s(?P<mode>on|active|passive))?",
+ intf,
+ )
+ if match_line:
+ member.update(match_line.groupdict())
+
+ if member and member.get("port_channel", None):
+ port_channel = "port-channel{0}".format(member.pop("port_channel"))
+ for x in result:
+ if x["name"] == port_channel:
+ x["members"].append(utils.remove_empties(member))
+
+ return result
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/base.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/base.py
new file mode 100644
index 00000000..2244e222
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/legacy/base.py
@@ -0,0 +1,793 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import platform
+import re
+
+from ansible.module_utils.six import iteritems
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_config,
+ run_commands,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ get_interface_type,
+ normalize_interface,
+)
+
+
+g_config = None
+
+
+class FactsBase(object):
+ def __init__(self, module):
+ self.module = module
+ self.warnings = list()
+ self.facts = dict()
+ self.capabilities = get_capabilities(self.module)
+
+ def populate(self):
+ pass
+
+ def run(self, command, output="text"):
+ command_string = command
+ command = {"command": command, "output": output}
+ resp = run_commands(self.module, [command], check_rc="retry_json")
+ try:
+ return resp[0]
+ except IndexError:
+ self.warnings.append(
+ "command %s failed, facts for this command will not be populated" % command_string,
+ )
+ return None
+
+ def get_config(self):
+ global g_config
+ if not g_config:
+ g_config = get_config(self.module)
+ return g_config
+
+ def transform_dict(self, data, keymap):
+ transform = dict()
+ for key, fact in keymap:
+ if key in data:
+ transform[fact] = data[key]
+ return transform
+
+ def transform_iterable(self, iterable, keymap):
+ for item in iterable:
+ yield self.transform_dict(item, keymap)
+
+
+class Default(FactsBase):
+ def populate(self):
+ data = None
+ data = self.run("show version")
+
+ if data:
+ self.facts["serialnum"] = self.parse_serialnum(data)
+
+ data = self.run("show license host-id")
+ if data:
+ self.facts["license_hostid"] = self.parse_license_hostid(data)
+
+ self.facts.update(self.platform_facts())
+
+ def parse_serialnum(self, data):
+ match = re.search(r"Processor Board ID\s*(\S+)", data, re.M)
+ if match:
+ return match.group(1)
+
+ def platform_facts(self):
+ platform_facts = {}
+
+ resp = self.capabilities
+ device_info = resp["device_info"]
+
+ platform_facts["system"] = device_info["network_os"]
+
+ for item in ("model", "image", "version", "platform", "hostname"):
+ val = device_info.get("network_os_%s" % item)
+ if val:
+ platform_facts[item] = val
+
+ platform_facts["api"] = resp["network_api"]
+ platform_facts["python_version"] = platform.python_version()
+
+ return platform_facts
+
+ def parse_license_hostid(self, data):
+ match = re.search(r"License hostid: VDH=(.+)$", data, re.M)
+ if match:
+ return match.group(1)
+
+
+class Config(FactsBase):
+ def populate(self):
+ super(Config, self).populate()
+ self.facts["config"] = self.get_config()
+
+
+class Features(FactsBase):
+ def populate(self):
+ super(Features, self).populate()
+ data = self.get_config()
+
+ if data:
+ features = []
+ for line in data.splitlines():
+ if line.startswith("feature"):
+ features.append(line.replace("feature", "").strip())
+
+ self.facts["features_enabled"] = features
+
+
+class Hardware(FactsBase):
+ def populate(self):
+ data = self.run("dir")
+ if data:
+ self.facts["filesystems"] = self.parse_filesystems(data)
+
+ data = None
+ data = self.run("show system resources", output="json")
+
+ if data:
+ if isinstance(data, dict):
+ self.facts["memtotal_mb"] = int(data["memory_usage_total"]) / 1024
+ self.facts["memfree_mb"] = int(data["memory_usage_free"]) / 1024
+ else:
+ self.facts["memtotal_mb"] = self.parse_memtotal_mb(data)
+ self.facts["memfree_mb"] = self.parse_memfree_mb(data)
+
+ def parse_filesystems(self, data):
+ return re.findall(r"^Usage for (\S+)//", data, re.M)
+
+ def parse_memtotal_mb(self, data):
+ match = re.search(r"(\S+)K(\s+|)total", data, re.M)
+ if match:
+ memtotal = match.group(1)
+ return int(memtotal) / 1024
+
+ def parse_memfree_mb(self, data):
+ match = re.search(r"(\S+)K(\s+|)free", data, re.M)
+ if match:
+ memfree = match.group(1)
+ return int(memfree) / 1024
+
+
+class Interfaces(FactsBase):
+ INTERFACE_MAP = frozenset(
+ [
+ ("state", "state"),
+ ("desc", "description"),
+ ("eth_bw", "bandwidth"),
+ ("eth_duplex", "duplex"),
+ ("eth_speed", "speed"),
+ ("eth_mode", "mode"),
+ ("eth_hw_addr", "macaddress"),
+ ("eth_mtu", "mtu"),
+ ("eth_hw_desc", "type"),
+ ],
+ )
+
+ INTERFACE_SVI_MAP = frozenset(
+ [
+ ("svi_line_proto", "state"),
+ ("svi_bw", "bandwidth"),
+ ("svi_mac", "macaddress"),
+ ("svi_mtu", "mtu"),
+ ("type", "type"),
+ ],
+ )
+
+ INTERFACE_IPV4_MAP = frozenset([("eth_ip_addr", "address"), ("eth_ip_mask", "masklen")])
+
+ INTERFACE_SVI_IPV4_MAP = frozenset([("svi_ip_addr", "address"), ("svi_ip_mask", "masklen")])
+
+ INTERFACE_IPV6_MAP = frozenset([("addr", "address"), ("prefix", "subnet")])
+
+ def ipv6_structure_op_supported(self):
+ data = self.capabilities
+ if data:
+ nxos_os_version = data["device_info"]["network_os_version"]
+ unsupported_versions = ["I2", "F1", "A8"]
+ for ver in unsupported_versions:
+ if ver in nxos_os_version:
+ return False
+ return True
+
+ def populate(self):
+ self.facts["all_ipv4_addresses"] = list()
+ self.facts["all_ipv6_addresses"] = list()
+ self.facts["neighbors"] = {}
+ data = None
+
+ data = self.run("show interface", output="json")
+
+ if data:
+ if isinstance(data, dict):
+ self.facts["interfaces"] = self.populate_structured_interfaces(data)
+ else:
+ interfaces = self.parse_interfaces(data)
+ self.facts["interfaces"] = self.populate_interfaces(interfaces)
+
+ if self.ipv6_structure_op_supported():
+ data = self.run("show ipv6 interface", output="json")
+ else:
+ data = None
+ if data:
+ if isinstance(data, dict):
+ self.populate_structured_ipv6_interfaces(data)
+ else:
+ interfaces = self.parse_interfaces(data)
+ self.populate_ipv6_interfaces(interfaces)
+
+ data = self.run("show lldp neighbors", output="json")
+ if data:
+ if isinstance(data, dict):
+ self.facts["neighbors"].update(self.populate_structured_neighbors_lldp(data))
+ else:
+ self.facts["neighbors"].update(self.populate_neighbors(data))
+
+ data = self.run("show cdp neighbors detail", output="json")
+ if data:
+ if isinstance(data, dict):
+ self.facts["neighbors"].update(self.populate_structured_neighbors_cdp(data))
+ else:
+ self.facts["neighbors"].update(self.populate_neighbors_cdp(data))
+
+ self.facts["neighbors"].pop(None, None) # Remove null key
+
+ def populate_structured_interfaces(self, data):
+ interfaces = dict()
+ data = data["TABLE_interface"]["ROW_interface"]
+
+ if isinstance(data, dict):
+ data = [data]
+
+ for item in data:
+ name = item["interface"]
+
+ intf = dict()
+ if "type" in item:
+ intf.update(self.transform_dict(item, self.INTERFACE_SVI_MAP))
+ else:
+ intf.update(self.transform_dict(item, self.INTERFACE_MAP))
+
+ if "eth_ip_addr" in item:
+ intf["ipv4"] = self.transform_dict(item, self.INTERFACE_IPV4_MAP)
+ self.facts["all_ipv4_addresses"].append(item["eth_ip_addr"])
+
+ if "svi_ip_addr" in item:
+ intf["ipv4"] = self.transform_dict(item, self.INTERFACE_SVI_IPV4_MAP)
+ self.facts["all_ipv4_addresses"].append(item["svi_ip_addr"])
+
+ interfaces[name] = intf
+
+ return interfaces
+
+ def populate_structured_ipv6_interfaces(self, data):
+ try:
+ data = data["TABLE_intf"]
+ if data:
+ if isinstance(data, dict):
+ data = [data]
+ for item in data:
+ name = item["ROW_intf"]["intf-name"]
+ intf = self.facts["interfaces"][name]
+ intf["ipv6"] = self.transform_dict(item, self.INTERFACE_IPV6_MAP)
+ try:
+ addr = item["ROW_intf"]["addr"]
+ except KeyError:
+ addr = item["ROW_intf"]["TABLE_addr"]["ROW_addr"]["addr"]
+ self.facts["all_ipv6_addresses"].append(addr)
+ else:
+ return ""
+ except TypeError:
+ return ""
+
+ def populate_structured_neighbors_lldp(self, data):
+ objects = dict()
+ data = data["TABLE_nbor"]["ROW_nbor"]
+
+ if isinstance(data, dict):
+ data = [data]
+
+ for item in data:
+ local_intf = normalize_interface(item["l_port_id"])
+ objects[local_intf] = list()
+ nbor = dict()
+ nbor["port"] = item["port_id"]
+ nbor["host"] = nbor["sysname"] = item["chassis_id"]
+ objects[local_intf].append(nbor)
+
+ return objects
+
+ def populate_structured_neighbors_cdp(self, data):
+ objects = dict()
+ data = data["TABLE_cdp_neighbor_detail_info"]["ROW_cdp_neighbor_detail_info"]
+
+ if isinstance(data, dict):
+ data = [data]
+
+ for item in data:
+ if "intf_id" in item:
+ local_intf = item["intf_id"]
+ else:
+ # in some N7Ks the key has been renamed
+ local_intf = item["interface"]
+ objects[local_intf] = list()
+ nbor = dict()
+ nbor["port"] = item["port_id"]
+ nbor["host"] = nbor["sysname"] = item["device_id"]
+ objects[local_intf].append(nbor)
+
+ return objects
+
+ def parse_interfaces(self, data):
+ parsed = dict()
+ key = ""
+ for line in data.split("\n"):
+ if len(line) == 0:
+ continue
+ elif line.startswith("admin") or line[0] == " ":
+ parsed[key] += "\n%s" % line
+ else:
+ match = re.match(r"^(\S+)", line)
+ if match:
+ key = match.group(1)
+ if not key.startswith("admin") or not key.startswith("IPv6 Interface"):
+ parsed[key] = line
+ return parsed
+
+ def populate_interfaces(self, interfaces):
+ facts = dict()
+ for key, value in iteritems(interfaces):
+ intf = dict()
+ if get_interface_type(key) == "svi":
+ intf["state"] = self.parse_state(key, value, intf_type="svi")
+ intf["macaddress"] = self.parse_macaddress(value, intf_type="svi")
+ intf["mtu"] = self.parse_mtu(value, intf_type="svi")
+ intf["bandwidth"] = self.parse_bandwidth(value, intf_type="svi")
+ intf["type"] = self.parse_type(value, intf_type="svi")
+ if "Internet Address" in value:
+ intf["ipv4"] = self.parse_ipv4_address(value, intf_type="svi")
+ facts[key] = intf
+ else:
+ intf["state"] = self.parse_state(key, value)
+ intf["description"] = self.parse_description(value)
+ intf["macaddress"] = self.parse_macaddress(value)
+ intf["mode"] = self.parse_mode(value)
+ intf["mtu"] = self.parse_mtu(value)
+ intf["bandwidth"] = self.parse_bandwidth(value)
+ intf["duplex"] = self.parse_duplex(value)
+ intf["speed"] = self.parse_speed(value)
+ intf["type"] = self.parse_type(value)
+ if "Internet Address" in value:
+ intf["ipv4"] = self.parse_ipv4_address(value)
+ facts[key] = intf
+
+ return facts
+
+ def parse_state(self, key, value, intf_type="ethernet"):
+ match = None
+ if intf_type == "svi":
+ match = re.search(r"line protocol is\s*(\S+)", value, re.M)
+ else:
+ match = re.search(r"%s is\s*(\S+)" % key, value, re.M)
+
+ if match:
+ return match.group(1)
+
+ def parse_macaddress(self, value, intf_type="ethernet"):
+ match = None
+ if intf_type == "svi":
+ match = re.search(r"address is\s*(\S+)", value, re.M)
+ else:
+ match = re.search(r"address:\s*(\S+)", value, re.M)
+
+ if match:
+ return match.group(1)
+
+ def parse_mtu(self, value, intf_type="ethernet"):
+ match = re.search(r"MTU\s*(\S+)", value, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_bandwidth(self, value, intf_type="ethernet"):
+ match = re.search(r"BW\s*(\S+)", value, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_type(self, value, intf_type="ethernet"):
+ match = None
+ if intf_type == "svi":
+ match = re.search(r"Hardware is\s*(\S+)", value, re.M)
+ else:
+ match = re.search(r"Hardware:\s*(.+),", value, re.M)
+
+ if match:
+ return match.group(1)
+
+ def parse_description(self, value, intf_type="ethernet"):
+ match = re.search(r"Description: (.+)$", value, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_mode(self, value, intf_type="ethernet"):
+ match = re.search(r"Port mode is (\S+)", value, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_duplex(self, value, intf_type="ethernet"):
+ match = re.search(r"(\S+)-duplex", value, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_speed(self, value, intf_type="ethernet"):
+ match = re.search(r"duplex, (.+)$", value, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_ipv4_address(self, value, intf_type="ethernet"):
+ ipv4 = {}
+ match = re.search(r"Internet Address is (.+)$", value, re.M)
+ if match:
+ address = match.group(1)
+ addr = address.split("/")[0]
+ ipv4["address"] = address.split("/")[0]
+ ipv4["masklen"] = address.split("/")[1]
+ self.facts["all_ipv4_addresses"].append(addr)
+ return ipv4
+
+ def populate_neighbors(self, data):
+ objects = dict()
+ # if there are no neighbors the show command returns
+ # ERROR: No neighbour information
+ if data.startswith("ERROR"):
+ return dict()
+
+ regex = re.compile(r"(\S+)\s+(\S+)\s+\d+\s+\w+\s+(\S+)")
+
+ for item in data.split("\n")[4:-1]:
+ match = regex.match(item)
+ if match:
+ nbor = dict()
+ nbor["host"] = nbor["sysname"] = match.group(1)
+ nbor["port"] = match.group(3)
+ local_intf = normalize_interface(match.group(2))
+ if local_intf not in objects:
+ objects[local_intf] = []
+ objects[local_intf].append(nbor)
+
+ return objects
+
+ def populate_neighbors_cdp(self, data):
+ facts = dict()
+
+ for item in data.split("----------------------------------------"):
+ if item == "":
+ continue
+ local_intf = self.parse_lldp_intf(item)
+ if local_intf not in facts:
+ facts[local_intf] = list()
+
+ fact = dict()
+ fact["port"] = self.parse_lldp_port(item)
+ fact["sysname"] = self.parse_lldp_sysname(item)
+ facts[local_intf].append(fact)
+
+ return facts
+
+ def parse_lldp_intf(self, data):
+ match = re.search(r"Interface:\s*(\S+)", data, re.M)
+ if match:
+ return match.group(1).strip(",")
+
+ def parse_lldp_port(self, data):
+ match = re.search(r"Port ID \(outgoing port\):\s*(\S+)", data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_lldp_sysname(self, data):
+ match = re.search(r"Device ID:(.+)$", data, re.M)
+ if match:
+ return match.group(1)
+
+ def populate_ipv6_interfaces(self, interfaces):
+ facts = dict()
+ for key, value in iteritems(interfaces):
+ intf = dict()
+ intf["ipv6"] = self.parse_ipv6_address(value)
+ facts[key] = intf
+
+ def parse_ipv6_address(self, value):
+ ipv6 = {}
+ match_addr = re.search(r"IPv6 address:\s*(\S+)", value, re.M)
+ if match_addr:
+ addr = match_addr.group(1)
+ ipv6["address"] = addr
+ self.facts["all_ipv6_addresses"].append(addr)
+ match_subnet = re.search(r"IPv6 subnet:\s*(\S+)", value, re.M)
+ if match_subnet:
+ ipv6["subnet"] = match_subnet.group(1)
+
+ return ipv6
+
+
+class Legacy(FactsBase):
+ # facts from nxos_facts 2.1
+
+ VERSION_MAP = frozenset(
+ [
+ ("host_name", "_hostname"),
+ ("kickstart_ver_str", "_os"),
+ ("chassis_id", "_platform"),
+ ],
+ )
+
+ MODULE_MAP = frozenset(
+ [
+ ("model", "model"),
+ ("modtype", "type"),
+ ("ports", "ports"),
+ ("status", "status"),
+ ],
+ )
+
+ FAN_MAP = frozenset(
+ [
+ ("fanname", "name"),
+ ("fanmodel", "model"),
+ ("fanhwver", "hw_ver"),
+ ("fandir", "direction"),
+ ("fanstatus", "status"),
+ ],
+ )
+
+ POWERSUP_MAP = frozenset(
+ [
+ ("psmodel", "model"),
+ ("psnum", "number"),
+ ("ps_status", "status"),
+ ("ps_status_3k", "status"),
+ ("actual_out", "actual_output"),
+ ("actual_in", "actual_in"),
+ ("total_capa", "total_capacity"),
+ ("input_type", "input_type"),
+ ("watts", "watts"),
+ ("amps", "amps"),
+ ],
+ )
+
+ def populate(self):
+ data = None
+
+ data = self.run("show version", output="json")
+ if data:
+ if isinstance(data, dict):
+ self.facts.update(self.transform_dict(data, self.VERSION_MAP))
+ else:
+ self.facts["hostname"] = self.parse_hostname(data)
+ self.facts["os"] = self.parse_os(data)
+ self.facts["platform"] = self.parse_platform(data)
+
+ data = self.run("show interface", output="json")
+ if data:
+ if isinstance(data, dict):
+ self.facts["interfaces_list"] = self.parse_structured_interfaces(data)
+ else:
+ self.facts["interfaces_list"] = self.parse_interfaces(data)
+
+ data = self.run("show vlan brief", output="json")
+ if data:
+ if isinstance(data, dict):
+ self.facts["vlan_list"] = self.parse_structured_vlans(data)
+ else:
+ self.facts["vlan_list"] = self.parse_vlans(data)
+
+ data = self.run("show module", output="json")
+ if data:
+ if isinstance(data, dict):
+ self.facts["module"] = self.parse_structured_module(data)
+ else:
+ self.facts["module"] = self.parse_module(data)
+
+ data = self.run("show environment fan", output="json")
+ if data:
+ if isinstance(data, dict):
+ self.facts["fan_info"] = self.parse_structured_fan_info(data)
+ else:
+ self.facts["fan_info"] = self.parse_fan_info(data)
+
+ data = self.run("show environment power", output="json")
+ if data:
+ if isinstance(data, dict):
+ self.facts["power_supply_info"] = self.parse_structured_power_supply_info(data)
+ else:
+ self.facts["power_supply_info"] = self.parse_power_supply_info(data)
+
+ def parse_structured_interfaces(self, data):
+ objects = list()
+ data = data["TABLE_interface"]["ROW_interface"]
+ if isinstance(data, dict):
+ objects.append(data["interface"])
+ elif isinstance(data, list):
+ for item in data:
+ objects.append(item["interface"])
+ return objects
+
+ def parse_structured_vlans(self, data):
+ objects = list()
+ data = data["TABLE_vlanbriefxbrief"]["ROW_vlanbriefxbrief"]
+ if isinstance(data, dict):
+ objects.append(data["vlanshowbr-vlanid-utf"])
+ elif isinstance(data, list):
+ for item in data:
+ objects.append(item["vlanshowbr-vlanid-utf"])
+ return objects
+
+ def parse_structured_module(self, data):
+ modinfo = data["TABLE_modinfo"]
+ if isinstance(modinfo, dict):
+ modinfo = [modinfo]
+
+ objects = []
+ for entry in modinfo:
+ entry = entry["ROW_modinfo"]
+ if isinstance(entry, dict):
+ entry = [entry]
+ entry_objects = list(self.transform_iterable(entry, self.MODULE_MAP))
+ objects.extend(entry_objects)
+ return objects
+
+ def parse_structured_fan_info(self, data):
+ objects = list()
+
+ for key in ("fandetails", "fandetails_3k"):
+ if data.get(key):
+ try:
+ data = data[key]["TABLE_faninfo"]["ROW_faninfo"]
+ except KeyError:
+ # Some virtual images don't actually report faninfo. In this case, move on and
+ # just return an empty list.
+ pass
+ else:
+ objects = list(self.transform_iterable(data, self.FAN_MAP))
+ break
+
+ return objects
+
+ def parse_structured_power_supply_info(self, data):
+ ps_data = data.get("powersup", {})
+ if ps_data.get("TABLE_psinfo_n3k"):
+ fact = ps_data["TABLE_psinfo_n3k"]["ROW_psinfo_n3k"]
+ else:
+ # {TABLE,ROW}_psinfo keys have been renamed to
+ # {TABLE,ROW}_ps_info in later NX-OS releases
+ tab_key, row_key = "TABLE_psinfo", "ROW_psinfo"
+ if tab_key not in ps_data:
+ tab_key, row_key = "TABLE_ps_info", "ROW_ps_info"
+
+ ps_tab_data = ps_data[tab_key]
+
+ if isinstance(ps_tab_data, list):
+ fact = []
+ for i in ps_tab_data:
+ fact.append(i[row_key])
+ else:
+ fact = ps_tab_data[row_key]
+
+ objects = list(self.transform_iterable(fact, self.POWERSUP_MAP))
+ return objects
+
+ def parse_hostname(self, data):
+ match = re.search(r"\s+Device name:\s+(\S+)", data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_os(self, data):
+ match = re.search(r"\s+system:\s+version\s*(\S+)", data, re.M)
+ if match:
+ return match.group(1)
+ else:
+ match = re.search(r"\s+kickstart:\s+version\s*(\S+)", data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_platform(self, data):
+ match = re.search(r"Hardware\n\s+cisco\s+(\S+\s+\S+)", data, re.M)
+ if match:
+ return match.group(1)
+
+ def parse_interfaces(self, data):
+ objects = list()
+ for line in data.split("\n"):
+ if len(line) == 0:
+ continue
+ elif line.startswith("admin") or line[0] == " ":
+ continue
+ else:
+ match = re.match(r"^(\S+)", line)
+ if match:
+ intf = match.group(1)
+ if get_interface_type(intf) != "unknown":
+ objects.append(intf)
+ return objects
+
+ def parse_vlans(self, data):
+ objects = list()
+ for line in data.splitlines():
+ if line == "":
+ continue
+ if line[0].isdigit():
+ vlan = line.split()[0]
+ objects.append(vlan)
+ return objects
+
+ def parse_module(self, data):
+ objects = list()
+ for line in data.splitlines():
+ if line == "":
+ break
+ if line[0].isdigit():
+ obj = {}
+ match_port = re.search(r"\d\s*(\d*)", line, re.M)
+ if match_port:
+ obj["ports"] = match_port.group(1)
+
+ match = re.search(r"\d\s*\d*\s*(.+)$", line, re.M)
+ if match:
+ l = match.group(1).split(" ")
+ items = list()
+ for item in l:
+ if item == "":
+ continue
+ items.append(item.strip())
+
+ if items:
+ obj["type"] = items[0]
+ obj["model"] = items[1]
+ obj["status"] = items[2]
+
+ objects.append(obj)
+ return objects
+
+ def parse_fan_info(self, data):
+ objects = list()
+
+ for l in data.splitlines():
+ if "-----------------" in l or "Status" in l:
+ continue
+ line = l.split()
+ if len(line) > 1:
+ obj = {}
+ obj["name"] = line[0]
+ obj["model"] = line[1]
+ obj["hw_ver"] = line[-2]
+ obj["status"] = line[-1]
+ objects.append(obj)
+ return objects
+
+ def parse_power_supply_info(self, data):
+ objects = list()
+
+ for l in data.splitlines():
+ if l == "":
+ break
+ if l[0].isdigit():
+ obj = {}
+ line = l.split()
+ obj["model"] = line[1]
+ obj["number"] = line[0]
+ obj["status"] = line[-1]
+
+ objects.append(obj)
+ return objects
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/lldp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/lldp_global.py
new file mode 100644
index 00000000..6e8aabde
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_global/lldp_global.py
@@ -0,0 +1,107 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos lldp_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 __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
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lldp_global.lldp_global import (
+ Lldp_globalArgs,
+)
+
+
+class Lldp_globalFacts(object):
+ """The nxos lldp_global fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Lldp_globalArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for lldp_global
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+
+ if not data:
+ data = connection.get("show running-config | include lldp")
+
+ objs = {}
+ objs = self.render_config(self.generated_spec, data)
+ ansible_facts["ansible_network_resources"].pop("lldp_global", None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ facts["lldp_global"] = params["config"]
+ facts = utils.remove_empties(facts)
+ ansible_facts["ansible_network_resources"].update((facts))
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+ conf = re.split("\n", conf)
+ for command in conf:
+ param = re.search(r"(.*)lldp (\w+(-?)\w+)", command) # get the word after 'lldp'
+ if param:
+ # get the nested-dict/value for that param
+ key2 = re.search(r"%s(.*)" % param.group(2), command)
+ key2 = key2.group(1).strip()
+ key1 = param.group(2).replace("-", "_")
+
+ if key1 == "portid_subtype":
+ key1 = "port_id"
+ config[key1] = key2
+ elif key1 == "tlv_select":
+ key2 = key2.split()
+ key2[0] = key2[0].replace("-", "_")
+ if len(key2) == 1:
+ if "port" in key2[0] or "system" in key2[0]: # nested dicts
+ key2 = key2[0].split("_")
+ # config[tlv_select][system][name]=False
+ config[key1][key2[0]][key2[1]] = False
+ else:
+ # config[tlv_select][dcbxp]=False
+ config[key1][key2[0]] = False
+ else:
+ # config[tlv_select][management_address][v6]=False
+ config[key1][key2[0]][key2[1]] = False
+ else:
+ config[key1] = key2 # config[reinit]=4
+ return utils.remove_empties(config)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/lldp_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/lldp_interfaces.py
new file mode 100644
index 00000000..4cd8b211
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/lldp_interfaces/lldp_interfaces.py
@@ -0,0 +1,128 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos lldp_interfaces fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lldp_interfaces.lldp_interfaces import (
+ Lldp_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ get_interface_type,
+)
+
+
+class Lldp_interfacesFacts(object):
+ """The nxos lldp_interfaces fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Lldp_interfacesArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def get_device_data(self, connection):
+ return connection.get("show running-config | section ^interface")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for lldp_interfaces
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ data = self.get_device_data(connection)
+
+ objs = []
+
+ data = data.split("interface")
+ resources = []
+
+ for i in range(len(data)):
+ intf = data[i].split("\n")
+ for l in range(1, len(intf)):
+ if not re.search("lldp", intf[l]):
+ intf[l] = ""
+ intf = list(filter(None, intf))
+ intf = "".join(i for i in intf)
+ resources.append(intf)
+
+ for resource in resources:
+ if resource: # and re.search(r'lldp', resource):
+ obj = self.render_config(self.generated_spec, resource)
+ if obj and len(obj.keys()) >= 1:
+ objs.append(obj)
+
+ ansible_facts["ansible_network_resources"].pop("lldp_interfaces", None)
+ facts = {}
+ if objs:
+ facts["lldp_interfaces"] = []
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ for cfg in params["config"]:
+ facts["lldp_interfaces"].append(utils.remove_empties(cfg))
+
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
+
+ def render_config(self, spec, conf):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+
+ :param spec: The facts tree, generated from the argspec
+ :param conf: The configuration
+ :rtype: dictionary
+ :returns: The generated config
+ """
+ config = deepcopy(spec)
+ match = re.search(r"^ (\S+)", conf)
+ if match is None:
+ return {}
+ intf = match.group(1)
+ if get_interface_type(intf) not in ["management", "ethernet"]:
+ return {}
+ config["name"] = intf
+ if "lldp receive" in conf: # for parsed state only
+ config["receive"] = True
+ if "no lldp receive" in conf:
+ config["receive"] = False
+
+ if "lldp transmit" in conf: # for parsed state only
+ config["transmit"] = True
+ if "no lldp transmit" in conf:
+ config["transmit"] = False
+ if "management-address" in conf:
+ config["tlv_set"]["management_address"] = re.search(
+ r"management-address (\S*)",
+ conf,
+ ).group(1)
+ if "vlan" in conf:
+ config["tlv_set"]["vlan"] = re.search(r"vlan (\S*)", conf).group(1)
+ return utils.remove_empties(config)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/logging_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/logging_global.py
new file mode 100644
index 00000000..189db0e4
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/logging_global/logging_global.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos logging_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.nxos.plugins.module_utils.network.nxos.argspec.logging_global.logging_global import (
+ Logging_globalArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.logging_global import (
+ Logging_globalTemplate,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import (
+ get_logging_sevmap,
+)
+
+
+class Logging_globalFacts(object):
+ """The nxos logging_global facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Logging_globalArgs.argument_spec
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config | include logging")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Logging_global network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+ objs = []
+ sev_map = get_logging_sevmap()
+
+ if not data:
+ data = self.get_config(connection)
+
+ # parse native config using the Logging_global template
+ logging_global_parser = Logging_globalTemplate(lines=data.splitlines(), module=self._module)
+ objs = logging_global_parser.parse()
+
+ if objs:
+ for k in ("console", "history", "logfile", "module", "monitor"):
+ if "severity" in objs.get(k, {}):
+ objs[k]["severity"] = sev_map[objs[k]["severity"]]
+ # pre-sort list of dictionaries
+ pkey = {"hosts": "host", "facilities": "facility"}
+ for x in ("hosts", "facilities"):
+ if x in objs:
+ for item in objs[x]:
+ if "severity" in item:
+ item["severity"] = sev_map[item["severity"]]
+ objs[x] = sorted(objs[x], key=lambda k: k[pkey[x]])
+
+ ansible_facts["ansible_network_resources"].pop("logging_global", None)
+
+ params = utils.remove_empties(
+ logging_global_parser.validate_config(
+ self.argument_spec,
+ {"config": objs},
+ redact=True,
+ ),
+ )
+
+ facts["logging_global"] = params.get("config", {})
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/ntp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/ntp_global.py
new file mode 100644
index 00000000..258b68aa
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ntp_global/ntp_global.py
@@ -0,0 +1,89 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos ntp_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.nxos.plugins.module_utils.network.nxos.argspec.ntp_global.ntp_global import (
+ Ntp_globalArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ntp_global import (
+ Ntp_globalTemplate,
+)
+
+
+class Ntp_globalFacts(object):
+ """The nxos ntp_global facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Ntp_globalArgs.argument_spec
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config ntp")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Ntp_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_config(connection)
+
+ # parse native config using the Ntp_global template
+ ntp_global_parser = Ntp_globalTemplate(lines=data.splitlines(), module=self._module)
+ objs = ntp_global_parser.parse()
+
+ if "access_group" in objs:
+ for x in ["peer", "query_only", "serve", "serve_only"]:
+ if x in objs["access_group"]:
+ objs["access_group"][x] = sorted(
+ objs["access_group"][x],
+ key=lambda k: k["access_list"],
+ )
+
+ pkey = {
+ "authentication_keys": "id",
+ "peers": "peer",
+ "servers": "server",
+ "trusted_keys": "key_id",
+ }
+
+ for x in pkey.keys():
+ if x in objs:
+ objs[x] = sorted(objs[x], key=lambda k: k[pkey[x]])
+
+ ansible_facts["ansible_network_resources"].pop("ntp_global", None)
+
+ params = utils.remove_empties(
+ ntp_global_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
+ )
+
+ facts["ntp_global"] = params.get("config", {})
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/ospf_interfaces.py
new file mode 100644
index 00000000..745f373b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospf_interfaces/ospf_interfaces.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos ospf_interfaces 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.nxos.plugins.module_utils.network.nxos.argspec.ospf_interfaces.ospf_interfaces import (
+ Ospf_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospf_interfaces import (
+ Ospf_interfacesTemplate,
+)
+
+
+class Ospf_interfacesFacts(object):
+ """The nxos ospf_interfaces facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Ospf_interfacesArgs.argument_spec
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config | section '^interface'")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Ospf_interfaces network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+ objs = []
+
+ if not data:
+ data = self.get_config(connection)
+
+ # parse native config using the Ospf_interfaces template
+ ospf_interfaces_parser = Ospf_interfacesTemplate(
+ lines=data.splitlines(),
+ module=self._module,
+ )
+ objs = list(ospf_interfaces_parser.parse().values())
+ if objs:
+ for item in objs:
+ item["address_family"] = list(item["address_family"].values())
+ if "address_family" in item:
+ for af in item["address_family"]:
+ if af.get("processes"):
+ af["processes"] = list(af["processes"].values())
+ if af.get("multi_areas"):
+ af["multi_areas"].sort()
+ item["address_family"] = sorted(item["address_family"], key=lambda i: i["afi"])
+
+ objs = sorted(
+ objs,
+ key=lambda i: [
+ int(k) if k.isdigit() else k for k in i["name"].replace(".", "/").split("/")
+ ],
+ )
+
+ ansible_facts["ansible_network_resources"].pop("ospf_interfaces", None)
+
+ params = utils.remove_empties(
+ ospf_interfaces_parser.validate_config(
+ self.argument_spec,
+ {"config": objs},
+ redact=True,
+ ),
+ )
+
+ facts["ospf_interfaces"] = params.get("config", [])
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/ospfv2.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/ospfv2.py
new file mode 100644
index 00000000..248f3ed4
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv2/ospfv2.py
@@ -0,0 +1,94 @@
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos snmp 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 copy import deepcopy
+
+from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospfv2.ospfv2 import (
+ Ospfv2Args,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospfv2 import (
+ Ospfv2Template,
+)
+
+
+class Ospfv2Facts(object):
+ """The nxos snmp fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Ospfv2Args.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config | section '^router ospf .*'")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for interfaces
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ data = self.get_config(connection)
+
+ ipv4 = {"processes": []}
+ for section in data.split("router "):
+ rmmod = Ospfv2Template(lines=section.splitlines())
+ entry = rmmod.parse()
+
+ if entry:
+ global_vals = entry.get("vrfs", {}).pop("vrf_", {})
+ for key, value in iteritems(global_vals):
+ entry[key] = value
+
+ if "vrfs" in entry:
+ entry["vrfs"] = list(entry["vrfs"].values())
+
+ for vrf in entry["vrfs"]:
+ if "areas" in vrf:
+ vrf["areas"] = list(vrf["areas"].values())
+
+ if "areas" in entry:
+ entry["areas"] = list(entry["areas"].values())
+
+ ipv4["processes"].append(entry)
+
+ ansible_facts["ansible_network_resources"].pop("ospfv2", None)
+ facts = {}
+ params = utils.validate_config(self.argument_spec, {"config": ipv4})
+ params = utils.remove_empties(params)
+
+ facts["ospfv2"] = params.get("config", [])
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/ospfv3.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/ospfv3.py
new file mode 100644
index 00000000..796dacfb
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/ospfv3/ospfv3.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos ospfv3 fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+
+from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospfv3.ospfv3 import (
+ Ospfv3Args,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.ospfv3 import (
+ Ospfv3Template,
+)
+
+
+class Ospfv3Facts(object):
+ """The nxos ospfv3 facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Ospfv3Args.argument_spec
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config | section '^router ospfv3'")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Ospfv3 network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ if not data:
+ data = self.get_config(connection)
+
+ ipv6 = {"processes": []}
+ for section in data.split("router "):
+ rmmod = Ospfv3Template(lines=section.splitlines())
+ entry = rmmod.parse()
+
+ if entry:
+ global_vals = entry.get("vrfs", {}).pop("vrf_", {})
+ for key, value in iteritems(global_vals):
+ entry[key] = value
+
+ if "vrfs" in entry:
+ entry["vrfs"] = list(entry["vrfs"].values())
+
+ for vrf in entry["vrfs"]:
+ if "areas" in vrf:
+ vrf["areas"] = list(vrf["areas"].values())
+
+ if "areas" in entry:
+ entry["areas"] = list(entry["areas"].values())
+
+ if "address_family" in entry:
+ if "areas" in entry["address_family"]:
+ entry["address_family"]["areas"] = list(
+ entry["address_family"]["areas"].values(),
+ )
+
+ ipv6["processes"].append(entry)
+
+ ansible_facts["ansible_network_resources"].pop("ospfv3", None)
+ facts = {}
+ params = utils.validate_config(self.argument_spec, {"config": ipv6})
+ params = utils.remove_empties(params)
+
+ facts["ospfv3"] = params.get("config", [])
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/prefix_lists.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/prefix_lists.py
new file mode 100644
index 00000000..7bb18631
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/prefix_lists/prefix_lists.py
@@ -0,0 +1,79 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos prefix_lists 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.nxos.plugins.module_utils.network.nxos.argspec.prefix_lists.prefix_lists import (
+ Prefix_listsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.prefix_lists import (
+ Prefix_listsTemplate,
+)
+
+
+class Prefix_listsFacts(object):
+ """The nxos prefix_lists facts class"""
+
+ def __init__(self, module):
+ self._module = module
+ self.argument_spec = Prefix_listsArgs.argument_spec
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config | section 'ip(.*) prefix-list'")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Prefix_lists network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+ objs = []
+ if not data:
+ data = self.get_config(connection)
+
+ # parse native config using the Prefix_lists template
+ prefix_lists_parser = Prefix_listsTemplate(lines=data.splitlines(), module=self._module)
+
+ objs = list(prefix_lists_parser.parse().values())
+ if objs:
+ # pre-sort lists of dictionaries
+ for item in objs:
+ item["prefix_lists"] = sorted(
+ list(item["prefix_lists"].values()),
+ key=lambda k: k["name"],
+ )
+ for x in item["prefix_lists"]:
+ if "entries" in x:
+ x["entries"] = sorted(x["entries"], key=lambda k: k["sequence"])
+ objs = sorted(objs, key=lambda k: k["afi"])
+
+ ansible_facts["ansible_network_resources"].pop("prefix_lists", None)
+ params = utils.remove_empties(
+ prefix_lists_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
+ )
+ facts["prefix_lists"] = params.get("config", [])
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/route_maps.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/route_maps.py
new file mode 100644
index 00000000..d1bad913
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/route_maps/route_maps.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos route_maps 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.nxos.plugins.module_utils.network.nxos.argspec.route_maps.route_maps import (
+ Route_mapsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.route_maps import (
+ Route_mapsTemplate,
+)
+
+
+class Route_mapsFacts(object):
+ """The nxos route_maps facts class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Route_mapsArgs.argument_spec
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config | section '^route-map'")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Route_maps network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+ objs = []
+
+ if not data:
+ data = self.get_config(connection)
+
+ # parse native config using the Route_maps template
+ route_maps_parser = Route_mapsTemplate(lines=data.splitlines(), module=self._module)
+
+ objs = list(route_maps_parser.parse().values())
+
+ for item in objs:
+ item["entries"] = list(item["entries"].values())
+
+ ansible_facts["ansible_network_resources"].pop("route_maps", None)
+
+ params = utils.remove_empties(
+ route_maps_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
+ )
+
+ facts["route_maps"] = params.get("config", [])
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/snmp_server.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/snmp_server.py
new file mode 100644
index 00000000..c46e1a78
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/snmp_server/snmp_server.py
@@ -0,0 +1,85 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The nxos snmp_server 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.module_utils._text import to_text
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.snmp_server.snmp_server import (
+ Snmp_serverArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.rm_templates.snmp_server import (
+ Snmp_serverTemplate,
+)
+
+
+class Snmp_serverFacts(object):
+ """The nxos snmp_server facts class"""
+
+ def __init__(self, module):
+ self._module = module
+ self.argument_spec = Snmp_serverArgs.argument_spec
+
+ def get_config(self, connection):
+ """Wrapper method for `connection.get()`
+ This method exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get("show running-config | section '^snmp-server'")
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for Snmp_server network resource
+
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+
+ :rtype: dictionary
+ :returns: facts
+ """
+ facts = {}
+ objs = []
+
+ if not data:
+ data = self.get_config(connection)
+
+ # parse native config using the Snmp_server template
+ snmp_server_parser = Snmp_serverTemplate(lines=data.splitlines(), module=self._module)
+ objs = snmp_server_parser.parse()
+
+ if "communities" in objs:
+ objs["communities"] = sorted(objs["communities"], key=lambda k: to_text(k["name"]))
+
+ if "users" in objs:
+ if "auth" in objs["users"]:
+ objs["users"]["auth"] = sorted(
+ objs["users"]["auth"],
+ key=lambda k: to_text(k["user"]),
+ )
+ if "use_acls" in objs["users"]:
+ objs["users"]["use_acls"] = sorted(
+ objs["users"]["use_acls"],
+ key=lambda k: to_text(k["user"]),
+ )
+
+ ansible_facts["ansible_network_resources"].pop("snmp_server", None)
+
+ params = utils.remove_empties(
+ snmp_server_parser.validate_config(self.argument_spec, {"config": objs}, redact=True),
+ )
+
+ facts["snmp_server"] = params.get("config", {})
+ ansible_facts["ansible_network_resources"].update(facts)
+
+ return ansible_facts
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/static_routes.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/static_routes.py
new file mode 100644
index 00000000..b62d2580
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/static_routes/static_routes.py
@@ -0,0 +1,230 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The nxos static_routes fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.static_routes.static_routes import (
+ Static_routesArgs,
+)
+
+
+class Static_routesFacts(object):
+ """The nxos static_routes fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = Static_routesArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def get_device_data(self, connection, data):
+ vrf_data = []
+ non_vrf_data = []
+ if not data:
+ non_vrf_data = connection.get("show running-config | include '^ip(v6)* route'")
+ vrf_data = connection.get("show running-config | section '^vrf context'")
+ if non_vrf_data:
+ non_vrf_data = non_vrf_data.split("\n")
+ else:
+ non_vrf_data = []
+ vrf_data = vrf_data.split("\nvrf context")
+ # as we split based on 'vrf context', it is stripped from the data except the first element
+ else:
+ # used for parsed state where data is from the 'running-config' key
+ data = data.split("\n")
+ i = 0
+ while i <= (len(data) - 1):
+ if "vrf context " in data[i]:
+ vrf_conf = data[i]
+ j = i + 1
+ while j < len(data) and "vrf context " not in data[j]:
+ vrf_conf += "\n" + data[j]
+ j += 1
+ i = j
+ vrf_data.append(vrf_conf)
+ else:
+ non_vrf_data.append(data[i])
+ i += 1
+
+ new_vrf_data = []
+ for v in vrf_data:
+ if re.search(r"\n\s*ip(v6)? route", v):
+ new_vrf_data.append(v)
+ # dont consider vrf if it does not have routes
+ for i in range(len(new_vrf_data)):
+ if not re.search("^vrf context", new_vrf_data[i]):
+ new_vrf_data[i] = "vrf context" + new_vrf_data[i]
+
+ resources = non_vrf_data + new_vrf_data
+ return resources
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for static_routes
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+ resources = self.get_device_data(connection, data)
+ objs = self.render_config(self.generated_spec, resources)
+ ansible_facts["ansible_network_resources"].pop("static_routes", None)
+ facts = {}
+ if objs:
+ params = utils.validate_config(self.argument_spec, {"config": objs})
+ params = utils.remove_empties(params)
+ for c in params["config"]:
+ if c == {"vrf": "default"}:
+ params["config"].remove(c)
+ facts["static_routes"] = params["config"]
+ ansible_facts["ansible_network_resources"].update(facts)
+ return ansible_facts
+
+ def get_inner_dict(self, conf, inner_dict):
+ """
+ This method parses the command to create the innermost dictionary of the config
+ """
+ conf = re.sub(r"\s*ip(v6)? route", "", conf)
+ # strip 'ip route'
+ inner_dict["dest"] = re.match(r"^\s*(\S+\/\d+) .*", conf).group(1)
+
+ # ethernet1/2/23
+ iface = re.match(r".* (Ethernet|loopback|mgmt|port\-channel)(\S*) .*", conf)
+ i = ["Ethernet", "loopback", "mgmt", "port-channel"]
+ if iface and iface.group(1) in i:
+ inner_dict["interface"] = (iface.group(1)) + (iface.group(2))
+ conf = re.sub(inner_dict["interface"], "", conf)
+
+ if "." in inner_dict["dest"]:
+ conf = re.sub(inner_dict["dest"], "", conf)
+ inner_dict["afi"] = "ipv4"
+ ipv4 = re.match(r".* (\d+\.\d+\.\d+\.\d+\/?\d*).*", conf) # gets next hop ip
+ if ipv4:
+ inner_dict["forward_router_address"] = ipv4.group(1)
+ conf = re.sub(inner_dict["forward_router_address"], "", conf)
+ else:
+ inner_dict["afi"] = "ipv6"
+ conf = re.sub(inner_dict["dest"], "", conf)
+ ipv6 = re.match(r".* (\S*:\S*:\S*\/?\d*).*", conf)
+ if ipv6:
+ inner_dict["forward_router_address"] = ipv6.group(1)
+ conf = re.sub(inner_dict["forward_router_address"], "", conf)
+
+ nullif = re.search(r"null0", conf, re.IGNORECASE)
+ if nullif:
+ inner_dict["interface"] = "Null0"
+ inner_dict["forward_router_address"] = None
+ return inner_dict # dest IP not needed for null if
+
+ keywords = ["vrf", "name", "tag", "track"]
+ for key in keywords:
+ pattern = re.match(r".* (?:%s) (\S+).*" % key, conf)
+ if pattern:
+ if key == "vrf":
+ key = "dest_vrf"
+ elif key == "name":
+ key = "route_name"
+ inner_dict[key] = pattern.group(1).strip()
+ conf = re.sub(key + " " + inner_dict[key], "", conf)
+
+ pref = re.match(r"(?:.*) (\d+)$", conf)
+ if pref:
+ # if something is left at the end without any key, it is the pref
+ inner_dict["admin_distance"] = pref.group(1)
+ return inner_dict
+
+ def get_command(self, conf, afi_list, dest_list, af):
+ inner_dict = {}
+ inner_dict = self.get_inner_dict(conf, inner_dict)
+ if inner_dict["afi"] not in afi_list:
+ af.append({"afi": inner_dict["afi"], "routes": []})
+ afi_list.append(inner_dict["afi"])
+
+ next_hop = {}
+ params = [
+ "forward_router_address",
+ "interface",
+ "admin_distance",
+ "route_name",
+ "tag",
+ "track",
+ "dest_vrf",
+ ]
+ for p in params:
+ if p in inner_dict.keys():
+ next_hop.update({p: inner_dict[p]})
+
+ if inner_dict["dest"] not in dest_list:
+ dest_list.append(inner_dict["dest"])
+ af[-1]["routes"].append({"dest": inner_dict["dest"], "next_hops": []})
+ # if 'dest' is new, create new list under 'routes'
+ af[-1]["routes"][-1]["next_hops"].append(next_hop)
+ else:
+ af[-1]["routes"][-1]["next_hops"].append(next_hop)
+ # just append if dest already exists
+ return af
+
+ def render_config(self, spec, con):
+ """
+ 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)
+ config = []
+ global_afi_list = []
+ global_af = []
+ global_dest_list = []
+ if con:
+ for conf in con:
+ if conf.startswith("vrf context"):
+ svrf = re.match(r"vrf context (\S+)\n", conf).group(1)
+ afi_list = []
+ af = []
+ dest_list = []
+ config_dict = {"vrf": svrf, "address_families": []}
+ conf = conf.split("\n")
+ # considering from the second line as first line is 'vrf context..'
+ conf = conf[1:]
+ for c in conf:
+ if ("ip route" in c or "ipv6 route" in c) and "bfd" not in c:
+ self.get_command(c, afi_list, dest_list, af)
+ config_dict["address_families"] = af
+ config.append(config_dict)
+ else:
+ if ("ip route" in conf or "ipv6 route" in conf) and "bfd" not in conf:
+ self.get_command(conf, global_afi_list, global_dest_list, global_af)
+ if global_af:
+ config.append(utils.remove_empties({"address_families": global_af}))
+ return config
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/telemetry.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/telemetry.py
new file mode 100644
index 00000000..bdb28031
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/telemetry/telemetry.py
@@ -0,0 +1,185 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos telemetry fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.telemetry.telemetry import (
+ TelemetryArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.cmdref.telemetry.telemetry import (
+ TMS_DESTGROUP,
+ TMS_GLOBAL,
+ TMS_SENSORGROUP,
+ TMS_SUBSCRIPTION,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import NxosCmdRef
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.telemetry.telemetry import (
+ cr_key_lookup,
+ get_instance_data,
+ normalize_data,
+)
+
+
+class TelemetryFacts(object):
+ """The nxos telemetry fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = TelemetryArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for telemetry
+ :param connection: the device connection
+ :param ansible_facts: Facts dictionary
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ if connection: # just for linting purposes, remove
+ pass
+
+ cmd_ref = {}
+ cmd_ref["TMS_GLOBAL"] = {}
+ cmd_ref["TMS_DESTGROUP"] = {}
+ cmd_ref["TMS_SENSORGROUP"] = {}
+ cmd_ref["TMS_SUBSCRIPTION"] = {}
+
+ # For fact gathering, module state should be 'present' when using
+ # NxosCmdRef to query state
+ if self._module.params.get("state"):
+ saved_module_state = self._module.params["state"]
+ self._module.params["state"] = "present"
+
+ # Get Telemetry Global Data
+ cmd_ref["TMS_GLOBAL"]["ref"] = []
+ cmd_ref["TMS_GLOBAL"]["ref"].append(NxosCmdRef(self._module, TMS_GLOBAL))
+ ref = cmd_ref["TMS_GLOBAL"]["ref"][0]
+ ref.set_context()
+ ref.get_existing()
+ device_cache = ref.cache_existing
+
+ if device_cache is None:
+ device_cache_lines = []
+ else:
+ device_cache_lines = device_cache.split("\n")
+
+ # Get Telemetry Destination Group Data
+ cmd_ref["TMS_DESTGROUP"]["ref"] = []
+ for line in device_cache_lines:
+ if re.search(r"destination-group", line):
+ resource_key = line.strip()
+ cmd_ref["TMS_DESTGROUP"]["ref"].append(NxosCmdRef(self._module, TMS_DESTGROUP))
+ ref = cmd_ref["TMS_DESTGROUP"]["ref"][-1]
+ ref.set_context([resource_key])
+ ref.get_existing(device_cache)
+ normalize_data(ref)
+
+ # Get Telemetry Sensorgroup Group Data
+ cmd_ref["TMS_SENSORGROUP"]["ref"] = []
+ for line in device_cache_lines:
+ if re.search(r"sensor-group", line):
+ resource_key = line.strip()
+ cmd_ref["TMS_SENSORGROUP"]["ref"].append(NxosCmdRef(self._module, TMS_SENSORGROUP))
+ ref = cmd_ref["TMS_SENSORGROUP"]["ref"][-1]
+ ref.set_context([resource_key])
+ ref.get_existing(device_cache)
+
+ # Get Telemetry Subscription Data
+ cmd_ref["TMS_SUBSCRIPTION"]["ref"] = []
+ for line in device_cache_lines:
+ if re.search(r"subscription", line):
+ resource_key = line.strip()
+ cmd_ref["TMS_SUBSCRIPTION"]["ref"].append(
+ NxosCmdRef(self._module, TMS_SUBSCRIPTION),
+ )
+ ref = cmd_ref["TMS_SUBSCRIPTION"]["ref"][-1]
+ ref.set_context([resource_key])
+ ref.get_existing(device_cache)
+
+ objs = []
+ objs = self.render_config(self.generated_spec, cmd_ref)
+ facts = {"telemetry": {}}
+ if objs:
+ # params = utils.validate_config(self.argument_spec, {'config': objs})
+ facts["telemetry"] = objs
+
+ ansible_facts["ansible_network_resources"].update(facts)
+ if self._module.params.get("state"):
+ self._module.params["state"] = saved_module_state
+ return ansible_facts
+
+ def render_config(self, spec, cmd_ref):
+ """
+ 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)
+ config["destination_groups"] = []
+ config["sensor_groups"] = []
+ config["subscriptions"] = []
+ managed_objects = [
+ "TMS_GLOBAL",
+ "TMS_DESTGROUP",
+ "TMS_SENSORGROUP",
+ "TMS_SUBSCRIPTION",
+ ]
+
+ # Walk the argspec and cmd_ref objects and build out config dict.
+ for key in config.keys():
+ for mo in managed_objects:
+ for cr in cmd_ref[mo]["ref"]:
+ cr_keys = cr_key_lookup(key, mo)
+ for cr_key in cr_keys:
+ if cr._ref.get(cr_key) and cr._ref[cr_key].get("existing"):
+ if isinstance(config[key], dict):
+ for k in config[key].keys():
+ for existing_key in cr._ref[cr_key]["existing"].keys():
+ config[key][k] = cr._ref[cr_key]["existing"][existing_key][
+ k
+ ]
+ continue
+ if isinstance(config[key], list):
+ for existing_key in cr._ref[cr_key]["existing"].keys():
+ data = get_instance_data(key, cr_key, cr, existing_key)
+ config[key].append(data)
+ continue
+ for existing_key in cr._ref[cr_key]["existing"].keys():
+ config[key] = cr._ref[cr_key]["existing"][existing_key]
+ elif cr._ref.get(cr_key):
+ data = get_instance_data(key, cr_key, cr, None)
+ if isinstance(config[key], list) and data not in config[key]:
+ config[key].append(data)
+
+ return utils.remove_empties(config)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/vlans.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/vlans.py
new file mode 100644
index 00000000..32968d2d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/facts/vlans/vlans.py
@@ -0,0 +1,197 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)#!/usr/bin/python
+
+"""
+The nxos vlans fact class
+It is in this file the configuration is collected from the device
+for a given resource, parsed, and the facts tree is populated
+based on the configuration.
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import ast
+import re
+
+from copy import deepcopy
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ parse_conf_arg,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.vlans.vlans import (
+ VlansArgs,
+)
+
+
+class VlansFacts(object):
+ """The nxos vlans fact class"""
+
+ def __init__(self, module, subspec="config", options="options"):
+ self._module = module
+ self.argument_spec = VlansArgs.argument_spec
+ spec = deepcopy(self.argument_spec)
+ if subspec:
+ if options:
+ facts_argument_spec = spec[subspec][options]
+ else:
+ facts_argument_spec = spec[subspec]
+ else:
+ facts_argument_spec = spec
+
+ self.generated_spec = utils.generate_dict(facts_argument_spec)
+
+ def get_device_data(self, connection, show_cmd):
+ """Wrapper method for `connection.get()`
+ This exists solely to allow the unit test framework to mock device connection calls.
+ """
+ return connection.get(show_cmd)
+
+ def populate_facts(self, connection, ansible_facts, data=None):
+ """Populate the facts for vlans
+ :param connection: the device connection
+ :param data: previously collected conf
+ :rtype: dictionary
+ :returns: facts
+ """
+ objs = []
+ # **TBD**
+ # N7K EOL/legacy image 6.2 does not support show vlan | json output.
+ # If support is still required for this image then:
+ # - Wrapp the json calls below in a try/except
+ # - When excepted, use a helper method to parse the run_cfg_output,
+ # using the run_cfg_output data to generate compatible json data that
+ # can be read by normalize_table_data.
+ if not data:
+ # Use structured for most of the vlan parameter states.
+ # This data is consistent across the supported nxos platforms.
+ try:
+ # Not all devices support | json-pretty but is a workaround for
+ # libssh issue https://github.com/ansible/pylibssh/issues/208
+ structured = self.get_device_data(connection, "show vlan | json-pretty")
+ except Exception:
+ # When json-pretty is not supported, we fall back to | json
+ structured = self.get_device_data(connection, "show vlan | json")
+
+ # Raw cli config is needed for mapped_vni, which is not included in structured.
+ run_cfg_output = self.get_device_data(connection, "show running-config | section ^vlan")
+ else:
+ running_config = data.split("\n\n")
+ structured, run_cfg_output = running_config[0], running_config[1]
+
+ # Create a single dictionary from all data sources
+ data = self.normalize_table_data(structured, run_cfg_output)
+
+ for vlan in data:
+ obj = self.render_config(self.generated_spec, vlan)
+ if obj:
+ objs.append(obj)
+
+ ansible_facts["ansible_network_resources"].pop("vlans", None)
+ 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, vlan):
+ """
+ Render config as dictionary structure and delete keys
+ from spec for null values
+ :param spec: The facts tree, generated from the argspec
+ :param vlan: structured data vlan settings (dict) and raw cfg from device
+ :rtype: dictionary
+ :returns: The generated config
+ Sample inputs: test/units/modules/network/nxos/fixtures/nxos_vlans/show_vlan
+ """
+ obj = deepcopy(spec)
+
+ obj["vlan_id"] = vlan["vlan_id"]
+
+ # name: 'VLAN000x' (default name) or custom name
+ name = vlan["vlanshowbr-vlanname"]
+ if name and re.match("VLAN%04d" % int(vlan["vlan_id"]), name):
+ name = None
+ obj["name"] = name
+
+ # mode: 'ce-vlan' or 'fabricpath-vlan'
+ obj["mode"] = vlan["vlanshowinfo-vlanmode"].replace("-vlan", "")
+
+ # enabled: shutdown, noshutdown
+ obj["enabled"] = True if "noshutdown" in vlan["vlanshowbr-shutstate"] else False
+
+ # state: active, suspend
+ obj["state"] = vlan["vlanshowbr-vlanstate"]
+
+ # non-structured data
+ obj["mapped_vni"] = parse_conf_arg(vlan["run_cfg"], "vn-segment")
+
+ return utils.remove_empties(obj)
+
+ def normalize_table_data(self, structured, run_cfg_output):
+ """Normalize structured output and raw running-config output into
+ a single dict to simplify render_config usage.
+ This is needed because:
+ - The NXOS devices report most of the vlan settings within two
+ structured data keys: 'vlanbrief' and 'mtuinfo', but the output is
+ incomplete and therefore raw running-config data is also needed.
+ - running-config by itself is insufficient because of major differences
+ in the cli config syntax across platforms.
+ - Thus a helper method combines settings from the separate top-level keys,
+ and adds a 'run_cfg' key containing raw cli from the device.
+ """
+ # device output may be string, convert to list
+ structured = ast.literal_eval(str(structured))
+
+ vlanbrief = []
+ mtuinfo = []
+ if "TABLE_vlanbrief" in structured:
+ # SAMPLE: {"TABLE_vlanbriefid": {"ROW_vlanbriefid": {
+ # "vlanshowbr-vlanid": "4", "vlanshowbr-vlanid-utf": "4",
+ # "vlanshowbr-vlanname": "VLAN0004", "vlanshowbr-vlanstate": "active",
+ # "vlanshowbr-shutstate": "noshutdown"}},
+ vlanbrief = structured["TABLE_vlanbrief"]["ROW_vlanbrief"]
+
+ # SAMPLE: "TABLE_mtuinfoid": {"ROW_mtuinfoid": {
+ # "vlanshowinfo-vlanid": "4", "vlanshowinfo-media-type": "enet",
+ # "vlanshowinfo-vlanmode": "ce-vlan"}}
+ mtuinfo = structured["TABLE_mtuinfo"]["ROW_mtuinfo"]
+
+ if type(vlanbrief) is not list:
+ # vlanbrief is not a list when only one vlan is found.
+ vlanbrief = [vlanbrief]
+ mtuinfo = [mtuinfo]
+
+ # split out any per-vlan cli config
+ run_cfg_list = re.split(r"[\n^]vlan ", run_cfg_output)
+
+ # Create a list of vlan dicts where each dict contains vlanbrief,
+ # mtuinfo, and non-structured running-config data for one vlan.
+ vlans = []
+ for index, v in enumerate(vlanbrief):
+ v["vlan_id"] = v.get("vlanshowbr-vlanid-utf")
+ vlan = {}
+ vlan.update(v)
+ vlan.update(mtuinfo[index])
+
+ vlan["run_cfg"] = ""
+ for item in run_cfg_list:
+ # Sample match lines
+ # 202\n name Production-Segment-100101\n vn-segment 100101
+ # 5\n state suspend\n shutdown\n name test-changeme\n vn-segment 942
+ pattern = r"^{0}\s+\S.*vn-segment".format(v["vlan_id"])
+ if re.search(pattern, item, flags=re.DOTALL):
+ vlan["run_cfg"] = item
+ break
+
+ vlans.append(vlan)
+ return vlans
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/nxos.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/nxos.py
new file mode 100644
index 00000000..f0e3843e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/nxos.py
@@ -0,0 +1,1031 @@
+#
+# This code is part of Ansible, but is an independent component.
+#
+# This particular file snippet, and this file snippet only, is BSD licensed.
+# Modules you write using this snippet, which is embedded dynamically by Ansible
+# still belong to the author of the module, and may assign their own license
+# to the complete work.
+#
+# Copyright: (c) 2017, Red Hat Inc.
+#
+# Redistribution and use in source and binary forms, with or without modification,
+# are permitted provided that the following conditions are met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright notice,
+# this list of conditions and the following disclaimer in the documentation
+# and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+import json
+import re
+
+from copy import deepcopy
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.common._collections_compat import Mapping
+from ansible.module_utils.connection import Connection, ConnectionError
+from ansible.module_utils.six import PY2, PY3
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+ NetworkConfig,
+ dumps,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ ComplexList,
+ to_list,
+)
+
+
+try:
+ import yaml
+
+ HAS_YAML = True
+except ImportError:
+ HAS_YAML = False
+
+
+_DEVICE_CONNECTION = None
+
+
+def get_connection(module):
+ global _DEVICE_CONNECTION
+ if not _DEVICE_CONNECTION:
+ connection_proxy = Connection(module._socket_path)
+ cap = json.loads(connection_proxy.get_capabilities())
+ if cap["network_api"] == "cliconf":
+ conn = Cli(module)
+ elif cap["network_api"] == "nxapi":
+ conn = HttpApi(module)
+ _DEVICE_CONNECTION = conn
+ return _DEVICE_CONNECTION
+
+
+class Cli:
+ def __init__(self, module):
+ self._module = module
+ self._device_configs = {}
+ self._connection = None
+
+ def _get_connection(self):
+ if self._connection:
+ return self._connection
+ self._connection = Connection(self._module._socket_path)
+
+ return self._connection
+
+ def get_config(self, flags=None):
+ """Retrieves the current config from the device or cache"""
+ flags = [] if flags is None else flags
+
+ cmd = "show running-config "
+ cmd += " ".join(flags)
+ cmd = cmd.strip()
+
+ try:
+ return self._device_configs[cmd]
+ except KeyError:
+ connection = self._get_connection()
+ try:
+ out = connection.get_config(flags=flags)
+ except ConnectionError as exc:
+ self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+
+ cfg = to_text(out, errors="surrogate_then_replace").strip() + "\n"
+ self._device_configs[cmd] = cfg
+ return cfg
+
+ def run_commands(self, commands, check_rc=True):
+ """Run list of commands on remote device and return results"""
+ connection = self._get_connection()
+
+ try:
+ out = connection.run_commands(commands, check_rc)
+ if check_rc == "retry_json":
+ capabilities = self.get_capabilities()
+ network_api = capabilities.get("network_api")
+
+ if network_api == "cliconf" and out:
+ for index, resp in enumerate(out):
+ if (
+ "Invalid command at" in resp or "Ambiguous command at" in resp
+ ) and "json" in resp:
+ if commands[index]["output"] == "json":
+ commands[index]["output"] = "text"
+ out = connection.run_commands(commands, check_rc)
+ return out
+ except ConnectionError as exc:
+ self._module.fail_json(msg=to_text(exc))
+
+ def load_config(self, config, return_error=False, opts=None, replace=None):
+ """Sends configuration commands to the remote device"""
+ if opts is None:
+ opts = {}
+
+ connection = self._get_connection()
+ responses = []
+ try:
+ resp = connection.edit_config(config, replace=replace)
+ if isinstance(resp, Mapping):
+ resp = resp["response"]
+ except ConnectionError as e:
+ code = getattr(e, "code", 1)
+ message = getattr(e, "err", e)
+ err = to_text(message, errors="surrogate_then_replace")
+ if opts.get("ignore_timeout") and code:
+ responses.append(err)
+ return responses
+ elif code and "no graceful-restart" in err:
+ if "ISSU/HA will be affected if Graceful Restart is disabled" in err:
+ msg = [""]
+ responses.extend(msg)
+ return responses
+ else:
+ self._module.fail_json(msg=err)
+ elif code:
+ self._module.fail_json(msg=err)
+
+ responses.extend(resp)
+ return responses
+
+ def get_diff(
+ self,
+ candidate=None,
+ running=None,
+ diff_match="line",
+ diff_ignore_lines=None,
+ path=None,
+ diff_replace="line",
+ ):
+ conn = self._get_connection()
+ try:
+ response = conn.get_diff(
+ candidate=candidate,
+ running=running,
+ diff_match=diff_match,
+ diff_ignore_lines=diff_ignore_lines,
+ path=path,
+ diff_replace=diff_replace,
+ )
+ except ConnectionError as exc:
+ self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+ return response
+
+ def get_capabilities(self):
+ """Returns platform info of the remove device"""
+ if hasattr(self._module, "_capabilities"):
+ return self._module._capabilities
+
+ connection = self._get_connection()
+ try:
+ capabilities = connection.get_capabilities()
+ except ConnectionError as exc:
+ self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+ self._module._capabilities = json.loads(capabilities)
+ return self._module._capabilities
+
+ def read_module_context(self, module_key):
+ connection = self._get_connection()
+ try:
+ module_context = connection.read_module_context(module_key)
+ except ConnectionError as exc:
+ self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+
+ return module_context
+
+ def save_module_context(self, module_key, module_context):
+ connection = self._get_connection()
+ try:
+ connection.save_module_context(module_key, module_context)
+ except ConnectionError as exc:
+ self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+
+ return None
+
+
+class HttpApi:
+ def __init__(self, module):
+ self._module = module
+ self._device_configs = {}
+ self._module_context = {}
+ self._connection_obj = None
+
+ @property
+ def _connection(self):
+ if not self._connection_obj:
+ self._connection_obj = Connection(self._module._socket_path)
+
+ return self._connection_obj
+
+ def run_commands(self, commands, check_rc=True):
+ """Runs list of commands on remote device and returns results"""
+ try:
+ out = self._connection.send_request(commands)
+ except ConnectionError as exc:
+ if check_rc is True:
+ raise
+ out = to_text(exc)
+
+ out = to_list(out)
+ if not out[0]:
+ return out
+
+ for index, response in enumerate(out):
+ if response[0] == "{":
+ out[index] = json.loads(response)
+
+ return out
+
+ def get_config(self, flags=None):
+ """Retrieves the current config from the device or cache"""
+ flags = [] if flags is None else flags
+
+ cmd = "show running-config "
+ cmd += " ".join(flags)
+ cmd = cmd.strip()
+
+ try:
+ return self._device_configs[cmd]
+ except KeyError:
+ try:
+ out = self._connection.send_request(cmd)
+ except ConnectionError as exc:
+ self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+
+ cfg = to_text(out).strip()
+ self._device_configs[cmd] = cfg
+ return cfg
+
+ def get_diff(
+ self,
+ candidate=None,
+ running=None,
+ diff_match="line",
+ diff_ignore_lines=None,
+ path=None,
+ diff_replace="line",
+ ):
+ diff = {}
+
+ # prepare candidate configuration
+ candidate_obj = NetworkConfig(indent=2)
+ candidate_obj.load(candidate)
+
+ if running and diff_match != "none" and diff_replace != "config":
+ # running configuration
+ running_obj = NetworkConfig(indent=2, contents=running, ignore_lines=diff_ignore_lines)
+ configdiffobjs = candidate_obj.difference(
+ running_obj,
+ path=path,
+ match=diff_match,
+ replace=diff_replace,
+ )
+
+ else:
+ configdiffobjs = candidate_obj.items
+
+ diff["config_diff"] = dumps(configdiffobjs, "commands") if configdiffobjs else ""
+ return diff
+
+ def load_config(self, commands, return_error=False, opts=None, replace=None):
+ """Sends the ordered set of commands to the device"""
+ if opts is None:
+ opts = {}
+
+ responses = []
+ try:
+ resp = self.edit_config(commands, replace=replace)
+ except ConnectionError as exc:
+ code = getattr(exc, "code", 1)
+ message = getattr(exc, "err", exc)
+ err = to_text(message, errors="surrogate_then_replace")
+ if opts.get("ignore_timeout") and code:
+ responses.append(code)
+ return responses
+ elif opts.get("catch_clierror") and "400" in code:
+ return [code, err]
+ elif code and "no graceful-restart" in err:
+ if "ISSU/HA will be affected if Graceful Restart is disabled" in err:
+ msg = [""]
+ responses.extend(msg)
+ return responses
+ else:
+ self._module.fail_json(msg=err)
+ elif code:
+ self._module.fail_json(msg=err)
+
+ responses.extend(resp)
+ return responses
+
+ def edit_config(self, candidate=None, commit=True, replace=None, comment=None):
+ resp = list()
+
+ self.check_edit_config_capability(candidate, commit, replace, comment)
+
+ if replace:
+ candidate = "config replace {0}".format(replace)
+
+ responses = self._connection.send_request(candidate, output="config")
+ for response in to_list(responses):
+ if response != "{}":
+ resp.append(response)
+ if not resp:
+ resp = [""]
+
+ return resp
+
+ def get_capabilities(self):
+ """Returns platform info of the remove device"""
+ try:
+ capabilities = self._connection.get_capabilities()
+ except ConnectionError as exc:
+ self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+
+ return json.loads(capabilities)
+
+ def check_edit_config_capability(self, candidate=None, commit=True, replace=None, comment=None):
+ operations = self._connection.get_device_operations()
+
+ if not candidate and not replace:
+ raise ValueError("must provide a candidate or replace to load configuration")
+
+ if commit not in (True, False):
+ raise ValueError("'commit' must be a bool, got %s" % commit)
+
+ if replace and not operations.get("supports_replace"):
+ raise ValueError("configuration replace is not supported")
+
+ if comment and not operations.get("supports_commit_comment", False):
+ raise ValueError("commit comment is not supported")
+
+ def read_module_context(self, module_key):
+ try:
+ module_context = self._connection.read_module_context(module_key)
+ except ConnectionError as exc:
+ self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+
+ return module_context
+
+ def save_module_context(self, module_key, module_context):
+ try:
+ self._connection.save_module_context(module_key, module_context)
+ except ConnectionError as exc:
+ self._module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+
+ return None
+
+
+class NxosCmdRef:
+ """NXOS Command Reference utilities.
+ The NxosCmdRef class takes a yaml-formatted string of nxos module commands
+ and converts it into dict-formatted database of getters/setters/defaults
+ and associated common and platform-specific values. The utility methods
+ add additional data such as existing states, playbook states, and proposed cli.
+ The utilities also abstract away platform differences such as different
+ defaults and different command syntax.
+
+ Callers must provide a yaml formatted string that defines each command and
+ its properties; e.g. BFD global:
+ ---
+ _template: # _template holds common settings for all commands
+ # Enable feature bfd if disabled
+ feature: bfd
+ # Common getter syntax for BFD commands
+ get_command: show run bfd all | incl '^(no )*bfd'
+
+ interval:
+ kind: dict
+ getval: bfd interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
+ setval: bfd interval {tx} min_rx {min_rx} multiplier {multiplier}
+ default:
+ tx: 50
+ min_rx: 50
+ multiplier: 3
+ N3K:
+ # Platform overrides
+ default:
+ tx: 250
+ min_rx: 250
+ multiplier: 3
+ """
+
+ def __init__(self, module, cmd_ref_str, ref_only=False):
+ """Initialize cmd_ref from yaml data."""
+
+ self._module = module
+ self._check_imports()
+ self._yaml_load(cmd_ref_str)
+ self.cache_existing = None
+ self.present_states = ["present", "merged", "replaced"]
+ self.absent_states = ["absent", "deleted"]
+ ref = self._ref
+
+ # Create a list of supported commands based on ref keys
+ ref["commands"] = sorted([k for k in ref if not k.startswith("_")])
+ ref["_proposed"] = []
+ ref["_context"] = []
+ ref["_resource_key"] = None
+
+ if not ref_only:
+ ref["_state"] = module.params.get("state", "present")
+ self.feature_enable()
+ self.get_platform_defaults()
+ self.normalize_defaults()
+
+ def __getitem__(self, key=None):
+ if key is None:
+ return self._ref
+ return self._ref[key]
+
+ def _check_imports(self):
+ module = self._module
+ msg = nxosCmdRef_import_check()
+ if msg:
+ module.fail_json(msg=msg)
+
+ def _yaml_load(self, cmd_ref_str):
+ if PY2:
+ self._ref = yaml.load(cmd_ref_str)
+ elif PY3:
+ self._ref = yaml.load(cmd_ref_str, Loader=yaml.FullLoader)
+
+ def feature_enable(self):
+ """Add 'feature <foo>' to _proposed if ref includes a 'feature' key."""
+ ref = self._ref
+ feature = ref["_template"].get("feature")
+ if feature:
+ show_cmd = "show run | incl 'feature {0}'".format(feature)
+ output = self.execute_show_command(show_cmd, "text")
+ if not output or "CLI command error" in output:
+ msg = "** 'feature {0}' is not enabled. Module will auto-enable feature {0} ** ".format(
+ feature,
+ )
+ self._module.warn(msg)
+ ref["_proposed"].append("feature {0}".format(feature))
+ ref["_cli_is_feature_disabled"] = ref["_proposed"]
+
+ def get_platform_shortname(self):
+ """Query device for platform type, normalize to a shortname/nickname.
+ Returns platform shortname (e.g. 'N3K-3058P' returns 'N3K') or None.
+ """
+ # TBD: add this method logic to get_capabilities() after those methods
+ # are made consistent across transports
+ platform_info = self.execute_show_command("show inventory", "json")
+ if not platform_info or not isinstance(platform_info, dict):
+ return None
+ inventory_table = platform_info["TABLE_inv"]["ROW_inv"]
+ for info in inventory_table:
+ if "Chassis" in info["name"]:
+ network_os_platform = info["productid"]
+ break
+ else:
+ return None
+
+ # Supported Platforms: N3K,N5K,N6K,N7K,N9K,N3K-F,N9K-F
+ m = re.match("(?P<short>N[35679][K57])-(?P<N35>C35)*", network_os_platform)
+ if not m:
+ return None
+ shortname = m.group("short")
+
+ # Normalize
+ if m.groupdict().get("N35"):
+ shortname = "N35"
+ elif re.match("N77", shortname):
+ shortname = "N7K"
+ elif re.match(r"N3K|N9K", shortname):
+ for info in inventory_table:
+ if "-R" in info["productid"]:
+ # Fretta Platform
+ shortname += "-F"
+ break
+ return shortname
+
+ def get_platform_defaults(self):
+ """Update ref with platform specific defaults"""
+ plat = self.get_platform_shortname()
+ if not plat:
+ return
+
+ ref = self._ref
+ ref["_platform_shortname"] = plat
+ # Remove excluded commands (no platform support for command)
+ for k in ref["commands"]:
+ if plat in ref[k].get("_exclude", ""):
+ ref["commands"].remove(k)
+
+ # Update platform-specific settings for each item in ref
+ plat_spec_cmds = [k for k in ref["commands"] if plat in ref[k]]
+ for k in plat_spec_cmds:
+ for plat_key in ref[k][plat]:
+ ref[k][plat_key] = ref[k][plat][plat_key]
+
+ def normalize_defaults(self):
+ """Update ref defaults with normalized data"""
+ ref = self._ref
+ for k in ref["commands"]:
+ if "default" in ref[k] and ref[k]["default"]:
+ kind = ref[k]["kind"]
+ if "int" == kind:
+ ref[k]["default"] = int(ref[k]["default"])
+ elif "list" == kind:
+ ref[k]["default"] = [str(i) for i in ref[k]["default"]]
+ elif "dict" == kind:
+ for key, v in ref[k]["default"].items():
+ if v:
+ v = str(v)
+ ref[k]["default"][key] = v
+
+ def execute_show_command(self, command, format):
+ """Generic show command helper.
+ Warning: 'CLI command error' exceptions are caught, must be handled by caller.
+ Return device output as a newline-separated string or None.
+ """
+ cmds = [{"command": command, "output": format}]
+ output = None
+ try:
+ output = run_commands(self._module, cmds)
+ if output:
+ output = output[0]
+ except ConnectionError as exc:
+ if "CLI command error" in repr(exc):
+ # CLI may be feature disabled
+ output = repr(exc)
+ else:
+ raise
+ return output
+
+ def pattern_match_existing(self, output, k):
+ """Pattern matching helper for `get_existing`.
+ `k` is the command name string. Use the pattern from cmd_ref to
+ find a matching string in the output.
+ Return regex match object or None.
+ """
+ ref = self._ref
+ pattern = re.compile(ref[k]["getval"])
+ multiple = "multiple" in ref[k].keys()
+ match_lines = [re.search(pattern, line) for line in output]
+ if "dict" == ref[k]["kind"]:
+ match = [m for m in match_lines if m]
+ if not match:
+ return None
+ if len(match) > 1 and not multiple:
+ raise ValueError("get_existing: multiple matches found for property {0}".format(k))
+ else:
+ match = [m.groups() for m in match_lines if m]
+ if not match:
+ return None
+ if len(match) > 1 and not multiple:
+ raise ValueError("get_existing: multiple matches found for property {0}".format(k))
+ for item in match:
+ index = match.index(item)
+ match[index] = list(item) # tuple to list
+
+ # Handle config strings that nvgen with the 'no' prefix.
+ # Example match behavior:
+ # When pattern is: '(no )*foo *(\S+)*$' AND
+ # When output is: 'no foo' -> match: ['no ', None]
+ # When output is: 'foo 50' -> match: [None, '50']
+ if None is match[index][0]:
+ match[index].pop(0)
+ elif "no" in match[index][0]:
+ match[index].pop(0)
+ if not match:
+ return None
+
+ return match
+
+ def set_context(self, context=None):
+ """Update ref with command context."""
+ if context is None:
+ context = []
+ ref = self._ref
+ # Process any additional context that this propoerty might require.
+ # 1) Global context from NxosCmdRef _template.
+ # 2) Context passed in using context arg.
+ ref["_context"] = ref["_template"].get("context", [])
+ for cmd in context:
+ ref["_context"].append(cmd)
+ # Last key in context is the resource key
+ ref["_resource_key"] = context[-1] if context else ref["_resource_key"]
+
+ def get_existing(self, cache_output=None):
+ """Update ref with existing command states from the device.
+ Store these states in each command's 'existing' key.
+ """
+ ref = self._ref
+ if ref.get("_cli_is_feature_disabled"):
+ # Add context to proposed if state is present
+ if ref["_state"] in self.present_states:
+ [ref["_proposed"].append(ctx) for ctx in ref["_context"]]
+ return
+
+ show_cmd = ref["_template"]["get_command"]
+ if cache_output:
+ output = cache_output
+ else:
+ output = self.execute_show_command(show_cmd, "text") or []
+ self.cache_existing = output
+
+ # Add additional command context if needed.
+ if ref["_context"]:
+ output = CustomNetworkConfig(indent=2, contents=output)
+ output = output.get_section(ref["_context"])
+
+ if not output:
+ # Add context to proposed if state is present
+ if ref["_state"] in self.present_states:
+ [ref["_proposed"].append(ctx) for ctx in ref["_context"]]
+ return
+
+ # We need to remove the last item in context for state absent case.
+ if ref["_state"] in self.absent_states and ref["_context"]:
+ if ref["_resource_key"] and ref["_resource_key"] == ref["_context"][-1]:
+ if ref["_context"][-1] in output:
+ ref["_context"][-1] = "no " + ref["_context"][-1]
+ else:
+ del ref["_context"][-1]
+ return
+
+ # Walk each cmd in ref, use cmd pattern to discover existing cmds
+ output = output.split("\n")
+ for k in ref["commands"]:
+ match = self.pattern_match_existing(output, k)
+ if not match:
+ continue
+ ref[k]["existing"] = {}
+ for item in match:
+ index = match.index(item)
+ kind = ref[k]["kind"]
+ if "int" == kind:
+ ref[k]["existing"][index] = int(item[0])
+ elif "list" == kind:
+ ref[k]["existing"][index] = [str(i) for i in item[0]]
+ elif "dict" == kind:
+ # The getval pattern should contain regex named group keys that
+ # match up with the setval named placeholder keys; e.g.
+ # getval: my-cmd (?P<foo>\d+) bar (?P<baz>\d+)
+ # setval: my-cmd {foo} bar {baz}
+ ref[k]["existing"][index] = {}
+ for key in item.groupdict().keys():
+ ref[k]["existing"][index][key] = str(item.group(key))
+ elif "str" == kind:
+ ref[k]["existing"][index] = item[0]
+ else:
+ raise ValueError(
+ "get_existing: unknown 'kind' value specified for key '{0}'".format(k),
+ )
+
+ def get_playvals(self):
+ """Update ref with values from the playbook.
+ Store these values in each command's 'playval' key.
+ """
+ ref = self._ref
+ module = self._module
+ params = {}
+ if module.params.get("config"):
+ # Resource module builder packs playvals under 'config' key
+ param_data = module.params.get("config")
+ params["global"] = param_data
+ for key in param_data.keys():
+ if isinstance(param_data[key], list):
+ params[key] = param_data[key]
+ else:
+ params["global"] = module.params
+ for k in ref.keys():
+ for level in params.keys():
+ if isinstance(params[level], dict):
+ params[level] = [params[level]]
+ for item in params[level]:
+ if k in item and item[k] is not None:
+ if not ref[k].get("playval"):
+ ref[k]["playval"] = {}
+ playval = item[k]
+ index = params[level].index(item)
+ # Normalize each value
+ if "int" == ref[k]["kind"]:
+ playval = int(playval)
+ elif "list" == ref[k]["kind"]:
+ playval = [str(i) for i in playval]
+ elif "dict" == ref[k]["kind"]:
+ for key, v in playval.items():
+ playval[key] = str(v)
+ ref[k]["playval"][index] = playval
+
+ def build_cmd_set(self, playval, existing, k):
+ """Helper function to create list of commands to configure device
+ Return a list of commands
+ """
+ ref = self._ref
+ proposed = ref["_proposed"]
+ cmd = None
+ kind = ref[k]["kind"]
+ if "int" == kind:
+ cmd = ref[k]["setval"].format(playval)
+ elif "list" == kind:
+ cmd = ref[k]["setval"].format(*(playval))
+ elif "dict" == kind:
+ # The setval pattern should contain placeholder keys that
+ # match up with the getval regex named group keys; e.g.
+ # getval: my-cmd (?P<foo>\d+) bar (?P<baz>\d+)
+ # setval: my-cmd {foo} bar {baz}
+ if ref[k]["setval"].startswith("path"):
+ tmplt = "path {name}"
+ if "depth" in playval:
+ tmplt += " depth {depth}"
+ if "query_condition" in playval:
+ tmplt += " query-condition {query_condition}"
+ if "filter_condition" in playval:
+ tmplt += " filter-condition {filter_condition}"
+ cmd = tmplt.format(**playval)
+ else:
+ cmd = ref[k]["setval"].format(**playval)
+ elif "str" == kind:
+ if "deleted" in str(playval):
+ if existing:
+ cmd = "no " + ref[k]["setval"].format(existing)
+ else:
+ cmd = ref[k]["setval"].format(playval)
+ else:
+ raise ValueError("get_proposed: unknown 'kind' value specified for key '{0}'".format(k))
+ if cmd:
+ if ref["_state"] in self.absent_states and not re.search(r"^no", cmd):
+ cmd = "no " + cmd
+ # Commands may require parent commands for proper context.
+ # Global _template context is replaced by parameter context
+ [proposed.append(ctx) for ctx in ref["_context"]]
+ [proposed.append(ctx) for ctx in ref[k].get("context", [])]
+ proposed.append(cmd)
+
+ def get_proposed(self):
+ """Compare playbook values against existing states and create a list
+ of proposed commands.
+ Return a list of raw cli command strings.
+ """
+ ref = self._ref
+ # '_proposed' may be empty list or contain initializations; e.g. ['feature foo']
+ proposed = ref["_proposed"]
+
+ if ref["_context"] and ref["_context"][-1].startswith("no"):
+ [proposed.append(ctx) for ctx in ref["_context"]]
+ return proposed
+
+ # Create a list of commands that have playbook values
+ play_keys = [k for k in ref["commands"] if "playval" in ref[k]]
+
+ def compare(playval, existing):
+ if ref["_state"] in self.present_states:
+ if existing is None:
+ return False
+ elif str(playval) == str(existing):
+ return True
+ elif isinstance(existing, dict) and playval in existing.values():
+ return True
+
+ if ref["_state"] in self.absent_states:
+ if isinstance(existing, dict) and all(x is None for x in existing.values()):
+ existing = None
+ if existing is None or playval not in existing.values():
+ return True
+ return False
+
+ # Compare against current state
+ for k in play_keys:
+ playval = ref[k]["playval"]
+ # Create playval copy to avoid RuntimeError
+ # dictionary changed size during iteration error
+ playval_copy = deepcopy(playval)
+ existing = ref[k].get("existing", ref[k]["default"])
+ multiple = "multiple" in ref[k].keys()
+
+ # Multiple Instances:
+ if isinstance(existing, dict) and multiple:
+ for ekey, evalue in existing.items():
+ if isinstance(evalue, dict):
+ # Remove values set to string 'None' from dvalue
+ evalue = dict((k, v) for k, v in evalue.items() if v != "None")
+ for pkey, pvalue in playval.items():
+ if compare(pvalue, evalue):
+ if playval_copy.get(pkey):
+ del playval_copy[pkey]
+ if not playval_copy:
+ continue
+ # Single Instance:
+ else:
+ for pkey, pval in playval.items():
+ if compare(pval, existing):
+ if playval_copy.get(pkey):
+ del playval_copy[pkey]
+ if not playval_copy:
+ continue
+
+ playval = playval_copy
+ # Multiple Instances:
+ if isinstance(existing, dict):
+ for dkey, dvalue in existing.items():
+ for pval in playval.values():
+ self.build_cmd_set(pval, dvalue, k)
+ # Single Instance:
+ else:
+ for pval in playval.values():
+ self.build_cmd_set(pval, existing, k)
+
+ # Remove any duplicate commands before returning.
+ # pylint: disable=unnecessary-lambda
+ cmds = sorted(set(proposed), key=lambda x: proposed.index(x))
+ return cmds
+
+
+def nxosCmdRef_import_check():
+ """Return import error messages or empty string"""
+ msg = ""
+ if not HAS_YAML:
+ msg += "Mandatory python library 'PyYAML' is not present, try 'pip install PyYAML'\n"
+ return msg
+
+
+def is_json(cmd):
+ return to_text(cmd).endswith("| json")
+
+
+def is_text(cmd):
+ return not is_json(cmd)
+
+
+def to_command(module, commands):
+ transform = ComplexList(
+ dict(
+ command=dict(key=True),
+ output=dict(type="str", default="text"),
+ prompt=dict(type="list"),
+ answer=dict(type="list"),
+ newline=dict(type="bool", default=True),
+ sendonly=dict(type="bool", default=False),
+ check_all=dict(type="bool", default=False),
+ ),
+ module,
+ )
+
+ commands = transform(to_list(commands))
+
+ for item in commands:
+ if is_json(item["command"]):
+ item["output"] = "json"
+
+ return commands
+
+
+def get_config(module, flags=None):
+ flags = [] if flags is None else flags
+
+ conn = get_connection(module)
+ return conn.get_config(flags=flags)
+
+
+def run_commands(module, commands, check_rc=True):
+ conn = get_connection(module)
+ return conn.run_commands(to_command(module, commands), check_rc)
+
+
+def load_config(module, config, return_error=False, opts=None, replace=None):
+ conn = get_connection(module)
+ return conn.load_config(config, return_error, opts, replace=replace)
+
+
+def get_capabilities(module):
+ conn = get_connection(module)
+ return conn.get_capabilities()
+
+
+def get_diff(
+ self,
+ candidate=None,
+ running=None,
+ diff_match="line",
+ diff_ignore_lines=None,
+ path=None,
+ diff_replace="line",
+):
+ conn = self.get_connection()
+ return conn.get_diff(
+ candidate=candidate,
+ running=running,
+ diff_match=diff_match,
+ diff_ignore_lines=diff_ignore_lines,
+ path=path,
+ diff_replace=diff_replace,
+ )
+
+
+def normalize_interface(name):
+ """Return the normalized interface name"""
+ if not name:
+ return
+
+ def _get_number(name):
+ digits = ""
+ for char in name:
+ if char.isdigit() or char in "/.":
+ digits += char
+ return digits
+
+ if name.lower().startswith("et"):
+ if_type = "Ethernet"
+ elif name.lower().startswith("vl"):
+ if_type = "Vlan"
+ elif name.lower().startswith("lo"):
+ if_type = "loopback"
+ elif name.lower().startswith("po"):
+ if_type = "port-channel"
+ elif name.lower().startswith("nv"):
+ if_type = "nve"
+ else:
+ if_type = None
+
+ number_list = name.split(" ")
+ if len(number_list) == 2:
+ number = number_list[-1].strip()
+ else:
+ number = _get_number(name)
+
+ if if_type:
+ proper_interface = if_type + number
+ else:
+ proper_interface = name
+
+ return proper_interface
+
+
+def get_interface_type(interface):
+ """Gets the type of interface"""
+ if interface.upper().startswith("ET"):
+ return "ethernet"
+ elif interface.upper().startswith("VL"):
+ return "svi"
+ elif interface.upper().startswith("LO"):
+ return "loopback"
+ elif interface.upper().startswith("MG"):
+ return "management"
+ elif interface.upper().startswith("MA"):
+ return "management"
+ elif interface.upper().startswith("PO"):
+ return "portchannel"
+ elif interface.upper().startswith("NV"):
+ return "nve"
+ else:
+ return "unknown"
+
+
+def default_intf_enabled(name="", sysdefs=None, mode=None):
+ """Get device/version/interface-specific default 'enabled' state.
+ L3:
+ - Most L3 intfs default to 'shutdown'. Loopbacks default to 'no shutdown'.
+ - Some legacy platforms default L3 intfs to 'no shutdown'.
+ L2:
+ - User-System-Default 'system default switchport shutdown' defines the
+ enabled state for L2 intf's. USD defaults may be different on some platforms.
+ - An intf may be explicitly defined as L2 with 'switchport' or it may be
+ implicitly defined as L2 when USD 'system default switchport' is defined.
+ """
+ if not name:
+ return None
+ if sysdefs is None:
+ sysdefs = {}
+ default = False
+
+ if re.search("port-channel|loopback", name):
+ default = True
+ else:
+ if mode is None:
+ # intf 'switchport' cli is not present so use the user-system-default
+ mode = sysdefs.get("mode")
+
+ if mode == "layer3":
+ default = sysdefs.get("L3_enabled")
+ elif mode == "layer2":
+ default = sysdefs.get("L2_enabled")
+ return default
+
+
+def read_module_context(module):
+ conn = get_connection(module)
+ return conn.read_module_context(module._name)
+
+
+def save_module_context(module, module_context):
+ conn = get_connection(module)
+ return conn.save_module_context(module._name, module_context)
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_address_family.py
new file mode 100644
index 00000000..942e993e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_address_family.py
@@ -0,0 +1,798 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Bgp_address_family parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_aggregate_address(aggaddr):
+ cmd = "aggregate-address {prefix}"
+
+ if aggaddr.get("advertise_map"):
+ cmd += " advertise-map {advertise_map}"
+ if aggaddr.get("as_set"):
+ cmd += " as-set"
+ if aggaddr.get("attribute_map"):
+ cmd += " attribute-map {attribute_map}"
+ if aggaddr.get("summary_only"):
+ cmd += " summary-only"
+ if aggaddr.get("suppress_map"):
+ cmd += " suppress-map {suppress_map}"
+
+ return cmd.format(**aggaddr)
+
+
+def _tmplt_dampening(proc):
+ damp = proc.get("dampening", {})
+ cmd = "dampening"
+
+ if damp.get("set") is False:
+ return "no {0}".format(cmd)
+ if damp.get("route_map"):
+ cmd += " route-map {route_map}".format(**damp)
+ for x in (
+ "decay_half_life",
+ "start_reuse_route",
+ "start_suppress_route",
+ "max_suppress_time",
+ ):
+ if x in damp:
+ cmd += " {0}".format(damp[x])
+ return cmd
+
+
+def _tmplt_redistribute(redis):
+ command = "redistribute {protocol}".format(**redis)
+ if redis.get("id"):
+ command += " {id}".format(**redis)
+ command += " route-map {route_map}".format(**redis)
+ return command
+
+
+class Bgp_address_familyTemplate(NetworkTemplate):
+ def __init__(self, lines=None):
+ super(Bgp_address_familyTemplate, self).__init__(lines=lines, tmplt=self)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "as_number",
+ "getval": re.compile(
+ r"""
+ ^router\sbgp\s(?P<as_number>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "router bgp {{ as_number }}",
+ "result": {
+ "as_number": "{{ as_number }}",
+ },
+ "shared": True,
+ },
+ {
+ "name": "address_family",
+ "getval": re.compile(
+ r"""
+ (\s+vrf\s(?P<vrf>\S+))?
+ (\s+neighbor\s(?P<nbr>\S+))?
+ \s+address-family
+ \s(?P<afi>\S+)
+ (\s(?P<safi>\S+))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "address-family {{ afi }}{{ (' ' + safi) if safi is defined else ''}}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "vrf": "{{ vrf }}",
+ "afi": "{{ afi }}",
+ "safi": "{{ safi }}",
+ },
+ },
+ },
+ "shared": True,
+ },
+ {
+ "name": "additional_paths.install_backup",
+ "getval": re.compile(
+ r"""
+ \s+additional-paths
+ \sinstall\s(?P<backup>backup)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "additional-paths install backup",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "additional_paths": {
+ "install_backup": "{{ not not backup }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "additional_paths.receive",
+ "getval": re.compile(
+ r"""
+ \s+additional-paths
+ \s(?P<receive>receive)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "additional-paths receive",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "additional_paths": {
+ "receive": "{{ not not receive }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "additional_paths.selection.route_map",
+ "getval": re.compile(
+ r"""
+ \s+additional-paths
+ \sselection\sroute-map
+ \s(?P<route_map>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "additional-paths selection route-map {{ additional_paths.selection.route_map }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "additional_paths": {
+ "selection": {
+ "route_map": "{{ route_map }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "additional_paths.send",
+ "getval": re.compile(
+ r"""
+ \s+additional-paths
+ \s(?P<send>send)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "additional-paths send",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "additional_paths": {
+ "send": "{{ not not send }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "advertise_l2vpn_evpn",
+ "getval": re.compile(
+ r"""
+ \s+(?P<advertise_l2vpn_evpn>advertise\sl2vpn\sevpn)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "advertise l2vpn evpn",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "advertise_l2vpn_evpn": "{{ not not advertise_l2vpn_evpn }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "advertise_pip",
+ "getval": re.compile(
+ r"""
+ \s+(?P<advertise_pip>advertise-pip)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "advertise-pip",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "advertise_pip": "{{ not not advertise_pip }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "advertise_system_mac",
+ "getval": re.compile(
+ r"""
+ \s+(?P<advertise_system_mac>advertise-system-mac)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "advertise-system-mac",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "advertise_system_mac": "{{ not not advertise_system_mac }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "allow_vni_in_ethertag",
+ "getval": re.compile(
+ r"""
+ \s+(?P<allow_vni_in_ethertag>allow-vni-in-ethertag)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "allow-vni-in-ethertag",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "allow_vni_in_ethertag": "{{ not not allow_vni_in_ethertag }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "aggregate_address",
+ "getval": re.compile(
+ r"""
+ \s+aggregate-address
+ \s(?P<prefix>\S+)
+ (\s(?P<as_set>as-set))?
+ (\s(?P<summary_only>summary-only))?
+ (\sadvertise-map\s(?P<advertise_map>\S+))?
+ (\sattribute-map\s(?P<attribute_map>\S+))?
+ (\ssuppress-map\s(?P<suppress_map>\S+))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_aggregate_address,
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "aggregate_address": [
+ {
+ "prefix": "{{ prefix }}",
+ "as_set": "{{ True if as_set is defined else None }}",
+ "summary_only": "{{ True if summary_only is defined else None }}",
+ "advertise_map": "{{ advertise_map }}",
+ "attribute_map": "{{ attribute_map }}",
+ "suppress_map": "{{ suppress_map }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "client_to_client.no_reflection",
+ "getval": re.compile(
+ r"""
+ \s+no\sclient-to-client
+ \s(?P<reflection>reflection)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "client-to-client reflection",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "client_to_client": {
+ "no_reflection": "{{ not not reflection }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "dampen_igp_metric",
+ "getval": re.compile(
+ r"""
+ \s+dampen-igp-metric
+ \s(?P<dampen_igp_metric>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "dampen-igp-metric {{ dampen_igp_metric }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "dampen_igp_metric": "{{ dampen_igp_metric }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "dampening",
+ "getval": re.compile(
+ r"""
+ \s+(?P<dampening>dampening)
+ (\s(?P<decay_half_life>\d+))?
+ (\s(?P<start_reuse_route>\d+))?
+ (\s(?P<start_suppress_route>\d+))?
+ (\s(?P<max_suppress_time>\d+))?
+ (\sroute-map\s(?P<route_map>\S+))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_dampening,
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "dampening": {
+ "set": "{{ True if dampening is defined"
+ " and ((not decay_half_life|d(False),"
+ " not start_reuse_route|d(False), "
+ " not start_suppress_route|d(False), not max_suppress_time|d(False), not route_map|d(""))|all) }}",
+ "decay_half_life": "{{ decay_half_life }}",
+ "start_reuse_route": "{{ start_reuse_route }}",
+ "start_suppress_route": "{{ start_suppress_route }}",
+ "max_suppress_time": "{{ max_suppress_time }}",
+ "route_map": "{{ route_map }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "default_information.originate",
+ "getval": re.compile(
+ r"""
+ \s+default-information
+ \s(?P<originate>originate)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "default-information originate",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "default_information": {
+ "originate": "{{ not not originate }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "default_metric",
+ "getval": re.compile(
+ r"""
+ \s+default-metric
+ \s(?P<default_metric>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "default-metric {{ default_metric }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "default_metric": "{{ default_metric }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "distance",
+ "getval": re.compile(
+ r"""
+ \s+distance
+ \s(?P<ebgp_routes>\d+)
+ \s(?P<ibgp_routes>\d+)
+ \s(?P<local_routes>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "distance {{ distance.ebgp_routes }} {{ distance.ibgp_routes }} {{ distance.local_routes }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "distance": {
+ "ebgp_routes": "{{ ebgp_routes }}",
+ "ibgp_routes": "{{ ibgp_routes }}",
+ "local_routes": "{{ local_routes }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "export_gateway_ip",
+ "getval": re.compile(
+ r"""
+ \s+(?P<export_gateway_ip>export-gateway-ip)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "export-gateway-ip",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "export_gateway_ip": "{{ not not export_gateway_ip }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "inject_map",
+ "getval": re.compile(
+ r"""
+ \s+inject-map
+ \s(?P<route_map>\S+)
+ \sexist-map\s(?P<exist_map>\S+)
+ (\s(?P<copy_attributes>copy-attributes))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "inject-map {{ route_map }} exist-map {{ exist_map }}{{ ' copy-attributes' if copy_attributes|d(False) else '' }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "inject_map": [
+ {
+ "route_map": "{{ route_map }}",
+ "exist_map": "{{ exist_map }}",
+ "copy_attributes": "{{ not not copy_attributes }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "maximum_paths.parallel_paths",
+ "getval": re.compile(
+ r"""
+ \s+maximum-paths
+ \s(?P<parallel_paths>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "maximum-paths {{ maximum_paths.parallel_paths }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "maximum_paths": {
+ "parallel_paths": "{{ parallel_paths }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "maximum_paths.ibgp.parallel_paths",
+ "getval": re.compile(
+ r"""
+ \s+maximum-paths
+ \sibgp\s(?P<parallel_paths>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "maximum-paths ibgp {{ maximum_paths.ibgp.parallel_paths }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "maximum_paths": {
+ "ibgp": {
+ "parallel_paths": "{{ parallel_paths }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "maximum_paths.eibgp.parallel_paths",
+ "getval": re.compile(
+ r"""
+ \s+maximum-paths
+ \seibgp\s(?P<parallel_paths>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "maximum-paths eibgp {{ maximum_paths.eibgp.parallel_paths }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "maximum_paths": {
+ "eibgp": {
+ "parallel_paths": "{{ parallel_paths }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "maximum_paths.local.parallel_paths",
+ "getval": re.compile(
+ r"""
+ \s+maximum-paths
+ \slocal\s(?P<parallel_paths>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "maximum-paths local {{ maximum_paths.local.parallel_paths }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "maximum_paths": {
+ "local": {
+ "parallel_paths": "{{ parallel_paths }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "maximum_paths.mixed.parallel_paths",
+ "getval": re.compile(
+ r"""
+ \s+maximum-paths
+ \smixed\s(?P<parallel_paths>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "maximum-paths mixed {{ maximum_paths.mixed.parallel_paths }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "maximum_paths": {
+ "mixed": {
+ "parallel_paths": "{{ parallel_paths }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "networks",
+ "getval": re.compile(
+ r"""
+ \s+network
+ \s(?P<prefix>\S+)
+ (\sroute-map\s(?P<route_map>\S+))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "network {{ prefix }}{{ (' route-map ' + route_map) if route_map is defined else '' }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "networks": [
+ {
+ "prefix": "{{ prefix }}",
+ "route_map": "{{ route_map }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "nexthop.route_map",
+ "getval": re.compile(
+ r"""
+ \s+nexthop
+ \sroute-map\s(?P<route_map>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nexthop route-map {{ nexthop.route_map }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "nexthop": {
+ "route_map": "{{ route_map }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "nexthop.trigger_delay",
+ "getval": re.compile(
+ r"""
+ \s+nexthop
+ \strigger-delay
+ \scritical\s(?P<critical_delay>\d+)
+ \snon-critical\s(?P<non_critical_delay>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "nexthop trigger-delay critical {{ nexthop.trigger_delay.critical_delay }} non-critical {{ nexthop.trigger_delay.non_critical_delay }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "nexthop": {
+ "trigger_delay": {
+ "critical_delay": "{{ critical_delay }}",
+ "non_critical_delay": "{{ non_critical_delay }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "redistribute",
+ "getval": re.compile(
+ r"""
+ \s+redistribute
+ \s(?P<protocol>\S+)
+ (\s(?P<id>\S+))?
+ \sroute-map\s(?P<rmap>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_redistribute,
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "redistribute": [
+ {
+ "protocol": "{{ protocol }}",
+ "id": "{{ id }}",
+ "route_map": "{{ rmap }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "retain.route_target.retain_all",
+ "getval": re.compile(
+ r"""
+ \s+retain\sroute-target
+ \s(?P<retain_all>all)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "retain route-target all",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "retain": {
+ "route_target": {
+ "retain_all": "{{ not not retain_all }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "retain.route_target.route_map",
+ "getval": re.compile(
+ r"""
+ \s+retain\sroute-target
+ \sroute-map\s(?P<route_map>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "retain route-target route-map {{ retain.route_target.route_map }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "retain": {
+ "route_target": {
+ "route_map": "{{ route_map }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "suppress_inactive",
+ "getval": re.compile(
+ r"""
+ \s+(?P<suppress_inactive>suppress-inactive)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "suppress-inactive",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "suppress_inactive": "{{ not not suppress_inactive }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "table_map",
+ "getval": re.compile(
+ r"""
+ \s+table-map
+ \s(?P<name>\S+)
+ (\s(?P<filter>filter))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "table-map {{ table_map.name }}{{ ' filter' if table_map.filter|d(False) }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "table_map": {
+ "name": "{{ name }}",
+ "filter": "{{ not not filter }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.bestpath_defer",
+ "getval": re.compile(
+ r"""
+ \s+timers
+ \sbestpath-defer\s(?P<defer_time>\d+)
+ \smaximum\s(?P<maximum_defer_time>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "timers bestpath-defer {{ timers.bestpath_defer.defer_time }} maximum {{ timers.bestpath_defer.maximum_defer_time }}",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "timers": {
+ "bestpath_defer": {
+ "defer_time": "{{ defer_time }}",
+ "maximum_defer_time": "{{ maximum_defer_time }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "wait_igp_convergence",
+ "getval": re.compile(
+ r"""
+ \s+(?P<wait_igp_convergence>wait-igp-convergence)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "wait-igp-convergence",
+ "result": {
+ "address_family": {
+ '{{ nbr|d("nbr_") + afi + "_" + safi|d() + "_" + vrf|d() }}': {
+ "wait_igp_convergence": "{{ not not wait_igp_convergence }}",
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_global.py
new file mode 100644
index 00000000..fa99397a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_global.py
@@ -0,0 +1,1536 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Bgp_global parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_confederation_peers(proc):
+ cmd = "confederation peers"
+ for peer in proc.get("confederation", {})["peers"]:
+ cmd += " {0}".format(peer)
+ return cmd
+
+
+def _tmplt_path_attribute(proc):
+ cmd = "path-attribute {action}".format(**proc)
+
+ if "type" in proc:
+ cmd += " {type}".format(**proc)
+ elif "range" in proc:
+ cmd += " range {start} {end}".format(**proc["range"])
+ cmd += " in"
+
+ return cmd
+
+
+def _tmplt_bfd(proc):
+ bfd = proc.get("bfd", {})
+ cmd = None
+
+ if bfd.get("set"):
+ cmd = "bfd"
+ if bfd.get("singlehop"):
+ cmd = "bfd singlehop"
+ elif bfd.get("multihop", {}).get("set"):
+ cmd = "bfd multihop"
+
+ if cmd:
+ return cmd
+
+
+class Bgp_globalTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Bgp_globalTemplate, self).__init__(lines=lines, tmplt=self, module=module)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "as_number",
+ "getval": re.compile(
+ r"""
+ ^router\sbgp\s(?P<as_number>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "router bgp {{ as_number }}",
+ "result": {
+ "as_number": "{{ as_number }}",
+ },
+ "shared": True,
+ },
+ {
+ "name": "vrf",
+ "getval": re.compile(
+ r"""
+ \s+vrf
+ \s(?P<vrf>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "vrf {{ vrf }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "vrf": "{{ vrf }}",
+ },
+ },
+ },
+ "shared": True,
+ },
+ {
+ "name": "affinity_group.group_id",
+ "getval": re.compile(
+ r"""
+ \s+affinity-group
+ \sactivate\s(?P<group_id>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "affinity-group activate {{ affinity_group.group_id }}",
+ "result": {
+ "affinity_group": {
+ "group_id": "{{ group_id }}",
+ },
+ },
+ },
+ {
+ "name": "bestpath.always_compare_med",
+ "getval": re.compile(
+ r"""
+ \s+bestpath\s(?P<always_compare_med>always-compare-med)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bestpath always-compare-med",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bestpath": {
+ "always_compare_med": "{{ not not always_compare_med }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bestpath.as_path.ignore",
+ "getval": re.compile(
+ r"""
+ \s+bestpath\sas-path\s(?P<ignore>ignore)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bestpath as-path ignore",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bestpath": {
+ "as_path": {
+ "ignore": "{{ not not ignore }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bestpath.as_path.multipath_relax",
+ "getval": re.compile(
+ r"""
+ \s+bestpath\sas-path\s(?P<multipath_relax>multipath-relax)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bestpath as-path multipath-relax",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bestpath": {
+ "as_path": {
+ "multipath_relax": "{{ not not multipath_relax }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bestpath.compare_neighborid",
+ "getval": re.compile(
+ r"""
+ \s+bestpath\s(?P<compare_neighborid>compare-neighborid)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bestpath compare-neighborid",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bestpath": {
+ "compare_neighborid": "{{ not not compare_neighborid }}",
+ },
+ },
+ },
+
+ },
+ },
+ {
+ "name": "bestpath.compare_routerid",
+ "getval": re.compile(
+ r"""
+ \s+bestpath\s(?P<compare_routerid>compare-routerid)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bestpath compare-routerid",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bestpath": {
+ "compare_routerid": "{{ not not compare_routerid }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bestpath.cost_community_ignore",
+ "getval": re.compile(
+ r"""
+ \s+bestpath\scost-community\s(?P<cost_community_ignore>ignore)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bestpath cost-community ignore",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bestpath": {
+ "cost_community_ignore": "{{ not not cost_community_ignore }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bestpath.igp_metric_ignore",
+ "getval": re.compile(
+ r"""
+ \s+bestpath\sigp-metric\s(?P<igp_metric_ignore>ignore)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bestpath igp-metric ignore",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bestpath": {
+ "igp_metric_ignore": "{{ not not igp_metric_ignore }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bestpath.med.confed",
+ "getval": re.compile(
+ r"""
+ \s+bestpath\smed\s(?P<confed>confed)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bestpath med confed",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bestpath": {
+ "med": {
+ "confed": "{{ not not confed }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bestpath.med.missing_as_worst",
+ "getval": re.compile(
+ r"""
+ \s+bestpath\smed\s(?P<missing_as_worst>missing-as-worst)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bestpath med missing-as-worst",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bestpath": {
+ "med": {
+ "missing_as_worst": "{{ not not missing_as_worst }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bestpath.med.non_deterministic",
+ "getval": re.compile(
+ r"""
+ \s+bestpath\smed\s(?P<non_deterministic>non-deterministic)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bestpath med non-deterministic",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bestpath": {
+ "med": {
+ "non_deterministic": "{{ not not non_deterministic }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "cluster_id",
+ "getval": re.compile(
+ r"""
+ \s+cluster-id\s(?P<cluster_id>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "cluster-id {{ cluster_id }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "cluster_id": "{{ cluster_id }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "confederation.identifier",
+ "getval": re.compile(
+ r"""
+ \s+confederation\sidentifier\s(?P<identifier>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "confederation identifier {{ confederation.identifier }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "confederation": {
+ "identifier": "{{ identifier }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "confederation.peers",
+ "getval": re.compile(
+ r"""
+ \s+confederation\speers\s(?P<peers>.*)
+ $""", re.VERBOSE,
+ ),
+ "setval": _tmplt_confederation_peers,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "confederation": {
+ "peers": "{{ peers }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "disable_policy_batching",
+ "getval": re.compile(
+ r"""
+ \s+(?P<disable_policy_batching>disable-policy-batching)
+ $""", re.VERBOSE,
+ ),
+ "setval": "disable-policy-batching",
+ "result": {
+ "disable_policy_batching": {
+ "set": "{{ not not disable_policy_batching }}",
+ },
+ },
+ },
+ {
+ "name": "disable_policy_batching.ipv4.prefix_list",
+ "getval": re.compile(
+ r"""
+ \s+disable-policy-batching\sipv4
+ \sprefix-list\s(?P<ipv4_prefix_list>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "disable-policy-batching ipv4 prefix-list {{ disable_policy_batching.ipv4.prefix_list }}",
+ "result": {
+ "disable_policy_batching": {
+ "ipv4": {
+ "prefix_list": "{{ ipv4_prefix_list }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "disable_policy_batching.ipv6.prefix_list",
+ "getval": re.compile(
+ r"""
+ \s+disable-policy-batching\sipv6
+ \sprefix-list\s(?P<ipv6_prefix_list>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "disable-policy-batching ipv6 prefix-list {{ disable_policy_batching.ipv6.prefix_list }}",
+ "result": {
+ "disable_policy_batching": {
+ "ipv6": {
+ "prefix_list": "{{ ipv6_prefix_list }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "disable_policy_batching.nexthop",
+ "getval": re.compile(
+ r"""
+ \s+disable-policy-batching\s(?P<nexthop>nexthop)
+ $""", re.VERBOSE,
+ ),
+ "setval": "disable-policy-batching nexthop",
+ "result": {
+ "disable_policy_batching": {
+ "nexthop": "{{ not not nexthop }}",
+ },
+ },
+ },
+ {
+ "name": "dynamic_med_interval",
+ "getval": re.compile(
+ r"""
+ \s+dynamic-med-interval\s(?P<dynamic_med_interval>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "dynamic-med-interval {{ dynamic_med_interval }}",
+ "result": {
+ "dynamic_med_interval": "{{ dynamic_med_interval }}",
+ },
+ },
+ {
+ "name": "enforce_first_as",
+ "getval": re.compile(
+ r"""
+ \s+no\s(?P<enforce_first_as>enforce-first-as)
+ $""", re.VERBOSE,
+ ),
+ "setval": "enforce-first-as",
+ "result": {
+ "enforce_first_as": "{{ not enforce_first_as }}",
+ },
+ },
+ {
+ "name": "enhanced_error",
+ "getval": re.compile(
+ r"""
+ \s+no\s(?P<enhanced_error>enhanced-error)
+ $""", re.VERBOSE,
+ ),
+ "setval": "enhanced-error",
+ "result": {
+ "enhanced_error": "{{ not enhanced_error }}",
+ },
+ },
+ {
+ "name": "fast_external_fallover",
+ "getval": re.compile(
+ r"""
+ \s+no\s(?P<fast_external_fallover>fast-external-fallover)
+ $""", re.VERBOSE,
+ ),
+ "setval": "fast-external-fallover",
+ "result": {
+ "fast_external_fallover": "{{ not fast_external_fallover }}",
+ },
+ },
+ {
+ "name": "flush_routes",
+ "getval": re.compile(
+ r"""
+ \s+(?P<flush_routes>flush-routes)
+ $""", re.VERBOSE,
+ ),
+ "setval": "flush-routes",
+ "result": {
+ "flush_routes": "{{ not not flush_routes }}",
+ },
+ },
+ {
+ "name": "graceful_restart",
+ "getval": re.compile(
+ r"""
+ \s+no\s(?P<graceful_restart>graceful-restart)
+ $""", re.VERBOSE,
+ ),
+ "setval": "graceful-restart",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "set": "{{ not graceful_restart }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart.restart_time",
+ "getval": re.compile(
+ r"""
+ \s+graceful-restart\srestart-time\s(?P<restart_time>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "graceful-restart restart-time {{ graceful_restart.restart_time }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "restart_time": "{{ restart_time }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart.stalepath_time",
+ "getval": re.compile(
+ r"""
+ \s+graceful-restart\sstalepath-time\s(?P<stalepath_time>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "graceful-restart stalepath-time {{ graceful_restart.stalepath_time }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "stalepath_time": "{{ stalepath_time }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart.helper",
+ "getval": re.compile(
+ r"""
+ \s+(?P<helper>graceful-restart-helper)
+ $""", re.VERBOSE,
+ ),
+ "setval": "graceful-restart-helper",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "helper": "{{ not not helper }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_shutdown.activate",
+ "getval": re.compile(
+ r"""
+ \s+graceful-shutdown
+ \s(?P<activate>activate)
+ (\sroute-map
+ \s(?P<route_map>\S+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "graceful-shutdown activate{{ ' route-map ' + graceful_shutdown.activate.route_map if graceful_shutdown.activate.route_map is defined }}",
+ "result": {
+ "graceful_shutdown": {
+ "activate": {
+ "set": "{{ True if activate is defined and route_map is undefined else None }}",
+ "route_map": "{{ route_map }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_shutdown.aware",
+ "getval": re.compile(
+ r"""
+ \s+no\sgraceful-shutdown
+ \s(?P<aware>aware)
+ $""", re.VERBOSE,
+ ),
+ "setval": "graceful-shutdown aware",
+ "result": {
+ "graceful_shutdown": {
+ "aware": "{{ not aware }}",
+ },
+ },
+ },
+ {
+ "name": "isolate",
+ "getval": re.compile(
+ r"""
+ \s+(?P<isolate>isolate)
+ (\s(?P<include_local>include-local))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "isolate{{ ' include-local' if isolate.include_local|d(False) is True }}",
+ "result": {
+ "isolate": {
+ "set": "{{ True if isolate is defined and include_local is not defined else None }}",
+ "include_local": "{{ not not include_local }}",
+ },
+ },
+ },
+ {
+ "name": "log_neighbor_changes",
+ "getval": re.compile(
+ r"""
+ \s+(?P<log_neighbor_changes>log-neighbor-changes)
+ $""", re.VERBOSE,
+ ),
+ "setval": "log-neighbor-changes",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "log_neighbor_changes": "{{ not not log_neighbor_changes }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "maxas_limit",
+ "getval": re.compile(
+ r"""
+ \s+maxas-limit\s(?P<maxas_limit>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "maxas-limit {{ maxas_limit }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "maxas_limit": "{{ maxas_limit }}",
+ },
+ },
+ },
+ },
+ # start neighbor parsers
+ {
+ "name": "neighbor_address",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "neighbor {{ neighbor_address }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "neighbor_address": "{{ neighbor_address }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bfd",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \s(?P<bfd>bfd)
+ (\s(?P<singlehop>singlehop))?
+ (\s(?P<multihop>multihop))?
+ $""", re.VERBOSE,
+ ),
+ "setval": _tmplt_bfd,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "bfd": {
+ "set": "{{ True if bfd is defined and singlehop is undefined and multihop is undefined else None }}",
+ "singlehop": "{{ not not singlehop }}",
+ "multihop": {
+ "set": "{{ not not multihop }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bfd.multihop.interval",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \sbfd\smultihop\sinterval
+ \s(?P<tx_interval>\d+)
+ \smin_rx\s(?P<min_rx_interval>\d+)
+ \smultiplier\s(?P<multiplier>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bfd multihop interval"
+ " {{ bfd.multihop.interval.tx_interval }}"
+ " min_rx {{ bfd.multihop.interval.min_rx_interval }}"
+ " multiplier {{ bfd.multihop.interval.multiplier }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "bfd": {
+ "multihop": {
+ "interval": {
+ "tx_interval": "{{ tx_interval }}",
+ "min_rx_interval": "{{ min_rx_interval }}",
+ "multiplier": "{{ multiplier }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "remote_as",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \sremote-as\s(?P<remote_as>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "remote-as {{ remote_as }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "remote_as": "{{ remote_as }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "neighbor_affinity_group.group_id",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \saffinity-group\s(?P<group_id>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "affinity-group {{ neighbor_affinity_group.group_id }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "neighbor_affinity_group": {
+ "group_id": "{{ group_id }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "bmp_activate_server",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \sbmp-activate-server\s(?P<bmp_activate_server>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "bmp-activate-server {{ bmp_activate_server }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "bmp_activate_server": "{{ bmp_activate_server }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "capability",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \scapability\ssuppress\s(?P<suppress_4_byte_as>4-byte-as)
+ $""", re.VERBOSE,
+ ),
+ "setval": "capability suppress 4-byte-as",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "capability": {
+ "suppress_4_byte_as": "{{ not not suppress_4_byte_as }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "description",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \sdescription\s(?P<description>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "description {{ description }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "description": "{{ description }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "disable_connected_check",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \s(?P<disable_connected_check>disable-connected-check)
+ $""", re.VERBOSE,
+ ),
+ "setval": "disable-connected-check",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "disable_connected_check": "{{ not not disable_connected_check }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "dont_capability_negotiate",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \s(?P<dont_capability_negotiate>dont-capability-negotiate)
+ $""", re.VERBOSE,
+ ),
+ "setval": "dont-capability-negotiate",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "dont_capability_negotiate": "{{ not not dont_capability_negotiate}}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "dscp",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \sdscp\s(?P<dscp>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "dscp {{ dscp }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "dscp": "{{ dscp }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "dynamic_capability",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \s(?P<dynamic_capability>dynamic-capability)
+ $""", re.VERBOSE,
+ ),
+ "setval": "dynamic-capability",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "dynamic_capability": "{{ not not dynamic_capability }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ebgp_multihop",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \sebgp-multihop\s(?P<ebgp_multihop>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ebgp-multihop {{ ebgp_multihop }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "ebgp_multihop": "{{ ebgp_multihop }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_shutdown",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \sgraceful-shutdown
+ \s(?P<activate>activate)
+ (\sroute-map\s(?P<route_map>\S+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "graceful-shutdown{{ (' route-map ' + graceful_shutdown.route_map) if graceful_shutdown.route_map is defined }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "graceful_shutdown": {
+ "activate": {
+ "set": "{{ True if activate is defined and route_map is undefined else None }}",
+ "route_map": "{{ route_map }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "inherit.peer",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \sinherit
+ \speer\s(?P<peer>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "inherit peer {{ inherit.peer }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "inherit": {
+ "peer": "{{ peer }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "inherit.peer_session",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \sinherit
+ \speer-session\s(?P<peer_session>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "inherit peer-session {{ inherit.peer_session }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "inherit": {
+ "peer_session": "{{ peer_session }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "local_as",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \slocal-as\s(?P<local_as>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "local-as {{ local_as }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "local_as": "{{ local_as }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "log_neighbor_changes",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \s(?P<log_neighbor_changes>log-neighbor-changes)
+ (\s(?P<disable>disable))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "log-neighbor-changes{{ ' disable' if log_neighbor_changes.disable is defined }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "log_neighbor_changes": {
+ "set": "{{ True if log_neighbor_changes is defined and disable is undefined }}",
+ "disable": "{{ not not disable }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "low_memory",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \slow-memory\s(?P<exempt>exempt)
+ $""", re.VERBOSE,
+ ),
+ "setval": "low-memory exempt",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "low_memory": {
+ "exempt": "{{ not not exempt }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "password",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \spassword\s(?P<encryption>\d+)
+ \s(?P<key>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "password{{ (' ' + password.encryption|string) if password.encryption is defined }} {{ password.key }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "password": {
+ "encryption": "{{ encryption }}",
+ "key": "{{ key }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "path_attribute",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \spath-attribute\s(?P<action>\S+)\s
+ (?P<type>\d+)?
+ (range\s(?P<start>\d+)\s(?P<end>\d+))?
+ \sin
+ $""", re.VERBOSE,
+ ),
+ "setval": _tmplt_path_attribute,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "path_attribute": [
+ {
+ "action": "{{ action }}",
+ "type": "{{ type if type is defined else None }}",
+ "range": {
+ "start": "{{ start if start is defined }}",
+ "end": "{{ end if end is defined }}",
+ },
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "peer_type",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \speer-type\s(?P<peer_type>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "peer-type {{ peer_type }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "peer_type": "{{ peer_type }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "remove_private_as",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \s(?P<remove_private_as>remove-private-as)
+ (\s(?P<all>all))?
+ (\s(?P<replace_as>replace-as))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "remove-private-as",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "remove_private_as": {
+ "set": "{{ True if remove_private_as is defined and replace_as is undefined and all is undefined else None }}",
+ "replace_as": "{{ not not replace_as }}",
+ "all": "{{ not not all }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "shutdown",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \s(?P<shutdown>shutdown)
+ $""", re.VERBOSE,
+ ),
+ "setval": "shutdown",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "shutdown": "{{ not not shutdown }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \stimers\s(?P<keepalive>\d+)\s(?P<holdtime>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "timers {{ timers.keepalive }} {{ timers.holdtime }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "timers": {
+ "keepalive": "{{ keepalive }}",
+ "holdtime": "{{ holdtime }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "transport",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \stransport\sconnection-mode
+ \s(?P<passive>passive)
+ $""", re.VERBOSE,
+ ),
+ "setval": "transport connection-mode passive",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "transport": {
+ "connection_mode": {
+ "passive": "{{ not not passive }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ttl_security",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \sttl-security\shops\s(?P<hops>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ttl-security hops {{ ttl_security.hops }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "ttl_security": {
+ "hops": "{{ hops }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "update_source",
+ "getval": re.compile(
+ r"""
+ \s+neighbor\s(?P<neighbor_address>\S+)
+ \supdate-source\s(?P<update_source>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "update-source {{ update_source }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbors": {
+ "{{ neighbor_address }}": {
+ "update_source": "{{ update_source }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ # end neighbor parsers
+ {
+ "name": "neighbor_down.fib_accelerate",
+ "getval": re.compile(
+ r"""
+ \s+neighbor-down\s(?P<fib_accelerate>fib-accelerate)
+ $""", re.VERBOSE,
+ ),
+ "setval": "neighbor-down fib-accelerate",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "neighbor_down": {
+ "fib_accelerate": "{{ not not fib_accelerate }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "nexthop.suppress_default_resolution",
+ "getval": re.compile(
+ r"""
+ \s+nexthop
+ \s(?P<suppress_default_resolution>suppress-default-resolution)
+ $""", re.VERBOSE,
+ ),
+ "setval": "nexthop suppress-default-resolution",
+ "result": {
+ "nexthop": {
+ "suppress_default_resolution": "{{ not not suppress_default_resolution }}",
+ },
+ },
+ },
+ {
+ "name": "reconnect_interval",
+ "getval": re.compile(
+ r"""
+ \s+reconnect-interval
+ \s(?P<reconnect_interval>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "reconnect-interval {{ reconnect_interval }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "reconnect_interval": "{{ reconnect_interval }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "router_id",
+ "getval": re.compile(
+ r"""
+ \s+router-id
+ \s(?P<router_id>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "router-id {{ router_id }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "router_id": "{{ router_id }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "shutdown",
+ "getval": re.compile(
+ r"""
+ \s+(?P<shutdown>shutdown)
+ $""", re.VERBOSE,
+ ),
+ "setval": "shutdown",
+ "result": {
+ "shutdown": "{{ not not shutdown }}",
+ },
+ },
+ {
+ "name": "suppress_fib_pending",
+ "getval": re.compile(
+ r"""
+ \s+no\s(?P<suppress_fib_pending>suppress-fib-pending)
+ $""", re.VERBOSE,
+ ),
+ "setval": "suppress-fib-pending",
+ "result": {
+ "suppress_fib_pending": "{{ not suppress_fib_pending }}",
+ },
+ },
+ {
+ "name": "timers.bestpath_limit",
+ "getval": re.compile(
+ r"""
+ \s+timers\sbestpath-limit
+ \s(?P<timeout>\d+)
+ (\s(?P<always>always))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "timers bestpath-limit {{ timers.bestpath_limit.timeout }}{{ ' always' if timers.bestpath_limit.timeout is defined }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "bestpath_limit": {
+ "timeout": "{{ timeout }}",
+ "always": "{{ not not always }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.bgp",
+ "getval": re.compile(
+ r"""
+ \s+timers\sbgp
+ \s(?P<keepalive>\d+)
+ (\s(?P<holdtime>\d+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "timers bgp {{ timers.bgp.keepalive }} {{ timers.bgp.holdtime }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "bgp": {
+ "keepalive": "{{ keepalive }}",
+ "holdtime": "{{ holdtime }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.prefix_peer_timeout",
+ "getval": re.compile(
+ r"""
+ \s+timers
+ \sprefix-peer-timeout\s(?P<prefix_peer_timeout>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "timers prefix-peer-timeout {{ timers.prefix_peer_timeout }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "prefix_peer_timeout": "{{ prefix_peer_timeout }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.prefix_peer_wait",
+ "getval": re.compile(
+ r"""
+ \s+timers
+ \sprefix-peer-wait\s(?P<prefix_peer_wait>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "timers prefix-peer-wait {{ timers.prefix_peer_wait }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "prefix_peer_wait": "{{ prefix_peer_wait }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "fabric_soo",
+ "getval": re.compile(
+ r"""
+ \s+fabric-soo
+ \s(?P<fabric_soo>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "fabric-soo {{ fabric_soo }}",
+ "result": {
+ "fabric_soo": "{{ fabric_soo }}",
+ },
+ },
+ {
+ "name": "rd",
+ "getval": re.compile(
+ r"""
+ \s+rd\s(?P<dual>dual)
+ (\sid\s(?P<id>\d+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "rd dual{{' id ' + rd.id if rd.id is defined }}",
+ "result": {
+ "rd": {
+ "dual": "{{ not not dual }}",
+ "id": "{{ id }}",
+ },
+ },
+ },
+ # VRF only
+ {
+ "name": "allocate_index",
+ "getval": re.compile(
+ r"""
+ \s+allocate-index\s(?P<allocate_index>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "allocate-index {{ allocate_index }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "allocate_index": "{{ allocate_index }}",
+ },
+ },
+ },
+ },
+ # VRF only
+ {
+ "name": "local_as",
+ "getval": re.compile(
+ r"""
+ \s+local-as\s(?P<local_as>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "local-as {{ local_as }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "local_as": "{{ local_as }}",
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_neighbor_address_family.py
new file mode 100644
index 00000000..327ba4d2
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/bgp_neighbor_address_family.py
@@ -0,0 +1,894 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Bgp_neighbor_address_family parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_maximum_prefix(data):
+ data = data["maximum_prefix"]
+ cmd = "maximum-prefix {max_prefix_limit}".format(**data)
+ if "generate_warning_threshold" in data:
+ cmd += " {generate_warning_threshold}".format(**data)
+ if "restart_interval" in data:
+ cmd += " restart {restart_interval}".format(**data)
+ if data.get("warning_only"):
+ cmd += " warning-only"
+ return cmd
+
+
+class Bgp_neighbor_address_familyTemplate(NetworkTemplate):
+ def __init__(self, lines=None):
+ super(Bgp_neighbor_address_familyTemplate, self).__init__(lines=lines, tmplt=self)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "as_number",
+ "getval": re.compile(
+ r"""
+ ^router\sbgp\s(?P<as_number>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "router bgp {{ as_number }}",
+ "result": {
+ "as_number": "{{ as_number }}",
+ },
+ "shared": True,
+ },
+ {
+ "name": "address_family",
+ "getval": re.compile(
+ r"""
+ (vrf\s(?P<vrf>\S+))?
+ \s*neighbor\s(?P<neighbor>\S+)
+ \saddress-family
+ \s(?P<afi>\S+)
+ (\s(?P<safi>\S+))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "address-family {{ afi }}{{ (' ' + safi) if safi is defined else '' }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "neighbor_address": "{{ neighbor }}",
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "afi": "{{ afi }}",
+ "safi": "{{ safi }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ "shared": True,
+ },
+ {
+ "name": "advertise_map.exist_map",
+ "getval": re.compile(
+ r"""
+ advertise-map
+ \s(?P<route_map>\S+)
+ \sexist-map\s(?P<exist_map>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "advertise-map {{ advertise_map.route_map }} exist-map {{ advertise_map.exist_map }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "advertise_map": {
+ "route_map": "{{ route_map }}",
+ "exist_map": "{{ exist_map }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "advertise_map.non_exist_map",
+ "getval": re.compile(
+ r"""
+ advertise-map
+ \s(?P<route_map>\S+)
+ \snon-exist-map\s(?P<non_exist_map>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "advertise-map {{ advertise_map.route_map }} non-exist-map {{ advertise_map.non_exist_map }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "advertise_map": {
+ "route_map": "{{ route_map }}",
+ "non_exist_map": "{{ non_exist_map }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "advertisement_interval",
+ "getval": re.compile(
+ r"""
+ advertisement-interval
+ \s(?P<advertisement_interval>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "advertisement-interval {{ advertisement_interval }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "advertisement_interval": "{{ advertisement_interval }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "allowas_in",
+ "getval": re.compile(
+ r"""
+ (?P<allowas_in>allowas-in)
+ \s(?P<max_occurences>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "allowas-in{{ ' ' + allowas_in.max_occurences|string if allowas_in.max_occurences is defined else '' }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "allowas_in": {
+ "set": "{{ True if allowas_in is defined and max_occurences is undefined }}",
+ "max_occurences": "{{ max_occurences }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "as_override",
+ "getval": re.compile(
+ r"""
+ (?P<as_override>as-override)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "as-override",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "as_override": "{{ not not as_override }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "capability.additional_paths.receive",
+ "getval": re.compile(
+ r"""
+ capability\sadditional-paths
+ \s(?P<receive>receive)
+ (\s(?P<disable>disable))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "capability additional-paths receive{{ ' disable' if capability.additional_paths.receive == 'disable' else '' }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "capability": {
+ "additional_paths": {
+ "receive": "{{ 'disable' if disable is defined else 'enable' }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "capability.additional_paths.send",
+ "getval": re.compile(
+ r"""
+ capability\sadditional-paths
+ \s(?P<send>send)
+ (\s(?P<disable>disable))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "capability additional-paths send{{ ' disable' if capability.additional_paths.send == 'disable' else '' }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "capability": {
+ "additional_paths": {
+ "send": "{{ 'disable' if disable is defined else 'enable' }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "default_originate",
+ "getval": re.compile(
+ r"""
+ (?P<default_originate>default-originate)
+ (\sroute-map\s(?P<route_map>\S+))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "default-originate{{ ' route-map ' + default_originate.route_map if default_originate.route_map is defined else '' }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "default_originate": {
+ "set": "{{ True if default_originate is defined and route_map is not defined }}",
+ "route_map": "{{ route_map }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "disable_peer_as_check",
+ "getval": re.compile(
+ r"""
+ (?P<disable_peer_as_check>disable-peer-as-check)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "disable-peer-as-check",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "disable_peer_as_check": "{{ not not disable_peer_as_check }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "filter_list.inbound",
+ "getval": re.compile(
+ r"""
+ filter-list
+ \s(?P<in>\S+)\s(?:in)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "filter-list {{ filter_list.inbound }} in",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "filter_list": {
+ "inbound": "{{ in }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "filter_list.outbound",
+ "getval": re.compile(
+ r"""
+ filter-list
+ \s(?P<out>\S+)\s(?:out)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "filter-list {{ filter_list.outbound }} out",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "filter_list": {
+ "outbound": "{{ out }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "inherit",
+ "getval": re.compile(
+ r"""
+ inherit\speer-policy
+ \s(?P<template>\S+)
+ \s(?P<sequence>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "inherit peer-policy {{ inherit.template }} {{ inherit.sequence }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "inherit": {
+ "template": "{{ template }}",
+ "sequence": "{{ sequence }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "maximum_prefix",
+ "getval": re.compile(
+ r"""
+ maximum-prefix
+ \s(?P<max_prefix_limit>\d+)
+ (\s(?P<generate_warning_threshold>\d+))?
+ (\srestart\s(?P<restart_interval>\d+))?
+ (\s(?P<warning_only>warning-only))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_maximum_prefix,
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "maximum_prefix": {
+ "max_prefix_limit": "{{ max_prefix_limit }}",
+ "generate_warning_threshold": "{{ generate_warning_threshold }}",
+ "restart_interval": "{{ restart_interval }}",
+ "warning_only": "{{ not not warning_only }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "next_hop_self",
+ "getval": re.compile(
+ r"""
+ (?P<next_hop_self>next-hop-self)
+ (\s(?P<all_routes>all))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "next-hop-self{{ ' all' if next_hop_self.all_routes|d(False) else '' }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "next_hop_self": {
+ "set": "{{ True if next_hop_self is defined and all_routes is not defined }}",
+ "all_routes": "{{ not not all_routes }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "next_hop_third_party",
+ "getval": re.compile(
+ r"""
+ no\s(?P<next_hop_third_party>next-hop-third-party)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "next-hop-third-party",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "next_hop_third_party": "{{ not next_hop_third_party }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "prefix_list.inbound",
+ "getval": re.compile(
+ r"""
+ prefix-list
+ \s(?P<in>\S+)\s(?:in)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "prefix-list {{ prefix_list.inbound }} in",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "prefix_list": {
+ "inbound": "{{ in }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "prefix_list.outbound",
+ "getval": re.compile(
+ r"""
+ prefix-list
+ \s(?P<out>\S+)\s(?:out)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "prefix-list {{ prefix_list.outbound }} out",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "prefix_list": {
+ "outbound": "{{ out }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "rewrite_evpn_rt_asn",
+ "getval": re.compile(
+ r"""
+ (?P<rewrite_evpn_rt_asn>rewrite-evpn-rt-asn)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "rewrite-evpn-rt-asn",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "rewrite_evpn_rt_asn": "{{ not not rewrite_evpn_rt_asn }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "route_map.inbound",
+ "getval": re.compile(
+ r"""
+ route-map
+ \s(?P<in>\S+)\s(?:in)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "route-map {{ route_map.inbound }} in",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "route_map": {
+ "inbound": "{{ in }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "route_map.outbound",
+ "getval": re.compile(
+ r"""
+ route-map
+ \s(?P<out>\S+)\s(?:out)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "route-map {{ route_map.outbound }} out",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "route_map": {
+ "outbound": "{{ out }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "route_reflector_client",
+ "getval": re.compile(
+ r"""
+ (?P<route_reflector_client>route-reflector-client)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "route-reflector-client",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "route_reflector_client": "{{ not not route_reflector_client }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "send_community.standard",
+ "getval": re.compile(
+ r"""
+ (?P<send_community>send-community)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "send-community",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "send_community": {
+ "standard": "{{ True if send_community is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "send_community.extended",
+ "getval": re.compile(
+ r"""
+ send-community
+ \s(?P<extended>extended)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "send-community extended",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "send_community": {
+ "extended": "{{ True if extended is defined }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "soft_reconfiguration_inbound",
+ "getval": re.compile(
+ r"""
+ (?P<soft_reconfiguration_inbound>soft-reconfiguration\sinbound)
+ (\s(?P<always>always))?
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "soft-reconfiguration inbound{{ ' always' if soft_reconfiguration_inbound.always|d(False) else '' }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "soft_reconfiguration_inbound": {
+ "set": "{{ True if soft_reconfiguration_inbound is defined and always is undefined }}",
+ "always": "{{ not not always }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "soo",
+ "getval": re.compile(
+ r"""
+ soo\s(?P<soo>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "soo {{ soo }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "soo": "{{ soo }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "suppress_inactive",
+ "getval": re.compile(
+ r"""
+ (?P<suppress_inactive>suppress-inactive)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "suppress-inactive",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "suppress_inactive": "{{ not not suppress_inactive }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "unsuppress_map",
+ "getval": re.compile(
+ r"""
+ unsuppress-map\s(?P<unsuppress_map>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "unsuppress-map {{ unsuppress_map }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "unsuppress_map": "{{ unsuppress_map }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "weight",
+ "getval": re.compile(
+ r"""
+ weight\s(?P<weight>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "weight {{ weight }}",
+ "result": {
+ "vrfs": {
+ "{{ 'vrf_' + vrf|d() }}": {
+ "vrf": "{{ vrf }}",
+ "neighbors": {
+ "{{ neighbor }}": {
+ "address_family": {
+ '{{ afi + "_" + safi|d() }}': {
+ "weight": "{{ weight }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/hostname.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/hostname.py
new file mode 100644
index 00000000..bf922fb4
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/hostname.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+# Copyright 2022 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 Hostname parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+class HostnameTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(HostnameTemplate, self).__init__(lines=lines, tmplt=self, module=module)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "hostname",
+ "getval": re.compile(
+ r"""
+ ^hostname\s(?P<hostname>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "hostname {{ hostname }}",
+ "result": {
+ "hostname": "{{ hostname }}",
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/logging_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/logging_global.py
new file mode 100644
index 00000000..8287794e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/logging_global.py
@@ -0,0 +1,480 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Logging_global parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_hosts(data):
+ cmd = "logging server {host}"
+ data["client_identity"] = data.get("secure", {}).get("trustpoint", {}).get("client_identity")
+
+ if "severity" in data:
+ cmd += " {severity}"
+ if "port" in data:
+ cmd += " port {port}"
+ if data["client_identity"]:
+ cmd += " secure trustpoint client-identity {client_identity}"
+ if "facility" in data:
+ cmd += " facility {facility}"
+ if "use_vrf" in data:
+ cmd += " use-vrf {use_vrf}"
+
+ cmd = cmd.format(**data)
+
+ return cmd
+
+
+class Logging_globalTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Logging_globalTemplate, self).__init__(lines=lines, tmplt=self, module=module)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "console",
+ "getval": re.compile(
+ r"""
+ ^(?P<negated>no\s)?
+ logging\sconsole
+ (\s(?P<severity>\d))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "{{ 'no ' if console.state|d('') == 'disabled' else '' }}"
+ "logging console"
+ "{{ (' ' + console.severity|string) if console.severity is defined else '' }}",
+ "result": {
+ "console": {
+ "state": "{{ 'disabled' if negated is defined else None }}",
+ "severity": "{{ severity }}",
+ },
+ },
+ },
+ {
+ "name": "event.link_status.enable",
+ "getval": re.compile(
+ r"""
+ ^(?P<negated>no\s)?
+ logging\sevent\slink-status\senable
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging event link-status enable",
+ "result": {
+ "event": {
+ "link_status": {
+ "enable": "{{ False if negated is defined else True }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "event.link_status.default",
+ "getval": re.compile(
+ r"""
+ ^(?P<negated>no\s)?
+ logging\sevent\slink-status\sdefault
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging event link-status default",
+ "result": {
+ "event": {
+ "link_status": {
+ "default": "{{ False if negated is defined else True }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "event.trunk_status.enable",
+ "getval": re.compile(
+ r"""
+ ^(?P<negated>no\s)?
+ logging\sevent\strunk-status\senable
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging event trunk-status enable",
+ "result": {
+ "event": {
+ "trunk_status": {
+ "enable": "{{ False if negated is defined else True }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "event.trunk_status.default",
+ "getval": re.compile(
+ r"""
+ ^(?P<negated>no\s)?
+ logging\sevent\strunk-status\sdefault
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging event trunk-status default",
+ "result": {
+ "event": {
+ "trunk_status": {
+ "default": "{{ False if negated is defined else True }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "history.severity",
+ "getval": re.compile(
+ r"""
+ ^logging\shistory
+ \s(?P<severity>\d)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging history {{ history.severity }}",
+ "result": {
+ "history": {
+ "severity": "{{ severity }}",
+ },
+ },
+ },
+ {
+ "name": "history.size",
+ "getval": re.compile(
+ r"""
+ ^logging\shistory\ssize
+ \s(?P<size>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging history size {{ history.size }}",
+ "result": {
+ "history": {
+ "size": "{{ size }}",
+ },
+ },
+ },
+ {
+ "name": "ip.access_list.cache.entries",
+ "getval": re.compile(
+ r"""
+ ^logging\sip\saccess-list\scache
+ \sentries\s(?P<entries>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging ip access-list cache entries {{ ip.access_list.cache.entries }}",
+ "result": {
+ "ip": {
+ "access_list": {
+ "cache": {
+ "entries": "{{ entries }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip.access_list.cache.interval",
+ "getval": re.compile(
+ r"""
+ ^logging\sip\saccess-list\scache
+ \sinterval\s(?P<interval>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging ip access-list cache interval {{ ip.access_list.cache.interval }}",
+ "result": {
+ "ip": {
+ "access_list": {
+ "cache": {
+ "interval": "{{ interval }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip.access_list.cache.threshold",
+ "getval": re.compile(
+ r"""
+ ^logging\sip\saccess-list\scache
+ \sthreshold\s(?P<threshold>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging ip access-list cache threshold {{ ip.access_list.cache.threshold }}",
+ "result": {
+ "ip": {
+ "access_list": {
+ "cache": {
+ "threshold": "{{ threshold }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "ip.access_list.detailed",
+ "getval": re.compile(
+ r"""
+ ^logging\sip\saccess-list
+ \s(?P<detailed>detailed)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging ip access-list detailed",
+ "result": {
+ "ip": {
+ "access_list": {
+ "detailed": "{{ not not detailed }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "ip.access_list.include.sgt",
+ "getval": re.compile(
+ r"""
+ ^logging\sip\saccess-list\sinclude
+ \s(?P<sgt>sgt)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging ip access-list include sgt",
+ "result": {
+ "ip": {
+ "access_list": {
+ "include": {
+ "sgt": "{{ not not sgt }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ # in some cases, the `logging level` command
+ # has an extra space at the end
+ "name": "facilities",
+ "getval": re.compile(
+ r"""
+ ^logging\slevel
+ \s(?P<facility>\S+)
+ \s(?P<severity>\d+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging level {{ facility }} {{ severity }}",
+ "result": {
+ "facilities": [
+ {
+ "facility": "{{ facility }}",
+ "severity": "{{ severity }}",
+ },
+ ],
+ },
+ },
+ {
+ "name": "logfile",
+ "getval": re.compile(
+ r"""
+ ^(?P<negated>no\s)?
+ logging\slogfile
+ (\s(?P<name>\S+))?
+ (\s(?P<severity>\d+))?
+ (\ssize\s(?P<size>\d+))?
+ (\spersistent\sthreshold\s(?P<persistent_threshold>\d+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "{{ 'no ' if logfile.state|d('') == 'disabled' else '' }}"
+ "logging logfile"
+ "{{ ' ' + logfile.name if logfile.name|d('') else '' }}"
+ "{{ (' ' + logfile.severity|string) if logfile.severity is defined else '' }}"
+ "{{ (' size ' + logfile.size|string) if logfile.size is defined else '' }}"
+ "{{ (' persistent threshold ' + logfile.persistent_threshold|string) if logfile.persistent_threshold is defined else '' }}",
+ "result": {
+ "logfile": {
+ "state": "{{ 'disabled' if negated is defined else None }}",
+ "name": "{{ name }}",
+ "severity": "{{ severity }}",
+ "persistent_threshold": "{{ persistent_threshold }}",
+ "size": "{{ size }}",
+ },
+ },
+ },
+ {
+ "name": "module",
+ "getval": re.compile(
+ r"""
+ ^(?P<negated>no\s)?
+ logging\smodule
+ (\s(?P<severity>\d))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "{{ 'no ' if module.state|d('') == 'disabled' else '' }}"
+ "logging module"
+ "{{ (' ' + module.severity|string) if module.severity is defined else '' }}",
+ "result": {
+ "module": {
+ "state": "{{ 'disabled' if negated is defined else None }}",
+ "severity": "{{ severity }}",
+ },
+ },
+ },
+ {
+ "name": "monitor",
+ "getval": re.compile(
+ r"""
+ ^(?P<negated>no\s)?
+ logging\smonitor
+ (\s(?P<severity>\d))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "{{ 'no ' if monitor.state|d('') == 'disabled' else '' }}"
+ "logging monitor"
+ "{{ (' ' + monitor.severity|string) if monitor.severity is defined else '' }}",
+ "result": {
+ "monitor": {
+ "state": "{{ 'disabled' if negated is defined else None }}",
+ "severity": "{{ severity }}",
+ },
+ },
+ },
+ {
+ "name": "origin_id.hostname",
+ "getval": re.compile(
+ r"""
+ ^logging\sorigin-id
+ \s(?P<hostname>hostname)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging origin-id hostname",
+ "result": {
+ "origin_id": {
+ "hostname": "{{ not not hostname }}",
+ },
+ },
+ },
+ {
+ "name": "origin_id.ip",
+ "getval": re.compile(
+ r"""
+ ^logging\sorigin-id
+ \sip\s(?P<ip>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging origin-id ip {{ origin_id.ip }}",
+ "result": {
+ "origin_id": {
+ "ip": "{{ ip }}",
+ },
+ },
+ },
+ {
+ "name": "origin_id.string",
+ "getval": re.compile(
+ r"""
+ ^logging\sorigin-id
+ \sstring\s(?P<string>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging origin-id string {{ origin_id.string }}",
+ "result": {
+ "origin_id": {
+ "string": "{{ string }}",
+ },
+ },
+ },
+ {
+ "name": "rate_limit",
+ "getval": re.compile(
+ r"""
+ ^(?P<negated>no\s)?
+ logging
+ \s(?P<rate_limit>rate-limit)
+ $""", re.VERBOSE,
+ ),
+ "setval": "{{ 'no ' if rate_limit|d('') == 'disabled' else '' }}"
+ "logging rate-limit",
+ "result": {
+ "rate_limit": "{{ 'disabled' if negated is defined else None }}",
+ },
+ },
+ {
+ "name": "rfc_strict",
+ "getval": re.compile(
+ r"""
+ logging\srfc-strict
+ \s(?P<rfc_strict>5424)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging rfc-strict 5424",
+ "result": {
+ "rfc_strict": "{{ not not rfc_strict }}",
+ },
+ },
+ {
+ "name": "hosts",
+ "getval": re.compile(
+ r"""
+ ^logging\sserver
+ \s(?P<host>\S+)
+ (\s(?P<severity>\d))?
+ (\sport\s(?P<port>\d+))?
+ (\ssecure\strustpoint\sclient-identity\s(?P<client_identity>\S+))?
+ (\suse-vrf\s(?P<use_vrf>\S+))?
+ (\sfacility\s(?P<facility>\S+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": _tmplt_hosts,
+ "result": {
+ "hosts": [
+ {
+ "host": "{{ host }}",
+ "severity": "{{ severity }}",
+ "secure": {
+ "trustpoint": {
+ "client_identity": "{{ client_identity }}",
+ },
+ },
+ "port": "{{ port }}",
+ "facility": "{{ facility }}",
+ "use_vrf": "{{ use_vrf }}",
+ },
+ ],
+ },
+ },
+ {
+ "name": "source_interface",
+ "getval": re.compile(
+ r"""
+ ^logging\ssource-interface
+ \s(?P<source_interface>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging source-interface {{ source_interface }}",
+ "result": {
+ "source_interface": "{{ source_interface }}",
+ },
+ },
+ {
+ "name": "timestamp",
+ "getval": re.compile(
+ r"""
+ ^logging\stimestamp
+ \s(?P<timestamp>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "logging timestamp {{ timestamp }}",
+ "result": {
+ "timestamp": "{{ timestamp }}",
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ntp_global.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ntp_global.py
new file mode 100644
index 00000000..8d5a354d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ntp_global.py
@@ -0,0 +1,320 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Ntp_global parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+class Ntp_globalTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Ntp_globalTemplate, self).__init__(lines=lines, tmplt=self, module=module)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "access_group.match_all",
+ "getval": re.compile(
+ r"""
+ ^ntp\saccess-group\s(?P<match_all>match-all)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp access-group match-all",
+ "result": {
+ "access_group": {
+ "match_all": "{{ True if match_all is defined else None }}",
+ },
+ },
+ },
+ {
+ "name": "peer",
+ "getval": re.compile(
+ r"""
+ ^ntp\saccess-group\speer\s(?P<acl>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp access-group peer {{ access_list }}",
+ "result": {
+ "access_group": {
+ "peer": [
+ {
+ "access_list": "{{ acl }}",
+ },
+ ],
+ },
+ },
+ },
+ {
+ "name": "query_only",
+ "getval": re.compile(
+ r"""
+ ^ntp\saccess-group\squery-only\s(?P<acl>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp access-group query-only {{ access_list }}",
+ "result": {
+ "access_group": {
+ "query_only": [
+ {
+ "access_list": "{{ acl }}",
+ },
+ ],
+ },
+ },
+ },
+ {
+ "name": "serve",
+ "getval": re.compile(
+ r"""
+ ^ntp\saccess-group\sserve\s(?P<acl>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp access-group serve {{ access_list }}",
+ "result": {
+ "access_group": {
+ "serve": [
+ {
+ "access_list": "{{ acl }}",
+ },
+ ],
+ },
+ },
+ },
+ {
+ "name": "serve_only",
+ "getval": re.compile(
+ r"""
+ ^ntp\saccess-group\sserve-only\s(?P<acl>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp access-group serve-only {{ access_list }}",
+ "result": {
+ "access_group": {
+ "serve_only": [
+ {
+ "access_list": "{{ acl }}",
+ },
+ ],
+ },
+ },
+ },
+ {
+ "name": "allow.control.rate_limit",
+ "getval": re.compile(
+ r"""
+ ^ntp\sallow\scontrol\srate-limit\s(?P<rate_limit>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp allow control rate-limit {{ allow.control.rate_limit }}",
+ "result": {
+ "allow": {
+ "control": {
+ "rate_limit": "{{ rate_limit }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "allow.private",
+ "getval": re.compile(
+ r"""
+ ^ntp\sallow\s(?P<private>private)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp allow private",
+ "result": {
+ "allow": {
+ "private": "{{ not not private }}",
+ },
+ },
+ },
+ {
+ "name": "authenticate",
+ "getval": re.compile(
+ r"""
+ ^ntp\s(?P<authenticate>authenticate)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp authenticate",
+ "result": {
+ "authenticate": "{{ not not authenticate }}",
+ },
+ },
+ {
+ "name": "authentication_keys",
+ "getval": re.compile(
+ r"""
+ ^ntp\sauthentication-key\s(?P<id>\d+)\smd5\s(?P<key>\S+)\s(?P<encryption>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp authentication-key {{ id }} md5 {{ key }} {{ encryption }}",
+ "result": {
+ "authentication_keys": [
+ {
+ "id": "{{ id }}",
+ "key": "{{ key }}",
+ "encryption": "{{ encryption }}",
+ },
+ ],
+ },
+ },
+ {
+ "name": "logging",
+ "getval": re.compile(
+ r"""
+ ^ntp\s(?P<logging>logging)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp logging",
+ "result": {
+ "logging": "{{ not not logging }}",
+ },
+ },
+ {
+ "name": "master.stratum",
+ "getval": re.compile(
+ r"""
+ ^ntp\smaster\s(?P<stratum>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp master {{ master.stratum }}",
+ "result": {
+ "master": {
+ "stratum": "{{ stratum }}",
+ },
+ },
+ },
+ {
+ "name": "passive",
+ "getval": re.compile(
+ r"""
+ ^ntp\s(?P<passive>passive)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp passive",
+ "result": {
+ "passive": "{{ not not passive }}",
+ },
+ },
+ {
+ "name": "peers",
+ "getval": re.compile(
+ r"""
+ ^ntp\speer
+ \s(?P<peer>\S+)
+ (\s(?P<prefer>prefer))?
+ (\suse-vrf\s(?P<use_vrf>\S+))?
+ (\skey\s(?P<key>\d+))?
+ (\sminpoll\s(?P<minpoll>\d+))?
+ (\smaxpoll\s(?P<maxpoll>\d+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp peer {{ peer }}"
+ "{{ ' prefer' if prefer is defined else ''}}"
+ "{{ (' use-vrf ' + vrf) if vrf is defined else '' }}"
+ "{{ (' key ' + key_id|string) if key_id is defined else '' }}"
+ "{{ (' minpoll ' + minpoll|string) if minpoll is defined else '' }}"
+ "{{ (' maxpoll ' + maxpoll|string) if maxpoll is defined else '' }}",
+ "result": {
+ "peers": [
+ {
+ "peer": "{{ peer }}",
+ "prefer": "{{ not not prefer }}",
+ "vrf": "{{ use_vrf }}",
+ "key_id": "{{ key }}",
+ "minpoll": "{{ minpoll }}",
+ "maxpoll": "{{ maxpoll }}",
+ },
+ ],
+ },
+ },
+ {
+ "name": "servers",
+ "getval": re.compile(
+ r"""
+ ^ntp\sserver
+ \s(?P<server>\S+)
+ (\s(?P<prefer>prefer))?
+ (\suse-vrf\s(?P<use_vrf>\S+))?
+ (\skey\s(?P<key>\d+))?
+ (\sminpoll\s(?P<minpoll>\d+))?
+ (\smaxpoll\s(?P<maxpoll>\d+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp server {{ server }}"
+ "{{ ' prefer' if prefer is defined else ''}}"
+ "{{ (' use-vrf ' + vrf) if vrf is defined else '' }}"
+ "{{ (' key ' + key_id|string) if key_id is defined else '' }}"
+ "{{ (' minpoll ' + minpoll|string) if minpoll is defined else '' }}"
+ "{{ (' maxpoll ' + maxpoll|string) if maxpoll is defined else '' }}",
+ "result": {
+ "servers": [
+ {
+ "server": "{{ server }}",
+ "prefer": "{{ not not prefer }}",
+ "vrf": "{{ use_vrf }}",
+ "key_id": "{{ key }}",
+ "minpoll": "{{ minpoll }}",
+ "maxpoll": "{{ maxpoll }}",
+ },
+ ],
+ },
+ },
+ {
+ "name": "source",
+ "getval": re.compile(
+ r"""
+ ^ntp\ssource\s(?P<source>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp source {{ source }}",
+ "result": {
+ "source": "{{ source }}",
+ },
+ },
+ {
+ "name": "source_interface",
+ "getval": re.compile(
+ r"""
+ ^ntp\ssource-interface(\s)+(?P<source_interface>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp source-interface {{ source_interface }}",
+ "result": {
+ "source_interface": "{{ source_interface }}",
+ },
+ },
+ {
+ "name": "trusted_keys",
+ "getval": re.compile(
+ r"""
+ ^ntp\strusted-key\s(?P<key>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "ntp trusted-key {{ key_id|string }}",
+ "result": {
+ "trusted_keys": [
+ {
+ "key_id": "{{ key }}",
+ },
+ ],
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospf_interfaces.py
new file mode 100644
index 00000000..2321fcaf
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospf_interfaces.py
@@ -0,0 +1,510 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Ospf_interfaces parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_authentication(data):
+ auth = data.get("authentication")
+ cmd = "ip ospf authentication"
+
+ if auth.get("enable") is False:
+ cmd = "no " + cmd
+ else:
+ if auth.get("message_digest"):
+ cmd += " message-digest"
+ elif auth.get("null_auth"):
+ cmd += " null"
+ return cmd
+
+
+class Ospf_interfacesTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Ospf_interfacesTemplate, self).__init__(lines=lines, tmplt=self, module=module)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "interface",
+ "getval": re.compile(
+ r'''
+ ^interface
+ \s(?P<name>\S+)$''', re.VERBOSE,
+ ),
+ "setval": "interface {{ name }}",
+ "result": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "address_family": {},
+ },
+ },
+ "shared": True,
+ },
+ {
+ "name": "area",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip|ipv6)
+ \srouter\s(ospf|ospfv3)
+ \s(?P<process_id>\S+)
+ \sarea\s(?P<area_id>\S+)
+ (\s(?P<secondaries>secondaries\snone))?$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip' if afi == 'ipv4' else 'ipv6' }} "
+ "router {{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "{{ process_id }} area {{ area.area_id }}{{ ' secondaries none' if area.secondaries|default('True') == False else '' }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi }}": {
+ "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}",
+ "processes": {
+ "{{ process_id }}": {
+ "process_id": "{{ process_id }}",
+ "area": {
+ "area_id": "{{ area_id }}",
+ "secondaries": "{{ False if secondaries is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "processes_multi_areas",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip|ipv6)
+ \srouter\s(ospf|ospfv3)
+ \s(?P<process_id>\S+)
+ \smulti-area\s(?P<area>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip' if afi == 'ipv4' else 'ipv6' }} "
+ "router {{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "{{ process_id }} multi-area {{ area }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi }}": {
+ "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}",
+ "processes": {
+ "{{ process_id }}": {
+ "process_id": "{{ process_id }}",
+ "multi_areas": [
+ "{{ area }}",
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "multi_areas",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip|ipv6)
+ \srouter\s(ospf|ospfv3)
+ \smulti-area\s(?P<area>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip' if afi == 'ipv4' else 'ipv6' }} "
+ "router {{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "multi-area {{ area }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi }}": {
+ "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}",
+ "multi_areas": [
+ "{{ area }}",
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip|ipv6)
+ \s(ospf|ospfv3)
+ \s(?P<authentication>authentication)
+ (\s(?P<opt>(message-digest|null)))?$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_authentication,
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi }}": {
+ "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}",
+ "authentication": {
+ "enable": "{{ True if authentication is defined and opt is undefined }}",
+ "message_digest": "{{ True if opt == 'message-digest' else None }}",
+ "null_auth": "{{ True if opt == 'null' else None }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication.key_chain",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)
+ \sospf
+ \s(?P<authentication>authentication)
+ \skey-chain\s(?P<key_chain>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "ip ospf authentication key-chain {{ authentication.key_chain }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi }}": {
+ "afi": "{{ afi|replace('ip', 'ipv4') }}",
+ "authentication": {
+ "key_chain": "{{ key_chain }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "authentication_key",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)
+ \sospf
+ \sauthentication-key
+ \s(?P<encryption>\d)
+ \s(?P<key>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "ip ospf authentication-key "
+ "{{ authentication_key.encryption }} {{ authentication_key.key }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi }}": {
+ "afi": "{{ afi|replace('ip', 'ipv4') }}",
+ "authentication_key": {
+ "encryption": "{{ encryption }}",
+ "key": "{{ key }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "message_digest_key",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)
+ \sospf
+ \smessage-digest-key
+ \s(?P<key_id>\d+)
+ \smd5
+ \s(?P<encryption>\d)
+ \s(?P<key>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "ip ospf "
+ "message-digest-key {{ message_digest_key.key_id }} "
+ "md5 {{ message_digest_key.encryption|default('') }} {{ message_digest_key.key }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi }}": {
+ "afi": "{{ afi|replace('ip', 'ipv4') }}",
+ "message_digest_key": {
+ "key_id": "{{ key_id }}",
+ "encryption": "{{ encryption }}",
+ "key": "{{ key }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "cost",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)?
+ \s(ospf|ospfv3)
+ \scost\s(?P<cost>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}"
+ "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "cost {{ cost }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi|d('ipv6') }}": {
+ "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}",
+ "cost": "{{ cost }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "dead_interval",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)?
+ \s(ospf|ospfv3)
+ \sdead-interval\s(?P<dead_interval>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}"
+ "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "dead-interval {{ dead_interval }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi|d('ipv6') }}": {
+ "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}",
+ "dead_interval": "{{ dead_interval }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "hello_interval",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)?
+ \s(ospf|ospfv3)
+ \shello-interval\s(?P<hello_interval>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}"
+ "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "hello-interval {{ hello_interval }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi|d('ipv6') }}": {
+ "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}",
+ "hello_interval": "{{ hello_interval }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "instance",
+ "getval": re.compile(
+ r"""
+ \s+(ospf|ospfv3)
+ \sinstance\s(?P<instance>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "ospfv3 instance {{ instance }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi|d('ipv6') }}": {
+ "afi": "ipv6",
+ "instance": "{{ instance }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "mtu_ignore",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)?
+ \s(ospf|ospfv3)
+ \s(?P<mtu_ignore>mtu-ignore)$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}"
+ "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "mtu-ignore",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi|d('ipv6') }}": {
+ "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}",
+ "mtu_ignore": "{{ not not mtu_ignore }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "network",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)?
+ \s(ospf|ospfv3)
+ \snetwork\s(?P<network>(broadcast|point-to-point))$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}"
+ "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "network {{ network }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi|d('ipv6') }}": {
+ "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}",
+ "network": "{{ network }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "passive_interface",
+ "getval": re.compile(
+ r"""
+ (\s+(?P<negated>no))?
+ (\s+(?P<afi>ip))?
+ \s*(ospf|ospfv3)
+ \s(?P<passive_interface>passive-interface)$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}"
+ "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "passive-interface",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi|d('ipv6') }}": {
+ "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}",
+ "passive_interface": "{{ False if negated is defined else (not not passive_interface) }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "priority",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)?
+ \s(ospf|ospfv3)
+ \spriority\s(?P<priority>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}"
+ "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "priority {{ priority }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi|d('ipv6') }}": {
+ "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}",
+ "priority": "{{ priority }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "retransmit_interval",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)?
+ \s(ospf|ospfv3)
+ \sretransmit-interval\s(?P<retransmit_interval>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}"
+ "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "retransmit-interval {{ retransmit_interval }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi|d('ipv6') }}": {
+ "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}",
+ "retransmit_interval": "{{ retransmit_interval }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "shutdown",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)?
+ \s(ospf|ospfv3)
+ \s(?P<shutdown>shutdown)$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}"
+ "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "shutdown",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi|d('ipv6') }}": {
+ "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}",
+ "shutdown": "{{ not not shutdown }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "transmit_delay",
+ "getval": re.compile(
+ r"""
+ \s+(?P<afi>ip)?
+ \s(ospf|ospfv3)
+ \stransmit-delay\s(?P<transmit_delay>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "{{ 'ip ' if afi == 'ipv4' else '' }}"
+ "{{ 'ospf' if afi == 'ipv4' else 'ospfv3' }} "
+ "transmit-delay {{ transmit_delay }}",
+ "result": {
+ "{{ name }}": {
+ "address_family": {
+ "{{ afi|d('ipv6') }}": {
+ "afi": "{{ 'ipv4' if afi is defined else 'ipv6' }}",
+ "transmit_delay": "{{ transmit_delay }}",
+ },
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv2.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv2.py
new file mode 100644
index 00000000..c8b518dd
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv2.py
@@ -0,0 +1,1101 @@
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_area_range(arange):
+ command = "area {area} range {range}".format(**arange)
+ if arange.get("not_advertise") is True:
+ command += " not-advertise"
+ if "cost" in arange:
+ command += " cost {cost}".format(**arange)
+ return command
+
+
+def _tmplt_default_information(proc):
+ command = "default-information originate"
+ if "always" in proc["default_information"] and proc["default_information"]["always"]:
+ command += " always"
+ if "route_map" in proc["default_information"]:
+ command += " route-map"
+ command += " {default_information[route_map]}".format(**proc)
+ return command
+
+
+def _tmplt_log_adjacency_changes(proc):
+ command = "log-adjacency-changes"
+ if proc.get("log_adjacency_changes").get("detail", False) is True:
+ command += " detail"
+ return command
+
+
+def _tmplt_area_authentication(area):
+ command = "area {area_id} authentication".format(**area)
+ if area.get("authentication", {}).get("message_digest"):
+ command += " message-digest"
+ return command
+
+
+def _tmplt_max_lsa(proc):
+ max_lsa = proc["max_lsa"]
+ command = "max-lsa {max_non_self_generated_lsa}".format(**max_lsa)
+ if max_lsa.get("threshold"):
+ command += " {threshold}".format(**max_lsa)
+ if max_lsa.get("warning_only"):
+ command += " warning-only"
+ if max_lsa.get("ignore_time"):
+ command += " ignore-time {ignore_time}".format(**max_lsa)
+ if max_lsa.get("ignore_count"):
+ command += " ignore-count {ignore_count}".format(**max_lsa)
+ if max_lsa.get("reset_time"):
+ command += " reset-time {reset_time}".format(**max_lsa)
+ return command
+
+
+def _tmplt_default_information(proc):
+ default_information = proc["default_information"]["originate"]
+ command = "default-information originate"
+
+ if default_information.get("set") is False:
+ command = "no {0}".format(command)
+ else:
+ if default_information.get("always"):
+ command += " always"
+ if default_information.get("route_map"):
+ command += " route-map {route_map}".format(**default_information)
+
+ return command
+
+
+def _tmplt_table_map(proc):
+ table_map = proc["table_map"]
+ command = "table-map"
+
+ if table_map.get("name"):
+ command += " {name}".format(**table_map)
+ if table_map.get("filter"):
+ command += " filter"
+
+ return command
+
+
+def _tmplt_max_metric(proc):
+ max_metric = proc["max_metric"]
+ command = "max-metric router-lsa"
+
+ if max_metric.get("router_lsa", {}).get("set") is False:
+ command = "no {0}".format(command)
+ else:
+ external_lsa = max_metric.get("router_lsa", {}).get("external_lsa", {})
+ include_stub = max_metric.get("router_lsa", {}).get("include_stub", {})
+ on_startup = max_metric.get("router_lsa", {}).get("on_startup", {})
+ summary_lsa = max_metric.get("router_lsa", {}).get("summary_lsa", {})
+ if external_lsa:
+ command += " external-lsa"
+ if external_lsa.get("max_metric_value"):
+ command += " {max_metric_value}".format(**external_lsa)
+ if include_stub:
+ command += " include-stub"
+ if on_startup:
+ command += " on-startup"
+ if on_startup.get("wait_period"):
+ command += " {wait_period}".format(**on_startup)
+ if on_startup.get("wait_for_bgp_asn"):
+ command += " wait-for bgp {wait_for_bgp_asn}".format(**on_startup)
+ if summary_lsa:
+ command += " summary-lsa"
+ if summary_lsa.get("max_metric_value"):
+ command += " {max_metric_value}".format(**summary_lsa)
+
+ return command
+
+
+def _tmplt_area_nssa(area):
+ nssa = area["nssa"]
+ command = "area {area_id} nssa".format(**area)
+ if nssa.get("set") is False:
+ command = "no {0}".format(command)
+ else:
+ for attrib in [
+ "no_summary",
+ "no_redistribution",
+ "default_information_originate",
+ ]:
+ if nssa.get(attrib):
+ command += " {0}".format(attrib.replace("_", "-"))
+ return command
+
+
+def _tmplt_area_nssa_translate(area):
+ translate = area["nssa"]["translate"]["type7"]
+ command = "area {area_id} nssa translate type7".format(**area)
+ for attrib in ["always", "never", "supress_fa"]:
+ if translate.get(attrib):
+ command += " {0}".format(attrib.replace("_", "-"))
+ return command
+
+
+def _tmplt_area_ranges(arange):
+ command = "area {area_id} range {prefix}".format(**arange)
+ if arange.get("not_advertise") is True:
+ command += " not-advertise"
+ if "cost" in arange:
+ command += " cost {cost}".format(**arange)
+ return command
+
+
+def _tmplt_area_ranges(arange):
+ command = "area {area_id} range {prefix}".format(**arange)
+ if arange.get("not_advertise") is True:
+ command += " not-advertise"
+ if "cost" in arange:
+ command += " cost {cost}".format(**arange)
+ return command
+
+
+def _tmplt_summary_address(proc):
+ command = "summary-address {prefix}".format(**proc)
+ if proc.get("tag"):
+ command += " tag {tag}".format(**proc)
+ elif proc.get("not_advertise"):
+ command += " not-advertise"
+ return command
+
+
+def _tmplt_area_stub(area):
+ stub = area["stub"]
+ command = "area {area_id} stub".format(**area)
+ if stub.get("set") is False:
+ command = "no {0}".format(command)
+ elif stub.get("no_summary"):
+ command += " no-summary"
+ return command
+
+
+def _tmplt_redistribute(redis):
+ command = "redistribute {protocol}".format(**redis)
+ if redis.get("id"):
+ command += " {id}".format(**redis)
+ if redis.get("route_map"):
+ command += " route-map {route_map}".format(**redis)
+ return command
+
+
+def _tmplt_capability_vrf_lite(proc):
+ command = "capability vrf-lite"
+ vrf_lite = proc["capability"]["vrf_lite"]
+ if vrf_lite.get("set") is False:
+ command = "no {0}".format(command)
+ else:
+ if vrf_lite.get("evpn"):
+ command += " evpn"
+ return command
+
+
+class Ospfv2Template(NetworkTemplate):
+ def __init__(self, lines=None):
+ super(Ospfv2Template, self).__init__(lines=lines, tmplt=self)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "vrf",
+ "getval": re.compile(
+ r"""
+ \s+vrf
+ \s(?P<vrf>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "vrf {{ vrf }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "vrf": "{{ vrf }}",
+ },
+ },
+ },
+ "shared": True,
+ },
+ {
+ "name": "bfd",
+ "getval": re.compile(
+ r"""
+ \s+(?P<bfd>bfd)$""",
+ re.VERBOSE,
+ ),
+ "setval": "bfd",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "bfd": "{{ not not bfd }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "process_id",
+ "getval": re.compile(
+ r"""
+ ospf(?:v3)*\s
+ (?P<process_id>\S+)""",
+ re.VERBOSE,
+ ),
+ "setval": "router ospf {{ process_id }}",
+ "result": {
+ "process_id": "{{ process_id }}",
+ },
+ "shared": True,
+ },
+ {
+ "name": "down_bit_ignore",
+ "getval": re.compile(
+ r"""
+ \s+(?P<down_bit_ignore>down-bit-ignore)$""",
+ re.VERBOSE,
+ ),
+ "setval": "down-bit-ignore",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "down_bit_ignore": "{{ not not down_bit_ignore }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "capability.vrf_lite",
+ "getval": re.compile(
+ r"""
+ \s+capability
+ \s(?P<vrf_lite>vrf-lite)
+ \s*(?P<evpn>evpn)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_capability_vrf_lite,
+ "remval": "capability vrf-lite",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "capability": {
+ "vrf_lite": {
+ "set": "{{ True if vrf_lite is defined and evpn is undefined else None }}",
+ "evpn": "{{ not not evpn }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "auto_cost",
+ "getval": re.compile(
+ r"""
+ \s+auto-cost\sreference-bandwidth\s
+ (?P<acrb>\d+)\s(?P<unit>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": (
+ "auto-cost reference-bandwidth"
+ " {{ auto_cost.reference_bandwidth }}"
+ " {{ auto_cost.unit }}"
+ ),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "auto_cost": {
+ "reference_bandwidth": "{{ acrb }}",
+ "unit": "{{ unit }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "flush_routes",
+ "getval": re.compile(
+ r"""
+ \s+(?P<flush_routes>flush-routes)$""",
+ re.VERBOSE,
+ ),
+ "setval": "flush-routes",
+ "result": {
+ "flush_routes": "{{ not not flush_routes }}",
+ },
+ },
+ {
+ "name": "graceful_restart.set",
+ "getval": re.compile(
+ r"""
+ \s+(?P<graceful_restart>no\sgraceful-restart)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "graceful-restart",
+ "remval": "no graceful-restart",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "set": "{{ not graceful_restart }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart.helper_disable",
+ "getval": re.compile(
+ r"""
+ \s+graceful-restart
+ \s+(?P<helper_disable>helper-disable)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "graceful-restart helper-disable",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "helper_disable": "{{ not not helper_disable }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart.grace_period",
+ "getval": re.compile(
+ r"""
+ \s+graceful-restart
+ \s+grace-period
+ \s+(?P<grace_period>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "graceful-restart helper-disable",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "grace_period": "{{ grace_period }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "isolate",
+ "getval": re.compile(
+ r"""
+ \s+(?P<isolate>isolate)$""",
+ re.VERBOSE,
+ ),
+ "setval": "isolate",
+ "result": {"isolate": "{{ not not isolate }}"},
+ },
+ {
+ "name": "log_adjacency_changes",
+ "getval": re.compile(
+ r"""
+ \s+(?P<log>log-adjacency-changes)
+ \s*(?P<detail>detail)*$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_log_adjacency_changes,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "log_adjacency_changes": {
+ "log": "{{ True if log is defined and detail is undefined else None }}",
+ "detail": "{{ True if detail is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "max_lsa",
+ "getval": re.compile(
+ r"""
+ \s+max-lsa
+ \s(?P<max_gen_lsa>\d+)
+ \s*(?P<threshold>\d*)
+ \s*(?P<warning_only>warning-only)*
+ \s*(ignore-time)*\s*(?P<ig_time>\d*)
+ \s*(ignore-count)*\s*(?P<ig_count>\d*)
+ \s*(reset-time)*\s*(?P<rst_time>\d*)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_max_lsa,
+ "remval": "max-lsa {{ max_lsa.max_non_self_generated_lsa }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "max_lsa": {
+ "max_non_self_generated_lsa": "{{ max_gen_lsa }}",
+ "threshold": "{{ threshold }}",
+ "ignore_time": "{{ ig_time }}",
+ "ignore_count": "{{ ig_count }}",
+ "reset_time": "{{ rst_time }}",
+ "warning_only": "{{ not not warning_only }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "mpls.traffic_eng.areas",
+ "getval": re.compile(
+ r"""
+ \s+mpls\straffic-eng\sarea
+ \s(?P<area_id>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": ("mpls traffic-eng area {{ area_id }}"),
+ "result": {
+ "mpls": {
+ "traffic_eng": {
+ "areas": [
+ {
+ "area_id": "{{ area_id }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "mpls.traffic_eng.router_id",
+ "getval": re.compile(
+ r"""
+ \s+mpls\straffic-eng\srouter-id
+ \s(?P<router_id>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": (
+ "mpls traffic-eng router-id" " {{ mpls.traffic_eng.router_id }}"
+ ),
+ "result": {"mpls": {"traffic_eng": {"router_id": "{{ router_id }}"}}},
+ },
+ {
+ "name": "mpls.traffic_eng.multicast_intact",
+ "getval": re.compile(
+ r"""
+ \s+mpls\straffic-eng
+ \s(?P<multicast_intact>multicast-intact)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": ("mpls traffic-eng multicast-intact"),
+ "result": {
+ "mpls": {
+ "traffic_eng": {
+ "multicast_intact": "{{ not not multicast_intact }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "name_lookup",
+ "getval": re.compile(
+ r"""
+ \s+(?P<name_lookup>name-lookup)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": ("name-lookup"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "name_lookup": "{{ not not name_lookup }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "passive_interface.default",
+ "getval": re.compile(
+ r"""
+ \s+passive-interface
+ \s+(?P<default>default)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": ("passive-interface default"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "passive_interface": {"default": "{{ not not default }}"},
+ },
+ },
+ },
+ },
+ {
+ "name": "rfc1583compatibility",
+ "getval": re.compile(
+ r"""
+ \s+(?P<rfc>rfc1583compatibility)$""",
+ re.VERBOSE,
+ ),
+ "setval": ("rfc1583compatibility"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "rfc1583compatibility": "{{ not not rfc }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "router_id",
+ "getval": re.compile(
+ r"""
+ \s+router-id
+ \s(?P<router_id>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": ("router-id" " {{ router_id }}"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "router_id": "{{ router_id }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "shutdown",
+ "getval": re.compile(
+ r"""
+ \s+(?P<shutdown>shutdown)$""",
+ re.VERBOSE,
+ ),
+ "setval": ("shutdown"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "shutdown": "{{ not not shutdown }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "default_information.originate",
+ "getval": re.compile(
+ r"""
+ \s+default-information
+ \s(?P<originate>originate)
+ \s*(?P<always>always)*
+ \s*(route-map)*
+ \s*(?P<route_map>\S+)*$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_default_information,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "default_information": {
+ "originate": {
+ "set": "{{ True if originate is defined and always is undefined and route_map is undefined else None }}",
+ "always": "{{ not not always }}",
+ "route_map": "{{ route_map }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "default_metric",
+ "getval": re.compile(
+ r"""
+ \s+default-metric
+ \s(?P<default_metric>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": ("default-metric {{ default_metric }}"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "default_metric": "{{ default_metric }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "distance",
+ "getval": re.compile(
+ r"""
+ \s+distance
+ \s(?P<distance>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": ("distance {{ distance }}"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "distance": "{{ distance }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "table_map",
+ "getval": re.compile(
+ r"""
+ \s+table-map
+ \s(?P<rmap>\S+)
+ \s*(?P<filter>filter)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_table_map,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "table_map": {
+ "name": "{{ rmap }}",
+ "filter": "{{ not not filter }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.lsa_arrival",
+ "getval": re.compile(
+ r"""
+ \s+timers
+ \slsa-arrival
+ \s(?P<lsa_arrival_val>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": ("timers lsa-arrival {{ timers.lsa_arrival }}"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "lsa_arrival": "{{ lsa_arrival_val }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.lsa_group_pacing",
+ "getval": re.compile(
+ r"""
+ \s+timers
+ \slsa-group-pacing
+ \s(?P<lsa_group_pacing>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "timers lsa-group-pacing {{ timers.lsa_group_pacing }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "lsa_group_pacing": "{{ lsa_group_pacing }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.throttle.lsa",
+ "getval": re.compile(
+ r"""
+ \s+timers\sthrottle\slsa
+ \s(?P<start>\d+)
+ \s(?P<hold>\d+)
+ \s(?P<max>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "timers throttle lsa {{ timers.throttle.lsa.start_interval }}"
+ " {{ timers.throttle.lsa.hold_interval }}"
+ " {{ timers.throttle.lsa.max_interval }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "throttle": {
+ "lsa": {
+ "start_interval": "{{ start }}",
+ "hold_interval": "{{ hold }}",
+ "max_interval": "{{ max }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.throttle.spf",
+ "getval": re.compile(
+ r"""
+ \s+timers\sthrottle\sspf
+ \s(?P<initial>\d+)
+ \s(?P<min>\d+)
+ \s(?P<max>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "timers throttle spf {{ timers.throttle.spf.initial_spf_delay }}"
+ " {{ timers.throttle.spf.min_hold_time }}"
+ " {{ timers.throttle.spf.max_wait_time }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "throttle": {
+ "spf": {
+ "initial_spf_delay": "{{ initial }}",
+ "min_hold_time": "{{ min }}",
+ "max_wait_time": "{{ max }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.default_cost",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)\s
+ default-cost\s(?P<default_cost>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "area {{ area_id }} default-cost {{ default_cost }}",
+ "compval": "default_cost",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "default_cost": "{{ default_cost|int }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.authentication",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)
+ \s(?P<auth>authentication)
+ \s*(?P<md>message-digest)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_area_authentication,
+ "remval": "area {{ area_id }} authentication",
+ "compval": "authentication",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "authentication": {
+ "set": "{{ True if auth is defined and md is undefined }}",
+ "message_digest": "{{ True if md is defined else False }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.filter_list",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)
+ \sfilter-list
+ \sroute-map\s(?P<rmap>\S+)
+ \s(?P<dir>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "area {{ area_id }} filter-list route-map {{ route_map }} {{ direction }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "filter_list": [
+ {
+ "route_map": "{{ rmap }}",
+ "direction": "{{ dir }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "redistribute",
+ "getval": re.compile(
+ r"""
+ \s+redistribute
+ \s(?P<protocol>\S+)
+ \s*(?P<id>\S+)*
+ \sroute-map\s(?P<rmap>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_redistribute,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "redistribute": [
+ {
+ "protocol": "{{ protocol }}",
+ "id": "{{ id }}",
+ "route_map": "{{ rmap }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "area.nssa",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)
+ \s(?P<nssa>nssa)
+ \s*(?P<no_sum>no-summary)*
+ \s*(?P<no_redis>no-redistribution)*
+ \s*(?P<def_info>default-information-originate)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_area_nssa,
+ "remval": "area {{ area_id }} nssa",
+ "compval": "nssa",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "nssa": {
+ "set": "{{ True if nssa is defined and no_sum is undefined and no_redis is undefined and def_info is undefined }}",
+ "no_summary": "{{ not not no_sum }}",
+ "no_redistribution": "{{ not not no_redis }}",
+ "default_information_originate": "{{ not not def_info }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.nssa.translate",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)\snssa
+ \stranslate
+ \stype7
+ \s(?P<choice>always|never)
+ \s*(?P<supress_fa>supress-fa)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_area_nssa_translate,
+ "compval": "nssa.translate",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "nssa": {
+ "translate": {
+ "type7": {
+ "always": '{{ True if choice == "always" else None }}',
+ "never": '{{ True if choice == "never" else None }}',
+ "supress_fa": "{{ not not supress_fa }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.ranges",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)
+ \srange\s(?P<prefix>\S+)
+ \s*(cost)*\s*(?P<cost>\d+)*
+ \s*(?P<not_adver>not-advertise)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_area_ranges,
+ "remval": "area {{ area_id }} range {{ prefix }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "ranges": [
+ {
+ "prefix": "{{ prefix }}",
+ "cost": "{{ cost }}",
+ "not_advertise": "{{ not not not_adver }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "summary_address",
+ "getval": re.compile(
+ r"""
+ \s+summary-address
+ \s(?P<prefix>\S+)
+ \s*(?P<not_adver>not-advertise)*
+ \s*(tag)*\s*(?P<tag>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_summary_address,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "summary_address": [
+ {
+ "prefix": "{{ prefix }}",
+ "not_advertise": "{{ not not not_adver }}",
+ "tag": "{{ tag }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ {
+ "name": "area.stub",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)
+ \s(?P<stub>stub)
+ \s*(?P<no_summary>no-summary)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_area_stub,
+ "remval": "area {{ area_id }} stub",
+ "compval": "stub",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "stub": {
+ "set": "{{ True if stub is defined and no_summary is undefined else None }}",
+ "no_summary": "{{ not not no_summary }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "maximum_paths",
+ "getval": re.compile(
+ r"""
+ \s+maximum-paths
+ \s(?P<maximum_paths>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": ("maximum-paths {{ maximum_paths }}"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {"maximum_paths": "{{ maximum_paths }}"},
+ },
+ },
+ },
+ {
+ "name": "max_metric",
+ "getval": re.compile(
+ r"""
+ \s+max-metric
+ \s+(?P<router_lsa>router-lsa)
+ \s*(?P<external_lsa>external-lsa)*
+ \s*(?P<max_metric_value>\d+)*
+ \s*(?P<include_stub>include-stub)*
+ \s*(?P<on_startup>on-startup)*
+ \s*(?P<wait_period>\d+)*
+ \s*(wait-for\sbgp)*
+ \s*(?P<bgp_asn>\d+)*
+ \s*(?P<summary_lsa>summary-lsa)*
+ \s*(?P<sum_lsa_max_metric_value>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_max_metric,
+ "remval": "max-metric router-lsa",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "max_metric": {
+ "router_lsa": {
+ "set": "{{ True if router_lsa is defined and external_lsa is undefined else None }}",
+ "external_lsa": {
+ "set": "{{ True if external_lsa is defined and max_metric_value is undefined else None }}",
+ "max_metric_value": "{{ max_metric_value }}",
+ },
+ "include_stub": "{{ not not include_stub }}",
+ "on_startup": {
+ "set": "{{ True if on_startup is defined and (wait_period and bgp_asn) is undefined else None }}",
+ "wait_period": "{{ wait_period }}",
+ "wait_for_bgp_asn": "{{ bgp_asn }}",
+ },
+ "summary_lsa": {
+ "set": "{{ True if summary_lsa is defined and sum_lsa_max_metric_value is undefined else None }}",
+ "max_metric_value": "{{ sum_lsa_max_metric_value }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv3.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv3.py
new file mode 100644
index 00000000..b46c3cf4
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/ospfv3.py
@@ -0,0 +1,945 @@
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Ospfv3 parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_area_nssa(area):
+ nssa = area["nssa"]
+ command = "area {area_id} nssa".format(**area)
+ if nssa.get("set") is False:
+ command = "no {0}".format(command)
+ else:
+ for attrib in [
+ "no_summary",
+ "no_redistribution",
+ "default_information_originate",
+ ]:
+ if nssa.get(attrib):
+ command += " {0}".format(attrib.replace("_", "-"))
+ if nssa.get("route_map"):
+ command += " route-map {route_map}".format(**nssa)
+ return command
+
+
+def _tmplt_area_nssa_translate(area):
+ translate = area["nssa"]["translate"]["type7"]
+ command = "area {area_id} nssa translate type7".format(**area)
+ for attrib in ["always", "never", "supress_fa"]:
+ if translate.get(attrib):
+ command += " {0}".format(attrib.replace("_", "-"))
+ return command
+
+
+def _tmplt_area_stub(area):
+ stub = area["stub"]
+ command = "area {area_id} stub".format(**area)
+ if stub.get("set") is False:
+ command = "no {0}".format(command)
+ elif stub.get("no_summary"):
+ command += " no-summary"
+ return command
+
+
+def _tmplt_log_adjacency_changes(proc):
+ command = "log-adjacency-changes"
+ if proc.get("log_adjacency_changes").get("detail", False) is True:
+ command += " detail"
+ return command
+
+
+def _tmplt_max_lsa(proc):
+ max_lsa = proc["max_lsa"]
+ command = "max-lsa {max_non_self_generated_lsa}".format(**max_lsa)
+ if max_lsa.get("threshold"):
+ command += " {threshold}".format(**max_lsa)
+ if max_lsa.get("warning_only"):
+ command += " warning-only"
+ if max_lsa.get("ignore_time"):
+ command += " ignore-time {ignore_time}".format(**max_lsa)
+ if max_lsa.get("ignore_count"):
+ command += " ignore-count {ignore_count}".format(**max_lsa)
+ if max_lsa.get("reset_time"):
+ command += " reset-time {reset_time}".format(**max_lsa)
+ return command
+
+
+def _tmplt_max_metric(proc):
+ max_metric = proc["max_metric"]
+ command = "max-metric router-lsa"
+
+ if max_metric.get("router_lsa", {}).get("set") is False:
+ command = "no {0}".format(command)
+ else:
+ external_lsa = max_metric.get("router_lsa", {}).get("external_lsa", {})
+ stub_prefix_lsa = max_metric.get("router_lsa", {}).get("stub_prefix_lsa", {})
+ on_startup = max_metric.get("router_lsa", {}).get("on_startup", {})
+ inter_area_prefix_lsa = max_metric.get("router_lsa", {}).get("inter_area_prefix_lsa", {})
+ if external_lsa:
+ command += " external-lsa"
+ if external_lsa.get("max_metric_value"):
+ command += " {max_metric_value}".format(**external_lsa)
+ if stub_prefix_lsa:
+ command += " stub-prefix-lsa"
+ if on_startup:
+ command += " on-startup"
+ if on_startup.get("wait_period"):
+ command += " {wait_period}".format(**on_startup)
+ if on_startup.get("wait_for_bgp_asn"):
+ command += " wait-for bgp {wait_for_bgp_asn}".format(**on_startup)
+ if inter_area_prefix_lsa:
+ command += " inter-area-prefix-lsa"
+ if inter_area_prefix_lsa.get("max_metric_value"):
+ command += " {max_metric_value}".format(**inter_area_prefix_lsa)
+
+ return command
+
+
+def _tmplt_area_ranges(arange):
+ command = "area {area_id} range {prefix}".format(**arange)
+ if arange.get("not_advertise") is True:
+ command += " not-advertise"
+ if "cost" in arange:
+ command += " cost {cost}".format(**arange)
+ return command
+
+
+def _tmplt_default_information(proc):
+ default_information = proc["default_information"]["originate"]
+ command = "default-information originate"
+
+ if default_information.get("set") is False:
+ command = "no {0}".format(command)
+ else:
+ if default_information.get("always"):
+ command += " always"
+ if default_information.get("route_map"):
+ command += " route-map {route_map}".format(**default_information)
+
+ return command
+
+
+def _tmplt_redistribute(redis):
+ command = "redistribute {protocol}".format(**redis)
+ if redis.get("id"):
+ command += " {id}".format(**redis)
+ command += " route-map {route_map}".format(**redis)
+ return command
+
+
+def _tmplt_summary_address(proc):
+ command = "summary-address {prefix}".format(**proc)
+ if proc.get("tag"):
+ command += " tag {tag}".format(**proc)
+ elif proc.get("not_advertise"):
+ command += " not-advertise"
+ return command
+
+
+def _tmplt_table_map(proc):
+ table_map = proc["table_map"]
+ command = "table-map {name}".format(**table_map)
+ if table_map.get("filter"):
+ command += " filter"
+
+ return command
+
+
+class Ospfv3Template(NetworkTemplate):
+ def __init__(self, lines=None):
+ super(Ospfv3Template, self).__init__(lines=lines, tmplt=self)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "vrf",
+ "getval": re.compile(
+ r"""
+ \s+vrf
+ \s(?P<vrf>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "vrf {{ vrf }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "vrf": "{{ vrf }}",
+ },
+ },
+ },
+ "shared": True,
+ },
+ {
+ "name": "process_id",
+ "getval": re.compile(
+ r"""
+ ospfv3
+ \s(?P<process_id>\S+)""",
+ re.VERBOSE,
+ ),
+ "setval": "router ospfv3 {{ process_id }}",
+ "result": {
+ "process_id": "{{ process_id }}",
+ },
+ "shared": True,
+ },
+ {
+ "name": "router_id",
+ "getval": re.compile(
+ r"""
+ \s+router-id
+ \s(?P<router_id>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "router-id {{ router_id }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "router_id": "{{ router_id }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "address_family",
+ "getval": re.compile(
+ r"""
+ \s+address-family
+ \s(?P<afi>\S+)
+ \s(?P<safi>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "address-family {{ afi }} {{ safi }}",
+ "result": {
+ "address_family": {
+ "afi": "{{ afi }}",
+ "safi": "{{ safi }}",
+ },
+ },
+ 'shared': True,
+ },
+ {
+ "name": "area.default_cost",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)\s
+ default-cost\s(?P<default_cost>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "area {{ area_id }} default-cost {{ default_cost }}",
+ "compval": "default_cost",
+ "result": {
+ 'address_family': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "default_cost": "{{ default_cost|int }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.filter_list",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)
+ \sfilter-list
+ \sroute-map\s(?P<rmap>\S+)
+ \s(?P<dir>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "area {{ area_id }} filter-list route-map {{ route_map }} {{ direction }}",
+ "result": {
+ 'address_family': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "filter_list": [
+ {
+ "route_map": "{{ rmap }}",
+ "direction": "{{ dir }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.ranges",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)
+ \srange\s(?P<prefix>\S+)
+ \s*(cost)*\s*(?P<cost>\d+)*
+ \s*(?P<not_adver>not-advertise)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_area_ranges,
+ "remval": "area {{ area_id }} range {{ prefix }}",
+ "result": {
+ "address_family": {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "ranges": [
+ {
+ "prefix": "{{ prefix }}",
+ "cost": "{{ cost }}",
+ "not_advertise": "{{ not not not_adver }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "default_information.originate",
+ "getval": re.compile(
+ r"""
+ \s+default-information
+ \s(?P<originate>originate)
+ \s*(?P<always>always)*
+ \s*(route-map)*
+ \s*(?P<route_map>\S+)*$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_default_information,
+ "result": {
+ "address_family": {
+ "default_information": {
+ "originate": {
+ "set": "{{ True if originate is defined and always is undefined and route_map is undefined else None }}",
+ "always": "{{ not not always }}",
+ "route_map": "{{ route_map }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "distance",
+ "getval": re.compile(
+ r"""
+ \s+distance
+ \s(?P<distance>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": "distance {{ distance }}",
+ "result": {
+ "address_family": {
+ "distance": "{{ distance }}",
+ },
+ },
+ },
+ {
+ "name": "maximum_paths",
+ "getval": re.compile(
+ r"""
+ \s+maximum-paths
+ \s(?P<maximum_paths>\d+)$""",
+ re.VERBOSE,
+ ),
+ "setval": ("maximum-paths {{ maximum_paths }}"),
+ "result": {
+ "address_family": {
+ "maximum_paths": "{{ maximum_paths }}",
+ },
+ },
+ },
+ {
+ "name": "redistribute",
+ "getval": re.compile(
+ r"""
+ \s+redistribute
+ \s(?P<protocol>\S+)
+ \s*(?P<id>\S+)*
+ \sroute-map\s(?P<rmap>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_redistribute,
+ "result": {
+ "address_family": {
+ "redistribute": [
+ {
+ "protocol": "{{ protocol }}",
+ "id": "{{ id }}",
+ "route_map": "{{ rmap }}",
+ },
+ ],
+ },
+ },
+ },
+ {
+ "name": "summary_address",
+ "getval": re.compile(
+ r"""
+ \s+summary-address
+ \s(?P<prefix>\S+)
+ \s*(?P<not_adver>not-advertise)*
+ \s*(tag)*\s*(?P<tag>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_summary_address,
+ "result": {
+ "address_family": {
+ "summary_address": [
+ {
+ "prefix": "{{ prefix }}",
+ "not_advertise": "{{ not not not_adver }}",
+ "tag": "{{ tag }}",
+ },
+ ],
+ },
+ },
+ },
+ {
+ "name": "table_map",
+ "getval": re.compile(
+ r"""
+ \s+table-map
+ \s(?P<rmap>\S+)
+ \s*(?P<filter>filter)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_table_map,
+ "result": {
+ "address_family": {
+ "table_map": {
+ "name": "{{ rmap }}",
+ "filter": "{{ not not filter }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.throttle.spf",
+ "getval": re.compile(
+ r"""
+ \s+timers\sthrottle\sspf
+ \s(?P<initial>\d+)
+ \s(?P<min>\d+)
+ \s(?P<max>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "timers throttle spf {{ timers.throttle.spf.initial_spf_delay }}"
+ " {{ timers.throttle.spf.min_hold_time }}"
+ " {{ timers.throttle.spf.max_wait_time }}",
+ "result": {
+ "address_family": {
+ "timers": {
+ "throttle": {
+ "spf": {
+ "initial_spf_delay": "{{ initial }}",
+ "min_hold_time": "{{ min }}",
+ "max_wait_time": "{{ max }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.nssa",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)
+ \s(?P<nssa>nssa)
+ \s*(?P<no_sum>no-summary)*
+ \s*(?P<no_redis>no-redistribution)*
+ \s*(?P<def_info>default-information-originate)*
+ \s*(route-map)*\s*(?P<rmap>\S+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_area_nssa,
+ "remval": "area {{ area_id }} nssa",
+ "compval": "nssa",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "nssa": {
+ "set": "{{ True if nssa is defined and no_sum is undefined and no_redis is undefined and \
+ def_info is undefined and rmap is undefined }}",
+ "no_summary": "{{ not not no_sum }}",
+ "no_redistribution": "{{ not not no_redis }}",
+ "default_information_originate": "{{ not not def_info }}",
+ "route_map": "{{ rmap }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.nssa.translate",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)\snssa
+ \stranslate
+ \stype7
+ \s(?P<choice>always|never)
+ \s*(?P<supress_fa>supress-fa)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_area_nssa_translate,
+ "compval": "nssa.translate",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "nssa": {
+ "translate": {
+ "type7": {
+ "always": '{{ True if choice == "always" else None }}',
+ "never": '{{ True if choice == "never" else None }}',
+ "supress_fa": "{{ not not supress_fa }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.stub",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)
+ \s(?P<stub>stub)
+ \s*(?P<no_summary>no-summary)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_area_stub,
+ "remval": "area {{ area_id }} stub",
+ "compval": "stub",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "stub": {
+ "set": "{{ True if stub is defined and no_summary is undefined else None }}",
+ "no_summary": "{{ not not no_summary }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "area.virtual_link",
+ "getval": re.compile(
+ r"""
+ \s+area\s(?P<area_id>\S+)
+ \svirtual-link
+ \s(?P<virtual_link>\S+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "area {{ area_id }} virtual-link {{ virtual_link }}",
+ "compval": "virtual_link",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "areas": {
+ "{{ area_id }}": {
+ "area_id": "{{ area_id }}",
+ "virtual_link": "{{ virtual_link }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "auto_cost",
+ "getval": re.compile(
+ r"""
+ \s+auto-cost\sreference-bandwidth\s
+ (?P<acrb>\d+)\s(?P<unit>\S+)$""",
+ re.VERBOSE,
+ ),
+ "setval": (
+ "auto-cost reference-bandwidth"
+ " {{ auto_cost.reference_bandwidth }}"
+ " {{ auto_cost.unit }}"
+ ),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "auto_cost": {
+ "reference_bandwidth": "{{ acrb }}",
+ "unit": "{{ unit }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "flush_routes",
+ "getval": re.compile(
+ r"""
+ \s+(?P<flush_routes>flush-routes)$""",
+ re.VERBOSE,
+ ),
+ "setval": "flush-routes",
+ "result": {
+ "flush_routes": "{{ not not flush_routes }}",
+ },
+ },
+ {
+ "name": "graceful_restart.set",
+ "getval": re.compile(
+ r"""
+ \s+(?P<graceful_restart>no\sgraceful-restart)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "graceful-restart",
+ "remval": "no graceful-restart",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "set": "{{ not graceful_restart }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart.helper_disable",
+ "getval": re.compile(
+ r"""
+ \s+graceful-restart
+ \s+(?P<helper_disable>helper-disable)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "graceful-restart helper-disable",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "helper_disable": "{{ not not helper_disable }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart.grace_period",
+ "getval": re.compile(
+ r"""
+ \s+graceful-restart
+ \s+grace-period
+ \s+(?P<grace_period>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "graceful-restart grace-period {{ graceful_restart.grace_period }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "grace_period": "{{ grace_period }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "graceful_restart.planned_only",
+ "getval": re.compile(
+ r"""
+ \s+no
+ \s+graceful-restart
+ \s+(?P<planned_only>planned-only)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "graceful-restart planned-only",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "graceful_restart": {
+ "planned_only": "{{ not planned_only }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "isolate",
+ "getval": re.compile(
+ r"""
+ \s+(?P<isolate>isolate)$""",
+ re.VERBOSE,
+ ),
+ "setval": "isolate",
+ "result": {"isolate": "{{ not not isolate }}"},
+ },
+ {
+ "name": "log_adjacency_changes",
+ "getval": re.compile(
+ r"""
+ \s+(?P<log>log-adjacency-changes)
+ \s*(?P<detail>detail)*$""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_log_adjacency_changes,
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "log_adjacency_changes": {
+ "log": "{{ True if log is defined and detail is undefined else None }}",
+ "detail": "{{ True if detail is defined else None }}",
+ },
+ },
+ },
+ },
+ },
+ {
+
+ "name": "max_lsa",
+ "getval": re.compile(
+ r"""
+ \s+max-lsa
+ \s(?P<max_gen_lsa>\d+)
+ \s*(?P<threshold>\d*)
+ \s*(?P<warning_only>warning-only)*
+ \s*(ignore-time)*\s*(?P<ig_time>\d*)
+ \s*(ignore-count)*\s*(?P<ig_count>\d*)
+ \s*(reset-time)*\s*(?P<rst_time>\d*)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_max_lsa,
+ "remval": "max-lsa {{ max_lsa.max_non_self_generated_lsa }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "max_lsa": {
+ "max_non_self_generated_lsa": "{{ max_gen_lsa }}",
+ "threshold": "{{ threshold }}",
+ "ignore_time": "{{ ig_time }}",
+ "ignore_count": "{{ ig_count }}",
+ "reset_time": "{{ rst_time }}",
+ "warning_only": "{{ not not warning_only }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "max_metric",
+ "getval": re.compile(
+ r"""
+ \s+max-metric
+ \s+(?P<router_lsa>router-lsa)
+ \s*(?P<external_lsa>external-lsa)*
+ \s*(?P<max_metric_value>\d+)*
+ \s*(?P<stub_prefix_lsa>stub-prefix-lsa)*
+ \s*(?P<on_startup>on-startup)*
+ \s*(?P<wait_period>\d+)*
+ \s*(wait-for\sbgp)*
+ \s*(?P<bgp_asn>\d+)*
+ \s*(?P<inter_area_prefix_lsa>inter-area-prefix-lsa)*
+ \s*(?P<max_metric_summary_lsa>\d+)*
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": _tmplt_max_metric,
+ "remval": "max-metric router-lsa",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "max_metric": {
+ "router_lsa": {
+ "set": "{{ True if router_lsa is defined and (external_lsa is undefined) and (inter_area_prefix_lsa is undefined) and \
+ (stub_prefix_lsa is undefined) and (on_startup is undefined) else None }}",
+ "external_lsa": {
+ "set": "{{ True if external_lsa is defined and max_metric_value is undefined else None }}",
+ "max_metric_value": "{{ max_metric_value }}",
+ },
+ "stub_prefix_lsa": "{{ not not stub_prefix_lsa }}",
+ "on_startup": {
+ "set": "{{ True if on_startup is defined and (wait_period and bgp_asn) is undefined else None }}",
+ "wait_period": "{{ wait_period }}",
+ "wait_for_bgp_asn": "{{ bgp_asn }}",
+ },
+ "inter_area_prefix_lsa": {
+ "set": "{{ True if inter_area_prefix_lsa is defined and max_metric_summary_lsa is undefined else None }}",
+ "max_metric_value": "{{ max_metric_summary_lsa }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "name_lookup",
+ "getval": re.compile(
+ r"""
+ \s+(?P<name_lookup>name-lookup)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": ("name-lookup"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "name_lookup": "{{ not not name_lookup }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "passive_interface.default",
+ "getval": re.compile(
+ r"""
+ \s+passive-interface
+ \s+(?P<default>default)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": ("passive-interface default"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "passive_interface": {"default": "{{ not not default }}"},
+ },
+ },
+ },
+ },
+ {
+ "name": "shutdown",
+ "getval": re.compile(
+ r"""
+ \s+(?P<shutdown>shutdown)$""",
+ re.VERBOSE,
+ ),
+ "setval": ("shutdown"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "shutdown": "{{ not not shutdown }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.lsa_arrival",
+ "getval": re.compile(
+ r"""
+ \s+timers
+ \slsa-arrival
+ \s(?P<lsa_arrival_val>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": ("timers lsa-arrival {{ timers.lsa_arrival }}"),
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "lsa_arrival": "{{ lsa_arrival_val }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.lsa_group_pacing",
+ "getval": re.compile(
+ r"""
+ \s+timers
+ \slsa-group-pacing
+ \s(?P<lsa_group_pacing>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "timers lsa-group-pacing {{ timers.lsa_group_pacing }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "lsa_group_pacing": "{{ lsa_group_pacing }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "timers.throttle.lsa",
+ "getval": re.compile(
+ r"""
+ \s+timers\sthrottle\slsa
+ \s(?P<start>\d+)
+ \s(?P<hold>\d+)
+ \s(?P<max>\d+)
+ $""",
+ re.VERBOSE,
+ ),
+ "setval": "timers throttle lsa {{ timers.throttle.lsa.start_interval }}"
+ " {{ timers.throttle.lsa.hold_interval }}"
+ " {{ timers.throttle.lsa.max_interval }}",
+ "result": {
+ "vrfs": {
+ '{{ "vrf_" + vrf|d() }}': {
+ "timers": {
+ "throttle": {
+ "lsa": {
+ "start_interval": "{{ start }}",
+ "hold_interval": "{{ hold }}",
+ "max_interval": "{{ max }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/prefix_lists.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/prefix_lists.py
new file mode 100644
index 00000000..0bb66ead
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/prefix_lists.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Prefix_lists parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+class Prefix_listsTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Prefix_listsTemplate, self).__init__(lines=lines, tmplt=self, module=module)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "entry",
+ "getval": re.compile(
+ r"""
+ ^(?P<afi>ip|ipv6)
+ \sprefix-list
+ \s(?P<name>\S+)
+ \sseq\s(?P<sequence>\d+)
+ \s(?P<action>permit|deny)
+ \s(?P<prefix>\S+)
+ (\seq\s(?P<eq>\d+))?
+ (\sge\s(?P<ge>\d+))?
+ (\sle\s(?P<le>\d+))?
+ (\smask\s(?P<mask>\S+))?
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "{{ 'ip' if afi == 'ipv4' else afi }} prefix-list {{ name }}"
+ "{{ (' seq ' + sequence|string) if sequence|d('') else '' }}"
+ " {{ action }}"
+ " {{ prefix }}"
+ "{{ (' eq ' + eq|string) if eq|d('') else '' }}"
+ "{{ (' ge ' + ge|string) if ge|d('') else '' }}"
+ "{{ (' le ' + le|string) if le|d('') else '' }}"
+ "{{ (' mask ' + mask) if mask|d('') else '' }}",
+ "result": {
+ "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}": {
+ "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}",
+ "prefix_lists": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "entries": [
+ {
+ "sequence": "{{ sequence|d(None) }}",
+ "action": "{{ action }}",
+ "prefix": "{{ prefix }}",
+ "eq": "{{ eq }}",
+ "ge": "{{ ge }}",
+ "le": "{{ le }}",
+ "mask": "{{ mask }}",
+ },
+ ],
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "description",
+ "getval": re.compile(
+ r"""
+ ^(?P<afi>ip|ipv6)
+ \sprefix-list
+ \s(?P<name>\S+)
+ \sdescription\s(?P<description>.+)\s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "{{ 'ip' if afi == 'ipv4' else afi }} prefix-list {{ name }} description {{ description }}",
+ "result": {
+ "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}": {
+ "afi": "{{ 'ipv4' if afi == 'ip' else 'ipv6' }}",
+ "prefix_lists": {
+ "{{ name }}": {
+ "name": "{{ name }}",
+ "description": "{{ description }}",
+ },
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/route_maps.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/route_maps.py
new file mode 100644
index 00000000..8267a905
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/route_maps.py
@@ -0,0 +1,1367 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Route_maps parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _tmplt_match_ip_multicast(data):
+ cmd = "match ip multicast"
+ multicast = data["match"]["ip"]["multicast"]
+
+ if "source" in multicast:
+ cmd += " source {source}".format(**multicast)
+
+ if "prefix" in multicast.get("group", {}):
+ cmd += " group {prefix}".format(**multicast["group"])
+ else:
+ if "first" in multicast.get("group_range", {}):
+ cmd += " group-range {first}".format(**multicast["group_range"])
+ if "last" in multicast.get("group_range", {}):
+ cmd += " to {last}".format(**multicast["group_range"])
+
+ if "rp" in multicast:
+ cmd += " rp {prefix}".format(**multicast["rp"])
+ if "rp_type" in multicast["rp"]:
+ cmd += " rp-type {rp_type}".format(**multicast["rp"])
+
+ return cmd
+
+
+def _tmplt_match_ipv6_multicast(data):
+ cmd = "match ipv6 multicast"
+ multicast = data["match"]["ipv6"]["multicast"]
+
+ if "source" in multicast:
+ cmd += " source {source}".format(**multicast)
+
+ if "prefix" in multicast.get("group", {}):
+ cmd += " group {prefix}".format(**multicast["group"])
+ else:
+ if "first" in multicast.get("group_range", {}):
+ cmd += " group-range {first}".format(**multicast["group_range"])
+ if "last" in multicast.get("group_range", {}):
+ cmd += " to {last}".format(**multicast["group_range"])
+
+ if "rp" in multicast:
+ cmd += " rp {prefix}".format(**multicast["rp"])
+ if "rp_type" in multicast["rp"]:
+ cmd += " rp-type {rp_type}".format(**multicast["rp"])
+
+ return cmd
+
+
+def _tmplt_set_metric(data):
+ cmd = "set metric"
+ metric = data["set"]["metric"]
+
+ for x in [
+ "bandwidth",
+ "igrp_delay_metric",
+ "igrp_reliability_metric",
+ "igrp_effective_bandwidth_metric",
+ "igrp_mtu",
+ ]:
+ if x in metric:
+ cmd += " {0}".format(metric[x])
+
+ return cmd
+
+
+class Route_mapsTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Route_mapsTemplate, self).__init__(lines=lines, tmplt=self, module=module)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "route_map",
+ "getval": re.compile(
+ r"""
+ ^route-map\s(?P<route_map>\S+)\s(?P<action>\S+)\s(?P<sequence>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "route-map {{ route_map }}"
+ "{{ ' ' + action if action is defined else '' }}"
+ "{{ ' ' + sequence|string if sequence is defined else '' }}",
+ "result": {
+ "{{ route_map }}": {
+ "route_map": "{{ route_map }}",
+ "entries": {
+ "{{ sequence }}": {
+ "sequence": "{{ sequence }}",
+ "action": "{{ action }}",
+ },
+ },
+ },
+ },
+ "shared": True,
+ },
+ {
+ "name": "continue_sequence",
+ "getval": re.compile(
+ r"""
+ \s+continue\s(?P<continue_sequence>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "continue {{ continue_sequence }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "continue_sequence": "{{ continue_sequence }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "description",
+ "getval": re.compile(
+ r"""
+ \s+description\s(?P<description>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "description {{ description }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "description": "{{ description }}",
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.as_number.asn",
+ "getval": re.compile(
+ r"""
+ \s+match\sas-number
+ (?!\sas-path-list)
+ \s(?P<asn>.+)\s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match as-number {{ match.as_number.asn|join(', ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "route_map": "{{ route_map }}",
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "as_number": {
+ "asn": "{{ asn.rstrip().split(', ') }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.as_number.as_path_list",
+ "getval": re.compile(
+ r"""
+ \s+match\sas-number
+ \sas-path-list\s(?P<as_path_list>.+)\s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match as-number as-path-list {{ match.as_number.as_path_list|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "as_number": {
+ "as_path_list": "{{ as_path_list.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.as_path",
+ "getval": re.compile(
+ r"""
+ \s+match\sas-path\s(?P<as_path>.+)\s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match as-path {{ match.as_path|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "as_path": "{{ as_path.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.community.community_list",
+ "getval": re.compile(
+ r"""
+ \s+match\scommunity
+ \s(?P<community_list>.+)
+ (\s(?P<exact_match>exact-match))?
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match community {{ match.community.community_list|join(' ') }}{{ ' exact-match' if match.community.exact_match|d(False) else '' }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "community": {
+ "community_list": "{{ community_list.split() }}",
+ "exact_match": "{{ not not exact_match }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.evpn.route_types",
+ "getval": re.compile(
+ r"""
+ \s+match\sevpn
+ \sroute-type
+ \s(?P<route_types>.+)\s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match evpn route-type {{ match.evpn.route_types|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "evpn": {
+ "route_types": "{{ route_types.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.extcommunity.extcommunity_list",
+ "getval": re.compile(
+ r"""
+ \s+match\sextcommunity
+ \s(?P<extcommunity_list>.+)
+ \s(?P<exact_match>exact-match)?
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match extcommunity {{ match.extcommunity.extcommunity_list|join(' ') }}"
+ "{{ ' exact-match' if match.extcommunity.exact_match|d(False) else '' }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "extcommunity": {
+ "extcommunity_list": "{{ extcommunity_list.split() }}",
+ "exact_match": "{{ not not exact_match }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.interfaces",
+ "getval": re.compile(
+ r"""
+ \s+match\sinterface
+ \s(?P<interfaces>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match interface {{ match.interfaces|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "interfaces": "{{ interfaces.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.ip.address.access_list",
+ "getval": re.compile(
+ r"""
+ \s+match\sip\saddress
+ \s(?P<access_list>\S+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match ip address {{ match.ip.address.access_list }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "ip": {
+ "address": {
+ "access_list": "{{ access_list }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.ip.address.prefix_lists",
+ "getval": re.compile(
+ r"""
+ \s+match\sip\saddress
+ \sprefix-list
+ \s(?P<prefix_lists>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match ip address prefix-list {{ match.ip.address.prefix_lists|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "ip": {
+ "address": {
+ "prefix_lists": "{{ prefix_lists.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ # match ip multicast source 192.1.2.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 209.165.201.0/27 rp-type Bidir
+ {
+ "name": "match.ip.multicast",
+ "getval": re.compile(
+ r"""
+ \s+match\sip\smulticast
+ (\ssource\s(?P<source>\S+))?
+ (\sgroup\s(?P<prefix>\S+))?
+ (\sgroup-range
+ (\s(?P<first>\S+))?
+ (\sto)?
+ (\s(?P<last>\S+)))?
+ (\srp\s(?P<rp>\S+))?
+ (\srp-type\s(?P<rp_type>\S+))?
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": _tmplt_match_ip_multicast,
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "ip": {
+ "multicast": {
+ "group": {
+ "prefix": "{{ prefix }}",
+ },
+ "group_range": {
+ "first": "{{ first }}",
+ "last": "{{ last }}",
+ },
+ "rp": {
+ "prefix": "{{ rp }}",
+ "rp_type": "{{ rp_type }}",
+ },
+ "source": "{{ source }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.ip.next_hop.prefix_lists",
+ "getval": re.compile(
+ r"""
+ \s+match\sip\snext-hop
+ \sprefix-list\s(?P<prefix_lists>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match ip next-hop prefix-list {{ match.ip.next_hop.prefix_lists|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "ip": {
+ "next_hop": {
+ "prefix_lists": "{{ prefix_lists.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.ip.route_source.prefix_lists",
+ "getval": re.compile(
+ r"""
+ \s+match\sip\sroute-source
+ \sprefix-list\s(?P<prefix_lists>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match ip route-source prefix-list {{ match.ip.route_source.prefix_lists|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "ip": {
+ "route_source": {
+ "prefix_lists": "{{ prefix_lists.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.ipv6.address.access_list",
+ "getval": re.compile(
+ r"""
+ \s+match\sipv6\saddress
+ \s(?P<access_list>\S+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match ipv6 address {{ match.ipv6.address.access_list }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "ipv6": {
+ "address": {
+ "access_list": "{{ access_list }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.ipv6.address.prefix_lists",
+ "getval": re.compile(
+ r"""
+ \s+match\sipv6\saddress
+ \sprefix-list
+ \s(?P<prefix_lists>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match ipv6 address prefix-list {{ match.ipv6.address.prefix_lists|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "ipv6": {
+ "address": {
+ "prefix_lists": "{{ prefix_lists.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.ipv6.multicast",
+ "getval": re.compile(
+ r"""
+ \s+match\sipv6\smulticast
+ (\ssource\s(?P<source>\S+))?
+ (\sgroup\s(?P<prefix>\S+))?
+ (\sgroup-range
+ (\s(?P<first>\S+))?
+ (\sto)?
+ (\s(?P<last>\S+)))?
+ (\srp\s(?P<rp>\S+))?
+ (\srp-type\s(?P<rp_type>\S+))?
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": _tmplt_match_ipv6_multicast,
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "ipv6": {
+ "multicast": {
+ "group": {
+ "prefix": "{{ prefix }}",
+ },
+ "group_range": {
+ "first": "{{ first }}",
+ "last": "{{ last }}",
+ },
+ "rp": {
+ "prefix": "{{ rp }}",
+ "rp_type": "{{ rp_type }}",
+ },
+ "source": "{{ source }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.ipv6.next_hop.prefix_lists",
+ "getval": re.compile(
+ r"""
+ \s+match\sipv6\snext-hop
+ \sprefix-list\s(?P<prefix_lists>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match ipv6 next-hop prefix-list {{ match.ipv6.next_hop.prefix_lists|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "ipv6": {
+ "next_hop": {
+ "prefix_lists": "{{ prefix_lists.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.ipv6.route_source.prefix_lists",
+ "getval": re.compile(
+ r"""
+ \s+match\sipv6\sroute-source
+ \sprefix-list\s(?P<prefix_lists>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match ipv6 route-source prefix-list {{ match.ipv6.route_source.prefix_lists|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "ipv6": {
+ "route_source": {
+ "prefix_lists": "{{ prefix_lists.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.mac_list",
+ "getval": re.compile(
+ r"""
+ \s+match\smac-list
+ \s(?P<mac_list>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match mac-list {{ match.mac_list|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "mac_list": "{{ mac_list.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.metric",
+ "getval": re.compile(
+ r"""
+ \s+match\smetric
+ \s(?P<metric>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match metric {{ match.metric|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "metric": "{{ metric.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.ospf_area",
+ "getval": re.compile(
+ r"""
+ \s+match\sospf-area
+ \s(?P<ospf_area>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match ospf-area {{ match.ospf_area|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "ospf_area": "{{ ospf_area.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.route_types",
+ "getval": re.compile(
+ r"""
+ \s+match\sroute-type
+ \s(?P<route_types>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match route-type {{ match.route_types|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "route_types": "{{ route_types.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.source_protocol",
+ "getval": re.compile(
+ r"""
+ \s+match\ssource-protocol
+ \s(?P<route_type>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match source-protocol {{ match.source_protocol|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "source_protocol": "{{ source_protocol.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "match.tags",
+ "getval": re.compile(
+ r"""
+ \s+match\stag
+ \s(?P<tags>.+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "match tag {{ match.tags|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "match": {
+ "tags": "{{ tags.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.as_path.prepend.as_number",
+ "getval": re.compile(
+ r"""
+ \s+set\sas-path\sprepend
+ \s(?P<as_number>(?!last-as).+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "set as-path prepend {{ set.as_path.prepend.as_number|join(' ') }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "as_path": {
+ "prepend": {
+ "as_number": "{{ as_number.split() }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.as_path.prepend.last_as",
+ "getval": re.compile(
+ r"""
+ \s+set\sas-path\sprepend
+ \slast-as\s(?P<last_as>\d+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "set as-path prepend last-as {{ set.as_path.prepend.last_as|string }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "as_path": {
+ "prepend": {
+ "last_as": "{{ last_as }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.as_path.tag",
+ "getval": re.compile(
+ r"""
+ \s+set\sas-path
+ \s(?P<tag>tag)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "set as-path tag",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "as_path": {
+ "tag": "{{ not not tag }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.comm_list",
+ "getval": re.compile(
+ r"""
+ \s+set\scomm-list
+ \s(?P<comm_list>\S+)
+ \s*delete
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set comm-list {{ set.comm_list }} delete",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "comm_list": "{{ comm_list }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.community",
+ "getval": re.compile(
+ r"""
+ \s+set\scommunity
+ (\s(?P<internet>internet))?
+ (?P<number>(\s\d+:\d+)*)
+ (\s(?P<no_export>no-export))?
+ (\s(?P<no_advertise>no-advertise))?
+ (\s(?P<local_as>local-AS))?
+ (\s(?P<graceful_shutdown>graceful-shutdown))?
+ (\s(?P<additive>additive))?\s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "set community"
+ "{{ ' internet' if set.community.internet|d(False) else '' }}"
+ "{{ ' ' + set.community.number|join(' ') if set.community.number|d(False) else '' }}"
+ "{{ ' no-export' if set.community.no_export|d(False) else '' }}"
+ "{{ ' no-advertise' if set.community.no_advertise|d(False) else '' }}"
+ "{{ ' local-AS' if set.community.local_as|d(False) else '' }}"
+ "{{ ' graceful-shutdown' if set.community.graceful_shutdown|d(False) else '' }}"
+ "{{ ' additive' if set.community.additive|d(False) else '' }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "community": {
+ "internet": "{{ not not internet }}",
+ "number": "{{ number.split() }}",
+ "no_export": "{{ not not no_export }}",
+ "no_advertise": "{{ not not no_advertise }}",
+ "local_as": "{{ not not local_as }}",
+ "graceful_shutdown": "{{ not not graceful_shutdown }}",
+ "additive": "{{ not not additive }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.dampening",
+ "getval": re.compile(
+ r"""
+ \s+set\sdampening
+ \s(?P<half_life>\d+)
+ \s(?P<start_reuse_route>\d+)
+ \s(?P<start_suppress_route>\d+)
+ \s(?P<max_suppress_time>\d+)
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "set dampening {{ set.dampening.half_life }}"
+ " {{ set.dampening.start_reuse_route }}"
+ " {{ set.dampening.start_suppress_route }}"
+ " {{ set.dampening.max_suppress_time }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "dampening": {
+ "half_life": "{{ half_life }}",
+ "start_reuse_route": "{{ start_reuse_route }}",
+ "start_suppress_route": "{{ start_suppress_route }}",
+ "max_suppress_time": "{{ max_suppress_time }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.distance",
+ "getval": re.compile(
+ r"""
+ \s+set\sdistance
+ \s(?P<igp_ebgp_routes>\d+)
+ (\s(?P<internal_routes>\d+))?
+ (\s(?P<local_routes>\d+))?
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "set distance {{ set.distance.igp_ebgp_routes }}"
+ "{{ ' ' + set.distance.internal_routes|string if set.distance.internal_routes|d(False) else '' }}"
+ "{{ ' ' + set.distance.local_routes|string if set.distance.internal_routes|d(False) else '' }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "distance": {
+ "igp_ebgp_routes": "{{ igp_ebgp_routes }}",
+ "internal_routes": "{{ internal_routes }}",
+ "local_routes": "{{ local_routes }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.evpn.gateway_ip",
+ "getval": re.compile(
+ r"""
+ \s+set\sevpn
+ \sgateway-ip
+ (\s(?P<ip>(?!use-nexthop)\S+))?
+ (\s(?P<use_nexthop>use-nexthop))?
+ \s*
+ $""", re.VERBOSE,
+ ),
+ "setval": "set evpn gateway-ip"
+ "{{ ' ' + set.evpn.gateway_ip.ip if set.evpn.gateway_ip.ip|d(False) else ''}}"
+ "{{ ' use-nexthop' if set.evpn.gateway_ip.use_nexthop|d(False) else '' }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "evpn": {
+ "gateway_ip": {
+ "ip": "{{ ip }}",
+ "use_nexthop": "{{ not not use_nexthop }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.extcomm_list",
+ "getval": re.compile(
+ r"""
+ \s+set\sextcomm-list
+ \s(?P<extcomm_list>\S+)
+ \s*delete
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set extcomm-list {{ set.extcomm_list }} delete",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "extcomm_list": "{{ extcomm_list }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.forwarding_address",
+ "getval": re.compile(
+ r"""
+ \s+set
+ \s(?P<forwarding_address>forwarding-address)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set forwarding-address",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "forwarding_address": "{{ not not forwarding_address }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.null_interface",
+ "getval": re.compile(
+ r"""
+ \s+set\sinterface
+ \s(?P<interface>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set interface {{ set.null_interface }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "null_interface": "{{ interface }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.ip.address.prefix_list",
+ "getval": re.compile(
+ r"""
+ \s+set\sip\saddress
+ \sprefix-list\s(?P<prefix_list>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set ip address prefix-list {{ set.ip.address.prefix_list }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "ip": {
+ "address": {
+ "prefix_list": "{{ prefix_list }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.ip.precedence",
+ "getval": re.compile(
+ r"""
+ \s+set\sip
+ \sprecedence\s(?P<precedence>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set ip precedence {{ set.ip.precedence }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "ip": {
+ "precedence": "{{ precedence }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.ipv6.address.prefix_list",
+ "getval": re.compile(
+ r"""
+ \s+set\sipv6\saddress
+ \sprefix-list\s(?P<prefix_list>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set ipv6 address prefix-list {{ set.ipv6.address.prefix_list }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "ipv6": {
+ "address": {
+ "prefix_list": "{{ prefix_list }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.ipv6.precedence",
+ "getval": re.compile(
+ r"""
+ \s+set\sipv6
+ \sprecedence\s(?P<precedence>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set ipv6 precedence {{ set.ipv6.precedence }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "ipv6": {
+ "precedence": "{{ precedence }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.label_index",
+ "getval": re.compile(
+ r"""
+ \s+set\slabel-index
+ \s(?P<label_index>\d+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set label-index {{ set.label_index }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "label_index": "{{ label_index }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.level",
+ "getval": re.compile(
+ r"""
+ \s+set\slevel
+ \s(?P<level>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set level {{ set.level }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "level": "{{ level }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.local_preference",
+ "getval": re.compile(
+ r"""
+ \s+set\slocal-preference
+ \s(?P<local_preference>\d+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set local-preference {{ set.local_preference }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "local_preference": "{{ local_preference }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.metric",
+ "getval": re.compile(
+ r"""
+ \s+set\smetric
+ \s(?P<bandwidth>\d+)
+ (\s(?P<igrp_delay_metric>\d+))?
+ (\s(?P<igrp_reliability_metric>\d+))?
+ (\s(?P<igrp_effective_bandwidth_metric>\d+))?
+ (\s(?P<igrp_mtu>\d+))?
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": _tmplt_set_metric,
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "metric": {
+ "bandwidth": "{{ bandwidth }}",
+ "igrp_delay_metric": "{{ igrp_delay_metric }}",
+ "igrp_reliability_metric": "{{ igrp_reliability_metric }}",
+ "igrp_effective_bandwidth_metric": "{{ igrp_effective_bandwidth_metric }}",
+ "igrp_mtu": "{{ igrp_mtu }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.metric_type",
+ "getval": re.compile(
+ r"""
+ \s+set\smetric-type
+ \s(?P<metric_type>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set metric-type {{ set.metric_type }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "metric_type": "{{ metric_type }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.nssa_only",
+ "getval": re.compile(
+ r"""
+ \s+set
+ \s(?P<nssa_only>nssa-only)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set nssa-only",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "nssa_only": "{{ not not nssa_only }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.origin",
+ "getval": re.compile(
+ r"""
+ \s+set\sorigin
+ \s(?P<origin>\S+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set origin {{ set.origin }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "origin": "{{ origin }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.path_selection",
+ "getval": re.compile(
+ r"""
+ \s+set\spath-selection
+ \s(?P<path_selection>\S+)
+ \sadvertise
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set path-selection {{ set.path_selection }} advertise",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "path_selection": "{{ path_selection }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.tag",
+ "getval": re.compile(
+ r"""
+ \s+set\stag
+ \s(?P<tag>\d+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set tag {{ set.tag }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "tag": "{{ tag }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ "name": "set.weight",
+ "getval": re.compile(
+ r"""
+ \s+set\sweight
+ \s(?P<weight>\d+)
+ \s*$""", re.VERBOSE,
+ ),
+ "setval": "set weight {{ set.weight }}",
+ "result": {
+ "{{ route_map }}": {
+ "entries": {
+ "{{ sequence }}": {
+ "set": {
+ "weight": "{{ weight }}",
+ },
+ },
+ },
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/snmp_server.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/snmp_server.py
new file mode 100644
index 00000000..8615bb2c
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/rm_templates/snmp_server.py
@@ -0,0 +1,1550 @@
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+"""
+The Snmp_server parser templates file. This contains
+a list of parser definitions and associated functions that
+facilitates both facts gathering and native command generation for
+the given network resource.
+"""
+
+import re
+
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.network_template import (
+ NetworkTemplate,
+)
+
+
+def _template_hosts(data):
+ cmd = "snmp-server host {0}".format(data["host"])
+ if data.get("traps"):
+ cmd += " traps"
+ if data.get("informs"):
+ cmd += " informs"
+ if data.get("use_vrf"):
+ cmd += " use-vrf {0}".format(data["use_vrf"])
+ if data.get("filter_vrf"):
+ cmd += " filter-vrf {0}".format(data["filter_vrf"])
+ if data.get("source_interface"):
+ cmd += " source-interface {0}".format(data["source_interface"])
+ if data.get("version"):
+ cmd += " version {0}".format(data["version"])
+ if data.get("community"):
+ cmd += " " + data["community"]
+ elif data.get("auth"):
+ cmd += " auth {0}".format(data["auth"])
+ elif data.get("priv"):
+ cmd += " priv {0}".format(data["priv"])
+ if data.get("udp_port"):
+ cmd += " udp-port {0}".format(data["udp_port"])
+
+ return cmd
+
+
+def _tmplt_users_auth(data):
+ cmd = "snmp-server user {0}".format(data["user"])
+
+ if "group" in data:
+ cmd += " {0}".format(data["group"])
+ if "authentication" in data:
+ auth = data["authentication"]
+ if "algorithm" in auth:
+ cmd += " auth {0}".format(auth["algorithm"])
+ if "password" in auth:
+ cmd += " {0}".format(auth["password"])
+ priv = auth.get("priv", {})
+ if priv:
+ cmd += " priv"
+ if priv.get("aes_128", False):
+ cmd += " aes-128"
+ if "privacy_password" in priv:
+ cmd += " {0}".format(priv["privacy_password"])
+ if auth.get("localized_key", False):
+ cmd += " localizedkey"
+ elif auth.get("localizedv2_key", False):
+ cmd += " localizedV2key"
+ if "engine_id" in auth:
+ cmd += " engineID {0}".format(auth["engine_id"])
+
+ return cmd
+
+
+def _template_communities(data):
+ cmd = "snmp-server community {0}".format(data["name"])
+
+ if "group" in data:
+ cmd += " group {0}".format(data["group"])
+ elif "use_ipv4acl" in data:
+ cmd += " use-ipv4acl {0}".format(data["use_ipv4acl"])
+ elif "use_ipv6acl" in data:
+ cmd += " use-ipv6acl {0}".format(data["use_ipv6acl"])
+ elif data.get("ro", False):
+ cmd += " ro"
+ elif data.get("rw", False):
+ cmd += " rw"
+
+ return cmd
+
+
+class Snmp_serverTemplate(NetworkTemplate):
+ def __init__(self, lines=None, module=None):
+ super(Snmp_serverTemplate, self).__init__(lines=lines, tmplt=self, module=module)
+
+ # fmt: off
+ PARSERS = [
+ {
+ "name": "aaa_user.cache_timeout",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\saaa-user
+ \scache-timeout\s(?P<cache_timeout>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server aaa-user cache-timeout {{ aaa_user.cache_timeout }}",
+ "result": {
+ "aaa_user": {
+ "cache_timeout": "{{ cache_timeout }}",
+ },
+ },
+ },
+ {
+ "name": "communities",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \scommunity\s(?P<community>\S+)
+ (\sgroup\s(?P<group>\S+))?
+ (\suse-ipv4acl\s(?P<use_ipv4acl>\S+))?
+ (\suse-ipv6acl\s(?P<use_ipv6acl>\S+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": _template_communities,
+ "result": {
+ "communities": [
+ {
+ "name": "{{ community }}",
+ "group": "{{ group }}",
+ "use_ipv4acl": "{{ use_ipv4acl }}",
+ "use_ipv6acl": "{{ use_ipv6acl }}",
+ },
+ ],
+ },
+ },
+ {
+ "name": "contact",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \scontact\s(?P<contact>.+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server contact {{ contact }}",
+ "result": {
+ "contact": "{{ contact }}",
+ },
+ },
+ {
+ "name": "context",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \scontext\s(?P<name>\S+)
+ (\sinstance\s(?P<instance>\S+))?
+ (\svrf\s(?P<vrf>\S+))?
+ (\stopology\s(?P<topology>\S+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server context {{ context.name }}"
+ "{{ ' instance ' + context.instance if context.instance is defined else '' }}"
+ "{{ ' topology ' + context.topology if context.topology is defined else '' }}"
+ "{{ ' vrf ' + context.vrf if context.vrf is defined else '' }}",
+ "result": {
+ "context": {
+ "name": "{{ name }}",
+ "instance": "{{ instance }}",
+ "vrf": "{{ vrf }}",
+ "topology": "{{ topology }}",
+ },
+
+ },
+ },
+ {
+ "name": "counter.enable",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \scounter
+ \scache\s(?P<enable>enable)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server counter cache enable",
+ "result": {
+ "counter": {
+ "cache": {
+ "enable": "{{ True if enable is defined else None }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "counter.cache.timeout",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \scounter
+ \scache\stimeout\s(?P<timeout>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server counter cache timeout {{ counter.cache.timeout }}",
+ "result": {
+ "counter": {
+ "cache": {
+ "timeout": "{{ timeout }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "drop.unknown_engine_id",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\sdrop
+ \s(?P<unknown_engine_id>unknown-engine-id)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server drop unknown-engine-id",
+ "result": {
+ "drop": {
+ "unknown_engine_id": "{{ not not unknown_engine_id }}",
+ },
+ },
+ },
+ {
+ "name": "drop.unknown_user",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\sdrop
+ \s(?P<unknown_user>unknown-user)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server drop unknown-user",
+ "result": {
+ "drop": {
+ "unknown_user": "{{ not not unknown_user }}",
+ },
+ },
+ },
+ {
+ "name": "traps.aaa",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps\saaa\s(?P<server_state_change>server-state-change)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps aaa"
+ "{{ ' server-state-change' if traps.aaa.server_state_change|d(False) else ''}}",
+ "result": {
+ "traps": {
+ "aaa": {
+ "server_state_change": "{{ not not server_state_change }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.bgp",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps\s(?P<enable>bgp)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps bgp",
+ "result": {
+ "traps": {
+ "bgp": {
+ "enable": "{{ not not enable }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.bridge.newroot",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sbridge\s(?P<newroot>newroot)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps bridge newroot",
+ "result": {
+ "traps": {
+ "bridge": {
+ "newroot": "{{ not not newroot }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.bridge.topologychange",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sbridge\s(?P<topologychange>topologychange)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps bridge topologychange",
+ "result": {
+ "traps": {
+ "bridge": {
+ "topologychange": "{{ not not topologychange }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.callhome.event_notify",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \scallhome\s(?P<event_notify>event-notify)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps callhome event-notify",
+ "result": {
+ "traps": {
+ "callhome": {
+ "event_notify": "{{ not not event_notify }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.callhome.smtp_send_fail",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \scallhome\s(?P<smtp_send_fail>smtp-send-fail)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps callhome smtp-send-fail",
+ "result": {
+ "traps": {
+ "callhome": {
+ "smtp_send_fail": "{{ not not smtp_send_fail }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.cfs.merge_failure",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \scfs\s(?P<merge_failure>merge-failure)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps cfs merge-failure",
+ "result": {
+ "traps": {
+ "cfs": {
+ "merge_failure": "{{ not not merge_failure }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.cfs.state_change_notif",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \scfs\s(?P<state_change_notif>state-change-notif)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps cfs state-change-notif",
+ "result": {
+ "traps": {
+ "cfs": {
+ "state_change_notif": "{{ not not state_change_notif }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.config.ccmCLIRunningConfigChanged",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sconfig\s(?P<ccmCLIRunningConfigChanged>ccmCLIRunningConfigChanged)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps config ccmCLIRunningConfigChanged",
+ "result": {
+ "traps": {
+ "config": {
+ "ccmCLIRunningConfigChanged": "{{ not not ccmCLIRunningConfigChanged }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.entity.cefcMIBEnableStatusNotification",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sentity\s(?P<cefcMIBEnableStatusNotification>cefcMIBEnableStatusNotification)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps entity cefcMIBEnableStatusNotification",
+ "result": {
+ "traps": {
+ "entity": {
+ "cefcMIBEnableStatusNotification": "{{ not not cefcMIBEnableStatusNotification }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.entity.entity_fan_status_change",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sentity\s(?P<entity_fan_status_change>entity-fan-status-change)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps entity entity-fan-status-change",
+ "result": {
+ "traps": {
+ "entity": {
+ "entity_fan_status_change": "{{ not not entity_fan_status_change }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.entity.entity_mib_change",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sentity\s(?P<entity_mib_change>entity-mib-change)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps entity entity-mib-change",
+ "result": {
+ "traps": {
+ "entity": {
+ "entity_mib_change": "{{ not not entity_mib_change }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.entity.entity_module_inserted",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sentity\s(?P<entity_module_inserted>entity-module-inserted)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps entity entity-module-inserted",
+ "result": {
+ "traps": {
+ "entity": {
+ "entity_module_inserted": "{{ not not entity_module_inserted }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.entity.entity_module_status_change",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sentity\s(?P<entity_module_status_change>entity-module-status-change)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps entity entity-module-status-change",
+ "result": {
+ "traps": {
+ "entity": {
+ "entity_module_status_change": "{{ not not entity_module_status_change }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.entity.entity_power_out_change",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sentity\s(?P<entity_power_out_change>entity-power-out-change)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps entity entity-power-out-change",
+ "result": {
+ "traps": {
+ "entity": {
+ "entity_power_out_change": "{{ not not entity_power_out_change }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.entity.entity_power_status_change",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sentity\s(?P<entity_power_status_change>entity_power_status_change)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps entity entity-power-status-change",
+ "result": {
+ "traps": {
+ "entity": {
+ "entity_power_status_change": "{{ not not entity_power_status_change }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.entity.entity_sensor",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sentity\s(?P<entity_sensor>entity-sensor)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps entity entity-sensor",
+ "result": {
+ "traps": {
+ "entity": {
+ "entity_sensor": "{{ not not entity_sensor }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.entity.entity_unrecognised_module",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sentity\s(?P<entity_unrecognised_module>entity-unrecognised-module)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps entity entity-unrecognised-module",
+ "result": {
+ "traps": {
+ "entity": {
+ "entity_unrecognised_module": "{{ not not entity_unrecognised_module }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.feature_control.featureOpStatusChange",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sfeature-control\s(?P<featureOpStatusChange>featureOpStatusChange)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps feature-control featureOpStatusChange",
+ "result": {
+ "traps": {
+ "feature_control": {
+ "featureOpStatusChange": "{{ not not featureOpStatusChange }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.feature_control.ciscoFeatOpStatusChange",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sfeature-control\s(?P<ciscoFeatOpStatusChange>ciscoFeatOpStatusChange)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps feature-control ciscoFeatOpStatusChange",
+ "result": {
+ "traps": {
+ "feature_control": {
+ "ciscoFeatOpStatusChange": "{{ not not ciscoFeatOpStatusChange }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.generic.coldStart",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sgeneric\s(?P<coldStart>coldStart)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps generic coldStart",
+ "result": {
+ "traps": {
+ "generic": {
+ "coldStart": "{{ not not coldStart }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.generic.warmStart",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sgeneric\s(?P<warmStart>warmStart)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps generic warmStart",
+ "result": {
+ "traps": {
+ "generic": {
+ "warmStart": "{{ not not warmStart }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.license.notify_license_expiry",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slicense\s(?P<notify_license_expiry>notify_license_expiry)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps license notify-license-expiry",
+ "result": {
+ "traps": {
+ "license": {
+ "notify_license_expiry": "{{ not not notify_license_expiry }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.license.notify_license_expiry_warning",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slicense\s(?P<notify_license_expiry_warning>notify-license-expiry-warning)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps license notify-license-expiry-warning",
+ "result": {
+ "traps": {
+ "license": {
+ "notify_license_expiry_warning": "{{ not not notify_license_expiry_warning }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.license.notify_licensefile_missing",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slicense\s(?P<notify_licensefile_missing>notify-licensefile-missing)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps license notify-licensefile-missing",
+ "result": {
+ "traps": {
+ "license": {
+ "notify_licensefile_missing": "{{ not not notify_licensefile_missing }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.license.notify_no_license_for_feature",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slicense\s(?P<notify_no_license_for_feature>notify-no-license-for-feature)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps license notify-no-license-for-feature",
+ "result": {
+ "traps": {
+ "license": {
+ "notify_no_license_for_feature": "{{ not not notify_no_license_for_feature }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.link.cErrDisableInterfaceEventRev1",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slink\s(?P<cErrDisableInterfaceEventRev1>cErrDisableInterfaceEventRev1)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps link cErrDisableInterfaceEventRev1",
+ "result": {
+ "traps": {
+ "link": {
+ "cErrDisableInterfaceEventRev1": "{{ not not cErrDisableInterfaceEventRev1 }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.link.cieLinkDown",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slink\s(?P<cieLinkDown>cieLinkDown)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps link cieLinkDown",
+ "result": {
+ "traps": {
+ "link": {
+ "cieLinkDown": "{{ not not cieLinkDown }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.link.cieLinkUp",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slink\s(?P<cieLinkUp>cieLinkUp)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps link cieLinkUp",
+ "result": {
+ "traps": {
+ "link": {
+ "cieLinkUp": "{{ not not cieLinkUp }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.link.cisco_xcvr_mon_status_chg",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slink\s(?P<cisco_xcvr_mon_status_chg>cisco-xcvr-mon-status-chg)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps link cisco-xcvr-mon-status-chg",
+ "result": {
+ "traps": {
+ "link": {
+ "cisco_xcvr_mon_status_chg": "{{ not not cisco_xcvr_mon_status_chg }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.link.cmn_mac_move_notification",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slink\s(?P<cmn_mac_move_notification>cmn-mac-move-notification)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps link cmn-mac-move-notification",
+ "result": {
+ "traps": {
+ "link": {
+ "cmn_mac_move_notification": "{{ not not cmn_mac_move_notification }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.link.delayed_link_state_change",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slink\s(?P<delayed_link_state_change>delayed-link-state-change)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps link delayed-link-state-change",
+ "result": {
+ "traps": {
+ "link": {
+ "delayed_link_state_change": "{{ not not delayed_link_state_change }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.link.extended_linkDown",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slink\s(?P<extended_linkDown>extended-linkDown)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps link extended-linkDown",
+ "result": {
+ "traps": {
+ "link": {
+ "extended_linkDown": "{{ not not extended_linkDown }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.link.extended_linkUp",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slink\s(?P<extended_linkUp>extended-linkUp)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps link extended-linkUp",
+ "result": {
+ "traps": {
+ "link": {
+ "extended_linkUp": "{{ not not extended_linkUp }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.link.linkDown",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slink\s(?P<linkDown>linkDown)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps link linkDown",
+ "result": {
+ "traps": {
+ "link": {
+ "linkDown": "{{ not not linkDown }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.link.linkUp",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \slink\s(?P<linkUp>linkUp)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps link linkUp",
+ "result": {
+ "traps": {
+ "link": {
+ "linkUp": "{{ not not linkUp }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.mmode.cseMaintModeChangeNotify",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \smmode\s(?P<cseMaintModeChangeNotify>cseMaintModeChangeNotify)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps mmode cseMaintModeChangeNotify",
+ "result": {
+ "traps": {
+ "mmode": {
+ "cseMaintModeChangeNotify": "{{ not not cseMaintModeChangeNotify }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.mmode.cseNormalModeChangeNotify",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \smmode\s(?P<cseNormalModeChangeNotify>cseNormalModeChangeNotify)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps mmode cseNormalModeChangeNotify",
+ "result": {
+ "traps": {
+ "mmode": {
+ "cseNormalModeChangeNotify": "{{ not not cseNormalModeChangeNotify }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.ospf",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps\s(?P<enable>ospf)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps ospf",
+ "result": {
+ "traps": {
+ "ospf": {
+ "enable": "{{ not not enable }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.ospfv3",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps\s(?P<enable>ospfv3)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps ospfv3",
+ "result": {
+ "traps": {
+ "ospfv3": {
+ "enable": "{{ not not enable }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.rf.redundancy_framework",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \srf\s(?P<redundancy_framework>redundancy-framework)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps rf redundancy-framework",
+ "result": {
+ "traps": {
+ "rf": {
+ "redundancy_framework": "{{ not not redundancy_framework }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.rmon.fallingAlarm",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \srmon\s(?P<fallingAlarm>fallingAlarm)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps rmon fallingAlarm",
+ "result": {
+ "traps": {
+ "rmon": {
+ "fallingAlarm": "{{ not not fallingAlarm }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.rmon.hcFallingAlarm",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \srmon\s(?P<hcFallingAlarm>hcFallingAlarm)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps rmon hcFallingAlarm",
+ "result": {
+ "traps": {
+ "rmon": {
+ "hcFallingAlarm": "{{ not not hcFallingAlarm }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.rmon.hcRisingAlarm",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \srmon\s(?P<hcRisingAlarm>hcRisingAlarm)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps rmon hcRisingAlarm",
+ "result": {
+ "traps": {
+ "rmon": {
+ "hcRisingAlarm": "{{ not not hcRisingAlarm }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.rmon.risingAlarm",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \srmon\s(?P<risingAlarm>risingAlarm)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps rmon risingAlarm",
+ "result": {
+ "traps": {
+ "rmon": {
+ "risingAlarm": "{{ not not risingAlarm }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.snmp.authentication",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \ssnmp\s(?P<authentication>authentication)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps snmp authentication",
+ "result": {
+ "traps": {
+ "snmp": {
+ "authentication": "{{ not not authentication }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.storm_control.cpscEventRev1",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sstorm-control\s(?P<cpscEventRev1>cpscEventRev1)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps storm-control cpscEventRev1",
+ "result": {
+ "traps": {
+ "storm_control": {
+ "cpscEventRev1n": "{{ not not cpscEventRev1 }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.storm_control.trap_rate",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sstorm-control\s(?P<trap_rate>trap-rate)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps storm-control trap-rate",
+ "result": {
+ "traps": {
+ "storm_control": {
+ "trap_rate": "{{ not not trap_rate }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.stpx.inconsistency",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sstpx\s(?P<inconsistency>inconsistency)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps stpx inconsistency",
+ "result": {
+ "traps": {
+ "stpx": {
+ "inconsistency": "{{ not not inconsistency }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.stpx.root_inconsistency",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sstpx\s(?P<root_inconsistency>root-inconsistency)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps stpx root-inconsistency",
+ "result": {
+ "traps": {
+ "stpx": {
+ "root_inconsistency": "{{ not not root_inconsistency }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.stpx.loop_inconsistency",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \sstpx\s(?P<loop_inconsistency>loop-inconsistency)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps stpx loop-inconsistency",
+ "result": {
+ "traps": {
+ "stpx": {
+ "loop_inconsistency": "{{ not not loop_inconsistency }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.syslog.message_generated",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \ssyslog\s(?P<message_generated>message-generated)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps syslog message-generated",
+ "result": {
+ "traps": {
+ "syslog": {
+ "message_generated": "{{ not not message_generated }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.sysmgr.cseFailSwCoreNotifyExtended",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \ssysmgr\s(?P<cseFailSwCoreNotifyExtended>cseFailSwCoreNotifyExtended)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps sysmgr cseFailSwCoreNotifyExtended",
+ "result": {
+ "traps": {
+ "sysmgr": {
+ "cseFailSwCoreNotifyExtended": "{{ not not cseFailSwCoreNotifyExtended }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.system.clock_change_notification",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \ssystem\s(?P<clock_change_notification>Clock-change-notification)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps system Clock-change-notification",
+ "result": {
+ "traps": {
+ "system": {
+ "clock_change_notification": "{{ not not clock_change_notification }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.upgrade.upgradeJobStatusNotify",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \supgrade\s(?P<upgradeJobStatusNotify>upgradeJobStatusNotify)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps upgrade upgradeJobStatusNotify",
+ "result": {
+ "traps": {
+ "upgrade": {
+ "upgradeJobStatusNotify": "{{ not not upgradeJobStatusNotify }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.upgrade.upgradeOpNotifyOnCompletion",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \supgrade\s(?P<upgradeOpNotifyOnCompletion>upgradeOpNotifyOnCompletion)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps upgrade upgradeOpNotifyOnCompletion",
+ "result": {
+ "traps": {
+ "upgrade": {
+ "upgradeOpNotifyOnCompletion": "{{ not not upgradeOpNotifyOnCompletion }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.vtp.notifs",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \svtp\s(?P<notifs>notifs)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps vtp notifs",
+ "result": {
+ "traps": {
+ "vtp": {
+ "notifs": "{{ not not notifs }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.vtp.vlancreate",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \svtp\s(?P<vlancreate>vlancreate)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps vtp vlancreate",
+ "result": {
+ "traps": {
+ "vtp": {
+ "vlancreate": "{{ not not vlancreate }}",
+ },
+ },
+ },
+ },
+ {
+ "name": "traps.vtp.vlandelete",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\senable
+ \straps
+ \svtp\s(?P<vlandelete>vlandelete)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server enable traps vtp vlandelete",
+ "result": {
+ "traps": {
+ "vtp": {
+ "vlandelete": "{{ not not vlandelete }}",
+ },
+ },
+ },
+ },
+
+ {
+ "name": "engine_id.local",
+ "getval": re.compile(
+ r"""
+ ^snmp-server\sengineID
+ \slocal\s(?P<local>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server engineID local {{ engine_id.local }}",
+ "result": {
+ "engine_id": {
+ "local": "{{ local }}",
+ },
+ },
+ },
+ {
+ "name": "global_enforce_priv",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \s(?P<global_enforce_priv>globalEnforcePriv)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server globalEnforcePriv",
+ "result": {
+ "global_enforce_priv": "{{ not not global_enforce_priv }}",
+ },
+ },
+ {
+ "name": "hosts",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \shost\s(?P<host>\S+)
+ (\s((?P<traps>traps)|(?P<informs>informs)|(use-vrf\s(?P<use_vrf>\S+)|(filter-vrf\s(?P<filter_vrf>\S+))|(source-interface\s(?P<source_interface>\S+)))))
+ (\sversion\s(?P<version>\S+))?
+ (\s((auth\s(?P<auth>\S+))|(priv\s(?P<priv>\S+))|((?P<community>\S+))))?
+ (\sudp-port\s(?P<udp_port>\S+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": _template_hosts,
+ "result": {
+ "hosts": [
+ {
+ "host": "{{ host }}",
+ "community": "{{ community }}",
+ "filter_vrf": "{{ filter_vrf }}",
+ "informs": "{{ not not informs }}",
+ "source_interface": "{{ source_interface }}",
+ "traps": "{{ not not traps }}",
+ "use_vrf": "{{ use_vrf }}",
+ "version": "{{ version }}",
+ "udp_port": "{{ udp_port }}",
+ "auth": "{{ auth }}",
+ "priv": "{{ priv }}",
+ },
+ ],
+ },
+ },
+ {
+ "name": "location",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \slocation\s(?P<location>.+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server location {{ location }}",
+ "result": {
+ "location": "{{ location }}",
+ },
+ },
+ {
+ "name": "mib.community_map",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \smib
+ \scommunity-map\s(?P<community>\S+)
+ \scontext\s(?P<context>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server mib community-map {{ mib.community_map.community }} context {{ mib.community_map.context }}",
+ "result": {
+ "mib": {
+ "community_map": {
+ "community": "{{ community }}",
+ "context": "{{ context }}",
+
+ },
+ },
+ },
+ },
+ {
+ "name": "packetsize",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \spacketsize\s(?P<packetsize>\d+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server packetsize {{ packetsize }}",
+ "result": {
+ "packetsize": "{{ packetsize }}",
+ },
+ },
+ {
+ "name": "protocol.enable",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \sprotocol\s(?P<enable>enable)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server protocol enable",
+ "result": {
+ "protocol": {
+ "enable": "{{ not not enable }}",
+ },
+ },
+ },
+ {
+ "name": "source_interface.informs",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \ssource-interface\sinforms\s(?P<informs>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server source-interface informs {{ source_interface.informs }}",
+ "result": {
+ "source_interface": {
+ "informs": "{{ informs }}",
+ },
+ },
+ },
+ {
+ "name": "source_interface.traps",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \ssource-interface\straps\s(?P<traps>\S+)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server source-interface traps {{ source_interface.traps }}",
+ "result": {
+ "source_interface": {
+ "traps": "{{ traps }}",
+ },
+ },
+ },
+ {
+ "name": "system_shutdown",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \s(?P<system_shutdown>system-shutdown)
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server system-shutdown",
+ "result": {
+ "system_shutdown": "{{ not not system_shutdown }}",
+ },
+ },
+ {
+ "name": "tcp_session",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \s(?P<tcp_session>tcp-session)
+ (\s(?P<auth>auth))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server tcp-session"
+ "{{ ' auth' if tcp_session.auth|d(False) else '' }}",
+ "result": {
+ "tcp_session": {
+ "enable": "{{ True if tcp_session is defined and auth is not defined else None }}",
+ "auth": "{{ not not auth }}",
+ },
+ },
+ },
+ {
+ "name": "users.auth",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \suser\s(?P<user>\S+)
+ (\s(?P<group>[^auth]\S+))?
+ (\sauth\s(?P<algorithm>md5|sha|sha-256)\s(?P<password>\S+))?
+ (\spriv(\s(?P<aes_128>aes-128))?\s(?P<privacy_password>\S+))?
+ (\s(?P<localized_key>localizedkey))?
+ (\s(?P<localizedv2_key>localizedV2key))?
+ (\sengineID\s(?P<engine_id>\S+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": _tmplt_users_auth,
+ "result": {
+ "users": {
+ "auth": [
+ {
+ "user": "{{ user }}",
+ "group": "{{ group }}",
+ "authentication": {
+ "algorithm": "{{ algorithm }}",
+ "password": "'{{ password }}'",
+ "engine_id": "'{{ engine_id }}'",
+ "localized_key": "{{ not not localized_key }}",
+ "localizedv2_key": "{{ not not localizedv2_key }}",
+ "priv": {
+ "privacy_password": "'{{ privacy_password }}'",
+ "aes_128": "{{ not not aes_128 }}",
+ },
+ },
+
+ },
+ ],
+ },
+ },
+ },
+ {
+ "name": "users.use_acls",
+ "getval": re.compile(
+ r"""
+ ^snmp-server
+ \suser\s(?P<user>\S+)
+ (\suse-ipv4acl\s(?P<ipv4>\S+))?
+ (\suse-ipv6acl\s(?P<ipv6>\S+))?
+ $""", re.VERBOSE,
+ ),
+ "setval": "snmp-server user {{ user }}"
+ "{{ (' use-ipv4acl ' + ipv4) if ipv4 is defined else '' }}"
+ "{{ (' use-ipv6acl ' + ipv6) if ipv6 is defined else '' }}",
+ "result": {
+ "users": {
+ "use_acls": [
+ {
+ "user": "{{ user }}",
+ "ipv4": "{{ ipv4 }}",
+ "ipv6": "{{ ipv6 }}",
+ },
+ ],
+ },
+ },
+ },
+ ]
+ # fmt: on
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/__init__.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/telemetry.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/telemetry.py
new file mode 100644
index 00000000..aa540ade
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/telemetry/telemetry.py
@@ -0,0 +1,264 @@
+#
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+"""
+The nxos telemetry utility library
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import re
+
+from copy import deepcopy
+
+
+def get_module_params_subsection(module_params, tms_config, resource_key=None):
+ """
+ Helper method to get a specific module_params subsection
+ """
+ mp = {}
+ if tms_config == "TMS_GLOBAL":
+ relevant_keys = [
+ "certificate",
+ "compression",
+ "source_interface",
+ "vrf",
+ ]
+ for key in relevant_keys:
+ mp[key] = module_params[key]
+
+ if tms_config == "TMS_DESTGROUP":
+ mp["destination_groups"] = []
+ for destgrp in module_params["destination_groups"]:
+ if destgrp["id"] == resource_key:
+ mp["destination_groups"].append(destgrp)
+
+ if tms_config == "TMS_SENSORGROUP":
+ mp["sensor_groups"] = []
+ for sensor in module_params["sensor_groups"]:
+ if sensor["id"] == resource_key:
+ mp["sensor_groups"].append(sensor)
+
+ if tms_config == "TMS_SUBSCRIPTION":
+ mp["subscriptions"] = []
+ for sensor in module_params["subscriptions"]:
+ if sensor["id"] == resource_key:
+ mp["subscriptions"].append(sensor)
+
+ return mp
+
+
+def valiate_input(playvals, type, module):
+ """
+ Helper method to validate playbook values for destination groups
+ """
+ if type == "destination_groups":
+ if not playvals.get("id"):
+ msg = "Invalid playbook value: {0}.".format(playvals)
+ msg += " Parameter <id> under <destination_groups> is required"
+ module.fail_json(msg=msg)
+ if playvals.get("destination") and not isinstance(playvals["destination"], dict):
+ msg = "Invalid playbook value: {0}.".format(playvals)
+ msg += " Parameter <destination> under <destination_groups> must be a dict"
+ module.fail_json(msg=msg)
+ if not playvals.get("destination") and len(playvals) > 1:
+ msg = "Invalid playbook value: {0}.".format(playvals)
+ msg += " Playbook entry contains unrecongnized parameters."
+ msg += (
+ " Make sure <destination> keys under <destination_groups> are specified as follows:"
+ )
+ msg += " destination: {ip: <ip>, port: <port>, protocol: <prot>, encoding: <enc>}}"
+ module.fail_json(msg=msg)
+
+ if type == "sensor_groups":
+ if not playvals.get("id"):
+ msg = "Invalid playbook value: {0}.".format(playvals)
+ msg += " Parameter <id> under <sensor_groups> is required"
+ module.fail_json(msg=msg)
+ if playvals.get("path") and "name" not in playvals["path"].keys():
+ msg = "Invalid playbook value: {0}.".format(playvals)
+ msg += " Parameter <path> under <sensor_groups> requires <name> key"
+ module.fail_json(msg=msg)
+
+
+def get_instance_data(key, cr_key, cr, existing_key):
+ """
+ Helper method to get instance data used to populate list structure in config
+ fact dictionary
+ """
+ data = {}
+ if existing_key is None:
+ instance = None
+ else:
+ instance = cr._ref[cr_key]["existing"][existing_key]
+
+ patterns = {
+ "destination_groups": r"destination-group (\S+)",
+ "sensor_groups": r"sensor-group (\S+)",
+ "subscriptions": r"subscription (\S+)",
+ }
+ if key in patterns.keys():
+ m = re.search(patterns[key], cr._ref["_resource_key"])
+ instance_key = m.group(1)
+ data = {"id": instance_key, cr_key: instance}
+
+ # Remove None values
+ data = dict((k, v) for k, v in data.items() if v is not None)
+ return data
+
+
+def cr_key_lookup(key, mo):
+ """
+ Helper method to get instance key value for Managed Object (mo)
+ """
+ cr_keys = [key]
+ if key == "destination_groups" and mo == "TMS_DESTGROUP":
+ cr_keys = ["destination"]
+ elif key == "sensor_groups" and mo == "TMS_SENSORGROUP":
+ cr_keys = ["data_source", "path"]
+ elif key == "subscriptions" and mo == "TMS_SUBSCRIPTION":
+ cr_keys = ["destination_group", "sensor_group"]
+
+ return cr_keys
+
+
+def normalize_data(cmd_ref):
+ """Normalize playbook values and get_existing data"""
+
+ playval = cmd_ref._ref.get("destination").get("playval")
+ existing = cmd_ref._ref.get("destination").get("existing")
+
+ dest_props = ["protocol", "encoding"]
+ if playval:
+ for prop in dest_props:
+ for key in playval.keys():
+ playval[key][prop] = playval[key][prop].lower()
+ if existing:
+ for key in existing.keys():
+ for prop in dest_props:
+ existing[key][prop] = existing[key][prop].lower()
+
+
+def remove_duplicate_context(cmds):
+ """Helper method to remove duplicate telemetry context commands"""
+ if not cmds:
+ return cmds
+ feature_indices = [i for i, x in enumerate(cmds) if x == "feature telemetry"]
+ telemetry_indices = [i for i, x in enumerate(cmds) if x == "telemetry"]
+ if len(feature_indices) == 1 and len(telemetry_indices) == 1:
+ return cmds
+ if len(feature_indices) == 1 and not telemetry_indices:
+ return cmds
+ if len(telemetry_indices) == 1 and not feature_indices:
+ return cmds
+ if feature_indices and feature_indices[-1] > 1:
+ cmds.pop(feature_indices[-1])
+ return remove_duplicate_context(cmds)
+ if telemetry_indices and telemetry_indices[-1] > 1:
+ cmds.pop(telemetry_indices[-1])
+ return remove_duplicate_context(cmds)
+
+
+def get_setval_path(module_or_path_data):
+ """Build setval for path parameter based on playbook inputs
+ Full Command:
+ - path {name} depth {depth} query-condition {query_condition} filter-condition {filter_condition}
+ Required:
+ - path {name}
+ Optional:
+ - depth {depth}
+ - query-condition {query_condition},
+ - filter-condition {filter_condition}
+ """
+ if isinstance(module_or_path_data, dict):
+ path = module_or_path_data
+ else:
+ path = module_or_path_data.params["config"]["sensor_groups"][0].get("path")
+ if path is None:
+ return path
+
+ setval = "path {name}"
+ if "depth" in path.keys():
+ if path.get("depth") != "None":
+ setval = setval + " depth {depth}"
+ if "query_condition" in path.keys():
+ if path.get("query_condition") != "None":
+ setval = setval + " query-condition {query_condition}"
+ if "filter_condition" in path.keys():
+ if path.get("filter_condition") != "None":
+ setval = setval + " filter-condition {filter_condition}"
+
+ return setval
+
+
+def remove_duplicate_commands(commands_list):
+ # Remove any duplicate commands.
+ # pylint: disable=unnecessary-lambda
+ return sorted(set(commands_list), key=lambda x: commands_list.index(x))
+
+
+def massage_data(have_or_want):
+ # Massage non global into a data structure that is indexed by id and
+ # normalized for destination_groups, sensor_groups and subscriptions.
+ data = deepcopy(have_or_want)
+ massaged = {}
+ massaged["destination_groups"] = {}
+ massaged["sensor_groups"] = {}
+ massaged["subscriptions"] = {}
+ from pprint import pprint
+
+ for subgroup in ["destination_groups", "sensor_groups", "subscriptions"]:
+ for item in data.get(subgroup, []):
+ id = str(item.get("id"))
+ if id not in massaged[subgroup].keys():
+ massaged[subgroup][id] = []
+ item.pop("id")
+ if not item:
+ item = None
+ else:
+ if item.get("destination"):
+ if item.get("destination").get("port"):
+ item["destination"]["port"] = str(item["destination"]["port"])
+ if item.get("destination").get("protocol"):
+ item["destination"]["protocol"] = item["destination"]["protocol"].lower()
+ if item.get("destination").get("encoding"):
+ item["destination"]["encoding"] = item["destination"]["encoding"].lower()
+ if item.get("path"):
+ for key in [
+ "filter_condition",
+ "query_condition",
+ "depth",
+ ]:
+ if item.get("path").get(key) == "None":
+ del item["path"][key]
+ if item.get("path").get("depth") is not None:
+ item["path"]["depth"] = str(item["path"]["depth"])
+ if item.get("destination_group"):
+ item["destination_group"] = str(item["destination_group"])
+ if item.get("sensor_group"):
+ if item.get("sensor_group").get("id"):
+ item["sensor_group"]["id"] = str(item["sensor_group"]["id"])
+ if item.get("sensor_group").get("sample_interval"):
+ item["sensor_group"]["sample_interval"] = str(
+ item["sensor_group"]["sample_interval"],
+ )
+ if item.get("destination_group") and item.get("sensor_group"):
+ item_copy = deepcopy(item)
+ del item_copy["sensor_group"]
+ del item["destination_group"]
+ massaged[subgroup][id].append(item_copy)
+ massaged[subgroup][id].append(item)
+ continue
+ if item.get("path") and item.get("data_source"):
+ item_copy = deepcopy(item)
+ del item_copy["data_source"]
+ del item["path"]
+ massaged[subgroup][id].append(item_copy)
+ massaged[subgroup][id].append(item)
+ continue
+ massaged[subgroup][id].append(item)
+ return massaged
diff --git a/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/utils.py b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/utils.py
new file mode 100644
index 00000000..01468edd
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/module_utils/network/nxos/utils/utils.py
@@ -0,0 +1,214 @@
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+import socket
+
+from functools import total_ordering
+from itertools import count, groupby
+
+from ansible.module_utils.six import iteritems
+
+
+LOGGING_SEVMAP = {
+ 0: "emergency",
+ 1: "alert",
+ 2: "critical",
+ 3: "error",
+ 4: "warning",
+ 5: "notification",
+ 6: "informational",
+ 7: "debugging",
+}
+
+
+def search_obj_in_list(name, lst, identifier):
+ for o in lst:
+ if o[identifier] == name:
+ return o
+ return None
+
+
+def flatten_dict(x):
+ result = {}
+ if not isinstance(x, dict):
+ return result
+
+ for key, value in iteritems(x):
+ if isinstance(value, dict):
+ result.update(flatten_dict(value))
+ else:
+ result[key] = value
+
+ return result
+
+
+def validate_ipv4_addr(address):
+ address = address.split("/")[0]
+ try:
+ socket.inet_aton(address)
+ except socket.error:
+ return False
+ return address.count(".") == 3
+
+
+def validate_ipv6_addr(address):
+ address = address.split("/")[0]
+ try:
+ socket.inet_pton(socket.AF_INET6, address)
+ except socket.error:
+ return False
+ return True
+
+
+def normalize_interface(name):
+ """Return the normalized interface name"""
+ if not name:
+ return
+
+ def _get_number(name):
+ digits = ""
+ for char in name:
+ if char.isdigit() or char in "/.":
+ digits += char
+ return digits
+
+ if name.lower().startswith("et"):
+ if_type = "Ethernet"
+ elif name.lower().startswith("vl"):
+ if_type = "Vlan"
+ elif name.lower().startswith("lo"):
+ if_type = "loopback"
+ elif name.lower().startswith("po"):
+ if_type = "port-channel"
+ elif name.lower().startswith("nv"):
+ if_type = "nve"
+ else:
+ if_type = None
+
+ number_list = name.split(" ")
+ if len(number_list) == 2:
+ number = number_list[-1].strip()
+ else:
+ number = _get_number(name)
+
+ if if_type:
+ proper_interface = if_type + number
+ else:
+ proper_interface = name
+
+ return proper_interface
+
+
+def get_interface_type(interface):
+ """Gets the type of interface"""
+ if interface.upper().startswith("ET"):
+ return "ethernet"
+ elif interface.upper().startswith("VL"):
+ return "svi"
+ elif interface.upper().startswith("LO"):
+ return "loopback"
+ elif interface.upper().startswith("MG"):
+ return "management"
+ elif interface.upper().startswith("MA"):
+ return "management"
+ elif interface.upper().startswith("PO"):
+ return "portchannel"
+ elif interface.upper().startswith("NV"):
+ return "nve"
+ else:
+ return "unknown"
+
+
+def remove_rsvd_interfaces(interfaces):
+ """Exclude reserved interfaces from user management"""
+ if not interfaces:
+ return []
+ return [i for i in interfaces if get_interface_type(i["name"]) != "management"]
+
+
+def vlan_range_to_list(vlans):
+ result = []
+ if vlans:
+ for part in vlans.split(","):
+ if part == "none":
+ break
+ if "-" in part:
+ a, b = part.split("-")
+ a, b = int(a), int(b)
+ result.extend(range(a, b + 1))
+ else:
+ a = int(part)
+ result.append(a)
+ return numerical_sort(result)
+ return result
+
+
+def numerical_sort(string_int_list):
+ """Sorts list of integers that are digits in numerical order."""
+
+ as_int_list = []
+
+ for vlan in string_int_list:
+ as_int_list.append(int(vlan))
+ as_int_list.sort()
+ return as_int_list
+
+
+def get_logging_sevmap(invert=False):
+ x = LOGGING_SEVMAP
+ if invert:
+ # cannot use dict comprehension yet
+ # since we still test with Python 2.6
+ x = dict(map(reversed, iteritems(x)))
+ return x
+
+
+def get_ranges(data):
+ """
+ Returns a generator object that yields lists of
+ consequtive integers from a list of integers.
+ """
+ for _k, group in groupby(data, lambda t, c=count(): int(t) - next(c)):
+ yield list(group)
+
+
+def vlan_list_to_range(cmd):
+ """
+ Converts a comma separated list of vlan IDs
+ into ranges.
+ """
+ ranges = []
+ for v in get_ranges(cmd):
+ ranges.append("-".join(map(str, (v[0], v[-1])[: len(v)])))
+ return ",".join(ranges)
+
+
+@total_ordering
+class Version:
+ """Simple class to compare arbitrary versions"""
+
+ def __init__(self, version_string):
+ self.components = version_string.split(".")
+
+ def __eq__(self, other):
+ other = _coerce(other)
+ if not isinstance(other, Version):
+ return NotImplemented
+
+ return self.components == other.components
+
+ def __lt__(self, other):
+ other = _coerce(other)
+ if not isinstance(other, Version):
+ return NotImplemented
+
+ return self.components < other.components
+
+
+def _coerce(other):
+ if isinstance(other, str):
+ other = Version(other)
+ if isinstance(other, (int, float)):
+ other = Version(str(other))
+ return other
diff --git a/ansible_collections/cisco/nxos/plugins/modules/__init__.py b/ansible_collections/cisco/nxos/plugins/modules/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server.py
new file mode 100644
index 00000000..9103f931
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server.py
@@ -0,0 +1,336 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_aaa_server
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages AAA server global configuration.
+description:
+- Manages AAA server global configuration
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- The server_type parameter is always required.
+- If encrypt_type is not supplied, the global AAA server key will be stored as encrypted
+ (type 7).
+- Changes to the global AAA server key with encrypt_type=0 are not idempotent.
+- state=default will set the supplied parameters to their default values. The parameters
+ that you want to default must also be set to default. If global_key=default, the
+ global key will be removed.
+options:
+ server_type:
+ description:
+ - The server type is either radius or tacacs.
+ required: true
+ choices:
+ - radius
+ - tacacs
+ type: str
+ global_key:
+ description:
+ - Global AAA shared secret or keyword 'default'.
+ type: str
+ encrypt_type:
+ description:
+ - The state of encryption applied to the entered global key. O clear text, 7 encrypted.
+ Type-6 encryption is not supported.
+ choices:
+ - '0'
+ - '7'
+ type: str
+ deadtime:
+ description:
+ - Duration for which a non-reachable AAA server is skipped, in minutes or keyword
+ 'default. Range is 1-1440. Device default is 0.
+ type: str
+ server_timeout:
+ description:
+ - Global AAA server timeout period, in seconds or keyword 'default. Range is 1-60.
+ Device default is 5.
+ type: str
+ directed_request:
+ description:
+ - Enables direct authentication requests to AAA server or keyword 'default' Device
+ default is disabled.
+ choices:
+ - enabled
+ - disabled
+ - default
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - default
+ type: str
+"""
+
+EXAMPLES = """
+# Radius Server Basic settings
+- name: Radius Server Basic settings
+ cisco.nxos.nxos_aaa_server:
+ server_type: radius
+ server_timeout: 9
+ deadtime: 20
+ directed_request: enabled
+
+# Tacacs Server Basic settings
+- name: Tacacs Server Basic settings
+ cisco.nxos.nxos_aaa_server:
+ server_type: tacacs
+ server_timeout: 8
+ deadtime: 19
+ directed_request: disabled
+
+# Setting Global Key
+- name: AAA Server Global Key
+ cisco.nxos.nxos_aaa_server:
+ server_type: radius
+ global_key: test_key
+"""
+
+RETURN = """
+commands:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["radius-server deadtime 22", "radius-server timeout 11",
+ "radius-server directed-request"]
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+PARAM_TO_DEFAULT_KEYMAP = {
+ "server_timeout": "5",
+ "deadtime": "0",
+ "directed_request": "disabled",
+}
+
+
+def execute_show_command(command, module):
+ command = {"command": command, "output": "text"}
+
+ return run_commands(module, command)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_aaa_server_info(server_type, module):
+ aaa_server_info = {}
+ server_command = "show {0}-server".format(server_type)
+ request_command = "show {0}-server directed-request".format(server_type)
+ global_key_command = "show run | sec {0}".format(server_type)
+ aaa_regex = r".*{0}-server\skey\s\d\s+(?P<key>\S+).*".format(server_type)
+
+ server_body = execute_show_command(server_command, module)[0]
+
+ split_server = server_body.splitlines()
+
+ for line in split_server:
+ if line.startswith("timeout"):
+ aaa_server_info["server_timeout"] = line.split(":")[1]
+
+ elif line.startswith("deadtime"):
+ aaa_server_info["deadtime"] = line.split(":")[1]
+
+ request_body = execute_show_command(request_command, module)[0]
+
+ if bool(request_body):
+ aaa_server_info["directed_request"] = request_body.replace("\n", "")
+ else:
+ aaa_server_info["directed_request"] = "disabled"
+
+ key_body = execute_show_command(global_key_command, module)[0]
+
+ try:
+ match_global_key = re.match(aaa_regex, key_body, re.DOTALL)
+ group_key = match_global_key.groupdict()
+ aaa_server_info["global_key"] = group_key["key"].replace('"', "")
+ except (AttributeError, TypeError):
+ aaa_server_info["global_key"] = None
+
+ return aaa_server_info
+
+
+def config_aaa_server(params, server_type):
+ cmds = []
+
+ deadtime = params.get("deadtime")
+ server_timeout = params.get("server_timeout")
+ directed_request = params.get("directed_request")
+ encrypt_type = params.get("encrypt_type", "7")
+ global_key = params.get("global_key")
+
+ if deadtime is not None:
+ cmds.append("{0}-server deadtime {1}".format(server_type, deadtime))
+
+ if server_timeout is not None:
+ cmds.append("{0}-server timeout {1}".format(server_type, server_timeout))
+
+ if directed_request is not None:
+ if directed_request == "enabled":
+ cmds.append("{0}-server directed-request".format(server_type))
+ elif directed_request == "disabled":
+ cmds.append("no {0}-server directed-request".format(server_type))
+
+ if global_key is not None:
+ cmds.append("{0}-server key {1} {2}".format(server_type, encrypt_type, global_key))
+
+ return cmds
+
+
+def default_aaa_server(existing, params, server_type):
+ cmds = []
+
+ deadtime = params.get("deadtime")
+ server_timeout = params.get("server_timeout")
+ directed_request = params.get("directed_request")
+ global_key = params.get("global_key")
+ existing_key = existing.get("global_key")
+
+ if deadtime is not None and existing.get("deadtime") != PARAM_TO_DEFAULT_KEYMAP["deadtime"]:
+ cmds.append("no {0}-server deadtime 1".format(server_type))
+
+ if (
+ server_timeout is not None
+ and existing.get("server_timeout") != PARAM_TO_DEFAULT_KEYMAP["server_timeout"]
+ ):
+ cmds.append("no {0}-server timeout 1".format(server_type))
+
+ if (
+ directed_request is not None
+ and existing.get("directed_request") != PARAM_TO_DEFAULT_KEYMAP["directed_request"]
+ ):
+ cmds.append("no {0}-server directed-request".format(server_type))
+
+ if global_key is not None and existing_key is not None:
+ cmds.append("no {0}-server key 7 {1}".format(server_type, existing_key))
+
+ return cmds
+
+
+def main():
+ argument_spec = dict(
+ server_type=dict(type="str", choices=["radius", "tacacs"], required=True),
+ global_key=dict(type="str", no_log=True),
+ encrypt_type=dict(type="str", choices=["0", "7"]),
+ deadtime=dict(type="str"),
+ server_timeout=dict(type="str"),
+ directed_request=dict(type="str", choices=["enabled", "disabled", "default"]),
+ state=dict(choices=["default", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ server_type = module.params["server_type"]
+ global_key = module.params["global_key"]
+ encrypt_type = module.params["encrypt_type"]
+ deadtime = module.params["deadtime"]
+ server_timeout = module.params["server_timeout"]
+ directed_request = module.params["directed_request"]
+ state = module.params["state"]
+
+ if encrypt_type and not global_key:
+ module.fail_json(msg="encrypt_type must be used with global_key.")
+
+ args = dict(
+ server_type=server_type,
+ global_key=global_key,
+ encrypt_type=encrypt_type,
+ deadtime=deadtime,
+ server_timeout=server_timeout,
+ directed_request=directed_request,
+ )
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ existing = get_aaa_server_info(server_type, module)
+
+ commands = []
+ if state == "present":
+ if deadtime:
+ try:
+ if int(deadtime) < 0 or int(deadtime) > 1440:
+ raise ValueError
+ except ValueError:
+ module.fail_json(msg="deadtime must be an integer between 0 and 1440")
+
+ if server_timeout:
+ try:
+ if int(server_timeout) < 1 or int(server_timeout) > 60:
+ raise ValueError
+ except ValueError:
+ module.fail_json(msg="server_timeout must be an integer between 1 and 60")
+
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = config_aaa_server(delta, server_type)
+ if command:
+ commands.append(command)
+
+ elif state == "default":
+ for key, value in proposed.items():
+ if key != "server_type" and value != "default":
+ module.fail_json(msg='Parameters must be set to "default"' "when state=default")
+ command = default_aaa_server(existing, proposed, server_type)
+ if command:
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server_host.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server_host.py
new file mode 100644
index 00000000..d2f84f76
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_aaa_server_host.py
@@ -0,0 +1,370 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_aaa_server_host
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages AAA server host-specific configuration.
+description:
+- Manages AAA server host-specific configuration.
+version_added: 1.0.0
+author: Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- Changes to the host key (shared secret) are not idempotent for type 0.
+- If C(state=absent) removes the whole host configuration.
+options:
+ server_type:
+ description:
+ - The server type is either radius or tacacs.
+ required: true
+ choices:
+ - radius
+ - tacacs
+ type: str
+ address:
+ description:
+ - Address or name of the radius or tacacs host.
+ required: true
+ type: str
+ key:
+ description:
+ - Shared secret for the specified host or keyword 'default'.
+ type: str
+ encrypt_type:
+ description:
+ - The state of encryption applied to the entered key. O for clear text, 7 for
+ encrypted. Type-6 encryption is not supported.
+ choices:
+ - '0'
+ - '7'
+ type: str
+ host_timeout:
+ description:
+ - Timeout period for specified host, in seconds or keyword 'default. Range is
+ 1-60.
+ type: str
+ auth_port:
+ description:
+ - Alternate UDP port for RADIUS authentication or keyword 'default'.
+ type: str
+ acct_port:
+ description:
+ - Alternate UDP port for RADIUS accounting or keyword 'default'.
+ type: str
+ tacacs_port:
+ description:
+ - Alternate TCP port TACACS Server or keyword 'default'.
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+EXAMPLES = """
+# Radius Server Host Basic settings
+- name: Radius Server Host Basic settings
+ cisco.nxos.nxos_aaa_server_host:
+ state: present
+ server_type: radius
+ address: 1.2.3.4
+ acct_port: 2084
+ host_timeout: 10
+
+# Radius Server Host Key Configuration
+- name: Radius Server Host Key Configuration
+ cisco.nxos.nxos_aaa_server_host:
+ state: present
+ server_type: radius
+ address: 1.2.3.4
+ key: hello
+ encrypt_type: 7
+
+# TACACS Server Host Configuration
+- name: Tacacs Server Host Configuration
+ cisco.nxos.nxos_aaa_server_host:
+ state: present
+ server_type: tacacs
+ tacacs_port: 89
+ host_timeout: 10
+ address: 5.6.7.8
+
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"address": "1.2.3.4", "auth_port": "2084",
+ "host_timeout": "10", "server_type": "radius"}
+existing:
+ description:
+ - k/v pairs of existing configuration
+ returned: always
+ type: dict
+ sample: {}
+end_state:
+ description: k/v pairs of configuration after module execution
+ returned: always
+ type: dict
+ sample: {"address": "1.2.3.4", "auth_port": "2084",
+ "host_timeout": "10", "server_type": "radius"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["radius-server host 1.2.3.4 auth-port 2084 timeout 10"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ device_info = get_capabilities(module)
+ network_api = device_info.get("network_api", "nxapi")
+
+ if network_api == "cliconf":
+ cmds = [command]
+ body = run_commands(module, cmds)
+ elif network_api == "nxapi":
+ cmds = {"command": command, "output": "text"}
+ body = run_commands(module, cmds)
+
+ return body
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_aaa_host_info(module, server_type, address):
+ aaa_host_info = {}
+ command = "show run | inc {0}-server.host.{1}".format(server_type, address)
+
+ body = execute_show_command(command, module)[0]
+ if body:
+ try:
+ if "radius" in body:
+ pattern = (
+ r"\S+ host \S+(?:\s+key 7\s+(\S+))?(?:\s+auth-port (\d+))?"
+ r"(?:\s+acct-port (\d+))?(?:\s+authentication)?"
+ r"(?:\s+accounting)?(?:\s+timeout (\d+))?"
+ )
+ match = re.search(pattern, body)
+ aaa_host_info["key"] = match.group(1)
+ if aaa_host_info["key"]:
+ aaa_host_info["key"] = aaa_host_info["key"].replace('"', "")
+ aaa_host_info["encrypt_type"] = "7"
+ aaa_host_info["auth_port"] = match.group(2)
+ aaa_host_info["acct_port"] = match.group(3)
+ aaa_host_info["host_timeout"] = match.group(4)
+ elif "tacacs" in body:
+ pattern = (
+ r"\S+ host \S+(?:\s+key 7\s+(\S+))?(?:\s+port (\d+))?(?:\s+timeout (\d+))?"
+ )
+ match = re.search(pattern, body)
+ aaa_host_info["key"] = match.group(1)
+ if aaa_host_info["key"]:
+ aaa_host_info["key"] = aaa_host_info["key"].replace('"', "")
+ aaa_host_info["encrypt_type"] = "7"
+ aaa_host_info["tacacs_port"] = match.group(2)
+ aaa_host_info["host_timeout"] = match.group(3)
+
+ aaa_host_info["server_type"] = server_type
+ aaa_host_info["address"] = address
+ except TypeError:
+ return {}
+ else:
+ return {}
+
+ return aaa_host_info
+
+
+def config_aaa_host(server_type, address, params, existing):
+ cmds = []
+ cmd_str = "{0}-server host {1}".format(server_type, address)
+ cmd_no_str = "no " + cmd_str
+
+ key = params.get("key")
+ enc_type = params.get("encrypt_type", "")
+
+ defval = False
+ nondef = False
+
+ if key:
+ if key != "default":
+ cmds.append(cmd_str + " key {0} {1}".format(enc_type, key))
+ else:
+ cmds.append(cmd_no_str + " key 7 {0}".format(existing.get("key")))
+
+ locdict = {
+ "auth_port": "auth-port",
+ "acct_port": "acct-port",
+ "tacacs_port": "port",
+ "host_timeout": "timeout",
+ }
+
+ # platform CLI needs the keywords in the following order
+ for key in ["auth_port", "acct_port", "tacacs_port", "host_timeout"]:
+ item = params.get(key)
+ if item:
+ if item != "default":
+ cmd_str += " {0} {1}".format(locdict.get(key), item)
+ nondef = True
+ else:
+ cmd_no_str += " {0} 1".format(locdict.get(key))
+ defval = True
+ if defval:
+ cmds.append(cmd_no_str)
+ if nondef or not existing:
+ cmds.append(cmd_str)
+
+ return cmds
+
+
+def main():
+ argument_spec = dict(
+ server_type=dict(choices=["radius", "tacacs"], required=True),
+ address=dict(type="str", required=True),
+ key=dict(type="str", no_log=False),
+ encrypt_type=dict(type="str", choices=["0", "7"]),
+ host_timeout=dict(type="str"),
+ auth_port=dict(type="str"),
+ acct_port=dict(type="str"),
+ tacacs_port=dict(type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ server_type = module.params["server_type"]
+ address = module.params["address"]
+ key = module.params["key"]
+ encrypt_type = module.params["encrypt_type"]
+ host_timeout = module.params["host_timeout"]
+ auth_port = module.params["auth_port"]
+ acct_port = module.params["acct_port"]
+ tacacs_port = module.params["tacacs_port"]
+ state = module.params["state"]
+
+ args = dict(
+ server_type=server_type,
+ address=address,
+ key=key,
+ encrypt_type=encrypt_type,
+ host_timeout=host_timeout,
+ auth_port=auth_port,
+ acct_port=acct_port,
+ tacacs_port=tacacs_port,
+ )
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ changed = False
+
+ if encrypt_type and not key:
+ module.fail_json(msg="encrypt_type must be used with key")
+
+ if tacacs_port and server_type != "tacacs":
+ module.fail_json(msg="tacacs_port can only be used with server_type=tacacs")
+
+ if (auth_port or acct_port) and server_type != "radius":
+ module.fail_json(msg="auth_port and acct_port can only be used" "when server_type=radius")
+
+ existing = get_aaa_host_info(module, server_type, address)
+ end_state = existing
+
+ commands = []
+ delta = {}
+ if state == "present":
+ if not existing:
+ delta = proposed
+ else:
+ for key, value in proposed.items():
+ if key == "encrypt_type":
+ delta[key] = value
+ if value != existing.get(key):
+ if value != "default" or existing.get(key):
+ delta[key] = value
+
+ command = config_aaa_host(server_type, address, delta, existing)
+ if command:
+ commands.append(command)
+
+ elif state == "absent":
+ intersect = dict(set(proposed.items()).intersection(existing.items()))
+ if intersect.get("address") and intersect.get("server_type"):
+ command = "no {0}-server host {1}".format(
+ intersect.get("server_type"),
+ intersect.get("address"),
+ )
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_aaa_host_info(module, server_type, address)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+ results["end_state"] = end_state
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_acl_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_acl_interfaces.py
new file mode 100644
index 00000000..e61746ff
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_acl_interfaces.py
@@ -0,0 +1,440 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_acl_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_acl_interfaces
+short_description: ACL interfaces resource module
+description: Add and remove Access Control Lists on interfaces in NX-OS platform
+version_added: 1.0.0
+author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
+notes:
+- Tested against NX-OS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^interface').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of interfaces to be configured with ACLs
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the interface
+ type: str
+ required: true
+ access_groups:
+ description: List of address family indicators with ACLs to be configured
+ on the interface
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: Address Family Indicator of the ACLs to be configured
+ type: str
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ acls:
+ description: List of Access Control Lists for the interface
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the ACL to be added/removed
+ type: str
+ required: true
+ direction:
+ description: Direction to be applied for the ACL
+ type: str
+ required: true
+ choices:
+ - in
+ - out
+ port:
+ description: Use ACL as port policy.
+ type: bool
+ state:
+ description: The state the configuration should be left in
+ type: str
+ choices:
+ - deleted
+ - gathered
+ - merged
+ - overridden
+ - rendered
+ - replaced
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# ------------
+#
+
+- name: Merge ACL interfaces configuration
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ - name: Ethernet1/2
+ access_groups:
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ direction: in
+
+ - name: Eth1/5
+ access_groups:
+ - afi: ipv4
+ acls:
+ - name: PortACL
+ direction: in
+ port: true
+
+ - name: ACL1v4
+ direction: out
+
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ direction: in
+ state: merged
+
+# After state:
+# ------------
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ip port access-group PortACL in
+# ip access-group ACL1v4 out
+# ipv6 traffic-filter ACL1v6 in
+
+# Using replaced
+
+# Before state:
+# ------------
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ip port access-group PortACL in
+# ip access-group ACL1v4 out
+# ipv6 traffic-filter ACL1v6 in
+
+- name: Replace interface configuration with given configuration
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ - name: Eth1/5
+ access_groups:
+ - afi: ipv4
+ acls:
+ - name: NewACLv4
+ direction: out
+
+ - name: Ethernet1/3
+ access_groups:
+ - afi: ipv6
+ acls:
+ - name: NewACLv6
+ direction: in
+ port: true
+ state: replaced
+
+# After state:
+# ------------
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/3
+# ipv6 port traffic-filter NewACLv6 in
+# interface Ethernet1/5
+# ip access-group NewACLv4 out
+
+# Using overridden
+
+# Before state:
+# ------------
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ip port access-group PortACL in
+# ip access-group ACL1v4 out
+# ipv6 traffic-filter ACL1v6 in
+
+- name: Override interface configuration with given configuration
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ - name: Ethernet1/3
+ access_groups:
+ - afi: ipv4
+ acls:
+ - name: ACL1v4
+ direction: out
+
+ - name: PortACL
+ port: true
+ direction: in
+ - afi: ipv6
+ acls:
+ - name: NewACLv6
+ direction: in
+ port: true
+ state: overridden
+
+# After state:
+# ------------
+# interface Ethernet1/3
+# ip access-group ACL1v4 out
+# ip port access-group PortACL in
+# ipv6 port traffic-filter NewACLv6 in
+
+# Using deleted to remove ACL config from specified interfaces
+
+# Before state:
+# -------------
+# interface Ethernet1/1
+# ip access-group ACL2v4 in
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ip port access-group PortACL in
+# ip access-group ACL1v4 out
+# ipv6 traffic-filter ACL1v6 in
+
+- name: Delete ACL configuration on interfaces
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ - name: Ethernet1/5
+ - name: Ethernet1/2
+ state: deleted
+
+# After state:
+# -------------
+# interface Ethernet1/1
+# ip access-group ACL2v4 in
+# interface Ethernet1/2
+# interface Ethernet1/5
+
+# Using deleted to remove ACL config from all interfaces
+
+# Before state:
+# -------------
+# interface Ethernet1/1
+# ip access-group ACL2v4 in
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ip port access-group PortACL in
+# ip access-group ACL1v4 out
+# ipv6 traffic-filter ACL1v6 in
+
+- name: Delete ACL configuration from all interfaces
+ cisco.nxos.nxos_acl_interfaces:
+ state: deleted
+
+# After state:
+# -------------
+# interface Ethernet1/1
+# interface Ethernet1/2
+# interface Ethernet1/5
+
+# Using parsed
+
+- name: Parse given configuration into structured format
+ cisco.nxos.nxos_acl_interfaces:
+ running_config: |
+ interface Ethernet1/2
+ ipv6 traffic-filter ACL1v6 in
+ interface Ethernet1/5
+ ipv6 traffic-filter ACL1v6 in
+ ip access-group ACL1v4 out
+ ip port access-group PortACL in
+ state: parsed
+
+# returns
+# parsed:
+# - name: Ethernet1/2
+# access_groups:
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# direction: in
+# - name: Ethernet1/5
+# access_groups:
+# - afi: ipv4
+# acls:
+# - name: PortACL
+# direction: in
+# port: True
+# - name: ACL1v4
+# direction: out
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# direction: in
+
+
+# Using gathered:
+
+# Before state:
+# ------------
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ipv6 traffic-filter ACL1v6 in
+# ip access-group ACL1v4 out
+# ip port access-group PortACL in
+
+- name: Gather existing configuration from device
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ state: gathered
+
+# returns
+# gathered:
+# - name: Ethernet1/2
+# access_groups:
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# direction: in
+# - name: Ethernet1/5
+# access_groups:
+# - afi: ipv4
+# acls:
+# - name: PortACL
+# direction: in
+# port: True
+# - name: ACL1v4
+# direction: out
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# direction: in
+
+
+# Using rendered
+
+- name: Render required configuration to be pushed to the device
+ cisco.nxos.nxos_acl_interfaces:
+ config:
+ - name: Ethernet1/2
+ access_groups:
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ direction: in
+
+ - name: Ethernet1/5
+ access_groups:
+ - afi: ipv4
+ acls:
+ - name: PortACL
+ direction: in
+ port: true
+ - name: ACL1v4
+ direction: out
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ direction: in
+ state: rendered
+
+# returns
+# rendered:
+# interface Ethernet1/2
+# ipv6 traffic-filter ACL1v6 in
+# interface Ethernet1/5
+# ipv6 traffic-filter ACL1v6 in
+# ip access-group ACL1v4 out
+# ip port access-group PortACL in
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet1/2', 'ipv6 traffic-filter ACL1v6 out', 'ip port access-group PortACL in']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acl_interfaces.acl_interfaces import (
+ Acl_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.acl_interfaces.acl_interfaces import (
+ Acl_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Acl_interfacesArgs.argument_spec,
+ supports_check_mode=True,
+ )
+
+ result = Acl_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_acls.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_acls.py
new file mode 100644
index 00000000..51a66504
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_acls.py
@@ -0,0 +1,915 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The module file for nxos_acls
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_acls
+short_description: ACLs resource module
+description: Manage named IP ACLs on the Cisco NX-OS platform
+version_added: 1.0.0
+author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
+notes:
+- Tested against NX-OS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- As NX-OS allows configuring a rule again with different sequence numbers, the user
+ is expected to provide sequence numbers for the access control entries to preserve
+ idempotency. If no sequence number is given, the rule will be added as a new rule
+ by the device.
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section 'ip(v6)* access-list).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of ACL options.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: The Address Family Indicator (AFI) for the ACL.
+ type: str
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ acls:
+ description: A list of the ACLs.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the ACL.
+ type: str
+ required: true
+ aces:
+ description: The entries within the ACL.
+ type: list
+ elements: dict
+ suboptions:
+ grant:
+ description: Action to be applied on the rule.
+ type: str
+ choices:
+ - permit
+ - deny
+ destination:
+ description: Specify the packet destination.
+ type: dict
+ suboptions:
+ address:
+ description: Destination network address.
+ type: str
+ any:
+ description: Any destination address.
+ type: bool
+ host:
+ description: Host IP address.
+ type: str
+ port_protocol:
+ description: Specify the destination port or protocol (only for
+ TCP and UDP).
+ type: dict
+ suboptions:
+ eq:
+ description: Match only packets on a given port number.
+ type: str
+ gt:
+ description: Match only packets with a greater port number.
+ type: str
+ lt:
+ description: Match only packets with a lower port number.
+ type: str
+ neq:
+ description: Match only packets not on a given port number.
+ type: str
+ range:
+ description: Match only packets in the range of port numbers.
+ type: dict
+ suboptions:
+ start:
+ description: Specify the start of the port range.
+ type: str
+ end:
+ description: Specify the end of the port range.
+ type: str
+ prefix:
+ description: Destination network prefix. Only for prefixes of
+ value less than 31 for ipv4 and 127 for ipv6. Prefixes of 32
+ (ipv4) and 128 (ipv6) should be given in the 'host' key.
+ type: str
+ wildcard_bits:
+ description: Destination wildcard bits.
+ type: str
+ dscp:
+ description: Match packets with given DSCP value.
+ type: str
+ fragments:
+ description: Check non-initial fragments.
+ type: bool
+ remark:
+ description: Access list entry comment.
+ type: str
+ sequence:
+ description: Sequence number.
+ type: int
+ source:
+ description: Specify the packet source.
+ type: dict
+ suboptions:
+ address:
+ description: Source network address.
+ type: str
+ any:
+ description: Any source address.
+ type: bool
+ host:
+ description: Host IP address.
+ type: str
+ port_protocol:
+ description: Specify the destination port or protocol (only for
+ TCP and UDP).
+ type: dict
+ suboptions:
+ eq:
+ description: Match only packets on a given port number.
+ type: str
+ gt:
+ description: Match only packets with a greater port number.
+ type: str
+ lt:
+ description: Match only packets with a lower port number.
+ type: str
+ neq:
+ description: Match only packets not on a given port number.
+ type: str
+ range:
+ description: Match only packets in the range of port numbers.
+ type: dict
+ suboptions:
+ start:
+ description: Specify the start of the port range.
+ type: str
+ end:
+ description: Specify the end of the port range.
+ type: str
+ prefix:
+ description: Source network prefix. Only for prefixes of mask
+ value less than 31 for ipv4 and 127 for ipv6. Prefixes of mask
+ 32 (ipv4) and 128 (ipv6) should be given in the 'host' key.
+ type: str
+ wildcard_bits:
+ description: Source wildcard bits.
+ type: str
+ log:
+ description: Log matches against this entry.
+ type: bool
+ precedence:
+ description: Match packets with given precedence value.
+ type: str
+ protocol:
+ description: Specify the protocol.
+ type: str
+ protocol_options:
+ description: All possible suboptions for the protocol chosen.
+ type: dict
+ suboptions:
+ icmp:
+ description: ICMP protocol options.
+ type: dict
+ suboptions:
+ administratively_prohibited:
+ description: Administratively prohibited
+ type: bool
+ alternate_address:
+ description: Alternate address
+ type: bool
+ conversion_error:
+ description: Datagram conversion
+ type: bool
+ dod_host_prohibited:
+ description: Host prohibited
+ type: bool
+ dod_net_prohibited:
+ description: Net prohibited
+ type: bool
+ echo:
+ description: Echo (ping)
+ type: bool
+ echo_reply:
+ description: Echo reply
+ type: bool
+ echo_request:
+ description: Echo request (ping)
+ type: bool
+ general_parameter_problem:
+ description: Parameter problem
+ type: bool
+ host_isolated:
+ description: Host isolated
+ type: bool
+ host_precedence_unreachable:
+ description: Host unreachable for precedence
+ type: bool
+ host_redirect:
+ description: Host redirect
+ type: bool
+ host_tos_redirect:
+ description: Host redirect for TOS
+ type: bool
+ host_tos_unreachable:
+ description: Host unreachable for TOS
+ type: bool
+ host_unknown:
+ description: Host unknown
+ type: bool
+ host_unreachable:
+ description: Host unreachable
+ type: bool
+ information_reply:
+ description: Information replies
+ type: bool
+ information_request:
+ description: Information requests
+ type: bool
+ mask_reply:
+ description: Mask replies
+ type: bool
+ mask_request:
+ description: Mask requests
+ type: bool
+ message_code:
+ description: ICMP message code
+ type: int
+ message_type:
+ description: ICMP message type
+ type: int
+ mobile_redirect:
+ description: Mobile host redirect
+ type: bool
+ net_redirect:
+ description: Network redirect
+ type: bool
+ net_tos_redirect:
+ description: Net redirect for TOS
+ type: bool
+ net_tos_unreachable:
+ description: Network unreachable for TOS
+ type: bool
+ net_unreachable:
+ description: Net unreachable
+ type: bool
+ network_unknown:
+ description: Network unknown
+ type: bool
+ no_room_for_option:
+ description: Parameter required but no room
+ type: bool
+ option_missing:
+ description: Parameter required but not present
+ type: bool
+ packet_too_big:
+ description: Fragmentation needed and DF set
+ type: bool
+ parameter_problem:
+ description: All parameter problems
+ type: bool
+ port_unreachable:
+ description: Port unreachable
+ type: bool
+ precedence_unreachable:
+ description: Precedence cutoff
+ type: bool
+ protocol_unreachable:
+ description: Protocol unreachable
+ type: bool
+ reassembly_timeout:
+ description: Reassembly timeout
+ type: bool
+ redirect:
+ description: All redirects
+ type: bool
+ router_advertisement:
+ description: Router discovery advertisements
+ type: bool
+ router_solicitation:
+ description: Router discovery solicitations
+ type: bool
+ source_quench:
+ description: Source quenches
+ type: bool
+ source_route_failed:
+ description: Source route failed
+ type: bool
+ time_exceeded:
+ description: All time exceeded.
+ type: bool
+ timestamp_reply:
+ description: Timestamp replies
+ type: bool
+ timestamp_request:
+ description: Timestamp requests
+ type: bool
+ traceroute:
+ description: Traceroute
+ type: bool
+ ttl_exceeded:
+ description: TTL exceeded
+ type: bool
+ unreachable:
+ description: All unreachables
+ type: bool
+ icmpv6:
+ description: ICMPv6 protocol options.
+ type: dict
+ suboptions:
+ beyond_scope:
+ description: Destination beyond scope.
+ type: bool
+ destination_unreachable:
+ description: Destination address is unreachable.
+ type: bool
+ echo_reply:
+ description: Echo reply.
+ type: bool
+ echo_request:
+ description: Echo request (ping).
+ type: bool
+ fragments:
+ description: Check non-initial fragments.
+ type: bool
+ header:
+ description: Parameter header problem.
+ type: bool
+ hop_limit:
+ description: Hop limit exceeded in transit.
+ type: bool
+ mld_query:
+ description: Multicast Listener Discovery Query.
+ type: bool
+ mld_reduction:
+ description: Multicast Listener Discovery Reduction.
+ type: bool
+ mld_report:
+ description: Multicast Listener Discovery Report.
+ type: bool
+ mldv2:
+ description: Multicast Listener Discovery Protocol.
+ type: bool
+ nd_na:
+ description: Neighbor discovery neighbor advertisements.
+ type: bool
+ nd_ns:
+ description: Neighbor discovery neighbor solicitations.
+ type: bool
+ next_header:
+ description: Parameter next header problems.
+ type: bool
+ no_admin:
+ description: Administration prohibited destination.
+ type: bool
+ no_route:
+ description: No route to destination.
+ type: bool
+ packet_too_big:
+ description: Packet too big.
+ type: bool
+ parameter_option:
+ description: Parameter option problems.
+ type: bool
+ parameter_problem:
+ description: All parameter problems.
+ type: bool
+ port_unreachable:
+ description: Port unreachable.
+ type: bool
+ reassembly_timeout:
+ description: Reassembly timeout.
+ type: bool
+ renum_command:
+ description: Router renumbering command.
+ type: bool
+ renum_result:
+ description: Router renumbering result.
+ type: bool
+ renum_seq_number:
+ description: Router renumbering sequence number reset.
+ type: bool
+ router_advertisement:
+ description: Neighbor discovery router advertisements.
+ type: bool
+ router_renumbering:
+ description: All router renumbering.
+ type: bool
+ router_solicitation:
+ description: Neighbor discovery router solicitations.
+ type: bool
+ time_exceeded:
+ description: All time exceeded.
+ type: bool
+ unreachable:
+ description: All unreachable.
+ type: bool
+ telemetry_path:
+ description: IPT enabled.
+ type: bool
+ telemetry_queue:
+ description: Flow of interest for BDC/HDC.
+ type: bool
+ tcp:
+ description: TCP flags.
+ type: dict
+ suboptions:
+ ack:
+ description: Match on the ACK bit
+ type: bool
+ established:
+ description: Match established connections
+ type: bool
+ fin:
+ description: Match on the FIN bit
+ type: bool
+ psh:
+ description: Match on the PSH bit
+ type: bool
+ rst:
+ description: Match on the RST bit
+ type: bool
+ syn:
+ description: Match on the SYN bit
+ type: bool
+ urg:
+ description: Match on the URG bit
+ type: bool
+ igmp:
+ description: IGMP protocol options.
+ type: dict
+ suboptions:
+ dvmrp:
+ description: Distance Vector Multicast Routing Protocol
+ type: bool
+ host_query:
+ description: Host Query
+ type: bool
+ host_report:
+ description: Host Report
+ type: bool
+ state:
+ description:
+ - The state the configuration should be left in
+ type: str
+ choices:
+ - deleted
+ - gathered
+ - merged
+ - overridden
+ - rendered
+ - replaced
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+
+- name: Merge new ACLs configuration
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: ACL1v4
+ aces:
+ - grant: deny
+ destination:
+ address: 192.0.2.64
+ wildcard_bits: 0.0.0.255
+ source:
+ any: true
+ port_protocol:
+ lt: 55
+ protocol: tcp
+ protocol_options:
+ tcp:
+ ack: true
+ fin: true
+ sequence: 50
+
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ aces:
+ - grant: permit
+ sequence: 10
+ source:
+ any: true
+ destination:
+ prefix: 2001:db8:12::/32
+ protocol: sctp
+ state: merged
+
+# After state:
+# ------------
+#
+# ip access-list ACL1v4
+# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
+# ipv6 access-list ACL1v6
+# 10 permit sctp any any
+
+# Using replaced
+
+# Before state:
+# ----------------
+#
+# ip access-list ACL1v4
+# 10 permit ip any any
+# 20 deny udp any any
+# ip access-list ACL2v4
+# 10 permit ahp 192.0.2.0 0.0.0.255 any
+# ip access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ip access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+- name: Replace existing ACL configuration with provided configuration
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ aces:
+ - sequence: 20
+ grant: permit
+ source:
+ any: true
+ destination:
+ any: true
+ protocol: pip
+
+ - remark: Replaced ACE
+
+ - name: ACL2v6
+ state: replaced
+
+# After state:
+# ---------------
+#
+# ipv6 access-list ACL1v6
+# 20 permit pip any any
+# 30 remark Replaced ACE
+# ipv6 access-list ACL2v6
+
+# Using overridden
+
+# Before state:
+# ----------------
+#
+# ip access-list ACL1v4
+# 10 permit ip any any
+# 20 deny udp any any
+# ip access-list ACL2v4
+# 10 permit ahp 192.0.2.0 0.0.0.255 any
+# ip access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ip access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+- name: Override existing configuration with provided configuration
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: NewACL
+ aces:
+ - grant: deny
+ source:
+ address: 192.0.2.0
+ wildcard_bits: 0.0.255.255
+ destination:
+ any: true
+ protocol: eigrp
+ - remark: Example for overridden state
+ state: overridden
+
+# After state:
+# ------------
+#
+# ip access-list NewACL
+# 10 deny eigrp 192.0.2.0 0.0.255.255 any
+# 20 remark Example for overridden state
+
+# Using deleted:
+#
+# Before state:
+# -------------
+#
+# ip access-list ACL1v4
+# 10 permit ip any any
+# 20 deny udp any any
+# ip access-list ACL2v4
+# 10 permit ahp 192.0.2.0 0.0.0.255 any
+# ip access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ip access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+- name: Delete all ACLs
+ cisco.nxos.nxos_acls:
+ config:
+ state: deleted
+
+# After state:
+# -----------
+#
+
+
+# Before state:
+# -------------
+#
+# ip access-list ACL1v4
+# 10 permit ip any any
+# 20 deny udp any any
+# ip access-list ACL2v4
+# 10 permit ahp 192.0.2.0 0.0.0.255 any
+# ip access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ip access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+- name: Delete all ACLs in given AFI
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ state: deleted
+
+# After state:
+# ------------
+#
+# ip access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ip access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+
+
+# Before state:
+# -------------
+#
+# ip access-list ACL1v4
+# 10 permit ip any any
+# 20 deny udp any any
+# ip access-list ACL2v4
+# 10 permit ahp 192.0.2.0 0.0.0.255 any
+# ipv6 access-list ACL1v6
+# 10 permit sctp any any
+# 20 remark IPv6 ACL
+# ipv6 access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+- name: Delete specific ACLs
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: ACL1v4
+ - name: ACL2v4
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ state: deleted
+
+# After state:
+# ------------
+# ipv6 access-list ACL2v6
+# 10 deny ipv6 any 2001:db8:3000::/36
+# 20 permit tcp 2001:db8:2000:2::2/128 2001:db8:2000:ab::2/128
+
+# Using parsed
+
+- name: Parse given config to structured data
+ cisco.nxos.nxos_acls:
+ running_config: |
+ ip access-list ACL1v4
+ 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
+ ipv6 access-list ACL1v6
+ 10 permit sctp any any
+ state: parsed
+
+# returns:
+# parsed:
+# - afi: ipv4
+# acls:
+# - name: ACL1v4
+# aces:
+# - grant: deny
+# destination:
+# address: 192.0.2.64
+# wildcard_bits: 0.0.0.255
+# source:
+# any: true
+# port_protocol:
+# lt: 55
+# protocol: tcp
+# protocol_options:
+# tcp:
+# ack: true
+# fin: true
+# sequence: 50
+#
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# aces:
+# - grant: permit
+# sequence: 10
+# source:
+# any: true
+# destination:
+# prefix: 2001:db8:12::/32
+# protocol: sctp
+
+
+# Using gathered:
+
+# Before state:
+# ------------
+#
+# ip access-list ACL1v4
+# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
+# ipv6 access-list ACL1v6
+# 10 permit sctp any any
+
+- name: Gather existing configuration
+ cisco.nxos.nxos_acls:
+ state: gathered
+
+# returns:
+# gathered:
+# - afi: ipv4
+# acls:
+# - name: ACL1v4
+# aces:
+# - grant: deny
+# destination:
+# address: 192.0.2.64
+# wildcard_bits: 0.0.0.255
+# source:
+# any: true
+# port_protocol:
+# lt: 55
+# protocol: tcp
+# protocol_options:
+# tcp:
+# ack: true
+# fin: true
+# sequence: 50
+
+# - afi: ipv6
+# acls:
+# - name: ACL1v6
+# aces:
+# - grant: permit
+# sequence: 10
+# source:
+# any: true
+# destination:
+# prefix: 2001:db8:12::/32
+# protocol: sctp
+
+
+# Using rendered
+
+- name: Render required configuration to be pushed to the device
+ cisco.nxos.nxos_acls:
+ config:
+ - afi: ipv4
+ acls:
+ - name: ACL1v4
+ aces:
+ - grant: deny
+ destination:
+ address: 192.0.2.64
+ wildcard_bits: 0.0.0.255
+ source:
+ any: true
+ port_protocol:
+ lt: 55
+ protocol: tcp
+ protocol_options:
+ tcp:
+ ack: true
+ fin: true
+ sequence: 50
+
+ - afi: ipv6
+ acls:
+ - name: ACL1v6
+ aces:
+ - grant: permit
+ sequence: 10
+ source:
+ any: true
+ destination:
+ prefix: 2001:db8:12::/32
+ protocol: sctp
+ state: rendered
+
+# returns:
+# rendered:
+# ip access-list ACL1v4
+# 50 deny tcp any lt 55 192.0.2.64 0.0.0.255 ack fin
+# ipv6 access-list ACL1v6
+# 10 permit sctp any any
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['ip access-list ACL1v4', '10 permit ip any any precedence critical log', '20 deny tcp any lt smtp host 192.0.2.64 ack fin']
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.acls.acls import (
+ AclsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.acls.acls import Acls
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=AclsArgs.argument_spec, supports_check_mode=True)
+
+ result = Acls(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_banner.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_banner.py
new file mode 100644
index 00000000..f946dc0a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_banner.py
@@ -0,0 +1,224 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# (c) 2017, Ansible by Red Hat, inc
+#
+# This file is part of Ansible by Red Hat
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+
+DOCUMENTATION = """
+module: nxos_banner
+author: Trishna Guha (@trishnaguha)
+short_description: Manage multiline banners on Cisco NXOS devices
+description:
+- This will configure both exec and motd banners on remote devices running Cisco NXOS.
+ It allows playbooks to add or remove banner text from the active running configuration.
+notes:
+- Since responses from the device are always read with surrounding whitespaces stripped,
+ tasks that configure banners with preceeding or trailing whitespaces will not be idempotent.
+- Limited Support for Cisco MDS
+version_added: 1.0.0
+options:
+ banner:
+ description:
+ - Specifies which banner that should be configured on the remote device.
+ required: true
+ choices:
+ - exec
+ - motd
+ type: str
+ text:
+ description:
+ - The banner text that should be present in the remote device running configuration.
+ This argument accepts a multiline string, with no empty lines. Requires I(state=present).
+ type: str
+ state:
+ description:
+ - Specifies whether or not the configuration is present in the current devices
+ active running configuration.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+extends_documentation_fragment:
+- cisco.nxos.nxos
+"""
+
+EXAMPLES = """
+- name: configure the exec banner
+ cisco.nxos.nxos_banner:
+ banner: exec
+ text: |
+ this is my exec banner
+ that contains a multiline
+ string
+ state: present
+- name: remove the motd banner
+ cisco.nxos.nxos_banner:
+ banner: motd
+ state: absent
+- name: Configure banner from file
+ cisco.nxos.nxos_banner:
+ banner: motd
+ text: "{{ lookup('file', './config_partial/raw_banner.cfg') }}"
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - banner exec
+ - this is my exec banner
+ - that contains a multiline
+ - string
+"""
+
+import re
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(module, command):
+ format = "text"
+ cmds = [{"command": command, "output": format}]
+ output = run_commands(module, cmds)
+ return output
+
+
+def map_obj_to_commands(want, have, module):
+ commands = list()
+ state = module.params["state"]
+ platform_regex = "Nexus.*Switch"
+
+ if state == "absent":
+ if have.get("text") and not (
+ (have.get("text") == "User Access Verification")
+ or re.match(platform_regex, have.get("text"))
+ ):
+ commands.append("no banner %s" % module.params["banner"])
+
+ elif state == "present" and want.get("text") != have.get("text"):
+ banner_cmd = "banner %s @\n%s\n@" % (
+ module.params["banner"],
+ want["text"],
+ )
+ commands.append(banner_cmd)
+
+ return commands
+
+
+def map_config_to_obj(module):
+ command = "show banner %s" % module.params["banner"]
+ output = execute_show_command(module, command)[0]
+
+ if "Invalid command" in output:
+ module.fail_json(
+ msg="banner: %s may not be supported on this platform. Possible values are : exec | motd"
+ % module.params["banner"],
+ )
+
+ if isinstance(output, dict):
+ output = list(output.values())
+ if output != []:
+ output = output[0]
+ else:
+ output = ""
+ if isinstance(output, dict):
+ output = list(output.values())
+ if output != []:
+ output = output[0]
+ else:
+ output = ""
+ else:
+ output = output.rstrip()
+
+ obj = {"banner": module.params["banner"], "state": "absent"}
+ if output:
+ obj["text"] = output
+ obj["state"] = "present"
+ return obj
+
+
+def map_params_to_obj(module):
+ text = module.params["text"]
+ return {
+ "banner": module.params["banner"],
+ "text": to_text(text) if text else None,
+ "state": module.params["state"],
+ }
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ banner=dict(required=True, choices=["exec", "motd"]),
+ text=dict(),
+ state=dict(default="present", choices=["present", "absent"]),
+ )
+
+ required_if = [("state", "present", ("text",))]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+ commands = map_obj_to_commands(want, have, module)
+ result["commands"] = commands
+
+ if commands:
+ if not module.check_mode:
+ msgs = load_config(module, commands, True)
+ if msgs:
+ for item in msgs:
+ if item:
+ if isinstance(item, dict):
+ err_str = item["clierror"]
+ else:
+ err_str = item
+ if "more than 40 lines" in err_str or "buffer overflowed" in err_str:
+ load_config(module, commands)
+
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_global.py
new file mode 100644
index 00000000..6ae1a88d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_global.py
@@ -0,0 +1,333 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bfd_global
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Bidirectional Forwarding Detection (BFD) global-level configuration
+description:
+- Manages Bidirectional Forwarding Detection (BFD) global-level configuration.
+version_added: 1.0.0
+author:
+- Chris Van Heuveln (@chrisvanheuveln)
+notes:
+- Tested against NXOSv 9.2(2)
+- Unsupported for Cisco MDS
+- BFD global will automatically enable 'feature bfd' if it is disabled.
+- BFD global does not have a 'state' parameter. All of the BFD commands are unique
+ and are defined if 'feature bfd' is enabled.
+options:
+ echo_interface:
+ description:
+ - Loopback interface used for echo frames.
+ - Valid values are loopback interface name or 'deleted'.
+ - Not supported on N5K/N6K
+ required: false
+ type: str
+ echo_rx_interval:
+ description:
+ - BFD Echo receive interval in milliseconds.
+ required: false
+ type: int
+ interval:
+ description:
+ - BFD interval timer values.
+ - Value must be a dict defining values for keys (tx, min_rx, and multiplier)
+ required: false
+ type: dict
+ slow_timer:
+ description:
+ - BFD slow rate timer in milliseconds.
+ required: false
+ type: int
+ startup_timer:
+ description:
+ - BFD delayed startup timer in seconds.
+ - Not supported on N5K/N6K/N7K
+ required: false
+ type: int
+ ipv4_echo_rx_interval:
+ description:
+ - BFD IPv4 session echo receive interval in milliseconds.
+ required: false
+ type: int
+ ipv4_interval:
+ description:
+ - BFD IPv4 interval timer values.
+ - Value must be a dict defining values for keys (tx, min_rx, and multiplier).
+ required: false
+ type: dict
+ ipv4_slow_timer:
+ description:
+ - BFD IPv4 slow rate timer in milliseconds.
+ required: false
+ type: int
+ ipv6_echo_rx_interval:
+ description:
+ - BFD IPv6 session echo receive interval in milliseconds.
+ required: false
+ type: int
+ ipv6_interval:
+ description:
+ - BFD IPv6 interval timer values.
+ - Value must be a dict defining values for keys (tx, min_rx, and multiplier).
+ required: false
+ type: dict
+ ipv6_slow_timer:
+ description:
+ - BFD IPv6 slow rate timer in milliseconds.
+ required: false
+ type: int
+ fabricpath_interval:
+ description:
+ - BFD fabricpath interval timer values.
+ - Value must be a dict defining values for keys (tx, min_rx, and multiplier).
+ required: false
+ type: dict
+ fabricpath_slow_timer:
+ description:
+ - BFD fabricpath slow rate timer in milliseconds.
+ required: false
+ type: int
+ fabricpath_vlan:
+ description:
+ - BFD fabricpath control vlan.
+ required: false
+ type: int
+"""
+EXAMPLES = """
+- cisco.nxos.nxos_bfd_global:
+ echo_interface: Ethernet1/2
+ echo_rx_interval: 50
+ interval:
+ tx: 50
+ min_rx: 50
+ multiplier: 4
+"""
+
+RETURN = """
+cmds:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["bfd echo-interface loopback1", "bfd slow-timer 2000"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ NxosCmdRef,
+ load_config,
+)
+
+
+BFD_CMD_REF = """
+# The cmd_ref is a yaml formatted list of module commands.
+# A leading underscore denotes a non-command variable; e.g. _template.
+# BFD does not have convenient json data so this cmd_ref uses raw cli configs.
+---
+_template: # _template holds common settings for all commands
+ # Enable feature bfd if disabled
+ feature: bfd
+ # Common get syntax for BFD commands
+ get_command: show run bfd all | incl '^(no )*bfd'
+
+echo_interface:
+ kind: str
+ getval: (no )*bfd echo-interface *(\\S+)*$
+ setval: 'bfd echo-interface {0}'
+ default: ~
+
+echo_rx_interval:
+ _exclude: ['N5K', 'N6K']
+ kind: int
+ getval: bfd echo-rx-interval (\\d+)$
+ setval: bfd echo-rx-interval {0}
+ default: 50
+ N3K:
+ default: 250
+
+interval:
+ kind: dict
+ getval: bfd interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
+ setval: bfd interval {tx} min_rx {min_rx} multiplier {multiplier}
+ default: &def_interval
+ tx: 50
+ min_rx: 50
+ multiplier: 3
+ N3K:
+ default: &n3k_def_interval
+ tx: 250
+ min_rx: 250
+ multiplier: 3
+
+slow_timer:
+ kind: int
+ getval: bfd slow-timer (\\d+)$
+ setval: bfd slow-timer {0}
+ default: 2000
+
+startup_timer:
+ _exclude: ['N5K', 'N6K', 'N7K']
+ kind: int
+ getval: bfd startup-timer (\\d+)$
+ setval: bfd startup-timer {0}
+ default: 5
+
+# IPv4/IPv6 specific commands
+ipv4_echo_rx_interval:
+ _exclude: ['N5K', 'N6K']
+ kind: int
+ getval: bfd ipv4 echo-rx-interval (\\d+)$
+ setval: bfd ipv4 echo-rx-interval {0}
+ default: 50
+ N3K:
+ default: 250
+
+ipv4_interval:
+ _exclude: ['N5K', 'N6K']
+ kind: dict
+ getval: bfd ipv4 interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
+ setval: bfd ipv4 interval {tx} min_rx {min_rx} multiplier {multiplier}
+ default: *def_interval
+ N3K:
+ default: *n3k_def_interval
+
+ipv4_slow_timer:
+ _exclude: ['N5K', 'N6K']
+ kind: int
+ getval: bfd ipv4 slow-timer (\\d+)$
+ setval: bfd ipv4 slow-timer {0}
+ default: 2000
+
+ipv6_echo_rx_interval:
+ _exclude: ['N35', 'N5K', 'N6K']
+ kind: int
+ getval: bfd ipv6 echo-rx-interval (\\d+)$
+ setval: bfd ipv6 echo-rx-interval {0}
+ default: 50
+ N3K:
+ default: 250
+
+ipv6_interval:
+ _exclude: ['N35', 'N5K', 'N6K']
+ kind: dict
+ getval: bfd ipv6 interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
+ setval: bfd ipv6 interval {tx} min_rx {min_rx} multiplier {multiplier}
+ default: *def_interval
+ N3K:
+ default: *n3k_def_interval
+
+ipv6_slow_timer:
+ _exclude: ['N35', 'N5K', 'N6K']
+ kind: int
+ getval: bfd ipv6 slow-timer (\\d+)$
+ setval: bfd ipv6 slow-timer {0}
+ default: 2000
+
+# Fabricpath Commands
+fabricpath_interval:
+ _exclude: ['N35', 'N3K', 'N9K']
+ kind: dict
+ getval: bfd fabricpath interval (?P<tx>\\d+) min_rx (?P<min_rx>\\d+) multiplier (?P<multiplier>\\d+)
+ setval: bfd fabricpath interval {tx} min_rx {min_rx} multiplier {multiplier}
+ default: *def_interval
+
+fabricpath_slow_timer:
+ _exclude: ['N35', 'N3K', 'N9K']
+ kind: int
+ getval: bfd fabricpath slow-timer (\\d+)$
+ setval: bfd fabricpath slow-timer {0}
+ default: 2000
+
+fabricpath_vlan:
+ _exclude: ['N35', 'N3K', 'N9K']
+ kind: int
+ getval: bfd fabricpath vlan (\\d+)$
+ setval: bfd fabricpath vlan {0}
+ default: 1
+"""
+
+
+def reorder_cmds(cmds):
+ """
+ There is a bug in some image versions where bfd echo-interface and
+ bfd echo-rx-interval need to be applied last for them to nvgen properly.
+ """
+ regex1 = re.compile(r"^bfd echo-interface")
+ regex2 = re.compile(r"^bfd echo-rx-interval")
+ filtered_cmds = [i for i in cmds if not regex1.match(i)]
+ filtered_cmds = [i for i in filtered_cmds if not regex2.match(i)]
+ echo_int_cmd = [i for i in cmds if regex1.match(i)]
+ echo_rx_cmd = [i for i in cmds if regex2.match(i)]
+ filtered_cmds.extend(echo_int_cmd)
+ filtered_cmds.extend(echo_rx_cmd)
+
+ return filtered_cmds
+
+
+def main():
+ argument_spec = dict(
+ echo_interface=dict(required=False, type="str"),
+ echo_rx_interval=dict(required=False, type="int"),
+ interval=dict(required=False, type="dict"),
+ slow_timer=dict(required=False, type="int"),
+ startup_timer=dict(required=False, type="int"),
+ ipv4_echo_rx_interval=dict(required=False, type="int"),
+ ipv4_interval=dict(required=False, type="dict"),
+ ipv4_slow_timer=dict(required=False, type="int"),
+ ipv6_echo_rx_interval=dict(required=False, type="int"),
+ ipv6_interval=dict(required=False, type="dict"),
+ ipv6_slow_timer=dict(required=False, type="int"),
+ fabricpath_interval=dict(required=False, type="dict"),
+ fabricpath_slow_timer=dict(required=False, type="int"),
+ fabricpath_vlan=dict(required=False, type="int"),
+ )
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ warnings = list()
+
+ cmd_ref = NxosCmdRef(module, BFD_CMD_REF)
+ cmd_ref.get_existing()
+ cmd_ref.get_playvals()
+ cmds = reorder_cmds(cmd_ref.get_proposed())
+
+ result = {
+ "changed": False,
+ "commands": cmds,
+ "warnings": warnings,
+ "check_mode": module.check_mode,
+ }
+ if cmds:
+ result["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_interfaces.py
new file mode 100644
index 00000000..1790f8e0
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bfd_interfaces.py
@@ -0,0 +1,302 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_bfd_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bfd_interfaces
+short_description: BFD interfaces resource module
+description: Manages attributes of Bidirectional Forwarding Detection (BFD) on the
+ interface.
+version_added: 1.0.0
+author: Chris Van Heuveln (@chrisvanheuveln)
+notes:
+- Tested against NX-OS 7.0(3)I5(1).
+- Unsupported for Cisco MDS
+- Feature bfd should be enabled for this module.
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^interface|^feature
+ bfd').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: The provided configuration
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ type: str
+ description: The name of the interface.
+ bfd:
+ type: str
+ description:
+ - Enable/Disable Bidirectional Forwarding Detection (BFD) on the interface.
+ choices:
+ - enable
+ - disable
+ echo:
+ type: str
+ description:
+ - Enable/Disable BFD Echo functionality on the interface.
+ choices:
+ - enable
+ - disable
+ state:
+ description:
+ - The state of the configuration after module completion
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using deleted
+
+- name: Configure interfaces
+ cisco.nxos.nxos_bfd_interfaces:
+ state: deleted
+
+
+# Using merged
+
+- name: Configure interfaces
+ cisco.nxos.nxos_bfd_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ echo: enable
+ - name: Ethernet1/2
+ bfd: disable
+ echo: disable
+ state: merged
+
+
+# Using overridden
+
+- name: Configure interfaces
+ cisco.nxos.nxos_bfd_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ echo: enable
+ - name: Ethernet1/2
+ bfd: disable
+ echo: disable
+ state: overridden
+
+
+# Using replaced
+
+- name: Configure interfaces
+ cisco.nxos.nxos_bfd_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ echo: enable
+ - name: Ethernet1/2
+ bfd: disable
+ echo: disable
+ state: replaced
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_bfd_interfaces:
+ config:
+ - name: Ethernet1/800
+ bfd: enable
+ echo: enable
+ - name: Ethernet1/801
+ bfd: disable
+ echo: disable
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/800"
+# - "bfd"
+# - "bfd echo"
+# - "interface Ethernet1/801"
+# - "no bfd"
+# - "no bfd echo"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+
+# feature bfd
+# interface Ethernet1/800
+# no switchport
+# no bfd
+# no bfd echo
+# interface Ethernet1/801
+# no switchport
+# no bfd
+# interface Ethernet1/802
+# no switchport
+# no bfd echo
+# interface mgmt0
+# ip address dhcp
+# vrf member management
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_bfd_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - bfd: disable
+# echo: disable
+# name: Ethernet1/800
+# - bfd: disable
+# echo: enable
+# name: Ethernet1/801
+# - bfd: enable
+# echo: disable
+# name: Ethernet1/802
+# - bfd: enable
+# echo: enable
+# name: mgmt0
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+
+# feature bfd
+# interface Ethernet1/1
+# no switchport
+# no bfd
+# interface Ethernet1/2
+# no switchport
+# no bfd echo
+# interface mgmt0
+# ip address dhcp
+# vrf member management
+
+- name: Gather bfd_interfaces facts from the device using nxos_bfd_interfaces
+ cisco.nxos.nxos_bfd_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# - name: Ethernet1/1
+# bfd: disable
+# echo: enable
+# - name: Ethernet1/3
+# echo: disable
+# bfd: enable
+# - name: mgmt0
+# bfd: enable
+# echo: enable
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet1/1', 'no bfd', 'no bfd echo']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bfd_interfaces.bfd_interfaces import (
+ Bfd_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bfd_interfaces.bfd_interfaces import (
+ Bfd_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Bfd_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Bfd_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp.py
new file mode 100644
index 00000000..2c56f412
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp.py
@@ -0,0 +1,761 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bgp
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2023-01-27) Manages BGP configuration.
+description:
+- Manages BGP configurations on NX-OS switches.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+deprecated:
+ alternative: nxos_bgp_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2023-01-27'
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) removes the whole BGP ASN configuration when C(vrf=default) or the
+ whole VRF instance within the BGP process when using a different VRF.
+- Default when supported restores params default value.
+- Configuring global params is only permitted if C(vrf=default).
+options:
+ asn:
+ description:
+ - BGP autonomous system number. Valid values are String, Integer in ASPLAIN or
+ ASDOT notation.
+ required: true
+ type: str
+ vrf:
+ description:
+ - Name of the VRF. The name 'default' is a valid VRF representing the global BGP.
+ default: 'default'
+ type: str
+ bestpath_always_compare_med:
+ description:
+ - Enable/Disable MED comparison on paths from different autonomous systems.
+ type: bool
+ bestpath_aspath_multipath_relax:
+ description:
+ - Enable/Disable load sharing across the providers with different (but equal-length)
+ AS paths.
+ type: bool
+ bestpath_compare_routerid:
+ description:
+ - Enable/Disable comparison of router IDs for identical eBGP paths.
+ type: bool
+ bestpath_compare_neighborid:
+ description:
+ - Enable/Disable neighborid. Use this when more paths available than max path
+ config.
+ type: bool
+ bestpath_cost_community_ignore:
+ description:
+ - Enable/Disable Ignores the cost community for BGP best-path calculations.
+ type: bool
+ bestpath_med_confed:
+ description:
+ - Enable/Disable enforcement of bestpath to do a MED comparison only between paths
+ originated within a confederation.
+ type: bool
+ bestpath_med_missing_as_worst:
+ description:
+ - Enable/Disable assigns the value of infinity to received routes that do not
+ carry the MED attribute, making these routes the least desirable.
+ type: bool
+ bestpath_med_non_deterministic:
+ description:
+ - Enable/Disable deterministic selection of the best MED pat from among the paths
+ from the same autonomous system.
+ type: bool
+ cluster_id:
+ description:
+ - Route Reflector Cluster-ID.
+ type: str
+ confederation_id:
+ description:
+ - Routing domain confederation AS.
+ type: str
+ confederation_peers:
+ description:
+ - AS confederation parameters.
+ type: list
+ elements: str
+ disable_policy_batching:
+ description:
+ - Enable/Disable the batching evaluation of prefix advertisement to all peers.
+ type: bool
+ disable_policy_batching_ipv4_prefix_list:
+ description:
+ - Enable/Disable the batching evaluation of prefix advertisements to all peers
+ with prefix list.
+ type: str
+ disable_policy_batching_ipv6_prefix_list:
+ description:
+ - Enable/Disable the batching evaluation of prefix advertisements to all peers
+ with prefix list.
+ type: str
+ enforce_first_as:
+ description:
+ - Enable/Disable enforces the neighbor autonomous system to be the first AS number
+ listed in the AS path attribute for eBGP. On NX-OS, this property is only supported
+ in the global BGP context.
+ type: bool
+ event_history_cli:
+ description:
+ - Enable/Disable cli event history buffer.
+ choices:
+ - size_small
+ - size_medium
+ - size_large
+ - size_disable
+ - default
+ - 'true'
+ - 'false'
+ type: str
+ event_history_detail:
+ description:
+ - Enable/Disable detail event history buffer.
+ choices:
+ - size_small
+ - size_medium
+ - size_large
+ - size_disable
+ - default
+ - 'true'
+ - 'false'
+ type: str
+ event_history_events:
+ description:
+ - Enable/Disable event history buffer.
+ choices:
+ - size_small
+ - size_medium
+ - size_large
+ - size_disable
+ - default
+ - 'true'
+ - 'false'
+ type: str
+ event_history_periodic:
+ description:
+ - Enable/Disable periodic event history buffer.
+ choices:
+ - size_small
+ - size_medium
+ - size_large
+ - size_disable
+ - default
+ - 'true'
+ - 'false'
+ type: str
+ fast_external_fallover:
+ description:
+ - Enable/Disable immediately reset the session if the link to a directly connected
+ BGP peer goes down. Only supported in the global BGP context.
+ type: bool
+ flush_routes:
+ description:
+ - Enable/Disable flush routes in RIB upon controlled restart. On NX-OS, this property
+ is only supported in the global BGP context.
+ type: bool
+ graceful_restart:
+ description:
+ - Enable/Disable graceful restart.
+ type: bool
+ graceful_restart_helper:
+ description:
+ - Enable/Disable graceful restart helper mode.
+ type: bool
+ graceful_restart_timers_restart:
+ description:
+ - Set maximum time for a restart sent to the BGP peer.
+ type: str
+ graceful_restart_timers_stalepath_time:
+ description:
+ - Set maximum time that BGP keeps the stale routes from the restarting BGP peer.
+ type: str
+ isolate:
+ description:
+ - Enable/Disable isolate this router from BGP perspective.
+ type: bool
+ local_as:
+ description:
+ - Local AS number to be used within a VRF instance.
+ type: str
+ log_neighbor_changes:
+ description:
+ - Enable/Disable message logging for neighbor up/down event.
+ type: bool
+ maxas_limit:
+ description:
+ - Specify Maximum number of AS numbers allowed in the AS-path attribute. Valid
+ values are between 1 and 512.
+ type: str
+ neighbor_down_fib_accelerate:
+ description:
+ - Enable/Disable handle BGP neighbor down event, due to various reasons.
+ type: bool
+ reconnect_interval:
+ description:
+ - The BGP reconnection interval for dropped sessions. Valid values are between
+ 1 and 60.
+ type: str
+ router_id:
+ description:
+ - Router Identifier (ID) of the BGP router VRF instance.
+ type: str
+ shutdown:
+ description:
+ - Administratively shutdown the BGP protocol.
+ type: bool
+ suppress_fib_pending:
+ description:
+ - Enable/Disable advertise only routes programmed in hardware to peers.
+ type: bool
+ timer_bestpath_limit:
+ description:
+ - Specify timeout for the first best path after a restart, in seconds.
+ type: str
+ timer_bgp_hold:
+ description:
+ - Set BGP hold timer.
+ type: str
+ timer_bgp_keepalive:
+ description:
+ - Set BGP keepalive timer.
+ type: str
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+
+EXAMPLES = """
+- name: Configure a simple ASN
+ cisco.nxos.nxos_bgp:
+ asn: 65535
+ vrf: test
+ router_id: 192.0.2.1
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["router bgp 65535", "vrf test", "router-id 192.0.2.1"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+BOOL_PARAMS = [
+ "bestpath_always_compare_med",
+ "bestpath_aspath_multipath_relax",
+ "bestpath_compare_neighborid",
+ "bestpath_compare_routerid",
+ "bestpath_cost_community_ignore",
+ "bestpath_med_confed",
+ "bestpath_med_missing_as_worst",
+ "bestpath_med_non_deterministic",
+ "disable_policy_batching",
+ "enforce_first_as",
+ "fast_external_fallover",
+ "flush_routes",
+ "graceful_restart",
+ "graceful_restart_helper",
+ "isolate",
+ "log_neighbor_changes",
+ "neighbor_down_fib_accelerate",
+ "shutdown",
+ "suppress_fib_pending",
+]
+GLOBAL_PARAMS = [
+ "disable_policy_batching",
+ "disable_policy_batching_ipv4_prefix_list",
+ "disable_policy_batching_ipv6_prefix_list",
+ "enforce_first_as",
+ "event_history_cli",
+ "event_history_detail",
+ "event_history_events",
+ "event_history_periodic",
+ "fast_external_fallover",
+ "flush_routes",
+ "isolate",
+ "suppress_fib_pending",
+ "shutdown",
+]
+PARAM_TO_DEFAULT_KEYMAP = {
+ "timer_bgp_keepalive": "60",
+ "timer_bgp_hold": "180",
+ "timer_bestpath_limit": "300",
+ "graceful_restart": True,
+ "graceful_restart_timers_restart": "120",
+ "graceful_restart_timers_stalepath_time": "300",
+ "reconnect_interval": "60",
+ "suppress_fib_pending": True,
+ "fast_external_fallover": True,
+ "enforce_first_as": True,
+ "event_history_cli": True,
+ "event_history_detail": False,
+ "event_history_events": True,
+ "event_history_periodic": True,
+ "maxas_limit": "",
+ "router_id": "",
+ "cluster_id": "",
+ "disable_policy_batching_ipv4_prefix_list": "",
+ "disable_policy_batching_ipv6_prefix_list": "",
+ "local_as": "",
+ "confederation_id": "",
+}
+PARAM_TO_COMMAND_KEYMAP = {
+ "asn": "router bgp",
+ "bestpath_always_compare_med": "bestpath always-compare-med",
+ "bestpath_aspath_multipath_relax": "bestpath as-path multipath-relax",
+ "bestpath_compare_neighborid": "bestpath compare-neighborid",
+ "bestpath_compare_routerid": "bestpath compare-routerid",
+ "bestpath_cost_community_ignore": "bestpath cost-community ignore",
+ "bestpath_med_confed": "bestpath med confed",
+ "bestpath_med_missing_as_worst": "bestpath med missing-as-worst",
+ "bestpath_med_non_deterministic": "bestpath med non-deterministic",
+ "cluster_id": "cluster-id",
+ "confederation_id": "confederation identifier",
+ "confederation_peers": "confederation peers",
+ "disable_policy_batching": "disable-policy-batching",
+ "disable_policy_batching_ipv4_prefix_list": "disable-policy-batching ipv4 prefix-list",
+ "disable_policy_batching_ipv6_prefix_list": "disable-policy-batching ipv6 prefix-list",
+ "enforce_first_as": "enforce-first-as",
+ "event_history_cli": "event-history cli",
+ "event_history_detail": "event-history detail",
+ "event_history_events": "event-history events",
+ "event_history_periodic": "event-history periodic",
+ "fast_external_fallover": "fast-external-fallover",
+ "flush_routes": "flush-routes",
+ "graceful_restart": "graceful-restart",
+ "graceful_restart_helper": "graceful-restart-helper",
+ "graceful_restart_timers_restart": "graceful-restart restart-time",
+ "graceful_restart_timers_stalepath_time": "graceful-restart stalepath-time",
+ "isolate": "isolate",
+ "local_as": "local-as",
+ "log_neighbor_changes": "log-neighbor-changes",
+ "maxas_limit": "maxas-limit",
+ "neighbor_down_fib_accelerate": "neighbor-down fib-accelerate",
+ "reconnect_interval": "reconnect-interval",
+ "router_id": "router-id",
+ "shutdown": "shutdown",
+ "suppress_fib_pending": "suppress-fib-pending",
+ "timer_bestpath_limit": "timers bestpath-limit",
+ "timer_bgp_hold": "timers bgp",
+ "timer_bgp_keepalive": "timers bgp",
+ "vrf": "vrf",
+}
+
+
+def get_value(arg, config):
+ command = PARAM_TO_COMMAND_KEYMAP.get(arg)
+
+ if command.split()[0] == "event-history":
+ has_size = re.search(r"^\s+{0} size\s(?P<value>.*)$".format(command), config, re.M)
+
+ if command == "event-history detail":
+ value = False
+ else:
+ value = "size_small"
+
+ if has_size:
+ value = "size_%s" % has_size.group("value")
+
+ elif arg in ["enforce_first_as", "fast_external_fallover"]:
+ no_command_re = re.compile(r"no\s+{0}\s*".format(command), re.M)
+ value = True
+
+ if no_command_re.search(config):
+ value = False
+
+ elif arg in BOOL_PARAMS:
+ has_command = re.search(r"^\s+{0}\s*$".format(command), config, re.M)
+ value = False
+
+ if has_command:
+ value = True
+ else:
+ command_val_re = re.compile(r"(?:{0}\s)(?P<value>.*)".format(command), re.M)
+ value = ""
+
+ has_command = command_val_re.search(config)
+ if has_command:
+ found_value = has_command.group("value")
+
+ if arg == "confederation_peers":
+ value = found_value.split()
+ elif arg == "timer_bgp_keepalive":
+ value = found_value.split()[0]
+ elif arg == "timer_bgp_hold":
+ split_values = found_value.split()
+ if len(split_values) == 2:
+ value = split_values[1]
+ elif found_value:
+ value = found_value
+
+ return value
+
+
+def get_existing(module, args, warnings):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module, flags=["bgp all"]))
+
+ asn_re = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.S)
+ asn_match = asn_re.match(str(netcfg))
+
+ if asn_match:
+ existing_asn = asn_match.group("existing_asn")
+ bgp_parent = "router bgp {0}".format(existing_asn)
+
+ if module.params["vrf"] != "default":
+ parents = [bgp_parent, "vrf {0}".format(module.params["vrf"])]
+ else:
+ parents = [bgp_parent]
+
+ config = netcfg.get_section(parents)
+ if config:
+ for arg in args:
+ if arg != "asn" and (module.params["vrf"] == "default" or arg not in GLOBAL_PARAMS):
+ existing[arg] = get_value(arg, config)
+
+ existing["asn"] = existing_asn
+ if module.params["vrf"] == "default":
+ existing["vrf"] = "default"
+
+ if not existing and module.params["vrf"] != "default" and module.params["state"] == "present":
+ msg = "VRF {0} doesn't exist.".format(module.params["vrf"])
+ warnings.append(msg)
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key in table:
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = table.get(key)
+
+ return new_dict
+
+
+def state_present(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+
+ for key, value in proposed_commands.items():
+ if value is True:
+ commands.append(key)
+ elif value is False:
+ commands.append("no {0}".format(key))
+ elif value == "default":
+ default_value = PARAM_TO_DEFAULT_KEYMAP.get(key)
+ existing_value = existing_commands.get(key)
+
+ if default_value:
+ commands.append("{0} {1}".format(key, default_value))
+ elif existing_value:
+ if key == "confederation peers":
+ existing_value = " ".join(existing_value)
+ commands.append("no {0} {1}".format(key, existing_value))
+ elif not value:
+ existing_value = existing_commands.get(key)
+ if existing_value:
+ commands.append("no {0} {1}".format(key, existing_value))
+ elif key == "confederation peers":
+ commands.append("{0} {1}".format(key, value))
+ elif key.startswith("timers bgp"):
+ command = "timers bgp {0} {1}".format(
+ proposed["timer_bgp_keepalive"],
+ proposed["timer_bgp_hold"],
+ )
+ if command not in commands:
+ commands.append(command)
+ else:
+ if value.startswith("size"):
+ value = value.replace("_", " ")
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+
+ parents = []
+ if commands:
+ commands = fix_commands(commands)
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+ elif proposed:
+ if module.params["vrf"] != "default":
+ commands.append("vrf {0}".format(module.params["vrf"]))
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ else:
+ commands.append("router bgp {0}".format(module.params["asn"]))
+
+ candidate.add(commands, parents=parents)
+
+
+def state_absent(module, existing, candidate):
+ commands = []
+ parents = []
+ if module.params["vrf"] == "default":
+ commands.append("no router bgp {0}".format(module.params["asn"]))
+ elif existing.get("vrf") == module.params["vrf"]:
+ commands.append("no vrf {0}".format(module.params["vrf"]))
+ parents = ["router bgp {0}".format(module.params["asn"])]
+
+ candidate.add(commands, parents=parents)
+
+
+def fix_commands(commands):
+ local_as_command = ""
+ confederation_id_command = ""
+ confederation_peers_command = ""
+
+ for command in commands:
+ if "local-as" in command:
+ local_as_command = command
+ elif "confederation identifier" in command:
+ confederation_id_command = command
+ elif "confederation peers" in command:
+ confederation_peers_command = command
+
+ if local_as_command and confederation_id_command:
+ if "no" in confederation_id_command:
+ commands.pop(commands.index(local_as_command))
+ commands.pop(commands.index(confederation_id_command))
+ commands.append(confederation_id_command)
+ commands.append(local_as_command)
+ else:
+ commands.pop(commands.index(local_as_command))
+ commands.pop(commands.index(confederation_id_command))
+ commands.append(local_as_command)
+ commands.append(confederation_id_command)
+
+ if confederation_peers_command and confederation_id_command:
+ if local_as_command:
+ if "no" in local_as_command:
+ commands.pop(commands.index(local_as_command))
+ commands.pop(commands.index(confederation_id_command))
+ commands.pop(commands.index(confederation_peers_command))
+ commands.append(confederation_id_command)
+ commands.append(confederation_peers_command)
+ commands.append(local_as_command)
+ else:
+ commands.pop(commands.index(local_as_command))
+ commands.pop(commands.index(confederation_id_command))
+ commands.pop(commands.index(confederation_peers_command))
+ commands.append(local_as_command)
+ commands.append(confederation_id_command)
+ commands.append(confederation_peers_command)
+ else:
+ commands.pop(commands.index(confederation_peers_command))
+ commands.pop(commands.index(confederation_id_command))
+ commands.append(confederation_id_command)
+ commands.append(confederation_peers_command)
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ asn=dict(required=True, type="str"),
+ vrf=dict(required=False, type="str", default="default"),
+ bestpath_always_compare_med=dict(required=False, type="bool"),
+ bestpath_aspath_multipath_relax=dict(required=False, type="bool"),
+ bestpath_compare_neighborid=dict(required=False, type="bool"),
+ bestpath_compare_routerid=dict(required=False, type="bool"),
+ bestpath_cost_community_ignore=dict(required=False, type="bool"),
+ bestpath_med_confed=dict(required=False, type="bool"),
+ bestpath_med_missing_as_worst=dict(required=False, type="bool"),
+ bestpath_med_non_deterministic=dict(required=False, type="bool"),
+ cluster_id=dict(required=False, type="str"),
+ confederation_id=dict(required=False, type="str"),
+ confederation_peers=dict(required=False, type="list", elements="str"),
+ disable_policy_batching=dict(required=False, type="bool"),
+ disable_policy_batching_ipv4_prefix_list=dict(required=False, type="str"),
+ disable_policy_batching_ipv6_prefix_list=dict(required=False, type="str"),
+ enforce_first_as=dict(required=False, type="bool"),
+ event_history_cli=dict(
+ required=False,
+ choices=[
+ "true",
+ "false",
+ "default",
+ "size_small",
+ "size_medium",
+ "size_large",
+ "size_disable",
+ ],
+ ),
+ event_history_detail=dict(
+ required=False,
+ choices=[
+ "true",
+ "false",
+ "default",
+ "size_small",
+ "size_medium",
+ "size_large",
+ "size_disable",
+ ],
+ ),
+ event_history_events=dict(
+ required=False,
+ choices=[
+ "true",
+ "false",
+ "default",
+ "size_small",
+ "size_medium",
+ "size_large",
+ "size_disable",
+ ],
+ ),
+ event_history_periodic=dict(
+ required=False,
+ choices=[
+ "true",
+ "false",
+ "default",
+ "size_small",
+ "size_medium",
+ "size_large",
+ "size_disable",
+ ],
+ ),
+ fast_external_fallover=dict(required=False, type="bool"),
+ flush_routes=dict(required=False, type="bool"),
+ graceful_restart=dict(required=False, type="bool"),
+ graceful_restart_helper=dict(required=False, type="bool"),
+ graceful_restart_timers_restart=dict(required=False, type="str"),
+ graceful_restart_timers_stalepath_time=dict(required=False, type="str"),
+ isolate=dict(required=False, type="bool"),
+ local_as=dict(required=False, type="str"),
+ log_neighbor_changes=dict(required=False, type="bool"),
+ maxas_limit=dict(required=False, type="str"),
+ neighbor_down_fib_accelerate=dict(required=False, type="bool"),
+ reconnect_interval=dict(required=False, type="str"),
+ router_id=dict(required=False, type="str"),
+ shutdown=dict(required=False, type="bool"),
+ suppress_fib_pending=dict(required=False, type="bool"),
+ timer_bestpath_limit=dict(required=False, type="str"),
+ timer_bgp_hold=dict(required=False, type="str"),
+ timer_bgp_keepalive=dict(required=False, type="str"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_together=[["timer_bgp_hold", "timer_bgp_keepalive"]],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = dict(changed=False, warnings=warnings)
+
+ state = module.params["state"]
+
+ if module.params["vrf"] != "default":
+ for param in GLOBAL_PARAMS:
+ if module.params[param]:
+ module.fail_json(
+ msg='Global params can be modified only under "default" VRF.',
+ vrf=module.params["vrf"],
+ global_param=param,
+ )
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing = get_existing(module, args, warnings)
+
+ if existing.get("asn") and state == "present":
+ if existing.get("asn") != module.params["asn"]:
+ module.fail_json(
+ msg="Another BGP ASN already exists.",
+ proposed_asn=module.params["asn"],
+ existing_asn=existing.get("asn"),
+ )
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key not in ["asn", "vrf"]:
+ if str(value).lower() == "default":
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default")
+ if key == "confederation_peers":
+ if value[0] == "default":
+ if existing.get(key):
+ proposed[key] = "default"
+ else:
+ v = set([int(i) for i in value])
+ ex = set([int(i) for i in existing.get(key)])
+ if v != ex:
+ proposed[key] = " ".join(str(s) for s in v)
+ else:
+ if existing.get(key) != value:
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present":
+ state_present(module, existing, proposed, candidate)
+ elif existing.get("asn") == module.params["asn"]:
+ state_absent(module, existing, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ if not module.check_mode:
+ load_config(module, candidate)
+ result["changed"] = True
+ result["commands"] = candidate
+ else:
+ result["commands"] = []
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_address_family.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_address_family.py
new file mode 100644
index 00000000..c838d25f
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_address_family.py
@@ -0,0 +1,1031 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_bgp_address_family
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_bgp_address_family
+short_description: BGP Address Family resource module.
+description:
+- This module manages BGP Address Family configuration on devices running Cisco NX-OS.
+version_added: 2.0.0
+notes:
+- Tested against NX-OS 9.3.6.
+- Unsupported for Cisco MDS
+- For managing BGP neighbor address family configurations please use
+ the M(cisco.nxos.nxos_bgp_neighbor_address_family) module.
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^router bgp').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of BGP process configuration.
+ type: dict
+ suboptions:
+ as_number:
+ description: Autonomous System Number of the router.
+ type: str
+ address_family:
+ description: Address Family related configurations.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: Address Family indicator.
+ type: str
+ choices: ["ipv4", "ipv6", "link-state", "vpnv4", "vpnv6", "l2vpn"]
+ required: True
+ safi:
+ description: Sub Address Family indicator.
+ type: str
+ choices: ["unicast", "multicast", "mvpn", "evpn"]
+ additional_paths:
+ description: Additional paths configuration.
+ type: dict
+ suboptions:
+ install_backup:
+ description: Install backup path.
+ type: bool
+ receive:
+ description: Additional paths Receive capability.
+ type: bool
+ selection:
+ description: Additional paths selection
+ type: dict
+ suboptions:
+ route_map:
+ description: Route-map for additional paths selection
+ type: str
+ send:
+ description: Additional paths Send capability
+ type: bool
+ advertise_pip:
+ description: Advertise physical ip for type-5 route.
+ type: bool
+ advertise_l2vpn_evpn:
+ description: Enable advertising EVPN routes.
+ type: bool
+ advertise_system_mac:
+ description: Advertise extra EVPN RT-2 with system MAC.
+ type: bool
+ allow_vni_in_ethertag:
+ description: Allow VNI in Ethernet Tag field in EVPN route.
+ type: bool
+ aggregate_address:
+ description: Configure BGP aggregate prefixes
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description: Aggregate prefix.
+ type: str
+ advertise_map:
+ description: Select attribute information from specific routes.
+ type: str
+ as_set:
+ description: Generate AS-SET information.
+ type: bool
+ attribute_map:
+ description: Set attribute information of aggregate.
+ type: str
+ summary_only:
+ description: Do not advertise more specifics.
+ type: bool
+ suppress_map:
+ description: Conditionally filter more specific routes.
+ type: str
+ client_to_client:
+ description: Configure client-to-client route reflection.
+ type: dict
+ suboptions:
+ no_reflection:
+ description: Reflection of routes permitted.
+ type: bool
+ dampen_igp_metric:
+ description: Dampen IGP metric-related changes.
+ type: int
+ dampening:
+ description: Configure route flap dampening.
+ type: dict
+ suboptions:
+ set:
+ description: Set route flap dampening.
+ type: bool
+ decay_half_life:
+ description: Decay half life.
+ type: int
+ start_reuse_route:
+ description: Value to start reusing a route.
+ type: int
+ start_suppress_route:
+ description: Value to start suppressing a route.
+ type: int
+ max_suppress_time:
+ description: Maximum suppress time for stable route.
+ type: int
+ route_map:
+ description: Apply route-map to specify dampening criteria.
+ type: str
+ default_information:
+ description: Control distribution of default information.
+ type: dict
+ suboptions:
+ originate:
+ description: Distribute a default route.
+ type: bool
+ default_metric:
+ description: Set metric of redistributed routes.
+ type: int
+ distance:
+ description: Configure administrative distance.
+ type: dict
+ suboptions:
+ ebgp_routes:
+ description: Distance for EBGP routes.
+ type: int
+ ibgp_routes:
+ description: Distance for IBGP routes.
+ type: int
+ local_routes:
+ description: Distance for local routes.
+ type: int
+ export_gateway_ip:
+ description: Export Gateway IP to Type-5 EVPN routes for VRF
+ type: bool
+ inject_map:
+ description: Routemap which specifies prefixes to inject.
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description: Route-map name.
+ type: str
+ exist_map:
+ description: Routemap which specifies exist condition.
+ type: str
+ copy_attributes:
+ description: Copy attributes from aggregate.
+ type: bool
+ maximum_paths:
+ description: Forward packets over multipath paths.
+ type: dict
+ suboptions:
+ parallel_paths:
+ description: Number of parallel paths.
+ type: int
+ ibgp:
+ description: Configure multipath for IBGP paths.
+ type: dict
+ suboptions:
+ parallel_paths:
+ description: Number of parallel paths.
+ type: int
+ eibgp:
+ description: Configure multipath for both EBGP and IBGP paths.
+ type: dict
+ suboptions:
+ parallel_paths:
+ description: Number of parallel paths.
+ type: int
+ local:
+ description: Configure multipath for local paths.
+ type: dict
+ suboptions:
+ parallel_paths:
+ description: Number of parallel paths.
+ type: int
+ mixed:
+ description: Configure multipath for local and remote paths.
+ type: dict
+ suboptions:
+ parallel_paths:
+ description: Number of parallel paths.
+ type: int
+ networks:
+ description: Configure an IP prefix to advertise.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description: IP prefix in CIDR format.
+ type: str
+ route_map:
+ description: Route-map name.
+ type: str
+ nexthop:
+ description: Nexthop tracking.
+ type: dict
+ suboptions:
+ route_map:
+ description: Route-map name.
+ type: str
+ trigger_delay:
+ description: Set the delay to trigger nexthop tracking.
+ type: dict
+ suboptions:
+ critical_delay:
+ description:
+ - Nexthop changes affecting reachability.
+ - Delay value (miliseconds).
+ type: int
+ non_critical_delay:
+ description:
+ - Other nexthop changes.
+ - Delay value (miliseconds).
+ type: int
+ redistribute:
+ description: Configure redistribution.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - The name of the protocol.
+ type: str
+ choices: ["am", "direct", "eigrp", "isis", "lisp", "ospf", "ospfv3", "rip", "static", "hmm"]
+ required: true
+ id:
+ description:
+ - The identifier for the protocol specified.
+ type: str
+ route_map:
+ description:
+ - The route map policy to constrain redistribution.
+ type: str
+ required: true
+ retain:
+ description: Retain the routes based on Target VPN Extended Communities.
+ type: dict
+ suboptions:
+ route_target:
+ description: Specify Target VPN Extended Communities
+ type: dict
+ suboptions:
+ retain_all:
+ description: All the routes regardless of Target-VPN community
+ type: bool
+ route_map:
+ description: Apply route-map to filter routes.
+ type: str
+ suppress_inactive:
+ description: Advertise only active routes to peers.
+ type: bool
+ table_map:
+ description:
+ - Policy for filtering/modifying OSPF routes before sending them to RIB.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The Route Map name.
+ type: str
+ required: true
+ filter:
+ description:
+ - Block the OSPF routes from being sent to RIB.
+ type: bool
+ timers:
+ description: Configure bgp related timers.
+ type: dict
+ suboptions:
+ bestpath_defer:
+ description: Configure bestpath defer timer value for batch prefix processing.
+ type: dict
+ suboptions:
+ defer_time:
+ description: Bestpath defer time (mseconds).
+ type: int
+ maximum_defer_time:
+ description: Maximum bestpath defer time (mseconds).
+ type: int
+ wait_igp_convergence:
+ description: Delay initial bestpath until redistributed IGPs have converged.
+ type: bool
+ vrf:
+ description: Virtual Router Context.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - State I(deleted) only removes BGP attributes that this modules
+ manages and does not negate the BGP process completely.
+ - Refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# Nexus9000v#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_bgp_address_family:
+ config:
+ as_number: 65536
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ networks:
+ - prefix: 192.0.2.32/27
+ - prefix: 192.0.2.64/27
+ route_map: rmap1
+ nexthop:
+ route_map: rmap2
+ trigger_delay:
+ critical_delay: 120
+ non_critical_delay: 180
+ - afi: ipv4
+ safi: unicast
+ vrf: site-1
+ default_information:
+ originate: True
+ aggregate_address:
+ - prefix: 203.0.113.0/24
+ as_set: True
+ summary_only: True
+ - afi: ipv6
+ safi: multicast
+ vrf: site-1
+ redistribute:
+ - protocol: ospfv3
+ id: 100
+ route_map: rmap-ospf-1
+ - protocol: eigrp
+ id: 101
+ route_map: rmap-eigrp-1
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - router bgp 65536
+# - address-family ipv4 multicast
+# - nexthop route-map rmap2
+# - nexthop trigger-delay critical 120 non-critical 180
+# - network 192.0.2.32/27
+# - network 192.0.2.64/27 route-map rmap1
+# - vrf site-1
+# - address-family ipv4 unicast
+# - default-information originate
+# - aggregate-address 203.0.113.0/24 as-set summary-only
+# - address-family ipv6 multicast
+# - redistribute ospfv3 100 route-map rmap-ospf-1
+# - redistribute eigrp 101 route-map rmap-eigrp-1
+#
+# after:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+#
+
+# Using replaced
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+- name: Replace configuration of specified AFs
+ cisco.nxos.nxos_bgp_address_family:
+ config:
+ as_number: 65536
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ networks:
+ - prefix: 192.0.2.64/27
+ route_map: rmap1
+ nexthop:
+ route_map: rmap2
+ trigger_delay:
+ critical_delay: 120
+ non_critical_delay: 180
+ aggregate_address:
+ - prefix: 203.0.113.0/24
+ as_set: True
+ summary_only: True
+ - afi: ipv4
+ safi: unicast
+ vrf: site-1
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+#
+# commands:
+# - router bgp 65536
+# - address-family ipv4 multicast
+# - no network 192.0.2.32/27
+# - aggregate-address 203.0.113.0/24 as-set summary-only
+# - vrf site-1
+# - address-family ipv4 unicast
+# - no default-information originate
+# - no aggregate-address 203.0.113.0/24 as-set summary-only
+#
+# after:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+#
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+#
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - protocol: ospfv3
+# id: "100"
+# route_map: rmap-ospf-1
+# - protocol: eigrp
+# id: "101"
+# route_map: rmap-eigrp-1
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.64/27 route-map rmap1
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# vrf site-1
+# address-family ipv4 unicast
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+# Using overridden
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+- name: Override all BGP AF configuration with provided configuration
+ cisco.nxos.nxos_bgp_address_family: &overridden
+ config:
+ as_number: 65536
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ networks:
+ - prefix: 192.0.2.64/27
+ route_map: rmap1
+ aggregate_address:
+ - prefix: 203.0.113.0/24
+ as_set: True
+ summary_only: True
+ - afi: ipv4
+ safi: unicast
+ vrf: site-1
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+#
+# commands:
+# - router bgp 65536
+# - vrf site-1
+# - no address-family ipv6 multicast
+# - exit
+# - address-family ipv4 multicast
+# - no nexthop route-map rmap2
+# - no nexthop trigger-delay critical 120 non-critical 180
+# - aggregate-address 203.0.113.0/24 as-set summary-only
+# - no network 192.0.2.32/27
+# - vrf site-1
+# - address-family ipv4 unicast
+# - no default-information originate
+# - no aggregate-address 203.0.113.0/24 as-set summary-only
+#
+# after:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# network 192.0.2.64/27 route-map rmap1
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# vrf site-1
+# address-family ipv4 unicast
+#
+
+# Using deleted to remove specified AFs
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+- name: Delete specified BGP AFs
+ cisco.nxos.nxos_bgp_address_family:
+ config:
+ as_number: 65536
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ - vrf: site-1
+ afi: ipv6
+ safi: multicast
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+#
+# commands:
+# - router bgp 65563
+# - no address-family ipv4 multicast
+# - vrf site-1
+# - no address-family ipv6 multicast
+#
+# after:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+
+# Using deleted to remove all BGP AFs
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+- name: Delete all BGP AFs
+ cisco.nxos.nxos_bgp_address_family:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+#
+# commands:
+# - router bgp 65563
+# - no address-family ipv4 multicast
+# - vrf site-1
+# - no address-family ipv4 unicast
+# - no address-family ipv6 multicast
+#
+# after:
+# as_number: "65536"
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# Nexus9000v#
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_bgp_address_family:
+ config:
+ as_number: 65536
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ networks:
+ - prefix: 192.0.2.32/27
+ - prefix: 192.0.2.64/27
+ route_map: rmap1
+ nexthop:
+ route_map: rmap2
+ trigger_delay:
+ critical_delay: 120
+ non_critical_delay: 180
+ - afi: ipv4
+ safi: unicast
+ vrf: site-1
+ default_information:
+ originate: True
+ aggregate_address:
+ - prefix: 203.0.113.0/24
+ as_set: True
+ summary_only: True
+ - afi: ipv6
+ safi: multicast
+ vrf: site-1
+ redistribute:
+ - protocol: ospfv3
+ id: 100
+ route_map: rmap-ospf-1
+ - protocol: eigrp
+ id: 101
+ route_map: rmap-eigrp-1
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - router bgp 65536
+# - address-family ipv4 multicast
+# - nexthop route-map rmap2
+# - nexthop trigger-delay critical 120 non-critical 180
+# - network 192.0.2.32/27
+# - network 192.0.2.64/27 route-map rmap1
+# - vrf site-1
+# - address-family ipv4 unicast
+# - default-information originate
+# - aggregate-address 203.0.113.0/24 as-set summary-only
+# - address-family ipv6 multicast
+# - redistribute ospfv3 100 route-map rmap-ospf-1
+# - redistribute eigrp 101 route-map rmap-eigrp-1
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# router bgp 65536
+# address-family ipv4 multicast
+# nexthop route-map rmap2
+# nexthop trigger-delay critical 120 non-critical 180
+# network 192.0.2.32/27
+# network 192.0.2.64/27 route-map rmap1
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# aggregate-address 203.0.113.0/24 as-set summary-only
+# address-family ipv6 multicast
+# redistribute ospfv3 100 route-map rmap-ospf-1
+# redistribute eigrp 101 route-map rmap-eigrp-1
+
+- name: Parse externally provided BGP AF config
+ cisco.nxos.nxos_bgp_address_family:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# as_number: "65536"
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# networks:
+# - prefix: 192.0.2.32/27
+# - prefix: 192.0.2.64/27
+# route_map: rmap1
+# nexthop:
+# route_map: rmap2
+# trigger_delay:
+# critical_delay: 120
+# non_critical_delay: 180
+# - afi: ipv4
+# safi: unicast
+# vrf: site-1
+# default_information:
+# originate: True
+# aggregate_address:
+# - prefix: 203.0.113.0/24
+# as_set: True
+# summary_only: True
+# - afi: ipv6
+# safi: multicast
+# vrf: site-1
+# redistribute:
+# - id: "100"
+# protocol: ospfv3
+# route_map: rmap-ospf-1
+# - id: "101"
+# protocol: eigrp
+# route_map: rmap-eigrp-1
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_address_family.bgp_address_family import (
+ Bgp_address_familyArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bgp_address_family.bgp_address_family import (
+ Bgp_address_family,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Bgp_address_familyArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Bgp_address_family(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_af.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_af.py
new file mode 100644
index 00000000..290f241d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_af.py
@@ -0,0 +1,877 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bgp_af
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2023-02-24) Manages BGP Address-family configuration.
+description:
+- Manages BGP Address-family configurations on NX-OS switches.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+deprecated:
+ alternative: nxos_bgp_address_family
+ why: Updated module released with more functionality.
+ removed_at_date: '2023-02-24'
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) removes the whole BGP ASN configuration
+- Default, where supported, restores params default value.
+options:
+ asn:
+ description:
+ - BGP autonomous system number. Valid values are String, Integer in ASPLAIN or
+ ASDOT notation.
+ required: true
+ type: str
+ vrf:
+ description:
+ - Name of the VRF. The name 'default' is a valid VRF representing the global bgp.
+ default: 'default'
+ type: str
+ afi:
+ description:
+ - Address Family Identifier.
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ - vpnv4
+ - vpnv6
+ - l2vpn
+ type: str
+ safi:
+ description:
+ - Sub Address Family Identifier.
+ required: true
+ choices:
+ - unicast
+ - multicast
+ - evpn
+ type: str
+ additional_paths_install:
+ description:
+ - Install a backup path into the forwarding table and provide prefix independent
+ convergence (PIC) in case of a PE-CE link failure.
+ type: bool
+ additional_paths_receive:
+ description:
+ - Enables the receive capability of additional paths for all of the neighbors
+ under this address family for which the capability has not been disabled.
+ type: bool
+ additional_paths_selection:
+ description:
+ - Configures the capability of selecting additional paths for a prefix. Valid
+ values are a string defining the name of the route-map.
+ type: str
+ additional_paths_send:
+ description:
+ - Enables the send capability of additional paths for all of the neighbors under
+ this address family for which the capability has not been disabled.
+ type: bool
+ advertise_l2vpn_evpn:
+ description:
+ - Advertise evpn routes.
+ type: bool
+ client_to_client:
+ description:
+ - Configure client-to-client route reflection.
+ type: bool
+ dampen_igp_metric:
+ description:
+ - Specify dampen value for IGP metric-related changes, in seconds. Valid values
+ are integer and keyword 'default'.
+ type: str
+ dampening_state:
+ description:
+ - Enable/disable route-flap dampening.
+ type: bool
+ dampening_half_time:
+ description:
+ - Specify decay half-life in minutes for route-flap dampening. Valid values are
+ integer and keyword 'default'.
+ type: str
+ dampening_max_suppress_time:
+ description:
+ - Specify max suppress time for route-flap dampening stable route. Valid values
+ are integer and keyword 'default'.
+ type: str
+ dampening_reuse_time:
+ description:
+ - Specify route reuse time for route-flap dampening. Valid values are integer
+ and keyword 'default'.
+ type: str
+ dampening_routemap:
+ description:
+ - Specify route-map for route-flap dampening. Valid values are a string defining
+ the name of the route-map.
+ type: str
+ dampening_suppress_time:
+ description:
+ - Specify route suppress time for route-flap dampening. Valid values are integer
+ and keyword 'default'.
+ type: str
+ default_information_originate:
+ description:
+ - Default information originate.
+ type: bool
+ default_metric:
+ description:
+ - Sets default metrics for routes redistributed into BGP. Valid values are Integer
+ or keyword 'default'
+ type: str
+ distance_ebgp:
+ description:
+ - Sets the administrative distance for eBGP routes. Valid values are Integer or
+ keyword 'default'.
+ type: str
+ distance_ibgp:
+ description:
+ - Sets the administrative distance for iBGP routes. Valid values are Integer or
+ keyword 'default'.
+ type: str
+ distance_local:
+ description:
+ - Sets the administrative distance for local BGP routes. Valid values are Integer
+ or keyword 'default'.
+ type: str
+ inject_map:
+ description:
+ - An array of route-map names which will specify prefixes to inject. Each array
+ entry must first specify the inject-map name, secondly an exist-map name, and
+ optionally the copy-attributes keyword which indicates that attributes should
+ be copied from the aggregate. For example [['lax_inject_map', 'lax_exist_map'],
+ ['nyc_inject_map', 'nyc_exist_map', 'copy-attributes'], ['fsd_inject_map', 'fsd_exist_map']].
+ type: list
+ elements: list
+ maximum_paths:
+ description:
+ - Configures the maximum number of equal-cost paths for load sharing. Valid value
+ is an integer in the range 1-64.
+ type: str
+ maximum_paths_ibgp:
+ description:
+ - Configures the maximum number of ibgp equal-cost paths for load sharing. Valid
+ value is an integer in the range 1-64.
+ type: str
+ networks:
+ description:
+ - Networks to configure. Valid value is a list of network prefixes to advertise.
+ The list must be in the form of an array. Each entry in the array must include
+ a prefix address and an optional route-map. For example [['10.0.0.0/16', 'routemap_LA'],
+ ['192.168.1.1', 'Chicago'], ['192.168.2.0/24'], ['192.168.3.0/24', 'routemap_NYC']].
+ type: list
+ elements: list
+ next_hop_route_map:
+ description:
+ - Configure a route-map for valid nexthops. Valid values are a string defining
+ the name of the route-map.
+ type: str
+ redistribute:
+ description:
+ - A list of redistribute directives. Multiple redistribute entries are allowed.
+ The list must be in the form of a nested array. the first entry of each array
+ defines the source-protocol to redistribute from; the second entry defines a
+ route-map name. A route-map is highly advised but may be optional on some platforms,
+ in which case it may be omitted from the array list. For example [['direct',
+ 'rm_direct'], ['lisp', 'rm_lisp']].
+ type: list
+ elements: list
+ suppress_inactive:
+ description:
+ - Advertises only active routes to peers.
+ type: bool
+ table_map:
+ description:
+ - Apply table-map to filter routes downloaded into URIB. Valid values are a string.
+ type: str
+ table_map_filter:
+ description:
+ - Filters routes rejected by the route-map and does not download them to the RIB.
+ type: bool
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ retain_route_target:
+ description:
+ - Retains all of the routes or the routes which are part of configured route-map.
+ Valid values are route-map names or keyword C(all) or keyword C(default). C(all)
+ retains all the routes regardless of Target-VPN community. C(default) will disable
+ the retain route target option. If you are using route-map name please ensure
+ that the name is not same as C(all) and C(default).
+ type: str
+ version_added: 1.1.0
+"""
+EXAMPLES = """
+# configure a simple address-family
+- cisco.nxos.nxos_bgp_af:
+ asn: 65535
+ vrf: TESTING
+ afi: ipv4
+ safi: unicast
+ advertise_l2vpn_evpn: true
+ state: present
+ retain_route_target: all
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["router bgp 65535", "vrf TESTING",
+ "address-family ipv4 unicast", "advertise l2vpn evpn",
+ "retain route-target all"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+BOOL_PARAMS = [
+ "additional_paths_install",
+ "additional_paths_receive",
+ "additional_paths_send",
+ "advertise_l2vpn_evpn",
+ "dampening_state",
+ "default_information_originate",
+ "suppress_inactive",
+]
+PARAM_TO_DEFAULT_KEYMAP = {
+ "maximum_paths": "1",
+ "maximum_paths_ibgp": "1",
+ "client_to_client": True,
+ "distance_ebgp": "20",
+ "distance_ibgp": "200",
+ "distance_local": "220",
+ "dampen_igp_metric": "600",
+}
+PARAM_TO_COMMAND_KEYMAP = {
+ "asn": "router bgp",
+ "afi": "address-family",
+ "safi": "address-family",
+ "additional_paths_install": "additional-paths install backup",
+ "additional_paths_receive": "additional-paths receive",
+ "additional_paths_selection": "additional-paths selection route-map",
+ "additional_paths_send": "additional-paths send",
+ "advertise_l2vpn_evpn": "advertise l2vpn evpn",
+ "client_to_client": "client-to-client reflection",
+ "dampen_igp_metric": "dampen-igp-metric",
+ "dampening_state": "dampening",
+ "dampening_half_time": "dampening",
+ "dampening_max_suppress_time": "dampening",
+ "dampening_reuse_time": "dampening",
+ "dampening_routemap": "dampening route-map",
+ "dampening_suppress_time": "dampening",
+ "default_information_originate": "default-information originate",
+ "default_metric": "default-metric",
+ "distance_ebgp": "distance",
+ "distance_ibgp": "distance",
+ "distance_local": "distance",
+ "inject_map": "inject-map",
+ "maximum_paths": "maximum-paths",
+ "maximum_paths_ibgp": "maximum-paths ibgp",
+ "networks": "network",
+ "redistribute": "redistribute",
+ "next_hop_route_map": "nexthop route-map",
+ "suppress_inactive": "suppress-inactive",
+ "table_map": "table-map",
+ "table_map_filter": "table-map-filter",
+ "vrf": "vrf",
+ "retain_route_target": "retain route-target",
+}
+DAMPENING_PARAMS = [
+ "dampening_half_time",
+ "dampening_suppress_time",
+ "dampening_reuse_time",
+ "dampening_max_suppress_time",
+]
+
+
+def get_value(arg, config, module):
+ command = PARAM_TO_COMMAND_KEYMAP[arg]
+ command_val_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M)
+ has_command_val = command_val_re.search(config)
+
+ if arg in ["networks", "redistribute", "inject_map"]:
+ value = []
+ for ele in command_val_re.findall(config):
+ tl = ele.split()
+ if "exist-map" in tl:
+ tl.remove("exist-map")
+ elif "route-map" in tl:
+ tl.remove("route-map")
+ value.append(tl)
+
+ elif command == "distance":
+ distance_re = r".*distance\s(?P<d_ebgp>\w+)\s(?P<d_ibgp>\w+)\s(?P<d_local>\w+)"
+ match_distance = re.match(distance_re, config, re.DOTALL)
+
+ value = ""
+ if match_distance:
+ distance_group = match_distance.groupdict()
+
+ if arg == "distance_ebgp":
+ value = distance_group["d_ebgp"]
+ elif arg == "distance_ibgp":
+ value = distance_group["d_ibgp"]
+ elif arg == "distance_local":
+ value = distance_group["d_local"]
+
+ elif command.split()[0] == "dampening":
+ value = ""
+ if arg == "dampen_igp_metric" or arg == "dampening_routemap":
+ if command in config:
+ value = has_command_val.group("value")
+ else:
+ dampening_re = r".*dampening\s(?P<half>\w+)\s(?P<reuse>\w+)\s(?P<suppress>\w+)\s(?P<max_suppress>\w+)"
+ match_dampening = re.match(dampening_re, config, re.DOTALL)
+ if match_dampening:
+ dampening_group = match_dampening.groupdict()
+
+ if arg == "dampening_half_time":
+ value = dampening_group["half"]
+ elif arg == "dampening_reuse_time":
+ value = dampening_group["reuse"]
+ elif arg == "dampening_suppress_time":
+ value = dampening_group["suppress"]
+ elif arg == "dampening_max_suppress_time":
+ value = dampening_group["max_suppress"]
+ else:
+ if arg == "dampening_state":
+ value = True if "dampening" in config else False
+ elif arg == "table_map_filter":
+ tmf_regex = re.compile(r"\s+table-map.*filter$", re.M)
+ value = False
+ if tmf_regex.search(config):
+ value = True
+
+ elif arg == "table_map":
+ tm_regex = re.compile(r"(?:table-map\s)(?P<value>\S+)(\sfilter)?$", re.M)
+ has_tablemap = tm_regex.search(config)
+ value = ""
+ if has_tablemap:
+ value = has_tablemap.group("value")
+
+ elif arg == "client_to_client":
+ no_command_re = re.compile(r"^\s+no\s{0}\s*$".format(command), re.M)
+ value = True
+
+ if no_command_re.search(config):
+ value = False
+
+ elif arg == "retain_route_target":
+ value = ""
+ if command in config:
+ route_target = has_command_val.group("value")
+ if route_target:
+ if route_target == "all":
+ value = "all"
+ elif "route-map" in route_target:
+ value = route_target.replace("route-map ", "")
+
+ elif arg in BOOL_PARAMS:
+ command_re = re.compile(r"^\s+{0}\s*$".format(command), re.M)
+ value = False
+
+ if command_re.search(config):
+ value = True
+
+ else:
+ value = ""
+
+ if has_command_val:
+ value = has_command_val.group("value")
+
+ return value
+
+
+def get_existing(module, args, warnings):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+
+ asn_regex = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.DOTALL)
+ match_asn = asn_regex.match(str(netcfg))
+
+ if match_asn:
+ existing_asn = match_asn.group("existing_asn")
+ parents = ["router bgp {0}".format(existing_asn)]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("address-family {0} {1}".format(module.params["afi"], module.params["safi"]))
+ config = netcfg.get_section(parents)
+
+ if config:
+ for arg in args:
+ if arg not in ["asn", "afi", "safi", "vrf"]:
+ gv = get_value(arg, config, module)
+ if gv:
+ existing[arg] = gv
+ else:
+ if arg != "client_to_client" and arg in PARAM_TO_DEFAULT_KEYMAP.keys():
+ existing[arg] = PARAM_TO_DEFAULT_KEYMAP.get(arg)
+ else:
+ existing[arg] = gv
+
+ existing["asn"] = existing_asn
+ existing["afi"] = module.params["afi"]
+ existing["safi"] = module.params["safi"]
+ existing["vrf"] = module.params["vrf"]
+ else:
+ warnings.append(
+ "The BGP process {0} didn't exist but the task just created it.".format(
+ module.params["asn"],
+ ),
+ )
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = value
+
+ return new_dict
+
+
+def fix_proposed(module, proposed, existing):
+ commands = list()
+ command = ""
+ fixed_proposed = {}
+ for key, value in proposed.items():
+ if key in DAMPENING_PARAMS:
+ if value != "default":
+ command = "dampening {0} {1} {2} {3}".format(
+ proposed.get("dampening_half_time"),
+ proposed.get("dampening_reuse_time"),
+ proposed.get("dampening_suppress_time"),
+ proposed.get("dampening_max_suppress_time"),
+ )
+ else:
+ if existing.get(key):
+ command = "no dampening {0} {1} {2} {3}".format(
+ existing["dampening_half_time"],
+ existing["dampening_reuse_time"],
+ existing["dampening_suppress_time"],
+ existing["dampening_max_suppress_time"],
+ )
+ if "default" in command:
+ command = ""
+ elif key.startswith("distance"):
+ command = "distance {0} {1} {2}".format(
+ proposed.get("distance_ebgp"),
+ proposed.get("distance_ibgp"),
+ proposed.get("distance_local"),
+ )
+ else:
+ fixed_proposed[key] = value
+
+ if command:
+ if command not in commands:
+ commands.append(command)
+
+ return fixed_proposed, commands
+
+
+def default_existing(existing_value, key, value):
+ commands = []
+ if key == "network":
+ for network in existing_value:
+ if len(network) == 2:
+ commands.append("no network {0} route-map {1}".format(network[0], network[1]))
+ elif len(network) == 1:
+ commands.append("no network {0}".format(network[0]))
+
+ elif key == "inject-map":
+ for maps in existing_value:
+ if len(maps) == 2:
+ commands.append("no inject-map {0} exist-map {1}".format(maps[0], maps[1]))
+ elif len(maps) == 3:
+ commands.append(
+ "no inject-map {0} exist-map {1} " "copy-attributes".format(maps[0], maps[1]),
+ )
+
+ elif key == "redistribute":
+ for maps in existing_value:
+ commands.append("no redistribute {0} route-map {1}".format(maps[0], maps[1]))
+
+ elif key == "retain route-target":
+ if existing_value == "all":
+ commands.append("no {0} {1}".format(key, existing_value))
+ elif existing_value != "default":
+ commands.append("no {0} route-map {1}".format(key, existing_value))
+
+ else:
+ commands.append("no {0} {1}".format(key, existing_value))
+ return commands
+
+
+def get_network_command(existing, key, value):
+ commands = []
+ existing_networks = existing.get("networks", [])
+ for inet in value:
+ if not isinstance(inet, list):
+ inet = [inet]
+ if inet not in existing_networks:
+ if len(inet) == 1:
+ command = "{0} {1}".format(key, inet[0])
+ elif len(inet) == 2:
+ command = "{0} {1} route-map {2}".format(key, inet[0], inet[1])
+ if command:
+ commands.append(command)
+ for enet in existing_networks:
+ if enet not in value:
+ if len(enet) == 1:
+ command = "no {0} {1}".format(key, enet[0])
+ elif len(enet) == 2:
+ command = "no {0} {1} route-map {2}".format(key, enet[0], enet[1])
+ if command:
+ commands.append(command)
+ return commands
+
+
+def get_inject_map_command(existing, key, value):
+ commands = []
+ existing_maps = existing.get("inject_map", [])
+ for maps in value:
+ if not isinstance(maps, list):
+ maps = [maps]
+ if maps not in existing_maps:
+ if len(maps) == 2:
+ command = "inject-map {0} exist-map {1}".format(maps[0], maps[1])
+ elif len(maps) == 3:
+ command = "inject-map {0} exist-map {1} " "copy-attributes".format(maps[0], maps[1])
+ if command:
+ commands.append(command)
+ for emaps in existing_maps:
+ if emaps not in value:
+ if len(emaps) == 2:
+ command = "no inject-map {0} exist-map {1}".format(emaps[0], emaps[1])
+ elif len(emaps) == 3:
+ command = "no inject-map {0} exist-map {1} " "copy-attributes".format(
+ emaps[0],
+ emaps[1],
+ )
+ if command:
+ commands.append(command)
+ return commands
+
+
+def get_redistribute_command(existing, key, value):
+ commands = []
+ existing_rules = existing.get("redistribute", [])
+ for rule in value:
+ if not isinstance(rule, list):
+ rule = [rule]
+ if rule not in existing_rules:
+ command = "redistribute {0} route-map {1}".format(rule[0], rule[1])
+ commands.append(command)
+ for erule in existing_rules:
+ if erule not in value:
+ command = "no redistribute {0} route-map {1}".format(erule[0], erule[1])
+ commands.append(command)
+ return commands
+
+
+def get_table_map_command(module, existing, key, value):
+ commands = []
+ if key == "table-map":
+ if value != "default":
+ command = "{0} {1}".format(key, module.params["table_map"])
+ if (
+ module.params["table_map_filter"] is not None
+ and module.params["table_map_filter"] != "default"
+ ):
+ command += " filter"
+ commands.append(command)
+ else:
+ if existing.get("table_map"):
+ command = "no {0} {1}".format(key, existing.get("table_map"))
+ commands.append(command)
+ return commands
+
+
+def get_retain_route_target_command(existing, key, value):
+ commands = []
+ if key == "retain route-target":
+ if value != "default":
+ if value == "all":
+ command = "{0} {1}".format(key, value)
+ else:
+ command = "{0} route-map {1}".format(key, value)
+ else:
+ existing_value = existing.get("retain_route_target")
+ if existing_value == "all":
+ command = "no {0} {1}".format(key, existing_value)
+ else:
+ command = "no {0} route-map {1}".format(key, existing_value)
+ commands.append(command)
+ return commands
+
+
+def get_default_table_map_filter(existing):
+ commands = []
+ existing_table_map_filter = existing.get("table_map_filter")
+ if existing_table_map_filter:
+ existing_table_map = existing.get("table_map")
+ if existing_table_map:
+ command = "table-map {0}".format(existing_table_map)
+ commands.append(command)
+ return commands
+
+
+def state_present(module, existing, proposed, candidate):
+ fixed_proposed, commands = fix_proposed(module, proposed, existing)
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, fixed_proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+ for key, value in proposed_commands.items():
+ if key == "address-family":
+ addr_family_command = "address-family {0} {1}".format(
+ module.params["afi"],
+ module.params["safi"],
+ )
+ if addr_family_command not in commands:
+ commands.append(addr_family_command)
+
+ elif key.startswith("table-map"):
+ table_map_commands = get_table_map_command(module, existing, key, value)
+ if table_map_commands:
+ commands.extend(table_map_commands)
+
+ elif value is True:
+ commands.append(key)
+
+ elif value is False:
+ commands.append("no {0}".format(key))
+
+ elif value == "default":
+ if key in PARAM_TO_DEFAULT_KEYMAP:
+ commands.append("{0} {1}".format(key, PARAM_TO_DEFAULT_KEYMAP[key]))
+
+ elif existing_commands.get(key):
+ if key == "table-map-filter":
+ default_tmf_command = get_default_table_map_filter(existing)
+
+ if default_tmf_command:
+ commands.extend(default_tmf_command)
+ else:
+ existing_value = existing_commands.get(key)
+ default_command = default_existing(existing_value, key, value)
+ if default_command:
+ commands.extend(default_command)
+ else:
+ if key == "network":
+ network_commands = get_network_command(existing, key, value)
+ if network_commands:
+ commands.extend(network_commands)
+
+ elif key == "inject-map":
+ inject_map_commands = get_inject_map_command(existing, key, value)
+ if inject_map_commands:
+ commands.extend(inject_map_commands)
+
+ elif key == "redistribute":
+ redistribute_commands = get_redistribute_command(existing, key, value)
+ if redistribute_commands:
+ commands.extend(redistribute_commands)
+
+ elif key == "retain route-target":
+ retain_route_target_commands = get_retain_route_target_command(existing, key, value)
+ if retain_route_target_commands:
+ commands.extend(retain_route_target_commands)
+
+ else:
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+
+ if commands:
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ addr_family_command = "address-family {0} {1}".format(
+ module.params["afi"],
+ module.params["safi"],
+ )
+ parents.append(addr_family_command)
+ if addr_family_command in commands:
+ commands.remove(addr_family_command)
+ candidate.add(commands, parents=parents)
+
+
+def state_absent(module, candidate):
+ commands = []
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ commands.append("no address-family {0} {1}".format(module.params["afi"], module.params["safi"]))
+ candidate.add(commands, parents=parents)
+
+
+def main():
+ argument_spec = dict(
+ asn=dict(required=True, type="str"),
+ vrf=dict(required=False, type="str", default="default"),
+ safi=dict(required=True, type="str", choices=["unicast", "multicast", "evpn"]),
+ afi=dict(
+ required=True,
+ type="str",
+ choices=["ipv4", "ipv6", "vpnv4", "vpnv6", "l2vpn"],
+ ),
+ additional_paths_install=dict(required=False, type="bool"),
+ additional_paths_receive=dict(required=False, type="bool"),
+ additional_paths_selection=dict(required=False, type="str"),
+ additional_paths_send=dict(required=False, type="bool"),
+ advertise_l2vpn_evpn=dict(required=False, type="bool"),
+ client_to_client=dict(required=False, type="bool"),
+ dampen_igp_metric=dict(required=False, type="str"),
+ dampening_state=dict(required=False, type="bool"),
+ dampening_half_time=dict(required=False, type="str"),
+ dampening_max_suppress_time=dict(required=False, type="str"),
+ dampening_reuse_time=dict(required=False, type="str"),
+ dampening_routemap=dict(required=False, type="str"),
+ dampening_suppress_time=dict(required=False, type="str"),
+ default_information_originate=dict(required=False, type="bool"),
+ default_metric=dict(required=False, type="str"),
+ distance_ebgp=dict(required=False, type="str"),
+ distance_ibgp=dict(required=False, type="str"),
+ distance_local=dict(required=False, type="str"),
+ inject_map=dict(required=False, type="list", elements="list"),
+ maximum_paths=dict(required=False, type="str"),
+ maximum_paths_ibgp=dict(required=False, type="str"),
+ networks=dict(required=False, type="list", elements="list"),
+ next_hop_route_map=dict(required=False, type="str"),
+ redistribute=dict(required=False, type="list", elements="list"),
+ suppress_inactive=dict(required=False, type="bool"),
+ table_map=dict(required=False, type="str"),
+ table_map_filter=dict(required=False, type="bool"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ retain_route_target=dict(required=False, type="str"),
+ )
+
+ mutually_exclusive = [
+ ("dampening_state", "dampening_routemap"),
+ ("dampening_state", "dampening_half_time"),
+ ("dampening_state", "dampening_suppress_time"),
+ ("dampening_state", "dampening_reuse_time"),
+ ("dampening_state", "dampening_max_suppress_time"),
+ ("dampening_routemap", "dampening_half_time"),
+ ("dampening_routemap", "dampening_suppress_time"),
+ ("dampening_routemap", "dampening_reuse_time"),
+ ("dampening_routemap", "dampening_max_suppress_time"),
+ ]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ required_together=[
+ DAMPENING_PARAMS,
+ ["distance_ibgp", "distance_ebgp", "distance_local"],
+ ],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = dict(changed=False, warnings=warnings)
+
+ state = module.params["state"]
+
+ if module.params["advertise_l2vpn_evpn"]:
+ if module.params["vrf"] == "default":
+ module.fail_json(
+ msg="It is not possible to advertise L2VPN "
+ "EVPN in the default VRF. Please specify "
+ "another one.",
+ vrf=module.params["vrf"],
+ )
+
+ if module.params["table_map_filter"] and not module.params["table_map"]:
+ module.fail_json(msg="table_map param is needed when using" " table_map_filter filter.")
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing = get_existing(module, args, warnings)
+
+ if existing.get("asn") and state == "present":
+ if existing.get("asn") != module.params["asn"]:
+ module.fail_json(
+ msg="Another BGP ASN already exists.",
+ proposed_asn=module.params["asn"],
+ existing_asn=existing.get("asn"),
+ )
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ for arg in ["networks", "inject_map", "redistribute"]:
+ if proposed_args.get(arg):
+ if proposed_args[arg][0] == "default":
+ proposed_args[arg] = "default"
+
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key not in ["asn", "vrf"]:
+ if str(value).lower() == "default":
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default")
+ if existing.get(key) != value:
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present":
+ state_present(module, existing, proposed, candidate)
+ elif state == "absent" and existing:
+ state_absent(module, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ if not module.check_mode:
+ load_config(module, candidate)
+ result["changed"] = True
+ result["commands"] = candidate
+ else:
+ result["commands"] = []
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_global.py
new file mode 100644
index 00000000..b3072d66
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_global.py
@@ -0,0 +1,1694 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_bgp_global
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_bgp_global
+short_description: BGP Global resource module.
+description:
+- This module manages global BGP configuration on devices running Cisco NX-OS.
+version_added: 1.4.0
+notes:
+- Tested against NX-OS 9.3.6.
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^router bgp').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of BGP process configuration.
+ type: dict
+ suboptions:
+ as_number:
+ description: Autonomous System Number of the router.
+ type: str
+ affinity_group:
+ description: Configure an affinity group.
+ type: dict
+ suboptions:
+ group_id:
+ description: Affinity Group ID.
+ type: int
+ bestpath: &bestpath
+ description: Define the default bestpath selection algorithm.
+ type: dict
+ suboptions:
+ always_compare_med:
+ description: Compare MED on paths from different AS.
+ type: bool
+ as_path:
+ description: AS-Path.
+ type: dict
+ suboptions:
+ ignore:
+ description: Ignore AS-Path during bestpath selection.
+ type: bool
+ multipath_relax:
+ description: Relax AS-Path restriction when choosing multipaths.
+ type: bool
+ compare_neighborid:
+ description: When more paths are available than max path config, use neighborid as tie-breaker.
+ type: bool
+ compare_routerid:
+ description: Compare router-id for identical EBGP paths.
+ type: bool
+ cost_community_ignore:
+ description: Ignore cost communities in bestpath selection.
+ type: bool
+ igp_metric_ignore:
+ description: Ignore IGP metric for next-hop during bestpath selection.
+ type: bool
+ med:
+ description: MED
+ type: dict
+ suboptions:
+ confed:
+ description: Compare MED only from paths originated from within a confederation.
+ type: bool
+ missing_as_worst:
+ description: Treat missing MED as highest MED.
+ type: bool
+ non_deterministic:
+ description: Not always pick the best-MED path among paths from same AS.
+ type: bool
+ cluster_id: &cluster_id
+ description: Configure Route Reflector Cluster-ID.
+ type: str
+ confederation: &confederation
+ description: AS confederation parameters.
+ type: dict
+ suboptions:
+ identifier:
+ description: Set routing domain confederation AS.
+ type: str
+ peers:
+ description: Peer ASs in BGP confederation.
+ type: list
+ elements: str
+ disable_policy_batching:
+ description: Disable batching evaluation of outbound policy for a peer.
+ type: dict
+ suboptions:
+ set:
+ description: Set policy batching.
+ type: bool
+ ipv4:
+ description: IPv4 address-family settings.
+ type: dict
+ suboptions:
+ prefix_list:
+ description: Name of prefix-list to apply.
+ type: str
+ ipv6:
+ description: IPv6 address-family settings.
+ type: dict
+ suboptions:
+ prefix_list:
+ description: Name of prefix-list to apply.
+ type: str
+ nexthop:
+ description: Batching based on nexthop.
+ type: bool
+ dynamic_med_interval:
+ description: Sets the interval for dampening of med changes.
+ type: int
+ enforce_first_as:
+ description: Enforce neighbor AS is the first AS in AS-PATH attribute (EBGP).
+ type: bool
+ enhanced_error:
+ description: Enable BGP Enhanced error handling.
+ type: bool
+ fabric_soo:
+ description: Fabric site of origin.
+ type: str
+ fast_external_fallover:
+ description: Immediately reset the session if the link to a directly connected BGP peer goes down.
+ type: bool
+ flush_routes:
+ description: Flush routes in RIB upon controlled restart.
+ type: bool
+ graceful_restart: &graceful_restart
+ description: Configure Graceful Restart functionality.
+ type: dict
+ suboptions:
+ set:
+ description: Enable graceful-restart.
+ type: bool
+ restart_time:
+ description: Maximum time for restart advertised to peers.
+ type: int
+ stalepath_time:
+ description: Maximum time to keep a restarting peer's stale routes.
+ type: int
+ helper:
+ description: Configure Graceful Restart Helper mode functionality.
+ type: bool
+ graceful_shutdown:
+ description: Graceful-shutdown for BGP protocol.
+ type: dict
+ suboptions:
+ activate:
+ description: Send graceful-shutdown community on all routes.
+ type: dict
+ suboptions:
+ set:
+ description: Activiate graceful-shutdown.
+ type: bool
+ route_map:
+ description: Apply route-map to modify attributes for outbound.
+ type: str
+ aware:
+ description: Lower preference of routes carrying graceful-shutdown community.
+ type: bool
+ isolate:
+ description: Isolate this router from BGP perspective.
+ type: dict
+ suboptions:
+ set:
+ description: Withdraw remote BGP routes to isolate this router.
+ type: bool
+ include_local:
+ description: Withdraw both local and remote BGP routes.
+ type: bool
+ log_neighbor_changes: &log_nbr
+ description: Log a message for neighbor up/down event.
+ type: bool
+ maxas_limit: &maxas_limit
+ description: Allow AS-PATH attribute from EBGP neighbor imposing a limit on number of ASes.
+ type: int
+ neighbors: &nbr
+ description: Configure BGP neighbors.
+ type: list
+ elements: dict
+ suboptions:
+ neighbor_address:
+ description: IP address/Prefix of the neighbor or interface.
+ type: str
+ required: True
+ bfd:
+ description: Bidirectional Fast Detection for the neighbor.
+ type: dict
+ suboptions:
+ set:
+ description: Set BFD for this neighbor.
+ type: bool
+ singlehop:
+ description: Single-hop session.
+ type: bool
+ multihop:
+ description: Multihop session.
+ type: dict
+ suboptions:
+ set:
+ description: Set BFD multihop.
+ type: bool
+ interval:
+ description: Configure BFD session interval parameters.
+ type: dict
+ suboptions:
+ tx_interval:
+ description: TX interval in milliseconds.
+ type: int
+ min_rx_interval:
+ description: Minimum RX interval.
+ type: int
+ multiplier:
+ description: Detect Multiplier.
+ type: int
+ neighbor_affinity_group:
+ description: Configure an affinity group.
+ type: dict
+ suboptions:
+ group_id:
+ description: Affinity Group ID.
+ type: int
+ bmp_activate_server:
+ description: Specify server ID for activating BMP monitoring for the peer.
+ type: int
+ capability:
+ description: Capability.
+ type: dict
+ suboptions:
+ suppress_4_byte_as:
+ description: Suppress 4-byte AS Capability.
+ type: bool
+ description:
+ description: Neighbor specific descripion.
+ type: str
+ disable_connected_check:
+ description: Disable check for directly connected peer.
+ type: bool
+ dont_capability_negotiate:
+ description: Don't negotiate capability with this neighbor.
+ type: bool
+ dscp:
+ description: Set dscp value for tcp transport.
+ type: str
+ dynamic_capability:
+ description: Dynamic Capability
+ type: bool
+ ebgp_multihop:
+ description: Specify multihop TTL for remote peer.
+ type: int
+ graceful_shutdown:
+ description: Graceful-shutdown for this neighbor.
+ type: dict
+ suboptions:
+ activate:
+ description: Send graceful-shutdown community.
+ type: dict
+ suboptions:
+ set:
+ description: Set activate.
+ type: bool
+ route_map:
+ description: Apply route-map to modify attributes for outbound.
+ type: str
+ inherit:
+ description: Inherit a template.
+ type: dict
+ suboptions:
+ peer:
+ description: Peer template to inherit.
+ type: str
+ peer_session:
+ description: Peer-session template to inherit.
+ type: str
+ local_as:
+ description: Specify the local-as number for the eBGP neighbor.
+ type: str
+ log_neighbor_changes:
+ description: Log message for neighbor up/down event.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set log-neighbor-changes.
+ type: bool
+ disable:
+ description:
+ - Disable logging of neighbor up/down event.
+ type: bool
+ low_memory:
+ description: Behaviour in low memory situations.
+ type: dict
+ suboptions:
+ exempt:
+ description: Do not shutdown this peer when under memory pressure.
+ type: bool
+ password:
+ description: Configure a password for neighbor.
+ type: dict
+ suboptions:
+ encryption:
+ description:
+ - 0 specifies an UNENCRYPTED neighbor password.
+ - 3 specifies an 3DES ENCRYPTED neighbor password will follow.
+ - 7 specifies a Cisco type 7 ENCRYPTED neighbor password will follow.
+ type: int
+ key:
+ description: Authentication password.
+ type: str
+ path_attribute:
+ description: BGP path attribute optional filtering.
+ type: list
+ elements: dict
+ suboptions:
+ action:
+ description: Action.
+ type: str
+ choices: ["discard", "treat-as-withdraw"]
+ type:
+ description: Path attribute type
+ type: int
+ range:
+ description: Path attribute range.
+ type: dict
+ suboptions:
+ start:
+ description: Path attribute range start value.
+ type: int
+ end:
+ description: Path attribute range end value.
+ type: int
+ peer_type:
+ description: Neighbor facing
+ type: str
+ choices: ["fabric-border-leaf", "fabric-external"]
+ remote_as:
+ description: Specify Autonomous System Number of the neighbor.
+ type: str
+ remove_private_as:
+ description: Remove private AS number from outbound updates.
+ type: dict
+ suboptions:
+ set:
+ description: Remove private AS.
+ type: bool
+ replace_as:
+ description: Replace.
+ type: bool
+ all:
+ description: All.
+ type: bool
+ shutdown:
+ description: Administratively shutdown this neighbor.
+ type: bool
+ timers:
+ description: Configure keepalive and hold timers.
+ type: dict
+ suboptions:
+ keepalive:
+ description: Keepalive interval (seconds).
+ type: int
+ holdtime:
+ description: Holdtime (seconds).
+ type: int
+ transport:
+ description: BGP transport connection.
+ type: dict
+ suboptions:
+ connection_mode:
+ description: Specify type of connection.
+ type: dict
+ suboptions:
+ passive:
+ description: Allow passive connection setup only.
+ type: bool
+ ttl_security:
+ description: Enable TTL Security Mechanism.
+ type: dict
+ suboptions:
+ hops:
+ description: Specify hop count for remote peer.
+ type: int
+ update_source:
+ description: Specify source of BGP session and updates.
+ type: str
+ neighbor_down: &nbr_down
+ description: Handle BGP neighbor down event, due to various reasons.
+ type: dict
+ suboptions:
+ fib_accelerate:
+ description: Accelerate the hardware updates for IP/IPv6 adjacencies for neighbor.
+ type: bool
+ nexthop:
+ description: Nexthop resolution options.
+ type: dict
+ suboptions:
+ suppress_default_resolution:
+ description: Prohibit use of default route for nexthop address resolution.
+ type: bool
+ rd:
+ description: Secondary Route Distinguisher for vxlan multisite border gateway.
+ type: dict
+ suboptions:
+ dual:
+ description: Generate Secondary RD for all VRFs and L2VNIs.
+ type: bool
+ id:
+ description: Specify 2 byte value for ID.
+ type: int
+ reconnect_interval: &reconn_intv
+ description: Configure connection reconnect interval.
+ type: int
+ router_id: &rtr_id
+ description: Specify the IP address to use as router-id.
+ type: str
+ shutdown: &shtdwn
+ description: Administratively shutdown BGP protocol.
+ type: bool
+ suppress_fib_pending: &suppr
+ description: Advertise only routes that are programmed in hardware to peers.
+ type: bool
+ timers: &timers
+ description: Configure bgp related timers.
+ type: dict
+ suboptions:
+ bestpath_limit:
+ description: Configure timeout for first bestpath after restart.
+ type: dict
+ suboptions:
+ timeout:
+ description: Bestpath timeout (seconds).
+ type: int
+ always:
+ description: Configure update-delay-always option.
+ type: bool
+ bgp:
+ description: Configure different bgp keepalive and holdtimes.
+ type: dict
+ suboptions:
+ keepalive:
+ description: Keepalive interval (seconds).
+ type: int
+ holdtime:
+ description: Holdtime (seconds).
+ type: int
+ prefix_peer_timeout:
+ description: Prefix Peer timeout (seconds).
+ type: int
+ prefix_peer_wait:
+ description: Configure wait timer for a prefix peer.
+ type: int
+ vrfs:
+ description: Virtual Router Context configurations.
+ type: list
+ elements: dict
+ suboptions:
+ vrf:
+ description: VRF name.
+ type: str
+ allocate_index:
+ description: Configure allocate-index.
+ type: int
+ bestpath: *bestpath
+ cluster_id: *cluster_id
+ confederation: *confederation
+ graceful_restart: *graceful_restart
+ local_as:
+ description: Specify the local-as for this vrf.
+ type: str
+ log_neighbor_changes: *log_nbr
+ maxas_limit: *maxas_limit
+ neighbors: *nbr
+ neighbor_down: *nbr_down
+ reconnect_interval: *reconn_intv
+ router_id: *rtr_id
+ timers: *timers
+ state:
+ description:
+ - The state the configuration should be left in.
+ - State I(purged) removes all the BGP configurations from the
+ target device. Use caution with this state.
+ - State I(deleted) only removes BGP attributes that this modules
+ manages and does not negate the BGP process completely. Thereby, preserving
+ address-family related configurations under BGP context.
+ - Running states I(deleted) and I(replaced) will result in an error if there
+ are address-family configuration lines present under a neighbor,
+ or a vrf context that is to be removed. Please use the
+ M(cisco.nxos.nxos_bgp_af) or M(cisco.nxos.nxos_bgp_neighbor_af)
+ modules for prior cleanup.
+ - States I(merged) and I(replaced) will result in a failure if BGP is already configured
+ with a different ASN than what is provided in the task. In such cases, please use
+ state I(purged) to remove the existing BGP process and proceed further.
+ - Refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - deleted
+ - purged
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# Nexus9000v#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_bgp_global:
+ config:
+ as_number: 65563
+ router_id: 192.168.1.1
+ bestpath:
+ as_path:
+ multipath_relax: True
+ compare_neighborid: True
+ cost_community_ignore: True
+ confederation:
+ identifier: 42
+ peers:
+ - 65020
+ - 65030
+ - 65040
+ log_neighbor_changes: True
+ maxas_limit: 20
+ neighbors:
+ - neighbor_address: 192.168.1.100
+ neighbor_affinity_group:
+ group_id: 160
+ bmp_activate_server: 1
+ remote_as: 65563
+ description: NBR-1
+ low_memory:
+ exempt: True
+ - neighbor_address: 192.168.1.101
+ remote_as: 65563
+ password:
+ encryption: 7
+ key: 12090404011C03162E
+ neighbor_down:
+ fib_accelerate: True
+ vrfs:
+ - vrf: site-1
+ allocate_index: 5000
+ local_as: 200
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 198.51.100.1
+ description: site-1-nbr-1
+ password:
+ encryption: 3
+ key: 13D4D3549493D2877B1DC116EE27A6BE
+ remote_as: 65562
+ - neighbor_address: 198.51.100.2
+ remote_as: 65562
+ description: site-1-nbr-2
+ - vrf: site-2
+ local_as: 300
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 203.0.113.2
+ description: site-2-nbr-1
+ password:
+ encryption: 3
+ key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+ remote_as: 65568
+ neighbor_down:
+ fib_accelerate: True
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - router bgp 65563
+# - bestpath as-path multipath-relax
+# - bestpath compare-neighborid
+# - bestpath cost-community ignore
+# - confederation identifier 42
+# - log-neighbor-changes
+# - maxas-limit 20
+# - neighbor-down fib-accelerate
+# - router-id 192.168.1.1
+# - confederation peers 65020 65030 65040
+# - neighbor 192.168.1.100
+# - remote-as 65563
+# - affinity-group 160
+# - bmp-activate-server 1
+# - description NBR-1
+# - low-memory exempt
+# - neighbor 192.168.1.101
+# - remote-as 65563
+# - password 7 12090404011C03162E
+# - vrf site-1
+# - allocate-index 5000
+# - local-as 200
+# - log-neighbor-changes
+# - neighbor 198.51.100.1
+# - remote-as 65562
+# - description site-1-nbr-1
+# - password 3 13D4D3549493D2877B1DC116EE27A6BE
+# - neighbor 198.51.100.2
+# - remote-as 65562
+# - description site-1-nbr-2
+# - vrf site-2
+# - local-as 300
+# - log-neighbor-changes
+# - neighbor-down fib-accelerate
+# - neighbor 203.0.113.2
+# - remote-as 65568
+# - description site-2-nbr-1
+# - password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+#
+# after:
+# as_number: '65563'
+# bestpath:
+# as_path:
+# multipath_relax: true
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65040'
+# log_neighbor_changes: true
+# maxas_limit: 20
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# - neighbor_address: 192.168.1.101
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - allocate_index: 5000
+# local_as: '200'
+# log_neighbor_changes: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 198.51.100.1
+# password:
+# encryption: 3
+# key: 13D4D3549493D2877B1DC116EE27A6BE
+# remote_as: '65562'
+# - description: site-1-nbr-2
+# neighbor_address: 198.51.100.2
+# remote_as: '65562'
+# vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - description: site-2-nbr-1
+# neighbor_address: 203.0.113.2
+# password:
+# encryption: 3
+# key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+# remote_as: '65568'
+# vrf: site-2
+
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65040
+# bestpath as-path multipath-relax
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 20
+# log-neighbor-changes
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# neighbor 192.168.1.101
+# remote-as 65563
+# password 7 12090404011C03162E
+# vrf site-1
+# local-as 200
+# log-neighbor-changes
+# allocate-index 5000
+# neighbor 198.51.100.1
+# remote-as 65562
+# description site-1-nbr-1
+# password 3 13D4D3549493D2877B1DC116EE27A6BE
+# neighbor 198.51.100.2
+# remote-as 65562
+# description site-1-nbr-2
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# remote-as 65568
+# description site-2-nbr-1
+# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+# Using replaced
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65040
+# bestpath as-path multipath-relax
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 20
+# log-neighbor-changes
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# neighbor 192.168.1.101
+# remote-as 65563
+# password 7 12090404011C03162E
+# vrf site-1
+# local-as 200
+# log-neighbor-changes
+# allocate-index 5000
+# neighbor 198.51.100.1
+# remote-as 65562
+# description site-1-nbr-1
+# password 3 13D4D3549493D2877B1DC116EE27A6BE
+# neighbor 198.51.100.2
+# remote-as 65562
+# description site-1-nbr-2
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# remote-as 65568
+# description site-2-nbr-1
+# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+- name: Replace BGP configuration with provided configuration
+ cisco.nxos.nxos_bgp_global:
+ config:
+ as_number: 65563
+ router_id: 192.168.1.1
+ bestpath:
+ compare_neighborid: True
+ cost_community_ignore: True
+ confederation:
+ identifier: 42
+ peers:
+ - 65020
+ - 65030
+ - 65050
+ maxas_limit: 40
+ neighbors:
+ - neighbor_address: 192.168.1.100
+ neighbor_affinity_group:
+ group_id: 160
+ bmp_activate_server: 1
+ remote_as: 65563
+ description: NBR-1
+ low_memory:
+ exempt: True
+ neighbor_down:
+ fib_accelerate: True
+ vrfs:
+ - vrf: site-2
+ local_as: 300
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 203.0.113.2
+ password:
+ encryption: 7
+ key: 12090404011C03162E
+ neighbor_down:
+ fib_accelerate: True
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# as_number: '65563'
+# bestpath:
+# as_path:
+# multipath_relax: true
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65040'
+# log_neighbor_changes: true
+# maxas_limit: 20
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# - neighbor_address: 192.168.1.101
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - allocate_index: 5000
+# local_as: '200'
+# log_neighbor_changes: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 198.51.100.1
+# password:
+# encryption: 3
+# key: 13D4D3549493D2877B1DC116EE27A6BE
+# remote_as: '65562'
+# - description: site-1-nbr-2
+# neighbor_address: 198.51.100.2
+# remote_as: '65562'
+# vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - description: site-2-nbr-1
+# neighbor_address: 203.0.113.2
+# password:
+# encryption: 3
+# key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+# remote_as: '65568'
+# vrf: site-2
+#
+# commands:
+# - router bgp 65563
+# - no bestpath as-path multipath-relax
+# - no log-neighbor-changes
+# - maxas-limit 40
+# - no confederation peers 65020 65030 65040
+# - confederation peers 65020 65030 65050
+# - no neighbor 192.168.1.101
+# - vrf site-2
+# - neighbor 203.0.113.2
+# - no remote-as 65568
+# - no description site-2-nbr-1
+# - password 7 12090404011C03162E
+# - no vrf site-1
+
+# after:
+# as_number: '65563'
+# bestpath:
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65050'
+# maxas_limit: 40
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - neighbor_address: 203.0.113.2
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# vrf: site-2
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65050
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 40
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# password 7 12090404011C03162E
+
+# Using deleted
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65040
+# bestpath as-path multipath-relax
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 20
+# log-neighbor-changes
+# address-family ipv4 unicast
+# default-metric 400
+# suppress-inactive
+# default-information originate
+# address-family ipv6 multicast
+# wait-igp-convergence
+# redistribute eigrp eigrp-1 route-map site-1-rmap
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# neighbor 192.168.1.101
+# remote-as 65563
+# password 7 12090404011C03162E
+# vrf site-1
+# local-as 200
+# log-neighbor-changes
+# allocate-index 5000
+# address-family ipv4 multicast
+# maximum-paths 40
+# dampen-igp-metric 1200
+# neighbor 198.51.100.1
+# remote-as 65562
+# description site-1-nbr-1
+# password 3 13D4D3549493D2877B1DC116EE27A6BE
+# neighbor 198.51.100.2
+# remote-as 65562
+# description site-1-nbr-2
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# remote-as 65568
+# description site-1-nbr-1
+# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+- name: Delete BGP configurations handled by this module
+ cisco.nxos.nxos_bgp_global:
+ state: deleted
+
+# Task output
+# -------------
+
+# before:
+# as_number: '65563'
+# bestpath:
+# as_path:
+# multipath_relax: true
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65040'
+# log_neighbor_changes: true
+# maxas_limit: 20
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# - neighbor_address: 192.168.1.101
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - allocate_index: 5000
+# local_as: '200'
+# log_neighbor_changes: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 198.51.100.1
+# password:
+# encryption: 3
+# key: 13D4D3549493D2877B1DC116EE27A6BE
+# remote_as: '65562'
+# - description: site-1-nbr-2
+# neighbor_address: 198.51.100.2
+# remote_as: '65562'
+# vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 203.0.113.2
+# password:
+# encryption: 3
+# key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+# remote_as: '65568'
+# vrf: site-2
+#
+# commands:
+# - router bgp 65563
+# - no bestpath as-path multipath-relax
+# - no bestpath compare-neighborid
+# - no bestpath cost-community ignore
+# - no confederation identifier 42
+# - no log-neighbor-changes
+# - no maxas-limit 20
+# - no neighbor-down fib-accelerate
+# - no router-id 192.168.1.1
+# - no confederation peers 65020 65030 65040
+# - no neighbor 192.168.1.100
+# - no neighbor 192.168.1.101
+# - no vrf site-1
+# - no vrf site-2
+#
+# after:
+# as_number: '65563'
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# address-family ipv4 unicast
+# default-metric 400
+# suppress-inactive
+# default-information originate
+# address-family ipv6 multicast
+# wait-igp-convergence
+# redistribute eigrp eigrp-1 route-map site-1-rmap
+#
+
+# Using purged
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65040
+# bestpath as-path multipath-relax
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 20
+# log-neighbor-changes
+# address-family ipv4 unicast
+# default-metric 400
+# suppress-inactive
+# default-information originate
+# address-family ipv6 multicast
+# wait-igp-convergence
+# redistribute eigrp eigrp-1 route-map site-1-rmap
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# neighbor 192.168.1.101
+# remote-as 65563
+# password 7 12090404011C03162E
+# vrf site-1
+# local-as 200
+# log-neighbor-changes
+# allocate-index 5000
+# address-family ipv4 multicast
+# maximum-paths 40
+# dampen-igp-metric 1200
+# neighbor 198.51.100.1
+# remote-as 65562
+# description site-1-nbr-1
+# password 3 13D4D3549493D2877B1DC116EE27A6BE
+# neighbor 198.51.100.2
+# remote-as 65562
+# description site-1-nbr-2
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# remote-as 65568
+# description site-1-nbr-1
+# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+- name: Purge all BGP configurations from the device
+ cisco.nxos.nxos_bgp_global:
+ state: purged
+
+# Task output
+# -------------
+
+# before:
+# as_number: '65563'
+# bestpath:
+# as_path:
+# multipath_relax: true
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65040'
+# log_neighbor_changes: true
+# maxas_limit: 20
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# - neighbor_address: 192.168.1.101
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - allocate_index: 5000
+# local_as: '200'
+# log_neighbor_changes: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 198.51.100.1
+# password:
+# encryption: 3
+# key: 13D4D3549493D2877B1DC116EE27A6BE
+# remote_as: '65562'
+# - description: site-1-nbr-2
+# neighbor_address: 198.51.100.2
+# remote_as: '65562'
+# vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 203.0.113.2
+# password:
+# encryption: 3
+# key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+# remote_as: '65568'
+# vrf: site-2
+#
+# commands:
+# - no router bgp 65563
+#
+# after: {}
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# Nexus9000v#
+
+# Using rendered
+
+- name: Render platform specific configuration lines (without connecting to the device)
+ cisco.nxos.nxos_bgp_global:
+ config:
+ as_number: 65563
+ router_id: 192.168.1.1
+ bestpath:
+ as_path:
+ multipath_relax: True
+ compare_neighborid: True
+ cost_community_ignore: True
+ confederation:
+ identifier: 42
+ peers:
+ - 65020
+ - 65030
+ - 65040
+ log_neighbor_changes: True
+ maxas_limit: 20
+ neighbors:
+ - neighbor_address: 192.168.1.100
+ neighbor_affinity_group:
+ group_id: 160
+ bmp_activate_server: 1
+ remote_as: 65563
+ description: NBR-1
+ low_memory:
+ exempt: True
+ - neighbor_address: 192.168.1.101
+ remote_as: 65563
+ password:
+ encryption: 7
+ key: 12090404011C03162E
+ neighbor_down:
+ fib_accelerate: True
+ vrfs:
+ - vrf: site-1
+ allocate_index: 5000
+ local_as: 200
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 198.51.100.1
+ description: site-1-nbr-1
+ password:
+ encryption: 3
+ key: 13D4D3549493D2877B1DC116EE27A6BE
+ remote_as: 65562
+ - neighbor_address: 198.51.100.2
+ remote_as: 65562
+ description: site-1-nbr-2
+ - vrf: site-2
+ local_as: 300
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 203.0.113.2
+ description: site-1-nbr-1
+ password:
+ encryption: 3
+ key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+ remote_as: 65568
+ neighbor_down:
+ fib_accelerate: True
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - router bgp 65563
+# - bestpath as-path multipath-relax
+# - bestpath compare-neighborid
+# - bestpath cost-community ignore
+# - confederation identifier 42
+# - log-neighbor-changes
+# - maxas-limit 20
+# - neighbor-down fib-accelerate
+# - router-id 192.168.1.1
+# - confederation peers 65020 65030 65040
+# - neighbor 192.168.1.100
+# - remote-as 65563
+# - affinity-group 160
+# - bmp-activate-server 1
+# - description NBR-1
+# - low-memory exempt
+# - neighbor 192.168.1.101
+# - remote-as 65563
+# - password 7 12090404011C03162E
+# - vrf site-1
+# - allocate-index 5000
+# - local-as 200
+# - log-neighbor-changes
+# - neighbor 198.51.100.1
+# - remote-as 65562
+# - description site-1-nbr-1
+# - password 3 13D4D3549493D2877B1DC116EE27A6BE
+# - neighbor 198.51.100.2
+# - remote-as 65562
+# - description site-1-nbr-2
+# - vrf site-2
+# - local-as 300
+# - log-neighbor-changes
+# - neighbor-down fib-accelerate
+# - neighbor 203.0.113.2
+# - remote-as 65568
+# - description site-1-nbr-1
+# - password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65040
+# bestpath as-path multipath-relax
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 20
+# log-neighbor-changes
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# neighbor 192.168.1.101
+# remote-as 65563
+# password 7 12090404011C03162E
+# vrf site-1
+# local-as 200
+# log-neighbor-changes
+# allocate-index 5000
+# neighbor 198.51.100.1
+# remote-as 65562
+# description site-1-nbr-1
+# password 3 13D4D3549493D2877B1DC116EE27A6BE
+# neighbor 198.51.100.2
+# remote-as 65562
+# description site-1-nbr-2
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# remote-as 65568
+# description site-1-nbr-1
+# password 3 AF92F4C16A0A0EC5BDF56CF58BC030F6
+
+- name: Parse externally provided BGP config
+ cisco.nxos.nxos_bgp_global:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# as_number: '65563'
+# bestpath:
+# as_path:
+# multipath_relax: true
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65040'
+# log_neighbor_changes: true
+# maxas_limit: 20
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# - neighbor_address: 192.168.1.101
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - allocate_index: 5000
+# local_as: '200'
+# log_neighbor_changes: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 198.51.100.1
+# password:
+# encryption: 3
+# key: 13D4D3549493D2877B1DC116EE27A6BE
+# remote_as: '65562'
+# - description: site-1-nbr-2
+# neighbor_address: 198.51.100.2
+# remote_as: '65562'
+# vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - description: site-1-nbr-1
+# neighbor_address: 203.0.113.2
+# password:
+# encryption: 3
+# key: AF92F4C16A0A0EC5BDF56CF58BC030F6
+# remote_as: '65568'
+# vrf: site-2
+
+# Using gathered
+
+# existing config
+#
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65563
+# router-id 192.168.1.1
+# confederation identifier 42
+# confederation peers 65020 65030 65050
+# bestpath cost-community ignore
+# bestpath compare-neighborid
+# neighbor-down fib-accelerate
+# maxas-limit 40
+# neighbor 192.168.1.100
+# low-memory exempt
+# bmp-activate-server 1
+# remote-as 65563
+# description NBR-1
+# affinity-group 160
+# vrf site-1
+# vrf site-2
+# local-as 300
+# neighbor-down fib-accelerate
+# log-neighbor-changes
+# neighbor 203.0.113.2
+# password 7 12090404011C03162E
+
+- name: Gather BGP facts using gathered
+ cisco.nxos.nxos_bgp_global:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# as_number: '65563'
+# bestpath:
+# compare_neighborid: true
+# cost_community_ignore: true
+# confederation:
+# identifier: '42'
+# peers:
+# - '65020'
+# - '65030'
+# - '65050'
+# maxas_limit: 40
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - bmp_activate_server: 1
+# description: NBR-1
+# low_memory:
+# exempt: true
+# neighbor_address: 192.168.1.100
+# neighbor_affinity_group:
+# group_id: 160
+# remote_as: '65563'
+# router_id: 192.168.1.1
+# vrfs:
+# - vrf: site-1
+# - local_as: '300'
+# log_neighbor_changes: true
+# neighbor_down:
+# fib_accelerate: true
+# neighbors:
+# - neighbor_address: 203.0.113.2
+# password:
+# encryption: 7
+# key: 12090404011C03162E
+# vrf: site-2
+
+# Remove a neighbor having AF configurations with state replaced (will fail)
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# log-neighbor-changes
+# maxas-limit 20
+# router-id 198.51.100.2
+# neighbor 203.0.113.2
+# address-family ipv4 unicast
+# next-hop-self
+# remote-as 65538
+# affinity-group 160
+# description NBR-1
+# low-memory exempt
+# neighbor 192.0.2.1
+# remote-as 65537
+# password 7 12090404011C03162E
+
+- name: Remove a neighbor having AF configurations (should fail)
+ cisco.nxos.nxos_bgp_global:
+ config:
+ as_number: 65536
+ router_id: 198.51.100.2
+ maxas_limit: 20
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 192.0.2.1
+ remote_as: 65537
+ password:
+ encryption: 7
+ key: 12090404011C03162E
+ state: replaced
+
+# Task output (redacted)
+# -----------------------
+# fatal: [Nexus9000v]: FAILED! => changed=false
+# msg: Neighbor 203.0.113.2 has address-family configurations.
+# Please use the nxos_bgp_neighbor_af module to remove those first.
+
+# Remove a VRF having AF configurations with state replaced (will fail)
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# log-neighbor-changes
+# maxas-limit 20
+# router-id 198.51.100.2
+# neighbor 192.0.2.1
+# remote-as 65537
+# password 7 12090404011C03162E
+# vrf site-1
+# address-family ipv4 unicast
+# default-information originate
+# neighbor 203.0.113.2
+# remote-as 65538
+# affinity-group 160
+# description NBR-1
+# low-memory exempt
+# vrf site-2
+# neighbor-down fib-accelerate
+
+- name: Remove a VRF having AF configurations (should fail)
+ cisco.nxos.nxos_bgp_global:
+ config:
+ as_number: 65536
+ router_id: 198.51.100.2
+ maxas_limit: 20
+ log_neighbor_changes: True
+ neighbors:
+ - neighbor_address: 192.0.2.1
+ remote_as: 65537
+ password:
+ encryption: 7
+ key: 12090404011C03162E
+ vrfs:
+ - vrf: site-2
+ neighbor_down:
+ fib_accelerate: True
+ state: replaced
+
+# Task output (redacted)
+# -----------------------
+# fatal: [Nexus9000v]: FAILED! => changed=false
+# msg: VRF site-1 has address-family configurations.
+# Please use the nxos_bgp_af module to remove those first.
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - router bgp 65563
+ - maxas-limit 20
+ - router-id 192.168.1.1
+ - confederation peers 65020 65030 65040
+ - neighbor 192.168.1.100
+ - remote-as 65563
+ - affinity-group 160
+ - bmp-activate-server 1
+ - description NBR-1
+ - low-memory exempt
+ - vrf site-1
+ - log-neighbor-changes
+ - neighbor 198.51.100.1
+ - remote-as 65562
+ - description site-1-nbr-1
+ - password 3 13D4D3549493D2877B1DC116EE27A6BE
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_global.bgp_global import (
+ Bgp_globalArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bgp_global.bgp_global import (
+ Bgp_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Bgp_globalArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Bgp_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor.py
new file mode 100644
index 00000000..2c5283ca
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor.py
@@ -0,0 +1,569 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bgp_neighbor
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2023-01-27) Manages BGP neighbors configurations.
+description:
+- Manages BGP neighbors configurations on NX-OS switches.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+deprecated:
+ alternative: nxos_bgp_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2023-01-27'
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) removes the whole BGP neighbor configuration.
+- Default, where supported, restores params default value.
+options:
+ asn:
+ description:
+ - BGP autonomous system number. Valid values are string, Integer in ASPLAIN or
+ ASDOT notation.
+ required: true
+ type: str
+ vrf:
+ description:
+ - Name of the VRF. The name 'default' is a valid VRF representing the global bgp.
+ default: default
+ type: str
+ neighbor:
+ description:
+ - Neighbor Identifier. Valid values are string. Neighbors may use IPv4 or IPv6
+ notation, with or without prefix length.
+ required: true
+ type: str
+ description:
+ description:
+ - Description of the neighbor.
+ type: str
+ bfd:
+ description:
+ - Enables/Disables BFD for a given neighbor.
+ - "Dependency: ''feature bfd''"
+ type: str
+ choices:
+ - enable
+ - disable
+ connected_check:
+ description:
+ - Configure whether or not to check for directly connected peer.
+ type: bool
+ capability_negotiation:
+ description:
+ - Configure whether or not to negotiate capability with this neighbor.
+ type: bool
+ dynamic_capability:
+ description:
+ - Configure whether or not to enable dynamic capability.
+ type: bool
+ ebgp_multihop:
+ description:
+ - Specify multihop TTL for a remote peer. Valid values are integers between 2
+ and 255, or keyword 'default' to disable this property.
+ type: str
+ local_as:
+ description:
+ - Specify the local-as number for the eBGP neighbor. Valid values are String or
+ Integer in ASPLAIN or ASDOT notation, or 'default', which means not to configure
+ it.
+ type: str
+ log_neighbor_changes:
+ description:
+ - Specify whether or not to enable log messages for neighbor up/down event.
+ choices:
+ - enable
+ - disable
+ - inherit
+ type: str
+ low_memory_exempt:
+ description:
+ - Specify whether or not to shut down this neighbor under memory pressure.
+ type: bool
+ maximum_peers:
+ description:
+ - Specify Maximum number of peers for this neighbor prefix Valid values are between
+ 1 and 1000, or 'default', which does not impose the limit. Note that this parameter
+ is accepted only on neighbors with address/prefix.
+ type: str
+ pwd:
+ description:
+ - Specify the password for neighbor. Valid value is string.
+ type: str
+ pwd_type:
+ description:
+ - Specify the encryption type the password will use. Valid values are '3des' or
+ 'cisco_type_7' encryption or keyword 'default'.
+ choices:
+ - 3des
+ - cisco_type_7
+ - default
+ type: str
+ remote_as:
+ description:
+ - Specify Autonomous System Number of the neighbor. Valid values are String or
+ Integer in ASPLAIN or ASDOT notation, or 'default', which means not to configure
+ it.
+ type: str
+ remove_private_as:
+ description:
+ - Specify the config to remove private AS number from outbound updates. Valid
+ values are 'enable' to enable this config, 'disable' to disable this config,
+ 'all' to remove all private AS number, or 'replace-as', to replace the private
+ AS number.
+ choices:
+ - enable
+ - disable
+ - all
+ - replace-as
+ type: str
+ shutdown:
+ description:
+ - Configure to administratively shutdown this neighbor.
+ type: bool
+ suppress_4_byte_as:
+ description:
+ - Configure to suppress 4-byte AS Capability.
+ type: bool
+ timers_keepalive:
+ description:
+ - Specify keepalive timer value. Valid values are integers between 0 and 3600
+ in terms of seconds, or 'default', which is 60.
+ type: str
+ timers_holdtime:
+ description:
+ - Specify holdtime timer value. Valid values are integers between 0 and 3600 in
+ terms of seconds, or 'default', which is 180.
+ type: str
+ transport_passive_only:
+ description:
+ - Specify whether or not to only allow passive connection setup. Valid values
+ are 'true', 'false', and 'default', which defaults to 'false'. This property
+ can only be configured when the neighbor is in 'ip' address format without prefix
+ length.
+ type: bool
+ update_source:
+ description:
+ - Specify source interface of BGP session and updates.
+ type: str
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ peer_type:
+ description:
+ - Specify the peer type for BGP session.
+ choices:
+ - fabric_border_leaf
+ - fabric_external
+ - disable
+ type: str
+ version_added: 1.1.0
+"""
+EXAMPLES = """
+# create a new neighbor
+- cisco.nxos.nxos_bgp_neighbor:
+ asn: 65535
+ neighbor: 192.0.2.3
+ local_as: 20
+ remote_as: 30
+ bfd: enable
+ description: just a description
+ update_source: Ethernet1/3
+ state: present
+ peer_type: fabric_external
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["router bgp 65535", "neighbor 192.0.2.3",
+ "remote-as 30", "update-source Ethernet1/3",
+ "description just a description", "local-as 20", "peer-type fabric-external"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+BOOL_PARAMS = [
+ "capability_negotiation",
+ "shutdown",
+ "connected_check",
+ "dynamic_capability",
+ "low_memory_exempt",
+ "suppress_4_byte_as",
+ "transport_passive_only",
+]
+PARAM_TO_COMMAND_KEYMAP = {
+ "asn": "router bgp",
+ "bfd": "bfd",
+ "capability_negotiation": "dont-capability-negotiate",
+ "connected_check": "disable-connected-check",
+ "description": "description",
+ "dynamic_capability": "dynamic-capability",
+ "ebgp_multihop": "ebgp-multihop",
+ "local_as": "local-as",
+ "log_neighbor_changes": "log-neighbor-changes",
+ "low_memory_exempt": "low-memory exempt",
+ "maximum_peers": "maximum-peers",
+ "neighbor": "neighbor",
+ "pwd": "password",
+ "pwd_type": "password",
+ "remote_as": "remote-as",
+ "remove_private_as": "remove-private-as",
+ "shutdown": "shutdown",
+ "suppress_4_byte_as": "capability suppress 4-byte-as",
+ "timers_keepalive": "timers",
+ "timers_holdtime": "timers",
+ "transport_passive_only": "transport connection-mode passive",
+ "update_source": "update-source",
+ "vrf": "vrf",
+ "peer_type": "peer-type",
+}
+PARAM_TO_DEFAULT_KEYMAP = {
+ "bfd": "disable",
+ "shutdown": False,
+ "dynamic_capability": True,
+ "timers_keepalive": 60,
+ "timers_holdtime": 180,
+ "peer_type": "disable",
+}
+
+
+def get_value(arg, config):
+ command = PARAM_TO_COMMAND_KEYMAP[arg]
+ has_command = re.search(r"^\s+{0}$".format(command), config, re.M)
+ has_command_val = re.search(r"(?:\s+{0}\s*)(?P<value>.*)$".format(command), config, re.M)
+
+ if arg == "dynamic_capability":
+ has_no_command = re.search(r"\s+no\s{0}\s*$".format(command), config, re.M)
+ value = True
+ if has_no_command:
+ value = False
+ elif arg in BOOL_PARAMS:
+ value = False
+ if has_command:
+ value = True
+ elif arg == "log_neighbor_changes":
+ value = ""
+ if has_command:
+ value = "enable"
+ elif has_command_val:
+ value = "disable"
+
+ elif arg == "remove_private_as":
+ value = "disable"
+ if has_command:
+ value = "enable"
+ elif has_command_val:
+ value = has_command_val.group("value")
+ elif arg == "bfd":
+ value = "enable" if has_command else "disable"
+
+ elif arg == "peer_type":
+ value = "disable"
+ if has_command_val:
+ value = has_command_val.group("value").replace("-", "_")
+ else:
+ value = ""
+
+ if has_command_val:
+ value = has_command_val.group("value")
+
+ if command in ["timers", "password"]:
+ split_value = value.split()
+ value = ""
+
+ if arg in ["timers_keepalive", "pwd_type"]:
+ value = split_value[0]
+ elif arg in ["timers_holdtime", "pwd"] and len(split_value) == 2:
+ value = split_value[1]
+
+ return value
+
+
+def get_existing(module, args, warnings):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+
+ asn_regex = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.S)
+ match_asn = asn_regex.match(str(netcfg))
+
+ if match_asn:
+ existing_asn = match_asn.group("existing_asn")
+ parents = ["router bgp {0}".format(existing_asn)]
+
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("neighbor {0}".format(module.params["neighbor"]))
+ config = netcfg.get_section(parents)
+ if config:
+ for arg in args:
+ if arg not in ["asn", "vrf", "neighbor"]:
+ existing[arg] = get_value(arg, config)
+
+ existing["asn"] = existing_asn
+ existing["neighbor"] = module.params["neighbor"]
+ existing["vrf"] = module.params["vrf"]
+ else:
+ warnings.append("The BGP process didn't exist but the task just created it.")
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key in table:
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = table.get(key)
+
+ return new_dict
+
+
+def state_present(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+
+ for key, value in proposed_commands.items():
+ if value is True:
+ commands.append(key)
+ elif value is False:
+ commands.append("no {0}".format(key))
+ elif value == "default":
+ if existing_commands.get(key):
+ if key == "password":
+ commands.append("no password")
+ else:
+ existing_value = existing_commands.get(key)
+ commands.append("no {0} {1}".format(key, existing_value))
+ else:
+ if key == "log-neighbor-changes":
+ if value == "enable":
+ commands.append("{0}".format(key))
+ elif value == "disable":
+ commands.append("{0} {1}".format(key, value))
+ elif value == "inherit":
+ if existing_commands.get(key):
+ commands.append("no {0}".format(key))
+ elif key == "password":
+ pwd_type = module.params["pwd_type"]
+ if pwd_type == "3des":
+ pwd_type = 3
+ else:
+ pwd_type = 7
+ command = "{0} {1} {2}".format(key, pwd_type, value)
+ if command not in commands:
+ commands.append(command)
+ elif key == "remove-private-as":
+ if value == "enable":
+ command = "{0}".format(key)
+ commands.append(command)
+ elif value == "disable":
+ if existing_commands.get(key) != "disable":
+ command = "no {0}".format(key)
+ commands.append(command)
+ else:
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+ elif key == "timers":
+ if proposed["timers_keepalive"] != PARAM_TO_DEFAULT_KEYMAP.get(
+ "timers_keepalive",
+ ) or proposed["timers_holdtime"] != PARAM_TO_DEFAULT_KEYMAP.get("timers_holdtime"):
+ command = "timers {0} {1}".format(
+ proposed["timers_keepalive"],
+ proposed["timers_holdtime"],
+ )
+ if command not in commands:
+ commands.append(command)
+ elif key == "bfd":
+ no_cmd = "no " if value == "disable" else ""
+ commands.append(no_cmd + key)
+ elif key == "peer-type":
+ if value == "disable":
+ if existing_commands.get(key) != "disable":
+ command = "no {0}".format(key)
+ commands.append(command)
+ elif value == "fabric_external":
+ ptype = "fabric-external"
+ command = "{0} {1}".format(key, ptype)
+ commands.append(command)
+ elif value == "fabric_border_leaf":
+ ptype = "fabric-border-leaf"
+ command = "{0} {1}".format(key, ptype)
+ commands.append(command)
+ else:
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+
+ if commands:
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("neighbor {0}".format(module.params["neighbor"]))
+
+ # make sure that local-as is the last command in the list.
+ local_as_command = "local-as {0}".format(module.params["local_as"])
+ if local_as_command in commands:
+ commands.remove(local_as_command)
+ commands.append(local_as_command)
+ candidate.add(commands, parents=parents)
+
+
+def state_absent(module, existing, proposed, candidate):
+ commands = []
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ commands.append("no neighbor {0}".format(module.params["neighbor"]))
+ candidate.add(commands, parents=parents)
+
+
+def main():
+ argument_spec = dict(
+ asn=dict(required=True, type="str"),
+ vrf=dict(required=False, type="str", default="default"),
+ neighbor=dict(required=True, type="str"),
+ description=dict(required=False, type="str"),
+ bfd=dict(required=False, type="str", choices=["enable", "disable"]),
+ capability_negotiation=dict(required=False, type="bool"),
+ connected_check=dict(required=False, type="bool"),
+ dynamic_capability=dict(required=False, type="bool"),
+ ebgp_multihop=dict(required=False, type="str"),
+ local_as=dict(required=False, type="str"),
+ log_neighbor_changes=dict(
+ required=False,
+ type="str",
+ choices=["enable", "disable", "inherit"],
+ ),
+ low_memory_exempt=dict(required=False, type="bool"),
+ maximum_peers=dict(required=False, type="str"),
+ pwd=dict(required=False, type="str"),
+ pwd_type=dict(
+ required=False,
+ type="str",
+ choices=["3des", "cisco_type_7", "default"],
+ ),
+ remote_as=dict(required=False, type="str"),
+ remove_private_as=dict(
+ required=False,
+ type="str",
+ choices=["enable", "disable", "all", "replace-as"],
+ ),
+ shutdown=dict(required=False, type="bool"),
+ suppress_4_byte_as=dict(required=False, type="bool"),
+ timers_keepalive=dict(required=False, type="str"),
+ timers_holdtime=dict(required=False, type="str"),
+ transport_passive_only=dict(required=False, type="bool"),
+ update_source=dict(required=False, type="str"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ peer_type=dict(
+ required=False,
+ type="str",
+ choices=["disable", "fabric_border_leaf", "fabric_external"],
+ ),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_together=[
+ ["timers_holdtime", "timers_keepalive"],
+ ["pwd", "pwd_type"],
+ ],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = dict(changed=False, warnings=warnings)
+
+ state = module.params["state"]
+
+ if module.params["pwd_type"] == "default":
+ module.params["pwd_type"] = "0"
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing = get_existing(module, args, warnings)
+
+ if existing.get("asn") and state == "present":
+ if existing["asn"] != module.params["asn"]:
+ module.fail_json(
+ msg="Another BGP ASN already exists.",
+ proposed_asn=module.params["asn"],
+ existing_asn=existing.get("asn"),
+ )
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key not in ["asn", "vrf", "neighbor", "pwd_type"]:
+ if str(value).lower() == "default":
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default")
+ if key == "bfd":
+ if existing.get("bfd", "disable") != value:
+ proposed[key] = value
+ elif existing.get(key) != value:
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present":
+ state_present(module, existing, proposed, candidate)
+ elif state == "absent" and existing:
+ state_absent(module, existing, proposed, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ if not module.check_mode:
+ load_config(module, candidate)
+ result["changed"] = True
+ result["commands"] = candidate
+ else:
+ result["commands"] = []
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_address_family.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_address_family.py
new file mode 100644
index 00000000..967f8ee4
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_address_family.py
@@ -0,0 +1,1155 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_bgp_neighbor_address_family
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_bgp_neighbor_address_family
+short_description: BGP Neighbor Address Family resource module.
+description:
+- This module manages BGP Neighbor Address Family configuration on devices running Cisco NX-OS.
+version_added: 2.0.0
+notes:
+- Tested against NX-OS 9.3.6.
+- Unsupported for Cisco MDS
+- For managing BGP address family configurations please use
+ the M(cisco.nxos.nxos_bgp_address_family) module.
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^router bgp').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: BGP Neighbor AF configuration.
+ type: dict
+ suboptions:
+ as_number:
+ description: Autonomous System Number of the router.
+ type: str
+ neighbors: &nbr
+ description: A list of BGP Neighbor AF configuration.
+ type: list
+ elements: dict
+ suboptions:
+ neighbor_address:
+ description: IP/IPv6 address of the neighbor.
+ type: str
+ required: True
+ address_family:
+ description: BGP Neighbor Address Family related configurations.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description: Address Family indicator.
+ type: str
+ choices: ["ipv4", "ipv6", "link-state", "vpnv4", "vpnv6", "l2vpn"]
+ required: True
+ safi:
+ description: Sub Address Family indicator.
+ type: str
+ choices: ["unicast", "multicast", "mvpn", "evpn"]
+ advertise_map:
+ description: Specify route-map for conditional advertisement.
+ type: dict
+ suboptions:
+ route_map:
+ description: Route-map name.
+ type: str
+ required: True
+ exist_map:
+ description: Condition route-map to advertise only when prefix in condition exists.
+ type: str
+ non_exist_map:
+ description: Condition route-map to advertise only when prefix in condition does not exist.
+ type: str
+ advertisement_interval:
+ description: Minimum interval between sending BGP routing updates.
+ type: int
+ allowas_in:
+ description: Accept as-path with my AS present in it.
+ type: dict
+ suboptions:
+ set:
+ description: Activate allowas-in property.
+ type: bool
+ max_occurences:
+ description: Number of occurrences of AS number, default is 3.
+ type: int
+ as_override:
+ description: Override matching AS-number while sending update.
+ type: bool
+ capability:
+ description: Advertise capability to the peer.
+ type: dict
+ suboptions:
+ additional_paths:
+ description: Additional paths capability.
+ type: dict
+ suboptions:
+ receive:
+ description: Additional paths Receive capability.
+ type: str
+ choices: ["enable", "disable"]
+ send:
+ description: Additional paths Send capability.
+ type: str
+ choices: ["enable", "disable"]
+ default_originate:
+ description: Originate a default toward this peer.
+ type: dict
+ suboptions:
+ set:
+ description: Set default-originate attribute.
+ type: bool
+ route_map:
+ description: Route-map to specify criteria for originating default.
+ type: str
+ disable_peer_as_check:
+ description: Disable checking of peer AS-number while advertising.
+ type: bool
+ filter_list:
+ description: Name of filter-list.
+ type: dict
+ suboptions:
+ inbound:
+ description: Apply policy to incoming routes.
+ type: str
+ outbound:
+ description: Apply policy to outgoing routes.
+ type: str
+ inherit:
+ description: Inherit a template.
+ type: dict
+ suboptions:
+ template:
+ description: Template name.
+ type: str
+ sequence:
+ description: Sequence number.
+ type: int
+ maximum_prefix:
+ description: Maximum number of prefixes from this neighbor.
+ type: dict
+ suboptions:
+ max_prefix_limit:
+ description: Maximum prefix limit.
+ type: int
+ generate_warning_threshold:
+ description: Threshold percentage at which to generate a warning.
+ type: int
+ restart_interval:
+ description: Restart bgp connection after limit is exceeded.
+ type: int
+ warning_only:
+ description: Only give a warning message when limit is exceeded.
+ type: bool
+ next_hop_self:
+ description: Set our address as nexthop (non-reflected).
+ type: dict
+ suboptions:
+ set:
+ description: Set next-hop-self attribute.
+ type: bool
+ all_routes:
+ description: Set our address as nexthop for all routes.
+ type: bool
+ next_hop_third_party:
+ description: Compute a third-party nexthop if possible.
+ type: bool
+ prefix_list:
+ description: Apply prefix-list.
+ type: dict
+ suboptions:
+ inbound:
+ description: Apply policy to incoming routes.
+ type: str
+ outbound:
+ description: Apply policy to outgoing routes.
+ type: str
+ rewrite_evpn_rt_asn:
+ description: Auto generate RTs for EBGP neighbor.
+ type: bool
+ route_map:
+ description: Apply route-map to neighbor.
+ type: dict
+ suboptions:
+ inbound:
+ description: Apply policy to incoming routes.
+ type: str
+ outbound:
+ description: Apply policy to outgoing routes.
+ type: str
+ route_reflector_client:
+ description: Configure a neighbor as Route reflector client.
+ type: bool
+ send_community:
+ description: Send Community attribute to this neighbor.
+ type: dict
+ suboptions:
+ set:
+ description: Set send-community attribute.
+ type: bool
+ extended:
+ description: Send Extended Community attribute.
+ type: bool
+ standard:
+ description: Send Standard Community attribute.
+ type: bool
+ both:
+ description: Send Standard and Extended Community attributes.
+ type: bool
+ soft_reconfiguration_inbound:
+ description: Soft reconfiguration.
+ type: dict
+ suboptions:
+ set:
+ description: Set soft-reconfiguration inbound attribute.
+ type: bool
+ always:
+ description: Always perform inbound soft reconfiguration.
+ type: bool
+ soo:
+ description: Specify Site-of-origin extcommunity.
+ type: str
+ suppress_inactive:
+ description: Advertise only active routes to peer.
+ type: bool
+ unsuppress_map:
+ description: Route-map to selectively unsuppress suppressed routes.
+ type: str
+ weight:
+ description: Set default weight for routes from this neighbor.
+ type: int
+ vrfs:
+ description: Virtual Router Context.
+ type: list
+ elements: dict
+ suboptions:
+ vrf:
+ description: VRF name.
+ type: str
+ neighbors: *nbr
+ state:
+ description:
+ - The state the configuration should be left in.
+ - State I(deleted) only removes BGP attributes that this modules
+ manages and does not negate the BGP process completely.
+ - Refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# Nexus9000v#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_bgp_neighbor_address_family: &id001
+ config:
+ as_number: 65536
+ neighbors:
+ - neighbor_address: 192.0.2.32
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ maximum_prefix:
+ max_prefix_limit: 20
+ generate_warning_threshold: 75
+ weight: 100
+ prefix_list:
+ inbound: rmap1
+ outbound: rmap2
+ - afi: ipv6
+ safi: unicast
+ - neighbor_address: 192.0.2.33
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ inherit:
+ template: BasePolicy
+ sequence: 200
+ vrfs:
+ - vrf: site-1
+ neighbors:
+ - neighbor_address: 203.0.113.1
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ suppress_inactive: True
+ next_hop_self:
+ set: True
+ - neighbor_address: 203.0.113.2
+ address_family:
+ - afi: ipv6
+ safi: unicast
+ - afi: ipv4
+ safi: multicast
+ send_community:
+ set: True
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - address-family ipv4 unicast
+# - maximum-prefix 20 75
+# - weight 100
+# - prefix-list rmap1 in
+# - prefix-list rmap2 out
+# - address-family ipv6 unicast
+# - neighbor 192.0.2.33
+# - address-family ipv4 multicast
+# - inherit peer-policy BasePolicy 200
+# - vrf site-1
+# - neighbor 203.0.113.1
+# - address-family ipv4 unicast
+# - suppress-inactive
+# - next-hop-self
+# - neighbor 203.0.113.2
+# - address-family ipv6 unicast
+# - address-family ipv4 multicast
+# - send-community
+#
+# after:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+# Using replaced
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+- name: Replace specified neighbor AFs with given configuration
+ cisco.nxos.nxos_bgp_neighbor_address_family: &replaced
+ config:
+ as_number: 65536
+ neighbors:
+ - neighbor_address: 192.0.2.32
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ weight: 110
+ - afi: ipv6
+ safi: unicast
+ - neighbor_address: 192.0.2.33
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ inherit:
+ template: BasePolicy
+ sequence: 200
+ vrfs:
+ - vrf: site-1
+ neighbors:
+ - neighbor_address: 203.0.113.1
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ - neighbor_address: 203.0.113.2
+ address_family:
+ - afi: ipv6
+ safi: unicast
+ - afi: ipv4
+ safi: multicast
+ send_community:
+ set: True
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+#
+# commands:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - address-family ipv4 unicast
+# - no maximum-prefix 20 75
+# - weight 110
+# - no prefix-list rmap1 in
+# - no prefix-list rmap2 out
+# - vrf site-1
+# - neighbor 203.0.113.1
+# - address-family ipv4 unicast
+# - no suppress-inactive
+# - no next-hop-self
+#
+# after:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# weight: 110
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# weight 110
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+# Using overridden
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+- name: Override all BGP AF configuration with provided configuration
+ cisco.nxos.nxos_bgp_neighbor_address_family:
+ config:
+ as_number: 65536
+ neighbors:
+ - neighbor_address: 192.0.2.32
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ vrfs:
+ - vrf: site-1
+ neighbors:
+ - neighbor_address: 203.0.113.1
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ suppress_inactive: True
+ next_hop_self:
+ set: True
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+#
+# commands:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - address-family ipv4 unicast
+# - no maximum-prefix 20 75
+# - no weight 100
+# - no prefix-list rmap1 in
+# - no prefix-list rmap2 out
+# - no address-family ipv6 unicast
+# - neighbor 192.0.2.33
+# - no address-family ipv4 multicast
+# - vrf site-1
+# - neighbor 203.0.113.2
+# - no address-family ipv4 multicast
+# - no address-family ipv6 unicast
+#
+# after:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: True
+# next_hop_self:
+# set: True
+
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+
+# Using deleted to remove specified neighbor AFs
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+- name: Delete BGP configs handled by this module
+ cisco.nxos.nxos_bgp_neighbor_address_family:
+ config:
+ as_number: 65536
+ neighbors:
+ - neighbor_address: 192.0.2.32
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ vrfs:
+ - vrf: site-1
+ neighbors:
+ - neighbor_address: 203.0.113.2
+ address_family:
+ - afi: ipv6
+ safi: unicast
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+#
+# commands:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - no address-family ipv4 unicast
+# - vrf site-1
+# - neighbor 203.0.113.2
+# - no address-family ipv6 unicast
+#
+# after:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+#
+
+# Using deleted to remove all neighbor AFs
+
+# Before state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+#
+
+- name: Delete all BGP neighbor AF configs handled by this module
+ cisco.nxos.nxos_bgp_neighbor_address_family:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+#
+# commands:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - no address-family ipv4 unicast
+# - no address-family ipv6 unicast
+# - neighbor 192.0.2.33
+# - no address-family ipv4 multicast
+# - vrf site-1
+# - neighbor 203.0.113.1
+# - no address-family ipv4 unicast
+# - neighbor 203.0.113.2
+# - no address-family ipv6 unicast
+# - no address-family ipv4 multicast
+#
+# after:
+# as_number: "65536"
+#
+# After state:
+# -------------
+# Nexus9000v# show running-config | section "^router bgp"
+# router bgp 65536
+# neighbor 192.0.2.32
+# neighbor 192.0.2.33
+# vrf site-1
+# neighbor 203.0.113.1
+# neighbor 203.0.113.2
+#
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_bgp_neighbor_address_family:
+ config:
+ as_number: 65536
+ neighbors:
+ - neighbor_address: 192.0.2.32
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ maximum_prefix:
+ max_prefix_limit: 20
+ generate_warning_threshold: 75
+ weight: 100
+ prefix_list:
+ inbound: rmap1
+ outbound: rmap2
+ - afi: ipv6
+ safi: unicast
+ - neighbor_address: 192.0.2.33
+ address_family:
+ - afi: ipv4
+ safi: multicast
+ inherit:
+ template: BasePolicy
+ sequence: 200
+ vrfs:
+ - vrf: site-1
+ neighbors:
+ - neighbor_address: 203.0.113.1
+ address_family:
+ - afi: ipv4
+ safi: unicast
+ suppress_inactive: True
+ next_hop_self:
+ set: True
+ - neighbor_address: 203.0.113.2
+ address_family:
+ - afi: ipv6
+ safi: unicast
+ - afi: ipv4
+ safi: multicast
+ send_community:
+ set: True
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - router bgp 65536
+# - neighbor 192.0.2.32
+# - address-family ipv4 unicast
+# - maximum-prefix 20 75
+# - weight 100
+# - prefix-list rmap1 in
+# - prefix-list rmap2 out
+# - address-family ipv6 unicast
+# - neighbor 192.0.2.33
+# - address-family ipv4 multicast
+# - inherit peer-policy BasePolicy 200
+# - vrf site-1
+# - neighbor 203.0.113.1
+# - address-family ipv4 unicast
+# - suppress-inactive
+# - next-hop-self
+# - neighbor 203.0.113.2
+# - address-family ipv6 unicast
+# - address-family ipv4 multicast
+# - send-community
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# router bgp 65536
+# neighbor 192.0.2.32
+# address-family ipv4 unicast
+# maximum-prefix 20 75
+# weight 100
+# prefix-list rmap1 in
+# prefix-list rmap2 out
+# address-family ipv6 unicast
+# neighbor 192.0.2.33
+# address-family ipv4 multicast
+# inherit peer-policy BasePolicy 200
+# vrf site-1
+# neighbor 203.0.113.1
+# address-family ipv4 unicast
+# suppress-inactive
+# next-hop-self
+# neighbor 203.0.113.2
+# address-family ipv4 multicast
+# send-community
+# address-family ipv6 unicast
+
+- name: Parse externally provided BGP neighbor AF config
+ register: result
+ cisco.nxos.nxos_bgp_neighbor_address_family:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# as_number: "65536"
+# neighbors:
+# - neighbor_address: 192.0.2.32
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# maximum_prefix:
+# max_prefix_limit: 20
+# generate_warning_threshold: 75
+# weight: 100
+# prefix_list:
+# inbound: rmap1
+# outbound: rmap2
+# - afi: ipv6
+# safi: unicast
+# - neighbor_address: 192.0.2.33
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# inherit:
+# template: BasePolicy
+# sequence: 200
+# vrfs:
+# - vrf: site-1
+# neighbors:
+# - neighbor_address: 203.0.113.1
+# address_family:
+# - afi: ipv4
+# safi: unicast
+# suppress_inactive: true
+# next_hop_self:
+# set: true
+# - neighbor_address: 203.0.113.2
+# address_family:
+# - afi: ipv4
+# safi: multicast
+# send_community:
+# set: True
+# - afi: ipv6
+# safi: unicast
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.bgp_neighbor_address_family.bgp_neighbor_address_family import (
+ Bgp_neighbor_address_familyArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.bgp_neighbor_address_family.bgp_neighbor_address_family import (
+ Bgp_neighbor_address_family,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Bgp_neighbor_address_familyArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Bgp_neighbor_address_family(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_af.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_af.py
new file mode 100644
index 00000000..cc5624ee
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_bgp_neighbor_af.py
@@ -0,0 +1,781 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_bgp_neighbor_af
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2023-02-24) Manages BGP address-family's neighbors configuration.
+description:
+- Manages BGP address-family's neighbors configurations on NX-OS switches.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+deprecated:
+ alternative: nxos_bgp_neighbor_address_family
+ why: Updated module released with more functionality.
+ removed_at_date: '2023-02-24'
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) removes the whole BGP address-family's neighbor configuration.
+- Default, when supported, removes properties
+- In order to default maximum-prefix configuration, only C(max_prefix_limit=default)
+ is needed.
+options:
+ asn:
+ description:
+ - BGP autonomous system number. Valid values are String, Integer in ASPLAIN or
+ ASDOT notation.
+ required: true
+ type: str
+ vrf:
+ description:
+ - Name of the VRF. The name 'default' is a valid VRF representing the global bgp.
+ default: default
+ type: str
+ neighbor:
+ description:
+ - Neighbor Identifier. Valid values are string. Neighbors may use IPv4 or IPv6
+ notation, with or without prefix length.
+ required: true
+ type: str
+ afi:
+ description:
+ - Address Family Identifier.
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ - vpnv4
+ - vpnv6
+ - l2vpn
+ type: str
+ safi:
+ description:
+ - Sub Address Family Identifier.
+ required: true
+ choices:
+ - unicast
+ - multicast
+ - evpn
+ type: str
+ additional_paths_receive:
+ description:
+ - Valid values are enable for basic command enablement; disable for disabling
+ the command at the neighbor af level (it adds the disable keyword to the basic
+ command); and inherit to remove the command at this level (the command value
+ is inherited from a higher BGP layer).
+ choices:
+ - enable
+ - disable
+ - inherit
+ type: str
+ additional_paths_send:
+ description:
+ - Valid values are enable for basic command enablement; disable for disabling
+ the command at the neighbor af level (it adds the disable keyword to the basic
+ command); and inherit to remove the command at this level (the command value
+ is inherited from a higher BGP layer).
+ choices:
+ - enable
+ - disable
+ - inherit
+ type: str
+ advertise_map_exist:
+ description:
+ - Conditional route advertisement. This property requires two route maps, an advertise-map
+ and an exist-map. Valid values are an array specifying both the advertise-map
+ name and the exist-map name, or simply 'default' e.g. ['my_advertise_map', 'my_exist_map'].
+ This command is mutually exclusive with the advertise_map_non_exist property.
+ type: list
+ elements: str
+ advertise_map_non_exist:
+ description:
+ - Conditional route advertisement. This property requires two route maps, an advertise-map
+ and an exist-map. Valid values are an array specifying both the advertise-map
+ name and the non-exist-map name, or simply 'default' e.g. ['my_advertise_map',
+ 'my_non_exist_map']. This command is mutually exclusive with the advertise_map_exist
+ property.
+ type: list
+ elements: str
+ allowas_in:
+ description:
+ - Activate allowas-in property
+ type: bool
+ allowas_in_max:
+ description:
+ - Max-occurrences value for allowas_in. Valid values are an integer value or 'default'.
+ This is mutually exclusive with allowas_in.
+ type: str
+ as_override:
+ description:
+ - Activate the as-override feature.
+ type: bool
+ default_originate:
+ description:
+ - Activate the default-originate feature.
+ type: bool
+ default_originate_route_map:
+ description:
+ - Route-map for the default_originate property. Valid values are a string defining
+ a route-map name, or 'default'. This is mutually exclusive with default_originate.
+ type: str
+ disable_peer_as_check:
+ description:
+ - Disable checking of peer AS-number while advertising
+ type: bool
+ filter_list_in:
+ description:
+ - Valid values are a string defining a filter-list name, or 'default'.
+ type: str
+ filter_list_out:
+ description:
+ - Valid values are a string defining a filter-list name, or 'default'.
+ type: str
+ max_prefix_limit:
+ description:
+ - maximum-prefix limit value. Valid values are an integer value or 'default'.
+ type: str
+ max_prefix_interval:
+ description:
+ - Optional restart interval. Valid values are an integer. Requires max_prefix_limit.
+ May not be combined with max_prefix_warning.
+ type: str
+ max_prefix_threshold:
+ description:
+ - Optional threshold percentage at which to generate a warning. Valid values are
+ an integer value. Requires max_prefix_limit.
+ type: str
+ max_prefix_warning:
+ description:
+ - Optional warning-only keyword. Requires max_prefix_limit. May not be combined
+ with max_prefix_interval.
+ type: bool
+ next_hop_self:
+ description:
+ - Activate the next-hop-self feature.
+ type: bool
+ next_hop_third_party:
+ description:
+ - Activate the next-hop-third-party feature.
+ type: bool
+ prefix_list_in:
+ description:
+ - Valid values are a string defining a prefix-list name, or 'default'.
+ type: str
+ prefix_list_out:
+ description:
+ - Valid values are a string defining a prefix-list name, or 'default'.
+ type: str
+ route_map_in:
+ description:
+ - Valid values are a string defining a route-map name, or 'default'.
+ type: str
+ route_map_out:
+ description:
+ - Valid values are a string defining a route-map name, or 'default'.
+ type: str
+ route_reflector_client:
+ description:
+ - Router reflector client.
+ type: bool
+ send_community:
+ description:
+ - send-community attribute.
+ choices:
+ - none
+ - both
+ - extended
+ - standard
+ - default
+ type: str
+ soft_reconfiguration_in:
+ description:
+ - Valid values are 'enable' for basic command enablement; 'always' to add the
+ always keyword to the basic command; and 'inherit' to remove the command at
+ this level (the command value is inherited from a higher BGP layer).
+ choices:
+ - enable
+ - always
+ - inherit
+ type: str
+ soo:
+ description:
+ - Site-of-origin. Valid values are a string defining a VPN extcommunity or 'default'.
+ type: str
+ suppress_inactive:
+ description:
+ - suppress-inactive feature.
+ type: bool
+ unsuppress_map:
+ description:
+ - unsuppress-map. Valid values are a string defining a route-map name or 'default'.
+ type: str
+ weight:
+ description:
+ - Weight value. Valid values are an integer value or 'default'.
+ type: str
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ rewrite_evpn_rt_asn:
+ description:
+ - Auto generate route targets for EBGP neighbor.
+ type: bool
+ version_added: 1.1.0
+"""
+EXAMPLES = """
+- name: configure RR client
+ cisco.nxos.nxos_bgp_neighbor_af:
+ asn: 65535
+ neighbor: 192.0.2.3
+ afi: ipv4
+ safi: unicast
+ route_reflector_client: true
+ state: present
+ rewrite_evpn_rt_asn: true
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["router bgp 65535", "neighbor 192.0.2.3",
+ "address-family ipv4 unicast", "route-reflector-client",
+ "rewrite-evpn-rt-asn"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+BOOL_PARAMS = [
+ "allowas_in",
+ "as_override",
+ "default_originate",
+ "disable_peer_as_check",
+ "next_hop_self",
+ "next_hop_third_party",
+ "route_reflector_client",
+ "suppress_inactive",
+ "rewrite_evpn_rt_asn",
+]
+PARAM_TO_COMMAND_KEYMAP = {
+ "afi": "address-family",
+ "asn": "router bgp",
+ "neighbor": "neighbor",
+ "additional_paths_receive": "capability additional-paths receive",
+ "additional_paths_send": "capability additional-paths send",
+ "advertise_map_exist": "advertise-map exist-map",
+ "advertise_map_non_exist": "advertise-map non-exist-map",
+ "allowas_in": "allowas-in",
+ "allowas_in_max": "allowas-in",
+ "as_override": "as-override",
+ "default_originate": "default-originate",
+ "default_originate_route_map": "default-originate route-map",
+ "disable_peer_as_check": "disable-peer-as-check",
+ "filter_list_in": "filter-list in",
+ "filter_list_out": "filter-list out",
+ "max_prefix_limit": "maximum-prefix",
+ "max_prefix_interval": "maximum-prefix interval",
+ "max_prefix_threshold": "maximum-prefix threshold",
+ "max_prefix_warning": "maximum-prefix warning",
+ "next_hop_self": "next-hop-self",
+ "next_hop_third_party": "next-hop-third-party",
+ "prefix_list_in": "prefix-list in",
+ "prefix_list_out": "prefix-list out",
+ "route_map_in": "route-map in",
+ "route_map_out": "route-map out",
+ "route_reflector_client": "route-reflector-client",
+ "safi": "address-family",
+ "send_community": "send-community",
+ "soft_reconfiguration_in": "soft-reconfiguration inbound",
+ "soo": "soo",
+ "suppress_inactive": "suppress-inactive",
+ "unsuppress_map": "unsuppress-map",
+ "weight": "weight",
+ "vrf": "vrf",
+ "rewrite_evpn_rt_asn": "rewrite-evpn-rt-asn",
+}
+
+
+def get_value(arg, config, module):
+ custom = [
+ "additional_paths_send",
+ "additional_paths_receive",
+ "max_prefix_limit",
+ "max_prefix_interval",
+ "max_prefix_threshold",
+ "max_prefix_warning",
+ "send_community",
+ "soft_reconfiguration_in",
+ ]
+ command = PARAM_TO_COMMAND_KEYMAP[arg]
+ has_command = re.search(r"^\s+{0}\s*".format(command), config, re.M)
+ has_command_val = re.search(r"(?:{0}\s)(?P<value>.*)$".format(command), config, re.M)
+ value = ""
+
+ if arg in custom:
+ value = get_custom_value(arg, config, module)
+
+ elif arg == "next_hop_third_party":
+ has_no_command = re.search(r"^\s+no\s+{0}\s*$".format(command), config, re.M)
+ value = False
+ if not has_no_command:
+ value = True
+
+ elif arg in BOOL_PARAMS:
+ value = False
+ if has_command:
+ value = True
+
+ elif command.startswith("advertise-map"):
+ value = []
+ has_adv_map = re.search(
+ r"{0}\s(?P<value1>.*)\s{1}\s(?P<value2>.*)$".format(*command.split()),
+ config,
+ re.M,
+ )
+ if has_adv_map:
+ value = list(has_adv_map.groups())
+
+ elif command.split()[0] in ["filter-list", "prefix-list", "route-map"]:
+ has_cmd_direction_val = re.search(
+ r"{0}\s(?P<value>.*)\s{1}$".format(*command.split()),
+ config,
+ re.M,
+ )
+ if has_cmd_direction_val:
+ value = has_cmd_direction_val.group("value")
+
+ elif has_command_val:
+ value = has_command_val.group("value")
+
+ return value
+
+
+def get_custom_value(arg, config, module):
+ command = PARAM_TO_COMMAND_KEYMAP.get(arg)
+ splitted_config = config.splitlines()
+ value = ""
+
+ if arg.startswith("additional_paths"):
+ value = "inherit"
+ for line in splitted_config:
+ if command in line:
+ if "disable" in line:
+ value = "disable"
+ else:
+ value = "enable"
+ elif arg.startswith("max_prefix"):
+ for line in splitted_config:
+ if "maximum-prefix" in line:
+ splitted_line = line.split()
+ if arg == "max_prefix_limit":
+ value = splitted_line[1]
+ elif arg == "max_prefix_interval" and "restart" in line:
+ value = splitted_line[-1]
+ elif arg == "max_prefix_threshold" and len(splitted_line) > 2:
+ try:
+ int(splitted_line[2])
+ value = splitted_line[2]
+ except ValueError:
+ value = ""
+ elif arg == "max_prefix_warning":
+ value = "warning-only" in line
+ elif arg == "soft_reconfiguration_in":
+ value = "inherit"
+ for line in splitted_config:
+ if command in line:
+ if "always" in line:
+ value = "always"
+ else:
+ value = "enable"
+
+ elif arg == "send_community":
+ value = "none"
+ for line in splitted_config:
+ if command in line:
+ if "extended" in line:
+ if value == "standard":
+ value = "both"
+ else:
+ value = "extended"
+ elif "both" in line:
+ value = "both"
+ else:
+ value = "standard"
+
+ return value
+
+
+def get_existing(module, args, warnings):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+
+ asn_regex = re.compile(r".*router\sbgp\s(?P<existing_asn>\d+(\.\d+)?).*", re.S)
+ match_asn = asn_regex.match(str(netcfg))
+
+ if match_asn:
+ existing_asn = match_asn.group("existing_asn")
+ parents = ["router bgp {0}".format(existing_asn)]
+
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("neighbor {0}".format(module.params["neighbor"]))
+ parents.append("address-family {0} {1}".format(module.params["afi"], module.params["safi"]))
+ config = netcfg.get_section(parents)
+
+ if config:
+ for arg in args:
+ if arg not in ["asn", "vrf", "neighbor", "afi", "safi"]:
+ existing[arg] = get_value(arg, config, module)
+
+ existing["asn"] = existing_asn
+ existing["neighbor"] = module.params["neighbor"]
+ existing["vrf"] = module.params["vrf"]
+ existing["afi"] = module.params["afi"]
+ existing["safi"] = module.params["safi"]
+ else:
+ warnings.append("The BGP process didn't exist but the task just created it.")
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key in table:
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = table.get(key)
+
+ return new_dict
+
+
+def get_default_command(key, value, existing_commands):
+ command = ""
+ if existing_commands.get(key):
+ existing_value = existing_commands.get(key)
+ if value == "inherit":
+ if existing_value != "inherit":
+ command = "no {0}".format(key)
+ else:
+ if key == "advertise-map exist-map":
+ command = "no advertise-map {0} exist-map {1}".format(
+ existing_value[0],
+ existing_value[1],
+ )
+ elif key == "advertise-map non-exist-map":
+ command = "no advertise-map {0} non-exist-map {1}".format(
+ existing_value[0],
+ existing_value[1],
+ )
+ elif key == "filter-list in":
+ command = "no filter-list {0} in".format(existing_value)
+ elif key == "filter-list out":
+ command = "no filter-list {0} out".format(existing_value)
+ elif key == "prefix-list in":
+ command = "no prefix-list {0} in".format(existing_value)
+ elif key == "prefix-list out":
+ command = "no prefix-list {0} out".format(existing_value)
+ elif key == "route-map in":
+ command = "no route-map {0} in".format(existing_value)
+ elif key == "route-map out":
+ command = "no route-map {0} out".format(existing_value)
+ elif key.startswith("maximum-prefix"):
+ command = "no maximum-prefix"
+ elif key == "allowas-in max":
+ command = ["no allowas-in {0}".format(existing_value)]
+ command.append("allowas-in")
+ else:
+ command = "no {0} {1}".format(key, existing_value)
+ else:
+ if key.replace(" ", "_").replace("-", "_") in BOOL_PARAMS:
+ command = "no {0}".format(key)
+ return command
+
+
+def fix_proposed(module, existing, proposed):
+ allowas_in = proposed.get("allowas_in")
+ allowas_in_max = proposed.get("allowas_in_max")
+
+ if allowas_in_max and not allowas_in:
+ proposed.pop("allowas_in_max")
+ elif allowas_in and allowas_in_max:
+ proposed.pop("allowas_in")
+
+ if existing.get("send_community") == "none" and proposed.get("send_community") == "default":
+ proposed.pop("send_community")
+ return proposed
+
+
+def state_present(module, existing, proposed, candidate):
+ commands = list()
+ proposed = fix_proposed(module, existing, proposed)
+
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+ for key, value in proposed_commands.items():
+ if value in ["inherit", "default"]:
+ command = get_default_command(key, value, existing_commands)
+
+ if isinstance(command, str):
+ if command and command not in commands:
+ commands.append(command)
+ elif isinstance(command, list):
+ for cmd in command:
+ if cmd not in commands:
+ commands.append(cmd)
+
+ elif key.startswith("maximum-prefix"):
+ if module.params["max_prefix_limit"] != "default":
+ command = "maximum-prefix {0}".format(module.params["max_prefix_limit"])
+ if module.params["max_prefix_threshold"]:
+ command += " {0}".format(module.params["max_prefix_threshold"])
+ if module.params["max_prefix_interval"]:
+ command += " restart {0}".format(module.params["max_prefix_interval"])
+ elif module.params["max_prefix_warning"]:
+ command += " warning-only"
+ commands.append(command)
+
+ elif value is True:
+ commands.append(key)
+ elif value is False:
+ commands.append("no {0}".format(key))
+ elif key == "address-family":
+ commands.append(
+ "address-family {0} {1}".format(module.params["afi"], module.params["safi"]),
+ )
+ elif key.startswith("capability additional-paths"):
+ command = key
+ if value == "disable":
+ command += " disable"
+ commands.append(command)
+ elif key.startswith("advertise-map"):
+ direction = key.split()[1]
+ commands.append("advertise-map {1} {0} {2}".format(direction, *value))
+ elif key.split()[0] in ["filter-list", "prefix-list", "route-map"]:
+ commands.append("{1} {0} {2}".format(value, *key.split()))
+
+ elif key == "soft-reconfiguration inbound":
+ command = ""
+ if value == "enable":
+ command = key
+ elif value == "always":
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+ elif key == "send-community":
+ command = key
+ if value in ["standard", "extended"]:
+ commands.append("no " + key + " both")
+ command += " {0}".format(value)
+ commands.append(command)
+ else:
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+
+ if commands:
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("neighbor {0}".format(module.params["neighbor"]))
+
+ af_command = "address-family {0} {1}".format(module.params["afi"], module.params["safi"])
+ parents.append(af_command)
+ if af_command in commands:
+ commands.remove(af_command)
+ candidate.add(commands, parents=parents)
+
+
+def state_absent(module, existing, candidate):
+ commands = []
+ parents = ["router bgp {0}".format(module.params["asn"])]
+ if module.params["vrf"] != "default":
+ parents.append("vrf {0}".format(module.params["vrf"]))
+
+ parents.append("neighbor {0}".format(module.params["neighbor"]))
+ commands.append("no address-family {0} {1}".format(module.params["afi"], module.params["safi"]))
+ candidate.add(commands, parents=parents)
+
+
+def main():
+ argument_spec = dict(
+ asn=dict(required=True, type="str"),
+ vrf=dict(required=False, type="str", default="default"),
+ neighbor=dict(required=True, type="str"),
+ afi=dict(
+ required=True,
+ type="str",
+ choices=["ipv4", "ipv6", "vpnv4", "vpnv6", "l2vpn"],
+ ),
+ safi=dict(required=True, type="str", choices=["unicast", "multicast", "evpn"]),
+ additional_paths_receive=dict(
+ required=False,
+ type="str",
+ choices=["enable", "disable", "inherit"],
+ ),
+ additional_paths_send=dict(
+ required=False,
+ type="str",
+ choices=["enable", "disable", "inherit"],
+ ),
+ advertise_map_exist=dict(required=False, type="list", elements="str"),
+ advertise_map_non_exist=dict(required=False, type="list", elements="str"),
+ allowas_in=dict(required=False, type="bool"),
+ allowas_in_max=dict(required=False, type="str"),
+ as_override=dict(required=False, type="bool"),
+ default_originate=dict(required=False, type="bool"),
+ default_originate_route_map=dict(required=False, type="str"),
+ disable_peer_as_check=dict(required=False, type="bool"),
+ filter_list_in=dict(required=False, type="str"),
+ filter_list_out=dict(required=False, type="str"),
+ max_prefix_limit=dict(required=False, type="str"),
+ max_prefix_interval=dict(required=False, type="str"),
+ max_prefix_threshold=dict(required=False, type="str"),
+ max_prefix_warning=dict(required=False, type="bool"),
+ next_hop_self=dict(required=False, type="bool"),
+ next_hop_third_party=dict(required=False, type="bool"),
+ prefix_list_in=dict(required=False, type="str"),
+ prefix_list_out=dict(required=False, type="str"),
+ route_map_in=dict(required=False, type="str"),
+ route_map_out=dict(required=False, type="str"),
+ route_reflector_client=dict(required=False, type="bool"),
+ send_community=dict(
+ required=False,
+ choices=["none", "both", "extended", "standard", "default"],
+ ),
+ soft_reconfiguration_in=dict(
+ required=False,
+ type="str",
+ choices=["enable", "always", "inherit"],
+ ),
+ soo=dict(required=False, type="str"),
+ suppress_inactive=dict(required=False, type="bool"),
+ unsuppress_map=dict(required=False, type="str"),
+ weight=dict(required=False, type="str"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ rewrite_evpn_rt_asn=dict(required=False, type="bool"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[
+ ["advertise_map_exist", "advertise_map_non_exist"],
+ ["max_prefix_interval", "max_prefix_warning"],
+ ["default_originate", "default_originate_route_map"],
+ ["allowas_in", "allowas_in_max"],
+ ],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = dict(changed=False, warnings=warnings)
+
+ state = module.params["state"]
+ for key in [
+ "max_prefix_interval",
+ "max_prefix_warning",
+ "max_prefix_threshold",
+ ]:
+ if module.params[key] and not module.params["max_prefix_limit"]:
+ module.fail_json(msg="max_prefix_limit is required when using %s" % key)
+ if module.params["vrf"] == "default" and module.params["soo"]:
+ module.fail_json(msg="SOO is only allowed in non-default VRF")
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing = get_existing(module, args, warnings)
+
+ if existing.get("asn") and state == "present":
+ if existing.get("asn") != module.params["asn"]:
+ module.fail_json(
+ msg="Another BGP ASN already exists.",
+ proposed_asn=module.params["asn"],
+ existing_asn=existing.get("asn"),
+ )
+
+ for param in ["advertise_map_exist", "advertise_map_non_exist"]:
+ if module.params[param] == ["default"]:
+ module.params[param] = "default"
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key not in ["asn", "vrf", "neighbor"]:
+ if not isinstance(value, list):
+ if str(value).lower() == "true":
+ value = True
+ elif str(value).lower() == "false":
+ value = False
+ elif str(value).lower() == "default":
+ if key in BOOL_PARAMS:
+ value = False
+ else:
+ value = "default"
+ elif key == "send_community" and str(value).lower() == "none":
+ value = "default"
+ if existing.get(key) != value:
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present":
+ state_present(module, existing, proposed, candidate)
+ elif state == "absent" and existing:
+ state_absent(module, existing, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ if not module.check_mode:
+ responses = load_config(module, candidate)
+ if responses:
+ for resp in responses:
+ if resp:
+ if resp.endswith("is valid only for EBGP peers"):
+ module.fail_json(msg=resp)
+ result["changed"] = True
+ result["commands"] = candidate
+ else:
+ result["commands"] = []
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_command.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_command.py
new file mode 100644
index 00000000..7febbf8a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_command.py
@@ -0,0 +1,231 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_command
+extends_documentation_fragment:
+- cisco.nxos.nxos
+author: Peter Sprygada (@privateip)
+notes:
+- Limited Support for Cisco MDS
+short_description: Run arbitrary command on Cisco NXOS devices
+description:
+- Sends an arbitrary command to an NXOS node and returns the results read from the
+ device. This module includes an argument that will cause the module to wait for
+ a specific condition before returning or timing out if the condition is not met.
+version_added: 1.0.0
+options:
+ commands:
+ description:
+ - The commands to send to the remote NXOS device. The resulting output from the
+ command is returned. If the I(wait_for) argument is provided, the module is
+ not returned until the condition is satisfied or the number of retires as expired.
+ - The I(commands) argument also accepts an alternative form that allows for complex
+ values that specify the command to run and the output format to return. This
+ can be done on a command by command basis. The complex argument supports the
+ keywords C(command) and C(output) where C(command) is the command to run and
+ C(output) is one of 'text' or 'json'.
+ - If a command sent to the device requires answering a prompt, it is possible to pass
+ a dict containing command, answer and prompt. Common answers are 'y' or "\\r"
+ (carriage return, must be double quotes). See examples.
+ required: true
+ type: list
+ elements: raw
+ wait_for:
+ description:
+ - Specifies what to evaluate from the output of the command and what conditionals
+ to apply. This argument will cause the task to wait for a particular conditional
+ to be true before moving forward. If the conditional is not true by the configured
+ retries, the task fails. See examples.
+ aliases:
+ - waitfor
+ type: list
+ elements: str
+ match:
+ description:
+ - The I(match) argument is used in conjunction with the I(wait_for) argument to
+ specify the match policy. Valid values are C(all) or C(any). If the value
+ is set to C(all) then all conditionals in the I(wait_for) must be satisfied. If
+ the value is set to C(any) then only one of the values must be satisfied.
+ default: all
+ choices: ['any', 'all']
+ type: str
+ retries:
+ description:
+ - Specifies the number of retries a command should by tried before it is considered
+ failed. The command is run on the target device every retry and evaluated against
+ the I(wait_for) conditionals.
+ - The commands are run once when I(retries) is set to C(0).
+ default: 9
+ type: int
+ interval:
+ description:
+ - Configures the interval in seconds to wait between retries of the command. If
+ the command does not pass the specified conditional, the interval indicates
+ how to long to wait before trying the command again.
+ default: 1
+ type: int
+"""
+
+EXAMPLES = """
+- name: run show version on remote devices
+ cisco.nxos.nxos_command:
+ commands: show version
+
+- name: run show version and check to see if output contains Cisco
+ cisco.nxos.nxos_command:
+ commands: show version
+ wait_for: result[0] contains Cisco
+
+- name: run multiple commands on remote nodes
+ cisco.nxos.nxos_command:
+ commands:
+ - show version
+ - show interfaces
+
+- name: run multiple commands and evaluate the output
+ cisco.nxos.nxos_command:
+ commands:
+ - show version
+ - show interfaces
+ wait_for:
+ - result[0] contains Cisco
+ - result[1] contains loopback0
+
+- name: run commands and specify the output format
+ cisco.nxos.nxos_command:
+ commands:
+ - command: show version
+ output: json
+
+- name: run commands that require answering a prompt
+ cisco.nxos.nxos_command:
+ commands:
+ - configure terminal
+ - command: no feature npv
+ prompt: Do you want to continue
+ answer: y
+
+"""
+
+RETURN = """
+stdout:
+ description: The set of responses from the commands
+ returned: always apart from low level errors (such as action plugin)
+ type: list
+ sample: ['...', '...']
+stdout_lines:
+ description: The value of stdout split into a list
+ returned: always apart from low level errors (such as action plugin)
+ type: list
+ sample: [['...', '...'], ['...'], ['...']]
+failed_conditions:
+ description: The list of conditionals that have failed
+ returned: failed
+ type: list
+ sample: ['...', '...']
+"""
+import time
+
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.parsing import (
+ Conditional,
+ FailedConditionalError,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ to_lines,
+ transform_commands,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import run_commands
+
+
+def parse_commands(module, warnings):
+ commands = transform_commands(module)
+
+ if module.check_mode:
+ for item in list(commands):
+ if not item["command"].startswith("show"):
+ warnings.append(
+ "Only show commands are supported when using check mode, not "
+ "executing %s" % item["command"],
+ )
+ commands.remove(item)
+
+ return commands
+
+
+def to_cli(obj):
+ cmd = obj["command"]
+ if obj.get("output") == "json":
+ cmd += " | json"
+ return cmd
+
+
+def main():
+ """entry point for module execution"""
+ argument_spec = dict(
+ # { command: <str>, output: <str>, prompt: <str>, response: <str> }
+ commands=dict(type="list", required=True, elements="raw"),
+ wait_for=dict(type="list", aliases=["waitfor"], elements="str"),
+ match=dict(default="all", choices=["any", "all"]),
+ retries=dict(default=9, type="int"),
+ interval=dict(default=1, type="int"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ result = {"changed": False, "warnings": warnings}
+ commands = parse_commands(module, warnings)
+ wait_for = module.params["wait_for"] or list()
+ conditionals = []
+
+ try:
+ conditionals = [Conditional(c) for c in wait_for]
+ except AttributeError as exc:
+ module.fail_json(msg=to_text(exc))
+
+ retries = module.params["retries"]
+ interval = module.params["interval"]
+ match = module.params["match"]
+
+ while retries >= 0:
+ responses = run_commands(module, commands)
+
+ for item in list(conditionals):
+ try:
+ if item(responses):
+ if match == "any":
+ conditionals = list()
+ break
+ conditionals.remove(item)
+ except FailedConditionalError as exc:
+ module.fail_json(msg=to_text(exc))
+
+ if not conditionals:
+ break
+
+ time.sleep(interval)
+ retries -= 1
+
+ if conditionals:
+ failed_conditions = [item.raw for item in conditionals]
+ msg = "One or more conditional statements have not been satisfied"
+ module.fail_json(msg=msg, failed_conditions=failed_conditions)
+
+ result.update({"stdout": responses, "stdout_lines": list(to_lines(responses))})
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_config.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_config.py
new file mode 100644
index 00000000..45789124
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_config.py
@@ -0,0 +1,579 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_config
+extends_documentation_fragment:
+- cisco.nxos.nxos
+author: Peter Sprygada (@privateip)
+short_description: Manage Cisco NXOS configuration sections
+description:
+- Cisco NXOS configurations use a simple block indent file syntax for segmenting configuration
+ into sections. This module provides an implementation for working with NXOS configuration
+ sections in a deterministic way. This module works with either CLI or NXAPI transports.
+version_added: 1.0.0
+options:
+ lines:
+ description:
+ - The ordered set of commands that should be configured in the section. The commands
+ must be the exact same commands as found in the device running-config to ensure idempotency
+ and correct diff. Be sure to note the configuration command syntax as some commands are
+ automatically modified by the device config parser.
+ type: list
+ aliases:
+ - commands
+ elements: str
+ parents:
+ description:
+ - The ordered set of parents that uniquely identify the section or hierarchy the
+ commands should be checked against. If the parents argument is omitted, the
+ commands are checked against the set of top level or global commands.
+ type: list
+ elements: str
+ src:
+ description:
+ - The I(src) argument provides a path to the configuration file to load into the
+ remote system. The path can either be a full system path to the configuration
+ file if the value starts with / or relative to the root of the implemented role
+ or playbook. This argument is mutually exclusive with the I(lines) and I(parents)
+ arguments. The configuration lines in the source file should be similar to how it
+ will appear if present in the running-configuration of the device including indentation
+ to ensure idempotency and correct diff.
+ type: path
+ replace_src:
+ description:
+ - The I(replace_src) argument provides path to the configuration file to load
+ into the remote system. This argument is used to replace the entire config with
+ a flat-file. This is used with argument I(replace) with value I(config). This
+ is mutually exclusive with the I(lines) and I(src) arguments. This argument
+ will only work for NX-OS versions that support `config replace`. Use I(nxos_file_copy)
+ module to copy the flat file to remote device and then use the path with this argument.
+ The configuration lines in the file should be similar to how it
+ will appear if present in the running-configuration of the device including the indentation
+ to ensure idempotency and correct diff.
+ type: str
+ before:
+ description:
+ - The ordered set of commands to push on to the command stack if a change needs
+ to be made. This allows the playbook designer the opportunity to perform configuration
+ commands prior to pushing any changes without affecting how the set of commands
+ are matched against the system.
+ type: list
+ elements: str
+ after:
+ description:
+ - The ordered set of commands to append to the end of the command stack if a change
+ needs to be made. Just like with I(before) this allows the playbook designer
+ to append a set of commands to be executed after the command set.
+ type: list
+ elements: str
+ match:
+ description:
+ - Instructs the module on the way to perform the matching of the set of commands
+ against the current device config. If match is set to I(line), commands are
+ matched line by line. If match is set to I(strict), command lines are matched
+ with respect to position. If match is set to I(exact), command lines must be
+ an equal match. Finally, if match is set to I(none), the module will not attempt
+ to compare the source configuration with the running configuration on the remote
+ device.
+ default: line
+ choices:
+ - line
+ - strict
+ - exact
+ - none
+ type: str
+ replace:
+ description:
+ - Instructs the module on the way to perform the configuration on the device. If
+ the replace argument is set to I(line) then the modified lines are pushed to
+ the device in configuration mode. If the replace argument is set to I(block)
+ then the entire command block is pushed to the device in configuration mode
+ if any line is not correct. replace I(config) will only work for NX-OS versions
+ that support `config replace`.
+ default: line
+ choices:
+ - line
+ - block
+ - config
+ type: str
+ backup:
+ description:
+ - This argument will cause the module to create a full backup of the current C(running-config)
+ from the remote device before any changes are made. If the C(backup_options)
+ value is not given, the backup file is written to the C(backup) folder in the
+ playbook root directory or role root directory, if playbook is part of an ansible
+ role. If the directory does not exist, it is created.
+ type: bool
+ default: no
+ running_config:
+ description:
+ - The module, by default, will connect to the remote device and retrieve the current
+ running-config to use as a base for comparing against the contents of source. There
+ are times when it is not desirable to have the task get the current running-config
+ for every task in a playbook. The I(running_config) argument allows the implementer
+ to pass in the configuration to use as the base config for comparison.
+ The configuration lines for this option should be similar to how it will appear if present
+ in the running-configuration of the device including the indentation to ensure idempotency
+ and correct diff.
+ aliases:
+ - config
+ type: str
+ defaults:
+ description:
+ - The I(defaults) argument will influence how the running-config is collected
+ from the device. When the value is set to true, the command used to collect
+ the running-config is append with the all keyword. When the value is set to
+ false, the command is issued without the all keyword
+ type: bool
+ default: no
+ save_when:
+ description:
+ - When changes are made to the device running-configuration, the changes are not
+ copied to non-volatile storage by default. Using this argument will change
+ that before. If the argument is set to I(always), then the running-config will
+ always be copied to the startup-config and the I(modified) flag will always
+ be set to True. If the argument is set to I(modified), then the running-config
+ will only be copied to the startup-config if it has changed since the last save
+ to startup-config. If the argument is set to I(never), the running-config will
+ never be copied to the startup-config. If the argument is set to I(changed),
+ then the running-config will only be copied to the startup-config if the task
+ has made a change. I(changed) was added in Ansible 2.6.
+ default: never
+ choices:
+ - always
+ - never
+ - modified
+ - changed
+ type: str
+ diff_against:
+ description:
+ - When using the C(ansible-playbook --diff) command line argument the module can
+ generate diffs against different sources.
+ - When this option is configure as I(startup), the module will return the diff
+ of the running-config against the startup-config.
+ - When this option is configured as I(intended), the module will return the diff
+ of the running-config against the configuration provided in the C(intended_config)
+ argument.
+ - When this option is configured as I(running), the module will return the before
+ and after diff of the running-config with respect to any changes made to the
+ device configuration.
+ choices:
+ - startup
+ - intended
+ - running
+ type: str
+ diff_ignore_lines:
+ description:
+ - Use this argument to specify one or more lines that should be ignored during
+ the diff. This is used for lines in the configuration that are automatically
+ updated by the system. This argument takes a list of regular expressions or
+ exact line matches.
+ type: list
+ elements: str
+ intended_config:
+ description:
+ - The C(intended_config) provides the master configuration that the node should
+ conform to and is used to check the final running-config against. This argument
+ will not modify any settings on the remote device and is strictly used to check
+ the compliance of the current device's configuration against. When specifying
+ this argument, the task should also modify the C(diff_against) value and set
+ it to I(intended). The configuration lines for this value should be similar to how it
+ will appear if present in the running-configuration of the device including the indentation
+ to ensure correct diff.
+ type: str
+ backup_options:
+ description:
+ - This is a dict object containing configurable options related to backup file
+ path. The value of this option is read only when C(backup) is set to I(True),
+ if C(backup) is set to I(false) this option will be silently ignored.
+ suboptions:
+ filename:
+ description:
+ - The filename to be used to store the backup configuration. If the filename
+ is not given it will be generated based on the hostname, current time and
+ date in format defined by <hostname>_config.<current-date>@<current-time>
+ type: str
+ dir_path:
+ description:
+ - This option provides the path ending with directory name in which the backup
+ configuration file will be stored. If the directory does not exist it will
+ be created and the filename is either the value of C(filename) or default
+ filename as described in C(filename) options description. If the path value
+ is not given in that case a I(backup) directory will be created in the current
+ working directory and backup configuration will be copied in C(filename)
+ within I(backup) directory.
+ type: path
+ type: dict
+notes:
+- Unsupported for Cisco MDS
+- Abbreviated commands are NOT idempotent, see
+ U(https://docs.ansible.com/ansible/latest/network/user_guide/faq.html#why-do-the-config-modules-always-return-changed-true-with-abbreviated-commands).
+- To ensure idempotency and correct diff the configuration lines in the relevant module options should be similar to how they
+ appear if present in the running configuration on device including the indentation.
+"""
+
+EXAMPLES = """
+- name: configure top level configuration and save it
+ cisco.nxos.nxos_config:
+ lines: hostname {{ inventory_hostname }}
+ save_when: modified
+
+- name: diff the running-config against a provided config
+ cisco.nxos.nxos_config:
+ diff_against: intended
+ intended_config: "{{ lookup('file', 'master.cfg') }}"
+
+- cisco.nxos.nxos_config:
+ lines:
+ - 10 permit ip 192.0.2.1/32 any log
+ - 20 permit ip 192.0.2.2/32 any log
+ - 30 permit ip 192.0.2.3/32 any log
+ - 40 permit ip 192.0.2.4/32 any log
+ - 50 permit ip 192.0.2.5/32 any log
+ parents: ip access-list test
+ before: no ip access-list test
+ match: exact
+
+- cisco.nxos.nxos_config:
+ lines:
+ - 10 permit ip 192.0.2.1/32 any log
+ - 20 permit ip 192.0.2.2/32 any log
+ - 30 permit ip 192.0.2.3/32 any log
+ - 40 permit ip 192.0.2.4/32 any log
+ parents: ip access-list test
+ before: no ip access-list test
+ replace: block
+
+- name: replace config with flat file
+ cisco.nxos.nxos_config:
+ replace_src: config.txt
+ replace: config
+
+- name: for idempotency, use full-form commands
+ cisco.nxos.nxos_config:
+ lines:
+ # - shut
+ - shutdown
+ # parents: int eth1/1
+ parents: interface Ethernet1/1
+
+- name: configurable backup path
+ cisco.nxos.nxos_config:
+ backup: yes
+ backup_options:
+ filename: backup.cfg
+ dir_path: /home/user
+"""
+
+RETURN = """
+commands:
+ description: The set of commands that will be pushed to the remote device
+ returned: always
+ type: list
+ sample: ['hostname foo', 'vlan 1', 'name default']
+updates:
+ description: The set of commands that will be pushed to the remote device
+ returned: always
+ type: list
+ sample: ['hostname foo', 'vlan 1', 'name default']
+backup_path:
+ description: The full path to the backup file
+ returned: when backup is yes
+ type: str
+ sample: /playbooks/ansible/backup/nxos_config.2016-07-16@22:28:34
+filename:
+ description: The name of the backup file
+ returned: when backup is yes and filename is not specified in backup options
+ type: str
+ sample: nxos_config.2016-07-16@22:28:34
+shortname:
+ description: The full path to the backup file excluding the timestamp
+ returned: when backup is yes and filename is not specified in backup options
+ type: str
+ sample: /playbooks/ansible/backup/nxos_config
+date:
+ description: The date extracted from the backup file name
+ returned: when backup is yes
+ type: str
+ sample: "2016-07-16"
+time:
+ description: The time extracted from the backup file name
+ returned: when backup is yes
+ type: str
+ sample: "22:28:34"
+"""
+from ansible.module_utils._text import to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.connection import ConnectionError
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ NetworkConfig,
+ dumps,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import to_list
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ get_connection,
+ load_config,
+ run_commands,
+)
+
+
+def get_running_config(module, config=None, flags=None):
+ contents = module.params["running_config"]
+ if not contents:
+ if config:
+ contents = config
+ else:
+ contents = get_config(module, flags=flags)
+ return contents
+
+
+def get_candidate(module):
+ candidate = ""
+ if module.params["src"]:
+ if module.params["replace"] != "config":
+ candidate = module.params["src"]
+ if module.params["replace"] == "config":
+ candidate = "config replace {0}".format(module.params["replace_src"])
+ elif module.params["lines"]:
+ candidate_obj = NetworkConfig(indent=2)
+ parents = module.params["parents"] or list()
+ candidate_obj.add(module.params["lines"], parents=parents)
+ candidate = dumps(candidate_obj, "raw")
+ return candidate
+
+
+def execute_show_commands(module, commands, output="text"):
+ cmds = []
+ for command in to_list(commands):
+ cmd = {"command": command, "output": output}
+ cmds.append(cmd)
+ body = run_commands(module, cmds)
+ return body
+
+
+def save_config(module, result):
+ result["changed"] = True
+ if not module.check_mode:
+ cmd = {
+ "command": "copy running-config startup-config",
+ "output": "text",
+ }
+ run_commands(module, [cmd])
+ else:
+ module.warn(
+ "Skipping command `copy running-config startup-config` "
+ "due to check_mode. Configuration not copied to "
+ "non-volatile storage",
+ )
+
+
+def main():
+ """main entry point for module execution"""
+ backup_spec = dict(filename=dict(), dir_path=dict(type="path"))
+ argument_spec = dict(
+ src=dict(type="path"),
+ replace_src=dict(),
+ lines=dict(aliases=["commands"], type="list", elements="str"),
+ parents=dict(type="list", elements="str"),
+ before=dict(type="list", elements="str"),
+ after=dict(type="list", elements="str"),
+ match=dict(default="line", choices=["line", "strict", "exact", "none"]),
+ replace=dict(default="line", choices=["line", "block", "config"]),
+ running_config=dict(aliases=["config"]),
+ intended_config=dict(),
+ defaults=dict(type="bool", default=False),
+ backup=dict(type="bool", default=False),
+ backup_options=dict(type="dict", options=backup_spec),
+ save_when=dict(choices=["always", "never", "modified", "changed"], default="never"),
+ diff_against=dict(choices=["running", "startup", "intended"]),
+ diff_ignore_lines=dict(type="list", elements="str"),
+ )
+
+ mutually_exclusive = [("lines", "src", "replace_src"), ("parents", "src")]
+
+ required_if = [
+ ("match", "strict", ["lines"]),
+ ("match", "exact", ["lines"]),
+ ("replace", "block", ["lines"]),
+ ("replace", "config", ["replace_src"]),
+ ("diff_against", "intended", ["intended_config"]),
+ ]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False, "warnings": warnings}
+
+ config = None
+
+ diff_ignore_lines = module.params["diff_ignore_lines"]
+ path = module.params["parents"]
+ connection = get_connection(module)
+ contents = None
+ flags = ["all"] if module.params["defaults"] else []
+ replace_src = module.params["replace_src"]
+ if replace_src:
+ if module.params["replace"] != "config":
+ module.fail_json(msg="replace: config is required with replace_src")
+
+ if module.params["backup"] or (module._diff and module.params["diff_against"] == "running"):
+ contents = get_config(module, flags=flags)
+ config = NetworkConfig(indent=2, contents=contents)
+ if module.params["backup"]:
+ result["__backup__"] = contents
+
+ if any((module.params["src"], module.params["lines"], replace_src)):
+ match = module.params["match"]
+ replace = module.params["replace"]
+
+ commit = not module.check_mode
+ candidate = get_candidate(module)
+ running = get_running_config(module, contents, flags=flags)
+ if replace_src:
+ commands = candidate.split("\n")
+ result["commands"] = result["updates"] = commands
+ if commit:
+ load_config(module, commands, replace=replace_src)
+
+ result["changed"] = True
+ else:
+ try:
+ response = connection.get_diff(
+ candidate=candidate,
+ running=running,
+ diff_match=match,
+ diff_ignore_lines=diff_ignore_lines,
+ path=path,
+ diff_replace=replace,
+ )
+ except ConnectionError as exc:
+ module.fail_json(msg=to_text(exc, errors="surrogate_then_replace"))
+
+ config_diff = response["config_diff"]
+ if config_diff:
+ commands = config_diff.split("\n")
+
+ if module.params["before"]:
+ commands[:0] = module.params["before"]
+
+ if module.params["after"]:
+ commands.extend(module.params["after"])
+
+ result["commands"] = commands
+ result["updates"] = commands
+
+ if commit:
+ load_config(module, commands, replace=replace_src)
+
+ result["changed"] = True
+
+ running_config = module.params["running_config"]
+ startup_config = None
+
+ if module.params["save_when"] == "always":
+ save_config(module, result)
+ elif module.params["save_when"] == "modified":
+ output = execute_show_commands(module, ["show running-config", "show startup-config"])
+
+ running_config = NetworkConfig(indent=2, contents=output[0], ignore_lines=diff_ignore_lines)
+ startup_config = NetworkConfig(indent=2, contents=output[1], ignore_lines=diff_ignore_lines)
+
+ if running_config.sha1 != startup_config.sha1:
+ save_config(module, result)
+ elif module.params["save_when"] == "changed" and result["changed"]:
+ save_config(module, result)
+
+ if module._diff:
+ if not running_config:
+ output = execute_show_commands(module, "show running-config")
+ contents = output[0]
+ else:
+ contents = running_config
+
+ # recreate the object in order to process diff_ignore_lines
+ running_config = NetworkConfig(indent=2, contents=contents, ignore_lines=diff_ignore_lines)
+
+ if module.params["diff_against"] == "running":
+ if module.check_mode:
+ module.warn("unable to perform diff against running-config due to check mode")
+ contents = None
+ else:
+ contents = config.config_text
+
+ elif module.params["diff_against"] == "startup":
+ if not startup_config:
+ output = execute_show_commands(module, "show startup-config")
+ contents = output[0]
+ else:
+ contents = startup_config.config_text
+
+ elif module.params["diff_against"] == "intended":
+ contents = module.params["intended_config"]
+
+ if contents is not None:
+ base_config = NetworkConfig(indent=2, contents=contents, ignore_lines=diff_ignore_lines)
+
+ if running_config.sha1 != base_config.sha1:
+ before = ""
+ after = ""
+ if module.params["diff_against"] == "intended":
+ before = running_config
+ after = base_config
+ elif module.params["diff_against"] in ("startup", "running"):
+ before = base_config
+ after = running_config
+
+ result.update(
+ {
+ "changed": True,
+ "diff": {"before": str(before), "after": str(after)},
+ },
+ )
+
+ if result.get("changed") and any((module.params["src"], module.params["lines"])):
+ msg = (
+ "To ensure idempotency and correct diff the input configuration lines should be"
+ " similar to how they appear if present in"
+ " the running configuration on device"
+ )
+ if module.params["src"]:
+ msg += " including the indentation"
+ if "warnings" in result:
+ result["warnings"].append(msg)
+ else:
+ result["warnings"] = msg
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_devicealias.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_devicealias.py
new file mode 100644
index 00000000..71d4ebb6
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_devicealias.py
@@ -0,0 +1,550 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_devicealias
+short_description: Configuration of device alias for Cisco NXOS MDS Switches.
+description:
+- Configuration of device alias for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ distribute:
+ description:
+ - Enable/Disable device-alias distribution
+ type: bool
+ mode:
+ description:
+ - Mode of devices-alias, basic or enhanced
+ choices:
+ - basic
+ - enhanced
+ type: str
+ da:
+ description:
+ - List of device-alias to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the device-alias to be added or removed
+ required: true
+ type: str
+ pwwn:
+ description:
+ - pwwn to which the name needs to be associated with
+ type: str
+ remove:
+ description:
+ - Removes the device-alias if set to True
+ type: bool
+ default: false
+ rename:
+ description:
+ - List of device-alias to be renamed
+ type: list
+ elements: dict
+ suboptions:
+ old_name:
+ description:
+ - Old name of the device-alias that needs to be renamed
+ required: true
+ type: str
+ new_name:
+ description:
+ - New name of the device-alias
+ required: true
+ type: str
+"""
+
+EXAMPLES = """
+- name: Test that device alias module works
+ cisco.nxos.nxos_devicealias:
+ da:
+ - name: test1_add
+ pwwn: 56:2:22:11:22:88:11:67
+ - name: test2_add
+ pwwn: 65:22:22:11:22:22:11:d
+ - name: dev1
+ remove: true
+ - name: dev2
+ remove: true
+ distribute: true
+ mode: enhanced
+ rename:
+ - new_name: bcd
+ old_name: abc
+ - new_name: bcd1
+ old_name: abc1
+
+
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - device-alias database
+ - device-alias name somename pwwn 10:00:00:00:89:a1:01:03
+ - device-alias name somename1 pwwn 10:00:00:00:89:a1:02:03
+ - device-alias commit
+ - no terminal dont-ask
+"""
+
+import string
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+VALID_DA_CHARS = ("-", "_", "$", "^")
+
+
+class showDeviceAliasStatus(object):
+ """docstring for showDeviceAliasStatus"""
+
+ def __init__(self, module):
+ self.module = module
+ self.distribute = ""
+ self.mode = ""
+ self.locked = False
+ self.update()
+
+ def execute_show_cmd(self, cmd):
+ output = execute_show_command(cmd, self.module)[0]
+ return output
+
+ def update(self):
+ command = "show device-alias status"
+ output = self.execute_show_cmd(command).split("\n")
+ for o in output:
+ if "Fabric Distribution" in o:
+ self.distribute = o.split(":")[1].strip().lower()
+ if "Mode" in o:
+ self.mode = o.split("Mode:")[1].strip().lower()
+ if "Locked" in o:
+ self.locked = True
+
+ def isLocked(self):
+ return self.locked
+
+ def getDistribute(self):
+ return self.distribute
+
+ def getMode(self):
+ return self.mode
+
+
+class showDeviceAliasDatabase(object):
+ """docstring for showDeviceAliasDatabase"""
+
+ def __init__(self, module):
+ self.module = module
+ self.da_dict = {}
+ self.update()
+
+ def execute_show_cmd(self, cmd):
+ output = execute_show_command(cmd, self.module)[0]
+ return output
+
+ def update(self):
+ command = "show device-alias database"
+ # output = execute_show_command(command, self.module)[0].split("\n")
+ output = self.execute_show_cmd(command)
+ self.da_list = output.split("\n")
+ for eachline in self.da_list:
+ if "device-alias" in eachline:
+ sv = eachline.strip().split()
+ self.da_dict[sv[2]] = sv[4]
+
+ def isNameInDaDatabase(self, name):
+ return name in self.da_dict.keys()
+
+ def isPwwnInDaDatabase(self, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ return newpwwn in self.da_dict.values()
+
+ def isNamePwwnPresentInDatabase(self, name, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ if name in self.da_dict.keys():
+ if newpwwn == self.da_dict[name]:
+ return True
+ return False
+
+ def getPwwnByName(self, name):
+ if name in self.da_dict.keys():
+ return self.da_dict[name]
+ else:
+ return None
+
+ def getNameByPwwn(self, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ for n, p in self.da_dict.items():
+ if p == newpwwn:
+ return n
+ return None
+
+
+def isPwwnValid(pwwn):
+ pwwnsplit = pwwn.split(":")
+ if len(pwwnsplit) != 8:
+ return False
+ for eachpwwnsplit in pwwnsplit:
+ if len(eachpwwnsplit) > 2 or len(eachpwwnsplit) < 1:
+ return False
+ if not all(c in string.hexdigits for c in eachpwwnsplit):
+ return False
+ return True
+
+
+def isNameValid(name):
+ if not name[0].isalpha():
+ # Illegal first character. Name must start with a letter
+ return False
+ if len(name) > 64:
+ return False
+ for character in name:
+ if not character.isalnum() and character not in VALID_DA_CHARS:
+ return False
+ return True
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ out = run_commands(module, commands)
+ return out
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def main():
+ element_spec = dict(
+ name=dict(required=True, type="str"),
+ pwwn=dict(type="str"),
+ remove=dict(type="bool", default=False),
+ )
+
+ element_spec_rename = dict(
+ old_name=dict(required=True, type="str"),
+ new_name=dict(required=True, type="str"),
+ )
+
+ argument_spec = dict(
+ distribute=dict(type="bool"),
+ mode=dict(type="str", choices=["enhanced", "basic"]),
+ da=dict(type="list", elements="dict", options=element_spec),
+ rename=dict(type="list", elements="dict", options=element_spec_rename),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ messages = list()
+ commands_to_execute = list()
+ result = {"changed": False}
+
+ distribute = module.params["distribute"]
+ mode = module.params["mode"]
+ da = module.params["da"]
+ rename = module.params["rename"]
+
+ # Step 0.0: Validate syntax of name and pwwn
+ # Also validate syntax of rename arguments
+ if da is not None:
+ for eachdict in da:
+ name = eachdict["name"]
+ pwwn = eachdict["pwwn"]
+ remove = eachdict["remove"]
+ if pwwn is not None:
+ pwwn = pwwn.lower()
+ if not remove:
+ if pwwn is None:
+ module.fail_json(
+ msg="This device alias name "
+ + str(name)
+ + " which needs to be added, does not have pwwn specified. Please specify a valid pwwn",
+ )
+ if not isNameValid(name):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(name)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+ if not isPwwnValid(pwwn):
+ module.fail_json(
+ msg="This pwwn is invalid : "
+ + str(pwwn)
+ + ". Please check that its a valid pwwn",
+ )
+ if rename is not None:
+ for eachdict in rename:
+ oldname = eachdict["old_name"]
+ newname = eachdict["new_name"]
+ if not isNameValid(oldname):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(oldname)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+ if not isNameValid(newname):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(newname)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+
+ # Step 0.1: Check DA status
+ shDAStausObj = showDeviceAliasStatus(module)
+ d = shDAStausObj.getDistribute()
+ m = shDAStausObj.getMode()
+ if shDAStausObj.isLocked():
+ module.fail_json(msg="device-alias has acquired lock on the switch. Hence cannot procced.")
+
+ # Step 1: Process distribute
+ commands = []
+ if distribute is not None:
+ if distribute:
+ # playbook has distribute as True(enabled)
+ if d == "disabled":
+ # but switch distribute is disabled(false), so set it to
+ # true(enabled)
+ commands.append("device-alias distribute")
+ messages.append("device-alias distribute changed from disabled to enabled")
+ else:
+ messages.append(
+ "device-alias distribute remains unchanged. current distribution mode is enabled",
+ )
+ else:
+ # playbook has distribute as False(disabled)
+ if d == "enabled":
+ # but switch distribute is enabled(true), so set it to
+ # false(disabled)
+ commands.append("no device-alias distribute")
+ messages.append("device-alias distribute changed from enabled to disabled")
+ else:
+ messages.append(
+ "device-alias distribute remains unchanged. current distribution mode is disabled",
+ )
+
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the da_add/da_remove stage
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step 2: Process mode
+ commands = []
+ if mode is not None:
+ if mode == "basic":
+ # playbook has mode as basic
+ if m == "enhanced":
+ # but switch mode is enhanced, so set it to basic
+ commands.append("no device-alias mode enhanced")
+ messages.append("device-alias mode changed from enhanced to basic")
+ else:
+ messages.append("device-alias mode remains unchanged. current mode is basic")
+
+ else:
+ # playbook has mode as enhanced
+ if m == "basic":
+ # but switch mode is basic, so set it to enhanced
+ commands.append("device-alias mode enhanced")
+ messages.append("device-alias mode changed from basic to enhanced")
+ else:
+ messages.append("device-alias mode remains unchanged. current mode is enhanced")
+
+ if commands:
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step 3: Process da
+ commands = []
+ shDADatabaseObj = showDeviceAliasDatabase(module)
+ if da is not None:
+ da_remove_list = []
+ da_add_list = []
+ for eachdict in da:
+ name = eachdict["name"]
+ pwwn = eachdict["pwwn"]
+ remove = eachdict["remove"]
+ if pwwn is not None:
+ pwwn = pwwn.lower()
+ if remove:
+ if shDADatabaseObj.isNameInDaDatabase(name):
+ commands.append("no device-alias name " + name)
+ da_remove_list.append(name)
+ else:
+ messages.append(
+ name
+ + " - This device alias name is not in switch device-alias database, hence cannot be removed.",
+ )
+ else:
+ if shDADatabaseObj.isNamePwwnPresentInDatabase(name, pwwn):
+ messages.append(
+ name
+ + " : "
+ + pwwn
+ + " - This device alias name,pwwn is already in switch device-alias database, hence nothing to configure",
+ )
+ else:
+ if shDADatabaseObj.isNameInDaDatabase(name):
+ module.fail_json(
+ msg=name
+ + " - This device alias name is already present in switch device-alias database but assigned to another pwwn ("
+ + shDADatabaseObj.getPwwnByName(name)
+ + ") hence cannot be added",
+ )
+
+ elif shDADatabaseObj.isPwwnInDaDatabase(pwwn):
+ module.fail_json(
+ msg=pwwn
+ + " - This device alias pwwn is already present in switch device-alias database but assigned to another name ("
+ + shDADatabaseObj.getNameByPwwn(pwwn)
+ + ") hence cannot be added",
+ )
+
+ else:
+ commands.append("device-alias name " + name + " pwwn " + pwwn)
+ da_add_list.append(name)
+
+ if len(da_add_list) != 0 or len(da_remove_list) != 0:
+ commands = ["device-alias database"] + commands
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+ if len(da_remove_list) != 0:
+ messages.append(
+ "the required device-alias were removed. " + ",".join(da_remove_list),
+ )
+ if len(da_add_list) != 0:
+ messages.append(
+ "the required device-alias were added. " + ",".join(da_add_list),
+ )
+
+ # Step 5: Process rename
+ commands = []
+ if rename is not None:
+ for eachdict in rename:
+ oldname = eachdict["old_name"]
+ newname = eachdict["new_name"]
+ if shDADatabaseObj.isNameInDaDatabase(newname):
+ module.fail_json(
+ changed=False,
+ commands=cmds,
+ msg=newname
+ + " - this name is already present in the device-alias database, hence we cannot rename "
+ + oldname
+ + " with this one",
+ )
+ if shDADatabaseObj.isNameInDaDatabase(oldname):
+ commands.append("device-alias rename " + oldname + " " + newname)
+ else:
+ module.fail_json(
+ changed=False,
+ commands=cmds,
+ msg=oldname
+ + " - this name is not present in the device-alias database, hence we cannot rename.",
+ )
+
+ if len(commands) != 0:
+ commands = ["device-alias database"] + commands
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step END: check for 'check' mode
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=commands_to_execute,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+
+ result["messages"] = messages
+ result["commands"] = commands_to_execute
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_global.py
new file mode 100644
index 00000000..83ef7839
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_global.py
@@ -0,0 +1,103 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_evpn_global
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Handles the EVPN control plane for VXLAN.
+description:
+- Handles the EVPN control plane for VXLAN.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- This module is not supported on Nexus 3000 series of switches.
+- Unsupported for Cisco MDS
+options:
+ nv_overlay_evpn:
+ description:
+ - EVPN control plane.
+ required: true
+ type: bool
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_evpn_global:
+ nv_overlay_evpn: true
+"""
+
+RETURN = """
+commands:
+ description: The set of commands to be sent to the remote device
+ returned: always
+ type: list
+ sample: ['nv overlay evpn']
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_config,
+ load_config,
+)
+
+
+def main():
+ argument_spec = dict(nv_overlay_evpn=dict(required=True, type="bool"))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ result = {"changed": False}
+
+ warnings = list()
+ if warnings:
+ result["warnings"] = warnings
+
+ config = get_config(module)
+ commands = list()
+
+ info = get_capabilities(module).get("device_info", {})
+ os_platform = info.get("network_os_platform", "")
+
+ if "3K" in os_platform:
+ module.fail_json(msg="This module is not supported on Nexus 3000 series")
+
+ if module.params["nv_overlay_evpn"] is True:
+ if "nv overlay evpn" not in config:
+ commands.append("nv overlay evpn")
+ elif "nv overlay evpn" in config:
+ commands.append("no nv overlay evpn")
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ result["commands"] = commands
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_vni.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_vni.py
new file mode 100644
index 00000000..d4490bb7
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_evpn_vni.py
@@ -0,0 +1,299 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_evpn_vni
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages Cisco EVPN VXLAN Network Identifier (VNI).
+description:
+- Manages Cisco Ethernet Virtual Private Network (EVPN) VXLAN Network Identifier (VNI)
+ configurations of a Nexus device.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- default, where supported, restores params default value.
+- RD override is not permitted. You should set it to the default values first and
+ then reconfigure it.
+- C(route_target_both), C(route_target_import) and C(route_target_export valid) values
+ are a list of extended communities, (i.e. ['1.2.3.4:5', '33:55']) or the keywords
+ 'auto' or 'default'.
+- The C(route_target_both) property is discouraged due to the inconsistent behavior
+ of the property across Nexus platforms and image versions. For this reason it is
+ recommended to use explicit C(route_target_export) and C(route_target_import) properties
+ instead of C(route_target_both).
+- RD valid values are a string in one of the route-distinguisher formats, the keyword
+ 'auto', or the keyword 'default'.
+options:
+ vni:
+ description:
+ - The EVPN VXLAN Network Identifier.
+ required: true
+ type: str
+ route_distinguisher:
+ description:
+ - The VPN Route Distinguisher (RD). The RD is combined with the IPv4 or IPv6 prefix
+ learned by the PE router to create a globally unique address.
+ type: str
+ route_target_both:
+ description:
+ - Enables/Disables route-target settings for both import and export target communities
+ using a single property.
+ type: list
+ elements: str
+ route_target_import:
+ description:
+ - Sets the route-target 'import' extended communities.
+ type: list
+ elements: str
+ route_target_export:
+ description:
+ - Sets the route-target 'export' extended communities.
+ type: list
+ elements: str
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: vni configuration
+ cisco.nxos.nxos_evpn_vni:
+ vni: 6000
+ route_distinguisher: 60:10
+ route_target_import:
+ - 5000:10
+ - 4100:100
+ route_target_export: auto
+ route_target_both: default
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["evpn", "vni 6000 l2", "route-target import 5001:10"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+PARAM_TO_COMMAND_KEYMAP = {
+ "vni": "vni",
+ "route_distinguisher": "rd",
+ "route_target_both": "route-target both",
+ "route_target_import": "route-target import",
+ "route_target_export": "route-target export",
+}
+
+
+def get_value(arg, config, module):
+ command = PARAM_TO_COMMAND_KEYMAP.get(arg)
+ command_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M)
+ value = ""
+ if command in config:
+ value = command_re.search(config).group("value")
+ return value
+
+
+def get_route_target_value(arg, config, module):
+ splitted_config = config.splitlines()
+ value_list = []
+ command = PARAM_TO_COMMAND_KEYMAP.get(arg)
+ command_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M)
+
+ for line in splitted_config:
+ value = ""
+ if command in line.strip():
+ value = command_re.search(line).group("value")
+ value_list.append(value)
+ return value_list
+
+
+def get_existing(module, args):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+ parents = ["evpn", "vni {0} l2".format(module.params["vni"])]
+ config = netcfg.get_section(parents)
+
+ if config:
+ for arg in args:
+ if arg != "vni":
+ if arg == "route_distinguisher":
+ existing[arg] = get_value(arg, config, module)
+ else:
+ existing[arg] = get_route_target_value(arg, config, module)
+
+ existing_fix = dict((k, v) for k, v in existing.items() if v)
+ if not existing_fix:
+ existing = existing_fix
+
+ existing["vni"] = module.params["vni"]
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key in table:
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = table.get(key)
+ return new_dict
+
+
+def fix_proposed(proposed_commands):
+ new_proposed = {}
+ for key, value in proposed_commands.items():
+ if key == "route-target both":
+ new_proposed["route-target export"] = value
+ new_proposed["route-target import"] = value
+ else:
+ new_proposed[key] = value
+ return new_proposed
+
+
+def state_present(module, existing, proposed):
+ commands = list()
+ parents = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+
+ if proposed_commands.get("route-target both"):
+ proposed_commands = fix_proposed(proposed_commands)
+
+ for key, value in proposed_commands.items():
+ if key.startswith("route-target"):
+ if value == ["default"]:
+ existing_value = existing_commands.get(key)
+
+ if existing_value:
+ for target in existing_value:
+ commands.append("no {0} {1}".format(key, target))
+ elif not isinstance(value, list):
+ value = [value]
+
+ for target in value:
+ if target == "default":
+ continue
+ if existing:
+ if target not in existing.get(key.replace("-", "_").replace(" ", "_")):
+ commands.append("{0} {1}".format(key, target))
+ else:
+ commands.append("{0} {1}".format(key, target))
+
+ if existing.get(key.replace("-", "_").replace(" ", "_")):
+ for exi in existing.get(key.replace("-", "_").replace(" ", "_")):
+ if exi not in value:
+ commands.append("no {0} {1}".format(key, exi))
+
+ elif value == "default":
+ existing_value = existing_commands.get(key)
+ if existing_value:
+ commands.append("no {0} {1}".format(key, existing_value))
+ else:
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+
+ if commands:
+ parents = ["evpn", "vni {0} l2".format(module.params["vni"])]
+
+ return commands, parents
+
+
+def state_absent(module, existing, proposed):
+ commands = ["no vni {0} l2".format(module.params["vni"])]
+ parents = ["evpn"]
+ return commands, parents
+
+
+def main():
+ argument_spec = dict(
+ vni=dict(required=True, type="str"),
+ route_distinguisher=dict(required=False, type="str"),
+ route_target_both=dict(required=False, type="list", elements="str"),
+ route_target_import=dict(required=False, type="list", elements="str"),
+ route_target_export=dict(required=False, type="list", elements="str"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = dict(changed=False, warnings=warnings)
+
+ state = module.params["state"]
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing = get_existing(module, args)
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+ commands = []
+ parents = []
+
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key != "vni":
+ if value == "true":
+ value = True
+ elif value == "false":
+ value = False
+ if existing.get(key) != value:
+ proposed[key] = value
+
+ if state == "present":
+ commands, parents = state_present(module, existing, proposed)
+ elif state == "absent" and existing:
+ commands, parents = state_absent(module, existing, proposed)
+
+ if commands:
+ candidate = CustomNetworkConfig(indent=3)
+ candidate.add(commands, parents=parents)
+ candidate = candidate.items_text()
+ if not module.check_mode:
+ load_config(module, candidate)
+ results["changed"] = True
+ results["commands"] = candidate
+ else:
+ results["commands"] = []
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_facts.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_facts.py
new file mode 100644
index 00000000..24e0dad2
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_facts.py
@@ -0,0 +1,268 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_facts
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Gets facts about NX-OS switches
+description:
+- Collects facts from Cisco Nexus devices running the NX-OS operating system. Fact
+ collection is supported over both C(network_cli) and C(httpapi). This module prepends
+ all of the base network fact keys with C(ansible_net_<fact>). The facts module
+ will always collect a base set of facts from the device and can enable or disable
+ collection of additional facts.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ gather_subset:
+ description:
+ - When supplied, this argument will gather operational facts only for the given subset. Possible
+ values for this argument include C(all), C(hardware), C(config), C(legacy), C(interfaces), and C(min). Can
+ specify a list of values to include a larger subset. Values can also be used
+ with an initial C(!) to specify that a specific subset should not be collected.
+ required: false
+ default: 'min'
+ type: list
+ elements: str
+ gather_network_resources:
+ description:
+ - When supplied, this argument will gather configuration facts only for the given subset.
+ Can specify a list of values to include a larger subset. Values can
+ also be used with an initial C(!) to specify that a specific subset should
+ not be collected.
+ - Valid subsets are C(all), C(bfd_interfaces), C(lag_interfaces),
+ C(telemetry), C(vlans), C(lacp), C(lacp_interfaces), C(interfaces), C(l3_interfaces),
+ C(l2_interfaces), C(lldp_global), C(acls), C(acl_interfaces), C(ospfv2), C(ospfv3), C(ospf_interfaces),
+ C(bgp_global), C(bgp_address_family), C(route_maps), C(prefix_lists), C(logging_global), C(ntp_global),
+ C(snmp_server), C(hostname).
+ required: false
+ type: list
+ elements: str
+ available_network_resources:
+ description: When set to C(true) a list of network resources for which resource modules are available will be provided.
+ type: bool
+ default: false
+"""
+
+EXAMPLES = """
+- name: Gather all legacy facts
+ cisco.nxos.nxos_facts:
+ gather_subset: all
+- name: Gather only the config and default facts
+ cisco.nxos.nxos_facts:
+ gather_subset:
+ - config
+- name: Do not gather hardware facts
+ cisco.nxos.nxos_facts:
+ gather_subset:
+ - '!hardware'
+- name: Gather legacy and resource facts
+ cisco.nxos.nxos_facts:
+ gather_subset: all
+ gather_network_resources: all
+- name: Gather only the interfaces resource facts and no legacy facts
+ cisco.nxos.nxos_facts:
+ gather_subset:
+ - '!all'
+ - '!min'
+ gather_network_resources:
+ - interfaces
+- name: Gather interfaces resource and minimal legacy facts
+ cisco.nxos.nxos_facts:
+ gather_subset: min
+ gather_network_resources: interfaces
+"""
+
+RETURN = """
+ansible_net_gather_subset:
+ description: The list of fact subsets collected from the device
+ returned: always
+ type: list
+ansible_net_gather_network_resources:
+ description: The list of fact for network resource subsets collected from the device
+ returned: when the resource is configured
+ type: list
+# default
+ansible_net_model:
+ description: The model name returned from the device
+ returned: always
+ type: str
+ansible_net_serialnum:
+ description: The serial number of the remote device
+ returned: always
+ type: str
+ansible_net_version:
+ description: The operating system version running on the remote device
+ returned: always
+ type: str
+ansible_net_hostname:
+ description: The configured hostname of the device
+ returned: always
+ type: str
+ansible_net_image:
+ description: The image file the device is running
+ returned: always
+ type: str
+ansible_net_api:
+ description: The name of the transport
+ returned: always
+ type: str
+ansible_net_license_hostid:
+ description: The License host id of the device
+ returned: always
+ type: str
+ansible_net_python_version:
+ description: The Python version Ansible controller is using
+ returned: always
+ type: str
+# hardware
+ansible_net_filesystems:
+ description: All file system names available on the device
+ returned: when hardware is configured
+ type: list
+ansible_net_memfree_mb:
+ description: The available free memory on the remote device in Mb
+ returned: when hardware is configured
+ type: int
+ansible_net_memtotal_mb:
+ description: The total memory on the remote device in Mb
+ returned: when hardware is configured
+ type: int
+# config
+ansible_net_config:
+ description: The current active config from the device
+ returned: when config is configured
+ type: str
+# interfaces
+ansible_net_all_ipv4_addresses:
+ description: All IPv4 addresses configured on the device
+ returned: when interfaces is configured
+ type: list
+ansible_net_all_ipv6_addresses:
+ description: All IPv6 addresses configured on the device
+ returned: when interfaces is configured
+ type: list
+ansible_net_interfaces:
+ description: A hash of all interfaces running on the system
+ returned: when interfaces is configured
+ type: dict
+ansible_net_neighbors:
+ description:
+ - The list of LLDP and CDP neighbors from the device. If both,
+ CDP and LLDP neighbor data is present on one port, CDP is preferred.
+ returned: when interfaces is configured
+ type: dict
+# legacy (pre Ansible 2.2)
+fan_info:
+ description: A hash of facts about fans in the remote device
+ returned: when legacy is configured
+ type: dict
+hostname:
+ description: The configured hostname of the remote device
+ returned: when legacy is configured
+ type: dict
+interfaces_list:
+ description: The list of interface names on the remote device
+ returned: when legacy is configured
+ type: dict
+kickstart:
+ description: The software version used to boot the system
+ returned: when legacy is configured
+ type: str
+module:
+ description: A hash of facts about the modules in a remote device
+ returned: when legacy is configured
+ type: dict
+platform:
+ description: The hardware platform reported by the remote device
+ returned: when legacy is configured
+ type: str
+power_supply_info:
+ description: A hash of facts about the power supplies in the remote device
+ returned: when legacy is configured
+ type: str
+vlan_list:
+ description: The list of VLAN IDs configured on the remote device
+ returned: when legacy is configured
+ type: list
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.connection import Connection
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.facts.facts import (
+ FactsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.facts.facts import Facts
+
+
+def get_chassis_type(connection):
+ """Return facts resource subsets based on
+ chassis model.
+ """
+ target_type = "nexus"
+
+ device_info = connection.get_device_info()
+ model = device_info.get("network_os_model", "")
+ platform = device_info.get("network_os_platform", "")
+
+ if platform.startswith("DS-") and "MDS" in model:
+ target_type = "mds"
+
+ return target_type
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: ansible_facts
+ """
+ argument_spec = FactsArgs.argument_spec
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ connection = Connection(module._socket_path)
+ facts = Facts(module, chassis_type=get_chassis_type(connection))
+
+ warnings = []
+
+ ansible_facts = {}
+ if module.params.get("available_network_resources"):
+ ansible_facts["available_network_resources"] = sorted(facts.get_resource_subsets().keys())
+
+ result = facts.get_facts()
+ additional_facts, additional_warnings = result
+ ansible_facts.update(additional_facts)
+ warnings.extend(additional_warnings)
+
+ module.exit_json(ansible_facts=ansible_facts, warnings=warnings)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_feature.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_feature.py
new file mode 100644
index 00000000..c9229c4d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_feature.py
@@ -0,0 +1,308 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_feature
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manage features in NX-OS switches.
+notes:
+- Tested against Cisco MDS NX-OS 9.2(2)
+description:
+- Offers ability to enable and disable features in NX-OS.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+- Suhas Bharadwaj (@srbharadwaj)
+options:
+ feature:
+ description:
+ - Name of feature.
+ required: true
+ type: str
+ state:
+ description:
+ - Desired state of the feature.
+ required: false
+ default: enabled
+ choices:
+ - enabled
+ - disabled
+ type: str
+"""
+
+EXAMPLES = """
+- name: Ensure lacp is enabled
+ cisco.nxos.nxos_feature:
+ feature: lacp
+ state: enabled
+
+- name: Ensure ospf is disabled
+ cisco.nxos.nxos_feature:
+ feature: ospf
+ state: disabled
+
+- name: Ensure vpc is enabled
+ cisco.nxos.nxos_feature:
+ feature: vpc
+ state: enabled
+"""
+
+RETURN = """
+commands:
+ description: The set of commands to be sent to the remote device
+ returned: always
+ type: list
+ sample: ['nv overlay evpn']
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.connection import ConnectionError
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_config,
+ load_config,
+ run_commands,
+)
+
+
+def get_available_features(feature, module):
+ available_features = {}
+ feature_regex = r"(?P<feature>\S+)\s+\d+\s+(?P<state>.*)"
+ command = {"command": "show feature", "output": "text"}
+
+ try:
+ body = run_commands(module, [command])[0]
+ split_body = body.splitlines()
+ except (KeyError, IndexError):
+ return {}
+
+ for line in split_body:
+ try:
+ match_feature = re.match(feature_regex, line, re.DOTALL)
+ feature_group = match_feature.groupdict()
+ feature = feature_group["feature"]
+ state = feature_group["state"]
+ except AttributeError:
+ feature = ""
+ state = ""
+
+ if feature and state:
+ if "enabled" in state:
+ state = "enabled"
+
+ if feature not in available_features:
+ available_features[feature] = state
+ else:
+ if available_features[feature] == "disabled" and state == "enabled":
+ available_features[feature] = state
+
+ # certain configurable features do not
+ # show up in the output of "show feature"
+ # but appear in running-config when set
+ run_cfg = get_config(module, flags=["| include ^feature"])
+ for item in re.findall(r"feature\s(.*)", run_cfg):
+ if item not in available_features:
+ available_features[item] = "enabled"
+
+ if "fabric forwarding" not in available_features:
+ available_features["fabric forwarding"] = "disabled"
+
+ return available_features
+
+
+def get_commands(proposed, existing, state, module):
+ feature = validate_feature(module, mode="config")
+ commands = []
+ feature_check = proposed == existing
+ if not feature_check:
+ if state == "enabled":
+ command = "feature {0}".format(feature)
+ commands.append(command)
+ elif state == "disabled":
+ command = "no feature {0}".format(feature)
+ commands.append(command)
+ return commands
+
+
+def get_mds_mapping_features():
+ feature_to_be_mapped = {
+ "show": {
+ "fcrxbbcredit": "extended_credit",
+ "port-track": "port_track",
+ "scp-server": "scpServer",
+ "sftp-server": "sftpServer",
+ "ssh": "sshServer",
+ "tacacs+": "tacacs",
+ "telnet": "telnetServer",
+ },
+ "config": {
+ "extended_credit": "fcrxbbcredit",
+ "port_track": "port-track",
+ "scpServer": "scp-server",
+ "sftpServer": "sftp-server",
+ "sshServer": "ssh",
+ "tacacs": "tacacs+",
+ "telnetServer": "telnet",
+ },
+ }
+ return feature_to_be_mapped
+
+
+def validate_feature(module, mode="show"):
+ """Some features may need to be mapped due to inconsistency
+ between how they appear from "show feature" output and
+ how they are configured"""
+
+ feature = module.params["feature"]
+
+ try:
+ info = get_capabilities(module)
+ device_info = info.get("device_info", {})
+ os_version = device_info.get("network_os_version", "")
+ os_platform = device_info.get("network_os_platform", "")
+ except ConnectionError:
+ os_version = ""
+ os_platform = ""
+
+ if "8.1" in os_version:
+ feature_to_be_mapped = {
+ "show": {
+ "nv overlay": "nve",
+ "vn-segment-vlan-based": "vnseg_vlan",
+ "hsrp": "hsrp_engine",
+ "fabric multicast": "fabric_mcast",
+ "scp-server": "scpServer",
+ "sftp-server": "sftpServer",
+ "sla responder": "sla_responder",
+ "sla sender": "sla_sender",
+ "ssh": "sshServer",
+ "tacacs+": "tacacs",
+ "telnet": "telnetServer",
+ "ethernet-link-oam": "elo",
+ },
+ "config": {
+ "nve": "nv overlay",
+ "vnseg_vlan": "vn-segment-vlan-based",
+ "hsrp_engine": "hsrp",
+ "fabric_mcast": "fabric multicast",
+ "scpServer": "scp-server",
+ "sftpServer": "sftp-server",
+ "sla_sender": "sla sender",
+ "sla_responder": "sla responder",
+ "sshServer": "ssh",
+ "tacacs": "tacacs+",
+ "telnetServer": "telnet",
+ "elo": "ethernet-link-oam",
+ },
+ }
+ else:
+ feature_to_be_mapped = {
+ "show": {
+ "nv overlay": "nve",
+ "vn-segment-vlan-based": "vnseg_vlan",
+ "hsrp": "hsrp_engine",
+ "fabric multicast": "fabric_mcast",
+ "scp-server": "scpServer",
+ "sftp-server": "sftpServer",
+ "sla responder": "sla_responder",
+ "sla sender": "sla_sender",
+ "ssh": "sshServer",
+ "tacacs+": "tacacs",
+ "telnet": "telnetServer",
+ "ethernet-link-oam": "elo",
+ "port-security": "eth_port_sec",
+ },
+ "config": {
+ "nve": "nv overlay",
+ "vnseg_vlan": "vn-segment-vlan-based",
+ "hsrp_engine": "hsrp",
+ "fabric_mcast": "fabric multicast",
+ "scpServer": "scp-server",
+ "sftpServer": "sftp-server",
+ "sla_sender": "sla sender",
+ "sla_responder": "sla responder",
+ "sshServer": "ssh",
+ "tacacs": "tacacs+",
+ "telnetServer": "telnet",
+ "elo": "ethernet-link-oam",
+ "eth_port_sec": "port-security",
+ },
+ }
+
+ if os_platform.startswith("DS-"):
+ feature_to_be_mapped = get_mds_mapping_features()
+
+ if feature in feature_to_be_mapped[mode]:
+ feature = feature_to_be_mapped[mode][feature]
+
+ return feature
+
+
+def main():
+ argument_spec = dict(
+ feature=dict(type="str", required=True),
+ state=dict(choices=["enabled", "disabled"], default="enabled"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = dict(changed=False, warnings=warnings)
+
+ feature = validate_feature(module)
+ state = module.params["state"].lower()
+
+ available_features = get_available_features(feature, module)
+ if feature not in available_features:
+ module.fail_json(
+ msg="Invalid feature name.",
+ features_currently_supported=available_features,
+ invalid_feature=feature,
+ )
+ else:
+ existstate = available_features[feature]
+
+ existing = dict(state=existstate)
+ proposed = dict(state=state)
+ results["changed"] = False
+
+ cmds = get_commands(proposed, existing, state, module)
+
+ if cmds:
+ # On N35 A8 images, some features return a yes/no prompt
+ # on enablement or disablement. Bypass using terminal dont-ask
+ cmds.insert(0, "terminal dont-ask")
+ if not module.check_mode:
+ load_config(module, cmds)
+ results["changed"] = True
+
+ results["commands"] = cmds
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_file_copy.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_file_copy.py
new file mode 100644
index 00000000..4847a446
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_file_copy.py
@@ -0,0 +1,497 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_file_copy
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Copy a file to a remote NXOS device.
+description:
+- This module supports two different workflows for copying a file to flash (or bootflash)
+ on NXOS devices. Files can either be (1) pushed from the Ansible controller to
+ the device or (2) pulled from a remote SCP file server to the device. File copies
+ are initiated from the NXOS device to the remote SCP server. This module only supports
+ the use of connection C(network_cli) or C(Cli) transport with connection C(local).
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+- Rewritten as a plugin by (@mikewiebe)
+notes:
+- Tested against NXOS 7.0(3)I2(5), 7.0(3)I4(6), 7.0(3)I5(3), 7.0(3)I6(1), 7.0(3)I7(3),
+ 6.0(2)A8(8), 7.0(3)F3(4), 7.3(0)D1(1), 8.3(0), 9.2, 9.3
+- Limited Support for Cisco MDS
+- When pushing files (file_pull is False) to the NXOS device, feature scp-server must
+ be enabled.
+- When pulling files (file_pull is True) to the NXOS device, feature scp-server is
+ not required.
+- When pulling files (file_pull is True) to the NXOS device, no transfer will take
+ place if the file is already present.
+- Check mode will tell you if the file would be copied.
+requirements:
+- paramiko or libssh (required when file_pull is False)
+- scp (required when file_pull is False)
+options:
+ local_file:
+ description:
+ - When (file_pull is False) this is the path to the local file on the Ansible
+ controller. The local directory must exist.
+ - When (file_pull is True) this is the target file name on the NXOS device.
+ type: path
+ remote_file:
+ description:
+ - When (file_pull is False) this is the remote file path on the NXOS device. If
+ omitted, the name of the local file will be used. The remote directory must
+ exist.
+ - When (file_pull is True) this is the full path to the file on the remote SCP
+ server to be copied to the NXOS device.
+ type: path
+ file_system:
+ description:
+ - The remote file system on the nxos device. If omitted, devices that support
+ a I(file_system) parameter will use their default values.
+ default: 'bootflash:'
+ type: str
+ connect_ssh_port:
+ description:
+ - B(Deprecated)
+ - This option has been deprecated and will be removed in a release after 2024-06-01.
+ - To maintain backwards compatibility, this option will continue to override the value of I(ansible_port) until removed.
+ - HORIZONTALLINE
+ - SSH server port used for file transfer.
+ - Only used when I(file_pull) is C(True).
+ default: 22
+ type: int
+ file_pull:
+ description:
+ - When (False) file is copied from the Ansible controller to the NXOS device.
+ - When (True) file is copied from a remote SCP server to the NXOS device. In this
+ mode, the file copy is initiated from the NXOS device.
+ - If the file is already present on the device it will be overwritten and therefore
+ the operation is NOT idempotent.
+ type: bool
+ default: false
+ file_pull_protocol:
+ description:
+ - When file_pull is True, this can be used to define the transfer protocol for
+ copying file from remote to the NXOS device.
+ - When (file_pull is False), this is not used.
+ default: 'scp'
+ choices:
+ - scp
+ - sftp
+ - ftp
+ - http
+ - https
+ - tftp
+ type: str
+ file_pull_compact:
+ description:
+ - When file_pull is True, this is used to compact nxos image files. This option
+ can only be used with nxos image files.
+ - When (file_pull is False), this is not used.
+ type: bool
+ default: false
+ file_pull_kstack:
+ description:
+ - When file_pull is True, this can be used to speed up file copies when the nxos
+ running image supports the use-kstack option.
+ - When (file_pull is False), this is not used.
+ type: bool
+ default: false
+ local_file_directory:
+ description:
+ - When (file_pull is True) file is copied from a remote SCP server to the NXOS
+ device, and written to this directory on the NXOS device. If the directory does
+ not exist, it will be created under the file_system. This is an optional parameter.
+ - When (file_pull is False), this is not used.
+ type: path
+ file_pull_timeout:
+ description:
+ - B(Deprecated)
+ - This option has been deprecated and will be removed in a release after 2024-06-01.
+ - To maintain backwards compatibility, this option will continue to override the value of I(ansible_command_timeout) until removed.
+ - HORIZONTALLINE
+ - Use this parameter to set timeout in seconds, when transferring large files
+ or when the network is slow.
+ - When (file_pull is False), this is not used.
+ default: 300
+ type: int
+ remote_scp_server:
+ description:
+ - The remote scp server address when file_pull is True. This is required if file_pull
+ is True.
+ - When (file_pull is False), this is not used.
+ type: str
+ remote_scp_server_user:
+ description:
+ - The remote scp server username when file_pull is True. This is required if file_pull
+ is True.
+ - When (file_pull is False), this is not used.
+ type: str
+ remote_scp_server_password:
+ description:
+ - The remote scp server password when file_pull is True. This is required if file_pull
+ is True.
+ - When (file_pull is False), this is not used.
+ type: str
+ vrf:
+ description:
+ - The VRF used to pull the file. Useful when no vrf management is defined.
+ - This option is not applicable for MDS switches.
+ default: management
+ type: str
+"""
+
+EXAMPLES = """
+# File copy from ansible controller to nxos device
+- name: copy from server to device
+ cisco.nxos.nxos_file_copy:
+ local_file: ./test_file.txt
+ remote_file: test_file.txt
+
+# Initiate file copy from the nxos device to transfer file from an SCP server back to the nxos device
+- name: initiate file copy from device
+ cisco.nxos.nxos_file_copy:
+ file_pull: true
+ local_file: xyz
+ local_file_directory: dir1/dir2/dir3
+ remote_file: /mydir/abc
+ remote_scp_server: 192.168.0.1
+ remote_scp_server_user: myUser
+ remote_scp_server_password: myPassword
+ vrf: management
+
+# Initiate file copy from the nxos device to transfer file from a ftp server back to the nxos device.
+# remote_scp_server_user and remote_scp_server_password are used to login to the FTP server.
+- name: initiate file copy from device
+ cisco.nxos.nxos_file_copy:
+ file_pull: true
+ file_pull_protocol: ftp
+ local_file: xyz
+ remote_file: /mydir/abc
+ remote_scp_server: 192.168.0.1
+ remote_scp_server_user: myUser
+ remote_scp_server_password: myPassword
+ vrf: management
+"""
+
+RETURN = """
+transfer_status:
+ description: Whether a file was transferred to the nxos device.
+ returned: success
+ type: str
+ sample: 'Sent'
+local_file:
+ description: The path of the local file.
+ returned: success
+ type: str
+ sample: '/path/to/local/file'
+remote_file:
+ description: The path of the remote file.
+ returned: success
+ type: str
+ sample: '/path/to/remote/file'
+remote_scp_server:
+ description: The name of the scp server when file_pull is True.
+ returned: success
+ type: str
+ sample: 'fileserver.example.com'
+changed:
+ description: Indicates whether or not the file was copied.
+ returned: success
+ type: bool
+ sample: true
+"""
+
+import hashlib
+import os
+import re
+
+from ansible.module_utils._text import to_bytes, to_text
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.network import (
+ get_resource_connection,
+)
+
+
+class FileCopy:
+ def __init__(self, module):
+ self._module = module
+ self._connection = get_resource_connection(self._module)
+ device_info = self._connection.get_device_info()
+ self._model = device_info.get("network_os_model", "")
+ self._platform = device_info.get("network_os_platform", "")
+
+
+class FilePush(FileCopy):
+ def __init__(self, module):
+ super(FilePush, self).__init__(module)
+ self.result = {}
+
+ def md5sum_check(self, dst, file_system):
+ command = "show file {0}{1} md5sum".format(file_system, dst)
+ remote_filehash = self._connection.run_commands(command)[0]
+ remote_filehash = to_bytes(remote_filehash, errors="surrogate_or_strict")
+
+ local_file = self._module.params["local_file"]
+ try:
+ with open(local_file, "rb") as f:
+ filecontent = f.read()
+ except (OSError, IOError) as exc:
+ self._module.fail_json("Error reading the file: {0}".format(to_text(exc)))
+
+ filecontent = to_bytes(filecontent, errors="surrogate_or_strict")
+ local_filehash = hashlib.md5(filecontent).hexdigest()
+
+ decoded_rhash = remote_filehash.decode("UTF-8")
+
+ if local_filehash == decoded_rhash:
+ return True
+ else:
+ return False
+
+ def remote_file_exists(self, remote_file, file_system):
+ command = "dir {0}/{1}".format(file_system, remote_file)
+ body = self._connection.run_commands(command)[0]
+
+ if "No such file" in body:
+ return False
+ else:
+ return self.md5sum_check(remote_file, file_system)
+
+ def get_flash_size(self, file_system):
+ command = "dir {0}".format(file_system)
+ body = self._connection.run_commands(command)[0]
+
+ match = re.search(r"(\d+) bytes free", body)
+ if match:
+ bytes_free = match.group(1)
+ return int(bytes_free)
+
+ match = re.search(r"No such file or directory", body)
+ if match:
+ self._module.fail_json("Invalid nxos filesystem {0}".format(file_system))
+ else:
+ self._module.fail_json("Unable to determine size of filesystem {0}".format(file_system))
+
+ def enough_space(self, file, file_system):
+ flash_size = self.get_flash_size(file_system)
+ file_size = os.path.getsize(file)
+ if file_size > flash_size:
+ return False
+
+ return True
+
+ def transfer_file_to_device(self, remote_file):
+ local_file = self._module.params["local_file"]
+ file_system = self._module.params["file_system"]
+
+ if not self.enough_space(local_file, file_system):
+ self._module.fail_json("Could not transfer file. Not enough space on device.")
+
+ # frp = full_remote_path, flp = full_local_path
+ frp = remote_file
+ if not file_system.startswith("bootflash:"):
+ frp = "{0}{1}".format(file_system, remote_file)
+ flp = os.path.join(os.path.abspath(local_file))
+
+ try:
+ self._connection.copy_file(
+ source=flp,
+ destination=frp,
+ proto="scp",
+ timeout=self._connection.get_option("persistent_command_timeout"),
+ )
+ self.result["transfer_status"] = "Sent: File copied to remote device."
+ except Exception as exc:
+ self.result["failed"] = True
+ self.result["msg"] = "Exception received : %s" % exc
+
+ def run(self):
+ local_file = self._module.params["local_file"]
+ remote_file = self._module.params["remote_file"] or os.path.basename(local_file)
+ file_system = self._module.params["file_system"]
+
+ if not os.path.isfile(local_file):
+ self._module.fail_json("Local file {0} not found".format(local_file))
+
+ remote_file = remote_file or os.path.basename(local_file)
+ remote_exists = self.remote_file_exists(remote_file, file_system)
+
+ if not remote_exists:
+ self.result["changed"] = True
+ file_exists = False
+ else:
+ self.result["transfer_status"] = "No Transfer: File already copied to remote device."
+ file_exists = True
+
+ if not self._module.check_mode and not file_exists:
+ self.transfer_file_to_device(remote_file)
+
+ self.result["local_file"] = local_file
+ if remote_file is None:
+ remote_file = os.path.basename(local_file)
+ self.result["remote_file"] = remote_file
+ self.result["file_system"] = file_system
+
+ return self.result
+
+
+class FilePull(FileCopy):
+ def __init__(self, module):
+ super(FilePull, self).__init__(module)
+ self.result = {}
+
+ def mkdir(self, directory):
+ local_dir_root = "/"
+ dir_array = directory.split("/")
+ for each in dir_array:
+ if each:
+ mkdir_cmd = "mkdir " + local_dir_root + each
+ self._connection.run_commands(mkdir_cmd)
+ local_dir_root += each + "/"
+ return local_dir_root
+
+ def copy_file_from_remote(self, local, local_file_directory, file_system):
+ # Build copy command components that will be used to initiate copy from the nxos device.
+ cmdroot = "copy " + self._module.params["file_pull_protocol"] + "://"
+ ruser = self._module.params["remote_scp_server_user"] + "@"
+ rserver = self._module.params["remote_scp_server"]
+ rserverpassword = self._module.params["remote_scp_server_password"]
+ rfile = self._module.params["remote_file"] + " "
+ if not rfile.startswith("/"):
+ rfile = "/" + rfile
+
+ if not self._platform.startswith("DS-") and "MDS" not in self._model:
+ vrf = " vrf " + self._module.params["vrf"]
+ else:
+ vrf = ""
+ if self._module.params["file_pull_compact"]:
+ compact = " compact "
+ else:
+ compact = ""
+ if self._module.params["file_pull_kstack"]:
+ kstack = " use-kstack "
+ else:
+ kstack = ""
+
+ # Create local file directory under NX-OS filesystem if
+ # local_file_directory playbook parameter is set.
+ local_dir_root = "/"
+ if local_file_directory:
+ local_dir_root = self.mkdir(local_file_directory)
+
+ copy_cmd = (
+ cmdroot
+ + ruser
+ + rserver
+ + rfile
+ + file_system
+ + local_dir_root
+ + local
+ + compact
+ + vrf
+ + kstack
+ )
+
+ self.result["copy_cmd"] = copy_cmd
+ pulled = self._connection.pull_file(command=copy_cmd, remotepassword=rserverpassword)
+ if pulled:
+ self.result[
+ "transfer_status"
+ ] = "Received: File copied/pulled to nxos device from remote scp server."
+ else:
+ self.result["failed"] = True
+
+ def run(self):
+ self.result["failed"] = False
+ remote_file = self._module.params["remote_file"]
+ local_file = self._module.params["local_file"] or remote_file.split("/")[-1]
+ file_system = self._module.params["file_system"]
+ # Note: This is the local file directory on the remote nxos device.
+ local_file_dir = self._module.params["local_file_directory"]
+
+ if not self._module.check_mode:
+ self.copy_file_from_remote(local_file, local_file_dir, file_system)
+
+ self.result["remote_file"] = remote_file
+ if local_file_dir:
+ dir = local_file_dir
+ else:
+ dir = ""
+ self.result["local_file"] = file_system + dir + "/" + local_file
+ self.result["remote_scp_server"] = self._module.params["remote_scp_server"]
+ self.result["file_system"] = self._module.params["file_system"]
+
+ if not self.result["failed"]:
+ self.result["changed"] = True
+
+ return self.result
+
+
+def main():
+ argument_spec = dict(
+ vrf=dict(type="str", default="management"),
+ connect_ssh_port=dict(type="int", default=22),
+ file_system=dict(type="str", default="bootflash:"),
+ file_pull=dict(type="bool", default=False),
+ file_pull_timeout=dict(type="int", default=300),
+ file_pull_protocol=dict(
+ type="str",
+ default="scp",
+ choices=["scp", "sftp", "http", "https", "tftp", "ftp"],
+ ),
+ file_pull_compact=dict(type="bool", default=False),
+ file_pull_kstack=dict(type="bool", default=False),
+ local_file=dict(type="path"),
+ local_file_directory=dict(type="path"),
+ remote_file=dict(type="path"),
+ remote_scp_server=dict(type="str"),
+ remote_scp_server_user=dict(type="str"),
+ remote_scp_server_password=dict(no_log=True),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_if=[("file_pull", True, ("remote_file", "remote_scp_server"))],
+ required_together=[("remote_scp_server", "remote_scp_server_user")],
+ supports_check_mode=True,
+ )
+
+ file_pull = module.params["file_pull"]
+
+ warnings = list()
+
+ if file_pull:
+ result = FilePull(module).run()
+ else:
+ result = FilePush(module).run()
+
+ result["warnings"] = warnings
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_gir.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir.py
new file mode 100644
index 00000000..c473ff5a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir.py
@@ -0,0 +1,342 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_gir
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Trigger a graceful removal or insertion (GIR) of the switch.
+description:
+- Trigger a graceful removal or insertion (GIR) of the switch.
+- GIR processing may take more than 2 minutes. Timeout settings are automatically
+ extended to 200s when user timeout settings are insufficient.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state) has effect only in combination with C(system_mode_maintenance_timeout)
+ or C(system_mode_maintenance_on_reload_reset_reason).
+- Using C(system_mode_maintenance) and C(system_mode_maintenance_dont_generate_profile)
+ would make the module fail, but the system mode will be triggered anyway.
+options:
+ system_mode_maintenance:
+ description:
+ - When C(system_mode_maintenance=true) it puts all enabled protocols in maintenance
+ mode (using the isolate command). When C(system_mode_maintenance=false) it puts
+ all enabled protocols in normal mode (using the no isolate command).
+ type: bool
+ system_mode_maintenance_dont_generate_profile:
+ description:
+ - When C(system_mode_maintenance_dont_generate_profile=true) it prevents the dynamic
+ searching of enabled protocols and executes commands configured in a maintenance-mode
+ profile. Use this option if you want the system to use a maintenance-mode profile
+ that you have created. When C(system_mode_maintenance_dont_generate_profile=false)
+ it prevents the dynamic searching of enabled protocols and executes commands
+ configured in a normal-mode profile. Use this option if you want the system
+ to use a normal-mode profile that you have created.
+ type: bool
+ system_mode_maintenance_timeout:
+ description:
+ - Keeps the switch in maintenance mode for a specified number of minutes. Range
+ is 5-65535.
+ type: str
+ system_mode_maintenance_shutdown:
+ description:
+ - Shuts down all protocols, vPC domains, and interfaces except the management
+ interface (using the shutdown command). This option is disruptive while C(system_mode_maintenance)
+ (which uses the isolate command) is not.
+ type: bool
+ system_mode_maintenance_on_reload_reset_reason:
+ description:
+ - Boots the switch into maintenance mode automatically in the event of a specified
+ system crash. Note that not all reset reasons are applicable for all platforms.
+ Also if reset reason is set to match_any, it is not idempotent as it turns on
+ all reset reasons. If reset reason is match_any and state is absent, it turns
+ off all the reset reasons.
+ choices:
+ - hw_error
+ - svc_failure
+ - kern_failure
+ - wdog_timeout
+ - fatal_error
+ - lc_failure
+ - match_any
+ - manual_reload
+ - any_other
+ - maintenance
+ type: str
+ state:
+ description:
+ - Specify desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# Trigger system maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance: true
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Trigger system normal mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance: false
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Configure on-reload reset-reason for maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance_on_reload_reset_reason: manual_reload
+ state: present
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Add on-reload reset-reason for maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance_on_reload_reset_reason: hw_error
+ state: present
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Remove on-reload reset-reason for maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance_on_reload_reset_reason: manual_reload
+ state: absent
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Set timeout for maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance_timeout: 30
+ state: present
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+# Remove timeout for maintenance mode
+- cisco.nxos.nxos_gir:
+ system_mode_maintenance_timeout: 30
+ state: absent
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+final_system_mode:
+ description: describe the last system mode
+ returned: verbose mode
+ type: str
+ sample: normal
+updates:
+ description: commands sent to the device
+ returned: verbose mode
+ type: list
+ sample: ["terminal dont-ask", "system mode maintenance timeout 10"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def get_system_mode(module):
+ command = {"command": "show system mode", "output": "text"}
+ body = run_commands(module, [command])[0]
+ if body and "normal" in body.lower():
+ mode = "normal"
+ else:
+ mode = "maintenance"
+ return mode
+
+
+def get_maintenance_timeout(module):
+ command = {"command": "show maintenance timeout", "output": "text"}
+ body = run_commands(module, [command])[0]
+ timeout = body.split()[4]
+ return timeout
+
+
+def get_reset_reasons(module):
+ command = {
+ "command": "show maintenance on-reload reset-reasons",
+ "output": "text",
+ }
+ body = run_commands(module, [command])[0]
+ return body
+
+
+def get_commands(module, state, mode):
+ commands = list()
+ if module.params["system_mode_maintenance"] is True and mode == "normal":
+ commands.append("system mode maintenance")
+ elif module.params["system_mode_maintenance"] is False and mode == "maintenance":
+ commands.append("no system mode maintenance")
+
+ elif (
+ module.params["system_mode_maintenance_dont_generate_profile"] is True and mode == "normal"
+ ):
+ commands.append("system mode maintenance dont-generate-profile")
+ elif (
+ module.params["system_mode_maintenance_dont_generate_profile"] is False
+ and mode == "maintenance"
+ ):
+ commands.append("no system mode maintenance dont-generate-profile")
+
+ elif module.params["system_mode_maintenance_timeout"]:
+ timeout = get_maintenance_timeout(module)
+ if state == "present" and timeout != module.params["system_mode_maintenance_timeout"]:
+ commands.append(
+ "system mode maintenance timeout {0}".format(
+ module.params["system_mode_maintenance_timeout"],
+ ),
+ )
+ elif state == "absent" and timeout == module.params["system_mode_maintenance_timeout"]:
+ commands.append(
+ "no system mode maintenance timeout {0}".format(
+ module.params["system_mode_maintenance_timeout"],
+ ),
+ )
+
+ elif module.params["system_mode_maintenance_shutdown"] and mode == "normal":
+ commands.append("system mode maintenance shutdown")
+ elif module.params["system_mode_maintenance_shutdown"] is False and mode == "maintenance":
+ commands.append("no system mode maintenance")
+
+ elif module.params["system_mode_maintenance_on_reload_reset_reason"]:
+ reset_reasons = get_reset_reasons(module)
+ if (
+ state == "present"
+ and module.params["system_mode_maintenance_on_reload_reset_reason"].lower()
+ not in reset_reasons.lower()
+ ):
+ commands.append(
+ "system mode maintenance on-reload "
+ "reset-reason {0}".format(
+ module.params["system_mode_maintenance_on_reload_reset_reason"],
+ ),
+ )
+ elif (
+ state == "absent"
+ and module.params["system_mode_maintenance_on_reload_reset_reason"].lower()
+ in reset_reasons.lower()
+ ):
+ commands.append(
+ "no system mode maintenance on-reload "
+ "reset-reason {0}".format(
+ module.params["system_mode_maintenance_on_reload_reset_reason"],
+ ),
+ )
+
+ if commands:
+ commands.insert(0, "terminal dont-ask")
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ system_mode_maintenance=dict(required=False, type="bool"),
+ system_mode_maintenance_dont_generate_profile=dict(required=False, type="bool"),
+ system_mode_maintenance_timeout=dict(required=False, type="str"),
+ system_mode_maintenance_shutdown=dict(required=False, type="bool"),
+ system_mode_maintenance_on_reload_reset_reason=dict(
+ required=False,
+ choices=[
+ "hw_error",
+ "svc_failure",
+ "kern_failure",
+ "wdog_timeout",
+ "fatal_error",
+ "lc_failure",
+ "match_any",
+ "manual_reload",
+ "any_other",
+ "maintenance",
+ ],
+ ),
+ state=dict(choices=["absent", "present"], default="present", required=False),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[
+ [
+ "system_mode_maintenance",
+ "system_mode_maintenance_dont_generate_profile",
+ "system_mode_maintenance_timeout",
+ "system_mode_maintenance_shutdown",
+ "system_mode_maintenance_on_reload_reset_reason",
+ ],
+ ],
+ required_one_of=[
+ [
+ "system_mode_maintenance",
+ "system_mode_maintenance_dont_generate_profile",
+ "system_mode_maintenance_timeout",
+ "system_mode_maintenance_shutdown",
+ "system_mode_maintenance_on_reload_reset_reason",
+ ],
+ ],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ state = module.params["state"]
+ mode = get_system_mode(module)
+ commands = get_commands(module, state, mode)
+ changed = False
+ if commands:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=commands)
+ else:
+ load_config(module, commands)
+ changed = True
+
+ result = {}
+ result["changed"] = changed
+ if module._verbosity > 0:
+ final_system_mode = get_system_mode(module)
+ result["final_system_mode"] = final_system_mode
+ result["updates"] = commands
+
+ result["warnings"] = warnings
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_gir_profile_management.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir_profile_management.py
new file mode 100644
index 00000000..84cfc145
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_gir_profile_management.py
@@ -0,0 +1,214 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_gir_profile_management
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Create a maintenance-mode or normal-mode profile for GIR.
+description:
+- Manage a maintenance-mode or normal-mode profile with configuration commands that
+ can be applied during graceful removal or graceful insertion.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) removes the whole profile.
+options:
+ commands:
+ description:
+ - List of commands to be included into the profile.
+ type: list
+ elements: str
+ mode:
+ description:
+ - Configure the profile as Maintenance or Normal mode.
+ required: true
+ choices:
+ - maintenance
+ - normal
+ type: str
+ state:
+ description:
+ - Specify desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# Create a maintenance-mode profile
+- cisco.nxos.nxos_gir_profile_management:
+ mode: maintenance
+ commands:
+ - router eigrp 11
+ - isolate
+
+# Remove the maintenance-mode profile
+- cisco.nxos.nxos_gir_profile_management:
+ mode: maintenance
+ state: absent
+"""
+
+RETURN = """
+proposed:
+ description: list of commands passed into module.
+ returned: verbose mode
+ type: list
+ sample: ["router eigrp 11", "isolate"]
+existing:
+ description: list of existing profile commands.
+ returned: verbose mode
+ type: list
+ sample: ["router bgp 65535","isolate","router eigrp 10","isolate",
+ "diagnostic bootup level complete"]
+end_state:
+ description: list of profile entries after module execution.
+ returned: verbose mode
+ type: list
+ sample: ["router bgp 65535","isolate","router eigrp 10","isolate",
+ "diagnostic bootup level complete","router eigrp 11", "isolate"]
+updates:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["configure maintenance profile maintenance-mode",
+ "router eigrp 11","isolate"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+def get_existing(module):
+ existing = []
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+
+ if module.params["mode"] == "maintenance":
+ parents = ["configure maintenance profile maintenance-mode"]
+ else:
+ parents = ["configure maintenance profile normal-mode"]
+
+ config = netcfg.get_section(parents)
+ if config:
+ existing = config.splitlines()
+ existing = [cmd.strip() for cmd in existing]
+ existing.pop(0)
+
+ return existing
+
+
+def state_present(module, existing, commands):
+ cmds = list()
+ if existing == commands:
+ # Idempotent case
+ return cmds
+ cmds.extend(commands)
+ if module.params["mode"] == "maintenance":
+ cmds.insert(0, "configure maintenance profile maintenance-mode")
+ else:
+ cmds.insert(0, "configure maintenance profile normal-mode")
+
+ return cmds
+
+
+def state_absent(module, existing, commands):
+ if module.params["mode"] == "maintenance":
+ cmds = ["no configure maintenance profile maintenance-mode"]
+ else:
+ cmds = ["no configure maintenance profile normal-mode"]
+ return cmds
+
+
+def invoke(name, *args, **kwargs):
+ func = globals().get(name)
+ if func:
+ return func(*args, **kwargs)
+
+
+def main():
+ argument_spec = dict(
+ commands=dict(required=False, type="list", elements="str"),
+ mode=dict(required=True, choices=["maintenance", "normal"]),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ state = module.params["state"]
+ commands = module.params["commands"] or []
+
+ if state == "absent" and commands:
+ module.fail_json(msg="when state is absent, no command can be used.")
+
+ existing = invoke("get_existing", module)
+ end_state = existing
+ changed = False
+
+ result = {}
+ cmds = []
+ if state == "present" or (state == "absent" and existing):
+ cmds = invoke("state_%s" % state, module, existing, commands)
+
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ if cmds:
+ load_config(module, cmds)
+ changed = True
+ end_state = invoke("get_existing", module)
+
+ result["changed"] = changed
+ if module._verbosity > 0:
+ end_state = invoke("get_existing", module)
+ result["end_state"] = end_state
+ result["existing"] = existing
+ result["proposed"] = commands
+ result["updates"] = cmds
+
+ result["warnings"] = warnings
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_hostname.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_hostname.py
new file mode 100644
index 00000000..42e45677
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_hostname.py
@@ -0,0 +1,229 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2022 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_hostname
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_hostname
+short_description: Hostname resource module.
+description:
+- This module manages hostname configuration on devices running Cisco NX-OS.
+version_added: 2.9.0
+notes:
+- Tested against NX-OS 9.3.6.
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section hostname).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of hostname configuration.
+ type: dict
+ suboptions:
+ hostname:
+ description: Hostname of the device.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - The states I(merged), I(replaced) and I(overridden) have identical
+ behaviour for this module.
+ - Refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+
+EXAMPLES = """
+# Using merged (replaced, overridden has the same behaviour)
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config | section ^hostname
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_hostname:
+ config:
+ hostname: NXOSv-9k
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - hostname NXOSv-9k
+#
+# after:
+# hostname: NXOSv-9k
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section ^hostname
+# hostname NXOSv-9k
+#
+
+# Using deleted
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section ^hostname
+# hostname NXOSv-9k
+
+- name: Delete hostname from running-config
+ cisco.nxos.nxos_hostname:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# hostname: NXOSv-9k
+#
+# commands:
+# - no hostname NXOSv-9k
+#
+# after: {}
+
+# Using gathered
+
+- name: Gather hostname facts using gathered
+ cisco.nxos.nxos_hostname:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# hostname: NXOSv-9k
+
+# Using rendered
+
+- name: Render platform specific configuration lines (without connecting to the device)
+ cisco.nxos.nxos_hostname:
+ config:
+ hostname: NXOSv-9k
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - hostname NXOSv-9k
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# hostname NXOSv-9k
+
+- name: Parse externally provided hostname config
+ cisco.nxos.nxos_hostname:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# hostname: NXOSv-9k
+
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: list
+ sample:
+ - hostname switch01
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when I(state) is C(rendered)
+ type: list
+ sample:
+ - hostname switch01
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when I(state) is C(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when I(state) is C(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.hostname.hostname import (
+ HostnameArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.hostname.hostname import (
+ Hostname,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=HostnameArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Hostname(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp.py
new file mode 100644
index 00000000..ee59b7dd
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp.py
@@ -0,0 +1,506 @@
+#!/usr/bin/python
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+DOCUMENTATION = """
+module: nxos_hsrp
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages HSRP configuration on NX-OS switches.
+description:
+- Manages HSRP configuration on NX-OS switches.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- HSRP feature needs to be enabled first on the system.
+- SVIs must exist before using this module.
+- Interface must be a L3 port before using this module.
+- HSRP cannot be configured on loopback interfaces.
+- MD5 authentication is only possible with HSRPv2 while it is ignored if HSRPv1 is
+ used instead, while it will not raise any error. Here we allow MD5 authentication
+ only with HSRPv2 in order to enforce better practice.
+options:
+ group:
+ description:
+ - HSRP group number.
+ required: true
+ type: str
+ interface:
+ description:
+ - Full name of interface that is being managed for HSRP.
+ required: true
+ type: str
+ version:
+ description:
+ - HSRP version.
+ default: '1'
+ choices:
+ - '1'
+ - '2'
+ type: str
+ priority:
+ description:
+ - HSRP priority or keyword 'default'.
+ type: str
+ preempt:
+ description:
+ - Enable/Disable preempt.
+ choices:
+ - enabled
+ - disabled
+ type: str
+ vip:
+ description:
+ - HSRP virtual IP address or keyword 'default'
+ type: str
+ auth_string:
+ description:
+ - Authentication string. If this needs to be hidden(for md5 type), the string
+ should be 7 followed by the key string. Otherwise, it can be 0 followed by key
+ string or just key string (for backward compatibility). For text type, this
+ should be just be a key string. if this is 'default', authentication is removed.
+ type: str
+ auth_type:
+ description:
+ - Authentication type.
+ choices:
+ - text
+ - md5
+ type: str
+ state:
+ description:
+ - Specify desired state of the resource.
+ choices:
+ - present
+ - absent
+ default: present
+ type: str
+"""
+
+EXAMPLES = """
+- name: Ensure HSRP is configured with following params on a SVI
+ cisco.nxos.nxos_hsrp:
+ group: 10
+ vip: 10.1.1.1
+ priority: 150
+ interface: vlan10
+ preempt: enabled
+
+- name: Ensure HSRP is configured with following params on a SVI with clear text authentication
+ cisco.nxos.nxos_hsrp:
+ group: 10
+ vip: 10.1.1.1
+ priority: 150
+ interface: vlan10
+ preempt: enabled
+ auth_type: text
+ auth_string: CISCO
+
+- name: Ensure HSRP is configured with md5 authentication and clear authentication
+ string
+ cisco.nxos.nxos_hsrp:
+ group: 10
+ vip: 10.1.1.1
+ priority: 150
+ interface: vlan10
+ preempt: enabled
+ auth_type: md5
+ auth_string: 0 1234
+
+- name: Ensure HSRP is configured with md5 authentication and hidden authentication
+ string
+ cisco.nxos.nxos_hsrp:
+ group: 10
+ vip: 10.1.1.1
+ priority: 150
+ interface: vlan10
+ preempt: enabled
+ auth_type: md5
+ auth_string: 7 1234
+
+- name: Remove HSRP config for given interface, group, and VIP
+ cisco.nxos.nxos_hsrp:
+ group: 10
+ interface: vlan10
+ vip: 10.1.1.1
+ state: absent
+"""
+
+RETURN = r"""
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface vlan10", "hsrp version 2", "hsrp 30", "ip 10.30.1.1"]
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_interface_type,
+ load_config,
+ run_commands,
+)
+
+
+PARAM_TO_DEFAULT_KEYMAP = {
+ "vip": None,
+ "priority": "100",
+ "auth_type": "text",
+ "auth_string": "cisco",
+}
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key in table:
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = str(value)
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def get_interface_mode(interface, intf_type, module):
+ command = "show interface {0} | json".format(interface)
+ interface = {}
+ mode = "unknown"
+ try:
+ body = run_commands(module, [command])[0]
+ except IndexError:
+ return None
+
+ if intf_type in ["ethernet", "portchannel"]:
+ interface_table = body["TABLE_interface"]["ROW_interface"]
+ mode = str(interface_table.get("eth_mode", "layer3"))
+ if mode == "access" or mode == "trunk":
+ mode = "layer2"
+ elif intf_type == "svi":
+ mode = "layer3"
+ return mode
+
+
+def get_hsrp_group(group, interface, module):
+ command = "show hsrp group {0} all | json".format(group)
+ hsrp = {}
+
+ hsrp_key = {
+ "sh_if_index": "interface",
+ "sh_group_num": "group",
+ "sh_group_version": "version",
+ "sh_cfg_prio": "priority",
+ "sh_preempt": "preempt",
+ "sh_vip": "vip",
+ "sh_authentication_type": "auth_type",
+ "sh_keystring_attr": "auth_enc",
+ "sh_authentication_data": "auth_string",
+ }
+
+ try:
+ body = run_commands(module, [command])[0]
+ hsrp_table = body["TABLE_grp_detail"]["ROW_grp_detail"]
+ if "sh_keystring_attr" not in hsrp_table:
+ del hsrp_key["sh_keystring_attr"]
+ if "unknown enum:" in str(hsrp_table):
+ hsrp_table = get_hsrp_group_unknown_enum(module, command, hsrp_table)
+ except (AttributeError, IndexError, TypeError, KeyError):
+ return {}
+
+ if isinstance(hsrp_table, dict):
+ hsrp_table = [hsrp_table]
+
+ for hsrp_group in hsrp_table:
+ parsed_hsrp = apply_key_map(hsrp_key, hsrp_group)
+
+ parsed_hsrp["interface"] = parsed_hsrp["interface"].lower()
+
+ if parsed_hsrp["version"] == "v1":
+ parsed_hsrp["version"] = "1"
+ elif parsed_hsrp["version"] == "v2":
+ parsed_hsrp["version"] = "2"
+
+ if parsed_hsrp["auth_type"] == "md5":
+ if parsed_hsrp["auth_enc"] == "hidden":
+ parsed_hsrp["auth_enc"] = "7"
+ else:
+ parsed_hsrp["auth_enc"] = "0"
+
+ if parsed_hsrp["interface"] == interface:
+ return parsed_hsrp
+
+ return hsrp
+
+
+def get_hsrp_group_unknown_enum(module, command, hsrp_table):
+ """Some older NXOS images fail to set the attr values when using structured output and
+ instead set the values to <unknown enum>. This fallback method is a workaround that
+ uses an unstructured (text) request to query the device a second time.
+ 'sh_preempt' is currently the only attr affected. Add checks for other attrs as needed.
+ """
+ if "unknown enum:" in hsrp_table["sh_preempt"]:
+ cmd = {"output": "text", "command": command.split("|")[0]}
+ out = run_commands(module, cmd)[0]
+ hsrp_table["sh_preempt"] = "enabled" if ("may preempt" in out) else "disabled"
+ return hsrp_table
+
+
+def get_commands_remove_hsrp(group, interface):
+ commands = ["interface {0}".format(interface), "no hsrp {0}".format(group)]
+ return commands
+
+
+def get_commands_config_hsrp(delta, interface, args, existing):
+ commands = []
+
+ config_args = {
+ "group": "hsrp {group}",
+ "priority": "{priority}",
+ "preempt": "{preempt}",
+ "vip": "{vip}",
+ }
+
+ preempt = delta.get("preempt", None)
+ group = delta.get("group", None)
+ vip = delta.get("vip", None)
+ priority = delta.get("priority", None)
+
+ if preempt:
+ if preempt == "enabled":
+ delta["preempt"] = "preempt"
+ elif preempt == "disabled":
+ delta["preempt"] = "no preempt"
+
+ if priority:
+ if priority == "default":
+ if existing and existing.get("priority") != PARAM_TO_DEFAULT_KEYMAP.get("priority"):
+ delta["priority"] = "no priority"
+ else:
+ del delta["priority"]
+ else:
+ delta["priority"] = "priority {0}".format(delta["priority"])
+
+ if vip:
+ if vip == "default":
+ if existing and existing.get("vip") != PARAM_TO_DEFAULT_KEYMAP.get("vip"):
+ delta["vip"] = "no ip"
+ else:
+ del delta["vip"]
+ else:
+ delta["vip"] = "ip {0}".format(delta["vip"])
+
+ for key in delta:
+ command = config_args.get(key, "DNE").format(**delta)
+ if command and command != "DNE":
+ if key == "group":
+ commands.insert(0, command)
+ else:
+ commands.append(command)
+ command = None
+
+ auth_type = delta.get("auth_type", None)
+ auth_string = delta.get("auth_string", None)
+ auth_enc = delta.get("auth_enc", None)
+ if auth_type or auth_string:
+ if not auth_type:
+ auth_type = args["auth_type"]
+ elif not auth_string:
+ auth_string = args["auth_string"]
+ if auth_string != "default":
+ if auth_type == "md5":
+ command = "authentication md5 key-string {0} {1}".format(auth_enc, auth_string)
+ commands.append(command)
+ elif auth_type == "text":
+ command = "authentication text {0}".format(auth_string)
+ commands.append(command)
+ else:
+ if existing and existing.get("auth_string") != PARAM_TO_DEFAULT_KEYMAP.get(
+ "auth_string",
+ ):
+ commands.append("no authentication")
+
+ if commands and not group:
+ commands.insert(0, "hsrp {0}".format(args["group"]))
+
+ version = delta.get("version", None)
+ if version:
+ if version == "2":
+ command = "hsrp version 2"
+ elif version == "1":
+ command = "hsrp version 1"
+ commands.insert(0, command)
+ commands.insert(0, "interface {0}".format(interface))
+
+ if commands:
+ if not commands[0].startswith("interface"):
+ commands.insert(0, "interface {0}".format(interface))
+
+ return commands
+
+
+def is_default(interface, module):
+ command = "show run interface {0}".format(interface)
+
+ try:
+ body = run_commands(module, [command], check_rc=False)[0]
+ if "invalid" in body.lower():
+ return "DNE"
+ else:
+ raw_list = body.split("\n")
+ if raw_list[-1].startswith("interface"):
+ return True
+ else:
+ return False
+ except KeyError:
+ return "DNE"
+
+
+def validate_config(body, vip, module):
+ new_body = "".join(body)
+ if "invalid ip address" in new_body.lower():
+ module.fail_json(msg="Invalid VIP. Possible duplicate IP address.", vip=vip)
+
+
+def main():
+ argument_spec = dict(
+ group=dict(required=True, type="str"),
+ interface=dict(required=True),
+ version=dict(choices=["1", "2"], default="1", required=False),
+ priority=dict(type="str", required=False),
+ preempt=dict(type="str", choices=["disabled", "enabled"], required=False),
+ vip=dict(type="str", required=False),
+ auth_type=dict(choices=["text", "md5"], required=False),
+ auth_string=dict(type="str", required=False),
+ state=dict(choices=["absent", "present"], required=False, default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = dict(changed=False, warnings=warnings)
+
+ interface = module.params["interface"].lower()
+ group = module.params["group"]
+ version = module.params["version"]
+ state = module.params["state"]
+ priority = module.params["priority"]
+ preempt = module.params["preempt"]
+ vip = module.params["vip"]
+ auth_type = module.params["auth_type"]
+ auth_full_string = module.params["auth_string"]
+ auth_enc = "0"
+ auth_string = None
+ if auth_full_string:
+ kstr = auth_full_string.split()
+ if len(kstr) == 2:
+ auth_enc = kstr[0]
+ auth_string = kstr[1]
+ elif len(kstr) == 1:
+ auth_string = kstr[0]
+ else:
+ module.fail_json(msg="Invalid auth_string")
+ if auth_enc != "0" and auth_enc != "7":
+ module.fail_json(msg="Invalid auth_string, only 0 or 7 allowed")
+
+ device_info = get_capabilities(module)
+ network_api = device_info.get("network_api", "nxapi")
+
+ intf_type = get_interface_type(interface)
+ if intf_type != "ethernet" and network_api == "cliconf":
+ if is_default(interface, module) == "DNE":
+ module.fail_json(
+ msg="That interface does not exist yet. Create " "it first.",
+ interface=interface,
+ )
+ if intf_type == "loopback":
+ module.fail_json(
+ msg="Loopback interfaces don't support HSRP.",
+ interface=interface,
+ )
+
+ mode = get_interface_mode(interface, intf_type, module)
+ if mode == "layer2":
+ module.fail_json(
+ msg="That interface is a layer2 port.\nMake it " "a layer 3 port first.",
+ interface=interface,
+ )
+
+ if auth_type or auth_string:
+ if not (auth_type and auth_string):
+ module.fail_json(
+ msg="When using auth parameters, you need BOTH " "auth_type AND auth_string.",
+ )
+
+ args = dict(
+ group=group,
+ version=version,
+ priority=priority,
+ preempt=preempt,
+ vip=vip,
+ auth_type=auth_type,
+ auth_string=auth_string,
+ auth_enc=auth_enc,
+ )
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ existing = get_hsrp_group(group, interface, module)
+
+ # This will enforce better practice with md5 and hsrp version.
+ if proposed.get("auth_type", None) == "md5":
+ if proposed["version"] == "1":
+ module.fail_json(msg="It's recommended to use HSRP v2 " "when auth_type=md5")
+
+ elif not proposed.get("auth_type", None) and existing:
+ if (proposed["version"] == "1" and existing["auth_type"] == "md5") and state == "present":
+ module.fail_json(
+ msg="Existing auth_type is md5. It's recommended " "to use HSRP v2 when using md5",
+ )
+
+ commands = []
+ if state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = get_commands_config_hsrp(delta, interface, args, existing)
+ commands.extend(command)
+
+ elif state == "absent":
+ if existing:
+ command = get_commands_remove_hsrp(group, interface)
+ commands.extend(command)
+
+ if commands:
+ if module.check_mode:
+ module.exit_json(**results)
+ else:
+ load_config(module, commands)
+
+ # validate IP
+ if network_api == "cliconf" and state == "present":
+ commands.insert(0, "config t")
+ body = run_commands(module, commands)
+ validate_config(body, vip, module)
+
+ results["changed"] = True
+
+ if "configure" in commands:
+ commands.pop(0)
+
+ results["commands"] = commands
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp_interfaces.py
new file mode 100644
index 00000000..e5ac6737
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_hsrp_interfaces.py
@@ -0,0 +1,264 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_hsrp_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_hsrp_interfaces
+short_description: HSRP interfaces resource module
+description: Manages Hot Standby Router Protocol (HSRP) interface attributes.
+version_added: 1.0.0
+author: Chris Van Heuveln (@chrisvanheuveln)
+notes:
+- Tested against NX-OS 7.0(3)I5(1).
+- Feature bfd should be enabled for this module.
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^interface').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: The provided configuration
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ type: str
+ description: The name of the interface.
+ bfd:
+ type: str
+ description:
+ - Enable/Disable HSRP Bidirectional Forwarding Detection (BFD) on the interface.
+ choices:
+ - enable
+ - disable
+ state:
+ description:
+ - The state the configuration should be left in
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using deleted
+
+- name: Configure hsrp attributes on interfaces
+ cisco.nxos.nxos_hsrp_interfaces:
+ config:
+ - name: Ethernet1/1
+ - name: Ethernet1/2
+ operation: deleted
+
+
+# Using merged
+
+- name: Configure hsrp attributes on interfaces
+ cisco.nxos.nxos_hsrp_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ - name: Ethernet1/2
+ bfd: disable
+ operation: merged
+
+
+# Using overridden
+
+- name: Configure hsrp attributes on interfaces
+ cisco.nxos.nxos_hsrp_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ - name: Ethernet1/2
+ bfd: disable
+ operation: overridden
+
+
+# Using replaced
+
+- name: Configure hsrp attributes on interfaces
+ cisco.nxos.nxos_hsrp_interfaces:
+ config:
+ - name: Ethernet1/1
+ bfd: enable
+ - name: Ethernet1/2
+ bfd: disable
+ operation: replaced
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_hsrp_interfaces:
+ config:
+ - name: Ethernet1/800
+ bfd: enable
+ - name: Ethernet1/801
+ bfd: enable
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/800"
+# - "hsrp bfd"
+# - "interface Ethernet1/801"
+# - "hsrp bfd"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# interface Ethernet1/800
+# no switchport
+# hsrp bfd
+# interface Ethernet1/801
+# no switchport
+# hsrp bfd
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_hsrp_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - name: Ethernet1/800
+# bfd: enable
+# - name: Ethernet1/801
+# bfd: enable
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+
+# interface Ethernet1/1
+# no switchport
+# hsrp bfd
+# interface Ethernet1/2
+# no switchport
+# hsrp bfd
+# interface Ethernet1/3
+# no switchport
+
+- name: Gather hsrp_interfaces facts from the device using nxos_hsrp_interfaces
+ cisco.nxos.nxos_hsrp_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+
+# gathered:
+# - name: Ethernet1/1
+# bfd: enable
+# - name: Ethernet1/2
+# bfd: enable
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet1/1', 'hsrp bfd']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.hsrp_interfaces.hsrp_interfaces import (
+ Hsrp_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.hsrp_interfaces.hsrp_interfaces import (
+ Hsrp_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Hsrp_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Hsrp_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp.py
new file mode 100644
index 00000000..93770616
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp.py
@@ -0,0 +1,162 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_igmp
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages IGMP global configuration.
+description:
+- Manages IGMP global configuration configuration settings.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- When C(state=default), all supported params will be reset to a default state.
+- If restart is set to true with other params set, the restart will happen last, i.e.
+ after the configuration takes place.
+options:
+ flush_routes:
+ description:
+ - Removes routes when the IGMP process is restarted. By default, routes are not
+ flushed.
+ type: bool
+ enforce_rtr_alert:
+ description:
+ - Enables or disables the enforce router alert option check for IGMPv2 and IGMPv3
+ packets.
+ type: bool
+ restart:
+ description:
+ - Restarts the igmp process (using an exec config command).
+ type: bool
+ default: False
+ state:
+ description:
+ - Manages desired state of the resource.
+ default: present
+ choices:
+ - present
+ - default
+ type: str
+"""
+EXAMPLES = """
+- name: Default igmp global params (all params except restart)
+ cisco.nxos.nxos_igmp:
+ state: default
+
+- name: Ensure the following igmp global config exists on the device
+ cisco.nxos.nxos_igmp:
+ flush_routes: true
+ enforce_rtr_alert: true
+
+- name: Restart the igmp process
+ cisco.nxos.nxos_igmp:
+ restart: true
+"""
+
+RETURN = """
+updates:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["ip igmp flush-routes"]
+"""
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def get_current(module):
+ output = run_commands(module, {"command": "show running-config", "output": "text"})
+ return {
+ "flush_routes": "ip igmp flush-routes" in output[0],
+ "enforce_rtr_alert": "ip igmp enforce-router-alert" in output[0],
+ }
+
+
+def get_desired(module):
+ return {
+ "flush_routes": module.params["flush_routes"],
+ "enforce_rtr_alert": module.params["enforce_rtr_alert"],
+ }
+
+
+def main():
+ argument_spec = dict(
+ flush_routes=dict(type="bool"),
+ enforce_rtr_alert=dict(type="bool"),
+ restart=dict(type="bool", default=False),
+ state=dict(choices=["present", "default"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ current = get_current(module)
+ desired = get_desired(module)
+
+ state = module.params["state"]
+
+ commands = list()
+
+ if state == "default":
+ if current["flush_routes"]:
+ commands.append("no ip igmp flush-routes")
+ if current["enforce_rtr_alert"]:
+ commands.append("no ip igmp enforce-router-alert")
+
+ elif state == "present":
+ ldict = {
+ "flush_routes": "flush-routes",
+ "enforce_rtr_alert": "enforce-router-alert",
+ }
+ for arg in ["flush_routes", "enforce_rtr_alert"]:
+ if desired[arg] and not current[arg]:
+ commands.append("ip igmp {0}".format(ldict.get(arg)))
+ elif current[arg] and not desired[arg]:
+ commands.append("no ip igmp {0}".format(ldict.get(arg)))
+
+ result = {"changed": False, "updates": commands, "warnings": warnings}
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ if module.params["restart"]:
+ cmd = {"command": "restart igmp", "output": "text"}
+ run_commands(module, cmd)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_interface.py
new file mode 100644
index 00000000..105dac5e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_interface.py
@@ -0,0 +1,650 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_igmp_interface
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages IGMP interface configuration.
+description:
+- Manages IGMP interface configuration settings.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- When C(state=default), supported params will be reset to a default state. These
+ include C(version), C(startup_query_interval), C(startup_query_count), C(robustness),
+ C(querier_timeout), C(query_mrt), C(query_interval), C(last_member_qrt), C(last_member_query_count),
+ C(group_timeout), C(report_llg), and C(immediate_leave).
+- When C(state=absent), all configs for C(oif_ps), and C(oif_routemap) will be removed.
+- PIM must be enabled to use this module.
+- This module is for Layer 3 interfaces.
+- Route-map check not performed (same as CLI) check when configuring route-map with
+ 'static-oif'
+- If restart is set to true with other params set, the restart will happen last, i.e.
+ after the configuration takes place. However, 'restart' itself is not idempotent
+ as it is an action and not configuration.
+options:
+ interface:
+ description:
+ - The full interface name for IGMP configuration. e.g. I(Ethernet1/2).
+ required: true
+ type: str
+ version:
+ description:
+ - IGMP version. It can be 2 or 3 or keyword 'default'.
+ choices:
+ - '2'
+ - '3'
+ - default
+ type: str
+ startup_query_interval:
+ description:
+ - Query interval used when the IGMP process starts up. The range is from 1 to
+ 18000 or keyword 'default'. The default is 31.
+ type: str
+ startup_query_count:
+ description:
+ - Query count used when the IGMP process starts up. The range is from 1 to 10
+ or keyword 'default'. The default is 2.
+ type: str
+ robustness:
+ description:
+ - Sets the robustness variable. Values can range from 1 to 7 or keyword 'default'.
+ The default is 2.
+ type: str
+ querier_timeout:
+ description:
+ - Sets the querier timeout that the software uses when deciding to take over as
+ the querier. Values can range from 1 to 65535 seconds or keyword 'default'.
+ The default is 255 seconds.
+ type: str
+ query_mrt:
+ description:
+ - Sets the response time advertised in IGMP queries. Values can range from 1 to
+ 25 seconds or keyword 'default'. The default is 10 seconds.
+ type: str
+ query_interval:
+ description:
+ - Sets the frequency at which the software sends IGMP host query messages. Values
+ can range from 1 to 18000 seconds or keyword 'default'. The default is 125 seconds.
+ type: str
+ last_member_qrt:
+ description:
+ - Sets the query interval waited after sending membership reports before the software
+ deletes the group state. Values can range from 1 to 25 seconds or keyword 'default'.
+ The default is 1 second.
+ type: str
+ last_member_query_count:
+ description:
+ - Sets the number of times that the software sends an IGMP query in response to
+ a host leave message. Values can range from 1 to 5 or keyword 'default'. The
+ default is 2.
+ type: str
+ group_timeout:
+ description:
+ - Sets the group membership timeout for IGMPv2. Values can range from 3 to 65,535
+ seconds or keyword 'default'. The default is 260 seconds.
+ type: str
+ report_llg:
+ description:
+ - Configures report-link-local-groups. Enables sending reports for groups in 224.0.0.0/24.
+ Reports are always sent for nonlink local groups. By default, reports are not
+ sent for link local groups.
+ type: bool
+ immediate_leave:
+ description:
+ - Enables the device to remove the group entry from the multicast routing table
+ immediately upon receiving a leave message for the group. Use this command to
+ minimize the leave latency of IGMPv2 group memberships on a given IGMP interface
+ because the device does not send group-specific queries. The default is disabled.
+ type: bool
+ oif_routemap:
+ description:
+ - Configure a routemap for static outgoing interface (OIF) or keyword 'default'.
+ type: str
+ oif_ps:
+ description:
+ - Configure prefixes and sources for static outgoing interface (OIF). This is
+ a list of dict where each dict has source and prefix defined or just prefix
+ if source is not needed. The specified values will be configured on the device
+ and if any previous prefix/sources exist, they will be removed. Keyword 'default'
+ is also accepted which removes all existing prefix/sources.
+ type: raw
+ restart:
+ description:
+ - Restart IGMP. This is NOT idempotent as this is action only.
+ type: bool
+ default: false
+ state:
+ description:
+ - Manages desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ - default
+ type: str
+"""
+EXAMPLES = """
+- cisco.nxos.nxos_igmp_interface:
+ interface: ethernet1/32
+ startup_query_interval: 30
+ oif_ps:
+ - {prefix: 238.2.2.6}
+ - {source: 192.168.0.1, prefix: 238.2.2.5}
+ state: present
+"""
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"startup_query_count": "30",
+ "oif_ps": [{'prefix': '238.2.2.6'}, {'source': '192.168.0.1', 'prefix': '238.2.2.5'}]}
+existing:
+ description: k/v pairs of existing igmp_interface configuration
+ returned: always
+ type: dict
+ sample: {"startup_query_count": "2", "oif_ps": []}
+end_state:
+ description: k/v pairs of igmp interface configuration after module execution
+ returned: always
+ type: dict
+ sample: {"startup_query_count": "30",
+ "oif_ps": [{'prefix': '238.2.2.6'}, {'source': '192.168.0.1', 'prefix': '238.2.2.5'}]}
+updates:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface Ethernet1/32", "ip igmp startup-query-count 30",
+ "ip igmp static-oif 238.2.2.6", "ip igmp static-oif 238.2.2.5 source 192.168.0.1"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_interface_type,
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ if command_type == "cli_show_ascii":
+ cmds = [{"command": command, "output": "text"}]
+ else:
+ cmds = [{"command": command, "output": "json"}]
+
+ return run_commands(module, cmds)
+
+
+def get_interface_mode(interface, intf_type, module):
+ command = "show interface {0}".format(interface)
+ interface = {}
+ mode = "unknown"
+
+ if intf_type in ["ethernet", "portchannel"]:
+ body = execute_show_command(command, module)[0]
+ interface_table = body["TABLE_interface"]["ROW_interface"]
+ mode = str(interface_table.get("eth_mode", "layer3"))
+ if mode == "access" or mode == "trunk":
+ mode = "layer2"
+ elif intf_type == "loopback" or intf_type == "svi":
+ mode = "layer3"
+ return mode
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = value
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_igmp_interface(module, interface):
+ command = "show ip igmp interface {0}".format(interface)
+ igmp = {}
+
+ key_map = {
+ "IGMPVersion": "version",
+ "ConfiguredStartupQueryInterval": "startup_query_interval",
+ "StartupQueryCount": "startup_query_count",
+ "RobustnessVariable": "robustness",
+ "ConfiguredQuerierTimeout": "querier_timeout",
+ "ConfiguredMaxResponseTime": "query_mrt",
+ "ConfiguredQueryInterval": "query_interval",
+ "LastMemberMTR": "last_member_qrt",
+ "LastMemberQueryCount": "last_member_query_count",
+ "ConfiguredGroupTimeout": "group_timeout",
+ }
+
+ body = execute_show_command(command, module)[0]
+
+ if body:
+ if "not running" in body:
+ return igmp
+ resource = body["TABLE_vrf"]["ROW_vrf"]["TABLE_if"]["ROW_if"]
+ igmp = apply_key_map(key_map, resource)
+ report_llg = str(resource["ReportingForLinkLocal"]).lower()
+ if report_llg == "true":
+ igmp["report_llg"] = True
+ elif report_llg == "false":
+ igmp["report_llg"] = False
+
+ immediate_leave = str(resource["ImmediateLeave"]).lower() # returns en or dis
+ if re.search(r"^en|^true|^enabled", immediate_leave):
+ igmp["immediate_leave"] = True
+ elif re.search(r"^dis|^false|^disabled", immediate_leave):
+ igmp["immediate_leave"] = False
+
+ # the next block of code is used to retrieve anything with:
+ # ip igmp static-oif *** i.e.. could be route-map ROUTEMAP
+ # or PREFIX source <ip>, etc.
+ command = "show run interface {0} | inc oif".format(interface)
+
+ body = execute_show_command(command, module, command_type="cli_show_ascii")[0]
+
+ staticoif = []
+ if body:
+ split_body = body.split("\n")
+ route_map_regex = r".*ip igmp static-oif route-map\s+(?P<route_map>\S+).*"
+ prefix_source_regex = (
+ r".*ip igmp static-oif\s+(?P<prefix>"
+ r"((\d+.){3}\d+))(\ssource\s"
+ r"(?P<source>\S+))?.*"
+ )
+
+ for line in split_body:
+ temp = {}
+ try:
+ match_route_map = re.match(route_map_regex, line, re.DOTALL)
+ route_map = match_route_map.groupdict()["route_map"]
+ except AttributeError:
+ route_map = ""
+
+ try:
+ match_prefix_source = re.match(prefix_source_regex, line, re.DOTALL)
+ prefix_source_group = match_prefix_source.groupdict()
+ prefix = prefix_source_group["prefix"]
+ source = prefix_source_group["source"]
+ except AttributeError:
+ prefix = ""
+ source = ""
+
+ if route_map:
+ temp["route_map"] = route_map
+ if prefix:
+ temp["prefix"] = prefix
+ if source:
+ temp["source"] = source
+ if temp:
+ staticoif.append(temp)
+
+ igmp["oif_routemap"] = None
+ igmp["oif_prefix_source"] = []
+
+ if staticoif:
+ if len(staticoif) == 1 and staticoif[0].get("route_map"):
+ igmp["oif_routemap"] = staticoif[0]["route_map"]
+ else:
+ igmp["oif_prefix_source"] = staticoif
+
+ return igmp
+
+
+def config_igmp_interface(delta, existing, existing_oif_prefix_source):
+ CMDS = {
+ "version": "ip igmp version {0}",
+ "startup_query_interval": "ip igmp startup-query-interval {0}",
+ "startup_query_count": "ip igmp startup-query-count {0}",
+ "robustness": "ip igmp robustness-variable {0}",
+ "querier_timeout": "ip igmp querier-timeout {0}",
+ "query_mrt": "ip igmp query-max-response-time {0}",
+ "query_interval": "ip igmp query-interval {0}",
+ "last_member_qrt": "ip igmp last-member-query-response-time {0}",
+ "last_member_query_count": "ip igmp last-member-query-count {0}",
+ "group_timeout": "ip igmp group-timeout {0}",
+ "report_llg": "ip igmp report-link-local-groups",
+ "immediate_leave": "ip igmp immediate-leave",
+ "oif_prefix_source": "ip igmp static-oif {0} source {1} ",
+ "oif_routemap": "ip igmp static-oif route-map {0}",
+ "oif_prefix": "ip igmp static-oif {0}",
+ }
+
+ commands = []
+ command = None
+ def_vals = get_igmp_interface_defaults()
+
+ for key, value in delta.items():
+ if key == "oif_ps" and value != "default":
+ for each in value:
+ if each in existing_oif_prefix_source:
+ existing_oif_prefix_source.remove(each)
+ else:
+ # add new prefix/sources
+ pf = each["prefix"]
+ src = ""
+ if "source" in each.keys():
+ src = each["source"]
+ if src:
+ commands.append(CMDS.get("oif_prefix_source").format(pf, src))
+ else:
+ commands.append(CMDS.get("oif_prefix").format(pf))
+ if existing_oif_prefix_source:
+ for each in existing_oif_prefix_source:
+ # remove stale prefix/sources
+ pf = each["prefix"]
+ src = ""
+ if "source" in each.keys():
+ src = each["source"]
+ if src:
+ commands.append("no " + CMDS.get("oif_prefix_source").format(pf, src))
+ else:
+ commands.append("no " + CMDS.get("oif_prefix").format(pf))
+ elif key == "oif_routemap":
+ if value == "default":
+ if existing.get(key):
+ command = "no " + CMDS.get(key).format(existing.get(key))
+ else:
+ command = CMDS.get(key).format(value)
+ elif value:
+ if value == "default":
+ if def_vals.get(key) != existing.get(key):
+ command = CMDS.get(key).format(def_vals.get(key))
+ else:
+ command = CMDS.get(key).format(value)
+ elif not value:
+ command = "no {0}".format(CMDS.get(key).format(value))
+
+ if command:
+ if command not in commands:
+ commands.append(command)
+ command = None
+
+ return commands
+
+
+def get_igmp_interface_defaults():
+ version = "2"
+ startup_query_interval = "31"
+ startup_query_count = "2"
+ robustness = "2"
+ querier_timeout = "255"
+ query_mrt = "10"
+ query_interval = "125"
+ last_member_qrt = "1"
+ last_member_query_count = "2"
+ group_timeout = "260"
+ report_llg = False
+ immediate_leave = False
+
+ args = dict(
+ version=version,
+ startup_query_interval=startup_query_interval,
+ startup_query_count=startup_query_count,
+ robustness=robustness,
+ querier_timeout=querier_timeout,
+ query_mrt=query_mrt,
+ query_interval=query_interval,
+ last_member_qrt=last_member_qrt,
+ last_member_query_count=last_member_query_count,
+ group_timeout=group_timeout,
+ report_llg=report_llg,
+ immediate_leave=immediate_leave,
+ )
+
+ default = dict((param, value) for (param, value) in args.items() if value is not None)
+
+ return default
+
+
+def config_default_igmp_interface(existing, delta):
+ commands = []
+ proposed = get_igmp_interface_defaults()
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = config_igmp_interface(delta, existing, existing_oif_prefix_source=None)
+
+ if command:
+ for each in command:
+ commands.append(each)
+
+ return commands
+
+
+def config_remove_oif(existing, existing_oif_prefix_source):
+ commands = []
+ command = None
+ if existing.get("oif_routemap"):
+ commands.append("no ip igmp static-oif route-map {0}".format(existing.get("oif_routemap")))
+ elif existing_oif_prefix_source:
+ for each in existing_oif_prefix_source:
+ if each.get("prefix") and each.get("source"):
+ command = "no ip igmp static-oif {0} source {1} ".format(
+ each.get("prefix"),
+ each.get("source"),
+ )
+ elif each.get("prefix"):
+ command = "no ip igmp static-oif {0}".format(each.get("prefix"))
+ if command:
+ commands.append(command)
+ command = None
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ interface=dict(required=True, type="str"),
+ version=dict(required=False, type="str", choices=["2", "3", "default"]),
+ startup_query_interval=dict(required=False, type="str"),
+ startup_query_count=dict(required=False, type="str"),
+ robustness=dict(required=False, type="str"),
+ querier_timeout=dict(required=False, type="str"),
+ query_mrt=dict(required=False, type="str"),
+ query_interval=dict(required=False, type="str"),
+ last_member_qrt=dict(required=False, type="str"),
+ last_member_query_count=dict(required=False, type="str"),
+ group_timeout=dict(required=False, type="str"),
+ report_llg=dict(type="bool"),
+ immediate_leave=dict(type="bool"),
+ oif_routemap=dict(required=False, type="str"),
+ oif_ps=dict(required=False, type="raw"),
+ restart=dict(type="bool", default=False),
+ state=dict(choices=["present", "absent", "default"], default="present"),
+ )
+
+ mutually_exclusive = [("oif_ps", "oif_routemap")]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ state = module.params["state"]
+ interface = module.params["interface"]
+ oif_routemap = module.params["oif_routemap"]
+ oif_ps = module.params["oif_ps"]
+
+ intf_type = get_interface_type(interface)
+ if get_interface_mode(interface, intf_type, module) == "layer2":
+ module.fail_json(msg="this module only works on Layer 3 interfaces")
+
+ existing = get_igmp_interface(module, interface)
+ existing_copy = existing.copy()
+ end_state = existing_copy
+
+ if not existing.get("version"):
+ module.fail_json(msg="pim needs to be enabled on the interface")
+
+ existing_oif_prefix_source = existing.get("oif_prefix_source")
+ # not json serializable
+ existing.pop("oif_prefix_source")
+
+ if oif_routemap and existing_oif_prefix_source:
+ module.fail_json(
+ msg="Delete static-oif configurations on this "
+ "interface if you want to use a routemap",
+ )
+
+ if oif_ps and existing.get("oif_routemap"):
+ module.fail_json(
+ msg="Delete static-oif route-map configuration "
+ "on this interface if you want to config "
+ "static entries",
+ )
+
+ args = [
+ "version",
+ "startup_query_interval",
+ "startup_query_count",
+ "robustness",
+ "querier_timeout",
+ "query_mrt",
+ "query_interval",
+ "last_member_qrt",
+ "last_member_query_count",
+ "group_timeout",
+ "report_llg",
+ "immediate_leave",
+ "oif_routemap",
+ ]
+
+ changed = False
+ commands = []
+ proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ CANNOT_ABSENT = [
+ "version",
+ "startup_query_interval",
+ "startup_query_count",
+ "robustness",
+ "querier_timeout",
+ "query_mrt",
+ "query_interval",
+ "last_member_qrt",
+ "last_member_query_count",
+ "group_timeout",
+ "report_llg",
+ "immediate_leave",
+ ]
+
+ if state == "absent":
+ for each in CANNOT_ABSENT:
+ if each in proposed:
+ module.fail_json(
+ msg="only params: " "oif_ps, oif_routemap can be used when " "state=absent",
+ )
+
+ # delta check for all params except oif_ps
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ if oif_ps:
+ if oif_ps == "default":
+ delta["oif_ps"] = []
+ else:
+ delta["oif_ps"] = oif_ps
+
+ if state == "present":
+ if delta:
+ command = config_igmp_interface(delta, existing, existing_oif_prefix_source)
+ if command:
+ commands.append(command)
+
+ elif state == "default":
+ command = config_default_igmp_interface(existing, delta)
+ if command:
+ commands.append(command)
+ elif state == "absent":
+ command = None
+ if existing.get("oif_routemap") or existing_oif_prefix_source:
+ command = config_remove_oif(existing, existing_oif_prefix_source)
+
+ if command:
+ commands.append(command)
+
+ command = config_default_igmp_interface(existing, delta)
+ if command:
+ commands.append(command)
+
+ cmds = []
+ results = {}
+ if commands:
+ commands.insert(0, ["interface {0}".format(interface)])
+ cmds = flatten_list(commands)
+
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ load_config(module, cmds)
+ changed = True
+ end_state = get_igmp_interface(module, interface)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ if module.params["restart"]:
+ cmd = {"command": "restart igmp", "output": "text"}
+ run_commands(module, cmd)
+
+ results["proposed"] = proposed
+ results["existing"] = existing_copy
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+ results["end_state"] = end_state
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_snooping.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_snooping.py
new file mode 100644
index 00000000..6d14ba17
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_igmp_snooping.py
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_igmp_snooping
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages IGMP snooping global configuration.
+description:
+- Manages IGMP snooping global configuration.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- When C(state=default), params will be reset to a default state.
+- C(group_timeout) also accepts I(never) as an input.
+options:
+ snooping:
+ description:
+ - Enables/disables IGMP snooping on the switch.
+ type: bool
+ group_timeout:
+ description:
+ - Group membership timeout value for all VLANs on the device. Accepted values
+ are integer in range 1-10080, I(never) and I(default).
+ type: str
+ link_local_grp_supp:
+ description:
+ - Global link-local groups suppression.
+ type: bool
+ report_supp:
+ description:
+ - Global IGMPv1/IGMPv2 Report Suppression.
+ type: bool
+ v3_report_supp:
+ description:
+ - Global IGMPv3 Report Suppression and Proxy Reporting.
+ type: bool
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - default
+ type: str
+"""
+
+EXAMPLES = """
+# ensure igmp snooping params supported in this module are in there default state
+- cisco.nxos.nxos_igmp_snooping:
+ state: default
+
+# ensure following igmp snooping params are in the desired state
+- cisco.nxos.nxos_igmp_snooping:
+ group_timeout: never
+ snooping: true
+ link_local_grp_supp: false
+ optimize_mcast_flood: false
+ report_supp: true
+ v3_report_supp: true
+"""
+
+RETURN = """
+commands:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["ip igmp snooping link-local-groups-suppression",
+ "ip igmp snooping group-timeout 50",
+ "no ip igmp snooping report-suppression",
+ "no ip igmp snooping v3-report-suppression",
+ "no ip igmp snooping"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, output="text"):
+ command = {"command": command, "output": output}
+
+ return run_commands(module, [command])
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_group_timeout(config):
+ match = re.search(r" Group timeout configured: (\S+)", config, re.M)
+ if match:
+ value = match.group(1)
+ else:
+ value = ""
+ return value
+
+
+def get_igmp_snooping(module):
+ command = "show ip igmp snooping"
+ existing = {}
+
+ try:
+ body = execute_show_command(command, module, output="json")[0]
+ except IndexError:
+ body = []
+
+ if body:
+ snooping = str(body.get("enabled")).lower()
+ if "none" in snooping:
+ snooping = str(body.get("GlobalSnoopEnabled")).lower()
+ if snooping == "true" or snooping == "enabled" or snooping == "yes":
+ existing["snooping"] = True
+ else:
+ existing["snooping"] = False
+
+ report_supp = str(body.get("grepsup")).lower()
+ if "none" in report_supp:
+ report_supp = str(body.get("GlobalReportSupression")).lower()
+ if report_supp == "true" or report_supp == "enabled":
+ existing["report_supp"] = True
+ else:
+ existing["report_supp"] = False
+
+ link_local_grp_supp = str(body.get("glinklocalgrpsup")).lower()
+ if "none" in link_local_grp_supp:
+ link_local_grp_supp = str(body.get("GlobalLinkLocalGroupSupression")).lower()
+ if link_local_grp_supp == "true" or link_local_grp_supp == "enabled":
+ existing["link_local_grp_supp"] = True
+ else:
+ existing["link_local_grp_supp"] = False
+
+ v3_report_supp = str(body.get("gv3repsup")).lower()
+ if "none" in v3_report_supp:
+ v3_report_supp = str(body.get("GlobalV3ReportSupression")).lower()
+ if v3_report_supp == "true" or v3_report_supp == "enabled":
+ existing["v3_report_supp"] = True
+ else:
+ existing["v3_report_supp"] = False
+
+ command = "show ip igmp snooping"
+ body = execute_show_command(command, module)[0]
+ if body:
+ existing["group_timeout"] = get_group_timeout(body)
+
+ return existing
+
+
+def config_igmp_snooping(delta, existing, default=False):
+ CMDS = {
+ "snooping": "ip igmp snooping",
+ "group_timeout": "ip igmp snooping group-timeout {}",
+ "link_local_grp_supp": "ip igmp snooping link-local-groups-suppression",
+ "v3_report_supp": "ip igmp snooping v3-report-suppression",
+ "report_supp": "ip igmp snooping report-suppression",
+ }
+
+ commands = []
+ command = None
+ gt_command = None
+ for key, value in delta.items():
+ if value:
+ if default and key == "group_timeout":
+ if existing.get(key):
+ gt_command = "no " + CMDS.get(key).format(existing.get(key))
+ elif value == "default" and key == "group_timeout":
+ if existing.get(key):
+ command = "no " + CMDS.get(key).format(existing.get(key))
+ else:
+ command = CMDS.get(key).format(value)
+ else:
+ command = "no " + CMDS.get(key).format(value)
+
+ if command:
+ commands.append(command)
+ command = None
+
+ if gt_command:
+ # ensure that group-timeout command is configured last
+ commands.append(gt_command)
+ return commands
+
+
+def get_igmp_snooping_defaults():
+ group_timeout = "dummy"
+ report_supp = True
+ link_local_grp_supp = True
+ v3_report_supp = False
+ snooping = True
+
+ args = dict(
+ snooping=snooping,
+ link_local_grp_supp=link_local_grp_supp,
+ report_supp=report_supp,
+ v3_report_supp=v3_report_supp,
+ group_timeout=group_timeout,
+ )
+
+ default = dict((param, value) for (param, value) in args.items() if value is not None)
+
+ return default
+
+
+def igmp_snooping_gt_dependency(command, existing, module):
+ # group-timeout will fail if igmp snooping is disabled
+ gt = [i for i in command if i.startswith("ip igmp snooping group-timeout")]
+ if gt:
+ if "no ip igmp snooping" in command or (
+ existing["snooping"] is False and "ip igmp snooping" not in command
+ ):
+ msg = "group-timeout cannot be enabled or changed when ip igmp snooping is disabled"
+ module.fail_json(msg=msg)
+ else:
+ # ensure that group-timeout command is configured last
+ command.remove(gt[0])
+ command.append(gt[0])
+
+
+def main():
+ argument_spec = dict(
+ snooping=dict(required=False, type="bool"),
+ group_timeout=dict(required=False, type="str"),
+ link_local_grp_supp=dict(required=False, type="bool"),
+ report_supp=dict(required=False, type="bool"),
+ v3_report_supp=dict(required=False, type="bool"),
+ state=dict(choices=["present", "default"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ snooping = module.params["snooping"]
+ link_local_grp_supp = module.params["link_local_grp_supp"]
+ report_supp = module.params["report_supp"]
+ v3_report_supp = module.params["v3_report_supp"]
+ group_timeout = module.params["group_timeout"]
+ state = module.params["state"]
+
+ args = dict(
+ snooping=snooping,
+ link_local_grp_supp=link_local_grp_supp,
+ report_supp=report_supp,
+ v3_report_supp=v3_report_supp,
+ group_timeout=group_timeout,
+ )
+
+ proposed = dict((param, value) for (param, value) in args.items() if value is not None)
+
+ existing = get_igmp_snooping(module)
+
+ commands = []
+ if state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = config_igmp_snooping(delta, existing)
+ if command:
+ if group_timeout:
+ igmp_snooping_gt_dependency(command, existing, module)
+ commands.append(command)
+ elif state == "default":
+ proposed = get_igmp_snooping_defaults()
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = config_igmp_snooping(delta, existing, default=True)
+ if command:
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_install_os.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_install_os.py
new file mode 100644
index 00000000..2327ab54
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_install_os.py
@@ -0,0 +1,594 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_install_os
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Set boot options like boot, kickstart image and issu.
+description:
+- Install an operating system by setting the boot options like boot image and kickstart
+ image and optionally select to install using ISSU (In Server Software Upgrade).
+version_added: 1.0.0
+notes:
+- Tested against the following platforms and images - N9k 7.0(3)I4(6), 7.0(3)I5(3),
+ 7.0(3)I6(1), 7.0(3)I7(1), 7.0(3)F2(2), 7.0(3)F3(2) - N3k 6.0(2)A8(6), 6.0(2)A8(8),
+ 7.0(3)I6(1), 7.0(3)I7(1) - N7k 7.3(0)D1(1), 8.0(1), 8.1(1), 8.2(1)
+- Tested against Cisco MDS NX-OS 9.2(1)
+- This module requires both the ANSIBLE_PERSISTENT_CONNECT_TIMEOUT and ANSIBLE_PERSISTENT_COMMAND_TIMEOUT
+ timers to be set to 600 seconds or higher. The module will exit if the timers are
+ not set properly.
+- When using connection local, ANSIBLE_PERSISTENT_CONNECT_TIMEOUT and ANSIBLE_PERSISTENT_COMMAND_TIMEOUT
+ can only be set using ENV variables or the ansible.cfg file.
+- Do not include full file paths, just the name of the file(s) stored on the top level
+ flash directory.
+- This module attempts to install the software immediately, which may trigger a reboot.
+- In check mode, the module will indicate if an upgrade is needed and whether or not
+ the upgrade is disruptive or non-disruptive(ISSU).
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbibo (@GGabriele)
+options:
+ system_image_file:
+ description:
+ - Name of the system (or combined) image file on flash.
+ required: true
+ type: str
+ kickstart_image_file:
+ description:
+ - Name of the kickstart image file on flash. (Not required on all Nexus platforms)
+ type: str
+ issu:
+ description:
+ - Upgrade using In Service Software Upgrade (ISSU). (Supported on N5k, N7k, N9k
+ platforms)
+ - Selecting 'required' or 'yes' means that upgrades will only proceed if the switch
+ is capable of ISSU.
+ - Selecting 'desired' means that upgrades will use ISSU if possible but will fall
+ back to disruptive upgrade if needed.
+ - Selecting 'no' means do not use ISSU. Forced disruptive.
+ choices:
+ - "required"
+ - "desired"
+ - "yes"
+ - "no"
+ default: "no"
+ type: str
+"""
+
+EXAMPLES = """
+- name: Install OS on N9k
+ check_mode: no
+ cisco.nxos.nxos_install_os:
+ system_image_file: nxos.7.0.3.I6.1.bin
+ issu: desired
+
+- name: Wait for device to come back up with new image
+ wait_for:
+ port: 22
+ state: started
+ timeout: 500
+ delay: 60
+ host: '{{ inventory_hostname }}'
+
+- name: Check installed OS for newly installed version
+ nxos_command:
+ commands: [show version | json]
+ register: output
+- assert:
+ that:
+ - output['stdout'][0]['kickstart_ver_str'] == '7.0(3)I6(1)'
+"""
+
+RETURN = """
+install_state:
+ description: Boot and install information.
+ returned: always
+ type: dict
+ sample: {
+ "install_state": [
+ "Compatibility check is done:",
+ "Module bootable Impact Install-type Reason",
+ "------ -------- -------------- ------------ ------",
+ " 1 yes non-disruptive reset ",
+ "Images will be upgraded according to following table:",
+ "Module Image Running-Version(pri:alt) New-Version Upg-Required",
+ "------ ---------- ---------------------------------------- -------------------- ------------",
+ " 1 nxos 7.0(3)I6(1) 7.0(3)I7(1) yes",
+ " 1 bios v4.4.0(07/12/2017) v4.4.0(07/12/2017) no"
+ ],
+ }
+"""
+
+
+import re
+
+from time import sleep
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+# Output options are 'text' or 'json'
+def execute_show_command(module, command, output="text"):
+ cmds = [{"command": command, "output": output}]
+
+ return run_commands(module, cmds)
+
+
+def get_platform(module):
+ """Determine platform type"""
+ data = execute_show_command(module, "show inventory", "json")
+ pid = data[0]["TABLE_inv"]["ROW_inv"][0]["productid"]
+
+ if re.search(r"N3K", pid):
+ type = "N3K"
+ elif re.search(r"N5K", pid):
+ type = "N5K"
+ elif re.search(r"N6K", pid):
+ type = "N6K"
+ elif re.search(r"N7K", pid):
+ type = "N7K"
+ elif re.search(r"N9K", pid):
+ type = "N9K"
+ else:
+ type = "unknown"
+
+ return type
+
+
+def parse_show_install(data):
+ """Helper method to parse the output of the 'show install all impact' or
+ 'install all' commands.
+
+ Sample Output:
+
+ Installer will perform impact only check. Please wait.
+
+ Verifying image bootflash:/nxos.7.0.3.F2.2.bin for boot variable "nxos".
+ [####################] 100% -- SUCCESS
+
+ Verifying image type.
+ [####################] 100% -- SUCCESS
+
+ Preparing "bios" version info using image bootflash:/nxos.7.0.3.F2.2.bin.
+ [####################] 100% -- SUCCESS
+
+ Preparing "nxos" version info using image bootflash:/nxos.7.0.3.F2.2.bin.
+ [####################] 100% -- SUCCESS
+
+ Performing module support checks.
+ [####################] 100% -- SUCCESS
+
+ Notifying services about system upgrade.
+ [####################] 100% -- SUCCESS
+
+
+
+ Compatibility check is done:
+ Module bootable Impact Install-type Reason
+ ------ -------- -------------- ------------ ------
+ 8 yes disruptive reset Incompatible image for ISSU
+ 21 yes disruptive reset Incompatible image for ISSU
+
+
+ Images will be upgraded according to following table:
+ Module Image Running-Version(pri:alt) New-Version Upg-Required
+ ------ ---------- ---------------------------------------- ------------
+ 8 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
+ 8 bios v01.17 v01.17 no
+ 21 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
+ 21 bios v01.70 v01.70 no
+ """
+ if len(data) > 0:
+ data = massage_install_data(data)
+ ud = {"raw": data}
+ ud["processed"] = []
+ ud["disruptive"] = False
+ ud["upgrade_needed"] = False
+ ud["error"] = False
+ ud["invalid_command"] = False
+ ud["install_in_progress"] = False
+ ud["server_error"] = False
+ ud["upgrade_succeeded"] = False
+ ud["use_impact_data"] = False
+
+ # Check for server errors
+ if isinstance(data, int):
+ if data == -1:
+ ud["server_error"] = True
+ elif data >= 500:
+ ud["server_error"] = True
+ elif data == -32603:
+ ud["server_error"] = True
+ elif data == 1:
+ ud["server_error"] = True
+ return ud
+ else:
+ ud["list_data"] = data.split("\n")
+
+ for x in ud["list_data"]:
+ # Check for errors and exit if found.
+ if re.search(r"Pre-upgrade check failed", x):
+ ud["error"] = True
+ break
+ if re.search(r"[I|i]nvalid command", x):
+ ud["invalid_command"] = True
+ ud["error"] = True
+ break
+ if re.search(r"No install all data found", x):
+ ud["error"] = True
+ break
+
+ # Check for potentially transient conditions
+ if re.search(r"Another install procedure may\s*be in progress", x):
+ ud["install_in_progress"] = True
+ break
+ if re.search(r"Backend processing error", x):
+ ud["server_error"] = True
+ break
+ if re.search(r"timed out", x):
+ ud["server_error"] = True
+ break
+ if re.search(r"^(-1|5\d\d)$", x):
+ ud["server_error"] = True
+ break
+
+ # Check for messages indicating a successful upgrade.
+ if re.search(r"Finishing the upgrade", x):
+ ud["upgrade_succeeded"] = True
+ break
+ if re.search(r"Install has been successful", x):
+ ud["upgrade_succeeded"] = True
+ break
+ if re.search(r"Switching over onto standby", x):
+ ud["upgrade_succeeded"] = True
+ break
+
+ # We get these messages when the upgrade is non-disruptive and
+ # we loose connection with the switchover but far enough along that
+ # we can be confident the upgrade succeeded.
+ if re.search(r"timeout .*trying to send command: install", x):
+ ud["upgrade_succeeded"] = True
+ ud["use_impact_data"] = True
+ break
+ if re.search(r"[C|c]onnection failure: timed out", x):
+ ud["upgrade_succeeded"] = True
+ ud["use_impact_data"] = True
+ break
+
+ # Begin normal parsing.
+ if re.search(r"----|Module|Images will|Compatibility", x):
+ ud["processed"].append(x)
+ continue
+ # Check to see if upgrade will be disruptive or non-disruptive and
+ # build dictionary of individual modules and their status.
+ # Sample Line:
+ #
+ # Module bootable Impact Install-type Reason
+ # ------ -------- ---------- ------------ ------
+ # 8 yes disruptive reset Incompatible image
+ rd = r"(\d+)\s+(\S+)\s+(disruptive|non-disruptive)\s+(\S+)"
+ mo = re.search(rd, x)
+ if mo:
+ ud["processed"].append(x)
+ key = "m%s" % mo.group(1)
+ field = "disruptive"
+ if mo.group(3) == "non-disruptive":
+ ud[key] = {field: False}
+ else:
+ ud[field] = True
+ ud[key] = {field: True}
+ field = "bootable"
+ if mo.group(2) == "yes":
+ ud[key].update({field: True})
+ else:
+ ud[key].update({field: False})
+ continue
+
+ # Check to see if switch needs an upgrade and build a dictionary
+ # of individual modules and their individual upgrade status.
+ # Sample Line:
+ #
+ # Module Image Running-Version(pri:alt) New-Version Upg-Required
+ # ------ ----- ---------------------------------------- ------------
+ # 8 lcn9k 7.0(3)F3(2) 7.0(3)F2(2) yes
+ mo = re.search(r"(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(yes|no)", x)
+ if mo:
+ ud["processed"].append(x)
+ key = "m%s_%s" % (mo.group(1), mo.group(2))
+ field = "upgrade_needed"
+ if mo.group(5) == "yes":
+ ud[field] = True
+ ud[key] = {field: True}
+ else:
+ ud[key] = {field: False}
+ continue
+
+ return ud
+
+
+def massage_install_data(data):
+ # Transport cli returns a list containing one result item.
+ # Transport nxapi returns a list containing two items. The second item
+ # contains the data we are interested in.
+ default_error_msg = "No install all data found"
+ if len(data) == 1:
+ result_data = data[0]
+ elif len(data) == 2:
+ result_data = data[1]
+ else:
+ result_data = default_error_msg
+
+ # Further processing may be needed for result_data
+ if len(data) == 2 and isinstance(data[1], dict):
+ if "clierror" in data[1].keys():
+ result_data = data[1]["clierror"]
+ elif "code" in data[1].keys() and data[1]["code"] == "500":
+ # We encountered a backend processing error for nxapi
+ result_data = data[1]["msg"]
+ else:
+ result_data = default_error_msg
+ return result_data
+
+
+def build_install_cmd_set(issu, image, kick, type, force=True):
+ commands = ["terminal dont-ask"]
+
+ # Different NX-OS platforms behave differently for
+ # disruptive and non-disruptive upgrade paths.
+ #
+ # 1) Combined kickstart/system image:
+ # * Use option 'non-disruptive' for issu.
+ # * Omit option 'non-disruptive' for disruptive upgrades.
+ # 2) Separate kickstart + system images.
+ # * Omit hidden 'force' option for issu.
+ # * Use hidden 'force' option for disruptive upgrades.
+ # * Note: Not supported on all platforms
+ if re.search(r"required|desired|yes", issu):
+ if kick is None:
+ issu_cmd = "non-disruptive"
+ else:
+ issu_cmd = ""
+ else:
+ if kick is None:
+ issu_cmd = ""
+ else:
+ issu_cmd = "force" if force else ""
+
+ if type == "impact":
+ rootcmd = "show install all impact"
+ # The force option is not available for the impact command.
+ if kick:
+ issu_cmd = ""
+ else:
+ rootcmd = "install all"
+ if kick is None:
+ commands.append("%s nxos %s %s" % (rootcmd, image, issu_cmd))
+ else:
+ commands.append("%s %s system %s kickstart %s" % (rootcmd, issu_cmd, image, kick))
+
+ return commands
+
+
+def parse_show_version(data):
+ version_data = {"raw": data[0].split("\n")}
+ version_data["version"] = ""
+ version_data["error"] = False
+ for x in version_data["raw"]:
+ mo = re.search(r"(kickstart|system|NXOS):\s+version\s+(\S+)", x)
+ if mo:
+ version_data["version"] = mo.group(2)
+ continue
+
+ if version_data["version"] == "":
+ version_data["error"] = True
+
+ return version_data
+
+
+def check_mode_legacy(module, issu, image, kick=None):
+ """Some platforms/images/transports don't support the 'install all impact'
+ command so we need to use a different method."""
+ current = execute_show_command(module, "show version", "json")[0]
+ # Call parse_show_data on empty string to create the default upgrade
+ # data structure dictionary
+ data = parse_show_install("")
+ upgrade_msg = "No upgrade required"
+
+ # Process System Image
+ data["error"] = False
+ tsver = "show version image bootflash:%s" % image
+ data["upgrade_cmd"] = [tsver]
+ target_image = parse_show_version(execute_show_command(module, tsver))
+ if target_image["error"]:
+ data["error"] = True
+ data["raw"] = target_image["raw"]
+ if current["kickstart_ver_str"] != target_image["version"] and not data["error"]:
+ data["upgrade_needed"] = True
+ data["disruptive"] = True
+ upgrade_msg = "Switch upgraded: system: %s" % tsver
+
+ # Process Kickstart Image
+ if kick is not None and not data["error"]:
+ tkver = "show version image bootflash:%s" % kick
+ data["upgrade_cmd"].append(tsver)
+ target_kick = parse_show_version(execute_show_command(module, tkver))
+ if target_kick["error"]:
+ data["error"] = True
+ data["raw"] = target_kick["raw"]
+ if current["kickstart_ver_str"] != target_kick["version"] and not data["error"]:
+ data["upgrade_needed"] = True
+ data["disruptive"] = True
+ upgrade_msg = upgrade_msg + " kickstart: %s" % tkver
+
+ data["list_data"] = data["raw"]
+ data["processed"] = upgrade_msg
+ return data
+
+
+def check_mode_nextgen(module, issu, image, kick=None):
+ """Use the 'install all impact' command for check_mode"""
+ opts = {"ignore_timeout": True}
+ commands = build_install_cmd_set(issu, image, kick, "impact")
+ data = parse_show_install(load_config(module, commands, True, opts))
+ # If an error is encountered when issu is 'desired' then try again
+ # but set issu to 'no'
+ if data["error"] and issu == "desired":
+ issu = "no"
+ commands = build_install_cmd_set(issu, image, kick, "impact")
+ # The system may be busy from the previous call to check_mode so loop
+ # until it's done.
+ data = check_install_in_progress(module, commands, opts)
+ if data["server_error"]:
+ data["error"] = True
+ data["upgrade_cmd"] = commands
+ return data
+
+
+def check_install_in_progress(module, commands, opts):
+ for attempt in range(20):
+ data = parse_show_install(load_config(module, commands, True, opts))
+ if data["install_in_progress"]:
+ sleep(1)
+ continue
+ break
+ return data
+
+
+def check_mode(module, issu, image, kick=None):
+ """Check switch upgrade impact using 'show install all impact' command"""
+ data = check_mode_nextgen(module, issu, image, kick)
+ if data["server_error"]:
+ # We encountered an unrecoverable error in the attempt to get upgrade
+ # impact data from the 'show install all impact' command.
+ # Fallback to legacy method.
+ data = check_mode_legacy(module, issu, image, kick)
+ if data["invalid_command"]:
+ # If we are upgrading from a device running a separate kickstart and
+ # system image the impact command will fail.
+ # Fallback to legacy method.
+ data = check_mode_legacy(module, issu, image, kick)
+ return data
+
+
+def do_install_all(module, issu, image, kick=None):
+ """Perform the switch upgrade using the 'install all' command"""
+ impact_data = check_mode(module, issu, image, kick)
+ if module.check_mode:
+ # Check mode set in the playbook so just return the impact data.
+ msg = "*** SWITCH WAS NOT UPGRADED: IMPACT DATA ONLY ***"
+ impact_data["processed"].append(msg)
+ return impact_data
+ if impact_data["error"]:
+ # Check mode discovered an error so return with this info.
+ return impact_data
+ elif not impact_data["upgrade_needed"]:
+ # The switch is already upgraded. Nothing more to do.
+ return impact_data
+ else:
+ # If we get here, check_mode returned no errors and the switch
+ # needs to be upgraded.
+ if impact_data["disruptive"]:
+ # Check mode indicated that ISSU is not possible so issue the
+ # upgrade command without the non-disruptive flag unless the
+ # playbook specified issu: yes/required.
+ if issu == "yes":
+ msg = "ISSU/ISSD requested but impact data indicates ISSU/ISSD is not possible"
+ module.fail_json(msg=msg, raw_data=impact_data["list_data"])
+ else:
+ issu = "no"
+
+ commands = build_install_cmd_set(issu, image, kick, "install")
+ opts = {"ignore_timeout": True}
+ # The system may be busy from the call to check_mode so loop until
+ # it's done.
+ upgrade = check_install_in_progress(module, commands, opts)
+ if upgrade["invalid_command"] and "force" in commands[1]:
+ # Not all platforms support the 'force' keyword. Check for this
+ # condition and re-try without the 'force' keyword if needed.
+ commands = build_install_cmd_set(issu, image, kick, "install", False)
+ upgrade = check_install_in_progress(module, commands, opts)
+ upgrade["upgrade_cmd"] = commands
+
+ # Special case: If we encounter a server error at this stage
+ # it means the command was sent and the upgrade was started but
+ # we will need to use the impact data instead of the current install
+ # data.
+ if upgrade["server_error"]:
+ upgrade["upgrade_succeeded"] = True
+ upgrade["use_impact_data"] = True
+
+ if upgrade["use_impact_data"]:
+ if upgrade["upgrade_succeeded"]:
+ upgrade = impact_data
+ upgrade["upgrade_succeeded"] = True
+ else:
+ upgrade = impact_data
+ upgrade["upgrade_succeeded"] = False
+
+ if not upgrade["upgrade_succeeded"]:
+ upgrade["error"] = True
+ return upgrade
+
+
+def main():
+ argument_spec = dict(
+ system_image_file=dict(required=True),
+ kickstart_image_file=dict(required=False),
+ issu=dict(choices=["required", "desired", "no", "yes"], default="no"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ # Get system_image_file(sif), kickstart_image_file(kif) and
+ # issu settings from module params.
+ sif = module.params["system_image_file"]
+ kif = module.params["kickstart_image_file"]
+ issu = module.params["issu"]
+
+ if re.search(r"(yes|required)", issu):
+ issu = "yes"
+
+ if kif == "null" or kif == "":
+ kif = None
+
+ install_result = do_install_all(module, issu, sif, kick=kif)
+ if install_result["error"]:
+ cmd = install_result["upgrade_cmd"]
+ msg = "Failed to upgrade device using command: %s" % cmd
+ module.fail_json(msg=msg, raw_data=install_result["list_data"])
+
+ state = install_result["processed"]
+ changed = install_result["upgrade_needed"]
+ module.exit_json(changed=changed, install_state=state, warnings=warnings)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_interfaces.py
new file mode 100644
index 00000000..f03f746b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_interfaces.py
@@ -0,0 +1,455 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_interfaces
+short_description: Interfaces resource module
+description: This module manages the interface attributes of NX-OS interfaces.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section ^interface)
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of interface options
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of interface, e.g. Ethernet1/1, port-channel10.
+ type: str
+ required: true
+ description:
+ description:
+ - Interface description.
+ type: str
+ enabled:
+ description:
+ - Administrative state of the interface. Set the value to C(true) to administratively
+ enable the interface or C(false) to disable it
+ type: bool
+ speed:
+ description:
+ - Interface link speed. Applicable for Ethernet interfaces only.
+ type: str
+ mode:
+ description:
+ - Manage Layer2 or Layer3 state of the interface. Applicable for Ethernet
+ and port channel interfaces only.
+ choices:
+ - layer2
+ - layer3
+ type: str
+ mtu:
+ description:
+ - MTU for a specific interface. Must be an even number between 576 and 9216.
+ Applicable for Ethernet interfaces only.
+ type: str
+ duplex:
+ description:
+ - Interface link status. Applicable for Ethernet interfaces only.
+ type: str
+ choices:
+ - full
+ - half
+ - auto
+ ip_forward:
+ description:
+ - Enable or disable IP forward feature on SVIs. Set the value to C(true) to
+ enable or C(false) to disable.
+ type: bool
+ fabric_forwarding_anycast_gateway:
+ description:
+ - Associate SVI with anycast gateway under VLAN configuration mode. Applicable
+ for SVI interfaces only.
+ type: bool
+ state:
+ description:
+ - The state of the configuration after module completion
+ - The state I(rendered) considers the system default mode for interfaces to be
+ "Layer 3" and the system default state for interfaces to be shutdown.
+ - The state I(purged) negates virtual interfaces that are specified in task
+ from running-config.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ - purged
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# description testing
+# mtu 1800
+
+- name: Merge provided configuration with device configuration
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Ethernet1/1
+ description: Configured by Ansible
+ enabled: true
+ - name: Ethernet1/2
+ description: Configured by Ansible Network
+ enabled: false
+ state: merged
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# description Configured by Ansible
+# no shutdown
+# mtu 1800
+# interface Ethernet2
+# description Configured by Ansible Network
+# shutdown
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# description Interface 1/1
+# interface Ethernet1/2
+
+- name: Replaces device configuration of listed interfaces with provided configuration
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Ethernet1/1
+ description: Configured by Ansible
+ enabled: true
+ mtu: 2000
+ - name: Ethernet1/2
+ description: Configured by Ansible Network
+ enabled: false
+ mode: layer2
+ state: replaced
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# description Configured by Ansible
+# no shutdown
+# mtu 1500
+# interface Ethernet2/2
+# description Configured by Ansible Network
+# shutdown
+# switchport
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# description Interface Ethernet1/1
+# interface Ethernet1/2
+# interface mgmt0
+# description Management interface
+# ip address dhcp
+
+- name: Override device configuration of all interfaces with provided configuration
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Ethernet1/1
+ enabled: true
+ - name: Ethernet1/2
+ description: Configured by Ansible Network
+ enabled: false
+ state: overridden
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# interface Ethernet1/2
+# description Configured by Ansible Network
+# shutdown
+# interface mgmt0
+# ip address dhcp
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# description Interface Ethernet1/1
+# interface Ethernet1/2
+# interface mgmt0
+# description Management interface
+# ip address dhcp
+
+- name: Delete or return interface parameters to default settings
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Ethernet1/1
+ state: deleted
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# interface Ethernet1/2
+# interface mgmt0
+# description Management interface
+# ip address dhcp
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Ethernet1/1
+ description: outbound-intf
+ mode: layer3
+ speed: 100
+ - name: Ethernet1/2
+ mode: layer2
+ enabled: true
+ duplex: full
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/1"
+# - "description outbound-intf"
+# - "speed 100"
+# - "interface Ethernet1/2"
+# - "switchport"
+# - "duplex full"
+# - "no shutdown"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# interface Ethernet1/800
+# description test-1
+# speed 1000
+# shutdown
+# no switchport
+# duplex half
+# interface Ethernet1/801
+# description test-2
+# switchport
+# no shutdown
+# mtu 1800
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# - description: "test-1"
+# duplex: "half"
+# enabled: false
+# mode: "layer3"
+# name: "Ethernet1/800"
+# speed: "1000"
+#
+# - description: "test-2"
+# enabled: true
+# mode: "layer2"
+# mtu: "1800"
+# name: "Ethernet1/801"
+
+# Using gathered
+
+# Existing device config state
+# -----------------------------
+# interface Ethernet1/1
+# description outbound-intf
+# switchport
+# no shutdown
+# interface Ethernet1/2
+# description intf-l3
+# speed 1000
+# interface Ethernet1/3
+# interface Ethernet1/4
+# interface Ethernet1/5
+
+- name: Gather interfaces facts from the device using nxos_interfaces
+ cisco.nxos.nxos_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# - name: Ethernet1/1
+# description: outbound-intf
+# mode: layer2
+# enabled: True
+# - name: Ethernet1/2
+# description: intf-l3
+# speed: "1000"
+
+# Using purged
+
+# Existing device config state
+# -----------------------------
+# interface Vlan1
+# interface Vlan42
+# mtu 1800
+# interface port-channel10
+# interface port-channel11
+# interface Ethernet1/1
+# interface Ethernet1/2
+# interface Ethernet1/2.100
+# description sub-intf
+
+- name: Purge virtual interfaces from running-config
+ cisco.nxos.nxos_interfaces:
+ config:
+ - name: Vlan42
+ - name: port-channel10
+ - name: Ethernet1/2.100
+ state: purged
+
+# Task output
+# ------------
+# before:
+# - name: Vlan1
+# - mtu: '1800'
+# name: Vlan42
+# - name: port-channel10
+# - name: port-channel11
+# - name: Ethernet1/1
+# - name: Ethernet1/2
+# - description: sub-intf
+# name: Ethernet1/2.100
+#
+# commands:
+# - no interface port-channel10
+# - no interface Ethernet1/2.100
+# - no interface Vlan42
+#
+# after:
+# - name: Vlan1
+# - name: port-channel11
+# - name: Ethernet1/1
+# - name: Ethernet1/2
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet1/1', 'mtu 1800']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.interfaces.interfaces import (
+ InterfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.interfaces.interfaces import (
+ Interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=InterfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_l2_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_l2_interfaces.py
new file mode 100644
index 00000000..3e73c432
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_l2_interfaces.py
@@ -0,0 +1,408 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_l2_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_l2_interfaces
+short_description: L2 interfaces resource module
+description: This module manages Layer-2 interfaces attributes of NX-OS Interfaces.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of Layer-2 interface options
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of interface, i.e. Ethernet1/1.
+ type: str
+ required: true
+ access:
+ description:
+ - Switchport mode access command to configure the interface as a Layer-2 access.
+ type: dict
+ suboptions:
+ vlan:
+ description:
+ - Configure given VLAN in access port. It's used as the access VLAN ID.
+ type: int
+ trunk:
+ description:
+ - Switchport mode trunk command to configure the interface as a Layer-2 trunk.
+ type: dict
+ suboptions:
+ native_vlan:
+ description:
+ - Native VLAN to be configured in trunk port. It is used as the trunk
+ native VLAN ID.
+ type: int
+ allowed_vlans:
+ description:
+ - List of allowed VLANs in a given trunk port. These are the only VLANs
+ that will be configured on the trunk.
+ type: str
+ mode:
+ description:
+ - Mode in which interface needs to be configured.
+ - Access mode is not shown in interface facts, so idempotency will not be
+ maintained for switchport mode access and every time the output will come
+ as changed=True.
+ type: str
+ choices:
+ - dot1q-tunnel
+ - access
+ - trunk
+ - fex-fabric
+ - fabricpath
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# switchport access vlan 20
+# interface Ethernet1/2
+# switchport trunk native vlan 20
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_l2_interfaces:
+ config:
+ - name: Ethernet1/1
+ trunk:
+ native_vlan: 10
+ allowed_vlans: 2,4,15
+ - name: Ethernet1/2
+ access:
+ vlan: 30
+ state: merged
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# switchport trunk native vlan 10
+# switchport trunk allowed vlans 2,4,15
+# interface Ethernet1/2
+# switchport access vlan 30
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# switchport access vlan 20
+# interface Ethernet1/2
+# switchport trunk native vlan 20
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Replace device configuration of specified L2 interfaces with provided configuration.
+ cisco.nxos.nxos_l2_interfaces:
+ config:
+ - name: Ethernet1/1
+ trunk:
+ native_vlan: 20
+ allowed_vlans: 5-10, 15
+ state: replaced
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# switchport trunk native vlan 20
+# switchport trunk allowed vlan 5-10,15
+# interface Ethernet1/2
+# switchport trunk native vlan 20
+# switchport mode trunk
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# switchport access vlan 20
+# interface Ethernet1/2
+# switchport trunk native vlan 20
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Override device configuration of all L2 interfaces on device with provided
+ configuration.
+ cisco.nxos.nxos_l2_interfaces:
+ config:
+ - name: Ethernet1/2
+ access:
+ vlan: 30
+ state: overridden
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# interface Ethernet1/2
+# switchport access vlan 30
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/1
+# switchport access vlan 20
+# interface Ethernet1/2
+# switchport trunk native vlan 20
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+- name: Delete L2 attributes of given interfaces (Note This won't delete the interface
+ itself).
+ cisco.nxos.nxos_l2_interfaces:
+ config:
+ - name: Ethernet1/1
+ - name: Ethernet1/2
+ state: deleted
+
+# After state:
+# ------------
+#
+# interface Ethernet1/1
+# interface Ethernet1/2
+# interface mgmt0
+# ip address dhcp
+# ipv6 address auto-config
+
+# Using rendered
+
+- name: Render platform specific configuration lines (without connecting to the device)
+ cisco.nxos.nxos_l2_interfaces:
+ config:
+ - name: Ethernet1/1
+ trunk:
+ native_vlan: 10
+ allowed_vlans: 2,4,15
+ - name: Ethernet1/2
+ access:
+ vlan: 30
+ - name: Ethernet1/3
+ trunk:
+ native_vlan: 20
+ allowed_vlans: 5-10, 15
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/1"
+# - "switchport trunk allowed vlan 2,4,15"
+# - "switchport trunk native vlan 10"
+# - "interface Ethernet1/2"
+# - "switchport access vlan 30"
+# - "interface Ethernet1/3"
+# - "switchport trunk allowed vlan 5,6,7,8,9,10,15"
+# - "switchport trunk native vlan 20"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# interface Ethernet1/800
+# switchport access vlan 18
+# switchport trunk allowed vlan 210
+# interface Ethernet1/801
+# switchport trunk allowed vlan 2,4,15
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_l2_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# - name: Ethernet1/800
+# access:
+# vlan: 18
+# trunk:
+# allowed_vlans: "210"
+# - name: Ethernet1/801
+# trunk:
+# allowed_vlans: "2,4,15"
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# Nexus9kvI5# sh running-config | section ^interface
+# interface Ethernet1/1
+# switchport access vlan 6
+# switchport trunk allowed vlan 200
+# interface Ethernet1/2
+# switchport trunk native vlan 10
+
+- name: Gather l2_interfaces facts from the device using nxos_l2_interfaces
+ cisco.nxos.nxos_l2_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# - name: "Ethernet1/1"
+# access:
+# vlan: 6
+# trunk:
+# allowed_vlans: "200"
+#
+# - name: "Ethernet1/2"
+# trunk:
+# native_vlan: 10
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - "interface Ethernet1/1"
+ - "switchport trunk allowed vlan 2,4,15"
+ - "switchport trunk native vlan 10"
+ - "interface Ethernet1/2"
+ - "switchport access vlan 30"
+ - "interface Ethernet1/3"
+ - "switchport trunk allowed vlan 5,6,7,8,9,10,15"
+ - "switchport trunk native vlan 20"
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.l2_interfaces.l2_interfaces import (
+ L2_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.l2_interfaces.l2_interfaces import (
+ L2_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=L2_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = L2_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_l3_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_l3_interfaces.py
new file mode 100644
index 00000000..7b5cb7a6
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_l3_interfaces.py
@@ -0,0 +1,411 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_l3_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_l3_interfaces
+short_description: L3 interfaces resource module
+description: This module manages Layer-3 interfaces attributes of NX-OS Interfaces.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^interface').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of Layer-3 interface options
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Full name of L3 interface, i.e. Ethernet1/1.
+ type: str
+ required: true
+ dot1q:
+ description:
+ - Configures IEEE 802.1Q VLAN encapsulation on a subinterface.
+ type: int
+ ipv4:
+ description:
+ - IPv4 address and attributes of the L3 interface.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPV4 address of the L3 interface.
+ type: str
+ tag:
+ description:
+ - URIB route tag value for local/direct routes.
+ type: int
+ secondary:
+ description:
+ - A boolean attribute to manage addition of secondary IP address.
+ type: bool
+ ipv6:
+ description:
+ - IPv6 address and attributes of the L3 interface.
+ type: list
+ elements: dict
+ suboptions:
+ address:
+ description:
+ - IPV6 address of the L3 interface.
+ type: str
+ tag:
+ description:
+ - URIB route tag value for local/direct routes.
+ type: int
+ redirects:
+ description:
+ - Enables/disables ipv4 redirects.
+ type: bool
+ ipv6_redirects:
+ description:
+ - Enables/disables ipv6 redirects.
+ type: bool
+ unreachables:
+ description:
+ - Enables/disables ip redirects
+ type: bool
+ evpn_multisite_tracking:
+ description:
+ - VxLAN evpn multisite Interface tracking. Supported only on selected model.
+ type: str
+ version_added: 1.1.0
+ choices:
+ - fabric-tracking
+ - dci-tracking
+ state:
+ description:
+ - The state of the configuration after module completion.
+ - The state I(overridden) would override the IP address configuration
+ of all interfaces on the device with the provided configuration in
+ the task. Use caution with this state as you may loose access to the
+ device.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/6
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_l3_interfaces:
+ config:
+ - name: Ethernet1/6
+ ipv4:
+ - address: 192.168.1.1/24
+ tag: 5
+ - address: 10.1.1.1/24
+ secondary: true
+ tag: 10
+ ipv6:
+ - address: fd5d:12c9:2201:2::1/64
+ tag: 6
+ - name: Ethernet1/7.42
+ dot1q: 42
+ redirects: false
+ unreachables: false
+ state: merged
+
+# After state:
+# ------------
+#
+# interface Ethernet1/6
+# ip address 192.168.22.1/24 tag 5
+# ip address 10.1.1.1/24 secondary tag 10
+# interface Ethernet1/6
+# ipv6 address fd5d:12c9:2201:2::1/64 tag 6
+# interface Ethernet1/7.42
+# encapsulation dot1q 42
+# no ip redirects
+# no ip unreachables
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/6
+# ip address 192.168.22.1/24
+# ipv6 address "fd5d:12c9:2201:1::1/64"
+
+- name: Replace device configuration of specified L3 interfaces with provided configuration.
+ cisco.nxos.nxos_l3_interfaces:
+ config:
+ - name: Ethernet1/6
+ ipv4:
+ - address: 192.168.22.3/24
+ state: replaced
+
+# After state:
+# ------------
+#
+# interface Ethernet1/6
+# ip address 192.168.22.3/24
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/2
+# ip address 192.168.22.1/24
+# interface Ethernet1/6
+# ipv6 address "fd5d:12c9:2201:1::1/64"
+
+- name: Override device configuration of all L3 interfaces on device with provided
+ configuration.
+ cisco.nxos.nxos_l3_interfaces:
+ config:
+ - name: Ethernet1/2
+ ipv4: 192.168.22.3/4
+ state: overridden
+
+# After state:
+# ------------
+#
+# interface Ethernet1/2
+# ipv4 address 192.168.22.3/24
+# interface Ethernet1/6
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/6
+# ip address 192.168.22.1/24
+# interface Ethernet1/2
+# ipv6 address "fd5d:12c9:2201:1::1/64"
+
+- name: Delete L3 attributes of given interfaces (This won't delete the interface
+ itself).
+ cisco.nxos.nxos_l3_interfaces:
+ config:
+ - name: Ethernet1/6
+ - name: Ethernet1/2
+ state: deleted
+
+# After state:
+# ------------
+#
+# interface Ethernet1/6
+# interface Ethernet1/2
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_l3_interfaces:
+ config:
+ - name: Ethernet1/800
+ ipv4:
+ - address: 192.168.1.100/24
+ tag: 5
+ - address: 10.1.1.1/24
+ secondary: true
+ tag: 10
+ - name: Ethernet1/800
+ ipv6:
+ - address: fd5d:12c9:2201:2::1/64
+ tag: 6
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/800"
+# - "ip address 192.168.1.100/24 tag 5"
+# - "ip address 10.1.1.1/24 secondary tag 10"
+# - "interface Ethernet1/800"
+# - "ipv6 address fd5d:12c9:2201:2::1/64 tag 6"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# interface Ethernet1/800
+# ip address 192.168.1.100/24 tag 5
+# ip address 10.1.1.1/24 secondary tag 10
+# no ip redirects
+# interface Ethernet1/801
+# ipv6 address fd5d:12c9:2201:2::1/64 tag 6
+# ip unreachables
+# interface mgmt0
+# ip address dhcp
+# vrf member management
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_l3_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - name: Ethernet1/800
+# ipv4:
+# - address: 192.168.1.100/24
+# tag: 5
+# - address: 10.1.1.1/24
+# secondary: True
+# tag: 10
+# redirects: False
+# - name: Ethernet1/801
+# ipv6:
+# - address: fd5d:12c9:2201:2::1/64
+# tag: 6
+# unreachables: True
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# interface Ethernet1/1
+# ip address 192.0.2.100/24
+# interface Ethernet1/2
+# no ip redirects
+# ip address 203.0.113.10/24
+# ip unreachables
+# ipv6 address 2001:db8::1/32
+
+- name: Gather l3_interfaces facts from the device using nxos_l3_interfaces
+ cisco.nxos.nxos_l3_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+
+# gathered:
+# - name: Ethernet1/1
+# ipv4:
+# - address: 192.0.2.100/24
+# - name: Ethernet1/2
+# ipv4:
+# - address: 203.0.113.10/24
+# ipv6:
+# - address: 2001:db8::1/32
+# redirects: False
+# unreachables: True
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet1/2', 'ip address 192.168.0.1/2']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.l3_interfaces.l3_interfaces import (
+ L3_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.l3_interfaces.l3_interfaces import (
+ L3_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=L3_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = L3_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp.py
new file mode 100644
index 00000000..23349113
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp.py
@@ -0,0 +1,276 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_lacp
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_lacp
+short_description: LACP resource module
+description: This module manages Global Link Aggregation Control Protocol (LACP) on
+ NX-OS devices.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL.
+- Unsupported for Cisco MDS
+- Feature lacp should be enabled for this module.
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | include lacp).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: LACP global options.
+ type: dict
+ suboptions:
+ system:
+ description:
+ - LACP system options
+ type: dict
+ suboptions:
+ priority:
+ description:
+ - The system priority to use in LACP negotiations.
+ type: int
+ mac:
+ description:
+ - MAC address to be used for the LACP Protocol exchanges
+ type: dict
+ suboptions:
+ address:
+ description:
+ - MAC-address (FORMAT :xxxx.xxxx.xxxx).
+ type: str
+ role:
+ description:
+ - The role for the Switch.
+ type: str
+ choices:
+ - primary
+ - secondary
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_lacp:
+ config:
+ system:
+ priority: 10
+ mac:
+ address: 00c1.4c00.bd15
+ state: merged
+
+# After state:
+# ------------
+#
+# lacp system-priority 10
+# lacp system-mac 00c1.4c00.bd15
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# lacp system-priority 10
+
+- name: Replace device global lacp configuration with the given configuration.
+ cisco.nxos.nxos_lacp:
+ config:
+ system:
+ mac:
+ address: 00c1.4c00.bd15
+ state: replaced
+
+# After state:
+# ------------
+#
+# lacp system-mac 00c1.4c00.bd15
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# lacp system-priority 10
+
+- name: Delete global LACP configurations.
+ cisco.nxos.nxos_lacp:
+ state: deleted
+
+# After state:
+# ------------
+#
+
+# Using rendered
+
+- name: Render platform specific configuration lines (without connecting to the device)
+ cisco.nxos.nxos_lacp:
+ config:
+ system:
+ priority: 10
+ mac:
+ address: 00c1.4c00.bd15
+ role: secondary
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "lacp system-priority 10"
+# - "lacp system-mac 00c1.4c00.bd15 role secondary"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# lacp system-priority 10
+# lacp system-mac 00c1.4c00.bd15 role secondary
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_lacp:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# system:
+# priority: 10
+# mac:
+# address: 00c1.4c00.bd15
+# role: secondary
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# Nexus9000v# show running-config | include lacp
+# lacp system-priority 11
+# lacp system-mac 00c1.4c00.bd15 role primary
+
+- name: Gather lacp facts from the device using nxos_lacp
+ cisco.nxos.nxos_lacp:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# system:
+# priority: 11
+# mac:
+# address: 00c1.4c00.bd15
+# role: primary
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['lacp system-priority 15', 'lacp system-mac 00c1.4c00.bd15 role primary']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lacp.lacp import (
+ LacpArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lacp.lacp import Lacp
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=LacpArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Lacp(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp_interfaces.py
new file mode 100644
index 00000000..98e5a633
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lacp_interfaces.py
@@ -0,0 +1,381 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_lacp_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_lacp_interfaces
+short_description: LACP interfaces resource module
+description: This module manages Link Aggregation Control Protocol (LACP) attributes
+ of NX-OS Interfaces.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of LACP interfaces options.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the interface.
+ required: true
+ type: str
+ port_priority:
+ description:
+ - LACP port priority for the interface. Range 1-65535. Applicable only for
+ Ethernet.
+ type: int
+ rate:
+ description:
+ - Rate at which PDUs are sent by LACP. Applicable only for Ethernet. At fast
+ rate LACP is transmitted once every 1 second. At normal rate LACP is transmitted
+ every 30 seconds after the link is bundled.
+ type: str
+ choices:
+ - fast
+ - normal
+ links:
+ description:
+ - This dict contains configurable options related to max and min port-channel
+ links. Applicable only for Port-channel.
+ type: dict
+ suboptions:
+ max:
+ description:
+ - Port-channel max bundle.
+ type: int
+ min:
+ description:
+ - Port-channel min links.
+ type: int
+ mode:
+ description:
+ - LACP mode. Applicable only for Port-channel.
+ type: str
+ choices:
+ - delay
+ suspend_individual:
+ description:
+ - port-channel lacp state. Disabling this will cause lacp to put the port
+ to individual state and not suspend the port in case it does not get LACP
+ BPDU from the peer ports in the port-channel.
+ type: bool
+ convergence:
+ description:
+ - This dict contains configurable options related to convergence. Applicable
+ only for Port-channel.
+ type: dict
+ suboptions:
+ graceful:
+ description:
+ - port-channel lacp graceful convergence. Disable this only with lacp
+ ports connected to Non-Nexus peer. Disabling this with Nexus peer can
+ lead to port suspension.
+ type: bool
+ vpc:
+ description:
+ - Enable lacp convergence for vPC port channels.
+ type: bool
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_lacp_interfaces:
+ config:
+ - name: Ethernet1/3
+ port_priority: 5
+ rate: fast
+ state: merged
+
+# After state:
+# ------------
+#
+# interface Ethernet1/3
+# lacp port-priority 5
+# lacp rate fast
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/3
+# lacp port-priority 5
+# interface port-channel11
+# lacp mode delay
+
+- name: Replace device lacp interfaces configuration with the given configuration.
+ cisco.nxos.nxos_lacp_interfaces:
+ config:
+ - name: port-channel11
+ links:
+ min: 4
+ state: replaced
+
+# After state:
+# ------------
+#
+# interface Ethernet1/3
+# lacp port-priority 5
+# interface port-channel11
+# lacp min-links 4
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/3
+# lacp port-priority 5
+# interface port-channel11
+# lacp mode delay
+
+- name: Override device configuration of all LACP interfaces attributes of given interfaces
+ on device with provided configuration.
+ cisco.nxos.nxos_lacp_interfaces:
+ config:
+ - name: port-channel11
+ links:
+ min: 4
+ state: overridden
+
+# After state:
+# ------------
+#
+# interface port-channel11
+# lacp min-links 4
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/3
+# lacp port-priority 5
+# interface port-channel11
+# lacp mode delay
+
+- name: Delete LACP interfaces configurations.
+ cisco.nxos.nxos_lacp_interfaces:
+ state: deleted
+
+# After state:
+# ------------
+#
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_lacp_interfaces:
+ config:
+ - name: Ethernet1/800
+ rate: fast
+ - name: Ethernet1/801
+ rate: fast
+ port_priority: 32
+ - name: port-channel10
+ links:
+ max: 15
+ min: 2
+ convergence:
+ graceful: true
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/800"
+# - "lacp rate fast"
+# - "interface Ethernet1/801"
+# - "lacp port-priority 32"
+# - "lacp rate fast"
+# - "interface port-channel10"
+# - "lacp min-links 2"
+# - "lacp max-bundle 15"
+# - "lacp graceful-convergence"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+
+# interface port-channel10
+# lacp min-links 10
+# lacp max-bundle 15
+# interface Ethernet1/800
+# lacp port-priority 100
+# lacp rate fast
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_lacp_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - name: port-channel10
+# links:
+# max: 15
+# min: 10
+# - name: Ethernet1/800
+# port_priority: 100
+# rate: fast
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# interface Ethernet1/1
+# lacp port-priority 5
+# lacp rate fast
+# interface port-channel10
+# lacp mode delay
+# interface port-channel11
+# lacp max-bundle 10
+# lacp min-links 5
+
+- name: Gather lacp_interfaces facts from the device using nxos_lacp_interfaces
+ cisco.nxos.nxos_lacp_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# - name: Ethernet1/1
+# port_priority: 5
+# rate: fast
+# - name: port-channel10
+# mode: delay
+# - name: port-channel11
+# links:
+# max: 10
+# min: 5
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface port-channel10', 'lacp min-links 5', 'lacp mode delay']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lacp_interfaces.lacp_interfaces import (
+ Lacp_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lacp_interfaces.lacp_interfaces import (
+ Lacp_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Lacp_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Lacp_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lag_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lag_interfaces.py
new file mode 100644
index 00000000..ce06462e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lag_interfaces.py
@@ -0,0 +1,368 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_lag_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_lag_interfaces
+short_description: LAG interfaces resource module
+description: This module manages attributes of link aggregation groups of NX-OS Interfaces.
+version_added: 1.0.0
+author:
+- Trishna Guha (@trishnaguha)
+- Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of link aggregation group configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the link aggregation group (LAG).
+ type: str
+ required: true
+ members:
+ description:
+ - The list of interfaces that are part of the group.
+ type: list
+ elements: dict
+ suboptions:
+ member:
+ description:
+ - The interface name.
+ type: str
+ mode:
+ description:
+ - Link aggregation group (LAG).
+ type: str
+ choices:
+ - 'active'
+ - 'on'
+ - 'passive'
+ force:
+ description:
+ - When true it forces link aggregation group members to match what is
+ declared in the members param. This can be used to remove members.
+ type: bool
+ state:
+ description:
+ - The state of the configuration after module completion.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL.
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli).
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_lag_interfaces:
+ config:
+ - name: port-channel99
+ members:
+ - member: Ethernet1/4
+ state: merged
+
+# After state:
+# ------------
+#
+# interface Ethernet1/4
+# channel-group 99
+
+
+# Using replaced
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+# channel-group 99 mode active
+
+- name: Replace device configuration of specified LAG attributes of given interfaces
+ with provided configuration.
+ cisco.nxos.nxos_lag_interfaces:
+ config:
+ - name: port-channel10
+ members:
+ - member: Ethernet1/4
+ state: replaced
+
+# After state:
+# ------------
+#
+# interface Ethernet1/4
+# channel-group 10
+
+
+# Using overridden
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+# channel-group 10
+# interface Ethernet1/2
+# channel-group 99 mode passive
+
+- name: Override device configuration of all LAG attributes of given interfaces on
+ device with provided configuration.
+ cisco.nxos.nxos_lag_interfaces:
+ config:
+ - name: port-channel20
+ members:
+ - member: Ethernet1/6
+ force: true
+ state: overridden
+
+# After state:
+# ------------
+# interface Ethernet1/2
+# interface Ethernet1/4
+# interface Ethernet1/6
+# channel-group 20 force
+
+
+# Using deleted
+
+# Before state:
+# -------------
+#
+# interface Ethernet1/4
+# channel-group 99 mode active
+
+- name: Delete LAG attributes of given interface (This won't delete the port-channel
+ itself).
+ cisco.nxos.nxos_lag_interfaces:
+ config:
+ - port-channel: port-channel99
+ state: deleted
+
+- name: Delete LAG attributes of all the interfaces
+ cisco.nxos.nxos_lag_interfaces:
+ state: deleted
+
+# After state:
+# ------------
+#
+# interface Ethernet1/4
+# no channel-group 99
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_lag_interfaces:
+ config:
+ - name: port-channel10
+ members:
+ - member: Ethernet1/800
+ mode: active
+ - member: Ethernet1/801
+ - name: port-channel11
+ members:
+ - member: Ethernet1/802
+ mode: passive
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "interface Ethernet1/800"
+# - "channel-group 10 mode active"
+# - "interface Ethernet1/801"
+# - "channel-group 10"
+# - "interface Ethernet1/802"
+# - "channel-group 11 mode passive"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+
+# interface port-channel10
+# interface port-channel11
+# interface port-channel12
+# interface Ethernet1/800
+# channel-group 10 mode active
+# interface Ethernet1/801
+# channel-group 10 mode active
+# interface Ethernet1/802
+# channel-group 11 mode passive
+# interface Ethernet1/803
+# channel-group 11 mode passive
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_lag_interfaces:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - members:
+# - member: Ethernet1/800
+# mode: active
+# - member: Ethernet1/801
+# mode: active
+# name: port-channel10
+#
+# - members:
+# - member: Ethernet1/802
+# mode: passive
+# - member: Ethernet1/803
+# mode: passive
+# name: port-channel11
+#
+# - name: port-channel12
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# interface port-channel10
+# interface port-channel11
+# interface Ethernet1/1
+# channel-group 10 mode active
+# interface Ethernet1/2
+# channel-group 11 mode passive
+#
+
+- name: Gather lag_interfaces facts from the device using nxos_lag_interfaces
+ cisco.nxos.nxos_lag_interfaces:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# - name: port-channel10
+# members:
+# - member: Ethernet1/1
+# mode: active
+# - name: port-channel11
+# members:
+# - member: Ethernet1/2
+# mode: passive
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - "interface Ethernet1/800"
+ - "channel-group 10 mode active"
+ - "interface Ethernet1/801"
+ - "channel-group 10"
+ - "interface Ethernet1/802"
+ - "channel-group 11 mode passive"
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lag_interfaces.lag_interfaces import (
+ Lag_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lag_interfaces.lag_interfaces import (
+ Lag_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Lag_interfacesArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Lag_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_global.py
new file mode 100644
index 00000000..3bd3c553
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_global.py
@@ -0,0 +1,345 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The module file for nxos_lldp_global
+"""
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_lldp_global
+short_description: LLDP resource module
+description: This module configures and manages the Link Layer Discovery Protocol(LLDP)
+ attributes on NX-OS platforms.
+version_added: 1.0.0
+author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
+notes:
+- Tested against NxOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- The LLDP feature needs to be enabled before using this module
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | include lldp).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description:
+ - A list of link layer discovery configurations
+ type: dict
+ suboptions:
+ holdtime:
+ description:
+ - Amount of time the receiving device should hold the information (in seconds)
+ type: int
+ port_id:
+ description:
+ - This attribute defines if the interface names should be advertised in the
+ long(0) or short(1) form.
+ type: int
+ choices:
+ - 0
+ - 1
+ reinit:
+ description:
+ - Amount of time to delay the initialization of LLDP on any interface (in
+ seconds)
+ type: int
+ timer:
+ description:
+ - Frequency at which LLDP updates need to be transmitted (in seconds)
+ type: int
+ tlv_select:
+ description:
+ - This attribute can be used to specify the TLVs that need to be sent and
+ received in the LLDP packets. By default, all TLVs are advertised
+ type: dict
+ suboptions:
+ dcbxp:
+ description:
+ - Used to specify the Data Center Bridging Exchange Protocol TLV
+ type: bool
+ management_address:
+ description:
+ - Used to specify the management address in TLV messages
+ type: dict
+ suboptions:
+ v4:
+ description: Management address with TLV v4
+ type: bool
+ v6:
+ description: Management address with TLV v6
+ type: bool
+ port:
+ description:
+ - Used to manage port based attributes in TLV messages
+ type: dict
+ suboptions:
+ description:
+ description:
+ - Used to specify the port description TLV
+ type: bool
+ vlan:
+ description:
+ - Used to specify the port VLAN ID TLV
+ type: bool
+ power_management:
+ description:
+ - Used to specify IEEE 802.3 DTE Power via MDI TLV
+ type: bool
+ system:
+ description:
+ - Used to manage system based attributes in TLV messages
+ type: dict
+ suboptions:
+ capabilities:
+ description:
+ - Used to specify the system capabilities TLV
+ type: bool
+ description:
+ description:
+ - Used to specify the system description TLV
+ type: bool
+ name:
+ description:
+ - Used to specify the system name TLV
+ type: bool
+ state:
+ description:
+ - The state of the configuration after module completion
+ type: str
+ choices:
+ - merged
+ - replaced
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+# Before state:
+# -------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+
+- name: Merge provided configuration with device configuration
+ cisco.nxos.nxos_lldp_global:
+ config:
+ timer: 35
+ holdtime: 100
+ state: merged
+
+# After state:
+# ------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+# lldp timer 35
+# lldp holdtime 100
+
+
+# Using replaced
+# Before state:
+# -------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+# lldp holdtime 100
+# lldp reinit 5
+# lldp timer 35
+
+- name: Replace device configuration of specific LLDP attributes with provided configuration
+ cisco.nxos.nxos_lldp_global:
+ config:
+ timer: 40
+ tlv_select:
+ system:
+ description: true
+ name: false
+ management_address:
+ v4: true
+ state: replaced
+
+# After state:
+# ------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+# lldp timer 40
+# no lldp tlv-select system-name
+
+
+# Using deleted
+# Before state:
+# -------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+# lldp holdtime 5
+# lldp reinit 3
+
+- name: Delete LLDP configuration (this will by default remove all lldp configuration)
+ cisco.nxos.nxos_lldp_global:
+ state: deleted
+
+# After state:
+# ------------
+#
+# user(config)# show running-config | include lldp
+# feature lldp
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_lldp_global:
+ config:
+ holdtime: 130
+ port_id: 1
+ reinit: 5
+ tlv_select:
+ dcbxp: yes
+ power_management: yes
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - "lldp tlv-select dcbxp"
+# - "lldp tlv-select power-management"
+# - "lldp portid-subtype 1"
+# - "lldp reinit 5"
+# - "lldp holdtime 130"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# lldp holdtime 131
+# lldp reinit 7
+# no lldp tlv-select system-name
+# no lldp tlv-select system-description
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# holdtime: 131
+# reinit: 7
+# tlv_select:
+# system:
+# description: false
+# name: false
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# feature lldp
+# lldp holdtime 129
+# lldp reinit 5
+# lldp timer 35
+# no lldp tlv-select system-name
+
+# Task output (redacted)
+# -----------------------
+
+# gathered:
+# reinit: 5
+# timer: 35
+# tlv_select:
+# system:
+# name: False
+# holdtime: 129
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['lldp holdtime 125', 'lldp reinit 4', 'no lldp tlv-select system-name']
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lldp_global.lldp_global import (
+ Lldp_globalArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lldp_global.lldp_global import (
+ Lldp_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ mutually_exclusive = [("config", "running_config")]
+
+ module = AnsibleModule(
+ argument_spec=Lldp_globalArgs.argument_spec,
+ required_if=required_if,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = Lldp_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_interfaces.py
new file mode 100644
index 00000000..cf49a9ac
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_lldp_interfaces.py
@@ -0,0 +1,263 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The module file for nxos_lldp_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_lldp_interfaces
+short_description: LLDP interfaces resource module
+description: This module manages interfaces' configuration for Link Layer Discovery
+ Protocol (LLDP) on NX-OS platforms.
+version_added: 1.0.0
+author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- The LLDP feature needs to be enabled before using this module
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section ^interface).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description:
+ - A list of link layer discovery configurations for interfaces.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the interface
+ required: true
+ type: str
+ receive:
+ description:
+ - Used to enable or disable the reception of LLDP packets on that interface.
+ By default, this is enabled after LLDP is enabled globally.
+ type: bool
+ transmit:
+ description:
+ - Used to enable or disable the transmission of LLDP packets on that interface.
+ By default, this is enabled after LLDP is enabled globally.
+ type: bool
+ tlv_set:
+ description:
+ - Used to configure TLV parameters on the interface
+ type: dict
+ suboptions:
+ management_address:
+ description:
+ - Used to mention the IPv4 or IPv6 management address for the interface
+ type: str
+ vlan:
+ description:
+ - Used to mention the VLAN for the interface
+ type: int
+ state:
+ description:
+ - The state the configuration should be left in
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+#
+
+- name: Merge provided configuration with device configuration
+ cisco.nxos.nxos_lldp_interfaces:
+ config:
+ - name: Ethernet1/4
+ receive: false
+ transmit: true
+ tlv_set:
+ management_address: 192.168.122.64
+ vlan: 12
+ state: merged
+
+# After state:
+# -------------
+#
+# interface Ethernet1/4
+# no lldp receive
+# lldp tlv-set management-address 192.168.122.64
+# lldp tlv-set vlan 12
+
+
+# Using replaced
+
+# Before state:
+# ------------
+#
+# interface Ethernet1/4
+# no lldp receive
+# lldp tlv-set management-address 192.168.122.64
+# interface Ethernet1/5
+# no lldp transmit
+# lldp tlv-set vlan 10
+
+- name: Replace LLDP configuration on interfaces with given configuration
+ cisco.nxos.nxos_lldp_interfaces:
+ config:
+ - name: Ethernet1/4
+ transmit: no
+ tlv_set:
+ vlan: 2
+ state: replaced
+
+
+# After state:
+# -----------
+#
+# interface Ethernet1/4
+# no lldp transmit
+# lldp tlv_set vlan 2
+# interface Ethernet1/5
+# no lldp transmit
+# lldp tlv-set vlan 10
+
+
+# Using overridden
+
+# Before state:
+# ------------
+#
+# interface Ethernet1/4
+# no lldp receive
+# lldp tlv-set management-address 192.168.122.64
+# interface Ethernet1/5
+# no lldp transmit
+# lldp tlv-set vlan 10
+
+- name: Override LLDP configuration on all interfaces with given configuration
+ cisco.nxos.nxos_lldp_interfaces:
+ config:
+ - name: Ethernet1/7
+ receive: no
+ tlv_set:
+ vlan: 12
+ state: overridden
+
+
+# After state:
+# -----------
+#
+# interface Ethernet1/7
+# no lldp receive
+# lldp tlv_set vlan 12
+
+
+# Using deleted
+
+# Before state:
+# ------------
+#
+# interface Ethernet1/4
+# lldp tlv-set management vlan 24
+# no lldp transmit
+# interface mgmt0
+# no lldp receive
+
+- name: Delete LLDP interfaces configuration
+ cisco.nxos.nxos_lldp_interfaces:
+ state: deleted
+
+# After state:
+# ------------
+#
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['interface Ethernet1/2', 'lldp receive', 'lldp tlv-set vlan 12']
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.lldp_interfaces.lldp_interfaces import (
+ Lldp_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.lldp_interfaces.lldp_interfaces import (
+ Lldp_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Lldp_interfacesArgs.argument_spec,
+ supports_check_mode=True,
+ )
+
+ result = Lldp_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_logging.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging.py
new file mode 100644
index 00000000..7782eb32
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging.py
@@ -0,0 +1,940 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# Copyright: (c) 2017, Ansible by Red Hat, inc
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+DOCUMENTATION = """
+module: nxos_logging
+author: Trishna Guha (@trishnaguha)
+short_description: Manage logging on network devices
+notes:
+- Limited Support for Cisco MDS
+description:
+- This module provides declarative management of logging on Cisco NX-OS devices.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_logging_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2023-08-01'
+options:
+ dest:
+ description:
+ - Destination of the logs.
+ choices:
+ - console
+ - logfile
+ - module
+ - monitor
+ - server
+ type: str
+ remote_server:
+ description:
+ - Hostname or IP Address for remote logging (when dest is 'server').
+ type: str
+ use_vrf:
+ description:
+ - VRF to be used while configuring remote logging (when dest is 'server').
+ type: str
+ interface:
+ description:
+ - Interface to be used while configuring source-interface for logging (e.g., 'Ethernet1/2',
+ 'mgmt0')
+ type: str
+ name:
+ description:
+ - If value of C(dest) is I(logfile) it indicates file-name.
+ type: str
+ facility:
+ description:
+ - Facility name for logging.
+ type: str
+ dest_level:
+ description:
+ - Set logging severity levels.
+ aliases:
+ - level
+ type: int
+ facility_level:
+ description:
+ - Set logging severity levels for facility based log messages.
+ type: int
+ aggregate:
+ description: List of logging definitions.
+ type: list
+ elements: dict
+ state:
+ description:
+ - State of the logging configuration.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ event:
+ description:
+ - Link/trunk enable/default interface configuration logging
+ choices:
+ - link-enable
+ - link-default
+ - trunk-enable
+ - trunk-default
+ type: str
+ interface_message:
+ description:
+ - Add interface description to interface syslogs. Does not work with version 6.0
+ images using nxapi as a transport.
+ choices:
+ - add-interface-description
+ type: str
+ file_size:
+ description:
+ - Set logfile size
+ type: int
+ facility_link_status:
+ description:
+ - Set logging facility ethpm link status. Not idempotent with version 6.0 images.
+ choices:
+ - link-down-notif
+ - link-down-error
+ - link-up-notif
+ - link-up-error
+ type: str
+ timestamp:
+ description:
+ - Set logging timestamp format
+ choices:
+ - microseconds
+ - milliseconds
+ - seconds
+ type: str
+ purge:
+ description:
+ - Remove any switch logging configuration that does not match what has been configured
+ Not supported for ansible_connection local. All nxos_logging tasks must use
+ the same ansible_connection type.
+ type: bool
+ default: false
+extends_documentation_fragment:
+- cisco.nxos.nxos
+"""
+
+EXAMPLES = """
+- name: configure console logging with level
+ cisco.nxos.nxos_logging:
+ dest: console
+ level: 2
+ state: present
+- name: remove console logging configuration
+ cisco.nxos.nxos_logging:
+ dest: console
+ level: 2
+ state: absent
+- name: configure file logging with level
+ cisco.nxos.nxos_logging:
+ dest: logfile
+ name: testfile
+ dest_level: 3
+ state: present
+- name: Configure logging logfile with size
+ cisco.nxos.nxos_logging:
+ dest: logfile
+ name: testfile
+ dest_level: 3
+ file_size: 16384
+- name: configure facility level logging
+ cisco.nxos.nxos_logging:
+ facility: daemon
+ facility_level: 0
+ state: present
+- name: remove facility level logging
+ cisco.nxos.nxos_logging:
+ facility: daemon
+ facility_level: 0
+ state: absent
+- name: Configure Remote Logging
+ cisco.nxos.nxos_logging:
+ dest: server
+ remote_server: test-syslogserver.com
+ facility: auth
+ facility_level: 1
+ use_vrf: management
+ state: present
+- name: Configure Source Interface for Logging
+ cisco.nxos.nxos_logging:
+ interface: mgmt0
+ state: present
+- name: Purge nxos_logging configuration not managed by this playbook
+ cisco.nxos.nxos_logging:
+ purge: true
+- name: Configure logging timestamp
+ cisco.nxos.nxos_logging:
+ timestamp: milliseconds
+ state: present
+- name: Configure logging facility ethpm link status
+ cisco.nxos.nxos_logging:
+ facility: ethpm
+ facility_link_status: link-up-notif
+ state: present
+- name: Configure logging message ethernet description
+ cisco.nxos.nxos_logging:
+ interface_message: add-interface-description
+ state: present
+- name: Configure logging event link enable
+ cisco.nxos.nxos_logging:
+ event: link-enable
+ state: present
+- name: Configure logging using aggregate
+ cisco.nxos.nxos_logging:
+ aggregate:
+ - {dest: console, dest_level: 2}
+ - {dest: logfile, dest_level: 2, name: testfile}
+ - {facility: daemon, facility_level: 0}
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - logging console 2
+ - logging logfile testfile 3
+ - logging level daemon 0
+"""
+
+import copy
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+ normalize_interface,
+ read_module_context,
+ run_commands,
+ save_module_context,
+)
+
+
+STATIC_CLI = {
+ "link-enable": "logging event link-status enable",
+ "link-default": "logging event link-status default",
+ "trunk-enable": "logging event trunk-status enable",
+ "trunk-default": "logging event trunk-status default",
+ "microseconds": "logging timestamp microseconds",
+ "milliseconds": "logging timestamp milliseconds",
+ "seconds": "logging timestamp seconds",
+ "link-up-error": "link-up error",
+ "link-up-notif": "link-up notif",
+ "link-down-error": "link-down error",
+ "link-down-notif": "link-down notif",
+ "add-interface-description": "logging message interface type ethernet description",
+}
+
+DEFAULT_LOGGING_LEVEL = {
+ 0: [],
+ 1: [],
+ 2: ["pktmgr"],
+ 3: ["adjmgr", "arp", "icmpv6", "l2rib", "netstack"],
+ 4: [],
+ 5: ["mrib", "m6rib"],
+ 6: [],
+ 7: [],
+}
+
+DEST_GROUP = ["console", "logfile", "module", "monitor", "server"]
+
+
+def map_obj_to_commands(module, updates):
+ commands = list()
+ want, have = updates
+
+ for w in want:
+ state = w["state"]
+ del w["state"]
+
+ if state == "absent" and w in have:
+ if w["facility"] is not None:
+ if (
+ not w["dest"]
+ and not w["facility_link_status"]
+ and w["facility"] not in DEFAULT_LOGGING_LEVEL[int(w["facility_level"])]
+ ):
+ commands.append(
+ "no logging level {0} {1}".format(w["facility"], w["facility_level"]),
+ )
+
+ if w["facility_link_status"] and w["facility"] in ("ethpm"):
+ commands.append(
+ "no logging level {0} {1}".format(
+ w["facility"],
+ STATIC_CLI[w["facility_link_status"]],
+ ),
+ )
+
+ if w["name"] is not None:
+ commands.append("no logging logfile")
+
+ if w["dest"] in ("console", "module", "monitor"):
+ commands.append("no logging {0}".format(w["dest"]))
+
+ if w["dest"] == "server":
+ commands.append("no logging server {0}".format(w["remote_server"]))
+
+ if w["interface"]:
+ commands.append("no logging source-interface")
+
+ if w["event"] and w["event"] in STATIC_CLI:
+ commands.append("no " + STATIC_CLI[w["event"]])
+
+ if w["message"] and w["message"] in STATIC_CLI:
+ commands.append("no " + STATIC_CLI[w["message"]])
+
+ if w["timestamp"] and w["timestamp"] in STATIC_CLI:
+ commands.append("no " + STATIC_CLI[w["timestamp"]])
+
+ if state == "present" and w not in have:
+ if w["facility"] is None:
+ if w["dest"]:
+ if w["dest"] not in ("logfile", "server"):
+ commands.append("logging {0} {1}".format(w["dest"], w["dest_level"]))
+
+ elif w["dest"] == "logfile":
+ if w["file_size"]:
+ commands.append(
+ "logging logfile {0} {1} size {2}".format(
+ w["name"],
+ w["dest_level"],
+ w["file_size"],
+ ),
+ )
+ else:
+ commands.append(
+ "logging logfile {0} {1}".format(w["name"], w["dest_level"]),
+ )
+
+ elif w["dest"] == "server":
+ if w["facility_level"]:
+ if w["use_vrf"]:
+ commands.append(
+ "logging server {0} {1} use-vrf {2}".format(
+ w["remote_server"],
+ w["facility_level"],
+ w["use_vrf"],
+ ),
+ )
+ else:
+ commands.append(
+ "logging server {0} {1}".format(
+ w["remote_server"],
+ w["facility_level"],
+ ),
+ )
+
+ else:
+ if w["use_vrf"]:
+ commands.append(
+ "logging server {0} use-vrf {1}".format(
+ w["remote_server"],
+ w["use_vrf"],
+ ),
+ )
+ else:
+ commands.append("logging server {0}".format(w["remote_server"]))
+
+ if w["facility"]:
+ if w["dest"] == "server":
+ if w["facility_level"]:
+ if w["use_vrf"]:
+ commands.append(
+ "logging server {0} {1} facility {2} use-vrf {3}".format(
+ w["remote_server"],
+ w["facility_level"],
+ w["facility"],
+ w["use_vrf"],
+ ),
+ )
+ else:
+ commands.append(
+ "logging server {0} {1} facility {2}".format(
+ w["remote_server"],
+ w["facility_level"],
+ w["facility"],
+ ),
+ )
+ else:
+ if w["use_vrf"]:
+ commands.append(
+ "logging server {0} facility {1} use-vrf {2}".format(
+ w["remote_server"],
+ w["facility"],
+ w["use_vrf"],
+ ),
+ )
+ else:
+ commands.append(
+ "logging server {0} facility {1}".format(
+ w["remote_server"],
+ w["facility"],
+ ),
+ )
+ else:
+ if w["facility_link_status"]:
+ commands.append(
+ "logging level {0} {1}".format(
+ w["facility"],
+ STATIC_CLI[w["facility_link_status"]],
+ ),
+ )
+ else:
+ if not match_facility_default(module, w["facility"], w["facility_level"]):
+ commands.append(
+ "logging level {0} {1}".format(w["facility"], w["facility_level"]),
+ )
+
+ if w["interface"]:
+ commands.append(
+ "logging source-interface {0} {1}".format(*split_interface(w["interface"])),
+ )
+
+ if w["event"] and w["event"] in STATIC_CLI:
+ commands.append(STATIC_CLI[w["event"]])
+
+ if w["message"] and w["message"] in STATIC_CLI:
+ commands.append(STATIC_CLI[w["message"]])
+
+ if w["timestamp"] and w["timestamp"] in STATIC_CLI:
+ commands.append(STATIC_CLI[w["timestamp"]])
+
+ return commands
+
+
+def match_facility_default(module, facility, want_level):
+ """Check wanted facility to see if it matches current device default"""
+
+ matches_default = False
+ # Sample output from show logging level command
+ # Facility Default Severity Current Session Severity
+ # -------- ---------------- ------------------------
+ # bfd 5 5
+ #
+ # 0(emergencies) 1(alerts) 2(critical)
+ # 3(errors) 4(warnings) 5(notifications)
+ # 6(information) 7(debugging)
+
+ regexl = r"\S+\s+(\d+)\s+(\d+)"
+ cmd = {
+ "command": "show logging level {0}".format(facility),
+ "output": "text",
+ }
+ facility_data = run_commands(module, cmd)
+ for line in facility_data[0].split("\n"):
+ mo = re.search(regexl, line)
+ if mo and int(mo.group(1)) == int(want_level) and int(mo.group(2)) == int(want_level):
+ matches_default = True
+
+ return matches_default
+
+
+def split_interface(interface):
+ match = re.search(r"(\D+)(\S*)", interface, re.M)
+ if match:
+ return match.group(1), match.group(2)
+
+
+def parse_facility_link_status(line, facility, status):
+ facility_link_status = None
+
+ if facility is not None:
+ match = re.search(r"logging level {0} {1} (\S+)".format(facility, status), line, re.M)
+ if match:
+ facility_link_status = status + "-" + match.group(1)
+
+ return facility_link_status
+
+
+def parse_event_status(line, event):
+ status = None
+
+ match = re.search(r"logging event {0} (\S+)".format(event + "-status"), line, re.M)
+ if match:
+ state = match.group(1)
+ if state:
+ status = state
+
+ return status
+
+
+def parse_event(line):
+ event = None
+
+ match = re.search(r"logging event (\S+)", line, re.M)
+ if match:
+ state = match.group(1)
+ if state == "link-status":
+ event = "link"
+ elif state == "trunk-status":
+ event = "trunk"
+
+ return event
+
+
+def parse_message(line):
+ message = None
+
+ match = re.search(r"logging message interface type ethernet description", line, re.M)
+ if match:
+ message = "add-interface-description"
+
+ return message
+
+
+def parse_file_size(line, name, level):
+ file_size = None
+
+ match = re.search(r"logging logfile {0} {1} size (\S+)".format(name, level), line, re.M)
+ if match:
+ file_size = match.group(1)
+ if file_size == "8192" or file_size == "4194304":
+ file_size = None
+
+ return file_size
+
+
+def parse_timestamp(line):
+ timestamp = None
+
+ match = re.search(r"logging timestamp (\S+)", line, re.M)
+ if match:
+ timestamp = match.group(1)
+
+ return timestamp
+
+
+def parse_name(line, dest):
+ name = None
+
+ if dest is not None:
+ if dest == "logfile":
+ match = re.search(r"logging logfile (\S+)", line, re.M)
+ if match:
+ name = match.group(1)
+ else:
+ pass
+
+ return name
+
+
+def parse_remote_server(line, dest):
+ remote_server = None
+
+ if dest and dest == "server":
+ match = re.search(r"logging server (\S+)", line, re.M)
+ if match:
+ remote_server = match.group(1)
+
+ return remote_server
+
+
+def parse_dest_level(line, dest, name):
+ dest_level = None
+
+ def parse_match(match):
+ level = None
+ if match:
+ if int(match.group(1)) in range(0, 8):
+ level = match.group(1)
+ else:
+ pass
+ return level
+
+ if dest and dest != "server":
+ if dest == "logfile":
+ match = re.search(r"logging logfile {0} (\S+)".format(name), line, re.M)
+ if match:
+ dest_level = parse_match(match)
+
+ elif dest == "server":
+ match = re.search(r"logging server (?:\S+) (\d+)", line, re.M)
+ if match:
+ dest_level = parse_match(match)
+ else:
+ match = re.search(r"logging {0} (\S+)".format(dest), line, re.M)
+ if match:
+ dest_level = parse_match(match)
+
+ return dest_level
+
+
+def parse_facility_level(line, facility, dest):
+ facility_level = None
+
+ if dest == "server":
+ match = re.search(r"logging server (?:\S+) (\d+)", line, re.M)
+ if match:
+ facility_level = match.group(1)
+
+ elif facility is not None:
+ match = re.search(r"logging level {0} (\S+)".format(facility), line, re.M)
+ if match:
+ facility_level = match.group(1)
+
+ return facility_level
+
+
+def parse_facility(line):
+ facility = None
+
+ match = re.search(
+ r"logging server (?:\S+) (?:\d+) (?:\S+) (?:\S+) (?:\S+) (\S+)",
+ line,
+ re.M,
+ )
+ if match:
+ facility = match.group(1)
+
+ return facility
+
+
+def parse_use_vrf(line, dest):
+ use_vrf = None
+
+ if dest and dest == "server":
+ match = re.search(r"logging server (?:\S+) (?:\d+) use-vrf (\S+)", line, re.M)
+ if match:
+ use_vrf = match.group(1)
+
+ return use_vrf
+
+
+def parse_interface(line):
+ interface = None
+
+ match = re.search(r"logging source-interface (\S*)", line, re.M)
+ if match:
+ interface = match.group(1)
+
+ return interface
+
+
+def map_config_to_obj(module):
+ obj = []
+
+ data = get_config(module, flags=[" all | section logging"])
+
+ for line in data.split("\n"):
+ if re.search(r"no (\S+)", line, re.M):
+ state = "absent"
+ else:
+ state = "present"
+
+ match = re.search(r"logging (\S+)", line, re.M)
+ if state == "present" and match:
+ event_status = None
+ name = None
+ dest_level = None
+ dest = None
+ facility = None
+ remote_server = None
+ facility_link_status = None
+ file_size = None
+ facility_level = None
+
+ if match.group(1) in DEST_GROUP:
+ dest = match.group(1)
+
+ name = parse_name(line, dest)
+ remote_server = parse_remote_server(line, dest)
+ dest_level = parse_dest_level(line, dest, name)
+
+ if dest == "server":
+ facility = parse_facility(line)
+
+ facility_level = parse_facility_level(line, facility, dest)
+
+ if dest == "logfile":
+ file_size = parse_file_size(line, name, dest_level)
+
+ elif match.group(1) == "level":
+ match_facility = re.search(r"logging level (\S+)", line, re.M)
+ facility = match_facility.group(1)
+
+ level = parse_facility_level(line, facility, dest)
+ if level.isdigit():
+ facility_level = level
+ else:
+ facility_link_status = parse_facility_link_status(line, facility, level)
+
+ elif match.group(1) == "event" and state == "present":
+ event = parse_event(line)
+ if event:
+ status = parse_event_status(line, event)
+ if status:
+ event_status = event + "-" + status
+ else:
+ continue
+
+ else:
+ pass
+
+ obj.append(
+ {
+ "dest": dest,
+ "remote_server": remote_server,
+ "use_vrf": parse_use_vrf(line, dest),
+ "name": name,
+ "facility": facility,
+ "dest_level": dest_level,
+ "facility_level": facility_level,
+ "interface": parse_interface(line),
+ "facility_link_status": facility_link_status,
+ "event": event_status,
+ "file_size": file_size,
+ "message": parse_message(line),
+ "timestamp": parse_timestamp(line),
+ },
+ )
+
+ cmd = [
+ {
+ "command": "show logging | section enabled | section console",
+ "output": "text",
+ },
+ {
+ "command": "show logging | section enabled | section monitor",
+ "output": "text",
+ },
+ ]
+
+ default_data = run_commands(module, cmd)
+
+ for line in default_data:
+ flag = False
+ match = re.search(
+ r"Logging (\w+):(?:\s+) (?:\w+) (?:\W)Severity: (\w+)",
+ str(line),
+ re.M,
+ )
+ if match:
+ if match.group(1) == "console" and match.group(2) == "critical":
+ dest_level = "2"
+ flag = True
+ elif match.group(1) == "monitor" and match.group(2) == "notifications":
+ dest_level = "5"
+ flag = True
+ if flag:
+ obj.append(
+ {
+ "dest": match.group(1),
+ "remote_server": None,
+ "name": None,
+ "facility": None,
+ "dest_level": dest_level,
+ "facility_level": None,
+ "use_vrf": None,
+ "interface": None,
+ "facility_link_status": None,
+ "event": None,
+ "file_size": None,
+ "message": None,
+ "timestamp": None,
+ },
+ )
+
+ return obj
+
+
+def map_params_to_obj(module):
+ obj = []
+
+ if "aggregate" in module.params and module.params["aggregate"]:
+ args = {
+ "dest": "",
+ "remote_server": "",
+ "use_vrf": "",
+ "name": "",
+ "facility": "",
+ "dest_level": "",
+ "facility_level": "",
+ "interface": "",
+ "facility_link_status": None,
+ "event": None,
+ "file_size": None,
+ "message": None,
+ "timestamp": None,
+ }
+
+ for c in module.params["aggregate"]:
+ d = c.copy()
+
+ for key in args:
+ if key not in d:
+ d[key] = None
+
+ if d["dest_level"] is not None:
+ d["dest_level"] = str(d["dest_level"])
+
+ if d["facility_level"] is not None:
+ d["facility_level"] = str(d["facility_level"])
+
+ if d["interface"]:
+ d["interface"] = normalize_interface(d["interface"])
+
+ if "state" not in d:
+ d["state"] = module.params["state"]
+
+ if d["file_size"]:
+ d["file_size"] = str(d["file_size"])
+
+ obj.append(d)
+
+ else:
+ dest_level = None
+ facility_level = None
+ file_size = None
+
+ if module.params["dest_level"] is not None:
+ dest_level = str(module.params["dest_level"])
+
+ if module.params["facility_level"] is not None:
+ facility_level = str(module.params["facility_level"])
+
+ if module.params["file_size"] is not None:
+ file_size = str(module.params["file_size"])
+
+ obj.append(
+ {
+ "dest": module.params["dest"],
+ "remote_server": module.params["remote_server"],
+ "use_vrf": module.params["use_vrf"],
+ "name": module.params["name"],
+ "facility": module.params["facility"],
+ "dest_level": dest_level,
+ "facility_level": facility_level,
+ "interface": normalize_interface(module.params["interface"]),
+ "state": module.params["state"],
+ "facility_link_status": module.params["facility_link_status"],
+ "event": module.params["event"],
+ "message": module.params["interface_message"],
+ "file_size": file_size,
+ "timestamp": module.params["timestamp"],
+ },
+ )
+ return obj
+
+
+def merge_wants(wants, want):
+ if not wants:
+ wants = list()
+
+ for w in want:
+ w = copy.copy(w)
+ state = w["state"]
+ del w["state"]
+
+ if state == "absent":
+ if w in wants:
+ wants.remove(w)
+ elif w not in wants:
+ wants.append(w)
+
+ return wants
+
+
+def absent(h):
+ h["state"] = "absent"
+ return h
+
+
+def outliers(haves, wants):
+ wants = list(wants)
+ return [absent(h) for h in haves if not (h in wants or wants.append(h))]
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ dest=dict(choices=DEST_GROUP),
+ name=dict(),
+ facility=dict(),
+ remote_server=dict(),
+ use_vrf=dict(),
+ dest_level=dict(type="int", aliases=["level"]),
+ facility_level=dict(type="int"),
+ interface=dict(),
+ facility_link_status=dict(
+ choices=[
+ "link-down-notif",
+ "link-down-error",
+ "link-up-notif",
+ "link-up-error",
+ ],
+ ),
+ event=dict(
+ choices=[
+ "link-enable",
+ "link-default",
+ "trunk-enable",
+ "trunk-default",
+ ],
+ ),
+ interface_message=dict(choices=["add-interface-description"]),
+ file_size=dict(type="int"),
+ timestamp=dict(choices=["microseconds", "milliseconds", "seconds"]),
+ state=dict(default="present", choices=["present", "absent"]),
+ aggregate=dict(type="list", elements="dict"),
+ purge=dict(default=False, type="bool"),
+ )
+
+ required_if = [
+ ("dest", "logfile", ["name"]),
+ ("dest", "server", ["remote_server"]),
+ ]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+
+ want = map_params_to_obj(module)
+ merged_wants = merge_wants(read_module_context(module), want)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands(module, (want, have))
+ result["commands"] = commands
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ save_module_context(module, merged_wants)
+
+ if module.params.get("purge"):
+ pcommands = map_obj_to_commands(module, (outliers(have, merged_wants), have))
+ if pcommands:
+ if not module.check_mode:
+ load_config(module, pcommands)
+ result["changed"] = True
+ result["commands"] += pcommands
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_logging_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging_global.py
new file mode 100644
index 00000000..1c060b01
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_logging_global.py
@@ -0,0 +1,735 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_logging_global
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_logging_global
+short_description: Logging resource module.
+description:
+- This module manages logging configuration on devices running Cisco NX-OS.
+version_added: 2.5.0
+notes:
+- Tested against NX-OS 9.3.6 on Cisco Nexus Switches.
+- Limited Support for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+- Tested against Cisco MDS NX-OS 9.2(2) with connection C(network_cli).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | include logging).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of logging configuration.
+ type: dict
+ suboptions:
+ console:
+ description: Set console logging parameters.
+ type: dict
+ suboptions:
+ state:
+ description: Enable or disable monitor logging.
+ type: str
+ choices: ["enabled", "disabled"]
+ severity: &sev
+ description: Set severity severity for console.
+ type: str
+ choices:
+ - emergency
+ - alert
+ - critical
+ - error
+ - warning
+ - notification
+ - informational
+ - debugging
+ event:
+ description: Interface events.
+ type: dict
+ suboptions:
+ link_status:
+ description: UPDOWN and CHANGE messages.
+ type: dict
+ suboptions: &event
+ enable:
+ description: To enable logging overriding port severity configuration.
+ type: bool
+ default:
+ description: Default logging configuration used by interfaces not explicitly configured.
+ type: bool
+ trunk_status:
+ description: TRUNK status messages.
+ type: dict
+ suboptions: *event
+ history:
+ description: Modifies severity severity or size for history table.
+ type: dict
+ suboptions:
+ severity: *sev
+ size:
+ description: Set history table size.
+ type: int
+ ip:
+ description:
+ - IP configuration.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ access_list:
+ description: Access-List.
+ type: dict
+ suboptions:
+ cache:
+ description: Set caching settings.
+ type: dict
+ suboptions:
+ entries:
+ description: Maximum number of log entries cached in software.
+ type: int
+ interval:
+ description: Log-update interval (in sec).
+ type: int
+ threshold:
+ description: Log-update threshold (number of hits)
+ type: int
+ detailed:
+ description: Detailed ACL information.
+ type: bool
+ include:
+ description: Include additional fields in syslogs.
+ type: dict
+ suboptions:
+ sgt:
+ description: Include source group tag info in syslogs.
+ type: bool
+ facilities:
+ description: Facility parameter for syslog messages.
+ type: list
+ elements: dict
+ suboptions:
+ facility:
+ description: Facility name.
+ type: str
+ severity: *sev
+ logfile:
+ description: Set file logging.
+ type: dict
+ suboptions:
+ state:
+ description: Enable or disable logfile.
+ type: str
+ choices: ["enabled", "disabled"]
+ name:
+ description: Logfile name.
+ type: str
+ severity: *sev
+ persistent_threshold:
+ description:
+ - Set persistent logging utilization alert threshold in percentage.
+ - This option is unsupported on MDS switches.
+ type: int
+ size:
+ description: Enter the logfile size in bytes.
+ type: int
+ module:
+ description: Set module(linecard) logging.
+ type: dict
+ suboptions:
+ state:
+ description: Enable or disable module logging.
+ type: str
+ choices: ["enabled", "disabled"]
+ severity: *sev
+ monitor:
+ description: Set terminal line(monitor) logging severity.
+ type: dict
+ suboptions:
+ state:
+ description: Enable or disable monitor logging.
+ type: str
+ choices: ["enabled", "disabled"]
+ severity: *sev
+ origin_id:
+ description: Enable origin information for Remote Syslog Server.
+ type: dict
+ suboptions:
+ hostname:
+ description:
+ - Use hostname as origin-id of logging messages.
+ - This option is mutually exclusive with I(ip) and I(string).
+ type: bool
+ ip:
+ description:
+ - Use ip address as origin-id of logging messages.
+ - This option is mutually exclusive with I(hostname) and I(string).
+ type: str
+ string:
+ description:
+ - Use text string as origin-id of logging messages.
+ - This option is mutually exclusive with I(hostname) and I(ip).
+ type: str
+ rate_limit:
+ description: Enable or disable rate limit for log messages.
+ type: str
+ choices: ["enabled", "disabled"]
+ rfc_strict:
+ description:
+ - Set RFC to which messages should compliant.
+ - Syslogs will be compliant to RFC 5424.
+ - This option is unsupported on MDS switches.
+ type: bool
+ hosts:
+ description: Enable forwarding to Remote Syslog Servers.
+ type: list
+ elements: dict
+ suboptions:
+ host:
+ description: Hostname/IPv4/IPv6 address of the Remote Syslog Server.
+ type: str
+ severity: *sev
+ facility:
+ description: Facility to use when forwarding to server.
+ type: str
+ port:
+ description: Destination Port when forwarding to remote server.
+ type: int
+ secure:
+ description: Enable secure connection to remote server.
+ type: dict
+ suboptions:
+ trustpoint:
+ description: Trustpoint configuration.
+ type: dict
+ suboptions:
+ client_identity:
+ description:
+ - Client Identity certificate for mutual authentication.
+ - Trustpoint to use for client certificate authentication.
+ type: str
+ use_vrf:
+ description:
+ - Display per-VRF information.
+ - This option is unsupported on MDS switches.
+ type: str
+ source_interface:
+ description:
+ - Enable Source-Interface for Remote Syslog Server.
+ - This option is unsupported on MDS switches.
+ type: str
+ timestamp:
+ description: Set logging timestamp granularity.
+ type: str
+ choices: ["microseconds", "milliseconds", "seconds"]
+ state:
+ description:
+ - The state the configuration should be left in.
+ - The states I(replaced) and I(overridden) have identical
+ behaviour for this module.
+ - Refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config | include logging
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_logging_global:
+ config:
+ console:
+ severity: error
+ monitor:
+ severity: warning
+ ip:
+ access_list:
+ cache:
+ entries: 16384
+ interval: 200
+ threshold: 5000
+ facilities:
+ - facility: auth
+ severity: critical
+ - facility: ospfv3
+ severity: alert
+ - facility: ftp
+ severity: informational
+ hosts:
+ - host: 203.0.113.100
+ severity: alert
+ use_vrf: management
+ - host: 203.0.113.101
+ severity: error
+ facility: local6
+ use_vrf: default
+ origin_id:
+ hostname: True
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - "logging console 3"
+# - "logging monitor 4"
+# - "logging ip access-list cache entries 16384"
+# - "logging ip access-list cache interval 200"
+# - "logging ip access-list cache threshold 5000"
+# - "logging severity auth 2"
+# - "logging severity ospfv3 1"
+# - "logging severity ftp 6"
+# - "logging server 203.0.113.100 1 use-vrf management"
+# - "logging server 203.0.113.101 3 facility local6 use-vrf default"
+# - "logging origin-id hostname"
+#
+# after:
+# console:
+# severity: error
+# facilities:
+# - facility: auth
+# severity: critical
+# - facility: ftp
+# severity: informational
+# - facility: ospfv3
+# severity: alert
+# ip:
+# access_list:
+# cache:
+# entries: 16384
+# interval: 200
+# threshold: 5000
+# monitor:
+# severity: warning
+# origin_id:
+# hostname: true
+# hosts:
+# - severity: alert
+# host: 203.0.113.100
+# use_vrf: management
+# - facility: local6
+# severity: error
+# host: 203.0.113.101
+# use_vrf: default
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | include logging
+# logging console 3
+# logging monitor 4
+# logging ip access-list cache entries 16384
+# logging ip access-list cache interval 200
+# logging ip access-list cache threshold 5000
+# logging severity auth 2
+# logging severity ospfv3 1
+# logging severity ftp 6
+# logging origin-id hostname
+# logging server 203.0.113.100 1 use-vrf management
+# logging server 203.0.113.101 3 use-vrf default facility local6
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | include logging
+# logging console 3
+# logging monitor 4
+# logging ip access-list cache entries 16384
+# logging ip access-list cache interval 200
+# logging ip access-list cache threshold 5000
+# logging severity auth 2
+# logging severity ospfv3 1
+# logging severity ftp 6
+# logging origin-id hostname
+# logging server 203.0.113.100 1 use-vrf management
+# logging server 203.0.113.101 3 use-vrf default facility local6
+
+- name: Replace logging configurations with provided config
+ cisco.nxos.nxos_logging_global:
+ config:
+ monitor:
+ severity: warning
+ ip:
+ access_list:
+ cache:
+ entries: 4096
+ facilities:
+ - facility: auth
+ severity: critical
+ - facility: ospfv3
+ severity: alert
+ - facility: ftp
+ severity: informational
+ hosts:
+ - host: 203.0.113.101
+ severity: error
+ facility: local6
+ use_vrf: default
+ - host: 198.51.100.101
+ severity: alert
+ port: 6538
+ use_vrf: management
+ origin_id:
+ ip: 192.0.2.100
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# console:
+# severity: error
+# facilities:
+# - facility: auth
+# severity: critical
+# - facility: ftp
+# severity: informational
+# - facility: ospfv3
+# severity: alert
+# ip:
+# access_list:
+# cache:
+# entries: 16384
+# interval: 200
+# threshold: 5000
+# monitor:
+# severity: warning
+# origin_id:
+# hostname: true
+# hosts:
+# - severity: alert
+# host: 203.0.113.100
+# use_vrf: management
+# - facility: local6
+# severity: error
+# host: 203.0.113.101
+# use_vrf: default
+#
+# commands:
+# - "logging console"
+# - "logging ip access-list cache entries 4096"
+# - "no logging ip access-list cache interval 200"
+# - "no logging ip access-list cache threshold 5000"
+# - "no logging origin-id hostname"
+# - "logging origin-id ip 192.0.2.100"
+# - "logging server 198.51.100.101 1 port 6538 use-vrf management"
+# - "no logging server 203.0.113.100 1 use-vrf management"
+#
+# after:
+# facilities:
+# - facility: auth
+# severity: critical
+# - facility: ftp
+# severity: informational
+# - facility: ospfv3
+# severity: alert
+# ip:
+# access_list:
+# cache:
+# entries: 4096
+# monitor:
+# severity: warning
+# origin_id:
+# ip: 192.0.2.100
+# hosts:
+# - severity: alert
+# port: 6538
+# host: 198.51.100.101
+# use_vrf: management
+# - facility: local6
+# severity: error
+# host: 203.0.113.101
+# use_vrf: default
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | include logging
+# logging monitor 4
+# logging ip access-list cache entries 4096
+# logging severity auth 2
+# logging severity ospfv3 1
+# logging severity ftp 6
+# logging origin-id ip 192.0.2.100
+# logging server 203.0.113.101 3 use-vrf default facility local6
+# logging server 198.51.100.101 1 port 6538 use-vrf management
+
+# Using deleted to delete all logging configurations
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | include logging
+# logging console 3
+# logging monitor 4
+# logging ip access-list cache entries 16384
+# logging ip access-list cache interval 200
+# logging ip access-list cache threshold 5000
+# logging severity auth 2
+# logging severity ospfv3 1
+# logging severity ftp 6
+# logging origin-id hostname
+# logging server 203.0.113.100 1 use-vrf management
+# logging server 203.0.113.101 3 use-vrf default facility local6
+
+- name: Delete all logging configuration
+ cisco.nxos.nxos_logging_global:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# console:
+# severity: error
+# facilities:
+# - facility: auth
+# severity: critical
+# - facility: ftp
+# severity: informational
+# - facility: ospfv3
+# severity: alert
+# ip:
+# access_list:
+# cache:
+# entries: 16384
+# interval: 200
+# threshold: 5000
+# monitor:
+# severity: warning
+# origin_id:
+# hostname: true
+# hosts:
+# - severity: alert
+# host: 203.0.113.100
+# use_vrf: management
+# - facility: local6
+# severity: error
+# host: 203.0.113.101
+# use_vrf: default
+#
+# commands:
+# - "logging console"
+# - "logging monitor"
+# - "no logging ip access-list cache entries 16384"
+# - "no logging ip access-list cache interval 200"
+# - "no logging ip access-list cache threshold 5000"
+# - "no logging origin-id hostname"
+# - "no logging severity auth 2"
+# - "no logging severity ospfv3 1"
+# - "no logging severity ftp 6"
+# - "no logging server 203.0.113.100 1 use-vrf management"
+# - "no logging server 203.0.113.101 3 facility local6 use-vrf default"
+#
+# after: {}
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_logging_global:
+ config:
+ console:
+ severity: error
+ monitor:
+ severity: warning
+ ip:
+ access_list:
+ cache:
+ entries: 16384
+ interval: 200
+ threshold: 5000
+ facilities:
+ - facility: auth
+ severity: critical
+ - facility: ospfv3
+ severity: alert
+ - facility: ftp
+ severity: informational
+ hosts:
+ - host: 203.0.113.100
+ severity: alert
+ use_vrf: management
+ - host: 203.0.113.101
+ severity: error
+ facility: local6
+ use_vrf: default
+ origin_id:
+ hostname: True
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - "logging console 3"
+# - "logging monitor 4"
+# - "logging ip access-list cache entries 16384"
+# - "logging ip access-list cache interval 200"
+# - "logging ip access-list cache threshold 5000"
+# - "logging severity auth 2"
+# - "logging severity ospfv3 1"
+# - "logging severity ftp 6"
+# - "logging server 203.0.113.100 1 use-vrf management"
+# - "logging server 203.0.113.101 3 facility local6 use-vrf default"
+# - "logging origin-id hostname"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# logging console 3
+# logging monitor 4
+# logging ip access-list cache entries 16384
+# logging ip access-list cache interval 200
+# logging ip access-list cache threshold 5000
+# logging severity auth 2
+# logging severity ospfv3 1
+# logging severity ftp 6
+# logging origin-id hostname
+# logging server 203.0.113.100 1 use-vrf management
+# logging server 203.0.113.101 3 use-vrf default facility local6
+
+- name: Parse externally provided logging configuration
+ cisco.nxos.nxos_logging_global:
+ running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# console:
+# severity: error
+# facilities:
+# - facility: auth
+# severity: critical
+# - facility: ftp
+# severity: informational
+# - facility: ospfv3
+# severity: alert
+# ip:
+# access_list:
+# cache:
+# entries: 16384
+# interval: 200
+# threshold: 5000
+# monitor:
+# severity: warning
+# origin_id:
+# hostname: true
+# hosts:
+# - severity: alert
+# host: 203.0.113.100
+# use_vrf: management
+# - facility: local6
+# severity: error
+# host: 203.0.113.101
+# use_vrf: default
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when state is I(merged), I(replaced), I(overridden), I(deleted) or I(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when state is I(merged), I(replaced), I(overridden), I(deleted) or I(purged)
+ type: list
+ sample:
+ - "logging console 3"
+ - "logging monitor 4"
+ - "logging ip access-list cache entries 16384"
+ - "logging ip access-list cache interval 200"
+ - "logging ip access-list cache threshold 5000"
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when state is I(rendered)
+ type: list
+ sample:
+ - "logging ip access-list cache entries 4096"
+ - "no logging ip access-list cache interval 200"
+ - "no logging ip access-list cache threshold 5000"
+ - "no logging origin-id hostname"
+ - "logging origin-id ip 192.0.2.100"
+ - "logging server 198.51.100.101 1 port 6538 use-vrf management"
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when state is I(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when state is I(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.logging_global.logging_global import (
+ Logging_globalArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.logging_global.logging_global import (
+ Logging_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Logging_globalArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Logging_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp.py
new file mode 100644
index 00000000..046436d4
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp.py
@@ -0,0 +1,446 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_ntp
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages core NTP configuration.
+notes:
+- Limited Support for Cisco MDS
+description:
+- Manages core NTP configuration.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_ntp_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+options:
+ server:
+ description:
+ - Network address of NTP server.
+ type: str
+ peer:
+ description:
+ - Network address of NTP peer.
+ type: str
+ key_id:
+ description:
+ - Authentication key identifier to use with given NTP server or peer or keyword
+ 'default'.
+ type: str
+ prefer:
+ description:
+ - Makes given NTP server or peer the preferred NTP server or peer for the device.
+ choices:
+ - enabled
+ - disabled
+ type: str
+ vrf_name:
+ description:
+ - Makes the device communicate with the given NTP server or peer over a specific
+ VRF or keyword 'default'.
+ type: str
+ source_addr:
+ description:
+ - Local source address from which NTP messages are sent or keyword 'default'.
+ type: str
+ source_int:
+ description:
+ - Local source interface from which NTP messages are sent. Must be fully qualified
+ interface name or keyword 'default'
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# Set NTP Server with parameters
+- cisco.nxos.nxos_ntp:
+ server: 1.2.3.4
+ key_id: 32
+ prefer: enabled
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"address": "192.0.2.2", "key_id": "48",
+ "peer_type": "server", "prefer": "enabled",
+ "source": "192.0.2.3", "source_type": "source"}
+existing:
+ description:
+ - k/v pairs of existing ntp server/peer
+ returned: always
+ type: dict
+ sample: {"address": "192.0.2.2", "key_id": "32",
+ "peer_type": "server", "prefer": "enabled",
+ "source": "ethernet2/1", "source_type": "source-interface"}
+end_state:
+ description: k/v pairs of ntp info after module execution
+ returned: always
+ type: dict
+ sample: {"address": "192.0.2.2", "key_id": "48",
+ "peer_type": "server", "prefer": "enabled",
+ "source": "192.0.2.3", "source_type": "source"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["ntp server 192.0.2.2 prefer key 48",
+ "no ntp source-interface ethernet2/1", "ntp source 192.0.2.3"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ if "show run" not in command:
+ output = "json"
+ else:
+ output = "text"
+
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_ntp_source(module):
+ source_type = None
+ source = None
+ command = "show run | inc ntp.source"
+ output = execute_show_command(command, module, command_type="cli_show_ascii")
+
+ if output:
+ try:
+ if "interface" in output[0]:
+ source_type = "source-interface"
+ else:
+ source_type = "source"
+ source = output[0].split()[2].lower()
+ except (AttributeError, IndexError):
+ source_type = None
+ source = None
+
+ return source_type, source
+
+
+def get_ntp_peer(module):
+ command = "show run | inc ntp.(server|peer)"
+ ntp_peer_list = []
+ response = execute_show_command(command, module, command_type="cli_show_ascii")
+
+ if response:
+ if isinstance(response, list):
+ ntp = response[0]
+ else:
+ ntp = response
+ if ntp:
+ ntp_regex = (
+ r".*ntp\s(server\s(?P<address>\S+)|peer\s(?P<peer_address>\S+))"
+ r"\s*((?P<prefer>prefer)\s*)?(use-vrf\s(?P<vrf_name>\S+)\s*)?"
+ r"(key\s(?P<key_id>\d+))?.*"
+ )
+
+ split_ntp = ntp.splitlines()
+ for peer_line in split_ntp:
+ if "access-group" in peer_line:
+ continue
+ ntp_peer = {}
+ try:
+ peer_address = None
+ vrf_name = "default"
+ prefer = None
+ key_id = None
+ match_ntp = re.match(ntp_regex, peer_line, re.DOTALL)
+ group_ntp = match_ntp.groupdict()
+
+ address = group_ntp["address"]
+ peer_address = group_ntp["peer_address"]
+ prefer = group_ntp["prefer"]
+ vrf_name = group_ntp["vrf_name"]
+ key_id = group_ntp["key_id"]
+
+ if prefer is not None:
+ prefer = "enabled"
+ else:
+ prefer = "disabled"
+
+ if address is not None:
+ peer_type = "server"
+ elif peer_address is not None:
+ peer_type = "peer"
+ address = peer_address
+
+ args = dict(
+ peer_type=peer_type,
+ address=address,
+ prefer=prefer,
+ vrf_name=vrf_name,
+ key_id=key_id,
+ )
+
+ ntp_peer = dict((k, v) for k, v in args.items())
+ ntp_peer_list.append(ntp_peer)
+ except AttributeError:
+ ntp_peer_list = []
+
+ return ntp_peer_list
+
+
+def get_ntp_existing(address, peer_type, module):
+ peer_dict = {}
+ peer_server_list = []
+
+ peer_list = get_ntp_peer(module)
+ for peer in peer_list:
+ if peer["address"] == address:
+ peer_dict.update(peer)
+ else:
+ peer_server_list.append(peer)
+
+ source_type, source = get_ntp_source(module)
+
+ if source_type is not None and source is not None:
+ peer_dict["source_type"] = source_type
+ peer_dict["source"] = source
+
+ return (peer_dict, peer_server_list)
+
+
+def set_ntp_server_peer(peer_type, address, prefer, key_id, vrf_name):
+ command_strings = []
+
+ if prefer:
+ command_strings.append(" prefer")
+ if key_id:
+ command_strings.append(" key {0}".format(key_id))
+ if vrf_name:
+ command_strings.append(" use-vrf {0}".format(vrf_name))
+
+ command_strings.insert(0, "ntp {0} {1}".format(peer_type, address))
+
+ command = "".join(command_strings)
+
+ return command
+
+
+def config_ntp(delta, existing):
+ if (
+ delta.get("address")
+ or delta.get("peer_type")
+ or delta.get("vrf_name")
+ or delta.get("key_id")
+ or delta.get("prefer")
+ ):
+ address = delta.get("address", existing.get("address"))
+ peer_type = delta.get("peer_type", existing.get("peer_type"))
+ key_id = delta.get("key_id", existing.get("key_id"))
+ prefer = delta.get("prefer", existing.get("prefer"))
+ vrf_name = delta.get("vrf_name", existing.get("vrf_name"))
+ if delta.get("key_id") == "default":
+ key_id = None
+ else:
+ peer_type = None
+ prefer = None
+
+ source_type = delta.get("source_type")
+ source = delta.get("source")
+
+ if prefer:
+ if prefer == "enabled":
+ prefer = True
+ elif prefer == "disabled":
+ prefer = False
+
+ if source:
+ source_type = delta.get("source_type", existing.get("source_type"))
+
+ ntp_cmds = []
+ if peer_type:
+ if existing.get("peer_type") and existing.get("address"):
+ ntp_cmds.append(
+ "no ntp {0} {1}".format(existing.get("peer_type"), existing.get("address")),
+ )
+ ntp_cmds.append(set_ntp_server_peer(peer_type, address, prefer, key_id, vrf_name))
+ if source:
+ existing_source_type = existing.get("source_type")
+ existing_source = existing.get("source")
+ if existing_source_type and source_type != existing_source_type:
+ ntp_cmds.append("no ntp {0} {1}".format(existing_source_type, existing_source))
+ if source == "default":
+ if existing_source_type and existing_source:
+ ntp_cmds.append("no ntp {0} {1}".format(existing_source_type, existing_source))
+ else:
+ ntp_cmds.append("ntp {0} {1}".format(source_type, source))
+
+ return ntp_cmds
+
+
+def main():
+ argument_spec = dict(
+ server=dict(type="str"),
+ peer=dict(type="str"),
+ key_id=dict(type="str"),
+ prefer=dict(type="str", choices=["enabled", "disabled"]),
+ vrf_name=dict(type="str"),
+ source_addr=dict(type="str"),
+ source_int=dict(type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[["server", "peer"], ["source_addr", "source_int"]],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+
+ server = module.params["server"] or None
+ peer = module.params["peer"] or None
+ key_id = module.params["key_id"]
+ prefer = module.params["prefer"]
+ vrf_name = module.params["vrf_name"]
+ source_addr = module.params["source_addr"]
+ source_int = module.params["source_int"]
+ state = module.params["state"]
+
+ if source_int is not None:
+ source_int = source_int.lower()
+
+ if server:
+ peer_type = "server"
+ address = server
+ elif peer:
+ peer_type = "peer"
+ address = peer
+ else:
+ peer_type = None
+ address = None
+
+ source_type = None
+ source = None
+ if source_addr:
+ source_type = "source"
+ source = source_addr
+ elif source_int:
+ source_type = "source-interface"
+ source = source_int
+
+ if key_id or vrf_name or prefer:
+ if not server and not peer:
+ module.fail_json(msg="Please supply the server or peer parameter")
+
+ args = dict(
+ peer_type=peer_type,
+ address=address,
+ key_id=key_id,
+ prefer=prefer,
+ vrf_name=vrf_name,
+ source_type=source_type,
+ source=source,
+ )
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ existing, peer_server_list = get_ntp_existing(address, peer_type, module)
+
+ end_state = existing
+ changed = False
+ commands = []
+
+ if state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta.get("key_id") and delta.get("key_id") == "default":
+ if not existing.get("key_id"):
+ delta.pop("key_id")
+ if delta:
+ command = config_ntp(delta, existing)
+ if command:
+ commands.append(command)
+
+ elif state == "absent":
+ if existing.get("peer_type") and existing.get("address"):
+ command = "no ntp {0} {1}".format(existing["peer_type"], existing["address"])
+ if command:
+ commands.append([command])
+
+ existing_source_type = existing.get("source_type")
+ existing_source = existing.get("source")
+ proposed_source_type = proposed.get("source_type")
+ proposed_source = proposed.get("source")
+
+ if proposed_source_type:
+ if proposed_source_type == existing_source_type:
+ if proposed_source == existing_source:
+ command = "no ntp {0} {1}".format(existing_source_type, existing_source)
+ if command:
+ commands.append([command])
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_ntp_existing(address, peer_type, module)[0]
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+ results["end_state"] = end_state
+ results["peer_server_list"] = peer_server_list
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_auth.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_auth.py
new file mode 100644
index 00000000..3e564381
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_auth.py
@@ -0,0 +1,336 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_ntp_auth
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages NTP authentication.
+description:
+- Manages NTP authentication.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_ntp_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- If C(state=absent), the module will remove the given key configuration if it exists.
+- If C(state=absent) and C(authentication=on), authentication will be turned off.
+options:
+ key_id:
+ description:
+ - Authentication key identifier (numeric).
+ type: str
+ md5string:
+ description:
+ - MD5 String.
+ type: str
+ auth_type:
+ description:
+ - Whether the given md5string is in cleartext or has been encrypted. If in cleartext,
+ the device will encrypt it before storing it.
+ default: text
+ choices:
+ - text
+ - encrypt
+ type: str
+ trusted_key:
+ description:
+ - Whether the given key is required to be supplied by a time source for the device
+ to synchronize to the time source.
+ choices:
+ - 'false'
+ - 'true'
+ default: 'false'
+ type: str
+ authentication:
+ description:
+ - Turns NTP authentication on or off.
+ choices:
+ - "on"
+ - "off"
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# Basic NTP authentication configuration
+- cisco.nxos.nxos_ntp_auth:
+ key_id: 32
+ md5string: hello
+ auth_type: text
+"""
+
+RETURN = """
+commands:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["ntp authentication-key 32 md5 helloWorld 0", "ntp trusted-key 32"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ if "show run" not in command:
+ command = {"command": command, "output": "json"}
+ else:
+ command = {"command": command, "output": "text"}
+
+ return run_commands(module, [command])
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_ntp_auth(module):
+ command = "show ntp authentication-status"
+
+ body = execute_show_command(command, module)[0]
+ ntp_auth_str = body["authentication"]
+
+ if "enabled" in ntp_auth_str:
+ ntp_auth = True
+ else:
+ ntp_auth = False
+
+ return ntp_auth
+
+
+def get_ntp_trusted_key(module):
+ trusted_key_list = []
+ command = "show run | inc ntp.trusted-key"
+
+ trusted_key_str = execute_show_command(command, module)[0]
+ if trusted_key_str:
+ trusted_keys = trusted_key_str.splitlines()
+
+ else:
+ trusted_keys = []
+
+ for line in trusted_keys:
+ if line:
+ trusted_key_list.append(str(line.split()[2]))
+
+ return trusted_key_list
+
+
+def get_ntp_auth_key(key_id, module):
+ authentication_key = {}
+ command = "show run | inc ntp.authentication-key.{0}".format(key_id)
+ auth_regex = (
+ r".*ntp\sauthentication-key\s(?P<key_id>\d+)\smd5\s(?P<md5string>\S+)\s(?P<atype>\S+).*"
+ )
+
+ body = execute_show_command(command, module)[0]
+
+ try:
+ match_authentication = re.match(auth_regex, body, re.DOTALL)
+ group_authentication = match_authentication.groupdict()
+ authentication_key["key_id"] = group_authentication["key_id"]
+ authentication_key["md5string"] = group_authentication["md5string"]
+ if group_authentication["atype"] == "7":
+ authentication_key["auth_type"] = "encrypt"
+ else:
+ authentication_key["auth_type"] = "text"
+ except (AttributeError, TypeError):
+ authentication_key = {}
+
+ return authentication_key
+
+
+def get_ntp_auth_info(key_id, module):
+ auth_info = get_ntp_auth_key(key_id, module)
+ trusted_key_list = get_ntp_trusted_key(module)
+ auth_power = get_ntp_auth(module)
+
+ if key_id in trusted_key_list:
+ auth_info["trusted_key"] = "true"
+ else:
+ auth_info["trusted_key"] = "false"
+
+ if auth_power:
+ auth_info["authentication"] = "on"
+ else:
+ auth_info["authentication"] = "off"
+
+ return auth_info
+
+
+def auth_type_to_num(auth_type):
+ if auth_type == "encrypt":
+ return "7"
+ else:
+ return "0"
+
+
+def set_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
+ ntp_auth_cmds = []
+ if key_id and md5string:
+ auth_type_num = auth_type_to_num(auth_type)
+ ntp_auth_cmds.append(
+ "ntp authentication-key {0} md5 {1} {2}".format(key_id, md5string, auth_type_num),
+ )
+
+ if trusted_key == "true":
+ ntp_auth_cmds.append("ntp trusted-key {0}".format(key_id))
+ elif trusted_key == "false":
+ ntp_auth_cmds.append("no ntp trusted-key {0}".format(key_id))
+
+ if authentication == "on":
+ ntp_auth_cmds.append("ntp authenticate")
+ elif authentication == "off":
+ ntp_auth_cmds.append("no ntp authenticate")
+
+ return ntp_auth_cmds
+
+
+def remove_ntp_auth_key(key_id, md5string, auth_type, trusted_key, authentication):
+ auth_remove_cmds = []
+ if key_id:
+ auth_type_num = auth_type_to_num(auth_type)
+ auth_remove_cmds.append(
+ "no ntp authentication-key {0} md5 {1} {2}".format(key_id, md5string, auth_type_num),
+ )
+
+ if authentication:
+ auth_remove_cmds.append("no ntp authenticate")
+ return auth_remove_cmds
+
+
+def main():
+ argument_spec = dict(
+ key_id=dict(type="str"),
+ md5string=dict(type="str"),
+ auth_type=dict(choices=["text", "encrypt"], default="text"),
+ trusted_key=dict(choices=["true", "false"], default="false"),
+ authentication=dict(choices=["on", "off"]),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ key_id = module.params["key_id"]
+ md5string = module.params["md5string"]
+ auth_type = module.params["auth_type"]
+ trusted_key = module.params["trusted_key"]
+ authentication = module.params["authentication"]
+ state = module.params["state"]
+
+ if key_id:
+ if not trusted_key and not md5string:
+ module.fail_json(msg="trusted_key or md5string MUST be specified")
+
+ args = dict(
+ key_id=key_id,
+ md5string=md5string,
+ auth_type=auth_type,
+ trusted_key=trusted_key,
+ authentication=authentication,
+ )
+
+ changed = False
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ existing = get_ntp_auth_info(key_id, module)
+ end_state = existing
+
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ commands = []
+ if state == "present":
+ if delta:
+ command = set_ntp_auth_key(
+ key_id,
+ md5string,
+ delta.get("auth_type"),
+ delta.get("trusted_key"),
+ delta.get("authentication"),
+ )
+ if command:
+ commands.append(command)
+ elif state == "absent":
+ auth_toggle = None
+ if existing.get("authentication") == "on":
+ auth_toggle = True
+ if not existing.get("key_id"):
+ key_id = None
+ command = remove_ntp_auth_key(key_id, md5string, auth_type, trusted_key, auth_toggle)
+ if command:
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ load_config(module, cmds)
+ end_state = get_ntp_auth_info(key_id, module)
+ delta = dict(set(end_state.items()).difference(existing.items()))
+ if delta or (len(existing) != len(end_state)):
+ changed = True
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+ results["end_state"] = end_state
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_global.py
new file mode 100644
index 00000000..e99fbef8
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_global.py
@@ -0,0 +1,736 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_ntp_global
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_ntp_global
+short_description: NTP Global resource module.
+description:
+- This module manages ntp configuration on devices running Cisco NX-OS.
+version_added: 2.6.0
+notes:
+- Tested against NX-OS 9.3.6 on Cisco Nexus Switches.
+- This module works with connection C(network_cli) and C(httpapi).
+- Tested against Cisco MDS NX-OS 9.2(2) with connection C(network_cli).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config ntp).
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dict of ntp configuration.
+ type: dict
+ suboptions:
+ access_group:
+ description:
+ - NTP access-group.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ match_all:
+ description: Scan ACLs present in all ntp access groups.
+ type: bool
+ peer:
+ description: Access-group peer.
+ type: list
+ elements: dict
+ suboptions:
+ access_list:
+ description: Name of access list.
+ type: str
+ query_only:
+ description: Access-group query-only.
+ type: list
+ elements: dict
+ suboptions:
+ access_list:
+ description: Name of access list.
+ type: str
+ serve:
+ description: Access-group serve.
+ type: list
+ elements: dict
+ suboptions:
+ access_list:
+ description: Name of access list.
+ type: str
+ serve_only:
+ description: Access-group serve-only.
+ type: list
+ elements: dict
+ suboptions:
+ access_list:
+ description: Name of access list.
+ type: str
+ allow:
+ description: Enable/Disable the packets.
+ type: dict
+ suboptions:
+ control:
+ description: Control mode packets.
+ type: dict
+ suboptions:
+ rate_limit:
+ description: Rate-limit delay.
+ type: int
+ private:
+ description: Enable/Disable Private mode packets.
+ type: bool
+ authenticate:
+ description: Enable/Disable authentication.
+ type: bool
+ authentication_keys:
+ description: NTP authentication key.
+ type: list
+ elements: dict
+ suboptions:
+ id:
+ description: Authentication key number (range 1-65535).
+ type: int
+ key:
+ description: Authentication key.
+ type: str
+ encryption:
+ description:
+ - 0 for Clear text
+ - 7 for Encrypted
+ type: int
+ logging:
+ description: Enable/Disable logging of NTPD Events.
+ type: bool
+ master:
+ description:
+ - Act as NTP master clock.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ stratum:
+ description: Stratum number.
+ type: int
+ passive:
+ description:
+ - NTP passive command.
+ - This option is unsupported on MDS switches.
+ type: bool
+ peers:
+ description: NTP Peers.
+ type: list
+ elements: dict
+ suboptions:
+ peer:
+ description: Hostname/IP address of the NTP Peer.
+ type: str
+ key_id:
+ description: Keyid to be used while communicating to this server.
+ type: int
+ maxpoll:
+ description:
+ - Maximum interval to poll a peer.
+ - Poll interval in secs to a power of 2.
+ type: int
+ minpoll:
+ description:
+ - Minimum interval to poll a peer.
+ - Poll interval in secs to a power of 2.
+ type: int
+ prefer:
+ description:
+ - Preferred Server.
+ type: bool
+ vrf:
+ description:
+ - Display per-VRF information.
+ - This option is unsupported on MDS switches.
+ type: str
+ aliases: ["use_vrf"]
+ servers:
+ description: NTP servers.
+ type: list
+ elements: dict
+ suboptions:
+ server:
+ description: Hostname/IP address of the NTP Peer.
+ type: str
+ key_id:
+ description: Keyid to be used while communicating to this server.
+ type: int
+ maxpoll:
+ description:
+ - Maximum interval to poll a peer.
+ - Poll interval in secs to a power of 2.
+ type: int
+ minpoll:
+ description:
+ - Minimum interval to poll a peer.
+ - Poll interval in secs to a power of 2.
+ type: int
+ prefer:
+ description:
+ - Preferred Server.
+ type: bool
+ vrf:
+ description:
+ - Display per-VRF information.
+ - This option is not applicable for MDS switches.
+ type: str
+ aliases: ["use_vrf"]
+ source:
+ description:
+ - Source of NTP packets.
+ - This option is unsupported on MDS switches.
+ type: str
+ source_interface:
+ description: Source interface sending NTP packets.
+ type: str
+ trusted_keys:
+ description: NTP trusted-key number.
+ type: list
+ elements: dict
+ suboptions:
+ key_id:
+ description: Trusted-Key number.
+ type: int
+ state:
+ description:
+ - The state the configuration should be left in.
+ - The states I(replaced) and I(overridden) have identical
+ behaviour for this module.
+ - Please refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config ntp
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_ntp_global: &id001
+ config:
+ access_group:
+ peer:
+ - access_list: PeerAcl1
+ serve:
+ - access_list: ServeAcl1
+ authenticate: True
+ authentication_keys:
+ - id: 1001
+ key: vagwwtKfkv
+ encryption: 7
+ - id: 1002
+ key: vagwwtKfkvgthz
+ encryption: 7
+ logging: True
+ master:
+ stratum: 2
+ peers:
+ - peer: 192.0.2.1
+ key_id: 1
+ maxpoll: 15
+ minpoll: 5
+ vrf: default
+ - peer: 192.0.2.2
+ key_id: 2
+ prefer: True
+ vrf: siteA
+ servers:
+ - server: 198.51.100.1
+ key_id: 2
+ vrf: default
+ - server: 203.0.113.1
+ key_id: 1
+ vrf: siteB
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - "ntp authenticate"
+# - "ntp logging"
+# - "ntp master 2"
+# - "ntp authentication-keys 1001 md5 vagwwtKfkv 7"
+# - "ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7"
+# - "ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15"
+# - "ntp peer 192.0.2.2 prefer use-vrf siteA key 2"
+# - "ntp server 198.51.100.1 use-vrf default key 2"
+# - "ntp server 203.0.113.1 use-vrf siteB key 1"
+# - "ntp access-group peer PeerAcl1"
+# - "ntp access-group serve ServeAcl1"
+#
+# after:
+# access_group:
+# peer:
+# - access_list: PeerAcl1
+# serve:
+# - access_list: ServeAcl1
+# authenticate: True
+# authentication_keys:
+# - id: 1001
+# key: vagwwtKfkv
+# encryption: 7
+# - id: 1002
+# key: vagwwtKfkvgthz
+# encryption: 7
+# logging: True
+# master:
+# stratum: 2
+# peers:
+# - peer: 192.0.2.1
+# key_id: 1
+# maxpoll: 15
+# minpoll: 5
+# vrf: default
+# - peer: 192.0.2.2
+# key_id: 2
+# prefer: True
+# vrf: siteA
+# servers:
+# - server: 198.51.100.1
+# key_id: 2
+# vrf: default
+# - server: 203.0.113.1
+# key_id: 1
+# vrf: siteB
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config ntp
+# ntp authenticate
+# ntp logging
+# ntp master 2
+# ntp authentication-keys 1001 md5 vagwwtKfkv 7
+# ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7
+# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15
+# ntp peer 192.0.2.2 prefer use-vrf siteA key 2
+# ntp server 198.51.100.1 use-vrf default key 2
+# ntp server 203.0.113.1 use-vrf siteB key 1
+# ntp access-group peer PeerAcl1
+# ntp access-group serve ServeAcl1
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config ntp
+# ntp authenticate
+# ntp logging
+# ntp master 2
+# ntp authentication-keys 1001 md5 vagwwtKfkv 7
+# ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7
+# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15
+# ntp peer 192.0.2.2 prefer use-vrf siteA key 2
+# ntp server 198.51.100.1 use-vrf default key 2
+# ntp server 203.0.113.1 use-vrf siteB key 1
+# ntp access-group peer PeerAcl1
+# ntp access-group serve ServeAcl1
+
+- name: Replace logging global configurations of listed logging global with provided configurations
+ cisco.nxos.nxos_ntp_global:
+ config:
+ access_group:
+ peer:
+ - access_list: PeerAcl2
+ serve:
+ - access_list: ServeAcl2
+ logging: True
+ master:
+ stratum: 2
+ peers:
+ - peer: 192.0.2.1
+ key_id: 1
+ maxpoll: 15
+ minpoll: 5
+ vrf: default
+ - peer: 192.0.2.5
+ key_id: 2
+ prefer: True
+ vrf: siteA
+ servers:
+ - server: 198.51.100.1
+ key_id: 2
+ vrf: default
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# access_group:
+# peer:
+# - access_list: PeerAcl1
+# serve:
+# - access_list: ServeAcl1
+# authenticate: True
+# authentication_keys:
+# - id: 1001
+# key: vagwwtKfkv
+# encryption: 7
+# - id: 1002
+# key: vagwwtKfkvgthz
+# encryption: 7
+# logging: True
+# master:
+# stratum: 2
+# peers:
+# - peer: 192.0.2.1
+# key_id: 1
+# maxpoll: 15
+# minpoll: 5
+# vrf: default
+# - peer: 192.0.2.2
+# key_id: 2
+# prefer: True
+# vrf: siteA
+# servers:
+# - server: 198.51.100.1
+# key_id: 2
+# vrf: default
+# - server: 203.0.113.1
+# key_id: 1
+# vrf: siteB
+#
+# commands:
+# - "no ntp authenticate"
+# - "no ntp authentication-keys 1001 md5 vagwwtKfkv 7"
+# - "no ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7"
+# - "ntp peer 192.0.2.5 prefer use-vrf siteA key 2"
+# - "no ntp peer 192.0.2.2 prefer use-vrf siteA key 2"
+# - "no ntp server 203.0.113.1 use-vrf siteB key 1"
+# - "ntp access-group peer PeerAcl2"
+# - "no ntp access-group peer PeerAcl1"
+# - "ntp access-group serve ServeAcl2"
+# - "no ntp access-group serve ServeAcl1"
+#
+# after:
+# access_group:
+# peer:
+# - access_list: PeerAcl2
+# serve:
+# - access_list: ServeAcl2
+# logging: True
+# master:
+# stratum: 2
+# peers:
+# - peer: 192.0.2.1
+# key_id: 1
+# maxpoll: 15
+# minpoll: 5
+# vrf: default
+# - peer: 192.0.2.5
+# key_id: 2
+# prefer: True
+# vrf: siteA
+# servers:
+# - server: 198.51.100.1
+# key_id: 2
+# vrf: default
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config ntp
+# ntp logging
+# ntp master 2
+# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15
+# ntp peer 192.0.2.5 prefer use-vrf siteA key 2
+# ntp server 198.51.100.1 use-vrf default key 2
+# ntp access-group peer PeerAcl2
+# ntp access-group serve ServeAcl2
+
+# Using deleted to delete all logging configurations
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config ntp
+
+- name: Delete all logging configuration
+ cisco.nxos.nxos_ntp_global:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# access_group:
+# peer:
+# - access_list: PeerAcl1
+# serve:
+# - access_list: ServeAcl1
+# authenticate: True
+# authentication_keys:
+# - id: 1001
+# key: vagwwtKfkv
+# encryption: 7
+# - id: 1002
+# key: vagwwtKfkvgthz
+# encryption: 7
+# logging: True
+# master:
+# stratum: 2
+# peers:
+# - peer: 192.0.2.1
+# key_id: 1
+# maxpoll: 15
+# minpoll: 5
+# vrf: default
+# - peer: 192.0.2.2
+# key_id: 2
+# prefer: True
+# vrf: siteA
+# servers:
+# - server: 198.51.100.1
+# key_id: 2
+# vrf: default
+# - server: 203.0.113.1
+# key_id: 1
+# vrf: siteB
+#
+# commands:
+# - "no ntp authenticate"
+# - "no ntp logging"
+# - "no ntp master 2"
+# - "no ntp authentication-keys 1001 md5 vagwwtKfkv 7"
+# - "no ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7"
+# - "no ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15"
+# - "no ntp peer 192.0.2.2 prefer use-vrf siteA key 2"
+# - "no ntp server 198.51.100.1 use-vrf default key 2"
+# - "no ntp server 203.0.113.1 use-vrf siteB key 1"
+# - "no ntp access-group peer PeerAcl1"
+# - "no ntp access-group serve ServeAcl1"
+#
+# after: {}
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config ntp
+# nxos-9k-rdo#
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_ntp_global:
+ config:
+ access_group:
+ peer:
+ - access_list: PeerAcl1
+ serve:
+ - access_list: ServeAcl1
+ authenticate: True
+ authentication_keys:
+ - id: 1001
+ key: vagwwtKfkv
+ encryption: 7
+ - id: 1002
+ key: vagwwtKfkvgthz
+ encryption: 7
+ logging: True
+ master:
+ stratum: 2
+ peers:
+ - peer: 192.0.2.1
+ key_id: 1
+ maxpoll: 15
+ minpoll: 5
+ vrf: default
+ - peer: 192.0.2.2
+ key_id: 2
+ prefer: True
+ vrf: siteA
+ servers:
+ - server: 198.51.100.1
+ key_id: 2
+ vrf: default
+ - server: 203.0.113.1
+ key_id: 1
+ vrf: siteB
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - "ntp authenticate"
+# - "ntp logging"
+# - "ntp master 2"
+# - "ntp authentication-keys 1001 md5 vagwwtKfkv 7"
+# - "ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7"
+# - "ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15"
+# - "ntp peer 192.0.2.2 prefer use-vrf siteA key 2"
+# - "ntp server 198.51.100.1 use-vrf default key 2"
+# - "ntp server 203.0.113.1 use-vrf siteB key 1"
+# - "ntp access-group peer PeerAcl1"
+# - "ntp access-group serve ServeAcl1"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# ntp authenticate
+# ntp logging
+# ntp master 2
+# ntp authentication-keys 1001 md5 vagwwtKfkv 7
+# ntp authentication-keys 1002 md5 vagwwtKfkvgthz 7
+# ntp peer 192.0.2.1 use-vrf default key 1 minpoll 5 maxpoll 15
+# ntp peer 192.0.2.2 prefer use-vrf siteA key 2
+# ntp server 198.51.100.1 use-vrf default key 2
+# ntp server 203.0.113.1 use-vrf siteB key 1
+# ntp access-group peer PeerAcl1
+# ntp access-group serve ServeAcl1
+
+- name: Parse externally provided ntp configuration
+ cisco.nxos.nxos_ntp_global:
+ running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# access_group:
+# peer:
+# - access_list: PeerAcl1
+# serve:
+# - access_list: ServeAcl1
+# authenticate: True
+# authentication_keys:
+# - id: 1001
+# key: vagwwtKfkv
+# encryption: 7
+# - id: 1002
+# key: vagwwtKfkvgthz
+# encryption: 7
+# logging: True
+# master:
+# stratum: 2
+# peers:
+# - peer: 192.0.2.1
+# key_id: 1
+# maxpoll: 15
+# minpoll: 5
+# vrf: default
+# - peer: 192.0.2.2
+# key_id: 2
+# prefer: True
+# vrf: siteA
+# servers:
+# - server: 198.51.100.1
+# key_id: 2
+# vrf: default
+# - server: 203.0.113.1
+# key_id: 1
+# vrf: siteB
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: list
+ sample:
+ - ntp master stratum 2
+ - ntp peer 198.51.100.1 use-vrf test maxpoll 7
+ - ntp authentication-key 10 md5 wawyhanx2 7
+ - ntp access-group peer PeerAcl1
+ - ntp access-group peer PeerAcl2
+ - ntp access-group query-only QueryAcl1
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when I(state) is C(rendered)
+ type: list
+ sample:
+ - ntp master stratum 2
+ - ntp peer 198.51.100.1 use-vrf test maxpoll 7
+ - ntp authentication-key 10 md5 wawyhanx2 7
+ - ntp access-group peer PeerAcl1
+ - ntp access-group peer PeerAcl2
+ - ntp access-group query-only QueryAcl1
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when I(state) is C(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when I(state) is C(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ntp_global.ntp_global import (
+ Ntp_globalArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ntp_global.ntp_global import (
+ Ntp_global,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Ntp_globalArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Ntp_global(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_options.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_options.py
new file mode 100644
index 00000000..28fd1aac
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ntp_options.py
@@ -0,0 +1,173 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_ntp_options
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages NTP options.
+description:
+- Manages NTP options, e.g. authoritative server and logging.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_ntp_global
+ why: Updated module released with more functionality.
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- When C(state=absent), master and logging will be set to False and stratum will be
+ removed as well
+options:
+ master:
+ description:
+ - Sets whether the device is an authoritative NTP server.
+ type: bool
+ stratum:
+ description:
+ - If C(master=true), an optional stratum can be supplied (1-15). The device default
+ is 8.
+ type: str
+ logging:
+ description:
+ - Sets whether NTP logging is enabled on the device.
+ type: bool
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+EXAMPLES = """
+# Basic NTP options configuration
+- cisco.nxos.nxos_ntp_options:
+ master: true
+ stratum: 12
+ logging: false
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["no ntp logging", "ntp master 12"]
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def get_current(module):
+ cmd = "show running-config | inc ntp"
+
+ master = False
+ logging = False
+ stratum = None
+
+ output = run_commands(module, ({"command": cmd, "output": "text"}))[0]
+
+ if output:
+ match = re.search(r"^ntp master(?: (\d+))", output, re.M)
+ if match:
+ master = True
+ stratum = match.group(1)
+ logging = "ntp logging" in output.lower()
+
+ return {"master": master, "stratum": stratum, "logging": logging}
+
+
+def main():
+ argument_spec = dict(
+ master=dict(required=False, type="bool"),
+ stratum=dict(required=False, type="str"),
+ logging=dict(required=False, type="bool"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ master = module.params["master"]
+ stratum = module.params["stratum"]
+ logging = module.params["logging"]
+ state = module.params["state"]
+
+ if stratum and master is False:
+ if stratum != 8:
+ module.fail_json(msg="master MUST be True when stratum is changed")
+
+ current = get_current(module)
+
+ result = {"changed": False}
+
+ commands = list()
+
+ if state == "absent":
+ if current["master"]:
+ commands.append("no ntp master")
+ if current["logging"]:
+ commands.append("no ntp logging")
+
+ elif state == "present":
+ if master and not current["master"]:
+ commands.append("ntp master")
+ elif master is False and current["master"]:
+ commands.append("no ntp master")
+ if stratum and stratum != current["stratum"]:
+ commands.append("ntp master %s" % stratum)
+
+ if logging and not current["logging"]:
+ commands.append("ntp logging")
+ elif logging is False and current["logging"]:
+ commands.append("no ntp logging")
+
+ result["commands"] = commands
+ result["updates"] = commands
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ result["warnings"] = warnings
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_nxapi.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_nxapi.py
new file mode 100644
index 00000000..cfd7cc87
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_nxapi.py
@@ -0,0 +1,424 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+# pylint: skip-file
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_nxapi
+extends_documentation_fragment:
+- cisco.nxos.nxos
+author: Peter Sprygada (@privateip)
+short_description: Manage NXAPI configuration on an NXOS device.
+notes:
+- Limited Support for Cisco MDS
+description:
+- Configures the NXAPI feature on devices running Cisco NXOS. The NXAPI feature is
+ absent from the configuration by default. Since this module manages the NXAPI feature
+ it only supports the use of the C(Cli) transport.
+version_added: 1.0.0
+options:
+ http_port:
+ description:
+ - Configure the port with which the HTTP server will listen on for requests. By
+ default, NXAPI will bind the HTTP service to the standard HTTP port 80. This
+ argument accepts valid port values in the range of 1 to 65535.
+ required: false
+ default: 80
+ type: int
+ http:
+ description:
+ - Controls the operating state of the HTTP protocol as one of the underlying transports
+ for NXAPI. By default, NXAPI will enable the HTTP transport when the feature
+ is first configured. To disable the use of the HTTP transport, set the value
+ of this argument to False.
+ required: false
+ default: true
+ type: bool
+ aliases:
+ - enable_http
+ https_port:
+ description:
+ - Configure the port with which the HTTPS server will listen on for requests. By
+ default, NXAPI will bind the HTTPS service to the standard HTTPS port 443. This
+ argument accepts valid port values in the range of 1 to 65535.
+ required: false
+ default: 443
+ type: int
+ https:
+ description:
+ - Controls the operating state of the HTTPS protocol as one of the underlying
+ transports for NXAPI. By default, NXAPI will disable the HTTPS transport when
+ the feature is first configured. To enable the use of the HTTPS transport,
+ set the value of this argument to True.
+ required: false
+ default: false
+ type: bool
+ aliases:
+ - enable_https
+ sandbox:
+ description:
+ - The NXAPI feature provides a web base UI for developers for entering commands. This
+ feature is initially disabled when the NXAPI feature is configured for the first
+ time. When the C(sandbox) argument is set to True, the developer sandbox URL
+ will accept requests and when the value is set to False, the sandbox URL is
+ unavailable. This is supported on NX-OS 7K series.
+ required: false
+ type: bool
+ aliases:
+ - enable_sandbox
+ state:
+ description:
+ - The C(state) argument controls whether or not the NXAPI feature is configured
+ on the remote device. When the value is C(present) the NXAPI feature configuration
+ is present in the device running-config. When the values is C(absent) the feature
+ configuration is removed from the running-config.
+ choices:
+ - present
+ - absent
+ required: false
+ default: present
+ type: str
+ ssl_strong_ciphers:
+ description:
+ - Controls the use of whether strong or weak ciphers are configured. By default,
+ this feature is disabled and weak ciphers are configured. To enable the use
+ of strong ciphers, set the value of this argument to True.
+ required: false
+ default: false
+ type: bool
+ tlsv1_0:
+ description:
+ - Controls the use of the Transport Layer Security version 1.0 is configured. By
+ default, this feature is enabled. To disable the use of TLSV1.0, set the value
+ of this argument to True.
+ required: false
+ default: true
+ type: bool
+ tlsv1_1:
+ description:
+ - Controls the use of the Transport Layer Security version 1.1 is configured. By
+ default, this feature is disabled. To enable the use of TLSV1.1, set the value
+ of this argument to True.
+ required: false
+ default: false
+ type: bool
+ tlsv1_2:
+ description:
+ - Controls the use of the Transport Layer Security version 1.2 is configured. By
+ default, this feature is disabled. To enable the use of TLSV1.2, set the value
+ of this argument to True.
+ required: false
+ default: false
+ type: bool
+"""
+
+EXAMPLES = """
+- name: Enable NXAPI access with default configuration
+ cisco.nxos.nxos_nxapi:
+ state: present
+
+- name: Enable NXAPI with no HTTP, HTTPS at port 9443 and sandbox disabled
+ cisco.nxos.nxos_nxapi:
+ enable_http: false
+ https_port: 9443
+ https: yes
+ enable_sandbox: no
+
+- name: remove NXAPI configuration
+ cisco.nxos.nxos_nxapi:
+ state: absent
+"""
+
+RETURN = """
+updates:
+ description:
+ - Returns the list of commands that need to be pushed into the remote
+ device to satisfy the arguments
+ returned: always
+ type: list
+ sample: ['no feature nxapi']
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.utils.utils import Version
+
+
+def check_args(module, warnings, capabilities):
+ network_api = capabilities.get("network_api", "nxapi")
+ if network_api == "nxapi":
+ module.fail_json(msg="module not supported over nxapi transport")
+
+ os_platform = capabilities["device_info"]["network_os_platform"]
+ if "7K" not in os_platform and module.params["sandbox"]:
+ module.fail_json(
+ msg="sandbox or enable_sandbox is supported on NX-OS 7K series of switches",
+ )
+
+ state = module.params["state"]
+
+ if state == "started":
+ module.params["state"] = "present"
+ warnings.append(
+ "state=started is deprecated and will be removed in a "
+ "a future release. Please use state=present instead",
+ )
+ elif state == "stopped":
+ module.params["state"] = "absent"
+ warnings.append(
+ "state=stopped is deprecated and will be removed in a "
+ "a future release. Please use state=absent instead",
+ )
+
+ for key in ["http_port", "https_port"]:
+ if module.params[key] is not None:
+ if not 1 <= module.params[key] <= 65535:
+ module.fail_json(msg="%s must be between 1 and 65535" % key)
+
+ return warnings
+
+
+def map_obj_to_commands(want, have, module, warnings, capabilities):
+ send_commands = list()
+ commands = dict()
+ os_platform = None
+ os_version = None
+
+ device_info = capabilities.get("device_info")
+ if device_info:
+ os_version = device_info.get("network_os_version")
+ if os_version:
+ os_version = os_version[:3]
+ os_platform = device_info.get("network_os_platform")
+ if os_platform:
+ os_platform = os_platform[:3]
+
+ def needs_update(x):
+ return want.get(x) is not None and (want.get(x) != have.get(x))
+
+ if needs_update("state"):
+ if want["state"] == "absent":
+ return ["no feature nxapi"]
+ send_commands.append("feature nxapi")
+ elif want["state"] == "absent":
+ return send_commands
+
+ for parameter in ["http", "https"]:
+ port_param = parameter + "_port"
+ if needs_update(parameter):
+ if want.get(parameter) is False:
+ commands[parameter] = "no nxapi %s" % parameter
+ else:
+ commands[parameter] = "nxapi %s port %s" % (
+ parameter,
+ want.get(port_param),
+ )
+
+ if needs_update(port_param) and want.get(parameter) is True:
+ commands[parameter] = "nxapi %s port %s" % (
+ parameter,
+ want.get(port_param),
+ )
+
+ if needs_update("sandbox"):
+ commands["sandbox"] = "nxapi sandbox"
+ if not want["sandbox"]:
+ commands["sandbox"] = "no %s" % commands["sandbox"]
+
+ if os_platform and os_version:
+ if (os_platform == "N9K" or os_platform == "N3K") and Version(os_version) >= "9.2":
+ if needs_update("ssl_strong_ciphers"):
+ commands["ssl_strong_ciphers"] = "nxapi ssl ciphers weak"
+ if want["ssl_strong_ciphers"] is True:
+ commands["ssl_strong_ciphers"] = "no nxapi ssl ciphers weak"
+
+ have_ssl_protocols = ""
+ want_ssl_protocols = ""
+ for key, value in {
+ "tlsv1_2": "TLSv1.2",
+ "tlsv1_1": "TLSv1.1",
+ "tlsv1_0": "TLSv1",
+ }.items():
+ if needs_update(key):
+ if want.get(key) is True:
+ want_ssl_protocols = " ".join([want_ssl_protocols, value])
+ elif have.get(key) is True:
+ have_ssl_protocols = " ".join([have_ssl_protocols, value])
+
+ if len(want_ssl_protocols) > 0:
+ commands["ssl_protocols"] = "nxapi ssl protocols%s" % (
+ " ".join([want_ssl_protocols, have_ssl_protocols])
+ )
+ else:
+ warnings.append(
+ "os_version and/or os_platform keys from "
+ "platform capabilities are not available. "
+ "Any NXAPI SSL optional arguments will be ignored",
+ )
+
+ send_commands.extend(commands.values())
+
+ return send_commands
+
+
+def parse_http(data):
+ http_res = [r"nxapi http port (\d+)"]
+ http_port = None
+
+ for regex in http_res:
+ match = re.search(regex, data, re.M)
+ if match:
+ http_port = int(match.group(1))
+ break
+
+ return {"http": http_port is not None, "http_port": http_port}
+
+
+def parse_https(data):
+ https_res = [r"nxapi https port (\d+)"]
+ https_port = None
+
+ for regex in https_res:
+ match = re.search(regex, data, re.M)
+ if match:
+ https_port = int(match.group(1))
+ break
+
+ return {"https": https_port is not None, "https_port": https_port}
+
+
+def parse_sandbox(data):
+ sandbox = [item for item in data.split("\n") if re.search(r".*sandbox.*", item)]
+ value = False
+ if sandbox and sandbox[0] == "nxapi sandbox":
+ value = True
+ return {"sandbox": value}
+
+
+def parse_ssl_strong_ciphers(data):
+ ciphers_res = [r"(\w+) nxapi ssl ciphers weak"]
+ value = None
+
+ for regex in ciphers_res:
+ match = re.search(regex, data, re.M)
+ if match:
+ value = match.group(1)
+ break
+
+ return {"ssl_strong_ciphers": value == "no"}
+
+
+def parse_ssl_protocols(data):
+ tlsv1_0 = re.search(r"(?<!\S)TLSv1(?!\S)", data, re.M) is not None
+ tlsv1_1 = re.search(r"(?<!\S)TLSv1.1(?!\S)", data, re.M) is not None
+ tlsv1_2 = re.search(r"(?<!\S)TLSv1.2(?!\S)", data, re.M) is not None
+
+ return {"tlsv1_0": tlsv1_0, "tlsv1_1": tlsv1_1, "tlsv1_2": tlsv1_2}
+
+
+def map_config_to_obj(module):
+ out = run_commands(module, ["show run all | inc nxapi"], check_rc=False)[0]
+ match = re.search(r"no feature nxapi", out, re.M)
+ # There are two possible outcomes when nxapi is disabled on nxos platforms.
+ # 1. Nothing is displayed in the running config.
+ # 2. The 'no feature nxapi' command is displayed in the running config.
+ if match or out == "":
+ return {"state": "absent"}
+
+ out = str(out).strip()
+
+ obj = {"state": "present"}
+ obj.update(parse_http(out))
+ obj.update(parse_https(out))
+ obj.update(parse_sandbox(out))
+ obj.update(parse_ssl_strong_ciphers(out))
+ obj.update(parse_ssl_protocols(out))
+
+ return obj
+
+
+def map_params_to_obj(module):
+ obj = {
+ "http": module.params["http"],
+ "http_port": module.params["http_port"],
+ "https": module.params["https"],
+ "https_port": module.params["https_port"],
+ "sandbox": module.params["sandbox"],
+ "state": module.params["state"],
+ "ssl_strong_ciphers": module.params["ssl_strong_ciphers"],
+ "tlsv1_0": module.params["tlsv1_0"],
+ "tlsv1_1": module.params["tlsv1_1"],
+ "tlsv1_2": module.params["tlsv1_2"],
+ }
+
+ return obj
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ http=dict(aliases=["enable_http"], type="bool", default=True),
+ http_port=dict(type="int", default=80),
+ https=dict(aliases=["enable_https"], type="bool", default=False),
+ https_port=dict(type="int", default=443),
+ sandbox=dict(aliases=["enable_sandbox"], type="bool"),
+ state=dict(default="present", choices=["present", "absent"]),
+ ssl_strong_ciphers=dict(type="bool", default=False),
+ tlsv1_0=dict(type="bool", default=True),
+ tlsv1_1=dict(type="bool", default=False),
+ tlsv1_2=dict(type="bool", default=False),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ warning_msg = "Module nxos_nxapi currently defaults to configure 'http port 80'. "
+ warning_msg += "Default behavior is changing to configure 'https port 443'"
+ warning_msg += " when params 'http, http_port, https, https_port' are not set in the playbook"
+ module.deprecate(msg=warning_msg, date="2022-06-01", collection_name="cisco.nxos")
+
+ capabilities = get_capabilities(module)
+
+ check_args(module, warnings, capabilities)
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands(want, have, module, warnings, capabilities)
+
+ result = {"changed": False, "warnings": warnings, "commands": commands}
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ospf_interfaces.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospf_interfaces.py
new file mode 100644
index 00000000..c0a754c7
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospf_interfaces.py
@@ -0,0 +1,1455 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_ospf_interfaces
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_ospf_interfaces
+version_added: 1.3.0
+short_description: OSPF Interfaces Resource Module.
+description:
+- This module manages OSPF(v2/v3) configuration of interfaces on devices running Cisco NX-OS.
+notes:
+- Unsupported for Cisco MDS
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section "^interface").
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of OSPF configuration for interfaces.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name/Identifier of the interface.
+ type: str
+ required: True
+ address_family:
+ description:
+ - OSPF settings on the interfaces in address-family context.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - Address Family Identifier (AFI) for OSPF settings on the interfaces.
+ type: str
+ choices: ['ipv4', 'ipv6']
+ required: True
+ processes:
+ description:
+ - Interfaces configuration for an OSPF process.
+ type: list
+ elements: dict
+ suboptions:
+ process_id:
+ description:
+ - OSPF process tag.
+ type: str
+ required: True
+ area:
+ description:
+ - Area associated with interface.
+ type: dict
+ suboptions:
+ area_id:
+ description:
+ - Area ID in IP address format.
+ type: str
+ required: True
+ secondaries:
+ description:
+ - Do not include secondary IPv4/IPv6 addresses.
+ type: bool
+ multi_areas:
+ description:
+ - Multi-Areas associated with interface.
+ - Valid values are Area Ids as an integer or IP address.
+ type: list
+ elements: str
+ multi_areas:
+ description:
+ - Multi-Areas associated with interface (not tied to OSPF process).
+ - Valid values are Area Ids as an integer or IP address.
+ type: list
+ elements: str
+ authentication:
+ description:
+ - Authentication settings on the interface.
+ type: dict
+ suboptions:
+ key_chain:
+ description:
+ - Authentication password key-chain.
+ type: str
+ message_digest:
+ description:
+ - Use message-digest authentication.
+ type: bool
+ enable:
+ description:
+ - Enable/disable authentication on the interface.
+ type: bool
+ null_auth:
+ description:
+ - Use null(disable) authentication.
+ type: bool
+ authentication_key:
+ description:
+ - Configure the authentication key for the interface.
+ type: dict
+ suboptions:
+ encryption:
+ description:
+ - 0 Specifies an UNENCRYPTED authentication key will follow.
+ - 3 Specifies an 3DES ENCRYPTED authentication key will follow.
+ - 7 Specifies a Cisco type 7 ENCRYPTED authentication key will follow.
+ type: int
+ key:
+ description:
+ - Authentication key.
+ - Valid values are Cisco type 7 ENCRYPTED password, 3DES ENCRYPTED password
+ and UNENCRYPTED (cleartext) password based on the value of encryption key.
+ type: str
+ required: True
+ message_digest_key:
+ description:
+ - Message digest authentication password (key) settings.
+ type: dict
+ suboptions:
+ key_id:
+ description:
+ - Key ID.
+ type: int
+ required: True
+ encryption:
+ description:
+ - 0 Specifies an UNENCRYPTED ospf password (key) will follow.
+ - 3 Specifies an 3DES ENCRYPTED ospf password (key) will follow.
+ - 7 Specifies a Cisco type 7 ENCRYPTED the ospf password (key) will follow.
+ type: int
+ key:
+ description:
+ - Authentication key.
+ - Valid values are Cisco type 7 ENCRYPTED password, 3DES ENCRYPTED password
+ and UNENCRYPTED (cleartext) password based on the value of encryption key.
+ type: str
+ required: True
+ cost:
+ description:
+ - Cost associated with interface.
+ type: int
+ dead_interval:
+ description:
+ - Dead interval value (in seconds).
+ type: int
+ hello_interval:
+ description:
+ - Hello interval value (in seconds).
+ type: int
+ instance:
+ description:
+ - Instance identifier.
+ type: int
+ mtu_ignore:
+ description:
+ - Enable/disable OSPF MTU mismatch detection.
+ type: bool
+ network:
+ description:
+ - Network type.
+ type: str
+ choices: ["broadcast", "point-to-point"]
+ passive_interface:
+ description:
+ - Suppress routing updates on the interface.
+ - This option is mutually exclusive with I(default_passive_interface).
+ type: bool
+ default_passive_interface:
+ description:
+ - Set passive-interface attribute on this interface to default.
+ - This option is mutually exclusive with I(passive_interface).
+ type: bool
+ priority:
+ description:
+ - Router priority.
+ type: int
+ retransmit_interval:
+ description:
+ - Packet retransmission interval.
+ type: int
+ shutdown:
+ description:
+ - Shutdown OSPF on this interface.
+ type: bool
+ transmit_delay:
+ description:
+ - Packet transmission delay.
+ type: int
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - parsed
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# interface Ethernet1/2
+# no switchport
+# interface Ethernet1/3
+# no switchport
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_ospf_interfaces:
+ config:
+ - name: Ethernet1/1
+ address_family:
+ - afi: ipv4
+ processes:
+ - process_id: "100"
+ area:
+ area_id: 1.1.1.1
+ secondaries: False
+ multi_areas:
+ - 11.11.11.11
+ - afi: ipv6
+ processes:
+ - process_id: "200"
+ area:
+ area_id: 2.2.2.2
+ multi_areas:
+ - 21.0.0.0
+ - process_id: "300"
+ multi_areas:
+ - 50.50.50.50
+ multi_areas:
+ - 16.10.10.10
+ - name: Ethernet1/2
+ address_family:
+ - afi: ipv4
+ authentication:
+ enable: True
+ key_chain: test-1
+ message_digest_key:
+ key_id: 10
+ encryption: 3
+ key: abc01d272be25d29
+ cost: 100
+ - afi: ipv6
+ network: broadcast
+ shutdown: True
+ - name: Ethernet1/3
+ address_family:
+ - afi: ipv4
+ authentication_key:
+ encryption: 7
+ key: 12090404011C03162E
+ state: merged
+
+# Task output
+# -------------
+# "before": [
+# {
+# "name": "Ethernet1/1"
+# },
+# {
+# "name": "Ethernet1/2"
+# },
+# {
+# "name": "Ethernet1/3"
+# },
+# ]
+#
+# "commands": [
+# "interface Ethernet1/1",
+# "ip router ospf multi-area 11.11.11.11",
+# "ip router ospf 100 area 1.1.1.1 secondaries none",
+# "ipv6 router ospfv3 multi-area 16.10.10.10",
+# "ipv6 router ospfv3 200 area 2.2.2.2",
+# "ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "ipv6 router ospfv3 300 multi-area 50.50.50.50",
+# "interface Ethernet1/2",
+# "ip ospf authentication key-chain test-1",
+# "ip ospf authentication",
+# "ip ospf message-digest-key 10 md5 3 abc01d272be25d29",
+# "ip ospf cost 100",
+# "ospfv3 network broadcast",
+# "ospfv3 shutdown",
+# "interface Ethernet1/3",
+# "ip ospf authentication-key 7 12090404011C03162E"
+# ]
+#
+# "after": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+
+# After state:
+# -------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+
+# Using replaced
+
+# Before state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+- name: Replace OSPF configurations of listed interfaces with provided configurations
+ cisco.nxos.nxos_ospf_interfaces:
+ config:
+ - name: Ethernet1/1
+ address_family:
+ - afi: ipv4
+ processes:
+ - process_id: "100"
+ area:
+ area_id: 1.1.1.1
+ secondaries: False
+ multi_areas:
+ - 11.11.11.12
+ - name: Ethernet1/3
+ state: replaced
+
+# Task output
+# -------------
+# "before": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+#
+# "commands": [
+# "interface Ethernet1/1",
+# "ip router ospf multi-area 11.11.11.12",
+# "no ip router ospf multi-area 11.11.11.11",
+# "no ipv6 router ospfv3 multi-area 16.10.10.10",
+# "no ipv6 router ospfv3 200 area 2.2.2.2",
+# "no ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "no ipv6 router ospfv3 300 multi-area 50.50.50.50",
+# "interface Ethernet1/3",
+# "no ip ospf authentication-key 7 12090404011C03162E"
+# ]
+#
+# "after": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.12"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "name": "Ethernet1/3"
+# },
+#
+# After state:
+# -------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.12
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+
+
+# Using overridden
+
+# Before state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+- name: Override all OSPF interfaces configuration with provided configuration
+ cisco.nxos.nxos_ospf_interfaces:
+ config:
+ - name: Ethernet1/1
+ address_family:
+ - afi: ipv4
+ processes:
+ - process_id: "100"
+ area:
+ area_id: 1.1.1.1
+ secondaries: False
+ multi_areas:
+ - 11.11.11.12
+ state: overridden
+
+# Task output
+# -------------
+# "before": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+#
+# "commands": [
+# "interface Ethernet1/2",
+# "no ip ospf authentication key-chain test-1",
+# "no ip ospf authentication",
+# "no ip ospf message-digest-key 10 md5 3 abc01d272be25d29",
+# "no ip ospf cost 100",
+# "no ospfv3 network broadcast",
+# "no ospfv3 shutdown",
+# "interface Ethernet1/3",
+# "no ip ospf authentication-key 7 12090404011C03162E",
+# "interface Ethernet1/1",
+# "ip router ospf multi-area 11.11.11.12",
+# "no ip router ospf multi-area 11.11.11.11",
+# "no ipv6 router ospfv3 multi-area 16.10.10.10",
+# "no ipv6 router ospfv3 200 area 2.2.2.2",
+# "no ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "no ipv6 router ospfv3 300 multi-area 50.50.50.50"
+# ]
+#
+# "after": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.12"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "name": "Ethernet1/2"
+# },
+# {
+# "name": "Ethernet1/3"
+# },
+# ]
+
+# After state:
+# -------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.12
+# interface Ethernet1/2
+# no switchport
+# interface Ethernet1/3
+# no switchport
+
+# Using deleted to delete OSPF config of a single interface
+
+# Before state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+- name: Delete OSPF config from a single interface
+ cisco.nxos.nxos_ospf_interfaces:
+ config:
+ - name: Ethernet1/1
+ state: deleted
+
+# Task output
+# -------------
+# "before": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+#
+# "commands": [
+# "interface Ethernet1/1",
+# "no ip router ospf multi-area 11.11.11.11",
+# "no ip router ospf 100 area 1.1.1.1 secondaries none",
+# "no ipv6 router ospfv3 multi-area 16.10.10.10",
+# "no ipv6 router ospfv3 200 area 2.2.2.2",
+# "no ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "no ipv6 router ospfv3 300 multi-area 50.50.50.50"
+# ]
+#
+# "before": [
+# {
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+
+# After state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+# Using deleted to delete OSPF config from all interfaces
+
+# Before state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+# ip ospf authentication-key 7 12090404011C03162E
+
+- name: Delete OSPF config from all interfaces
+ cisco.nxos.nxos_ospf_interfaces:
+ state: deleted
+
+# Task output
+# -------------
+# "before": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+#
+# "commands": [
+# "interface Ethernet1/1",
+# "no ip router ospf multi-area 11.11.11.11",
+# "no ip router ospf 100 area 1.1.1.1 secondaries none",
+# "no ipv6 router ospfv3 multi-area 16.10.10.10",
+# "no ipv6 router ospfv3 200 area 2.2.2.2",
+# "no ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "no ipv6 router ospfv3 300 multi-area 50.50.50.50",
+# "interface Ethernet1/2",
+# "no ip ospf authentication key-chain test-1",
+# "no ip ospf authentication",
+# "no ip ospf message-digest-key 10 md5 3 abc01d272be25d29",
+# "no ip ospf cost 100",
+# "no ospfv3 network broadcast",
+# "no ospfv3 shutdown",
+# "interface Ethernet1/3",
+# "no ip ospf authentication-key 7 12090404011C03162E"
+# ]
+#
+# "after": [
+# {
+# "name": "Ethernet1/1"
+# },
+# {
+# "name": "Ethernet1/2"
+# },
+# {
+# "name": "Ethernet1/3"
+# },
+# ]
+
+# After state:
+# ------------
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# interface Ethernet1/2
+# no switchport
+# interface Ethernet1/3
+# no switchport
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_ospf_interfaces:
+ config:
+ - name: Ethernet1/1
+ address_family:
+ - afi: ipv4
+ processes:
+ - process_id: "100"
+ area:
+ area_id: 1.1.1.1
+ secondaries: False
+ multi_areas:
+ - 11.11.11.11
+ - afi: ipv6
+ processes:
+ - process_id: "200"
+ area:
+ area_id: 2.2.2.2
+ multi_areas:
+ - 21.0.0.0
+ - process_id: "300"
+ multi_areas:
+ - 50.50.50.50
+ multi_areas:
+ - 16.10.10.10
+ - name: Ethernet1/2
+ address_family:
+ - afi: ipv4
+ authentication:
+ enable: True
+ key_chain: test-1
+ message_digest_key:
+ key_id: 10
+ encryption: 3
+ key: abc01d272be25d29
+ cost: 100
+ - afi: ipv6
+ network: broadcast
+ shutdown: True
+ - name: Ethernet1/3
+ address_family:
+ - afi: ipv4
+ authentication_key:
+ encryption: 7
+ key: 12090404011C03162E
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# "rendered": [
+# "interface Ethernet1/1",
+# "ip router ospf multi-area 11.11.11.11",
+# "ip router ospf 100 area 1.1.1.1 secondaries none",
+# "ipv6 router ospfv3 multi-area 16.10.10.10",
+# "ipv6 router ospfv3 200 area 2.2.2.2",
+# "ipv6 router ospfv3 200 multi-area 21.0.0.0",
+# "ipv6 router ospfv3 300 multi-area 50.50.50.50",
+# "interface Ethernet1/2",
+# "ip ospf authentication key-chain test-1",
+# "ip ospf authentication",
+# "ip ospf message-digest-key 10 md5 3 abc01d272be25d29",
+# "ip ospf cost 100",
+# "ospfv3 network broadcast",
+# "ospfv3 shutdown",
+# "interface Ethernet1/3",
+# "ip ospf authentication-key 7 12090404011C03162E"
+# ]
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# interface Ethernet1/1
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.11
+# ipv6 router ospfv3 200 area 2.2.2.2
+# ipv6 router ospfv3 200 multi-area 21.0.0.0
+# ipv6 router ospfv3 300 multi-area 50.50.50.50
+# ipv6 router ospfv3 multi-area 16.10.10.10
+# interface Ethernet1/2
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# ip ospf authentication-key 7 12090404011C03162E
+
+- name: arse externally provided OSPF interfaces config
+ cisco.nxos.nxos_ospf_interfaces:
+ running_config: "{{ lookup('file', 'ospf_interfaces.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# "parsed": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.11"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# },
+# {
+# "afi": "ipv6",
+# "multi_areas": [
+# "16.10.10.10"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "2.2.2.2"
+# },
+# "multi_areas": [
+# "21.0.0.0"
+# ],
+# "process_id": "200"
+# },
+# {
+# "multi_areas": [
+# "50.50.50.50"
+# ],
+# "process_id": "300"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication_key": {
+# "encryption": 7,
+# "key": "12090404011C03162E"
+# }
+# }
+# ],
+# "name": "Ethernet1/3"
+# },
+# ]
+
+# Using gathered
+
+# On-box config
+
+# NXOS# show running-config | section ^interface
+# interface Ethernet1/1
+# no switchport
+# ip router ospf 100 area 1.1.1.1 secondaries none
+# ip router ospf multi-area 11.11.11.12
+# interface Ethernet1/2
+# no switchport
+# ip ospf authentication
+# ip ospf authentication key-chain test-1
+# ip ospf message-digest-key 10 md5 3 abc01d272be25d29
+# ip ospf cost 100
+# ospfv3 network broadcast
+# ospfv3 shutdown
+# interface Ethernet1/3
+# no switchport
+
+# Task output (redacted)
+# -----------------------
+# "gathered": [
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "multi_areas": [
+# "11.11.11.12"
+# ],
+# "processes": [
+# {
+# "area": {
+# "area_id": "1.1.1.1",
+# "secondaries": false
+# },
+# "process_id": "100"
+# }
+# ]
+# }
+# ],
+# "name": "Ethernet1/1"
+# },
+# {
+# "address_family": [
+# {
+# "afi": "ipv4",
+# "authentication": {
+# "enable": true,
+# "key_chain": "test-1"
+# },
+# "cost": 100,
+# "message_digest_key": {
+# "encryption": 3,
+# "key": "abc01d272be25d29",
+# "key_id": 10
+# }
+# },
+# {
+# "afi": "ipv6",
+# "network": "broadcast",
+# "shutdown": true
+# }
+# ],
+# "name": "Ethernet1/2"
+# },
+# {
+# "name": "Ethernet1/3"
+# },
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - interface Ethernet1/1
+ - ip router ospf multi-area 11.11.11.11
+ - ip router ospf 100 area 1.1.1.1 secondaries none
+ - no ipv6 router ospfv3 multi-area 16.10.10.10
+ - ipv6 router ospfv3 200 area 2.2.2.2
+ - ipv6 router ospfv3 200 multi-area 21.0.0.0
+ - ipv6 router ospfv3 300 multi-area 50.50.50.50
+ - interface Ethernet1/2
+ - no ip ospf authentication key-chain test-1
+ - ip ospf authentication
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospf_interfaces.ospf_interfaces import (
+ Ospf_interfacesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ospf_interfaces.ospf_interfaces import (
+ Ospf_interfaces,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Ospf_interfacesArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Ospf_interfaces(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv2.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv2.py
new file mode 100644
index 00000000..174640a1
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv2.py
@@ -0,0 +1,1984 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_ospfv2
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_ospfv2
+short_description: OSPFv2 resource module
+description:
+- This module manages OSPFv2 configuration on devices running Cisco NX-OS.
+version_added: 1.0.0
+notes:
+- Tested against NX-OS 7.0(3)I5(1).
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section "^router ospf .*").
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of OSPF process configuration.
+ type: dict
+ suboptions:
+ processes:
+ description:
+ - A list of OSPF instances' configurations.
+ type: list
+ elements: dict
+ suboptions:
+ areas:
+ description:
+ - Configure properties of OSPF Areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - The Area ID in IP Address format.
+ type: str
+ required: true
+ authentication:
+ description:
+ - Authentication settings for the Area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set authentication for the area.
+ type: bool
+ message_digest:
+ description:
+ - Use message-digest authentication.
+ type: bool
+ default_cost:
+ description:
+ - Specify the default cost for default summary LSA.
+ type: int
+ filter_list:
+ description:
+ - Filter prefixes between OSPF areas.
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description:
+ - The Route-map name.
+ type: str
+ required: true
+ direction:
+ description:
+ - The direction to apply the route map.
+ type: str
+ choices: [in, out]
+ required: true
+ nssa:
+ description:
+ - NSSA settings for the area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure area as NSSA.
+ type: bool
+ default_information_originate:
+ description:
+ - Originate Type-7 default LSA into NSSA area.
+ type: bool
+ no_redistribution:
+ description:
+ - Do not send redistributed LSAs into NSSA area.
+ type: bool
+ no_summary:
+ description:
+ - Do not send summary LSAs into NSSA area.
+ type: bool
+ translate:
+ description:
+ - Translate LSA.
+ type: dict
+ suboptions:
+ type7:
+ description:
+ - Translate from Type 7 to Type 5.
+ type: dict
+ suboptions:
+ always:
+ description:
+ - Always translate LSAs
+ type: bool
+ never:
+ description:
+ - Never translate LSAs
+ type: bool
+ supress_fa:
+ description:
+ - Suppress forwarding address in translated LSAs.
+ type: bool
+ ranges:
+ description:
+ - Configure an address range for the area.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IP in Prefix format (x.x.x.x/len)
+ type: str
+ required: true
+ cost:
+ description:
+ - Cost to use for the range.
+ type: int
+ not_advertise:
+ description:
+ - Suppress advertising the specified range.
+ type: bool
+ stub:
+ description:
+ - Settings for configuring the area as a stub.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure the area as a stub.
+ type: bool
+ no_summary:
+ description:
+ - Prevent ABR from sending summary LSAs into stub area.
+ type: bool
+ auto_cost:
+ description:
+ - Calculate OSPF cost according to bandwidth.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description:
+ - Reference bandwidth used to assign OSPF cost.
+ type: int
+ required: true
+ unit:
+ description:
+ - Specify in which unit the reference bandwidth is specified.
+ type: str
+ required: true
+ choices: [Gbps, Mbps]
+ bfd:
+ description:
+ - Enable BFD on all OSPF interfaces.
+ type: bool
+ default_information:
+ description:
+ - Control distribution of default routes.
+ type: dict
+ suboptions:
+ originate:
+ description:
+ - Distribute a default route.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable distribution of default route.
+ type: bool
+ always:
+ description:
+ - Always advertise a default route.
+ type: bool
+ route_map:
+ description:
+ - Policy to control distribution of default routes
+ type: str
+ default_metric:
+ description:
+ - Specify default metric for redistributed routes.
+ type: int
+ distance:
+ description:
+ - Configure the OSPF administrative distance.
+ type: int
+ flush_routes:
+ description:
+ - Flush routes on a non-graceful controlled restart.
+ type: bool
+ graceful_restart:
+ description:
+ - Configure graceful restart.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable graceful-restart.
+ type: bool
+ grace_period:
+ description:
+ - Configure maximum interval to restart gracefully.
+ type: int
+ helper_disable:
+ description:
+ - Enable/Disable helper mode.
+ type: bool
+ isolate:
+ description:
+ - Isolate this router from OSPF perspective.
+ type: bool
+ log_adjacency_changes:
+ description:
+ - Log changes in adjacency state.
+ type: dict
+ suboptions:
+ log:
+ description:
+ - Enable/disable logging changes in adjacency state.
+ type: bool
+ detail:
+ description:
+ - Notify all state changes.
+ type: bool
+ max_lsa:
+ description:
+ - Feature to limit the number of non-self-originated LSAs.
+ type: dict
+ suboptions:
+ max_non_self_generated_lsa:
+ description:
+ - Set the maximum number of non self-generated LSAs.
+ type: int
+ required: true
+ threshold:
+ description:
+ - Threshold value (%) at which to generate a warning message.
+ type: int
+ ignore_count:
+ description:
+ - Set count on how many times adjacencies can be suppressed.
+ type: int
+ ignore_time:
+ description:
+ - Set time during which all adjacencies are suppressed.
+ type: int
+ reset_time:
+ description:
+ - Set number of minutes after which ignore-count is reset to zero.
+ type: int
+ warning_only:
+ description:
+ - Log a warning message when limit is exceeded.
+ type: bool
+ max_metric:
+ description:
+ - Maximize the cost metric.
+ type: dict
+ suboptions:
+ router_lsa:
+ description:
+ - Router LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set router-lsa attribute.
+ type: bool
+ external_lsa:
+ description:
+ - External LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ include_stub:
+ description:
+ - Advertise Max metric for Stub links as well.
+ type: bool
+ on_startup:
+ description:
+ - Effective only at startup.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set on-startup attribute.
+ type: bool
+ wait_period:
+ description:
+ - Wait period in seconds after startup.
+ type: int
+ wait_for_bgp_asn:
+ description:
+ - ASN of BGP to wait for.
+ type: int
+ summary_lsa:
+ description:
+ - Summary LSAs configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set summary-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Max metric value for summary LSAs.
+ type: int
+ maximum_paths:
+ description:
+ - Maximum paths per destination.
+ type: int
+ mpls:
+ description:
+ - OSPF MPLS configuration settings.
+ type: dict
+ suboptions:
+ traffic_eng:
+ description:
+ - OSPF MPLS Traffic Engineering commands.
+ type: dict
+ suboptions:
+ areas:
+ description:
+ - List of Area IDs.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - Area Id in ip address format.
+ type: str
+ multicast_intact:
+ description:
+ - MPLS TE multicast support.
+ type: bool
+ router_id:
+ description:
+ - Router ID associated with TE.
+ type: str
+ name_lookup:
+ description:
+ - Display OSPF router ids as DNS names.
+ type: bool
+ passive_interface:
+ description:
+ - Suppress routing updates on the interface.
+ type: dict
+ suboptions:
+ default:
+ description:
+ - Interfaces passive by default.
+ type: bool
+ process_id:
+ description:
+ - The OSPF process tag.
+ type: str
+ required: true
+ redistribute:
+ description:
+ - Redistribute information from another routing protocol.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - The name of the protocol.
+ type: str
+ choices: [bgp, direct, eigrp, isis, lisp, ospf, rip, static]
+ required: true
+ id:
+ description:
+ - The identifier for the protocol specified.
+ type: str
+ route_map:
+ description:
+ - The route map policy to constrain redistribution.
+ type: str
+ required: true
+ rfc1583compatibility:
+ description:
+ - Configure 1583 compatibility for external path preferences.
+ type: bool
+ router_id:
+ description:
+ - Set OSPF process router-id.
+ type: str
+ shutdown:
+ description:
+ - Shutdown the OSPF protocol instance.
+ type: bool
+ summary_address:
+ description:
+ - Configure route summarization for redistribution.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IP prefix in format x.x.x.x/ml.
+ type: str
+ required: true
+ not_advertise:
+ description:
+ - Suppress advertising the specified summary.
+ type: bool
+ tag:
+ description:
+ - A 32-bit tag value.
+ type: int
+ table_map:
+ description:
+ - Policy for filtering/modifying OSPF routes before sending them to RIB.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The Route Map name.
+ type: str
+ required: true
+ filter:
+ description:
+ - Block the OSPF routes from being sent to RIB.
+ type: bool
+ timers:
+ description:
+ - Configure timer related constants.
+ type: dict
+ suboptions:
+ lsa_arrival:
+ description:
+ - Mimimum interval between arrival of a LSA.
+ type: int
+ lsa_group_pacing:
+ description:
+ - LSA group refresh/maxage interval.
+ type: int
+ throttle:
+ description:
+ - Configure throttle related constants.
+ type: dict
+ suboptions:
+ lsa:
+ description:
+ - Set rate-limiting for LSA generation.
+ type: dict
+ suboptions:
+ start_interval:
+ description:
+ - The start interval.
+ type: int
+ hold_interval:
+ description:
+ - The hold interval.
+ type: int
+ max_interval:
+ description:
+ - The max interval.
+ type: int
+ spf:
+ description:
+ - Set OSPF SPF timers.
+ type: dict
+ suboptions:
+ initial_spf_delay:
+ description:
+ - Initial SPF schedule delay in milliseconds.
+ type: int
+ min_hold_time:
+ description:
+ - Minimum hold time between SPF calculations.
+ type: int
+ max_wait_time:
+ description:
+ - Maximum wait time between SPF calculations.
+ type: int
+ vrfs:
+ description:
+ - Configure VRF specific OSPF settings.
+ type: list
+ elements: dict
+ suboptions:
+ areas:
+ description:
+ - Configure properties of OSPF Areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - The Area ID in IP Address format.
+ type: str
+ required: true
+ authentication:
+ description:
+ - Authentication settings for the Area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set authentication for the area.
+ type: bool
+ message_digest:
+ description:
+ - Use message-digest authentication.
+ type: bool
+ default_cost:
+ description:
+ - Specify the default cost for default summary LSA.
+ type: int
+ filter_list:
+ description:
+ - Filter prefixes between OSPF areas.
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description:
+ - The Route-map name.
+ type: str
+ required: true
+ direction:
+ description:
+ - The direction to apply the route map.
+ type: str
+ choices: [in, out]
+ required: true
+ nssa:
+ description:
+ - NSSA settings for the area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure area as NSSA.
+ type: bool
+ default_information_originate:
+ description:
+ - Originate Type-7 default LSA into NSSA area.
+ type: bool
+ no_redistribution:
+ description:
+ - Do not send redistributed LSAs into NSSA area.
+ type: bool
+ no_summary:
+ description:
+ - Do not send summary LSAs into NSSA area.
+ type: bool
+ translate:
+ description:
+ - Translate LSA.
+ type: dict
+ suboptions:
+ type7:
+ description:
+ - Translate from Type 7 to Type 5.
+ type: dict
+ suboptions:
+ always:
+ description:
+ - Always translate LSAs
+ type: bool
+ never:
+ description:
+ - Never translate LSAs
+ type: bool
+ supress_fa:
+ description:
+ - Suppress forwarding address in translated LSAs.
+ type: bool
+ ranges:
+ description:
+ - Configure an address range for the area.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IP in Prefix format (x.x.x.x/len)
+ type: str
+ required: true
+ cost:
+ description:
+ - Cost to use for the range.
+ type: int
+ not_advertise:
+ description:
+ - Suppress advertising the specified range.
+ type: bool
+ stub:
+ description:
+ - Settings for configuring the area as a stub.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure the area as a stub.
+ type: bool
+ no_summary:
+ description:
+ - Prevent ABR from sending summary LSAs into stub area.
+ type: bool
+ auto_cost:
+ description:
+ - Calculate OSPF cost according to bandwidth.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description:
+ - Reference bandwidth used to assign OSPF cost.
+ type: int
+ required: true
+ unit:
+ description:
+ - Specify in which unit the reference bandwidth is specified.
+ type: str
+ required: True
+ choices: [Gbps, Mbps]
+ bfd:
+ description:
+ - Enable BFD on all OSPF interfaces.
+ type: bool
+ default_information:
+ description:
+ - Control distribution of default routes.
+ type: dict
+ suboptions:
+ originate:
+ description:
+ - Distribute a default route.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable distribution of default route.
+ type: bool
+ always:
+ description:
+ - Always advertise a default route.
+ type: bool
+ route_map:
+ description:
+ - Policy to control distribution of default routes
+ type: str
+ default_metric:
+ description:
+ - Specify default metric for redistributed routes.
+ type: int
+ distance:
+ description:
+ - Configure the OSPF administrative distance.
+ type: int
+ down_bit_ignore:
+ description:
+ - Configure a PE router to ignore the DN bit for network summary,
+ external and NSSA external LSA.
+ type: bool
+ capability:
+ description:
+ - OSPF capability settings.
+ type: dict
+ suboptions:
+ vrf_lite:
+ description:
+ - Enable VRF-lite capability settings.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable VRF-lite support.
+ type: bool
+ evpn:
+ description:
+ - Ethernet VPN.
+ type: bool
+ graceful_restart:
+ description:
+ - Configure graceful restart.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable graceful-restart.
+ type: bool
+ grace_period:
+ description:
+ - Configure maximum interval to restart gracefully.
+ type: int
+ helper_disable:
+ description:
+ - Enable/Disable helper mode.
+ type: bool
+ log_adjacency_changes:
+ description:
+ - Log changes in adjacency state.
+ type: dict
+ suboptions:
+ log:
+ description:
+ - Enable/disable logging changes in adjacency state.
+ type: bool
+ detail:
+ description:
+ - Notify all state changes.
+ type: bool
+ max_lsa:
+ description:
+ - Feature to limit the number of non-self-originated LSAs.
+ type: dict
+ suboptions:
+ max_non_self_generated_lsa:
+ description:
+ - Set the maximum number of non self-generated LSAs.
+ type: int
+ required: true
+ threshold:
+ description:
+ - Threshold value (%) at which to generate a warning message.
+ type: int
+ ignore_count:
+ description:
+ - Set count on how many times adjacencies can be suppressed.
+ type: int
+ ignore_time:
+ description:
+ - Set time during which all adjacencies are suppressed.
+ type: int
+ reset_time:
+ description:
+ - Set number of minutes after which ignore-count is reset to zero.
+ type: int
+ warning_only:
+ description:
+ - Log a warning message when limit is exceeded.
+ type: bool
+ max_metric:
+ description:
+ - Maximize the cost metric.
+ type: dict
+ suboptions:
+ router_lsa:
+ description:
+ - Router LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set router-lsa attribute.
+ type: bool
+ external_lsa:
+ description:
+ - External LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ include_stub:
+ description:
+ - Advertise Max metric for Stub links as well.
+ type: bool
+ on_startup:
+ description:
+ - Effective only at startup.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set on-startup attribute.
+ type: bool
+ wait_period:
+ description:
+ - Wait period in seconds after startup.
+ type: int
+ wait_for_bgp_asn:
+ description:
+ - ASN of BGP to wait for.
+ type: int
+ summary_lsa:
+ description:
+ - Summary LSAs configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set summary-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Max metric value for summary LSAs.
+ type: int
+ maximum_paths:
+ description:
+ - Maximum paths per destination.
+ type: int
+ name_lookup:
+ description:
+ - Display OSPF router ids as DNS names.
+ type: bool
+ passive_interface:
+ description:
+ - Suppress routing updates on the interface.
+ type: dict
+ suboptions:
+ default:
+ description:
+ - Interfaces passive by default.
+ type: bool
+ redistribute:
+ description:
+ - Redistribute information from another routing protocol.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - The name of the protocol.
+ type: str
+ choices: [bgp, direct, eigrp, isis, lisp, ospf, rip, static]
+ required: true
+ id:
+ description:
+ - The identifier for the protocol specified.
+ type: str
+ route_map:
+ description:
+ - The route map policy to constrain redistribution.
+ type: str
+ required: true
+ rfc1583compatibility:
+ description:
+ - Configure 1583 compatibility for external path preferences.
+ type: bool
+ router_id:
+ description:
+ - Set OSPF process router-id.
+ type: str
+ shutdown:
+ description:
+ - Shutdown the OSPF protocol instance.
+ type: bool
+ summary_address:
+ description:
+ - Configure route summarization for redistribution.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IP prefix in format x.x.x.x/ml.
+ type: str
+ required: true
+ not_advertise:
+ description:
+ - Suppress advertising the specified summary.
+ type: bool
+ tag:
+ description:
+ - A 32-bit tag value.
+ type: int
+ table_map:
+ description:
+ - Policy for filtering/modifying OSPF routes before sending them to
+ RIB.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The Route Map name.
+ type: str
+ required: true
+ filter:
+ description:
+ - Block the OSPF routes from being sent to RIB.
+ type: bool
+ timers:
+ description:
+ - Configure timer related constants.
+ type: dict
+ suboptions:
+ lsa_arrival:
+ description:
+ - Mimimum interval between arrival of a LSA.
+ type: int
+ lsa_group_pacing:
+ description:
+ - LSA group refresh/maxage interval.
+ type: int
+ throttle:
+ description:
+ - Configure throttle related constants.
+ type: dict
+ suboptions:
+ lsa:
+ description:
+ - Set rate-limiting for LSA generation.
+ type: dict
+ suboptions:
+ start_interval:
+ description:
+ - The start interval.
+ type: int
+ hold_interval:
+ description:
+ - The hold interval.
+ type: int
+ max_interval:
+ description:
+ - The max interval.
+ type: int
+ spf:
+ description:
+ - Set OSPF SPF timers.
+ type: dict
+ suboptions:
+ initial_spf_delay:
+ description:
+ - Initial SPF schedule delay in milliseconds.
+ type: int
+ min_hold_time:
+ description:
+ - Minimum hold time between SPF calculations.
+ type: int
+ max_wait_time:
+ description:
+ - Maximum wait time between SPF calculations.
+ type: int
+ vrf:
+ description:
+ - Name/Identifier of the VRF.
+ type: str
+ required: true
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - parsed
+ - rendered
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_ospfv2:
+ config:
+ processes:
+ - process_id: 100
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_1
+ direction: in
+ - route_map: rmap_2
+ direction: out
+ ranges:
+ - prefix: 198.51.100.64/27
+ not_advertise: true
+ - prefix: 198.51.100.96/27
+ cost: 120
+ - area_id: 0.0.0.101
+ authentication:
+ message_digest: true
+ redistribute:
+ - protocol: eigrp
+ id: 120
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ redistribute:
+ - protocol: static
+ route_map: zone1-static-connect
+ summary_address:
+ - prefix: 198.51.100.128/27
+ tag: 121
+ - prefix: 198.51.100.160/27
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: true
+ no_summary: true
+ - area_id: 0.0.0.103
+ nssa:
+ no_summary: true
+ translate:
+ type7:
+ always: true
+ - vrf: zone2
+ auto_cost:
+ reference_bandwidth: 45
+ unit: Gbps
+ state: merged
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - router ospf 102
+# - router-id 198.51.100.1
+# - redistribute eigrp 120 route-map rmap_1
+# - redistribute direct route-map ospf102-direct-connect
+# - area 0.0.0.100 filter-list route-map rmap_1 in
+# - area 0.0.0.100 filter-list route-map rmap_2 out
+# - area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# - area 0.0.0.100 range 198.51.100.96/27 cost 120
+# - area 0.0.0.101 authentication message-digest
+# - vrf zone1
+# - router-id 198.51.100.129
+# - summary-address 198.51.100.128/27 tag 121
+# - summary-address 198.51.100.160/27
+# - redistribute static route-map zone1-static-connect
+# - area 0.0.0.102 nssa no-summary default-information-originate
+# - area 0.0.0.103 nssa no-summary
+# - area 0.0.0.103 nssa translate type7 always
+# - vrf zone2
+# - auto-cost reference-bandwidth 45 Gbps
+# - router ospf 100
+# - router-id 203.0.113.20
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# - cost: 120
+# prefix: 198.51.100.96/27
+# - area_id: 0.0.0.101
+# authentication:
+# message_digest: true
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-static-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# area 0.0.0.100 range 198.51.100.96/27 cost 120
+# area 0.0.0.101 authentication message-digest
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# redistribute static route-map zone1-static-connect
+# summary-address 198.51.100.128/27 tag 121
+# summary-address 198.51.100.160/27
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# area 0.0.0.100 range 198.51.100.96/27 cost 120
+# area 0.0.0.101 authentication message-digest
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# redistribute static route-map zone1-static-connect
+# summary-address 198.51.100.128/27 tag 121
+# summary-address 198.51.100.160/27
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Replace device configurations of listed OSPF processes with provided configurations
+ cisco.nxos.nxos_ospfv2:
+ config:
+ processes:
+ - process_id: 102
+ router_id: 198.51.100.1
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_8
+ direction: in
+ ranges:
+ - prefix: 198.51.100.64/27
+ not_advertise: true
+ - area_id: 0.0.0.101
+ stub:
+ no_summary: true
+ redistribute:
+ - protocol: eigrp
+ id: 130
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ redistribute:
+ - protocol: bgp
+ id: 65563
+ route_map: zone1-bgp-connect
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: true
+ no_summary: true
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# - cost: 120
+# prefix: 198.51.100.96/27
+# - area_id: 0.0.0.101
+# authentication:
+# message_digest: true
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-static-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - router ospf 102
+# - redistribute eigrp 130 route-map rmap_1
+# - no redistribute eigrp 120 route-map rmap_1
+# - area 0.0.0.100 filter-list route-map rmap_8 in
+# - no area 0.0.0.100 filter-list route-map rmap_2 out
+# - no area 0.0.0.100 range 198.51.100.96/27
+# - no area 0.0.0.101 authentication
+# - area 0.0.0.101 stub no-summary
+# - vrf zone1
+# - no summary-address 198.51.100.128/27 tag 121
+# - no summary-address 198.51.100.160/27
+# - redistribute bgp 65563 route-map zone1-bgp-connect
+# - no redistribute static route-map zone1-static-connect
+# - no area 0.0.0.103 nssa
+# - no area 0.0.0.103 nssa translate type7 always
+# - no vrf zone2
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.101
+# stub:
+# no_summary: true
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: in
+# route_map: rmap_8
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "130"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# redistribute:
+# - id: "65563"
+# protocol: bgp
+# route_map: zone1-bgp-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# area 0.0.0.101 stub no-summary
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 130 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_8 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# redistribute bgp 65563 route-map zone1-bgp-connect
+
+# Using overridden
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# area 0.0.0.100 range 198.51.100.96/27 cost 120
+# area 0.0.0.101 authentication message-digest
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# redistribute static route-map zone1-static-connect
+# summary-address 198.51.100.128/27 tag 121
+# summary-address 198.51.100.160/27
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Override all OSPF configuration with provided configuration
+ cisco.nxos.nxos_ospfv2:
+ config:
+ processes:
+ - process_id: 104
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ shutdown: true
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# - cost: 120
+# prefix: 198.51.100.96/27
+# - area_id: 0.0.0.101
+# authentication:
+# message_digest: true
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-static-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospf 100
+# - router ospf 104
+# - router-id 203.0.113.20
+# - router ospf 102
+# - shutdown
+# - no redistribute direct route-map ospf102-direct-connect
+# - no redistribute eigrp 120 route-map rmap_1
+# - no area 0.0.0.100 filter-list route-map rmap_2 out
+# - no area 0.0.0.100 filter-list route-map rmap_1 in
+# - no area 0.0.0.100 range 198.51.100.64/27
+# - no area 0.0.0.100 range 198.51.100.96/27
+# - no area 0.0.0.101 authentication
+# - no vrf zone1
+# - no vrf zone2
+#
+# after:
+# processes:
+# - process_id: "102"
+# router_id: 198.51.100.1
+# shutdown: true
+# - process_id: "104"
+# router_id: 203.0.113.20
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 102
+# router-id 198.51.100.1
+# shutdown
+# router ospf 104
+# router-id 203.0.113.20
+
+# Using deleted to delete a single OSPF process
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# area 0.0.0.100 range 198.51.100.96/27 cost 120
+# area 0.0.0.101 authentication message-digest
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# redistribute static route-map zone1-static-connect
+# summary-address 198.51.100.128/27 tag 121
+# summary-address 198.51.100.160/27
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Delete a single OSPF process
+ cisco.nxos.nxos_ospfv2:
+ config:
+ processes:
+ - process_id: 102
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# - cost: 120
+# prefix: 198.51.100.96/27
+# - area_id: 0.0.0.101
+# authentication:
+# message_digest: true
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-static-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospf 102
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+
+# Using deleted all OSPF processes from the device
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospf 100
+# router-id 203.0.113.20
+# router ospf 102
+# router-id 198.51.100.1
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# area 0.0.0.100 range 198.51.100.96/27 cost 120
+# area 0.0.0.101 authentication message-digest
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# redistribute static route-map zone1-static-connect
+# summary-address 198.51.100.128/27 tag 121
+# summary-address 198.51.100.160/27
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Delete all OSPF processes from the device
+ cisco.nxos.nxos_ospfv2:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# - cost: 120
+# prefix: 198.51.100.96/27
+# - area_id: 0.0.0.101
+# authentication:
+# message_digest: true
+# process_id: "102"
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-static-connect
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospf 100
+# - no router ospf 102
+#
+# after: {}
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# nxos-9k-rdo#
+
+# Using rendered
+
+- name: Render platform specific configuration lines (without connecting to the device)
+ cisco.nxos.nxos_ospfv2:
+ config:
+ processes:
+ - process_id: 100
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_1
+ direction: in
+ - route_map: rmap_2
+ direction: out
+ ranges:
+ - prefix: 198.51.100.64/27
+ not_advertise: true
+ - prefix: 198.51.100.96/27
+ cost: 120
+ - area_id: 0.0.0.101
+ authentication:
+ message_digest: true
+ redistribute:
+ - protocol: eigrp
+ id: 120
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ redistribute:
+ - protocol: static
+ route_map: zone1-static-connect
+ summary_address:
+ - prefix: 198.51.100.128/27
+ tag: 121
+ - prefix: 198.51.100.160/27
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: true
+ no_summary: true
+ - area_id: 0.0.0.103
+ nssa:
+ no_summary: true
+ translate:
+ type7:
+ always: true
+ - vrf: zone2
+ auto_cost:
+ reference_bandwidth: 45
+ unit: Gbps
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - router ospf 100
+# - router-id 203.0.113.20
+# - router ospf 102
+# - router-id 198.51.100.1
+# - redistribute eigrp 120 route-map rmap_1
+# - redistribute direct route-map ospf102-direct-connect
+# - area 0.0.0.100 filter-list route-map rmap_1 in
+# - area 0.0.0.100 filter-list route-map rmap_2 out
+# - area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# - area 0.0.0.100 range 198.51.100.96/27 cost 120
+# - area 0.0.0.101 authentication message-digest
+# - vrf zone1
+# - router-id 198.51.100.129
+# - summary-address 198.51.100.128/27 tag 121
+# - summary-address 198.51.100.160/27
+# - redistribute static route-map zone1-static-connect
+# - area 0.0.0.102 nssa no-summary default-information-originate
+# - area 0.0.0.103 nssa no-summary
+# - area 0.0.0.103 nssa translate type7 always
+# - vrf zone2
+# - auto-cost reference-bandwidth 45 Gbps
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# router ospf 100
+# router-id 192.0.100.1
+# area 0.0.0.101 nssa no-summary no-redistribution
+# area 0.0.0.102 stub no-summary
+# redistribute direct route-map ospf-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 192.0.2.0/24 not-advertise
+# area 0.0.0.100 range 192.0.3.0/24 cost 120
+# area 0.0.0.100 authentication message-digest
+# vrf zone1
+# router-id 192.0.100.2
+# area 0.0.100.1 nssa no-summary no-redistribution
+# redistribute static route-map zone1-direct-connect
+# summary-address 10.0.0.0/24 tag 120
+# summary-address 11.0.0.0/24 not-advertise
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+# down-bit-ignore
+# capability vrf-lite evpn
+# shutdown
+# router ospf 102
+# router-id 198.54.100.1
+# shutdown
+# vrf zone2
+# summary-address 192.0.8.0/24 tag 120
+# vrf zone4
+# shutdown
+
+- name: Parse externally provided OSPFv2 config
+ cisco.nxos.nxos_ospfv2:
+ running_config: "{{ lookup('file', 'ospfv2.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# processes:
+# - process_id: "100"
+# areas:
+# - area_id: 0.0.0.101
+# nssa:
+# no_redistribution: true
+# no_summary: true
+# - area_id: 0.0.0.102
+# stub:
+# no_summary: true
+# - area_id: 0.0.0.100
+# authentication:
+# message_digest: true
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 192.0.2.0/24
+# - cost: 120
+# prefix: 192.0.3.0/24
+# redistribute:
+# - protocol: direct
+# route_map: ospf-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 192.0.100.1
+# vrfs:
+# - vrf: zone1
+# areas:
+# - area_id: 0.0.100.1
+# nssa:
+# no_redistribution: true
+# no_summary: true
+# redistribute:
+# - protocol: static
+# route_map: zone1-direct-connect
+# router_id: 192.0.100.2
+# summary_address:
+# - prefix: 10.0.0.0/24
+# tag: 120
+# - not_advertise: true
+# prefix: 11.0.0.0/24
+# - vrf: zone2
+# auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# capability:
+# vrf_lite:
+# evpn: true
+# down_bit_ignore: true
+# shutdown: true
+# - process_id: "102"
+# router_id: 198.54.100.1
+# shutdown: true
+# vrfs:
+# - vrf: zone2
+# summary_address:
+# - prefix: 192.0.8.0/24
+# tag: 120
+# - vrf: zone4
+# shutdown: true
+
+# Using gathered
+
+- name: Gather OSPFv2 facts using gathered
+ cisco.nxos.nxos_ospfv2:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# processes:
+# - process_id: "102"
+# areas:
+# - area_id: 0.0.0.101
+# stub:
+# no_summary: true
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: in
+# route_map: rmap_8
+# ranges:
+# - not_advertise: true
+# prefix: 198.51.100.64/27
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "130"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 198.51.100.1
+# vrfs:
+# - vrf: zone1
+# areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# redistribute:
+# - id: "65563"
+# protocol: bgp
+# route_map: zone1-bgp-connect
+# router_id: 198.51.100.129
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - "router ospf 102"
+ - "router-id 198.54.100.1"
+ - "router ospf 100"
+ - "router-id 192.0.100.1"
+ - "redistribute eigrp 120 route-map rmap_1"
+ - "redistribute direct route-map ospf-direct-connect"
+ - "area 0.0.0.100 filter-list route-map rmap_1 in"
+ - "area 0.0.0.100 filter-list route-map rmap_2 out"
+ - "area 0.0.0.100 range 192.0.2.0/24 not-advertise"
+ - "area 0.0.0.100 range 192.0.3.0/24 cost 120"
+ - "vrf zone1"
+ - "router-id 192.0.100.2"
+ - "summary-address 10.0.0.0/24 tag 121"
+ - "summary-address 11.0.0.0/24"
+ - "redistribute static route-map zone1-direct-connect"
+ - "vrf zone2"
+ - "auto-cost reference-bandwidth 45 Gbps"
+ - "capability vrf-lite evpn"
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospfv2.ospfv2 import (
+ Ospfv2Args,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ospfv2.ospfv2 import (
+ Ospfv2,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ required_if = [
+ ("state", "merged", ("config",)),
+ ("state", "replaced", ("config",)),
+ ("state", "overridden", ("config",)),
+ ("state", "rendered", ("config",)),
+ ("state", "parsed", ("running_config",)),
+ ]
+ module = AnsibleModule(
+ argument_spec=Ospfv2Args.argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ result = Ospfv2(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv3.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv3.py
new file mode 100644
index 00000000..27d9ff65
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ospfv3.py
@@ -0,0 +1,1702 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2020 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_ospfv3
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_ospfv3
+short_description: OSPFv3 resource module
+description:
+- This module manages OSPFv3 configuration on devices running Cisco NX-OS.
+version_added: 1.2.0
+notes:
+- Tested against NX-OS 7.0(3)I5(1).
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section "^router ospfv3").
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of OSPFv3 process configuration.
+ type: dict
+ suboptions:
+ processes:
+ description:
+ - A list of OSPFv3 instances' configurations.
+ type: list
+ elements: dict
+ suboptions:
+ address_family:
+ description:
+ - IPv6 unicast address-family OSPFv3 settings.
+ type: dict
+ suboptions:
+ afi:
+ description:
+ - Configure OSPFv3 settings under IPv6 address-family.
+ type: str
+ choices: ['ipv6']
+ safi:
+ description:
+ - Configure OSPFv3 settings under IPv6 unicast address-family.
+ type: str
+ choices: ['unicast']
+ areas:
+ description:
+ - Configure properties of OSPF Areas under address-family.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - The Area ID in IP Address format.
+ type: str
+ required: True
+ default_cost:
+ description:
+ - Specify the default cost.
+ type: int
+ filter_list:
+ description:
+ - Filter prefixes between OSPF areas.
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description:
+ - The Route-map name.
+ type: str
+ required: True
+ direction:
+ description:
+ - The direction to apply the route map.
+ type: str
+ choices: [in, out]
+ required: True
+ ranges:
+ description:
+ - Configure an address range for the area.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IP in Prefix format (x.x.x.x/len)
+ type: str
+ required: True
+ cost:
+ description:
+ - Cost to use for the range.
+ type: int
+ not_advertise:
+ description:
+ - Suppress advertising the specified range.
+ type: bool
+ default_information:
+ description:
+ - Control distribution of default routes.
+ type: dict
+ suboptions:
+ originate:
+ description:
+ - Distribute a default route.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable distribution of default route.
+ type: bool
+ always:
+ description:
+ - Always advertise a default route.
+ type: bool
+ route_map:
+ description:
+ - Policy to control distribution of default routes
+ type: str
+ distance:
+ description:
+ - Configure the OSPF administrative distance.
+ type: int
+ maximum_paths:
+ description:
+ - Maximum paths per destination.
+ type: int
+ redistribute:
+ description:
+ - Redistribute information from another routing protocol.
+ type: list
+ elements: dict
+ suboptions:
+ protocol:
+ description:
+ - The name of the protocol.
+ type: str
+ choices: [bgp, direct, eigrp, isis, lisp, ospfv3, rip, static]
+ required: True
+ id:
+ description:
+ - The identifier for the protocol specified.
+ type: str
+ route_map:
+ description:
+ - The route map policy to constrain redistribution.
+ type: str
+ required: True
+ summary_address:
+ description:
+ - Configure route summarization for redistribution.
+ type: list
+ elements: dict
+ suboptions:
+ prefix:
+ description:
+ - IPv6 prefix format 'xxxx:xxxx/ml', 'xxxx:xxxx::/ml' or 'xxxx::xx/128'
+ type: str
+ required: True
+ not_advertise:
+ description:
+ - Suppress advertising the specified summary.
+ type: bool
+ tag:
+ description:
+ - A 32-bit tag value.
+ type: int
+ table_map:
+ description:
+ - Policy for filtering/modifying OSPF routes before sending them to
+ RIB.
+ type: dict
+ suboptions:
+ name:
+ description:
+ - The Route Map name.
+ type: str
+ required: True
+ filter:
+ description:
+ - Block the OSPF routes from being sent to RIB.
+ type: bool
+ timers:
+ description:
+ - Configure timer related constants.
+ type: dict
+ suboptions:
+ throttle:
+ description:
+ - Configure throttle related constants.
+ type: dict
+ suboptions:
+ spf:
+ description:
+ - Set OSPF SPF timers.
+ type: dict
+ suboptions:
+ initial_spf_delay:
+ description:
+ - Initial SPF schedule delay in milliseconds.
+ type: int
+ min_hold_time:
+ description:
+ - Minimum hold time between SPF calculations.
+ type: int
+ max_wait_time:
+ description:
+ - Maximum wait time between SPF calculations.
+ type: int
+ areas:
+ description:
+ - Configure properties of OSPF Areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - The Area ID in IP Address format.
+ type: str
+ required: True
+ nssa:
+ description:
+ - NSSA settings for the area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure area as NSSA.
+ type: bool
+ default_information_originate:
+ description:
+ - Originate Type-7 default LSA into NSSA area.
+ type: bool
+ no_redistribution:
+ description:
+ - Do not send redistributed LSAs into NSSA area.
+ type: bool
+ no_summary:
+ description:
+ - Do not send summary LSAs into NSSA area.
+ type: bool
+ route_map:
+ description:
+ - Policy to control distribution of default route.
+ type: str
+ translate:
+ description:
+ - Translate LSA.
+ type: dict
+ suboptions:
+ type7:
+ description:
+ - Translate from Type 7 to Type 5.
+ type: dict
+ suboptions:
+ always:
+ description:
+ - Always translate LSAs
+ type: bool
+ never:
+ description:
+ - Never translate LSAs
+ type: bool
+ supress_fa:
+ description:
+ - Suppress forwarding address in translated LSAs.
+ type: bool
+ stub:
+ description:
+ - Settings for configuring the area as a stub.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure the area as a stub.
+ type: bool
+ no_summary:
+ description:
+ - Prevent ABR from sending summary LSAs into stub area.
+ type: bool
+ auto_cost:
+ description:
+ - Calculate OSPF cost according to bandwidth.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description:
+ - Reference bandwidth used to assign OSPF cost.
+ type: int
+ required: True
+ unit:
+ description:
+ - Specify in which unit the reference bandwidth is specified.
+ type: str
+ required: True
+ choices: [Gbps, Mbps]
+ flush_routes:
+ description:
+ - Flush routes on a non-graceful controlled restart.
+ type: bool
+ graceful_restart:
+ description:
+ - Configure graceful restart.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable graceful-restart.
+ type: bool
+ grace_period:
+ description:
+ - Configure maximum interval to restart gracefully.
+ type: int
+ helper_disable:
+ description:
+ - Enable/Disable helper mode.
+ type: bool
+ planned_only:
+ description:
+ - Enable graceful restart only for a planned restart
+ type: bool
+ isolate:
+ description:
+ - Isolate this router from OSPF perspective.
+ type: bool
+ log_adjacency_changes:
+ description:
+ - Log changes in adjacency state.
+ type: dict
+ suboptions:
+ log:
+ description:
+ - Enable/disable logging changes in adjacency state.
+ type: bool
+ detail:
+ description:
+ - Notify all state changes.
+ type: bool
+ max_lsa:
+ description:
+ - Feature to limit the number of non-self-originated LSAs.
+ type: dict
+ suboptions:
+ max_non_self_generated_lsa:
+ description:
+ - Set the maximum number of non self-generated LSAs.
+ type: int
+ required: True
+ threshold:
+ description:
+ - Threshold value (%) at which to generate a warning message.
+ type: int
+ ignore_count:
+ description:
+ - Set count on how many times adjacencies can be suppressed.
+ type: int
+ ignore_time:
+ description:
+ - Set time during which all adjacencies are suppressed.
+ type: int
+ reset_time:
+ description:
+ - Set number of minutes after which ignore-count is reset to zero.
+ type: int
+ warning_only:
+ description:
+ - Log a warning message when limit is exceeded.
+ type: bool
+ max_metric:
+ description:
+ - Maximize the cost metric.
+ type: dict
+ suboptions:
+ router_lsa:
+ description:
+ - Router LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set router-lsa attribute.
+ type: bool
+ external_lsa:
+ description:
+ - External LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ stub_prefix_lsa:
+ description:
+ - Advertise Max metric for Stub links as well.
+ type: bool
+ on_startup:
+ description:
+ - Effective only at startup.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set on-startup attribute.
+ type: bool
+ wait_period:
+ description:
+ - Wait period in seconds after startup.
+ type: int
+ wait_for_bgp_asn:
+ description:
+ - ASN of BGP to wait for.
+ type: int
+ inter_area_prefix_lsa:
+ description:
+ - Inter-area-prefix LSAs configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set summary-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Max metric value for summary LSAs.
+ type: int
+ name_lookup:
+ description:
+ - Display OSPF router ids as DNS names.
+ type: bool
+ passive_interface:
+ description:
+ - Suppress routing updates on the interface.
+ type: dict
+ suboptions:
+ default:
+ description:
+ - Interfaces passive by default.
+ type: bool
+ process_id:
+ description:
+ - The OSPF process tag.
+ type: str
+ required: True
+ router_id:
+ description:
+ - Set OSPF process router-id.
+ type: str
+ shutdown:
+ description:
+ - Shutdown the OSPF protocol instance.
+ type: bool
+ timers:
+ description:
+ - Configure timer related constants.
+ type: dict
+ suboptions:
+ lsa_arrival:
+ description:
+ - Mimimum interval between arrival of a LSA.
+ type: int
+ lsa_group_pacing:
+ description:
+ - LSA group refresh/maxage interval.
+ type: int
+ throttle:
+ description:
+ - Configure throttle related constants.
+ type: dict
+ suboptions:
+ lsa:
+ description:
+ - Set rate-limiting for LSA generation.
+ type: dict
+ suboptions:
+ start_interval:
+ description:
+ - The start interval.
+ type: int
+ hold_interval:
+ description:
+ - The hold interval.
+ type: int
+ max_interval:
+ description:
+ - The max interval.
+ type: int
+ vrfs:
+ description:
+ - Configure VRF specific OSPF settings.
+ type: list
+ elements: dict
+ suboptions:
+ areas:
+ description:
+ - Configure properties of OSPF Areas.
+ type: list
+ elements: dict
+ suboptions:
+ area_id:
+ description:
+ - The Area ID in IP Address format.
+ type: str
+ required: True
+ nssa:
+ description:
+ - NSSA settings for the area.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure area as NSSA.
+ type: bool
+ default_information_originate:
+ description:
+ - Originate Type-7 default LSA into NSSA area.
+ type: bool
+ no_redistribution:
+ description:
+ - Do not send redistributed LSAs into NSSA area.
+ type: bool
+ no_summary:
+ description:
+ - Do not send summary LSAs into NSSA area.
+ type: bool
+ route_map:
+ description:
+ - Policy to control distribution of default route.
+ type: str
+ translate:
+ description:
+ - Translate LSA.
+ type: dict
+ suboptions:
+ type7:
+ description:
+ - Translate from Type 7 to Type 5.
+ type: dict
+ suboptions:
+ always:
+ description:
+ - Always translate LSAs
+ type: bool
+ never:
+ description:
+ - Never translate LSAs
+ type: bool
+ supress_fa:
+ description:
+ - Suppress forwarding address in translated LSAs.
+ type: bool
+ stub:
+ description:
+ - Settings for configuring the area as a stub.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Configure the area as a stub.
+ type: bool
+ no_summary:
+ description:
+ - Prevent ABR from sending summary LSAs into stub area.
+ type: bool
+ auto_cost:
+ description:
+ - Calculate OSPF cost according to bandwidth.
+ type: dict
+ suboptions:
+ reference_bandwidth:
+ description:
+ - Reference bandwidth used to assign OSPF cost.
+ type: int
+ required: True
+ unit:
+ description:
+ - Specify in which unit the reference bandwidth is specified.
+ type: str
+ required: True
+ choices: [Gbps, Mbps]
+ graceful_restart:
+ description:
+ - Configure graceful restart.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Enable graceful-restart.
+ type: bool
+ grace_period:
+ description:
+ - Configure maximum interval to restart gracefully.
+ type: int
+ helper_disable:
+ description:
+ - Enable/Disable helper mode.
+ type: bool
+ planned_only:
+ description:
+ - Enable graceful restart only for a planned restart
+ type: bool
+ log_adjacency_changes:
+ description:
+ - Log changes in adjacency state.
+ type: dict
+ suboptions:
+ log:
+ description:
+ - Enable/disable logging changes in adjacency state.
+ type: bool
+ detail:
+ description:
+ - Notify all state changes.
+ type: bool
+ max_lsa:
+ description:
+ - Feature to limit the number of non-self-originated LSAs.
+ type: dict
+ suboptions:
+ max_non_self_generated_lsa:
+ description:
+ - Set the maximum number of non self-generated LSAs.
+ type: int
+ required: True
+ threshold:
+ description:
+ - Threshold value (%) at which to generate a warning message.
+ type: int
+ ignore_count:
+ description:
+ - Set count on how many times adjacencies can be suppressed.
+ type: int
+ ignore_time:
+ description:
+ - Set time during which all adjacencies are suppressed.
+ type: int
+ reset_time:
+ description:
+ - Set number of minutes after which ignore-count is reset to zero.
+ type: int
+ warning_only:
+ description:
+ - Log a warning message when limit is exceeded.
+ type: bool
+ max_metric:
+ description:
+ - Maximize the cost metric.
+ type: dict
+ suboptions:
+ router_lsa:
+ description:
+ - Router LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set router-lsa attribute.
+ type: bool
+ external_lsa:
+ description:
+ - External LSA configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set external-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Set max metric value for external LSAs.
+ type: int
+ stub_prefix_lsa:
+ description:
+ - Advertise Max metric for Stub links as well.
+ type: bool
+ on_startup:
+ description:
+ - Effective only at startup.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set on-startup attribute.
+ type: bool
+ wait_period:
+ description:
+ - Wait period in seconds after startup.
+ type: int
+ wait_for_bgp_asn:
+ description:
+ - ASN of BGP to wait for.
+ type: int
+ inter_area_prefix_lsa:
+ description:
+ - Inter-area-prefix LSAs configuration.
+ type: dict
+ suboptions:
+ set:
+ description:
+ - Set summary-lsa attribute.
+ type: bool
+ max_metric_value:
+ description:
+ - Max metric value for summary LSAs.
+ type: int
+ name_lookup:
+ description:
+ - Display OSPF router ids as DNS names.
+ type: bool
+ passive_interface:
+ description:
+ - Suppress routing updates on the interface.
+ type: dict
+ suboptions:
+ default:
+ description:
+ - Interfaces passive by default.
+ type: bool
+ router_id:
+ description:
+ - Set OSPF process router-id.
+ type: str
+ shutdown:
+ description:
+ - Shutdown the OSPF protocol instance.
+ type: bool
+ timers:
+ description:
+ - Configure timer related constants.
+ type: dict
+ suboptions:
+ lsa_arrival:
+ description:
+ - Mimimum interval between arrival of a LSA.
+ type: int
+ lsa_group_pacing:
+ description:
+ - LSA group refresh/maxage interval.
+ type: int
+ throttle:
+ description:
+ - Configure throttle related constants.
+ type: dict
+ suboptions:
+ lsa:
+ description:
+ - Set rate-limiting for LSA generation.
+ type: dict
+ suboptions:
+ start_interval:
+ description:
+ - The start interval.
+ type: int
+ hold_interval:
+ description:
+ - The hold interval.
+ type: int
+ max_interval:
+ description:
+ - The max interval.
+ type: int
+ vrf:
+ description:
+ - Name/Identifier of the VRF.
+ type: str
+ required: True
+ state:
+ description:
+ - The state the configuration should be left in.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - parsed
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_ospfv3:
+ config:
+ processes:
+ - process_id: 100
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ address_family:
+ afi: ipv6
+ safi: unicast
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_1
+ direction: in
+ - route_map: rmap_2
+ direction: out
+ ranges:
+ - prefix: 2001:db2::/32
+ not_advertise: true
+ - prefix: 2001:db3::/32
+ cost: 120
+ redistribute:
+ - protocol: eigrp
+ id: 120
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: true
+ no_summary: true
+ - area_id: 0.0.0.103
+ nssa:
+ no_summary: true
+ translate:
+ type7:
+ always: true
+ - vrf: zone2
+ auto_cost:
+ reference_bandwidth: 45
+ unit: Gbps
+ state: merged
+
+# Task output
+# -------------
+# before: {}
+#
+# commands:
+# - router ospf 102
+# - router-id 198.51.100.1
+# - address-family ipv6 unicast
+# - redistribute eigrp 120 route-map rmap_1
+# - redistribute direct route-map ospf102-direct-connect
+# - area 0.0.0.100 filter-list route-map rmap_1 in
+# - area 0.0.0.100 filter-list route-map rmap_2 out
+# - area 0.0.0.100 range 2001:db2::/32 not-advertise
+# - area 0.0.0.100 range 2001:db3::/32 cost 120
+# - vrf zone1
+# - router-id 198.51.100.129
+# - area 0.0.0.102 nssa no-summary default-information-originate
+# - area 0.0.0.103 nssa no-summary
+# - area 0.0.0.103 nssa translate type7 always
+# - vrf zone2
+# - auto-cost reference-bandwidth 45 Gbps
+# - router ospf 100
+# - router-id 203.0.113.20
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family upv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Replace device configurations of listed OSPFv3 processes with provided configurations
+ cisco.nxos.nxos_ospfv3:
+ config:
+ processes:
+ - process_id: 102
+ router_id: 198.51.100.1
+ address_family:
+ afi: ipv6
+ safi: unicast
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_8
+ direction: in
+ ranges:
+ - not_advertise: true
+ prefix: 2001:db2::/32
+ redistribute:
+ - protocol: eigrp
+ id: 130
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: True
+ no_summary: True
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - router ospf 102
+# - address-family ipv6 unicast
+# - redistribute eigrp 130 route-map rmap_1
+# - no redistribute eigrp 120 route-map rmap_1
+# - area 0.0.0.100 filter-list route-map rmap_8 in
+# - no area 0.0.0.100 filter-list route-map rmap_2 out
+# - no area 0.0.0.100 range 2001:db3::/32
+# - vrf zone1
+# - no area 0.0.0.103 nssa
+# - no area 0.0.0.103 nssa translate type7 always
+# - no vrf zone2
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv6
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: in
+# route_map: rmap_8
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "130"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# router_id: 198.51.100.129
+# vrf: zone1
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 130 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_8 in
+# area 0.0.0.100 range 198.51.100.64/27 not-advertise
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+
+# Using overridden
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Override all OSPFv3 configuration with provided configuration
+ cisco.nxos.nxos_ospfv3:
+ config:
+ processes:
+ - process_id: 104
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ shutdown: true
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospfv3 100
+# - router ospfv3 104
+# - router-id 203.0.113.20
+# - router ospfv3 102
+# - shutdown
+# - address-family ipv6 unicast
+# - no redistribute direct route-map ospf102-direct-connect
+# - no redistribute eigrp 120 route-map rmap_1
+# - no area 0.0.0.100 filter-list route-map rmap_2 out
+# - no area 0.0.0.100 filter-list route-map rmap_1 in
+# - no area 0.0.0.100 range 2001:db2::/32
+# - no area 0.0.0.100 range 2001:db3::/32
+# - no vrf zone1
+# - no vrf zone2
+#
+# after:
+# processes:
+# - process_id: "102"
+# router_id: 198.51.100.1
+# shutdown: true
+# address_family:
+# afi: ipv6
+# safi: unicast
+# - process_id: "104"
+# router_id: 203.0.113.20
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# shutdown
+# router ospfv3 104
+# router-id 203.0.113.20
+
+# Using deleted to delete a single OSPF process
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospf .*"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Delete a single OSPFv3 process
+ cisco.nxos.nxos_ospfv3:
+ config:
+ processes:
+ - process_id: 102
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospfv3 102
+#
+# after:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+
+# Using deleted all OSPFv3 processes from the device
+
+# Before state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# router ospfv3 100
+# router-id 203.0.113.20
+# router ospfv3 102
+# router-id 198.51.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf102-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.0.102 nssa no-summary default-information-originate
+# area 0.0.0.103 nssa no-summary
+# area 0.0.0.103 nssa translate type7 always
+# vrf zone2
+# auto-cost reference-bandwidth 45 Gbps
+
+- name: Delete all OSPFv3 processes from the device
+ cisco.nxos.nxos_ospfv3:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+# commands:
+# - no router ospfv3 100
+# - no router ospfv3 102
+#
+# after: {}
+
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^router ospfv3"
+# nxos-9k-rdo#
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_ospfv3:
+ config:
+ processes:
+ - process_id: 100
+ router_id: 203.0.113.20
+ - process_id: 102
+ router_id: 198.51.100.1
+ address_family:
+ afi: ipv6
+ safi: unicast
+ areas:
+ - area_id: 0.0.0.100
+ filter_list:
+ - route_map: rmap_1
+ direction: in
+ - route_map: rmap_2
+ direction: out
+ ranges:
+ - prefix: 2001:db2::/32
+ not_advertise: true
+ - prefix: 2001:db3::/32
+ cost: 120
+ redistribute:
+ - protocol: eigrp
+ id: 120
+ route_map: rmap_1
+ - protocol: direct
+ route_map: ospf102-direct-connect
+ vrfs:
+ - vrf: zone1
+ router_id: 198.51.100.129
+ areas:
+ - area_id: 0.0.0.102
+ nssa:
+ default_information_originate: true
+ no_summary: true
+ - area_id: 0.0.0.103
+ nssa:
+ no_summary: true
+ translate:
+ type7:
+ always: true
+ - vrf: zone2
+ auto_cost:
+ reference_bandwidth: 45
+ unit: Gbps
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - router ospfv3 100
+# - router-id 203.0.113.20
+# - router ospfv3 102
+# - router-id 198.51.100.1
+# - address-family ipv6 unicast
+# - redistribute eigrp 120 route-map rmap_1
+# - redistribute direct route-map ospf102-direct-connect
+# - area 0.0.0.100 filter-list route-map rmap_1 in
+# - area 0.0.0.100 filter-list route-map rmap_2 out
+# - area 0.0.0.100 range 2001:db2::/32 not-advertise
+# - area 0.0.0.100 range 2001:db3::/32 cost 120
+# - vrf zone1
+# - router-id 198.51.100.129
+# - area 0.0.0.102 nssa no-summary default-information-originate
+# - area 0.0.0.103 nssa no-summary
+# - area 0.0.0.103 nssa translate type7 always
+# - vrf zone2
+# - auto-cost reference-bandwidth 45 Gbps
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# router ospfv3 100
+# router-id 192.0.100.1
+# address-family ipv6 unicast
+# redistribute direct route-map ospf-direct-connect
+# redistribute eigrp 120 route-map rmap_1
+# area 0.0.0.100 filter-list route-map rmap_2 out
+# area 0.0.0.100 filter-list route-map rmap_1 in
+# area 0.0.0.100 range 2001:db2::/32 not-advertise
+# area 0.0.0.100 range 2001:db3::/32 cost 120
+# vrf zone1
+# router-id 198.51.100.129
+# area 0.0.100.1 nssa no-summary no-redistribution
+# router ospfv3 102
+# router-id 198.54.100.1
+# shutdown
+
+- name: Parse externally provided OSPFv3 config
+ cisco.nxos.nxos_ospfv3:
+ running_config: "{{ lookup('file', 'ospfv2.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# processes:
+# - process_id: "100"
+# address_family:
+# afi: ipv6
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.101
+# nssa:
+# no_redistribution: true
+# no_summary: true
+# - area_id: 0.0.0.102
+# stub:
+# no_summary: true
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 192.0.2.0/24
+# - cost: 120
+# prefix: 192.0.3.0/24
+# redistribute:
+# - protocol: direct
+# route_map: ospf-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# router_id: 192.0.100.1
+# vrfs:
+# - vrf: zone1
+# areas:
+# - area_id: 0.0.100.1
+# nssa:
+# no_redistribution: true
+# no_summary: true
+# router_id: 192.0.100.2
+# - process_id: "102"
+# router_id: 198.54.100.1
+# shutdown: True
+
+# Using gathered
+
+- name: Gather OSPFv3 facts using gathered
+ cisco.nxos.nxos_ospfv3:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# processes:
+# - process_id: "100"
+# router_id: 203.0.113.20
+# - address_family:
+# afi: ipv4
+# safi: unicast
+# areas:
+# - area_id: 0.0.0.100
+# filter_list:
+# - direction: out
+# route_map: rmap_2
+# - direction: in
+# route_map: rmap_1
+# ranges:
+# - not_advertise: true
+# prefix: 2001:db2::/32
+# - cost: 120
+# prefix: 2001:db3::/32
+# redistribute:
+# - protocol: direct
+# route_map: ospf102-direct-connect
+# - id: "120"
+# protocol: eigrp
+# route_map: rmap_1
+# process_id: "102"
+# router_id: 198.51.100.1
+# vrfs:
+# - areas:
+# - area_id: 0.0.0.102
+# nssa:
+# default_information_originate: true
+# no_summary: true
+# - area_id: 0.0.0.103
+# nssa:
+# no_summary: true
+# translate:
+# type7:
+# always: true
+# router_id: 198.51.100.129
+# vrf: zone1
+# - auto_cost:
+# reference_bandwidth: 45
+# unit: Gbps
+# vrf: zone2
+#
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - "router ospfv3 102"
+ - "router-id 198.54.100.1"
+ - "router ospfv3 100"
+ - "router-id 192.0.100.1"
+ - "address-family ipv6 unicast"
+ - "redistribute eigrp 120 route-map rmap_1"
+ - "redistribute direct route-map ospf-direct-connect"
+ - "area 0.0.0.100 filter-list route-map rmap_1 in"
+ - "area 0.0.0.100 filter-list route-map rmap_2 out"
+ - "area 0.0.0.100 range 2001:db2::/32 not-advertise"
+ - "area 0.0.0.100 range 2001:db3::/32 cost 120"
+ - "vrf zone1"
+ - "router-id 192.0.100.2"
+ - "vrf zone2"
+ - "auto-cost reference-bandwidth 45 Gbps"
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.ospfv3.ospfv3 import (
+ Ospfv3Args,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.ospfv3.ospfv3 import (
+ Ospfv3,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Ospfv3Args.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Ospfv3(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_overlay_global.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_overlay_global.py
new file mode 100644
index 00000000..70b25246
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_overlay_global.py
@@ -0,0 +1,194 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_overlay_global
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Configures anycast gateway MAC of the switch.
+description:
+- Configures anycast gateway MAC of the switch.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Default restores params default value
+- Supported MAC address format are "E.E.E", "EE-EE-EE-EE-EE-EE", "EE:EE:EE:EE:EE:EE"
+ and "EEEE.EEEE.EEEE"
+options:
+ anycast_gateway_mac:
+ description:
+ - Anycast gateway mac of the switch.
+ required: true
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_overlay_global:
+ anycast_gateway_mac: b.b.b
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["fabric forwarding anycast-gateway-mac 000B.000B.000B"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+PARAM_TO_COMMAND_KEYMAP = {"anycast_gateway_mac": "fabric forwarding anycast-gateway-mac"}
+
+
+def get_existing(module, args):
+ existing = {}
+ config = str(get_config(module))
+
+ for arg in args:
+ command = PARAM_TO_COMMAND_KEYMAP[arg]
+ has_command = re.findall(r"(?:{0}\s)(?P<value>.*)$".format(command), config, re.M)
+ value = ""
+ if has_command:
+ value = has_command[0]
+ existing[arg] = value
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if value:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def get_commands(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+
+ for key, proposed in proposed_commands.items():
+ existing_value = existing_commands.get(key)
+ if proposed == "default" and existing_value:
+ commands.append("no {0} {1}".format(key, existing_value))
+ elif "anycast-gateway-mac" in key and proposed != "default":
+ proposed = normalize_mac(proposed, module)
+ existing_value = normalize_mac(existing_value, module)
+ if proposed != existing_value:
+ command = "{0} {1}".format(key, proposed)
+ commands.append(command)
+ if commands:
+ candidate.add(commands, parents=[])
+
+
+def normalize_mac(proposed_mac, module):
+ if proposed_mac is None:
+ return ""
+ try:
+ if "-" in proposed_mac:
+ splitted_mac = proposed_mac.split("-")
+ if len(splitted_mac) != 6:
+ raise ValueError
+
+ for octect in splitted_mac:
+ if len(octect) != 2:
+ raise ValueError
+
+ elif "." in proposed_mac:
+ splitted_mac = []
+ splitted_dot_mac = proposed_mac.split(".")
+ if len(splitted_dot_mac) != 3:
+ raise ValueError
+
+ for octect in splitted_dot_mac:
+ if len(octect) > 4:
+ raise ValueError
+ else:
+ octect_len = len(octect)
+ padding = 4 - octect_len
+ splitted_mac.append(octect.zfill(padding + 1))
+
+ elif ":" in proposed_mac:
+ splitted_mac = proposed_mac.split(":")
+ if len(splitted_mac) != 6:
+ raise ValueError
+
+ for octect in splitted_mac:
+ if len(octect) != 2:
+ raise ValueError
+ else:
+ raise ValueError
+ except ValueError:
+ module.fail_json(msg="Invalid MAC address format", proposed_mac=proposed_mac)
+
+ joined_mac = "".join(splitted_mac)
+ # fmt: off
+ mac = [joined_mac[i: i + 4] for i in range(0, len(joined_mac), 4)]
+ # fmt: on
+ return ".".join(mac).upper()
+
+
+def main():
+ argument_spec = dict(anycast_gateway_mac=dict(required=True, type="str"))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ result = {"changed": False, "commands": [], "warnings": warnings}
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+
+ existing = get_existing(module, args)
+ proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ candidate = CustomNetworkConfig(indent=3)
+ get_commands(module, existing, proposed, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ result["commands"] = candidate
+
+ if not module.check_mode:
+ load_config(module, candidate)
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_pim.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim.py
new file mode 100644
index 00000000..d9fc6f46
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim.py
@@ -0,0 +1,216 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_pim
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages configuration of a PIM instance.
+description:
+- Manages configuration of a Protocol Independent Multicast (PIM) instance.
+notes:
+- Unsupported for Cisco MDS
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+options:
+ bfd:
+ description:
+ - Enables BFD on all PIM interfaces.
+ - "Dependency: ''feature bfd''"
+ type: str
+ choices:
+ - enable
+ - disable
+ ssm_range:
+ description:
+ - Configure group ranges for Source Specific Multicast (SSM). Valid values are
+ multicast addresses or the keyword C(none) or keyword C(default). C(none) removes
+ all SSM group ranges. C(default) will set ssm_range to the default multicast
+ address. If you set multicast address, please ensure that it is not the same
+ as the C(default), otherwise use the C(default) option.
+ type: list
+ default: []
+ elements: str
+"""
+EXAMPLES = """
+- name: Configure ssm_range, enable bfd
+ cisco.nxos.nxos_pim:
+ bfd: enable
+ ssm_range: 224.0.0.0/8
+
+- name: Set to default
+ cisco.nxos.nxos_pim:
+ ssm_range: default
+
+- name: Remove all ssm group ranges
+ cisco.nxos.nxos_pim:
+ ssm_range: none
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - ip pim bfd
+ - ip pim ssm range 224.0.0.0/8
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+PARAM_TO_COMMAND_KEYMAP = {
+ "bfd": "ip pim bfd",
+ "ssm_range": "ip pim ssm range",
+}
+
+
+def get_existing(module, args):
+ existing = {}
+ config = str(get_config(module))
+
+ for arg in args:
+ if "ssm_range" in arg:
+ # <value> may be 'n.n.n.n/s', 'none', or 'default'
+ m = re.search(
+ r"ssm range (?P<value>(?:[\s\d.\/]+|none|default))?$",
+ config,
+ re.M,
+ )
+ if m:
+ # Remove rsvd SSM range
+ value = m.group("value").replace("232.0.0.0/8", "")
+ existing[arg] = value.split()
+
+ elif "bfd" in arg and "ip pim bfd" in config:
+ existing[arg] = "enable"
+
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if value is not None:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def get_commands(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+
+ for key, value in proposed_commands.items():
+ command = ""
+ if key == "ip pim ssm range":
+ if value == "default":
+ # no cmd needs a value but the actual value does not matter
+ command = "no ip pim ssm range none"
+ elif value == "none":
+ command = "ip pim ssm range none"
+ elif value:
+ command = "ip pim ssm range {0}".format(value)
+ elif key == "ip pim bfd":
+ no_cmd = "no " if value == "disable" else ""
+ command = no_cmd + key
+
+ if command:
+ commands.append(command)
+
+ if commands:
+ candidate.add(commands, parents=[])
+
+
+def main():
+ argument_spec = dict(
+ bfd=dict(required=False, type="str", choices=["enable", "disable"]),
+ ssm_range=dict(required=False, type="list", default=[], elements="str"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ warnings = list()
+ result = {"changed": False, "commands": [], "warnings": warnings}
+
+ params = module.params
+ args = [k for k in PARAM_TO_COMMAND_KEYMAP.keys() if params[k] is not None]
+
+ # SSM syntax check
+ if "ssm_range" in args:
+ for item in params["ssm_range"]:
+ if re.search("none|default", item):
+ break
+ if len(item.split(".")) != 4:
+ module.fail_json(
+ msg="Valid ssm_range values are multicast addresses "
+ "or the keyword 'none' or the keyword 'default'.",
+ )
+
+ existing = get_existing(module, args)
+ proposed_args = dict((k, v) for k, v in params.items() if k in args)
+
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key == "ssm_range":
+ if value and value[0] == "default":
+ if existing.get(key):
+ proposed[key] = "default"
+ else:
+ v = sorted(set([str(i) for i in value]))
+ ex = sorted(set([str(i) for i in existing.get(key, [])]))
+ if v != ex:
+ proposed[key] = " ".join(str(s) for s in v)
+
+ elif key == "bfd":
+ if value != existing.get("bfd", "disable"):
+ proposed[key] = value
+
+ elif value != existing.get(key):
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ get_commands(module, existing, proposed, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ result["commands"] = candidate
+ result["changed"] = True
+ load_config(module, candidate)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py
new file mode 100644
index 00000000..4d0f1052
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_interface.py
@@ -0,0 +1,604 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+DOCUMENTATION = """
+module: nxos_pim_interface
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages PIM interface configuration.
+description:
+- Manages PIM interface configuration settings.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- When C(state=default), supported params will be reset to a default state. These
+ include C(dr_prio), C(hello_auth_key), C(hello_interval), C(jp_policy_out), C(jp_policy_in),
+ C(jp_type_in), C(jp_type_out), C(border), C(neighbor_policy), C(neighbor_type).
+- The C(hello_auth_key) param is not idempotent.
+- C(hello_auth_key) only supports clear text passwords.
+- When C(state=absent), pim interface configuration will be set to defaults and pim-sm
+ will be disabled on the interface.
+- PIM must be enabled on the device to use this module.
+- This module is for Layer 3 interfaces.
+options:
+ interface:
+ description:
+ - Full name of the interface such as Ethernet1/33.
+ type: str
+ required: true
+ sparse:
+ description:
+ - Enable/disable sparse-mode on the interface.
+ type: bool
+ default: false
+ bfd:
+ description:
+ - Enables BFD for PIM at the interface level. This overrides the bfd variable
+ set at the pim global level.
+ - Valid values are 'enable', 'disable' or 'default'.
+ - "Dependency: ''feature bfd''"
+ type: str
+ choices:
+ - enable
+ - disable
+ - default
+ dr_prio:
+ description:
+ - Configures priority for PIM DR election on interface.
+ type: str
+ hello_auth_key:
+ description:
+ - Authentication for hellos on this interface.
+ type: str
+ hello_interval:
+ description:
+ - Hello interval in milliseconds or seconds for this interface.
+ - Use the option I(hello_interval_ms) to specify if the given value is in
+ milliseconds or seconds. The default is seconds.
+ type: int
+ hello_interval_ms:
+ description:
+ - Specifies that the hello_interval is in milliseconds.
+ - When set to True, this indicates that the user is providing the
+ hello_interval in milliseconds and hence, no conversion is required.
+ type: bool
+ version_added: 2.0.0
+ jp_policy_out:
+ description:
+ - Policy for join-prune messages (outbound).
+ type: str
+ jp_policy_in:
+ description:
+ - Policy for join-prune messages (inbound).
+ type: str
+ jp_type_out:
+ description:
+ - Type of policy mapped to C(jp_policy_out).
+ type: str
+ choices:
+ - prefix
+ - routemap
+ jp_type_in:
+ description:
+ - Type of policy mapped to C(jp_policy_in).
+ type: str
+ choices:
+ - prefix
+ - routemap
+ border:
+ description:
+ - Configures interface to be a boundary of a PIM domain.
+ type: bool
+ default: false
+ neighbor_policy:
+ description:
+ - Configures a neighbor policy for filtering adjacencies.
+ type: str
+ neighbor_type:
+ description:
+ - Type of policy mapped to neighbor_policy.
+ type: str
+ choices:
+ - prefix
+ - routemap
+ state:
+ description:
+ - Manages desired state of the resource.
+ type: str
+ choices:
+ - present
+ - absent
+ - default
+ default: present
+"""
+EXAMPLES = """
+- name: Ensure PIM is not running on the interface
+ cisco.nxos.nxos_pim_interface:
+ interface: eth1/33
+ state: absent
+
+- name: Ensure the interface has pim-sm enabled with the appropriate priority and
+ hello interval
+ cisco.nxos.nxos_pim_interface:
+ interface: eth1/33
+ dr_prio: 10
+ hello_interval: 40
+ state: present
+
+- name: Ensure join-prune policies exist
+ cisco.nxos.nxos_pim_interface:
+ interface: eth1/33
+ jp_policy_in: JPIN
+ jp_policy_out: JPOUT
+ jp_type_in: routemap
+ jp_type_out: routemap
+
+- name: disable bfd on the interface
+ cisco.nxos.nxos_pim_interface:
+ interface: eth1/33
+ bfd: disable
+
+- name: Ensure defaults are in place
+ cisco.nxos.nxos_pim_interface:
+ interface: eth1/33
+ state: default
+"""
+
+RETURN = r"""
+commands:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["interface eth1/33",
+ "ip pim neighbor-policy test",
+ "ip pim bfd-instance disable",
+ "ip pim neighbor-policy test"
+ ]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ get_interface_type,
+ load_config,
+ run_commands,
+)
+
+
+PARAM_TO_COMMAND_KEYMAP = {
+ "interface": "",
+ "bfd": "ip pim bfd-instance",
+ "sparse": "ip pim sparse-mode",
+ "dr_prio": "ip pim dr-priority {0}",
+ "hello_interval": "ip pim hello-interval {0}",
+ "hello_auth_key": "ip pim hello-authentication ah-md5 {0}",
+ "border": "ip pim border",
+ "jp_policy_out": "ip pim jp-policy prefix-list {0} out",
+ "jp_policy_in": "ip pim jp-policy prefix-list {0} in",
+ "jp_type_in": "",
+ "jp_type_out": "",
+ "neighbor_policy": "ip pim neighbor-policy prefix-list {0}",
+ "neighbor_type": "",
+}
+
+PARAM_TO_DEFAULT_KEYMAP = {
+ "bfd": "default",
+ "dr_prio": "1",
+ "hello_interval": "30000",
+ "sparse": False,
+ "border": False,
+ "hello_auth_key": False,
+}
+
+BFD_KEYMAP = {
+ None: None,
+ "default": "no ip pim bfd-instance",
+ "disable": "ip pim bfd-instance disable",
+ "enable": "ip pim bfd-instance",
+}
+
+
+def execute_show_command(command, module, text=False):
+ if text:
+ cmds = [{"command": command, "output": "text"}]
+ else:
+ cmds = [{"command": command, "output": "json"}]
+
+ return run_commands(module, cmds)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def local_existing(gexisting):
+ jp_bidir = False
+ isauth = False
+ if gexisting:
+ jp_bidir = gexisting.get("jp_bidir")
+ isauth = gexisting.get("isauth")
+ if jp_bidir and isauth:
+ gexisting.pop("jp_bidir")
+ gexisting.pop("isauth")
+
+ return gexisting, jp_bidir, isauth
+
+
+def get_interface_mode(interface, intf_type, module):
+ mode = "unknown"
+ command = "show interface {0}".format(interface)
+ body = execute_show_command(command, module)
+
+ try:
+ interface_table = body[0]["TABLE_interface"]["ROW_interface"]
+ except (KeyError, AttributeError, IndexError):
+ return mode
+
+ if intf_type in ["ethernet", "portchannel"]:
+ mode = str(interface_table.get("eth_mode", "layer3"))
+ if mode in ["access", "trunk"]:
+ mode = "layer2"
+ elif mode == "routed":
+ mode = "layer3"
+ elif intf_type in ["loopback", "svi"]:
+ mode = "layer3"
+ return mode
+
+
+def get_pim_interface(module, interface):
+ pim_interface = {}
+ body = get_config(module, flags=["interface {0}".format(interface)])
+
+ pim_interface["bfd"] = "default"
+ pim_interface["neighbor_type"] = None
+ pim_interface["neighbor_policy"] = None
+ pim_interface["jp_policy_in"] = None
+ pim_interface["jp_policy_out"] = None
+ pim_interface["jp_type_in"] = None
+ pim_interface["jp_type_out"] = None
+ pim_interface["jp_bidir"] = False
+ pim_interface["isauth"] = False
+
+ if body:
+ all_lines = body.splitlines()
+
+ for each in all_lines:
+ if "jp-policy" in each:
+ policy_name = re.search(
+ r"ip pim jp-policy(?: prefix-list)? (\S+)(?: \S+)?",
+ each,
+ ).group(1)
+ if "prefix-list" in each:
+ ptype = "prefix"
+ else:
+ ptype = "routemap"
+ if "out" in each:
+ pim_interface["jp_policy_out"] = policy_name
+ pim_interface["jp_type_out"] = ptype
+ elif "in" in each:
+ pim_interface["jp_policy_in"] = policy_name
+ pim_interface["jp_type_in"] = ptype
+ else:
+ pim_interface["jp_policy_in"] = policy_name
+ pim_interface["jp_policy_out"] = policy_name
+ pim_interface["jp_bidir"] = True
+ elif "neighbor-policy" in each:
+ pim_interface["neighbor_policy"] = re.search(
+ r"ip pim neighbor-policy(?: prefix-list)? (\S+)",
+ each,
+ ).group(1)
+ if "prefix-list" in each:
+ pim_interface["neighbor_type"] = "prefix"
+ else:
+ pim_interface["neighbor_type"] = "routemap"
+ elif "ah-md5" in each:
+ pim_interface["isauth"] = True
+ elif "sparse-mode" in each:
+ pim_interface["sparse"] = True
+ elif "bfd-instance" in each:
+ m = re.search(r"ip pim bfd-instance(?P<disable> disable)?", each)
+ if m:
+ pim_interface["bfd"] = "disable" if m.group("disable") else "enable"
+ elif "border" in each:
+ pim_interface["border"] = True
+ elif "hello-interval" in each:
+ pim_interface["hello_interval"] = re.search(
+ r"ip pim hello-interval (\d+)",
+ body,
+ ).group(1)
+ elif "dr-priority" in each:
+ pim_interface["dr_prio"] = re.search(r"ip pim dr-priority (\d+)", body).group(1)
+
+ return pim_interface
+
+
+def fix_delta(delta, existing):
+ for key in list(delta):
+ if key in ["dr_prio", "hello_interval", "sparse", "border"]:
+ if delta.get(key) == PARAM_TO_DEFAULT_KEYMAP.get(key) and existing.get(key) is None:
+ delta.pop(key)
+ return delta
+
+
+def config_pim_interface(delta, existing, jp_bidir, isauth):
+ command = None
+ commands = []
+
+ delta = fix_delta(delta, existing)
+
+ if jp_bidir:
+ if delta.get("jp_policy_in") or delta.get("jp_policy_out"):
+ if existing.get("jp_type_in") == "prefix":
+ command = "no ip pim jp-policy prefix-list {0}".format(existing.get("jp_policy_in"))
+ else:
+ command = "no ip pim jp-policy {0}".format(existing.get("jp_policy_in"))
+ if command:
+ commands.append(command)
+
+ for k, v in delta.items():
+ if k in [
+ "bfd",
+ "dr_prio",
+ "hello_interval",
+ "hello_auth_key",
+ "border",
+ "sparse",
+ ]:
+ if k == "bfd":
+ command = BFD_KEYMAP[v]
+ elif v:
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(v)
+ elif k == "hello_auth_key":
+ if isauth:
+ command = "no ip pim hello-authentication ah-md5"
+ else:
+ command = "no " + PARAM_TO_COMMAND_KEYMAP.get(k).format(v)
+
+ if command:
+ commands.append(command)
+ elif k in [
+ "neighbor_policy",
+ "jp_policy_in",
+ "jp_policy_out",
+ "neighbor_type",
+ ]:
+ if k in ["neighbor_policy", "neighbor_type"]:
+ temp = delta.get("neighbor_policy") or existing.get("neighbor_policy")
+ if delta.get("neighbor_type") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif delta.get("neighbor_type") == "routemap":
+ command = "ip pim neighbor-policy {0}".format(temp)
+ elif existing.get("neighbor_type") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif existing.get("neighbor_type") == "routemap":
+ command = "ip pim neighbor-policy {0}".format(temp)
+ elif k in ["jp_policy_in", "jp_type_in"]:
+ temp = delta.get("jp_policy_in") or existing.get("jp_policy_in")
+ if delta.get("jp_type_in") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif delta.get("jp_type_in") == "routemap":
+ command = "ip pim jp-policy {0} in".format(temp)
+ elif existing.get("jp_type_in") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif existing.get("jp_type_in") == "routemap":
+ command = "ip pim jp-policy {0} in".format(temp)
+ elif k in ["jp_policy_out", "jp_type_out"]:
+ temp = delta.get("jp_policy_out") or existing.get("jp_policy_out")
+ if delta.get("jp_type_out") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif delta.get("jp_type_out") == "routemap":
+ command = "ip pim jp-policy {0} out".format(temp)
+ elif existing.get("jp_type_out") == "prefix":
+ command = PARAM_TO_COMMAND_KEYMAP.get(k).format(temp)
+ elif existing.get("jp_type_out") == "routemap":
+ command = "ip pim jp-policy {0} out".format(temp)
+ if command:
+ commands.append(command)
+ command = None
+
+ if "no ip pim sparse-mode" in commands:
+ # sparse is long-running on some platforms, process it last
+ commands.remove("no ip pim sparse-mode")
+ commands.append("no ip pim sparse-mode")
+ return commands
+
+
+def get_pim_interface_defaults():
+ args = dict(
+ dr_prio=PARAM_TO_DEFAULT_KEYMAP.get("dr_prio"),
+ bfd=PARAM_TO_DEFAULT_KEYMAP.get("bfd"),
+ border=PARAM_TO_DEFAULT_KEYMAP.get("border"),
+ sparse=PARAM_TO_DEFAULT_KEYMAP.get("sparse"),
+ hello_interval=PARAM_TO_DEFAULT_KEYMAP.get("hello_interval"),
+ hello_auth_key=PARAM_TO_DEFAULT_KEYMAP.get("hello_auth_key"),
+ )
+
+ default = dict((param, value) for (param, value) in args.items() if value is not None)
+
+ return default
+
+
+def default_pim_interface_policies(existing, jp_bidir):
+ commands = []
+
+ if jp_bidir:
+ if existing.get("jp_policy_in") or existing.get("jp_policy_out"):
+ if existing.get("jp_type_in") == "prefix":
+ command = "no ip pim jp-policy prefix-list {0}".format(existing.get("jp_policy_in"))
+ if command:
+ commands.append(command)
+
+ elif not jp_bidir:
+ command = None
+ for k in existing:
+ if k == "jp_policy_in":
+ if existing.get("jp_policy_in"):
+ if existing.get("jp_type_in") == "prefix":
+ command = "no ip pim jp-policy prefix-list {0} in".format(
+ existing.get("jp_policy_in"),
+ )
+ else:
+ command = "no ip pim jp-policy {0} in".format(existing.get("jp_policy_in"))
+ elif k == "jp_policy_out":
+ if existing.get("jp_policy_out"):
+ if existing.get("jp_type_out") == "prefix":
+ command = "no ip pim jp-policy prefix-list {0} out".format(
+ existing.get("jp_policy_out"),
+ )
+ else:
+ command = "no ip pim jp-policy {0} out".format(
+ existing.get("jp_policy_out"),
+ )
+ if command:
+ commands.append(command)
+ command = None
+
+ if existing.get("neighbor_policy"):
+ command = "no ip pim neighbor-policy"
+ commands.append(command)
+
+ return commands
+
+
+def config_pim_interface_defaults(existing, jp_bidir, isauth):
+ command = []
+
+ # returns a dict
+ defaults = get_pim_interface_defaults()
+ delta = dict(set(defaults.items()).difference(existing.items()))
+ if delta:
+ # returns a list
+ command = config_pim_interface(delta, existing, jp_bidir, isauth)
+ comm = default_pim_interface_policies(existing, jp_bidir)
+ if comm:
+ for each in comm:
+ command.append(each)
+
+ return command
+
+
+def normalize_proposed_values(proposed, module):
+ keys = proposed.keys()
+ if "bfd" in keys:
+ # bfd is a tri-state string: enable, disable, default
+ proposed["bfd"] = proposed["bfd"].lower()
+ if "hello_interval" in keys:
+ hello_interval = proposed["hello_interval"]
+ if not module.params["hello_interval_ms"]:
+ hello_interval = hello_interval * 1000
+ proposed["hello_interval"] = str(hello_interval)
+
+
+def main():
+ argument_spec = dict(
+ interface=dict(type="str", required=True),
+ sparse=dict(type="bool", default=False),
+ dr_prio=dict(type="str"),
+ hello_auth_key=dict(type="str", no_log=True),
+ hello_interval=dict(type="int"),
+ hello_interval_ms=dict(type="bool"),
+ jp_policy_out=dict(type="str"),
+ jp_policy_in=dict(type="str"),
+ jp_type_out=dict(type="str", choices=["prefix", "routemap"]),
+ jp_type_in=dict(type="str", choices=["prefix", "routemap"]),
+ bfd=dict(type="str", choices=["enable", "disable", "default"]),
+ border=dict(type="bool", default=False),
+ neighbor_policy=dict(type="str"),
+ neighbor_type=dict(type="str", choices=["prefix", "routemap"]),
+ state=dict(
+ type="str",
+ default="present",
+ choices=["absent", "default", "present"],
+ ),
+ )
+
+ required_by = {"hello_interval_ms": "hello_interval"}
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=True,
+ required_by=required_by,
+ )
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ state = module.params["state"]
+ interface = module.params["interface"]
+ jp_type_in = module.params["jp_type_in"]
+ jp_type_out = module.params["jp_type_out"]
+ jp_policy_in = module.params["jp_policy_in"]
+ jp_policy_out = module.params["jp_policy_out"]
+ neighbor_policy = module.params["neighbor_policy"]
+ neighbor_type = module.params["neighbor_type"]
+
+ intf_type = get_interface_type(interface)
+ if get_interface_mode(interface, intf_type, module) == "layer2":
+ module.fail_json(msg="this module only works on Layer 3 interfaces.")
+
+ if jp_policy_in:
+ if not jp_type_in:
+ module.fail_json(msg="jp_type_in required when using jp_policy_in.")
+ if jp_policy_out:
+ if not jp_type_out:
+ module.fail_json(msg="jp_type_out required when using jp_policy_out.")
+ if neighbor_policy:
+ if not neighbor_type:
+ module.fail_json(msg="neighbor_type required when using neighbor_policy.")
+
+ get_existing = get_pim_interface(module, interface)
+ existing, jp_bidir, isauth = local_existing(get_existing)
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ proposed = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+ normalize_proposed_values(proposed, module)
+
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ commands = []
+ if state == "present":
+ if delta:
+ command = config_pim_interface(delta, existing, jp_bidir, isauth)
+ if command:
+ commands.append(command)
+ elif state == "default" or state == "absent":
+ defaults = config_pim_interface_defaults(existing, jp_bidir, isauth)
+ if defaults:
+ commands.append(defaults)
+
+ if commands:
+ commands.insert(0, ["interface {0}".format(interface)])
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_rp_address.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_rp_address.py
new file mode 100644
index 00000000..fe9989ed
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_pim_rp_address.py
@@ -0,0 +1,248 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_pim_rp_address
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages configuration of an PIM static RP address instance.
+description:
+- Manages configuration of an Protocol Independent Multicast (PIM) static rendezvous
+ point (RP) address instance.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(state=absent) is currently not supported on all platforms.
+options:
+ rp_address:
+ description:
+ - Configures a Protocol Independent Multicast (PIM) static rendezvous point (RP)
+ address. Valid values are unicast addresses.
+ required: true
+ type: str
+ group_list:
+ description:
+ - Group range for static RP. Valid values are multicast addresses.
+ type: str
+ prefix_list:
+ description:
+ - Prefix list policy for static RP. Valid values are prefix-list policy names.
+ type: str
+ route_map:
+ description:
+ - Route map policy for static RP. Valid values are route-map policy names.
+ type: str
+ bidir:
+ description:
+ - Group range is treated in PIM bidirectional mode.
+ type: bool
+ state:
+ description:
+ - Specify desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+EXAMPLES = """
+- cisco.nxos.nxos_pim_rp_address:
+ rp_address: 10.1.1.20
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["router bgp 65535", "vrf test", "router-id 192.0.2.1"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+def get_existing(module, args, gl):
+ existing = {}
+ config = str(get_config(module))
+ address = module.params["rp_address"]
+
+ pim_address_re = r"ip pim rp-address (?P<value>.*)$"
+ for line in re.findall(pim_address_re, config, re.M):
+ values = line.split()
+ if values[0] != address:
+ continue
+ if gl and "group-list" not in line:
+ continue
+ elif not gl and "group-list" in line:
+ if "224.0.0.0/4" not in line: # ignore default group-list
+ continue
+
+ existing["bidir"] = existing.get("bidir") or "bidir" in line
+ if len(values) > 2:
+ value = values[2]
+ if values[1] == "route-map":
+ existing["route_map"] = value
+ elif values[1] == "prefix-list":
+ existing["prefix_list"] = value
+ elif values[1] == "group-list":
+ if value != "224.0.0.0/4": # ignore default group-list
+ existing["group_list"] = value
+
+ return existing
+
+
+def state_present(module, existing, proposed, candidate):
+ address = module.params["rp_address"]
+ command = "ip pim rp-address {0}".format(address)
+ if module.params["group_list"] and not proposed.get("group_list"):
+ command += " group-list " + module.params["group_list"]
+ if module.params["prefix_list"]:
+ if not proposed.get("prefix_list"):
+ command += " prefix-list " + module.params["prefix_list"]
+ if module.params["route_map"]:
+ if not proposed.get("route_map"):
+ command += " route-map " + module.params["route_map"]
+ commands = build_command(proposed, command)
+ if commands:
+ candidate.add(commands, parents=[])
+
+
+def build_command(param_dict, command):
+ for param in ["group_list", "prefix_list", "route_map"]:
+ if param_dict.get(param):
+ command += " {0} {1}".format(param.replace("_", "-"), param_dict.get(param))
+ if param_dict.get("bidir"):
+ command += " bidir"
+ return [command]
+
+
+def state_absent(module, existing, candidate):
+ address = module.params["rp_address"]
+
+ commands = []
+ command = "no ip pim rp-address {0}".format(address)
+ if module.params["group_list"] == existing.get("group_list"):
+ commands = build_command(existing, command)
+ elif not module.params["group_list"]:
+ commands = [command]
+
+ if commands:
+ candidate.add(commands, parents=[])
+
+
+def get_proposed(pargs, existing):
+ proposed = {}
+
+ for key, value in pargs.items():
+ if key != "rp_address":
+ if str(value).lower() == "true":
+ value = True
+ elif str(value).lower() == "false":
+ value = False
+
+ if existing.get(key) != value:
+ proposed[key] = value
+
+ return proposed
+
+
+def main():
+ argument_spec = dict(
+ rp_address=dict(required=True, type="str"),
+ group_list=dict(required=False, type="str"),
+ prefix_list=dict(required=False, type="str"),
+ route_map=dict(required=False, type="str"),
+ bidir=dict(required=False, type="bool"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[
+ ["group_list", "route_map"],
+ ["group_list", "prefix_list"],
+ ["route_map", "prefix_list"],
+ ],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = {"changed": False, "commands": [], "warnings": warnings}
+
+ state = module.params["state"]
+
+ args = ["rp_address", "group_list", "prefix_list", "route_map", "bidir"]
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ if module.params["group_list"]:
+ existing = get_existing(module, args, True)
+ proposed = get_proposed(proposed_args, existing)
+
+ else:
+ existing = get_existing(module, args, False)
+ proposed = get_proposed(proposed_args, existing)
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present" and (proposed or not existing):
+ state_present(module, existing, proposed, candidate)
+ elif state == "absent" and existing:
+ state_absent(module, existing, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ result["commands"] = candidate
+ result["changed"] = True
+ msgs = load_config(module, candidate, True)
+ if msgs:
+ for item in msgs:
+ if item:
+ if isinstance(item, dict):
+ err_str = item["clierror"]
+ else:
+ err_str = item
+ if "No policy was configured" in err_str:
+ if state == "absent":
+ addr = module.params["rp_address"]
+ new_cmd = "no ip pim rp-address {0}".format(addr)
+ load_config(module, new_cmd)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_ping.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_ping.py
new file mode 100644
index 00000000..31c8517a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_ping.py
@@ -0,0 +1,255 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_ping
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Tests reachability using ping from Nexus switch.
+description:
+- Tests reachability using ping from switch to a remote destination.
+- For a general purpose network module, see the M(ansible.netcommon.net_ping) module.
+- For Windows targets, use the M(ansible.windows.win_ping) module instead.
+- For targets running Python, use the M(ansible.builtin.ping) module instead.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+options:
+ dest:
+ description:
+ - IP address or hostname (resolvable by switch) of remote node.
+ required: true
+ type: str
+ count:
+ description:
+ - Number of packets to send.
+ default: 5
+ type: int
+ source:
+ description:
+ - Source IP Address or hostname (resolvable by switch)
+ type: str
+ vrf:
+ description:
+ - Outgoing VRF.
+ type: str
+ df_bit:
+ description:
+ - Set the DF bit.
+ default: false
+ type: bool
+ size:
+ description:
+ - Size of packets to send.
+ type: int
+ state:
+ description:
+ - Determines if the expected result is success or fail.
+ choices:
+ - absent
+ - present
+ default: present
+ type: str
+notes:
+- Unsupported for Cisco MDS
+- For a general purpose network module, see the M(ansible.netcommon.net_ping) module.
+- For Windows targets, use the M(ansible.windows.win_ping) module instead.
+- For targets running Python, use the M(ansible.builtin.ping) module instead.
+"""
+
+EXAMPLES = """
+- name: Test reachability to 8.8.8.8 using mgmt vrf
+ cisco.nxos.nxos_ping:
+ dest: 8.8.8.8
+ vrf: management
+ host: 68.170.147.165
+
+- name: Test reachability to a few different public IPs using mgmt vrf
+ cisco.nxos.nxos_ping:
+ dest: "{{ item }}"
+ vrf: management
+ host: 68.170.147.165
+ with_items:
+ - 8.8.8.8
+ - 4.4.4.4
+ - 198.6.1.4
+
+- name: Test reachability to 8.8.8.8 using mgmt vrf, size and df-bit
+ cisco.nxos.nxos_ping:
+ dest: 8.8.8.8
+ df_bit: true
+ size: 1400
+ vrf: management
+"""
+
+RETURN = """
+commands:
+ description: Show the command sent
+ returned: always
+ type: list
+ sample: ["ping 8.8.8.8 count 2 vrf management"]
+rtt:
+ description: Show RTT stats
+ returned: always
+ type: dict
+ sample: {"avg": 6.264, "max": 6.564, "min": 5.978}
+packets_rx:
+ description: Packets successfully received
+ returned: always
+ type: int
+ sample: 2
+packets_tx:
+ description: Packets successfully transmitted
+ returned: always
+ type: int
+ sample: 2
+packet_loss:
+ description: Percentage of packets lost
+ returned: always
+ type: str
+ sample: "0.00%"
+"""
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import run_commands
+
+
+def get_summary(results_list, reference_point):
+ summary_string = results_list[reference_point + 1]
+ summary_list = summary_string.split(",")
+
+ summary = dict(
+ packets_tx=int(summary_list[0].split("packets")[0].strip()),
+ packets_rx=int(summary_list[1].split("packets")[0].strip()),
+ packet_loss=summary_list[2].split("packet")[0].strip(),
+ )
+
+ if "bytes from" not in results_list[reference_point - 2]:
+ ping_pass = False
+ else:
+ ping_pass = True
+
+ return summary, ping_pass
+
+
+def get_rtt(results_list, packet_loss, location):
+ rtt = dict(min=None, avg=None, max=None)
+
+ if packet_loss != "100.00%":
+ rtt_string = results_list[location]
+ base = rtt_string.split("=")[1]
+ rtt_list = base.split("/")
+
+ rtt["min"] = float(rtt_list[0].lstrip())
+ rtt["avg"] = float(rtt_list[1])
+ rtt["max"] = float(rtt_list[2][:-3])
+
+ return rtt
+
+
+def get_statistics_summary_line(response_as_list):
+ for each in response_as_list:
+ if "---" in each:
+ index = response_as_list.index(each)
+ return index
+
+
+def get_ping_results(command, module):
+ cmd = {"command": command, "output": "text"}
+ ping = run_commands(module, [cmd])[0]
+
+ if not ping:
+ module.fail_json(
+ msg="An unexpected error occurred. Check all params.",
+ command=command,
+ destination=module.params["dest"],
+ vrf=module.params["vrf"],
+ source=module.params["source"],
+ size=module.params["size"],
+ df_bit=module.params["df_bit"],
+ )
+
+ elif "can't bind to address" in ping:
+ module.fail_json(msg="Can't bind to source address.", command=command)
+ elif "bad context" in ping:
+ module.fail_json(
+ msg="Wrong VRF name inserted.",
+ command=command,
+ vrf=module.params["vrf"],
+ )
+ else:
+ splitted_ping = ping.split("\n")
+ reference_point = get_statistics_summary_line(splitted_ping)
+ summary, ping_pass = get_summary(splitted_ping, reference_point)
+ rtt = get_rtt(splitted_ping, summary["packet_loss"], reference_point + 2)
+
+ return (summary, rtt, ping_pass)
+
+
+def main():
+ argument_spec = dict(
+ dest=dict(required=True),
+ count=dict(required=False, default=5, type="int"),
+ vrf=dict(required=False),
+ source=dict(required=False),
+ size=dict(required=False, type="int"),
+ df_bit=dict(required=False, default=False, type="bool"),
+ state=dict(required=False, choices=["present", "absent"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ destination = module.params["dest"]
+ state = module.params["state"]
+ size = module.params["size"]
+ df_bit = module.params["df_bit"]
+
+ ping_command = "ping {0}".format(destination)
+ for command in ["count", "source", "vrf"]:
+ arg = module.params[command]
+ if arg:
+ ping_command += " {0} {1}".format(command, arg)
+
+ if size:
+ ping_command += " packet-size {0}".format(size)
+
+ if df_bit:
+ ping_command += " df-bit"
+
+ summary, rtt, ping_pass = get_ping_results(ping_command, module)
+
+ results = summary
+ results["rtt"] = rtt
+ results["commands"] = [ping_command]
+
+ if ping_pass and state == "absent":
+ module.fail_json(msg="Ping succeeded unexpectedly")
+ elif not ping_pass and state == "present":
+ module.fail_json(msg="Ping failed unexpectedly")
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_prefix_lists.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_prefix_lists.py
new file mode 100644
index 00000000..cbd2415c
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_prefix_lists.py
@@ -0,0 +1,842 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_prefix_lists
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_prefix_lists
+short_description: Prefix-Lists resource module.
+description:
+- This module manages prefix-lists configuration on devices running Cisco NX-OS.
+version_added: 2.4.0
+notes:
+- Tested against NX-OS 9.3.6.
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^ip(.*) prefix-list').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of prefix-list configuration.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - The Address Family Identifier (AFI) for the prefix-lists.
+ type: str
+ choices: ["ipv4", "ipv6"]
+ prefix_lists:
+ description: List of prefix-list configurations.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: Name of the prefix-list.
+ type: str
+ description:
+ description: Description of the prefix list
+ type: str
+ entries:
+ description: List of configurations for the specified prefix-list
+ type: list
+ elements: dict
+ suboptions:
+ sequence:
+ description: Sequence Number.
+ type: int
+ action:
+ description: Prefix-List permit or deny.
+ type: str
+ choices: ["permit", "deny"]
+ prefix:
+ description: IP or IPv6 prefix in A.B.C.D/LEN or A:B::C:D/LEN format.
+ type: str
+ eq:
+ description: Exact prefix length to be matched.
+ type: int
+ ge:
+ description: Minimum prefix length to be matched.
+ type: int
+ le:
+ description: Maximum prefix length to be matched.
+ type: int
+ mask:
+ description: Explicit match mask.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - Refer to examples for more details.
+ - With state I(replaced), for the listed prefix-lists,
+ sequences that are in running-config but not in the task are negated.
+ - With state I(overridden), all prefix-lists that are in running-config but
+ not in the task are negated.
+ - Please refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_prefix_lists:
+ config:
+ - afi: ipv4
+ prefix_lists:
+ - name: AllowPrefix
+ description: allows engineering IPv4 networks
+ entries:
+ - sequence: 10
+ action: permit
+ prefix: 192.0.2.0/23
+ eq: 24
+ - sequence: 20
+ action: permit
+ prefix: 198.51.100.128/26
+ - name: DenyPrefix
+ description: denies lab IPv4 networks
+ entries:
+ - sequence: 20
+ action: deny
+ prefix: 203.0.113.0/24
+ le: 25
+
+ - afi: ipv6
+ prefix_lists:
+ - name: AllowIPv6Prefix
+ description: allows engineering IPv6 networks
+ entries:
+ - sequence: 8
+ action: permit
+ prefix: "2001:db8:400::/38"
+ - sequence: 20
+ action: permit
+ prefix: "2001:db8:8000::/35"
+ le: 37
+
+# Task output
+# -------------
+# before: []
+#
+# commands:
+# - "ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks"
+# - "ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38"
+# - "ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37"
+# - "ip prefix-list AllowPrefix description allows engineering IPv4 networks"
+# - "ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24"
+# - "ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26"
+# - "ip prefix-list DenyPrefix description denies lab IPv4 networks"
+# - "ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25"
+#
+# after:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Replace prefix-lists configurations of listed prefix-lists with provided configurations
+ cisco.nxos.nxos_prefix_lists:
+ config:
+ - afi: ipv4
+ prefix_lists:
+ - name: AllowPrefix
+ description: allows engineering IPv4 networks
+ entries:
+ - sequence: 10
+ action: permit
+ prefix: 203.0.113.64/27
+
+ - sequence: 30
+ action: permit
+ prefix: 203.0.113.96/27
+ - name: AllowPrefix2Stub
+ description: allow other engineering IPv4 network
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# commands:
+# - "no ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24"
+# - "ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27"
+# - "ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27"
+# - "no ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26"
+# - "ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network"
+#
+# after:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 203.0.113.64/27
+# - sequence: 30
+# action: permit
+# prefix: 203.0.113.96/27
+# name: AllowPrefix
+# - description: allow other engineering IPv4 network
+# name: AllowPrefix2Stub
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27
+# ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27
+# ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+# Using overridden
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Override all prefix-lists configuration with provided configuration
+ cisco.nxos.nxos_prefix_lists: &id003
+ config:
+ - afi: ipv4
+ prefix_lists:
+ - name: AllowPrefix
+ description: allows engineering IPv4 networks
+ entries:
+ - sequence: 10
+ action: permit
+ prefix: 203.0.113.64/27
+
+ - sequence: 30
+ action: permit
+ prefix: 203.0.113.96/27
+ - name: AllowPrefix2Stub
+ description: allow other engineering IPv4 network
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# commands:
+# - "no ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24"
+# - "ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27"
+# - "ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27"
+# - "no ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26"
+# - "ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network"
+# - "no ip prefix-list DenyPrefix"
+# - "no ipv6 prefix-list AllowIPv6Prefix"
+#
+# after:
+# - afi: ipv4
+# prefix_lists:
+# - name: AllowPrefix
+# description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 203.0.113.64/27
+#
+# - sequence: 30
+# action: permit
+# prefix: 203.0.113.96/27
+# - name: AllowPrefix2Stub
+# description: allow other engineering IPv4 network
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 203.0.113.64/27
+# ip prefix-list AllowPrefix seq 30 permit 203.0.113.96/27
+# ip prefix-list AllowPrefix2Stub description allow other engineering IPv4 network
+
+# Using deleted to delete a all prefix lists for an AFI
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Delete all prefix-lists for an AFI
+ cisco.nxos.nxos_prefix_lists:
+ config:
+ - afi: ipv4
+ state: deleted
+ register: result
+
+# Task output
+# -------------
+# before:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# commands:
+# - "no ip prefix-list AllowPrefix"
+# - "no ip prefix-list DenyPrefix"
+#
+# after:
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+# Using deleted to delete a single prefix-list
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Delete a single prefix-list
+ cisco.nxos.nxos_prefix_lists:
+ config:
+ - afi: ipv4
+ prefix_lists:
+ - name: AllowPrefix
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# commands:
+# - "no ip prefix-list AllowPrefix"
+#
+# after:
+# - afi: ipv4
+# prefix_lists:
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+# Using deleted to delete all prefix-lists from the device
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Delete all prefix-lists
+ cisco.nxos.nxos_prefix_lists:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+#
+# commands:
+# - "no ip prefix-list AllowPrefix"
+# - "no ip prefix-list DenyPrefix"
+# - "no ipv6 prefix-list AllowIPv6Prefix"
+#
+# after: []
+#
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section 'ip(.*) prefix-list'
+# nxos-9k-rdo#
+
+# Using rendered
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_prefix_lists: &id001
+ config:
+ - afi: ipv4
+ prefix_lists:
+ - name: AllowPrefix
+ description: allows engineering IPv4 networks
+ entries:
+ - sequence: 10
+ action: permit
+ prefix: 192.0.2.0/23
+ eq: 24
+ - sequence: 20
+ action: permit
+ prefix: 198.51.100.128/26
+ - name: DenyPrefix
+ description: denies lab IPv4 networks
+ entries:
+ - sequence: 20
+ action: deny
+ prefix: 203.0.113.0/24
+ le: 25
+
+ - afi: ipv6
+ prefix_lists:
+ - name: AllowIPv6Prefix
+ description: allows engineering IPv6 networks
+ entries:
+ - sequence: 8
+ action: permit
+ prefix: "2001:db8:400::/38"
+ - sequence: 20
+ action: permit
+ prefix: "2001:db8:8000::/35"
+ le: 37
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# ip prefix-list AllowPrefix description allows engineering IPv4 networks
+# ip prefix-list AllowPrefix seq 10 permit 192.0.2.0/23 eq 24
+# ip prefix-list AllowPrefix seq 20 permit 198.51.100.128/26
+# ip prefix-list DenyPrefix description denies lab IPv4 networks
+# ip prefix-list DenyPrefix seq 20 deny 203.0.113.0/24 le 25
+# ipv6 prefix-list AllowIPv6Prefix description allows engineering IPv6 networks
+# ipv6 prefix-list AllowIPv6Prefix seq 8 permit 2001:db8:400::/38
+# ipv6 prefix-list AllowIPv6Prefix seq 20 permit 2001:db8:8000::/35 le 37
+
+- name: Parse externally provided prefix-lists configuration
+ register: result
+ cisco.nxos.nxos_prefix_lists:
+ running_config: "{{ lookup('file', './parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# - afi: ipv4
+# prefix_lists:
+# - description: allows engineering IPv4 networks
+# entries:
+# - sequence: 10
+# action: permit
+# prefix: 192.0.2.0/23
+# eq: 24
+# - sequence: 20
+# action: permit
+# prefix: 198.51.100.128/26
+# name: AllowPrefix
+# - description: denies lab IPv4 networks
+# entries:
+# - sequence: 20
+# action: deny
+# prefix: 203.0.113.0/24
+# le: 25
+# name: DenyPrefix
+#
+# - afi: ipv6
+# prefix_lists:
+# - description: allows engineering IPv6 networks
+# entries:
+# - sequence: 8
+# action: permit
+# prefix: "2001:db8:400::/38"
+# - sequence: 20
+# action: permit
+# prefix: "2001:db8:8000::/35"
+# le: 37
+# name: AllowIPv6Prefix
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.prefix_lists.prefix_lists import (
+ Prefix_listsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.prefix_lists.prefix_lists import (
+ Prefix_lists,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Prefix_listsArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Prefix_lists(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_reboot.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_reboot.py
new file mode 100644
index 00000000..51d0d70a
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_reboot.py
@@ -0,0 +1,89 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_reboot
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Reboot a network device.
+description:
+- Reboot a network device.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Tested against Cisco MDS NX-OS 9.2(1)
+- The module will fail due to timeout issues, but the reboot will be performed anyway.
+options:
+ confirm:
+ description:
+ - Safeguard boolean. Set to true if you're sure you want to reboot.
+ required: false
+ default: false
+ type: bool
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_reboot:
+ confirm: true
+"""
+
+RETURN = """
+rebooted:
+ description: Whether the device was instructed to reboot.
+ returned: success
+ type: bool
+ sample: true
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import load_config
+
+
+def reboot(module):
+ cmds = "terminal dont-ask ; reload"
+ opts = {"ignore_timeout": True}
+ load_config(module, cmds, False, opts)
+
+
+def main():
+ argument_spec = dict(confirm=dict(default=False, type="bool"))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = dict(changed=False, warnings=warnings)
+
+ if module.params["confirm"]:
+ if not module.check_mode:
+ reboot(module)
+ results["changed"] = True
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_rollback.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_rollback.py
new file mode 100644
index 00000000..4b26919d
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_rollback.py
@@ -0,0 +1,130 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_rollback
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Set a checkpoint or rollback to a checkpoint.
+description:
+- This module offers the ability to set a configuration checkpoint file or rollback
+ to a configuration checkpoint file on Cisco NXOS switches.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Sometimes C(transport=nxapi) may cause a timeout error.
+options:
+ checkpoint_file:
+ description:
+ - Name of checkpoint file to create. Mutually exclusive with rollback_to.
+ type: str
+ rollback_to:
+ description:
+ - Name of checkpoint file to rollback to. Mutually exclusive with checkpoint_file.
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_rollback:
+ checkpoint_file: backup.cfg
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+ host: '{{ inventory_hostname }}'
+- cisco.nxos.nxos_rollback:
+ rollback_to: backup.cfg
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+ host: '{{ inventory_hostname }}'
+"""
+
+RETURN = """
+filename:
+ description: The filename of the checkpoint/rollback file.
+ returned: success
+ type: str
+ sample: 'backup.cfg'
+status:
+ description: Which operation took place and whether it was successful.
+ returned: success
+ type: str
+ sample: 'rollback executed'
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import run_commands
+
+
+def checkpoint(filename, module):
+ commands = [
+ {"command": "terminal dont-ask", "output": "text"},
+ {"command": "checkpoint file %s" % filename, "output": "text"},
+ ]
+ run_commands(module, commands)
+
+
+def rollback(filename, module):
+ commands = [
+ {
+ "command": "rollback running-config file %s" % filename,
+ "output": "text",
+ },
+ ]
+ run_commands(module, commands)
+
+
+def main():
+ argument_spec = dict(checkpoint_file=dict(required=False), rollback_to=dict(required=False))
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[["checkpoint_file", "rollback_to"]],
+ supports_check_mode=False,
+ )
+
+ checkpoint_file = module.params["checkpoint_file"]
+ rollback_to = module.params["rollback_to"]
+
+ status = None
+ filename = None
+ changed = False
+
+ if checkpoint_file:
+ checkpoint(checkpoint_file, module)
+ status = "checkpoint file created"
+ elif rollback_to:
+ rollback(rollback_to, module)
+ status = "rollback executed"
+ changed = True
+ filename = rollback_to or checkpoint_file
+
+ module.exit_json(changed=changed, status=status, filename=filename)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_route_maps.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_route_maps.py
new file mode 100644
index 00000000..111cc4da
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_route_maps.py
@@ -0,0 +1,1648 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_route_maps
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_route_maps
+short_description: Route Maps resource module.
+description:
+- This module manages route maps configuration on devices running Cisco NX-OS.
+version_added: 2.2.0
+notes:
+- Tested against NX-OS 9.3.6.
+- Unsupported for Cisco MDS
+- This module works with connection C(network_cli) and C(httpapi).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^route-map').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A list of route-map configuration.
+ type: list
+ elements: dict
+ suboptions:
+ route_map:
+ description: Route-map name.
+ type: str
+ entries:
+ description: List of entries (identified by sequence number) for this route-map.
+ type: list
+ elements: dict
+ suboptions:
+ sequence:
+ description: Sequence to insert to/delete from existing route-map entry.
+ type: int
+ action:
+ description: Route map denies or permits set operations.
+ type: str
+ choices: ["deny", "permit"]
+ continue_sequence:
+ description: Continue on a different entry within the route-map.
+ type: int
+ description:
+ description: Description of the route-map.
+ type: str
+ match:
+ description: Match values from routing table.
+ type: dict
+ suboptions:
+ as_number:
+ description: Match BGP peer AS number.
+ type: dict
+ suboptions:
+ asn:
+ description: AS number.
+ type: list
+ elements: str
+ as_path_list:
+ description: AS path access list name.
+ type: list
+ elements: str
+ as_path:
+ description: Match BGP AS path access-list.
+ type: list
+ elements: str
+ community:
+ description: Match BGP community list.
+ type: dict
+ suboptions:
+ community_list:
+ description: Community list.
+ type: list
+ elements: str
+ exact_match:
+ description: Do exact matching of communities.
+ type: bool
+ evpn:
+ description: Match BGP EVPN Routes.
+ type: dict
+ suboptions:
+ route_types:
+ description: Match route type for evpn route.
+ type: list
+ elements: str
+ extcommunity:
+ description: Match BGP community list.
+ type: dict
+ suboptions:
+ extcommunity_list:
+ description: Extended Community list.
+ type: list
+ elements: str
+ exact_match:
+ description: Do exact matching of extended communities.
+ type: bool
+ interfaces:
+ description: Match first hop interface of route.
+ type: list
+ elements: str
+ ip:
+ description: Configure IP specific information.
+ type: dict
+ suboptions: &id001
+ address:
+ description: Match address of route or match packet.
+ type: dict
+ suboptions:
+ access_list:
+ description: IP access-list name (for use in route-maps for PBR only).
+ type: str
+ prefix_lists:
+ description: Match entries of prefix-lists.
+ type: list
+ elements: str
+ multicast:
+ description: Match multicast attributes.
+ type: dict
+ suboptions:
+ source:
+ description: Multicast source address.
+ type: str
+ group:
+ description:
+ - Multicast Group prefix.
+ - Mutually exclusive with group_range.
+ type: dict
+ suboptions:
+ prefix:
+ description: IPv4 group prefix.
+ type: str
+ group_range:
+ description:
+ - Multicast Group address range.
+ - Mutually exclusive with group.
+ type: dict
+ suboptions:
+ first:
+ description: First Group address.
+ type: str
+ last:
+ description: Last Group address.
+ type: str
+ rp:
+ description: Rendezvous point.
+ type: dict
+ suboptions:
+ prefix:
+ description: IPv4 rendezvous prefix.
+ type: str
+ rp_type:
+ description: Multicast rendezvous point type.
+ type: str
+ choices: ["ASM", "Bidir"]
+ next_hop:
+ description: Match next-hop address of route.
+ type: dict
+ suboptions:
+ prefix_lists:
+ description: Match entries of prefix-lists.
+ type: list
+ elements: str
+ route_source:
+ description: Match advertising source address of route.
+ type: dict
+ suboptions:
+ prefix_lists:
+ description: Match entries of prefix-lists.
+ type: list
+ elements: str
+ ipv6:
+ description: Configure IPv6 specific information.
+ type: dict
+ suboptions: *id001
+ mac_list:
+ description: Match entries of mac-lists.
+ type: list
+ elements: str
+ metric:
+ description: Match metric of route.
+ type: list
+ elements: int
+ ospf_area:
+ description: Match ospf area.
+ type: list
+ elements: int
+ route_types:
+ description: Match route-type of route.
+ type: list
+ elements: str
+ choices: ["external", "inter-area", "internal", "intra-area", "level-1", "level-2", "local", "nssa-external", "type-1", "type-2"]
+ source_protocol:
+ description: Match source protocol.
+ type: list
+ elements: str
+ tags:
+ description: Match tag of route.
+ type: list
+ elements: int
+ set:
+ description: Set values in destination routing protocol.
+ type: dict
+ suboptions:
+ as_path:
+ description: Prepend string for a BGP AS-path attribute.
+ type: dict
+ suboptions:
+ prepend:
+ description: Prepend to the AS-Path.
+ type: dict
+ suboptions:
+ as_number:
+ description: AS number.
+ type: list
+ elements: str
+ last_as:
+ description: Number of last-AS prepends.
+ type: int
+ tag:
+ description: Set the tag as an AS-path attribute.
+ type: bool
+ comm_list:
+ description: Set BGP community list (for deletion).
+ type: str
+ community:
+ description: Set BGP community attribute.
+ type: dict
+ suboptions:
+ additive:
+ description: Add to existing community.
+ type: bool
+ graceful_shutdown:
+ description: Graceful Shutdown (well-known community).
+ type: bool
+ internet:
+ description: Internet (well-known community).
+ type: bool
+ local_as:
+ description: Do not send outside local AS (well-known community).
+ type: bool
+ no_advertise:
+ description: Do not advertise to any peer (well-known community).
+ type: bool
+ no_export:
+ description: Do not export to next AS (well-known community).
+ type: bool
+ number:
+ description: "Community number aa:nn format"
+ type: list
+ elements: str
+ dampening:
+ description: Set BGP route flap dampening parameters.
+ type: dict
+ suboptions:
+ half_life:
+ description: Half-life time for the penalty.
+ type: int
+ start_reuse_route:
+ description: Value to start reusing a route.
+ type: int
+ start_suppress_route:
+ description: Value to start suppressing a route.
+ type: int
+ max_suppress_time:
+ description: Maximum suppress time for stable route.
+ type: int
+ distance:
+ description: Configure administrative distance.
+ type: dict
+ suboptions:
+ igp_ebgp_routes:
+ description: Administrative distance for IGP or EBGP routes
+ type: int
+ internal_routes:
+ description: Distance for internal routes.
+ type: int
+ local_routes:
+ description: Distance for local routes.
+ type: int
+ evpn:
+ description: Set BGP EVPN Routes.
+ type: dict
+ suboptions:
+ gateway_ip:
+ description:
+ - Set gateway IP for type 5 EVPN routes.
+ - Cannot set ip and use-nexthop in the same route-map sequence.
+ type: dict
+ suboptions:
+ ip:
+ description: Gateway IP address.
+ type: str
+ use_nexthop:
+ description: Use nexthop address as gateway IP.
+ type: bool
+ extcomm_list:
+ description: Set BGP extcommunity list (for deletion).
+ type: str
+ forwarding_address:
+ description: Set the forwarding address.
+ type: bool
+ null_interface:
+ description: Output Null interface.
+ type: str
+ ip:
+ description: Configure IP features.
+ type: dict
+ suboptions: &id002
+ address:
+ description: Specify IP address.
+ type: dict
+ suboptions:
+ prefix_list:
+ description: Name of prefix list (Max Size 63).
+ type: str
+ precedence:
+ description: Set precedence field.
+ type: str
+ ipv6:
+ description: Configure IPv6 features.
+ type: dict
+ suboptions: *id002
+ label_index:
+ description: Set Segment Routing (SR) label index of route.
+ type: int
+ level:
+ description: Where to import route.
+ type: str
+ choices: ["level-1", "level-1-2", "level-2"]
+ local_preference:
+ description: BGP local preference path attribute.
+ type: int
+ metric:
+ description: Set metric for destination routing protocol.
+ type: dict
+ suboptions:
+ bandwidth:
+ description: Metric value or Bandwidth in Kbits per second (Max Size 11).
+ type: int
+ igrp_delay_metric:
+ description: IGRP delay metric.
+ type: int
+ igrp_reliability_metric:
+ description: IGRP reliability metric where 255 is 100 percent reliable.
+ type: int
+ igrp_effective_bandwidth_metric:
+ description: IGRP Effective bandwidth metric (Loading) 255 is 100%.
+ type: int
+ igrp_mtu:
+ description: IGRP MTU of the path.
+ type: int
+ metric_type:
+ description: Type of metric for destination routing protocol.
+ type: str
+ choices: ["external", "internal", "type-1", "type-2"]
+ nssa_only:
+ description: OSPF NSSA Areas.
+ type: bool
+ origin:
+ description: BGP origin code.
+ type: str
+ choices: ["egp", "igp", "incomplete"]
+ path_selection:
+ description: Path selection criteria for BGP.
+ type: str
+ choices: ["all", "backup", "best2", "multipaths"]
+ tag:
+ description: Tag value for destination routing protocol.
+ type: int
+ weight:
+ description: BGP weight for routing table.
+ type: int
+ state:
+ description:
+ - The state the configuration should be left in.
+ - With state I(replaced), for the listed route-maps,
+ sequences that are in running-config but not in the task are negated.
+ - With state I(overridden), all route-maps that are in running-config but
+ not in the task are negated.
+ - Please refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# nxos-9k-rdo#
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_route_maps:
+ config:
+ - route_map: rmap1
+ entries:
+ - sequence: 10
+ action: permit
+ description: rmap1-10-permit
+ match:
+ ip:
+ address:
+ access_list: acl_1
+ as_path: Allow40
+ as_number:
+ asn: 65564
+
+ - sequence: 20
+ action: deny
+ description: rmap1-20-deny
+ match:
+ community:
+ community_list:
+ - BGPCommunity1
+ - BGPCommunity2
+ ip:
+ address:
+ prefix_lists:
+ - AllowPrefix1
+ - AllowPrefix2
+ set:
+ dampening:
+ half_life: 30
+ start_reuse_route: 1500
+ start_suppress_route: 10000
+ max_suppress_time: 120
+
+ - route_map: rmap2
+ entries:
+ - sequence: 20
+ action: permit
+ description: rmap2-20-permit
+ continue_sequence: 40
+ match:
+ ipv6:
+ address:
+ prefix_lists: AllowIPv6Prefix
+ interfaces: "{{ nxos_int1 }}"
+ set:
+ as_path:
+ prepend:
+ as_number:
+ - 65563
+ - 65568
+ - 65569
+ comm_list: BGPCommunity
+
+ - sequence: 40
+ action: deny
+ description: rmap2-40-deny
+ match:
+ route_types:
+ - level-1
+ - level-2
+ tags: 2
+ ip:
+ multicast:
+ rp:
+ prefix: 192.0.2.0/24
+ rp_type: ASM
+ source: 203.0.113.0/24
+ group_range:
+ first: 239.0.0.1
+ last: 239.255.255.255
+ state: merged
+
+# Task output
+# -------------
+# before: []
+#
+# commands:
+# - "route-map rmap1 permit 10"
+# - "match as-number 65564"
+# - "match as-path Allow40"
+# - "match ip address acl_1"
+# - "description rmap1-10-permit"
+# - "route-map rmap1 deny 20"
+# - "match community BGPCommunity1 BGPCommunity2"
+# - "match ip address prefix-list AllowPrefix1 AllowPrefix2"
+# - "description rmap1-20-deny"
+# - "set dampening 30 1500 10000 120"
+# - "route-map rmap2 permit 20"
+# - "match interface Ethernet1/1"
+# - "match ipv6 address prefix-list AllowIPv6Prefix"
+# - "set as-path prepend 65563 65568 65569"
+# - "description rmap2-20-permit"
+# - "continue 40"
+# - "set comm-list BGPCommunity delete"
+# - "route-map rmap2 deny 40"
+# - "match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM"
+# - "match route-type level-1 level-2"
+# - "match tag 2"
+# - "description rmap2-40-deny"
+#
+# after:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+# Using replaced
+# (for the listed route-map(s), sequences that are in running-config but not in the task are negated)
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+- name: Replace route-maps configurations of listed route-maps with provided configurations
+ cisco.nxos.nxos_route_maps:
+ config:
+ - route_map: rmap1
+ entries:
+ - sequence: 20
+ action: deny
+ description: rmap1-20-deny
+ match:
+ community:
+ community_list:
+ - BGPCommunity4
+ - BGPCommunity5
+ ip:
+ address:
+ prefix_lists:
+ - AllowPrefix1
+ set:
+ community:
+ local_as: True
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+# commands:
+# - no route-map rmap1 permit 10
+# - route-map rmap1 deny 20
+# - no match community BGPCommunity1 BGPCommunity2
+# - match community BGPCommunity4 BGPCommunity5
+# - no match ip address prefix-list AllowPrefix1 AllowPrefix2
+# - match ip address prefix-list AllowPrefix1
+# - no set dampening 30 1500 10000 120
+# - set community local-AS
+#
+# after:
+# - route_map: rmap1
+# entries:
+# - sequence: 20
+# action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity4
+# - BGPCommunity5
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# set:
+# community:
+# local_as: True
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 deny 20
+# description rmap1-20-deny
+# match community BGPCommunity4 BGPCommunity5
+# match ip address prefix-list AllowPrefix1
+# set community local-AS
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+# Using overridden
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+- name: Override all route-maps configuration with provided configuration
+ cisco.nxos.nxos_route_maps:
+ config:
+ - route_map: rmap1
+ entries:
+ - sequence: 20
+ action: deny
+ description: rmap1-20-deny
+ match:
+ community:
+ community_list:
+ - BGPCommunity4
+ - BGPCommunity5
+ ip:
+ address:
+ prefix_lists:
+ - AllowPrefix1
+ set:
+ community:
+ local_as: True
+ state: overridden
+
+# Task output
+# -------------
+# before:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+# commands:
+# - no route-map rmap1 permit 10
+# - route-map rmap1 deny 20
+# - no match community BGPCommunity1 BGPCommunity2
+# - match community BGPCommunity4 BGPCommunity5
+# - no match ip address prefix-list AllowPrefix1 AllowPrefix2
+# - match ip address prefix-list AllowPrefix1
+# - no set dampening 30 1500 10000 120
+# - set community local-AS
+# - no route-map rmap2 permit 20
+# - no route-map rmap2 deny 40
+#
+# after:
+# - route_map: rmap1
+# entries:
+# - sequence: 20
+# action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity4
+# - BGPCommunity5
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# set:
+# community:
+# local_as: True
+#
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^route-map"
+# route-map rmap1 deny 20
+# description rmap1-20-deny
+# match community BGPCommunity4 BGPCommunity5
+# match ip address prefix-list AllowPrefix1
+# set community local-AS
+
+# Using deleted to delete a single route-map
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+- name: Delete single route-map
+ cisco.nxos.nxos_route_maps:
+ config:
+ - route_map: rmap1
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+# commands:
+# - no route-map rmap1 permit 10
+# - no route-map rmap1 deny 20
+#
+# after:
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^route-map"
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+# Using deleted to delete all route-maps from the device running-config
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+- name: Delete all route-maps
+ cisco.nxos.nxos_route_maps:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+#
+# commands:
+# - no route-map rmap1 permit 10
+# - no route-map rmap1 deny 20
+# - no route-map rmap2 permit 20
+# - no route-map rmap2 deny 40
+#
+# after: []
+#
+# After state:
+# ------------
+# nxos-9k-rdo# sh running-config | section "^route-map"
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_route_maps:
+ config:
+ - route_map: rmap1
+ entries:
+ - sequence: 10
+ action: permit
+ description: rmap1-10-permit
+ match:
+ ip:
+ address:
+ access_list: acl_1
+ as_path: Allow40
+ as_number:
+ asn: 65564
+
+ - sequence: 20
+ action: deny
+ description: rmap1-20-deny
+ match:
+ community:
+ community_list:
+ - BGPCommunity1
+ - BGPCommunity2
+ ip:
+ address:
+ prefix_lists:
+ - AllowPrefix1
+ - AllowPrefix2
+ set:
+ dampening:
+ half_life: 30
+ start_reuse_route: 1500
+ start_suppress_route: 10000
+ max_suppress_time: 120
+
+ - route_map: rmap2
+ entries:
+ - sequence: 20
+ action: permit
+ description: rmap2-20-permit
+ continue_sequence: 40
+ match:
+ ipv6:
+ address:
+ prefix_lists: AllowIPv6Prefix
+ interfaces: "{{ nxos_int1 }}"
+ set:
+ as_path:
+ prepend:
+ as_number:
+ - 65563
+ - 65568
+ - 65569
+ comm_list: BGPCommunity
+
+ - sequence: 40
+ action: deny
+ description: rmap2-40-deny
+ match:
+ route_types:
+ - level-1
+ - level-2
+ tags: 2
+ ip:
+ multicast:
+ rp:
+ prefix: 192.0.2.0/24
+ rp_type: ASM
+ source: 203.0.113.0/24
+ group_range:
+ first: 239.0.0.1
+ last: 239.255.255.255
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - "route-map rmap1 permit 10"
+# - "match as-number 65564"
+# - "match as-path Allow40"
+# - "match ip address acl_1"
+# - "description rmap1-10-permit"
+# - "route-map rmap1 deny 20"
+# - "match community BGPCommunity1 BGPCommunity2"
+# - "match ip address prefix-list AllowPrefix1 AllowPrefix2"
+# - "description rmap1-20-deny"
+# - "set dampening 30 1500 10000 120"
+# - "route-map rmap2 permit 20"
+# - "match interface Ethernet1/1"
+# - "match ipv6 address prefix-list AllowIPv6Prefix"
+# - "set as-path prepend 65563 65568 65569"
+# - "description rmap2-20-permit"
+# - "continue 40"
+# - "set comm-list BGPCommunity delete"
+# - "route-map rmap2 deny 40"
+# - "match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM"
+# - "match route-type level-1 level-2"
+# - "match tag 2"
+# - "description rmap2-40-deny"
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap1 deny 20
+# match community BGPCommunity1 BGPCommunity2
+# match ip address prefix-list AllowPrefix1 AllowPrefix2
+# description rmap1-20-deny
+# set dampening 30 1500 10000 120
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+# route-map rmap2 deny 40
+# match ip multicast source 203.0.113.0/24 group-range 239.0.0.1 to 239.255.255.255 rp 192.0.2.0/24 rp-type ASM
+# match route-type level-1 level-2
+# match tag 2
+# description rmap2-40-deny
+
+- name: Parse externally provided route-maps configuration
+ cisco.nxos.nxos_route_maps:
+ running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - action: deny
+# description: rmap1-20-deny
+# match:
+# community:
+# community_list:
+# - BGPCommunity1
+# - BGPCommunity2
+# ip:
+# address:
+# prefix_lists:
+# - AllowPrefix1
+# - AllowPrefix2
+# sequence: 20
+# set:
+# dampening:
+# half_life: 30
+# max_suppress_time: 120
+# start_reuse_route: 1500
+# start_suppress_route: 10000
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+# - action: deny
+# description: rmap2-40-deny
+# match:
+# ip:
+# multicast:
+# group_range:
+# first: 239.0.0.1
+# last: 239.255.255.255
+# rp:
+# prefix: 192.0.2.0/24
+# rp_type: ASM
+# source: 203.0.113.0/24
+# route_types:
+# - level-1
+# - level-2
+# tags:
+# - 2
+# sequence: 40
+
+# Using gathered
+
+# Existing route-map config
+# ---------------------------
+# nxos-9k-rdo# show running-config | section "^route-map"
+# route-map rmap1 permit 10
+# match as-number 65564
+# match as-path Allow40
+# match ip address acl_1
+# description rmap1-10-permit
+# route-map rmap2 permit 20
+# match interface Ethernet1/1
+# match ipv6 address prefix-list AllowIPv6Prefix
+# set as-path prepend 65563 65568 65569
+# description rmap2-20-permit
+# continue 40
+# set comm-list BGPCommunity delete
+
+- name: Gather route-maps facts using gathered
+ cisco.nxos.nxos_route_maps:
+ state: gathered
+
+# gathered:
+# - route_map: rmap1
+# entries:
+# - action: permit
+# description: rmap1-10-permit
+# match:
+# as_number:
+# asn:
+# - '65564'
+# as_path:
+# - Allow40
+# ip:
+# address:
+# access_list: acl_1
+# sequence: 10
+#
+# - route_map: rmap2
+# entries:
+# - action: permit
+# continue_sequence: 40
+# description: rmap2-20-permit
+# match:
+# interfaces:
+# - Ethernet1/1
+# ipv6:
+# address:
+# prefix_lists:
+# - AllowIPv6Prefix
+# sequence: 20
+# set:
+# as_path:
+# prepend:
+# as_number:
+# - '65563'
+# - '65568'
+# - '65569'
+# comm_list: BGPCommunity
+#
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample:
+ - "route-map rmap1 permit 10"
+ - "match as-number 65564"
+ - "match as-path Allow40"
+ - "match ip address acl_1"
+ - "description rmap1-10-permit"
+ - "route-map rmap1 deny 20"
+ - "match community BGPCommunity1 BGPCommunity2"
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.route_maps.route_maps import (
+ Route_mapsArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.route_maps.route_maps import (
+ Route_maps,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Route_mapsArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Route_maps(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_rpm.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_rpm.py
new file mode 100644
index 00000000..7d7ab4db
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_rpm.py
@@ -0,0 +1,396 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_rpm
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Install patch or feature rpms on Cisco NX-OS devices.
+description:
+- Install software maintenance upgrade (smu) RPMS and 3rd party RPMS on Cisco NX-OS
+ devices.
+version_added: 1.0.0
+author: Sai Chintalapudi (@saichint)
+notes:
+- Tested against NXOSv 7.0(3)I2(5), 7.0(3)I4(6), 7.0(3)I5(3), 7.0(3)I6(1), 7.0(3)I7(3)
+- Unsupported for Cisco MDS
+- For patches, the minimum platform version needed is 7.0(3)I2(5)
+- For feature rpms, the minimum platform version needed is 7.0(3)I6(1)
+- The module manages the entire RPM lifecycle (Add, activate, commit, deactivate,
+ remove)
+- For reload patches, this module is NOT idempotent until the patch is committed.
+options:
+ pkg:
+ description:
+ - Name of the RPM package.
+ type: str
+ file_system:
+ description:
+ - The remote file system of the device. If omitted, devices that support a file_system
+ parameter will use their default values.
+ default: bootflash
+ type: str
+ aggregate:
+ description:
+ - List of RPM/patch definitions.
+ type: list
+ elements: dict
+ suboptions:
+ pkg:
+ description:
+ - Name of the RPM package.
+ required: True
+ type: str
+ file_system:
+ description:
+ - The remote file system of the device. If omitted, devices that support a file_system
+ parameter will use their default values.
+ type: str
+ state:
+ description:
+ - If the state is present, the rpm will be installed, If the state is absent,
+ it will be removed.
+ choices:
+ - present
+ - absent
+ type: str
+ state:
+ description:
+ - If the state is present, the rpm will be installed, If the state is absent,
+ it will be removed.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_rpm:
+ pkg: nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000.rpm
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["install add bootflash:nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000.rpm forced",
+ "install activate nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000 forced",
+ "install commit nxos.sample-n9k_ALL-1.0.0-7.0.3.I7.3.lib32_n9000"]
+"""
+
+
+import time
+
+from copy import deepcopy
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_default_spec,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ iteration = 0
+ cmds = [{"command": command, "output": "text"}]
+
+ while iteration < 10:
+ body = run_commands(module, cmds)[0]
+ if body:
+ return body
+ else:
+ time.sleep(2)
+ iteration += 1
+
+
+def remote_file_exists(module, dst, file_system):
+ command = "dir {0}:/{1}".format(file_system, dst)
+ body = execute_show_command(command, module)
+ if "No such file" in body:
+ return False
+ return True
+
+
+def config_cmd_operation(module, cmd):
+ iteration = 0
+ while iteration < 10:
+ msg = load_config(module, [cmd], True)
+ if msg:
+ if (
+ "another install operation is in progress" in msg[0].lower()
+ or "failed" in msg[0].lower()
+ ):
+ time.sleep(2)
+ iteration += 1
+ else:
+ return
+ else:
+ return
+
+
+def validate_operation(module, show_cmd, cfg_cmd, pkg, pkg_not_present):
+ iteration = 0
+ while iteration < 10:
+ body = execute_show_command(show_cmd, module)
+ if pkg_not_present:
+ if pkg not in body:
+ return
+ else:
+ if pkg in body:
+ return
+ time.sleep(2)
+ iteration += 1
+
+ err = 'Operation "{0}" Failed'.format(cfg_cmd)
+ module.fail_json(msg=err)
+
+
+def add_operation(module, show_cmd, file_system, full_pkg, pkg):
+ cmd = "install add {0}:{1}".format(file_system, full_pkg)
+ config_cmd_operation(module, cmd)
+ validate_operation(module, show_cmd, cmd, pkg, False)
+ return cmd
+
+
+def activate_operation(module, show_cmd, pkg):
+ cmd = "install activate {0} forced".format(pkg)
+ config_cmd_operation(module, cmd)
+ validate_operation(module, show_cmd, cmd, pkg, False)
+ return cmd
+
+
+def activate_reload(module, pkg, flag):
+ iteration = 0
+ if flag:
+ cmd = "install activate {0} forced".format(pkg)
+ else:
+ cmd = "install deactivate {0} forced".format(pkg)
+ opts = {"ignore_timeout": True}
+ while iteration < 10:
+ msg = load_config(module, [cmd], True, opts)
+ if msg:
+ if isinstance(msg[0], int):
+ if msg[0] == -32603:
+ return cmd
+ elif isinstance(msg[0], str):
+ if "socket is closed" in msg[0].lower():
+ return cmd
+ if (
+ "another install operation is in progress" in msg[0].lower()
+ or "failed" in msg[0].lower()
+ ):
+ time.sleep(2)
+ iteration += 1
+
+
+def commit_operation(module, show_cmd, pkg, flag):
+ cmd = "install commit {0}".format(pkg)
+ config_cmd_operation(module, cmd)
+ validate_operation(module, show_cmd, cmd, pkg, flag)
+ return cmd
+
+
+def deactivate_operation(module, show_cmd, pkg, flag):
+ cmd = "install deactivate {0} forced".format(pkg)
+ config_cmd_operation(module, cmd)
+ validate_operation(module, show_cmd, cmd, pkg, flag)
+ return cmd
+
+
+def terminal_operation(module, config):
+ if config:
+ cmd = "terminal dont-ask"
+ else:
+ cmd = "no terminal dont-ask"
+ config_cmd_operation(module, cmd)
+ return cmd
+
+
+def remove_operation(module, show_cmd, pkg):
+ commands = []
+ commands.append(terminal_operation(module, True))
+ cmd = "install remove {0} forced".format(pkg)
+ config_cmd_operation(module, cmd)
+ validate_operation(module, show_cmd, cmd, pkg, True)
+ commands.append(cmd)
+ commands.append(terminal_operation(module, False))
+ return commands
+
+
+def install_remove_rpm(module, full_pkg, file_system, state):
+ commands = []
+ reload_patch = False
+
+ splitted_pkg = full_pkg.split(".")
+ pkg = ".".join(splitted_pkg[0:-1])
+
+ show_inactive = "show install inactive"
+ show_active = "show install active"
+ show_commit = "show install committed"
+ show_patches = "show install patches"
+ show_pkg_info = "show install pkg-info {0}".format(pkg)
+
+ if state == "present":
+ inactive_body = execute_show_command(show_inactive, module)
+ active_body = execute_show_command(show_active, module)
+
+ if pkg not in inactive_body and pkg not in active_body:
+ commands.append(add_operation(module, show_inactive, file_system, full_pkg, pkg))
+
+ patch_type_body = execute_show_command(show_pkg_info, module)
+ if patch_type_body and "Patch Type : reload" in patch_type_body:
+ # This is reload smu/patch rpm
+ reload_patch = True
+
+ if pkg not in active_body:
+ if reload_patch:
+ commands.append(activate_reload(module, pkg, True))
+ return commands
+ else:
+ commands.append(activate_operation(module, show_active, pkg))
+
+ commit_body = execute_show_command(show_commit, module)
+ if pkg not in commit_body:
+ patch_body = execute_show_command(show_patches, module)
+ if pkg in patch_body:
+ # This is smu/patch rpm
+ commands.append(commit_operation(module, show_commit, pkg, False))
+ else:
+ err = 'Operation "install activate {0} forced" Failed'.format(pkg)
+ module.fail_json(msg=err)
+
+ else:
+ commit_body = execute_show_command(show_commit, module)
+ active_body = execute_show_command(show_active, module)
+
+ patch_type_body = execute_show_command(show_pkg_info, module)
+ if patch_type_body and "Patch Type : reload" in patch_type_body:
+ # This is reload smu/patch rpm
+ reload_patch = True
+
+ if pkg in commit_body and pkg in active_body:
+ if reload_patch:
+ commands.append(activate_reload(module, pkg, False))
+ return commands
+ else:
+ commands.append(deactivate_operation(module, show_active, pkg, True))
+ commit_body = execute_show_command(show_commit, module)
+ if pkg in commit_body:
+ # This is smu/patch rpm
+ commands.append(commit_operation(module, show_commit, pkg, True))
+ commands.extend(remove_operation(module, show_inactive, pkg))
+
+ elif pkg in commit_body:
+ # This is smu/patch rpm
+ commands.append(commit_operation(module, show_commit, pkg, True))
+ commands.extend(remove_operation(module, show_inactive, pkg))
+
+ elif pkg in active_body:
+ # This is smu/patch rpm
+ if reload_patch:
+ commands.append(activate_reload(module, pkg, False))
+ return commands
+ else:
+ commands.append(deactivate_operation(module, show_inactive, pkg, False))
+ commands.extend(remove_operation(module, show_inactive, pkg))
+
+ else:
+ inactive_body = execute_show_command(show_inactive, module)
+ if pkg in inactive_body:
+ commands.extend(remove_operation(module, show_inactive, pkg))
+
+ return commands
+
+
+def main():
+ element_spec = dict(
+ pkg=dict(type="str"),
+ file_system=dict(type="str", default="bootflash"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ aggregate_spec = deepcopy(element_spec)
+ aggregate_spec["pkg"] = dict(required=True)
+
+ # remove default in aggregate spec, to handle common arguments
+ remove_default_spec(aggregate_spec)
+
+ argument_spec = dict(aggregate=dict(type="list", elements="dict", options=aggregate_spec))
+
+ argument_spec.update(element_spec)
+ required_one_of = [["pkg", "aggregate"]]
+ mutually_exclusive = [["pkg", "aggregate"]]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_one_of=required_one_of,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=False,
+ )
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ aggregate = module.params.get("aggregate")
+ objects = []
+ if aggregate:
+ for item in aggregate:
+ for key in item:
+ if item.get(key) is None:
+ item[key] = module.params[key]
+
+ d = item.copy()
+ objects.append(d)
+ else:
+ objects.append(
+ {
+ "pkg": module.params["pkg"],
+ "file_system": module.params["file_system"],
+ "state": module.params["state"],
+ },
+ )
+
+ for obj in objects:
+ if obj["state"] == "present":
+ remote_exists = remote_file_exists(module, obj["pkg"], file_system=obj["file_system"])
+
+ if not remote_exists:
+ module.fail_json(msg="The requested package doesn't exist on the device")
+
+ cmds = install_remove_rpm(module, obj["pkg"], obj["file_system"], obj["state"])
+
+ if cmds:
+ results["changed"] = True
+ results["commands"].extend(cmds)
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snapshot.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snapshot.py
new file mode 100644
index 00000000..9697a128
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snapshot.py
@@ -0,0 +1,413 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+DOCUMENTATION = """
+module: nxos_snapshot
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manage snapshots of the running states of selected features.
+description:
+- Create snapshots of the running states of selected features, add new show commands
+ for snapshot creation, delete and compare existing snapshots.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- C(transport=cli) may cause timeout errors.
+- The C(element_key1) and C(element_key2) parameter specify the tags used to distinguish
+ among row entries. In most cases, only the element_key1 parameter needs to specified
+ to be able to distinguish among row entries.
+- C(action=compare) will always store a comparison report on a local file.
+options:
+ action:
+ description:
+ - Define what snapshot action the module would perform.
+ required: true
+ choices:
+ - add
+ - compare
+ - create
+ - delete
+ - delete_all
+ type: str
+ snapshot_name:
+ description:
+ - Snapshot name, to be used when C(action=create) or C(action=delete).
+ type: str
+ description:
+ description:
+ - Snapshot description to be used when C(action=create).
+ type: str
+ snapshot1:
+ description:
+ - First snapshot to be used when C(action=compare).
+ type: str
+ snapshot2:
+ description:
+ - Second snapshot to be used when C(action=compare).
+ type: str
+ comparison_results_file:
+ description:
+ - Name of the file where snapshots comparison will be stored when C(action=compare).
+ type: str
+ compare_option:
+ description:
+ - Snapshot options to be used when C(action=compare).
+ choices:
+ - summary
+ - ipv4routes
+ - ipv6routes
+ type: str
+ section:
+ description:
+ - Used to name the show command output, to be used when C(action=add).
+ type: str
+ show_command:
+ description:
+ - Specify a new show command, to be used when C(action=add).
+ type: str
+ row_id:
+ description:
+ - Specifies the tag of each row entry of the show command's XML output, to be
+ used when C(action=add).
+ type: str
+ element_key1:
+ description:
+ - Specify the tags used to distinguish among row entries, to be used when C(action=add).
+ type: str
+ element_key2:
+ description:
+ - Specify the tags used to distinguish among row entries, to be used when C(action=add).
+ type: str
+ save_snapshot_locally:
+ description:
+ - Specify to locally store a new created snapshot, to be used when C(action=create).
+ type: bool
+ default: no
+ path:
+ description:
+ - Specify the path of the file where new created snapshot or snapshots comparison
+ will be stored, to be used when C(action=create) and C(save_snapshot_locally=true)
+ or C(action=compare).
+ default: ./
+ type: str
+"""
+
+EXAMPLES = """
+# Create a snapshot and store it locally
+- cisco.nxos.nxos_snapshot:
+ action: create
+ snapshot_name: test_snapshot
+ description: Done with Ansible
+ save_snapshot_locally: true
+ path: /home/user/snapshots/
+
+# Delete a snapshot
+- cisco.nxos.nxos_snapshot:
+ action: delete
+ snapshot_name: test_snapshot
+
+# Delete all existing snapshots
+- cisco.nxos.nxos_snapshot:
+ action: delete_all
+
+# Add a show command for snapshots creation
+- cisco.nxos.nxos_snapshot:
+ section: myshow
+ show_command: show ip interface brief
+ row_id: ROW_intf
+ element_key1: intf-name
+
+# Compare two snapshots
+- cisco.nxos.nxos_snapshot:
+ action: compare
+ snapshot1: pre_snapshot
+ snapshot2: post_snapshot
+ comparison_results_file: compare_snapshots.txt
+ compare_option: summary
+ path: ../snapshot_reports/
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: verbose mode
+ type: list
+ sample: ["snapshot create post_snapshot Post-snapshot"]
+"""
+
+import os
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ command = [{"command": command, "output": "text"}]
+
+ return run_commands(module, command)
+
+
+def get_existing(module):
+ existing = []
+ command = "show snapshots"
+
+ body = execute_show_command(command, module)[0]
+ if body:
+ split_body = body.splitlines()
+ snapshot_regex = (
+ r"(?P<name>\S+)\s+(?P<date>\w+\s+\w+\s+\d+\s+\d+"
+ r":\d+:\d+\s+\d+)\s+(?P<description>.*)"
+ )
+ for snapshot in split_body:
+ temp = {}
+ try:
+ match_snapshot = re.match(snapshot_regex, snapshot, re.DOTALL)
+ snapshot_group = match_snapshot.groupdict()
+ temp["name"] = snapshot_group["name"]
+ temp["date"] = snapshot_group["date"]
+ temp["description"] = snapshot_group["description"]
+ existing.append(temp)
+ except AttributeError:
+ pass
+
+ return existing
+
+
+def action_create(module, existing_snapshots):
+ commands = list()
+ exist = False
+ for snapshot in existing_snapshots:
+ if module.params["snapshot_name"] == snapshot["name"]:
+ exist = True
+
+ if exist is False:
+ commands.append(
+ "snapshot create {0} {1}".format(
+ module.params["snapshot_name"],
+ module.params["description"],
+ ),
+ )
+
+ return commands
+
+
+def action_add(module, existing_snapshots):
+ commands = list()
+ command = "show snapshot sections"
+ sections = []
+ body = execute_show_command(command, module)[0]
+
+ if body:
+ section_regex = r".*\[(?P<section>\S+)\].*"
+ split_body = body.split("\n\n")
+ for section in split_body:
+ temp = {}
+ for line in section.splitlines():
+ try:
+ match_section = re.match(section_regex, section, re.DOTALL)
+ temp["section"] = match_section.groupdict()["section"]
+ except (AttributeError, KeyError):
+ pass
+
+ if "show command" in line:
+ temp["show_command"] = line.split("show command: ")[1]
+ elif "row id" in line:
+ temp["row_id"] = line.split("row id: ")[1]
+ elif "key1" in line:
+ temp["element_key1"] = line.split("key1: ")[1]
+ elif "key2" in line:
+ temp["element_key2"] = line.split("key2: ")[1]
+
+ if temp:
+ sections.append(temp)
+
+ proposed = {
+ "section": module.params["section"],
+ "show_command": module.params["show_command"],
+ "row_id": module.params["row_id"],
+ "element_key1": module.params["element_key1"],
+ "element_key2": module.params["element_key2"] or "-",
+ }
+
+ if proposed not in sections:
+ if module.params["element_key2"]:
+ commands.append(
+ 'snapshot section add {0} "{1}" {2} {3} {4}'.format(
+ module.params["section"],
+ module.params["show_command"],
+ module.params["row_id"],
+ module.params["element_key1"],
+ module.params["element_key2"],
+ ),
+ )
+ else:
+ commands.append(
+ 'snapshot section add {0} "{1}" {2} {3}'.format(
+ module.params["section"],
+ module.params["show_command"],
+ module.params["row_id"],
+ module.params["element_key1"],
+ ),
+ )
+
+ return commands
+
+
+def action_compare(module, existing_snapshots):
+ command = "show snapshot compare {0} {1}".format(
+ module.params["snapshot1"],
+ module.params["snapshot2"],
+ )
+
+ if module.params["compare_option"]:
+ command += " {0}".format(module.params["compare_option"])
+
+ body = execute_show_command(command, module)[0]
+ return body
+
+
+def action_delete(module, existing_snapshots):
+ commands = list()
+
+ exist = False
+ for snapshot in existing_snapshots:
+ if module.params["snapshot_name"] == snapshot["name"]:
+ exist = True
+
+ if exist:
+ commands.append("snapshot delete {0}".format(module.params["snapshot_name"]))
+
+ return commands
+
+
+def action_delete_all(module, existing_snapshots):
+ commands = list()
+ if existing_snapshots:
+ commands.append("snapshot delete all")
+ return commands
+
+
+def invoke(name, *args, **kwargs):
+ func = globals().get(name)
+ if func:
+ return func(*args, **kwargs)
+
+
+def write_on_file(content, filename, module):
+ path = module.params["path"]
+ if path[-1] != "/":
+ path += "/"
+ filepath = "{0}{1}".format(path, filename)
+ try:
+ report = open(filepath, "w")
+ report.write(content)
+ report.close()
+ except Exception:
+ module.fail_json(msg="Error while writing on file.")
+
+ return filepath
+
+
+def main():
+ argument_spec = dict(
+ action=dict(
+ required=True,
+ choices=["create", "add", "compare", "delete", "delete_all"],
+ ),
+ snapshot_name=dict(type="str"),
+ description=dict(type="str"),
+ snapshot1=dict(type="str"),
+ snapshot2=dict(type="str"),
+ compare_option=dict(choices=["summary", "ipv4routes", "ipv6routes"]),
+ comparison_results_file=dict(type="str"),
+ section=dict(type="str"),
+ show_command=dict(type="str"),
+ row_id=dict(type="str"),
+ element_key1=dict(type="str", no_log=False),
+ element_key2=dict(type="str", no_log=False),
+ save_snapshot_locally=dict(type="bool", default=False),
+ path=dict(type="str", default="./"),
+ )
+
+ required_if = [
+ (
+ "action",
+ "compare",
+ ["snapshot1", "snapshot2", "comparison_results_file"],
+ ),
+ ("action", "create", ["snapshot_name", "description"]),
+ (
+ "action",
+ "add",
+ ["section", "show_command", "row_id", "element_key1"],
+ ),
+ ("action", "delete", ["snapshot_name"]),
+ ]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_if=required_if,
+ supports_check_mode=True,
+ )
+
+ action = module.params["action"]
+ comparison_results_file = module.params["comparison_results_file"]
+
+ if not os.path.isdir(module.params["path"]):
+ module.fail_json(msg="{0} is not a valid directory name.".format(module.params["path"]))
+
+ existing_snapshots = invoke("get_existing", module)
+ action_results = invoke("action_%s" % action, module, existing_snapshots)
+
+ result = {"changed": False, "commands": []}
+
+ if not module.check_mode:
+ if action == "compare":
+ result["commands"] = []
+
+ if module.params["path"] and comparison_results_file:
+ snapshot1 = module.params["snapshot1"]
+ snapshot2 = module.params["snapshot2"]
+ compare_option = module.params["compare_option"]
+ command = "show snapshot compare {0} {1}".format(snapshot1, snapshot2)
+ if compare_option:
+ command += " {0}".format(compare_option)
+ content = execute_show_command(command, module)[0]
+ if content:
+ write_on_file(content, comparison_results_file, module)
+ else:
+ if action_results:
+ load_config(module, action_results)
+ result["commands"] = action_results
+ result["changed"] = True
+
+ if (
+ action == "create"
+ and module.params["path"]
+ and module.params["save_snapshot_locally"]
+ ):
+ command = "show snapshot dump {0} | json".format(module.params["snapshot_name"])
+ content = execute_show_command(command, module)[0]
+ if content:
+ write_on_file(str(content), module.params["snapshot_name"], module)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_community.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_community.py
new file mode 100644
index 00000000..4e94b1f0
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_community.py
@@ -0,0 +1,254 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_community
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP community configs.
+description:
+- Manages SNMP community configuration.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+options:
+ community:
+ description:
+ - Case-sensitive community string.
+ required: true
+ type: str
+ access:
+ description:
+ - Access type for community.
+ choices:
+ - ro
+ - rw
+ type: str
+ group:
+ description:
+ - Group to which the community belongs.
+ type: str
+ acl:
+ description:
+ - ACL name to filter snmp requests or keyword 'default'.
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# ensure snmp community is configured
+- cisco.nxos.nxos_snmp_community:
+ community: TESTING7
+ group: network-operator
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["snmp-server community TESTING7 group network-operator"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ if "show run" not in command:
+ output = "json"
+ else:
+ output = "text"
+ cmds = [{"command": command, "output": output}]
+
+ body = run_commands(module, cmds)
+ return body
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_groups(module):
+ data = execute_show_command("show snmp group", module)[0]
+ group_list = []
+
+ try:
+ group_table = data["TABLE_role"]["ROW_role"]
+ for group in group_table:
+ group_list.append(group["role_name"])
+ except (KeyError, AttributeError):
+ pass
+
+ return group_list
+
+
+def get_snmp_community(module, name):
+ command = "show run snmp all | grep word-exp {0}".format(name)
+ data = execute_show_command(command, module)[0]
+ community_dict = {}
+
+ if not data:
+ return community_dict
+
+ community_re = r"snmp-server community (\S+)"
+ mo = re.search(community_re, data)
+ if mo:
+ community_name = mo.group(1)
+ else:
+ return community_dict
+
+ community_dict["group"] = None
+ group_re = r"snmp-server community {0} group (\S+)".format(community_name)
+ mo = re.search(group_re, data)
+ if mo:
+ community_dict["group"] = mo.group(1)
+
+ community_dict["acl"] = None
+ acl_re = r"snmp-server community {0} use-acl (\S+)".format(community_name)
+ mo = re.search(acl_re, data)
+ if mo:
+ community_dict["acl"] = mo.group(1)
+
+ return community_dict
+
+
+def config_snmp_community(delta, community):
+ CMDS = {
+ "group": "snmp-server community {0} group {group}",
+ "acl": "snmp-server community {0} use-acl {acl}",
+ "no_acl": "no snmp-server community {0} use-acl {no_acl}",
+ }
+ commands = []
+ for k in delta.keys():
+ cmd = CMDS.get(k).format(community, **delta)
+ if cmd:
+ if "group" in cmd:
+ commands.insert(0, cmd)
+ else:
+ commands.append(cmd)
+ cmd = None
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ community=dict(required=True, type="str"),
+ access=dict(choices=["ro", "rw"]),
+ group=dict(type="str"),
+ acl=dict(type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_one_of=[["access", "group"]],
+ mutually_exclusive=[["access", "group"]],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ access = module.params["access"]
+ group = module.params["group"]
+ community = module.params["community"]
+ acl = module.params["acl"]
+ state = module.params["state"]
+
+ if access:
+ if access == "ro":
+ group = "network-operator"
+ elif access == "rw":
+ group = "network-admin"
+
+ # group check - ensure group being configured exists on the device
+ configured_groups = get_snmp_groups(module)
+
+ if group not in configured_groups:
+ module.fail_json(msg="Group not on switch. Please add before moving forward")
+
+ existing = get_snmp_community(module, community)
+ args = dict(group=group, acl=acl)
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta.get("acl") == "default":
+ delta.pop("acl")
+ if existing.get("acl"):
+ delta["no_acl"] = existing.get("acl")
+
+ commands = []
+
+ if state == "absent":
+ if existing:
+ command = "no snmp-server community {0}".format(community)
+ commands.append(command)
+ elif state == "present":
+ if delta:
+ command = config_snmp_community(dict(delta), community)
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_contact.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_contact.py
new file mode 100644
index 00000000..49dbc8a8
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_contact.py
@@ -0,0 +1,151 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_contact
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP contact info.
+description:
+- Manages SNMP contact information.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- C(state=absent) removes the contact configuration if it is configured.
+options:
+ contact:
+ description:
+ - Contact information.
+ required: true
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# ensure snmp contact is configured
+- cisco.nxos.nxos_snmp_contact:
+ contact: Test
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["snmp-server contact New_Test"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ command = {"command": command, "output": "text"}
+
+ return run_commands(module, command)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_contact(module):
+ contact = {}
+ contact_regex = r"^\s*snmp-server\scontact\s(?P<contact>.+)$"
+
+ body = execute_show_command("show run snmp", module)[0]
+ match_contact = re.search(contact_regex, body, re.M)
+ if match_contact:
+ contact["contact"] = match_contact.group("contact")
+
+ return contact
+
+
+def main():
+ argument_spec = dict(
+ contact=dict(required=True, type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ contact = module.params["contact"]
+ state = module.params["state"]
+
+ existing = get_snmp_contact(module)
+ commands = []
+
+ if state == "absent":
+ if existing and existing["contact"] == contact:
+ commands.append("no snmp-server contact")
+ elif state == "present":
+ if not existing or existing["contact"] != contact:
+ commands.append("snmp-server contact {0}".format(contact))
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_host.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_host.py
new file mode 100644
index 00000000..3c23374c
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_host.py
@@ -0,0 +1,513 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_host
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP host configuration.
+description:
+- Manages SNMP host configuration parameters.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- C(state=absent) removes the host configuration if it is configured.
+options:
+ snmp_host:
+ description:
+ - IP address of hostname of target host.
+ required: true
+ type: str
+ version:
+ description:
+ - SNMP version. If this is not specified, v1 is used.
+ choices:
+ - v1
+ - v2c
+ - v3
+ type: str
+ v3:
+ description:
+ - Use this when verion is v3. SNMPv3 Security level.
+ choices:
+ - noauth
+ - auth
+ - priv
+ type: str
+ community:
+ description:
+ - Community string or v3 username.
+ type: str
+ udp:
+ description:
+ - UDP port number (0-65535).
+ default: '162'
+ type: str
+ snmp_type:
+ description:
+ - type of message to send to host. If this is not specified, trap type is used.
+ choices:
+ - trap
+ - inform
+ type: str
+ vrf:
+ description:
+ - VRF to use to source traffic to source. If state = absent, the vrf is removed.
+ type: str
+ vrf_filter:
+ description:
+ - Name of VRF to filter. If state = absent, the vrf is removed from the filter.
+ type: str
+ src_intf:
+ description:
+ - Source interface. Must be fully qualified interface name. If state = absent,
+ the interface is removed.
+ type: str
+ state:
+ description:
+ - Manage the state of the resource. If state = present, the host is added to the
+ configuration. If only vrf and/or vrf_filter and/or src_intf are given, they
+ will be added to the existing host configuration. If state = absent, the host
+ is removed if community parameter is given. It is possible to remove only vrf
+ and/or src_int and/or vrf_filter by providing only those parameters and no community
+ parameter.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# ensure snmp host is configured
+- cisco.nxos.nxos_snmp_host:
+ snmp_host: 192.0.2.3
+ community: TESTING
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["snmp-server host 192.0.2.3 filter-vrf another_test_vrf"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ command = {"command": command, "output": "json"}
+
+ return run_commands(module, command)
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = str(value)
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_host(host, udp, module):
+ body = execute_show_command("show snmp host", module)
+
+ host_map = {
+ "port": "udp",
+ "version": "version",
+ "level": "v3",
+ "type": "snmp_type",
+ "secname": "community",
+ }
+
+ host_map_5k = {
+ "port": "udp",
+ "version": "version",
+ "sec_level": "v3",
+ "notif_type": "snmp_type",
+ "commun_or_user": "community",
+ }
+
+ resource = {}
+
+ if body:
+ try:
+ resource_table = body[0]["TABLE_host"]["ROW_host"]
+
+ if isinstance(resource_table, dict):
+ resource_table = [resource_table]
+
+ for each in resource_table:
+ key = str(each["host"]) + "_" + str(each["port"]).strip()
+ src = each.get("src_intf")
+ host_resource = apply_key_map(host_map, each)
+
+ if src:
+ host_resource["src_intf"] = src
+ if re.search(r"interface:", src):
+ host_resource["src_intf"] = src.split(":")[1].strip()
+
+ vrf_filt = each.get("TABLE_vrf_filters")
+ if vrf_filt:
+ vrf_filter = vrf_filt["ROW_vrf_filters"]["vrf_filter"].split(":")[1].split(",")
+ filters = [vrf.strip() for vrf in vrf_filter]
+ host_resource["vrf_filter"] = filters
+
+ vrf = each.get("vrf")
+ if vrf:
+ host_resource["vrf"] = vrf.split(":")[1].strip()
+ resource[key] = host_resource
+ except KeyError:
+ # Handle the 5K case
+ try:
+ resource_table = body[0]["TABLE_hosts"]["ROW_hosts"]
+
+ if isinstance(resource_table, dict):
+ resource_table = [resource_table]
+
+ for each in resource_table:
+ key = str(each["address"]) + "_" + str(each["port"]).strip()
+ src = each.get("src_intf")
+ host_resource = apply_key_map(host_map_5k, each)
+
+ if src:
+ host_resource["src_intf"] = src
+ if re.search(r"interface:", src):
+ host_resource["src_intf"] = src.split(":")[1].strip()
+
+ vrf = each.get("use_vrf_name")
+ if vrf:
+ host_resource["vrf"] = vrf.strip()
+
+ vrf_filt = each.get("TABLE_filter_vrf")
+ if vrf_filt:
+ vrf_filter = vrf_filt["ROW_filter_vrf"]["filter_vrf_name"].split(",")
+ filters = [vrf.strip() for vrf in vrf_filter]
+ host_resource["vrf_filter"] = filters
+
+ resource[key] = host_resource
+ except (KeyError, AttributeError, TypeError):
+ return resource
+ except (AttributeError, TypeError):
+ return resource
+
+ find = resource.get(host + "_" + udp)
+
+ if find:
+ fix_find = {}
+ for key, value in find.items():
+ if isinstance(value, str):
+ fix_find[key] = value.strip()
+ else:
+ fix_find[key] = value
+ return fix_find
+
+ return {}
+
+
+def remove_snmp_host(host, udp, existing):
+ commands = []
+ if existing["version"] == "v3":
+ existing["version"] = "3"
+ command = "no snmp-server host {0} {snmp_type} version \
+ {version} {v3} {community} udp-port {1}".format(
+ host, udp, **existing
+ )
+
+ elif existing["version"] == "v2c":
+ existing["version"] = "2c"
+ command = "no snmp-server host {0} {snmp_type} version \
+ {version} {community} udp-port {1}".format(
+ host, udp, **existing
+ )
+
+ elif existing["version"] == "v1":
+ existing["version"] = "1"
+ command = "no snmp-server host {0} {snmp_type} version \
+ {version} {community} udp-port {1}".format(
+ host, udp, **existing
+ )
+
+ if command:
+ commands.append(command)
+ return commands
+
+
+def remove_vrf(host, udp, proposed, existing):
+ commands = []
+ if existing.get("vrf"):
+ commands.append(
+ "no snmp-server host {0} use-vrf \
+ {1} udp-port {2}".format(
+ host,
+ proposed.get("vrf"),
+ udp,
+ ),
+ )
+ return commands
+
+
+def remove_filter(host, udp, proposed, existing):
+ commands = []
+ if existing.get("vrf_filter"):
+ if proposed.get("vrf_filter") in existing.get("vrf_filter"):
+ commands.append(
+ "no snmp-server host {0} filter-vrf \
+ {1} udp-port {2}".format(
+ host,
+ proposed.get("vrf_filter"),
+ udp,
+ ),
+ )
+ return commands
+
+
+def remove_src(host, udp, proposed, existing):
+ commands = []
+ if existing.get("src_intf"):
+ commands.append(
+ "no snmp-server host {0} source-interface \
+ {1} udp-port {2}".format(
+ host,
+ proposed.get("src_intf"),
+ udp,
+ ),
+ )
+ return commands
+
+
+def config_snmp_host(delta, udp, proposed, existing, module):
+ commands = []
+ command_builder = []
+ host = proposed["snmp_host"]
+ cmd = "snmp-server host {0}".format(proposed["snmp_host"])
+
+ snmp_type = delta.get("snmp_type")
+ version = delta.get("version")
+ ver = delta.get("v3")
+ community = delta.get("community")
+
+ command_builder.append(cmd)
+ if any([snmp_type, version, ver, community]):
+ type_string = snmp_type or existing.get("type")
+ if type_string:
+ command_builder.append(type_string)
+
+ version = version or existing.get("version")
+ if version:
+ if version == "v1":
+ vn = "1"
+ elif version == "v2c":
+ vn = "2c"
+ elif version == "v3":
+ vn = "3"
+
+ version_string = "version {0}".format(vn)
+ command_builder.append(version_string)
+
+ if ver:
+ ver_string = ver or existing.get("v3")
+ command_builder.append(ver_string)
+
+ if community:
+ community_string = community or existing.get("community")
+ command_builder.append(community_string)
+
+ udp_string = " udp-port {0}".format(udp)
+ command_builder.append(udp_string)
+
+ cmd = " ".join(command_builder)
+
+ commands.append(cmd)
+
+ CMDS = {
+ "vrf_filter": "snmp-server host {0} filter-vrf {vrf_filter} udp-port {1}",
+ "vrf": "snmp-server host {0} use-vrf {vrf} udp-port {1}",
+ "src_intf": "snmp-server host {0} source-interface {src_intf} udp-port {1}",
+ }
+
+ for key in delta:
+ command = CMDS.get(key)
+ if command:
+ cmd = command.format(host, udp, **delta)
+ commands.append(cmd)
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ snmp_host=dict(required=True, type="str"),
+ community=dict(type="str"),
+ udp=dict(type="str", default="162"),
+ version=dict(choices=["v1", "v2c", "v3"]),
+ src_intf=dict(type="str"),
+ v3=dict(choices=["noauth", "auth", "priv"]),
+ vrf_filter=dict(type="str"),
+ vrf=dict(type="str"),
+ snmp_type=dict(choices=["trap", "inform"]),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ snmp_host = module.params["snmp_host"]
+ community = module.params["community"]
+ udp = module.params["udp"]
+ version = module.params["version"]
+ src_intf = module.params["src_intf"]
+ v3 = module.params["v3"]
+ vrf_filter = module.params["vrf_filter"]
+ vrf = module.params["vrf"]
+ snmp_type = module.params["snmp_type"]
+ state = module.params["state"]
+
+ existing = get_snmp_host(snmp_host, udp, module)
+
+ if version is None:
+ if existing:
+ version = existing.get("version")
+ else:
+ version = "v1"
+
+ if snmp_type is None:
+ if existing:
+ snmp_type = existing.get("snmp_type")
+ else:
+ snmp_type = "trap"
+
+ if v3 is None:
+ if version == "v3" and existing:
+ v3 = existing.get("v3")
+
+ if snmp_type == "inform" and version == "v1":
+ module.fail_json(msg="inform requires snmp v2c or v3")
+
+ if (version == "v1" or version == "v2c") and v3:
+ module.fail_json(msg='param: "v3" should not be used when ' "using version v1 or v2c")
+
+ if not any([vrf_filter, vrf, src_intf]):
+ if not all([snmp_type, version, community, udp]):
+ module.fail_json(
+ msg="when not configuring options like "
+ "vrf_filter, vrf, and src_intf,"
+ "the following params are required: "
+ "type, version, community",
+ )
+
+ if version == "v3" and v3 is None:
+ module.fail_json(
+ msg="when using version=v3, the param v3 "
+ "(options: auth, noauth, priv) is also required",
+ )
+
+ # existing returns the list of vrfs configured for a given host
+ # checking to see if the proposed is in the list
+ store = existing.get("vrf_filter")
+ if existing and store:
+ if vrf_filter not in existing["vrf_filter"]:
+ existing["vrf_filter"] = None
+ else:
+ existing["vrf_filter"] = vrf_filter
+ commands = []
+
+ args = dict(
+ community=community,
+ snmp_host=snmp_host,
+ udp=udp,
+ version=version,
+ src_intf=src_intf,
+ vrf_filter=vrf_filter,
+ v3=v3,
+ vrf=vrf,
+ snmp_type=snmp_type,
+ )
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ if state == "absent" and existing:
+ if proposed.get("community"):
+ commands.append(remove_snmp_host(snmp_host, udp, existing))
+ else:
+ if proposed.get("src_intf"):
+ commands.append(remove_src(snmp_host, udp, proposed, existing))
+ if proposed.get("vrf"):
+ commands.append(remove_vrf(snmp_host, udp, proposed, existing))
+ if proposed.get("vrf_filter"):
+ commands.append(remove_filter(snmp_host, udp, proposed, existing))
+
+ elif state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = config_snmp_host(delta, udp, proposed, existing, module)
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_location.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_location.py
new file mode 100644
index 00000000..d7526a9e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_location.py
@@ -0,0 +1,156 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_location
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP location information.
+description:
+- Manages SNMP location configuration.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+options:
+ location:
+ description:
+ - Location information.
+ required: true
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# ensure snmp location is configured
+- cisco.nxos.nxos_snmp_location:
+ location: Test
+ state: present
+
+# ensure snmp location is not configured
+- cisco.nxos.nxos_snmp_location:
+ location: Test
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["snmp-server location New_Test"]
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ command = {"command": command, "output": "text"}
+
+ return run_commands(module, command)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_location(module):
+ location = {}
+ location_regex = r"^\s*snmp-server\s+location\s+(?P<location>.+)$"
+
+ body = execute_show_command("show run snmp", module)[0]
+ match_location = re.search(location_regex, body, re.M)
+ if match_location:
+ location["location"] = match_location.group("location")
+
+ return location
+
+
+def main():
+ argument_spec = dict(
+ location=dict(required=True, type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ location = module.params["location"]
+ state = module.params["state"]
+
+ existing = get_snmp_location(module)
+ commands = []
+
+ if state == "absent":
+ if existing and existing["location"] == location:
+ commands.append("no snmp-server location")
+ elif state == "present":
+ if not existing or existing["location"] != location:
+ commands.append("snmp-server location {0}".format(location))
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_server.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_server.py
new file mode 100644
index 00000000..7354dc1e
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_server.py
@@ -0,0 +1,1480 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2021 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+"""
+The module file for nxos_snmp_server
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+module: nxos_snmp_server
+short_description: SNMP Server resource module.
+description:
+- This module manages SNMP server configuration on devices running Cisco NX-OS.
+version_added: 2.8.0
+notes:
+- Tested against NX-OS 9.3.6 on Cisco Nexus Switches.
+- This module works with connection C(network_cli) and C(httpapi).
+- Tested against Cisco MDS NX-OS 9.2(2) with connection C(network_cli).
+author: Nilashish Chakraborty (@NilashishC)
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the command B(show running-config | section '^snmp-server').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dict of SNMP server configuration
+ type: dict
+ suboptions:
+ aaa_user:
+ description: Set duration for which aaa-cached snmp user exists.
+ type: dict
+ suboptions:
+ cache_timeout:
+ description: Timeout for which aaa-cached user exists(in secs).
+ type: int
+ communities:
+ description: Set community string and access privs.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description: SNMP community string (Max Size 32).
+ type: str
+ aliases: ["community"]
+ group:
+ description: Group to which the community belongs.
+ type: str
+ ro:
+ description: Read-only access with this community string.
+ type: bool
+ rw:
+ description: Read-write access with this community string.
+ type: bool
+ use_ipv4acl:
+ description:
+ - Specify IPv4 ACL, the ACL name specified must be IPv4 ACL.
+ - This option is unsupported on MDS switches.
+ type: str
+ use_ipv6acl:
+ description:
+ - Specify IPv6 ACL, the ACL name specified after must be IPv6 ACL.
+ - This option is unsupported on MDS switches.
+ type: str
+ contact:
+ description: Modify sysContact.
+ type: str
+ context:
+ description: SNMP context to be mapped.
+ type: dict
+ suboptions:
+ name:
+ description: Name of the SNMP context (Max Size 32).
+ type: str
+ instance:
+ description: Name of the protocol instance (Max Size 32).
+ type: str
+ topology:
+ description: Topology associated with the SNMP context.
+ type: str
+ vrf:
+ description:
+ - VRF associated with the SNMP context.
+ - This option is unsupported on MDS switches.
+ type: str
+ counter:
+ description:
+ - Configure port counter configuration.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ cache:
+ description: Port stats cache.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable port stats cache.
+ type: bool
+ timeout:
+ description: Timeout for which cached port stats exists(in secs).
+ type: int
+ drop:
+ description:
+ - Silently drop unknown v3 user packets.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ unknown_engine_id:
+ description: Unknown v3 engine id.
+ type: bool
+ unknown_user:
+ description: Unknown v3 user.
+ type: bool
+ traps:
+ description: Enable SNMP Traps
+ type: dict
+ suboptions:
+ aaa:
+ description: AAA traps
+ type: dict
+ suboptions:
+ enable:
+ description: Enable AAA traps.
+ type: bool
+ server_state_change:
+ description: AAA server state change notification.
+ type: bool
+ bgp:
+ description: SNMP BGP traps.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable SNMP BGP traps.
+ type: bool
+ bridge:
+ description:
+ - Bridge traps.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable bridge traps.
+ type: bool
+ newroot:
+ description: Enable SNMP STP Bridge MIB newroot traps.
+ type: bool
+ topologychange:
+ description: Enable SNMP STP Bridge MIB topologychange traps.
+ type: bool
+ callhome:
+ description: Callhome traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable callhome traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ event_notify:
+ description: Callhome External Event Notification.
+ type: bool
+ smtp_send_fail:
+ description: SMTP Message Send Fail notification.
+ type: bool
+ cfs:
+ description: CFS traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable cfs traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ merge_failure:
+ description: Merge failure notification.
+ type: bool
+ state_change_notif:
+ description: State change notification.
+ type: bool
+ config:
+ description: Config traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable config traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ ccmCLIRunningConfigChanged:
+ description: Running config change trap.
+ type: bool
+ entity:
+ description: Entity traps.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable entity traps.
+ type: bool
+ cefcMIBEnableStatusNotification:
+ description: CefcMIBEnableStatusNotification.
+ type: bool
+ entity_fan_status_change:
+ description: Entity Fan Status Change.
+ type: bool
+ entity_mib_change:
+ description: Entity MIB change.
+ type: bool
+ entity_module_inserted:
+ description: Entity Module Inserted.
+ type: bool
+ entity_module_removed:
+ description: Entity Module Removed.
+ type: bool
+ entity_module_status_change:
+ description: Entity Module Status Change.
+ type: bool
+ entity_power_out_change:
+ description: Entity Power Out Change.
+ type: bool
+ entity_power_status_change:
+ description: Entity Power Status Change.
+ type: bool
+ entity_sensor:
+ description: Entity sensor.
+ type: bool
+ entity_unrecognised_module:
+ description: Entity Unrecognised Module.
+ type: bool
+ feature_control:
+ description: Feature-Control traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable feature-control traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ featureOpStatusChange:
+ description: Feature operation status change notification.
+ type: bool
+ ciscoFeatOpStatusChange:
+ description: Feature operation status change Notification.
+ type: bool
+ generic:
+ description: Generic traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable generic traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ coldStart:
+ description: Generic coldStart trap.
+ type: bool
+ warmStart:
+ description: Generic warmStart trap.
+ type: bool
+ license:
+ description: License traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable license traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ notify_license_expiry:
+ description: License Expiry Notification.
+ type: bool
+ notify_license_expiry_warning:
+ description: License Expiry Warning Notification.
+ type: bool
+ notify_licensefile_missing:
+ description: License File Missing Notification.
+ type: bool
+ notify_no_license_for_feature:
+ description: No License installed for feature Notification.
+ type: bool
+ link:
+ description: Link traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable link traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ cErrDisableInterfaceEventRev1:
+ description:
+ - Err-disable state notification.
+ - This option is unsupported on MDS switches.
+ type: bool
+ cieLinkDown:
+ description: Cisco extended link state down notification.
+ type: bool
+ cieLinkUp:
+ description: Cisco extended link state up notification.
+ type: bool
+ cisco_xcvr_mon_status_chg:
+ description: Cisco interface transceiver monitor status change notification.
+ type: bool
+ cmn_mac_move_notification:
+ description:
+ - Mac addr move trap.
+ - This option is unsupported on MDS switches.
+ type: bool
+ delayed_link_state_change:
+ description: Delayed link state change.
+ type: bool
+ extended_linkDown:
+ description: IETF extended link state down notification.
+ type: bool
+ extended_linkUp:
+ description: IETF extended link state up notification.
+ type: bool
+ linkDown:
+ description: IETF Link state down notification.
+ type: bool
+ linkUp:
+ description: IETF Link state up notification.
+ type: bool
+ mmode:
+ description:
+ - MMode traps.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable mmode traps.
+ type: bool
+ cseMaintModeChangeNotify:
+ description: Maint Mode Change Notification.
+ type: bool
+ cseNormalModeChangeNotify:
+ description: Normal Mode Change Notification.
+ type: bool
+ ospf:
+ description: SNMP OSPF traps.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable SNMP OSPF traps.
+ type: bool
+ ospfv3:
+ description: SNMP OSPFv3 traps.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable SNMP OSPFv3 traps.
+ type: bool
+ rf:
+ description: RF traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable rf traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ redundancy_framework:
+ description: Redundancy_Framework (RF) Sup switchover MIB.
+ type: bool
+ rmon:
+ description: RMON traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable rmon traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ fallingAlarm:
+ description: Rmon falling alarm.
+ type: bool
+ hcFallingAlarm:
+ description: High capacity Rmon falling alarm.
+ type: bool
+ hcRisingAlarm:
+ description: High capacity Rmon rising alarm.
+ type: bool
+ risingAlarm:
+ description: Rmon rising alarm.
+ type: bool
+ snmp:
+ description: SNMP traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable snmp traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ authentication:
+ description: SNMP authentication trap.
+ type: bool
+ storm_control:
+ description: Storm-Control traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable storm-control traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ cpscEventRev1:
+ description:
+ - Port-Storm-Control-Event.
+ - This option is unsupported on MDS switches.
+ type: bool
+ trap_rate:
+ description: Number of traps per minute.
+ type: bool
+ stpx:
+ description:
+ - Stpx traps.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable stpx traps.
+ type: bool
+ inconsistency:
+ description: Enable SNMP STPX MIB InconsistencyUpdate traps.
+ type: bool
+ loop_inconsistency:
+ description: Enable SNMP STPX MIB LoopInconsistencyUpdate traps.
+ type: bool
+ root_inconsistency:
+ description: Enable SNMP STPX MIB RootInconsistencyUpdate traps.
+ type: bool
+ syslog:
+ description: Enable syslog traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable syslog traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ message_generated:
+ description: Message Generated Notification.
+ type: bool
+ sysmgr:
+ description: Sysmgr traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable sysmgr traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ cseFailSwCoreNotifyExtended:
+ description: Software Core Notification.
+ type: bool
+ system:
+ description: System traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable system traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ clock_change_notification:
+ description: Clock-change-notification traps.
+ type: bool
+ upgrade:
+ description: Upgrade traps.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable upgrade traps.
+ - This option is unsupported on MDS switches.
+ type: bool
+ upgradeJobStatusNotify:
+ description: Upgrade Job Status Notification.
+ type: bool
+ upgradeOpNotifyOnCompletion:
+ description: Upgrade Global Status Notification.
+ type: bool
+ vtp:
+ description:
+ - VTP traps.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable VTP traps.
+ type: bool
+ notifs:
+ description:
+ - Enable vtpConfigRevNumberError vtpConfigDigestEnable vtpConfigRevNumberError vtpConfigDigestError
+ vtpServerDisabled vtpVersionOneDeviceDetected vlanTrunkPortDynamicStatusChange vtpLocalModeChanged
+ vtpVersionInUseChanged notification.
+ type: bool
+ vlancreate:
+ description: Enable vtpVlanCreated notification.
+ type: bool
+ vlandelete:
+ description: Enable vtpVlanDeleted notification.
+ type: bool
+ engine_id:
+ description:
+ - Configure a local SNMPv3 engineID.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ local:
+ description: EngineID of the local agent.
+ type: str
+ global_enforce_priv:
+ description: Globally enforce privacy for all the users.
+ type: bool
+ hosts:
+ description:
+ - Specify hosts to receive SNMP notifications.
+ - SNMP hosts config lines that appear separately in running-config must be added as individual dictionaries.
+ type: list
+ elements: dict
+ suboptions:
+ host:
+ description: IPv4 or IPv6 address or DNS Name of SNMP notification host.
+ type: str
+ community:
+ description: SNMP community string or SNMPv3 user name (Max Size 32).
+ type: str
+ filter_vrf:
+ description:
+ - Filters notifications to the notification host receiver based on the configured VRF.
+ - This option is unsupported on MDS switches.
+ type: str
+ informs:
+ description: Send Inform messages to this host.
+ type: bool
+ source_interface:
+ description: Source interface to be used for sending out SNMP notifications to this host.
+ type: str
+ traps:
+ description: Send Traps messages to this host.
+ type: bool
+ use_vrf:
+ description:
+ - Configures SNMP to use the selected VRF to communicate with the host receiver.
+ - This option is unsupported on MDS switches.
+ type: str
+ version:
+ description: SNMP version to use for notification messages.
+ type: str
+ choices: ["1", "2c", "3"]
+ auth:
+ description: Use the SNMPv3 authNoPriv Security Level.
+ type: str
+ priv:
+ description: Use the SNMPv3 authPriv Security Level.
+ type: str
+ udp_port:
+ description: The notification host's UDP port number.
+ type: int
+ location:
+ description: Modify sysLocation.
+ type: str
+ mib:
+ description: Mib access parameters.
+ type: dict
+ suboptions:
+ community_map:
+ description: SNMP community.
+ type: dict
+ suboptions:
+ community:
+ description: SNMP community string (Max Size 32).
+ type: str
+ context:
+ description: Name of the SNMP context (Max Size 32).
+ type: str
+ packetsize:
+ description: Largest SNMP packet size
+ type: int
+ protocol:
+ description: Snmp protocol operations.
+ type: dict
+ suboptions:
+ enable:
+ description: Enable/Disable snmp protocol operations.
+ type: bool
+ source_interface:
+ description:
+ - Source interface to be used for sending out SNMP notifications.
+ - This option is unsupported on MDS switches.
+ type: dict
+ suboptions:
+ informs:
+ description: SNMP Inform notifications for which this source interface needs to be used.
+ type: str
+ traps:
+ description: SNMP Trap notifications for which this source interface needs to be used.
+ type: str
+ system_shutdown:
+ description: Configure snmp-server for reload(2).
+ type: bool
+ tcp_session:
+ description: Enable one time authentication for snmp over tcp session.
+ type: dict
+ suboptions:
+ enable:
+ description:
+ - Enable tcp-session.
+ - This option is unsupported on MDS switches.
+ type: bool
+ auth:
+ description: Enable one time authentication for snmp over tcp session.
+ type: bool
+ users:
+ description: Define users who can access the SNMP engine.
+ type: dict
+ suboptions:
+ auth:
+ description: SNMP User authentication related settings
+ type: list
+ elements: dict
+ suboptions:
+ user:
+ description: Name of the user (Max Size 28).
+ type: str
+ group:
+ description: Group name (ignored for notif target user) (Max Size 28).
+ type: str
+ authentication:
+ description: Authentication parameters for the user.
+ type: dict
+ suboptions:
+ algorithm:
+ description: Select algorithm for authentication.
+ type: str
+ choices: ["md5", "sha", "sha-256"]
+ password:
+ description:
+ - Authentication password for user (Max Size 127).
+ - If this value is localized, it has to be enclosed in quotes in the task.
+ type: str
+ engine_id:
+ description:
+ - EngineID for configuring notif target user (for V3 informs).
+ - This value needs to be enclosed in quotes in the task.
+ type: str
+ localized_key:
+ description: Specifies whether the passwords are in localized key format.
+ type: bool
+ localizedv2_key:
+ description: Specifies whether the passwords are in localized V2 key format.
+ type: bool
+ priv:
+ description: Encryption parameters for the user.
+ type: dict
+ suboptions:
+ privacy_password:
+ description:
+ - Privacy password for user (Max Size 130).
+ - If this value is localized, it has to be enclosed in quotes in the task.
+ type: str
+ aes_128:
+ description: Use 128-bit AES algorithm for privacy.
+ type: bool
+ use_acls:
+ description: Set IPv4 and IPv6 ACL to use.
+ type: list
+ elements: dict
+ suboptions:
+ user:
+ description: Name of the user (Max Size 28).
+ type: str
+ ipv4:
+ description: Specify IPv4 ACL, the ACL name specified after must be IPv4 ACL.
+ type: str
+ ipv6:
+ description: Specify IPv6 ACL, the ACL name specified after must be IPv6 ACL.
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in.
+ - The states C(replaced) and C(overridden) have identical behaviour for this module.
+ - Please refer to examples for more details.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - parsed
+ - gathered
+ - rendered
+ default: merged
+"""
+
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+
+- name: Merge the provided configuration with the existing running configuration
+ cisco.nxos.nxos_snmp_server:
+ config:
+ aaa_user:
+ cache_timeout: 36000
+ communities:
+ - community: public
+ group: network-operator
+ - community: private
+ group: network-admin
+ contact: nxosswitchadmin@localhost
+ location: serverroom-1
+ traps:
+ aaa:
+ server_state_change: True
+ system:
+ clock_change_notification: True
+ hosts:
+ - host: 192.0.2.1
+ traps: True
+ version: '1'
+ community: public
+ - host: 192.0.2.1
+ source_interface: Ethernet1/1
+ - host: 192.0.2.2
+ informs: True
+ version: '3'
+ auth: NMS
+ users:
+ auth:
+ - user: snmp_user_1
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+ - user: snmp_user_2
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+ priv:
+ privacy_password: '0x5632724fb8ac3699296af26281e1d0f1'
+ aes_128: True
+ use_acls:
+ - user: snmp_user_1
+ ipv4: acl1
+ ipv6: acl2
+ - user: snmp_user_2
+ ipv4: acl3
+ ipv6: acl4
+
+# Task output
+# -------------
+# before:
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+#
+# commands:
+# - snmp-server contact nxosswitchadmin@localhost
+# - snmp-server location serverroom-1
+# - snmp-server aaa-user cache-timeout 36000
+# - snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# - snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# - snmp-server host 192.0.2.1 traps version 1 public
+# - snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# - snmp-server host 192.0.2.2 informs version 3 auth NMS
+# - snmp-server community private group network-admin
+# - snmp-server community public group network-operator
+# - snmp-server enable traps aaa server-state-change
+# - snmp-server enable traps system Clock-change-notification
+#
+# after:
+# aaa_user:
+# cache_timeout: 36000
+# communities:
+# - community: private
+# group: network-admin
+# - community: public
+# group: network-operator
+# contact: nxosswitchadmin@localhost
+# location: serverroom-1
+# traps:
+# aaa:
+# server_state_change: True
+# system:
+# clock_change_notification: True
+# hosts:
+# - host: 192.0.2.1
+# traps: true
+# version: "1"
+# community: public
+#
+# - host: 192.0.2.1
+# source_interface: Ethernet1/1
+#
+# - host: 192.0.2.2
+# informs: true
+# version: "3"
+# auth: NMS
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+#
+# - user: snmp_user_1
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# localized_key: True
+#
+# - authentication:
+# algorithm: md5
+# localized_key: true
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# priv:
+# aes_128: true
+# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1"
+# group: network-operator
+# user: snmp_user_2
+#
+# use_acls:
+# - user: snmp_user_1
+# ipv4: acl1
+# ipv6: acl2
+# - user: snmp_user_2
+# ipv4: acl3
+# ipv6: acl4
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server contact nxosswitchadmin@localhost
+# snmp-server location serverroom-1
+# snmp-server aaa-user cache-timeout 36000
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# snmp-server host 192.0.2.1 traps version 1 public
+# snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# snmp-server host 192.0.2.2 informs version 3 auth NMS
+# snmp-server community private group network-admin
+# snmp-server community public group network-operator
+# snmp-server enable traps aaa server-state-change
+# snmp-server enable traps system Clock-change-notification
+
+# Using replaced
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server contact nxosswitchadmin@localhost
+# snmp-server location serverroom-1
+# snmp-server aaa-user cache-timeout 36000
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# snmp-server host 192.0.2.1 traps version 1 public
+# snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# snmp-server host 192.0.2.2 informs version 3 auth NMS
+# snmp-server community private group network-admin
+# snmp-server community public group network-operator
+# snmp-server enable traps aaa server-state-change
+# snmp-server enable traps system Clock-change-notification
+
+- name: Replace snmp-server configurations of listed snmp-server with provided configurations
+ cisco.nxos.nxos_snmp_server:
+ config:
+ aaa_user:
+ cache_timeout: 36000
+ communities:
+ - community: public
+ group: network-operator
+ - community: secret
+ group: network-operator
+ contact: nxosswitchadmin2@localhost
+ location: serverroom-2
+ traps:
+ aaa:
+ server_state_change: True
+ hosts:
+ - host: 192.0.2.1
+ traps: True
+ version: '1'
+ community: public
+ - host: 192.0.2.1
+ source_interface: Ethernet1/1
+ - host: 192.0.3.2
+ informs: True
+ version: '3'
+ auth: NMS
+ users:
+ auth:
+ - user: admin
+ group: network-admin
+ authentication:
+ algorithm: md5
+ password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+ localized_key: True
+ priv:
+ privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+
+ - user: snmp_user_1
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+
+ - user: snmp_user_2
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+ priv:
+ privacy_password: '0x5632724fb8ac3699296af26281e1d0f1'
+ aes_128: True
+ use_acls:
+ - user: snmp_user_1
+ ipv4: acl1
+ ipv6: acl2
+ state: replaced
+
+# Task output
+# -------------
+# before:
+# aaa_user:
+# cache_timeout: 36000
+# communities:
+# - community: private
+# group: network-admin
+# - community: public
+# group: network-operator
+# contact: nxosswitchadmin@localhost
+# location: serverroom-1
+# traps:
+# aaa:
+# server_state_change: True
+# system:
+# clock_change_notification: True
+# hosts:
+# - host: 192.0.2.1
+# traps: true
+# version: "1"
+# community: public
+#
+# - host: 192.0.2.1
+# source_interface: Ethernet1/1
+#
+# - host: 192.0.2.2
+# informs: true
+# version: "3"
+# auth: NMS
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+#
+# - user: snmp_user_1
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# localized_key: True
+#
+# - authentication:
+# algorithm: md5
+# localized_key: true
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# priv:
+# aes_128: true
+# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1"
+# group: network-operator
+# user: snmp_user_2
+#
+# use_acls:
+# - user: snmp_user_1
+# ipv4: acl1
+# ipv6: acl2
+# - user: snmp_user_2
+# ipv4: acl3
+# ipv6: acl4
+#
+# commands:
+# - snmp-server contact nxosswitchadmin2@localhost
+# - no snmp-server enable traps system Clock-change-notification
+# - snmp-server location serverroom-2
+# - no snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# - no snmp-server host 192.0.2.2 informs version 3 auth NMS
+# - snmp-server host 192.0.3.2 informs version 3 auth NMS
+# - no snmp-server community private group network-admin
+# - snmp-server community secret group network-operator
+#
+# after:
+# aaa_user:
+# cache_timeout: 36000
+# communities:
+# - community: public
+# group: network-operator
+# - community: secret
+# group: network-operator
+# contact: nxosswitchadmin2@localhost
+# location: serverroom-2
+# traps:
+# aaa:
+# server_state_change: True
+# hosts:
+# - host: 192.0.2.1
+# traps: True
+# version: '1'
+# community: public
+# - host: 192.0.2.1
+# source_interface: Ethernet1/1
+# - host: 192.0.3.2
+# informs: True
+# version: '3'
+# auth: NMS
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+#
+# - user: snmp_user_1
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: '0x5632724fb8ac3699296af26281e1d0f1'
+# localized_key: True
+#
+# - user: snmp_user_2
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: '0x5632724fb8ac3699296af26281e1d0f1'
+# localized_key: True
+# priv:
+# privacy_password: '0x5632724fb8ac3699296af26281e1d0f1'
+# aes_128: True
+#
+# use_acls:
+# - user: snmp_user_1
+# ipv4: acl1
+# ipv6: acl2
+#
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server contact nxosswitchadmin2@localhost
+# snmp-server location serverroom-2
+# snmp-server aaa-user cache-timeout 36000
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# snmp-server host 192.0.2.1 traps version 1 public
+# snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# snmp-server host 192.0.2.2 informs version 3 auth NMS
+# snmp-server community secret group network-operator
+# snmp-server community public group network-operator
+# snmp-server enable traps aaa server-state-change
+# snmp-server enable traps system Clock-change-notification
+
+# Using deleted
+
+# Before state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server contact nxosswitchadmin@localhost
+# snmp-server location serverroom-1
+# snmp-server aaa-user cache-timeout 36000
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# snmp-server host 192.0.2.1 traps version 1 public
+# snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# snmp-server host 192.0.2.2 informs version 3 auth NMS
+# snmp-server community private group network-admin
+# snmp-server community public group network-operator
+# snmp-server enable traps aaa server-state-change
+# snmp-server enable traps system Clock-change-notification
+
+- name: Delete SNMP Server configurations from the device (admin user will not be deleted)
+ cisco.nxos.nxos_snmp_server:
+ state: deleted
+
+# Task output
+# -------------
+# before:
+# aaa_user:
+# cache_timeout: 36000
+# communities:
+# - community: private
+# group: network-admin
+# - community: public
+# group: network-operator
+# contact: nxosswitchadmin@localhost
+# location: serverroom-1
+# traps:
+# aaa:
+# server_state_change: True
+# system:
+# clock_change_notification: True
+# hosts:
+# - host: 192.0.2.1
+# traps: true
+# version: "1"
+# community: public
+#
+# - host: 192.0.2.1
+# source_interface: Ethernet1/1
+#
+# - host: 192.0.2.2
+# informs: true
+# version: "3"
+# auth: NMS
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+#
+# - user: snmp_user_1
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# localized_key: True
+#
+# - authentication:
+# algorithm: md5
+# localized_key: true
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# priv:
+# aes_128: true
+# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1"
+# group: network-operator
+# user: snmp_user_2
+#
+# use_acls:
+# - user: snmp_user_1
+# ipv4: acl1
+# ipv6: acl2
+# - user: snmp_user_2
+# ipv4: acl3
+# ipv6: acl4
+#
+# commands:
+# - no snmp-server contact nxosswitchadmin@localhost
+# - no snmp-server location serverroom-1
+# - no snmp-server aaa-user cache-timeout 36000
+# - no snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+# - no snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - no snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - no snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# - no snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# - no snmp-server host 192.0.2.1 traps version 1 public
+# - no snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# - no snmp-server host 192.0.2.2 informs version 3 auth NMS
+# - no snmp-server community private group network-admin
+# - no snmp-server community public group network-operator
+# - no snmp-server enable traps aaa server-state-change
+# - no snmp-server enable traps system Clock-change-notification
+#
+# after:
+# users:
+# auth:
+# - user: admin
+# group: network-admin
+# authentication:
+# algorithm: md5
+# password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+# localized_key: True
+# priv:
+# privacy_password: "0xcbde46b02c46e0bcd3ac5af6a8b13da9"
+
+# After state:
+# ------------
+# nxos-9k-rdo# show running-config | section "^snmp-server"
+# snmp-server user admin network-admin auth md5 0xcbde46b02c46e0bcd3ac5af6a8b13da9 priv 0xcbde46b02c46e0bcd3ac5af6a8b13da9 localizedkey
+
+# Using rendered
+# ---------------
+
+- name: Render platform specific configuration lines with state rendered (without connecting to the device)
+ cisco.nxos.nxos_snmp_server:
+ config:
+ aaa_user:
+ cache_timeout: 36000
+ communities:
+ - community: public
+ group: network-operator
+ - community: private
+ group: network-admin
+ contact: nxosswitchadmin@localhost
+ location: serverroom-1
+ traps:
+ aaa:
+ server_state_change: True
+ system:
+ clock_change_notification: True
+ hosts:
+ - host: 192.0.2.1
+ traps: True
+ version: '1'
+ community: public
+ - host: 192.0.2.1
+ source_interface: Ethernet1/1
+ - host: 192.0.2.2
+ informs: True
+ version: '3'
+ auth: NMS
+ users:
+ auth:
+ - user: snmp_user_1
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+ - user: snmp_user_2
+ group: network-operator
+ authentication:
+ algorithm: md5
+ password: '0x5632724fb8ac3699296af26281e1d0f1'
+ localized_key: True
+ priv:
+ privacy_password: '0x5632724fb8ac3699296af26281e1d0f1'
+ aes_128: True
+ use_acls:
+ - user: snmp_user_1
+ ipv4: acl1
+ ipv6: acl2
+ - user: snmp_user_2
+ ipv4: acl3
+ ipv6: acl4
+ state: rendered
+
+
+# Task Output (redacted)
+# -----------------------
+# rendered:
+# - snmp-server contact nxosswitchadmin@localhost
+# - snmp-server location serverroom-1
+# - snmp-server aaa-user cache-timeout 36000
+# - snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# - snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# - snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# - snmp-server host 192.0.2.1 traps version 1 public
+# - snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# - snmp-server host 192.0.2.2 informs version 3 auth NMS
+# - snmp-server community private group network-admin
+# - snmp-server community public group network-operator
+# - snmp-server enable traps aaa server-state-change
+# - snmp-server enable traps system Clock-change-notification
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# snmp-server contact nxosswitchadmin@localhost
+# snmp-server location serverroom-1
+# snmp-server aaa-user cache-timeout 36000
+# snmp-server user snmp_user_1 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_2 network-operator auth md5 0x5632724fb8ac3699296af26281e1d0f1 priv aes-128 0x5632724fb8ac3699296af26281e1d0f1 localizedkey
+# snmp-server user snmp_user_1 use-ipv4acl acl1 use-ipv6acl acl2
+# snmp-server user snmp_user_2 use-ipv4acl acl3 use-ipv6acl acl4
+# snmp-server host 192.0.2.1 traps version 1 public
+# snmp-server host 192.0.2.1 source-interface Ethernet1/1
+# snmp-server host 192.0.2.2 informs version 3 auth NMS
+# snmp-server community private group network-admin
+# snmp-server community public group network-operator
+# snmp-server enable traps aaa server-state-change
+# snmp-server enable traps system Clock-change-notification
+
+- name: Parse externally provided snmp-server configuration
+ cisco.nxos.nxos_snmp_server:
+ running_config: "{{ lookup('file', './parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+# parsed:
+# aaa_user:
+# cache_timeout: 36000
+# communities:
+# - community: private
+# group: network-admin
+# - community: public
+# group: network-operator
+# contact: nxosswitchadmin@localhost
+# location: serverroom-1
+# traps:
+# aaa:
+# server_state_change: True
+# system:
+# clock_change_notification: True
+# hosts:
+# - host: 192.0.2.1
+# traps: true
+# version: "1"
+# community: public
+#
+# - host: 192.0.2.1
+# source_interface: Ethernet1/1
+#
+# - host: 192.0.2.2
+# informs: true
+# version: "3"
+# auth: NMS
+# users:
+# auth:
+# - user: snmp_user_1
+# group: network-operator
+# authentication:
+# algorithm: md5
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# localized_key: True
+#
+# - authentication:
+# algorithm: md5
+# localized_key: true
+# password: "0x5632724fb8ac3699296af26281e1d0f1"
+# priv:
+# aes_128: true
+# privacy_password: "0x5632724fb8ac3699296af26281e1d0f1"
+# group: network-operator
+# user: snmp_user_2
+#
+# use_acls:
+# - user: snmp_user_1
+# ipv4: acl1
+# ipv6: acl2
+# - user: snmp_user_2
+# ipv4: acl3
+# ipv6: acl4
+#
+"""
+
+RETURN = """
+before:
+ description: The configuration prior to the module execution.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+after:
+ description: The resulting configuration after module execution.
+ returned: when changed
+ type: dict
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: when I(state) is C(merged), C(replaced), C(overridden), C(deleted) or C(purged)
+ type: list
+ sample:
+ - sample command 1
+ - sample command 2
+ - sample command 3
+rendered:
+ description: The provided configuration in the task rendered in device-native format (offline).
+ returned: when I(state) is C(rendered)
+ type: list
+ sample:
+ - sample command 1
+ - sample command 2
+ - sample command 3
+gathered:
+ description: Facts about the network resource gathered from the remote device as structured data.
+ returned: when I(state) is C(gathered)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+parsed:
+ description: The device native config provided in I(running_config) option parsed into structured data as per module argspec.
+ returned: when I(state) is C(parsed)
+ type: list
+ sample: >
+ This output will always be in the same format as the
+ module argspec.
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.snmp_server.snmp_server import (
+ Snmp_serverArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.snmp_server.snmp_server import (
+ Snmp_server,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(
+ argument_spec=Snmp_serverArgs.argument_spec,
+ mutually_exclusive=[["config", "running_config"]],
+ required_if=[
+ ["state", "merged", ["config"]],
+ ["state", "replaced", ["config"]],
+ ["state", "overridden", ["config"]],
+ ["state", "rendered", ["config"]],
+ ["state", "parsed", ["running_config"]],
+ ],
+ supports_check_mode=True,
+ )
+
+ result = Snmp_server(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_traps.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_traps.py
new file mode 100644
index 00000000..fce1a8a2
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_traps.py
@@ -0,0 +1,319 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_traps
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP traps.
+description:
+- Manages SNMP traps configurations.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- This module works at the group level for traps. If you need to only enable/disable
+ 1 specific trap within a group, use the M(cisco.nxos.nxos_command) module.
+- Be aware that you can set a trap only for an enabled feature.
+options:
+ group:
+ description:
+ - Case sensitive group.
+ required: true
+ choices:
+ - aaa
+ - bfd
+ - bgp
+ - bridge
+ - callhome
+ - cfs
+ - config
+ - eigrp
+ - entity
+ - feature-control
+ - generic
+ - hsrp
+ - license
+ - link
+ - lldp
+ - mmode
+ - ospf
+ - pim
+ - rf
+ - rmon
+ - snmp
+ - storm-control
+ - stpx
+ - switchfabric
+ - syslog
+ - sysmgr
+ - system
+ - upgrade
+ - vtp
+ - all
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ required: false
+ default: enabled
+ choices:
+ - enabled
+ - disabled
+ type: str
+"""
+
+EXAMPLES = """
+# ensure lldp trap configured
+- cisco.nxos.nxos_snmp_traps:
+ group: lldp
+ state: enabled
+
+# ensure lldp trap is not configured
+- cisco.nxos.nxos_snmp_traps:
+ group: lldp
+ state: disabled
+"""
+
+RETURN = """
+commands:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: "snmp-server enable traps lldp ;"
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+
+
+def get_platform_id(module):
+ info = get_capabilities(module).get("device_info", {})
+ return info.get("network_os_platform", "")
+
+
+def execute_show_command(command, module):
+ command = {"command": command, "output": "text"}
+
+ return run_commands(module, command)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_traps(group, module):
+ body = execute_show_command("show run snmp all", module)[0].split("\n")
+
+ resource = {}
+ feature_list = [
+ "aaa",
+ "bfd",
+ "bgp",
+ "bridge",
+ "callhome",
+ "cfs",
+ "config",
+ "eigrp",
+ "entity",
+ "feature-control",
+ "generic",
+ "hsrp",
+ "license",
+ "link",
+ "lldp",
+ "mmode",
+ "ospf",
+ "pim",
+ "rf",
+ "rmon",
+ "snmp",
+ "storm-control",
+ "stpx",
+ "switchfabric",
+ "syslog",
+ "sysmgr",
+ "system",
+ "upgrade",
+ "vtp",
+ ]
+
+ if "all" in group and "N3K-C35" in get_platform_id(module):
+ module.warn("Platform does not support bfd traps; bfd ignored for 'group: all' request")
+ feature_list.remove("bfd")
+
+ for each in feature_list:
+ for line in body:
+ if each == "ospf":
+ # ospf behaves differently when routers are present
+ if "snmp-server enable traps ospf" == line:
+ resource[each] = True
+ break
+ else:
+ if "enable traps {0}".format(each) in line:
+ if "no " in line:
+ resource[each] = False
+ break
+ else:
+ resource[each] = True
+
+ for each in feature_list:
+ if resource.get(each) is None:
+ # on some platforms, the 'no' cmd does not
+ # show up and so check if the feature is enabled
+ body = execute_show_command("show run | inc feature", module)[0]
+ if "feature {0}".format(each) in body:
+ resource[each] = False
+
+ find = resource.get(group, None)
+
+ if group == "all".lower():
+ return resource
+ elif find is not None:
+ trap_resource = {group: find}
+ return trap_resource
+ else:
+ # if 'find' is None, it means that 'group' is a
+ # currently disabled feature.
+ return {}
+
+
+def get_trap_commands(group, state, existing, module):
+ commands = []
+ enabled = False
+ disabled = False
+
+ if group == "all":
+ if state == "disabled":
+ for feature in existing:
+ if existing[feature]:
+ trap_command = "no snmp-server enable traps {0}".format(feature)
+ commands.append(trap_command)
+
+ elif state == "enabled":
+ for feature in existing:
+ if existing[feature] is False:
+ trap_command = "snmp-server enable traps {0}".format(feature)
+ commands.append(trap_command)
+
+ else:
+ if group in existing:
+ if existing[group]:
+ enabled = True
+ else:
+ disabled = True
+
+ if state == "disabled" and enabled:
+ commands.append(["no snmp-server enable traps {0}".format(group)])
+ elif state == "enabled" and disabled:
+ commands.append(["snmp-server enable traps {0}".format(group)])
+ else:
+ module.fail_json(msg="{0} is not a currently " "enabled feature.".format(group))
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ state=dict(choices=["enabled", "disabled"], default="enabled"),
+ group=dict(
+ choices=[
+ "aaa",
+ "bfd",
+ "bgp",
+ "bridge",
+ "callhome",
+ "cfs",
+ "config",
+ "eigrp",
+ "entity",
+ "feature-control",
+ "generic",
+ "hsrp",
+ "license",
+ "link",
+ "lldp",
+ "mmode",
+ "ospf",
+ "pim",
+ "rf",
+ "rmon",
+ "snmp",
+ "storm-control",
+ "stpx",
+ "switchfabric",
+ "syslog",
+ "sysmgr",
+ "system",
+ "upgrade",
+ "vtp",
+ "all",
+ ],
+ required=True,
+ ),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ group = module.params["group"].lower()
+ state = module.params["state"]
+
+ existing = get_snmp_traps(group, module)
+
+ commands = get_trap_commands(group, state, existing, module)
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_user.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_user.py
new file mode 100644
index 00000000..a3b0b5fa
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_snmp_user.py
@@ -0,0 +1,412 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_snmp_user
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: (deprecated, removed after 2024-01-01) Manages SNMP users for monitoring.
+description:
+- Manages SNMP user configuration.
+version_added: 1.0.0
+deprecated:
+ alternative: nxos_snmp_server
+ why: Updated modules released with more functionality
+ removed_at_date: '2024-01-01'
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Limited Support for Cisco MDS
+- Authentication parameters not idempotent.
+options:
+ user:
+ description:
+ - Name of the user.
+ required: true
+ type: str
+ group:
+ description:
+ - Group to which the user will belong to. If state = present, and the user is
+ existing, the group is added to the user. If the user is not existing, user
+ entry is created with this group argument. If state = absent, only the group
+ is removed from the user entry. However, to maintain backward compatibility,
+ if the existing user belongs to only one group, and if group argument is same
+ as the existing user's group, then the user entry also is deleted.
+ type: str
+ authentication:
+ description:
+ - Authentication parameters for the user.
+ choices:
+ - md5
+ - sha
+ type: str
+ pwd:
+ description:
+ - Authentication password when using md5 or sha. This is not idempotent
+ type: str
+ privacy:
+ description:
+ - Privacy password for the user. This is not idempotent
+ type: str
+ encrypt:
+ description:
+ - Enables AES-128 bit encryption when using privacy password.
+ type: bool
+ state:
+ description:
+ - Manage the state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_snmp_user:
+ user: ntc
+ group: network-operator
+ authentication: md5
+ pwd: test_password
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["snmp-server user ntc network-operator auth md5 test_password"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, text=False):
+ command = {"command": command, "output": "json"}
+ if text:
+ command["output"] = "text"
+
+ return run_commands(module, command)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_snmp_groups(module):
+ data = execute_show_command("show snmp group", module)[0]
+ group_list = []
+
+ try:
+ group_table = data["TABLE_role"]["ROW_role"]
+ for group in group_table:
+ group_list.append(group["role_name"])
+ except (KeyError, AttributeError):
+ return group_list
+
+ return group_list
+
+
+def get_snmp_user(user, module):
+ command = "show snmp user {0}".format(user)
+ body = execute_show_command(command, module, text=True)
+ body_text = body[0]
+
+ if "No such entry" not in body[0]:
+ body = execute_show_command(command, module)
+
+ resource = {}
+ try:
+ # The TABLE and ROW keys differ between NXOS platforms.
+ if body[0].get("TABLE_snmp_user"):
+ tablekey = "TABLE_snmp_user"
+ rowkey = "ROW_snmp_user"
+ tablegrpkey = "TABLE_snmp_group_names"
+ rowgrpkey = "ROW_snmp_group_names"
+ authkey = "auth_protocol"
+ privkey = "priv_protocol"
+ grpkey = "group_names"
+ elif body[0].get("TABLE_snmp_users"):
+ tablekey = "TABLE_snmp_users"
+ rowkey = "ROW_snmp_users"
+ tablegrpkey = "TABLE_groups"
+ rowgrpkey = "ROW_groups"
+ authkey = "auth"
+ privkey = "priv"
+ grpkey = "group"
+
+ rt = body[0][tablekey][rowkey]
+ # on some older platforms, all groups except the 1st one
+ # are in list elements by themselves and they are
+ # indexed by 'user'. This is due to a platform bug.
+ # Get first element if rt is a list due to the bug
+ # or if there is no bug, parse rt directly
+ if isinstance(rt, list):
+ resource_table = rt[0]
+ else:
+ resource_table = rt
+
+ resource["user"] = user
+ resource["authentication"] = str(resource_table[authkey]).strip()
+ encrypt = str(resource_table[privkey]).strip()
+ if encrypt.startswith("aes"):
+ resource["encrypt"] = "aes-128"
+ else:
+ resource["encrypt"] = "none"
+
+ groups = []
+ if tablegrpkey in resource_table:
+ group_table = resource_table[tablegrpkey][rowgrpkey]
+ try:
+ for group in group_table:
+ groups.append(str(group[grpkey]).strip())
+ except TypeError:
+ groups.append(str(group_table[grpkey]).strip())
+
+ # Now for the platform bug case, get the groups
+ if isinstance(rt, list):
+ # remove 1st element from the list as this is parsed already
+ rt.pop(0)
+ # iterate through other elements indexed by
+ # 'user' and add it to groups.
+ for each in rt:
+ groups.append(each["user"].strip())
+
+ # Some 'F' platforms use 'group' key instead
+ elif "group" in resource_table:
+ # single group is a string, multiple groups in a list
+ groups = resource_table["group"]
+ if isinstance(groups, str):
+ groups = [groups]
+
+ resource["group"] = groups
+
+ except (KeyError, AttributeError, IndexError, TypeError):
+ if not resource and body_text and "No such entry" not in body_text:
+ # 6K and other platforms may not return structured output;
+ # attempt to get state from text output
+ resource = get_non_structured_snmp_user(body_text)
+
+ return resource
+
+
+def get_non_structured_snmp_user(body_text):
+ # This method is a workaround for platforms that don't support structured
+ # output for 'show snmp user <foo>'. This workaround may not work on all
+ # platforms. Sample non-struct output:
+ #
+ # User Auth Priv(enforce) Groups acl_filter
+ # ____ ____ _____________ ______ __________
+ # sample1 no no network-admin ipv4:my_acl
+ # network-operator
+ # priv-11
+ # -OR-
+ # sample2 md5 des(no) priv-15
+ # -OR-
+ # sample3 md5 aes-128(no) network-admin
+ resource = {}
+ output = body_text.rsplit("__________")[-1]
+ pat = re.compile(
+ r"^(?P<user>\S+)\s+"
+ r"(?P<auth>\S+)\s+"
+ r"(?P<priv>[\w\d-]+)(?P<enforce>\([\w\d-]+\))*\s+"
+ r"(?P<group>\S+)",
+ re.M,
+ )
+ m = re.search(pat, output)
+ if not m:
+ return resource
+ resource["user"] = m.group("user")
+ resource["auth"] = m.group("auth")
+ resource["encrypt"] = "aes-128" if "aes" in str(m.group("priv")) else "none"
+
+ resource["group"] = [m.group("group")]
+ more_groups = re.findall(r"^\s+([\w\d-]+)\s*$", output, re.M)
+ if more_groups:
+ resource["group"] += more_groups
+
+ return resource
+
+
+def remove_snmp_user(user, group=None):
+ if group:
+ return ["no snmp-server user {0} {1}".format(user, group)]
+ else:
+ return ["no snmp-server user {0}".format(user)]
+
+
+def config_snmp_user(proposed, user, reset):
+ if reset:
+ commands = remove_snmp_user(user)
+ else:
+ commands = []
+
+ if proposed.get("group"):
+ cmd = "snmp-server user {0} {group}".format(user, **proposed)
+ else:
+ cmd = "snmp-server user {0}".format(user)
+
+ auth = proposed.get("authentication", None)
+ pwd = proposed.get("pwd", None)
+
+ if auth and pwd:
+ cmd += " auth {authentication} {pwd}".format(**proposed)
+
+ encrypt = proposed.get("encrypt", None)
+ privacy = proposed.get("privacy", None)
+
+ if encrypt and privacy:
+ cmd += " priv {encrypt} {privacy}".format(**proposed)
+ elif privacy:
+ cmd += " priv {privacy}".format(**proposed)
+
+ if cmd:
+ commands.append(cmd)
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ user=dict(required=True, type="str"),
+ group=dict(type="str"),
+ pwd=dict(type="str", no_log=True),
+ privacy=dict(type="str"),
+ authentication=dict(choices=["md5", "sha"]),
+ encrypt=dict(type="bool"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_together=[["authentication", "pwd"], ["encrypt", "privacy"]],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ user = module.params["user"]
+ group = module.params["group"]
+ pwd = module.params["pwd"]
+ privacy = module.params["privacy"]
+ encrypt = module.params["encrypt"]
+ authentication = module.params["authentication"]
+ state = module.params["state"]
+
+ if privacy and encrypt:
+ if not pwd and authentication:
+ module.fail_json(
+ msg="pwd and authentication must be provided " "when using privacy and encrypt",
+ )
+
+ if group and group not in get_snmp_groups(module):
+ module.fail_json(msg="group not configured yet on switch.")
+
+ existing = get_snmp_user(user, module)
+
+ if state == "present" and existing:
+ if group:
+ if group not in existing["group"]:
+ existing["group"] = None
+ else:
+ existing["group"] = group
+ else:
+ existing["group"] = None
+
+ commands = []
+
+ if state == "absent" and existing:
+ if group:
+ if group in existing["group"]:
+ if len(existing["group"]) == 1:
+ commands.append(remove_snmp_user(user))
+ else:
+ commands.append(remove_snmp_user(user, group))
+ else:
+ commands.append(remove_snmp_user(user))
+
+ elif state == "present":
+ reset = False
+
+ args = dict(
+ user=user,
+ pwd=pwd,
+ group=group,
+ privacy=privacy,
+ encrypt=encrypt,
+ authentication=authentication,
+ )
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ if not existing:
+ if encrypt:
+ proposed["encrypt"] = "aes-128"
+ commands.append(config_snmp_user(proposed, user, reset))
+
+ elif existing:
+ if encrypt and not existing["encrypt"].startswith("aes"):
+ reset = True
+ proposed["encrypt"] = "aes-128"
+
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ if delta.get("pwd"):
+ delta["authentication"] = authentication
+
+ if delta and encrypt:
+ delta["encrypt"] = "aes-128"
+
+ if delta:
+ command = config_snmp_user(delta, user, reset)
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+
+ if "configure" in cmds:
+ cmds.pop(0)
+ results["commands"] = cmds
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_static_routes.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_static_routes.py
new file mode 100644
index 00000000..15b8e690
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_static_routes.py
@@ -0,0 +1,477 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+"""
+The module file for nxos_static_routes
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_static_routes
+short_description: Static routes resource module
+description: This module configures and manages the attributes of static routes on
+ Cisco NX-OS platforms.
+version_added: 1.0.0
+author: Adharsh Srivats Rangarajan (@adharshsrivatsr)
+notes:
+- Tested against NX-OS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- When a route is configured for a non-existent VRF, the VRF is created and the route
+ is added to it.
+- When deleting routes for a VRF, all routes inside the VRF are deleted, but the VRF
+ is not deleted.
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the following commands in order B(show running-config | include
+ '^ip(v6)* route') and B(show running-config | section '^vrf context').
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description:
+ - A list of configurations for static routes
+ type: list
+ elements: dict
+ suboptions:
+ vrf:
+ description:
+ - The VRF to which the static route(s) belong
+ type: str
+ address_families:
+ description: A dictionary specifying the address family to which the static
+ route(s) belong.
+ type: list
+ elements: dict
+ suboptions:
+ afi:
+ description:
+ - Specifies the top level address family indicator.
+ type: str
+ choices: [ipv4, ipv6]
+ required: true
+ routes:
+ description: A dictionary that specifies the static route configurations
+ elements: dict
+ type: list
+ suboptions:
+ dest:
+ description:
+ - Destination prefix of static route
+ - The address format is <ipv4/v6 address>/<mask>
+ - The mask is number in range 0-32 for IPv4 and in range 0-128 for
+ IPv6
+ type: str
+ required: true
+ next_hops:
+ description:
+ - Details of route to be taken
+ type: list
+ elements: dict
+ suboptions:
+ forward_router_address:
+ description:
+ - IP address of the next hop router
+ type: str
+ # required: True
+ interface:
+ description:
+ - Outgoing interface to take. For anything except 'Null0', then
+ next hop IP address should also be configured.
+ type: str
+ admin_distance:
+ description:
+ - Preference or administrative distance of route (range 1-255)
+ type: int
+ route_name:
+ description:
+ - Name of the static route
+ type: str
+ tag:
+ description:
+ - Route tag value (numeric)
+ type: int
+ track:
+ description:
+ - Track value (range 1 - 512). Track must already be configured
+ on the device before adding the route.
+ type: int
+ dest_vrf:
+ description:
+ - VRF of the destination
+ type: str
+ state:
+ description:
+ - The state the configuration should be left in
+ type: str
+ choices:
+ - deleted
+ - merged
+ - overridden
+ - replaced
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using deleted:
+
+# Before state:
+# -------------
+#
+# ip route 192.0.2.32/28 192.0.2.12 name new_route
+# ip route 192.0.2.26/24 192.0.2.13 tag 12
+
+- name: Delete all routes
+ cisco.nxos.nxos_static_routes:
+ state: deleted
+
+# After state:
+# ------------
+#
+
+
+# Before state:
+# ------------
+#
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ip route 192.0.2.64/28 192.0.2.22 tag 4
+# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1
+# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5
+
+- name: Delete routes based on VRF
+ cisco.nxos.nxos_static_routes:
+ config:
+ - vrf: trial_vrf
+ state: deleted
+
+# After state:
+# -----------
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+
+
+# Before state:
+# ------------
+#
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ip route 192.0.2.64/28 192.0.2.22 tag 4
+# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1
+# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5
+
+- name: Delete routes based on AFI in a VRF
+ cisco.nxos.nxos_static_routes:
+ config:
+ - vrf: trial_vrf
+ address_families:
+ - afi: ipv4
+ state: deleted
+
+# After state:
+# -----------
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5
+
+
+# Before state:
+# -----------
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# vrf context trial_vrf
+# ipv6 route 2200:10::/36 2048:ae12::1 vrf dest 5
+
+
+# Using merged
+
+# Before state:
+# -------------
+#
+
+- name: Merge new static route configuration
+ cisco.nxos.nxos_static_routes:
+ config:
+ - vrf: trial_vrf
+ address_families:
+ - afi: ipv4
+ routes:
+ - dest: 192.0.2.64/24
+ next_hops:
+ - forward_router_address: 192.0.2.22
+ tag: 4
+ admin_distance: 2
+
+ - address_families:
+ - afi: ipv4
+ routes:
+ - dest: 192.0.2.16/24
+ next_hops:
+ - forward_router_address: 192.0.2.24
+ route_name: new_route
+ - afi: ipv6
+ routes:
+ - dest: 2001:db8::/64
+ next_hops:
+ - interface: eth1/3
+ forward_router_address: 2001:db8::12
+ state: merged
+
+# After state:
+# ------------
+#
+# ip route 192.0.2.16/24 192.0.2.24 name new_route
+# ipv6 route 2001:db8::/64 Ethernet1/3 2001:db8::12
+# vrf context trial_vrf
+# ip route 192.0.2.0/24 192.0.2.22 tag 4 2
+
+
+# Using overridden:
+
+# Before state:
+# -------------
+#
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ip route 192.0.2.64/28 192.0.2.22 tag 4
+# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1
+
+- name: Overriden existing static route configuration with new configuration
+ cisco.nxos.nxos_static_routes:
+ config:
+ - vrf: trial_vrf
+ address_families:
+ - afi: ipv4
+ routes:
+ - dest: 192.0.2.16/28
+ next_hops:
+ - forward_router_address: 192.0.2.23
+ route_name: overridden_route1
+ admin_distance: 3
+
+ - forward_router_address: 192.0.2.45
+ route_name: overridden_route2
+ dest_vrf: destinationVRF
+ interface: Ethernet1/2
+ state: overridden
+
+# After state:
+# ------------
+#
+# ip route 192.0.2.16/28 192.0.2.23 name replaced_route1 3
+# ip route 192.0.2.16/28 Ethernet1/2 192.0.2.45 vrf destinationVRF name replaced_route2
+
+
+# Using replaced:
+
+# Before state:
+# ------------
+# ip route 192.0.2.16/28 192.0.2.24 name new_route
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ip route 192.0.2.64/28 192.0.2.22 tag 4
+# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1
+
+- name: Replaced the existing static configuration of a prefix with new configuration
+ cisco.nxos.nxos_static_routes:
+ config:
+ - address_families:
+ - afi: ipv4
+ routes:
+ - dest: 192.0.2.16/28
+ next_hops:
+ - forward_router_address: 192.0.2.23
+ route_name: replaced_route1
+ admin_distance: 3
+
+ - forward_router_address: 192.0.2.45
+ route_name: replaced_route2
+ dest_vrf: destinationVRF
+ interface: Ethernet1/2
+ state: replaced
+
+# After state:
+# -----------
+# ip route 192.0.2.16/28 192.0.2.23 name replaced_route1 3
+# ip route 192.0.2.16/28 Ethernet1/2 192.0.2.45 vrf destinationVRF name replaced_route2
+# ip route 192.0.2.80/28 192.0.2.26 tag 12
+# vrf context trial_vrf
+# ip route 192.0.2.64/28 192.0.2.22 tag 4
+# ip route 192.0.2.64/28 192.0.2.23 name merged_route 1
+
+
+# Using gathered:
+
+# Before state:
+# -------------
+# ipv6 route 2001:db8:12::/32 2001:db8::12
+# vrf context Test
+# ip route 192.0.2.48/28 192.0.2.13
+# ip route 192.0.2.48/28 192.0.2.14 5
+
+- name: Gather the exisitng condiguration
+ cisco.nxos.nxos_static_routes:
+ state: gathered
+
+# returns:
+# gathered:
+# - vrf: Test
+# address_families:
+# - afi: ipv4
+# routes:
+# - dest: 192.0.2.48/28
+# next_hops:
+# - forward_router_address: 192.0.2.13
+#
+# - forward_router_address: 192.0.2.14
+# admin_distance: 5
+#
+# - address_families:
+# - afi: ipv6
+# routes:
+# - dest: 2001:db8:12::/32
+# next_hops:
+# - forward_router_address: 2001:db8::12
+
+
+# Using rendered:
+
+- name: Render required configuration to be pushed to the device
+ cisco.nxos.nxos_static_routes:
+ config:
+ - address_families:
+ - afi: ipv4
+ routes:
+ - dest: 192.0.2.48/28
+ next_hops:
+ - forward_router_address: 192.0.2.13
+
+ - afi: ipv6
+ routes:
+ - dest: 2001:db8::/64
+ next_hops:
+ - interface: eth1/3
+ forward_router_address: 2001:db8::12
+ state: rendered
+
+# returns
+# rendered:
+# vrf context default
+# ip route 192.0.2.48/28 192.0.2.13
+# ipv6 route 2001:db8::/64 Ethernet1/3 2001:db8::12
+
+
+# Using parsed
+
+- name: Parse the config to structured data
+ cisco.nxos.nxos_static_routes:
+ running_config: |
+ ipv6 route 2002:db8:12::/32 2002:db8:12::1
+ vrf context Test
+ ip route 192.0.2.48/28 192.0.2.13
+ ip route 192.0.2.48/28 192.0.2.14 5
+
+# returns:
+# parsed:
+# - vrf: Test
+# address_families:
+# - afi: ipv4
+# routes:
+# - dest: 192.0.2.48/28
+# next_hops:
+# - forward_router_address: 192.0.2.13
+#
+# - forward_router_address: 192.0.2.14
+# admin_distance: 5
+#
+# - address_families:
+# - afi: ipv6
+# routes:
+# - dest: 2002:db8:12::/32
+# next_hops:
+# - forward_router_address: 2002:db8:12::1
+
+
+"""
+RETURN = """
+before:
+ description: The configuration prior to the model invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The resulting configuration model invocation.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['ip route 192.0.2.48/28 192.0.2.12 Ethernet1/2 name sample_route',
+ 'ipv6 route 2001:db8:3000::/36 2001:db8:200:2::2', 'vrf context test','ip route 192.0.2.48/28 192.0.2.121']
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.static_routes.static_routes import (
+ Static_routesArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.static_routes.static_routes import (
+ Static_routes,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=Static_routesArgs.argument_spec, supports_check_mode=True)
+
+ result = Static_routes(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_system.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_system.py
new file mode 100644
index 00000000..df4bbde0
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_system.py
@@ -0,0 +1,399 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_system
+extends_documentation_fragment:
+- cisco.nxos.nxos
+author: Peter Sprygada (@privateip)
+short_description: Manage the system attributes on Cisco NXOS devices
+notes:
+- Unsupported for Cisco MDS
+description:
+- This module provides declarative management of node system attributes on Cisco NXOS
+ devices. It provides an option to configure host system parameters or remove those
+ parameters from the device active configuration.
+version_added: 1.0.0
+options:
+ hostname:
+ description:
+ - Configure the device hostname parameter. This option takes an ASCII string value
+ or keyword 'default'
+ type: str
+ domain_name:
+ description:
+ - Configures the default domain name suffix to be used when referencing this node
+ by its FQDN. This argument accepts either a list of domain names or a list
+ of dicts that configure the domain name and VRF name or keyword 'default'. See
+ examples.
+ type: list
+ elements: raw
+ domain_lookup:
+ description:
+ - Enables or disables the DNS lookup feature in Cisco NXOS. This argument accepts
+ boolean values. When enabled, the system will try to resolve hostnames using
+ DNS and when disabled, hostnames will not be resolved.
+ type: bool
+ domain_search:
+ description:
+ - Configures a list of domain name suffixes to search when performing DNS name
+ resolution. This argument accepts either a list of domain names or a list of
+ dicts that configure the domain name and VRF name or keyword 'default'. See
+ examples.
+ type: list
+ elements: raw
+ name_servers:
+ description:
+ - List of DNS name servers by IP address to use to perform name resolution lookups. This
+ argument accepts either a list of DNS servers or a list of hashes that configure
+ the name server and VRF name or keyword 'default'. See examples.
+ type: list
+ elements: raw
+ system_mtu:
+ description:
+ - Specifies the mtu, must be an integer or keyword 'default'.
+ type: str
+ state:
+ description:
+ - State of the configuration values in the device's current active configuration. When
+ set to I(present), the values should be configured in the device active configuration
+ and when set to I(absent) the values should not be in the device active configuration
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: configure hostname and domain-name
+ cisco.nxos.nxos_system:
+ hostname: nxos01
+ domain_name: test.example.com
+
+- name: remove configuration
+ cisco.nxos.nxos_system:
+ state: absent
+
+- name: configure name servers
+ cisco.nxos.nxos_system:
+ name_servers:
+ - 8.8.8.8
+ - 8.8.4.4
+
+- name: configure name servers with VRF support
+ cisco.nxos.nxos_system:
+ name_servers:
+ - {server: 8.8.8.8, vrf: mgmt}
+ - {server: 8.8.4.4, vrf: mgmt}
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - hostname nxos01
+ - ip domain-name test.example.com
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ NetworkConfig,
+)
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ ComplexList,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+_CONFIGURED_VRFS = None
+
+
+def has_vrf(module, vrf):
+ global _CONFIGURED_VRFS
+ if _CONFIGURED_VRFS is not None:
+ return vrf in _CONFIGURED_VRFS
+ config = get_config(module)
+ _CONFIGURED_VRFS = re.findall(r"vrf context (\S+)", config)
+ return vrf in _CONFIGURED_VRFS
+
+
+def map_obj_to_commands(want, have, module):
+ commands = list()
+ state = module.params["state"]
+
+ def needs_update(x):
+ return want.get(x) and (want.get(x) != have.get(x))
+
+ def difference(x, y, z):
+ return [item for item in x[z] if item not in y[z]]
+
+ def remove(cmd, commands, vrf=None):
+ if vrf:
+ commands.append("vrf context %s" % vrf)
+ commands.append(cmd)
+ if vrf:
+ commands.append("exit")
+
+ def add(cmd, commands, vrf=None):
+ if vrf:
+ if not has_vrf(module, vrf):
+ module.fail_json(msg="invalid vrf name %s" % vrf)
+ return remove(cmd, commands, vrf)
+
+ if state == "absent":
+ if have["hostname"]:
+ commands.append("no hostname")
+
+ for item in have["domain_name"]:
+ cmd = "no ip domain-name %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+
+ for item in have["domain_search"]:
+ cmd = "no ip domain-list %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+
+ for item in have["name_servers"]:
+ cmd = "no ip name-server %s" % item["server"]
+ remove(cmd, commands, item["vrf"])
+
+ if have["system_mtu"]:
+ commands.append("no system jumbomtu")
+
+ if state == "present":
+ if needs_update("hostname"):
+ if want["hostname"] == "default":
+ if have["hostname"]:
+ commands.append("no hostname")
+ else:
+ commands.append("hostname %s" % want["hostname"])
+
+ if want.get("domain_lookup") is not None:
+ if have.get("domain_lookup") != want.get("domain_lookup"):
+ cmd = "ip domain-lookup"
+ if want["domain_lookup"] is False:
+ cmd = "no %s" % cmd
+ commands.append(cmd)
+
+ if want["domain_name"]:
+ if want.get("domain_name")[0]["name"] == "default":
+ if have["domain_name"]:
+ for item in have["domain_name"]:
+ cmd = "no ip domain-name %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+ else:
+ for item in difference(have, want, "domain_name"):
+ cmd = "no ip domain-name %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+ for item in difference(want, have, "domain_name"):
+ cmd = "ip domain-name %s" % item["name"]
+ add(cmd, commands, item["vrf"])
+
+ if want["domain_search"]:
+ if want.get("domain_search")[0]["name"] == "default":
+ if have["domain_search"]:
+ for item in have["domain_search"]:
+ cmd = "no ip domain-list %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+ else:
+ for item in difference(have, want, "domain_search"):
+ cmd = "no ip domain-list %s" % item["name"]
+ remove(cmd, commands, item["vrf"])
+ for item in difference(want, have, "domain_search"):
+ cmd = "ip domain-list %s" % item["name"]
+ add(cmd, commands, item["vrf"])
+
+ if want["name_servers"]:
+ if want.get("name_servers")[0]["server"] == "default":
+ if have["name_servers"]:
+ for item in have["name_servers"]:
+ cmd = "no ip name-server %s" % item["server"]
+ remove(cmd, commands, item["vrf"])
+ else:
+ for item in difference(have, want, "name_servers"):
+ cmd = "no ip name-server %s" % item["server"]
+ remove(cmd, commands, item["vrf"])
+ for item in difference(want, have, "name_servers"):
+ cmd = "ip name-server %s" % item["server"]
+ add(cmd, commands, item["vrf"])
+
+ if needs_update("system_mtu"):
+ if want["system_mtu"] == "default":
+ if have["system_mtu"]:
+ commands.append("no system jumbomtu")
+ else:
+ commands.append("system jumbomtu %s" % want["system_mtu"])
+
+ return commands
+
+
+def parse_hostname(config):
+ match = re.search(r"^hostname (\S+)", config, re.M)
+ if match:
+ return match.group(1)
+
+
+def parse_domain_name(config, vrf_config):
+ objects = list()
+ match = re.search(r"^ip domain-name (\S+)", config, re.M)
+ if match:
+ objects.append({"name": match.group(1), "vrf": None})
+
+ for vrf, cfg in iteritems(vrf_config):
+ match = re.search(r"ip domain-name (\S+)", cfg, re.M)
+ if match:
+ objects.append({"name": match.group(1), "vrf": vrf})
+
+ return objects
+
+
+def parse_domain_search(config, vrf_config):
+ objects = list()
+
+ for item in re.findall(r"^ip domain-list (\S+)", config, re.M):
+ objects.append({"name": item, "vrf": None})
+
+ for vrf, cfg in iteritems(vrf_config):
+ for item in re.findall(r"ip domain-list (\S+)", cfg, re.M):
+ objects.append({"name": item, "vrf": vrf})
+
+ return objects
+
+
+def parse_name_servers(config, vrf_config, vrfs):
+ objects = list()
+
+ match = re.search("^ip name-server (.+)$", config, re.M)
+ if match and "use-vrf" not in match.group(1):
+ for addr in match.group(1).split(" "):
+ objects.append({"server": addr, "vrf": None})
+
+ for vrf, cfg in iteritems(vrf_config):
+ vrf_match = re.search("ip name-server (.+)", cfg, re.M)
+ if vrf_match:
+ for addr in vrf_match.group(1).split(" "):
+ objects.append({"server": addr, "vrf": vrf})
+
+ return objects
+
+
+def parse_system_mtu(config):
+ match = re.search(r"^system jumbomtu (\d+)", config, re.M)
+ if match:
+ return match.group(1)
+
+
+def map_config_to_obj(module):
+ config = get_config(module)
+ configobj = NetworkConfig(indent=2, contents=config)
+
+ vrf_config = {}
+
+ vrfs = re.findall(r"^vrf context (\S+)$", config, re.M)
+ for vrf in vrfs:
+ config_data = configobj.get_block_config(path=["vrf context %s" % vrf])
+ vrf_config[vrf] = config_data
+
+ return {
+ "hostname": parse_hostname(config),
+ "domain_lookup": "no ip domain-lookup" not in config,
+ "domain_name": parse_domain_name(config, vrf_config),
+ "domain_search": parse_domain_search(config, vrf_config),
+ "name_servers": parse_name_servers(config, vrf_config, vrfs),
+ "system_mtu": parse_system_mtu(config),
+ }
+
+
+def map_params_to_obj(module):
+ obj = {
+ "hostname": module.params["hostname"],
+ "domain_lookup": module.params["domain_lookup"],
+ "system_mtu": module.params["system_mtu"],
+ }
+
+ domain_name = ComplexList(dict(name=dict(key=True), vrf=dict()), module)
+
+ domain_search = ComplexList(dict(name=dict(key=True), vrf=dict()), module)
+
+ name_servers = ComplexList(dict(server=dict(key=True), vrf=dict()), module)
+
+ for arg, cast in [
+ ("domain_name", domain_name),
+ ("domain_search", domain_search),
+ ("name_servers", name_servers),
+ ]:
+ if module.params[arg] is not None:
+ obj[arg] = cast(module.params[arg])
+ else:
+ obj[arg] = None
+
+ return obj
+
+
+def main():
+ """main entry point for module execution"""
+ argument_spec = dict(
+ hostname=dict(),
+ domain_lookup=dict(type="bool"),
+ # { name: <str>, vrf: <str> }
+ domain_name=dict(type="list", elements="raw"),
+ # {name: <str>, vrf: <str> }
+ domain_search=dict(type="list", elements="raw"),
+ # { server: <str>; vrf: <str> }
+ name_servers=dict(type="list", elements="raw"),
+ system_mtu=dict(type="str"),
+ state=dict(default="present", choices=["present", "absent"]),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands(want, have, module)
+ result["commands"] = commands
+
+ if commands:
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_telemetry.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_telemetry.py
new file mode 100644
index 00000000..7498ff88
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_telemetry.py
@@ -0,0 +1,339 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Cisco and/or its affiliates.
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_telemetry
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_telemetry
+short_description: TELEMETRY resource module
+description: Manages Telemetry Monitoring Service (TMS) configuration
+version_added: 1.0.0
+author: Mike Wiebe (@mikewiebe)
+notes:
+- Supported on N9k Version 7.0(3)I7(5) and later.
+- Unsupported for Cisco MDS
+options:
+ config:
+ description: The provided configuration
+ type: dict
+ suboptions:
+ certificate:
+ type: dict
+ description:
+ - Certificate SSL/TLS and hostname values.
+ - Value must be a dict defining values for keys (key and hostname).
+ suboptions:
+ key:
+ description:
+ - Certificate key
+ type: str
+ hostname:
+ description:
+ - Certificate hostname
+ type: str
+ compression:
+ type: str
+ description:
+ - Destination profile compression method.
+ choices:
+ - gzip
+ source_interface:
+ type: str
+ description:
+ - Destination profile source interface.
+ - Valid value is a str representing the source interface name.
+ vrf:
+ type: str
+ description:
+ - Destination profile vrf.
+ - Valid value is a str representing the vrf name.
+ destination_groups:
+ type: list
+ description:
+ - List of telemetry destination groups.
+ elements: raw
+ suboptions:
+ id:
+ type: str
+ description:
+ - Destination group identifier.
+ - Value must be an integer or string representing the destination group identifier.
+ destination:
+ type: dict
+ description:
+ - Group destination ipv4, port, protocol and encoding values.
+ - Value must be a dict defining values for keys (ip, port, protocol, encoding).
+ suboptions:
+ ip:
+ type: str
+ description:
+ - Destination group IP address.
+ port:
+ type: int
+ description:
+ - Destination group port number.
+ protocol:
+ type: str
+ description:
+ - Destination group protocol.
+ choices:
+ - HTTP
+ - TCP
+ - UDP
+ - gRPC
+ encoding:
+ type: str
+ description:
+ - Destination group encoding.
+ choices:
+ - GPB
+ - JSON
+ sensor_groups:
+ type: list
+ description:
+ - List of telemetry sensor groups.
+ elements: raw
+ suboptions:
+ id:
+ type: str
+ description:
+ - Sensor group identifier.
+ - Value must be a integer or a string representing the sensor group identifier.
+ data_source:
+ type: str
+ description:
+ - Telemetry data source.
+ choices:
+ - NX-API
+ - DME
+ - YANG
+ path:
+ type: dict
+ description:
+ - Telemetry sensor path.
+ - Value must be a dict defining values for keys (name, depth, filter_condition,
+ query_condition).
+ - Mandatory Keys (name)
+ - Optional Keys (depth, filter_condition, query_condition)
+ suboptions:
+ name:
+ type: str
+ description:
+ - Sensor group path name.
+ depth:
+ type: str
+ description:
+ - Sensor group depth.
+ filter_condition:
+ type: str
+ description:
+ - Sensor group filter condition.
+ query_condition:
+ type: str
+ description:
+ - Sensor group query condition.
+ subscriptions:
+ type: list
+ description:
+ - List of telemetry subscriptions.
+ elements: raw
+ suboptions:
+ id:
+ type: str
+ description:
+ - Subscription identifier.
+ - Value must be an integer or string representing the subscription identifier.
+ destination_group:
+ type: str
+ description:
+ - Associated destination group.
+ sensor_group:
+ type: dict
+ description:
+ - Associated sensor group.
+ - Value must be a dict defining values for keys (id, sample_interval).
+ suboptions:
+ id:
+ type: str
+ description:
+ - Associated sensor group id.
+ sample_interval:
+ type: int
+ description:
+ - Associated sensor group id sample interval.
+ state:
+ description:
+ - Final configuration state
+ type: str
+ choices:
+ - merged
+ - replaced
+ - deleted
+ - gathered
+ default: merged
+
+"""
+EXAMPLES = """
+# Using deleted
+# This action will delete all telemetry configuration on the device
+
+- name: Delete Telemetry Configuration
+ cisco.nxos.nxos_telemetry:
+ state: deleted
+
+
+# Using merged
+# This action will merge telemetry configuration defined in the playbook with
+# telemetry configuration that is already on the device.
+
+- name: Merge Telemetry Configuration
+ cisco.nxos.nxos_telemetry:
+ config:
+ certificate:
+ key: /bootflash/server.key
+ hostname: localhost
+ compression: gzip
+ source_interface: Ethernet1/1
+ vrf: management
+ destination_groups:
+ - id: 2
+ destination:
+ ip: 192.168.0.2
+ port: 50001
+ protocol: gRPC
+ encoding: GPB
+ - id: 55
+ destination:
+ ip: 192.168.0.55
+ port: 60001
+ protocol: gRPC
+ encoding: GPB
+ sensor_groups:
+ - id: 1
+ data_source: NX-API
+ path:
+ name: '"show lldp neighbors detail"'
+ depth: 0
+ - id: 55
+ data_source: DME
+ path:
+ name: sys/ch
+ depth: unbounded
+ filter_condition: ne(eqptFt.operSt,"ok")
+ subscriptions:
+ - id: 5
+ destination_group: 55
+ sensor_group:
+ id: 1
+ sample_interval: 1000
+ - id: 6
+ destination_group: 2
+ sensor_group:
+ id: 55
+ sample_interval: 2000
+ state: merged
+
+
+# Using replaced
+# This action will replace telemetry configuration on the device with the
+# telemetry configuration defined in the playbook.
+
+- name: Override Telemetry Configuration
+ cisco.nxos.nxos_telemetry:
+ config:
+ certificate:
+ key: /bootflash/server.key
+ hostname: localhost
+ compression: gzip
+ source_interface: Ethernet1/1
+ vrf: management
+ destination_groups:
+ - id: 2
+ destination:
+ ip: 192.168.0.2
+ port: 50001
+ protocol: gRPC
+ encoding: GPB
+ subscriptions:
+ - id: 5
+ destination_group: 55
+ state: replaced
+
+
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: dict
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['command 1', 'command 2', 'command 3']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.telemetry.telemetry import (
+ TelemetryArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.telemetry.telemetry import (
+ Telemetry,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=TelemetryArgs.argument_spec, supports_check_mode=True)
+
+ result = Telemetry(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_udld.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld.py
new file mode 100644
index 00000000..83955e15
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld.py
@@ -0,0 +1,254 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_udld
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages UDLD global configuration params.
+description:
+- Manages UDLD global configuration params.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Module will fail if the udld feature has not been previously enabled.
+options:
+ aggressive:
+ description:
+ - Toggles aggressive mode.
+ choices:
+ - enabled
+ - disabled
+ type: str
+ msg_time:
+ description:
+ - Message time in seconds for UDLD packets or keyword 'default'.
+ type: str
+ reset:
+ description:
+ - Ability to reset all ports shut down by UDLD. 'state' parameter cannot be 'absent'
+ when this is present.
+ type: bool
+ state:
+ description:
+ - Manage the state of the resource. When set to 'absent', aggressive and msg_time
+ are set to their default values.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+EXAMPLES = """
+# ensure udld aggressive mode is globally disabled and se global message interval is 20
+- cisco.nxos.nxos_udld:
+ aggressive: disabled
+ msg_time: 20
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+
+# Ensure agg mode is globally enabled and msg time is 15
+- cisco.nxos.nxos_udld:
+ aggressive: enabled
+ msg_time: 15
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"aggressive": "enabled", "msg_time": "40"}
+existing:
+ description:
+ - k/v pairs of existing udld configuration
+ returned: always
+ type: dict
+ sample: {"aggressive": "disabled", "msg_time": "15"}
+end_state:
+ description: k/v pairs of udld configuration after module execution
+ returned: always
+ type: dict
+ sample: {"aggressive": "enabled", "msg_time": "40"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["udld message-time 40", "udld aggressive"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+PARAM_TO_DEFAULT_KEYMAP = {"msg_time": "15"}
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = str(value)
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def get_commands_config_udld_global(delta, reset, existing):
+ commands = []
+ for param, value in delta.items():
+ if param == "aggressive":
+ command = "udld aggressive" if value == "enabled" else "no udld aggressive"
+ commands.append(command)
+ elif param == "msg_time":
+ if value == "default":
+ if existing.get("msg_time") != PARAM_TO_DEFAULT_KEYMAP.get("msg_time"):
+ commands.append("no udld message-time")
+ else:
+ commands.append("udld message-time " + value)
+ if reset:
+ command = "udld reset"
+ commands.append(command)
+ return commands
+
+
+def get_commands_remove_udld_global(existing):
+ commands = []
+ if existing.get("aggressive") == "enabled":
+ command = "no udld aggressive"
+ commands.append(command)
+ if existing.get("msg_time") != PARAM_TO_DEFAULT_KEYMAP.get("msg_time"):
+ command = "no udld message-time"
+ commands.append(command)
+ return commands
+
+
+def get_udld_global(module):
+ command = "show udld global | json"
+ udld_table = run_commands(module, [command])[0]
+
+ status = str(udld_table.get("udld-global-mode", None))
+ if status == "enabled-aggressive":
+ aggressive = "enabled"
+ else:
+ aggressive = "disabled"
+
+ interval = str(udld_table.get("message-interval", None))
+ udld = dict(msg_time=interval, aggressive=aggressive)
+
+ return udld
+
+
+def main():
+ argument_spec = dict(
+ aggressive=dict(required=False, choices=["enabled", "disabled"]),
+ msg_time=dict(required=False, type="str"),
+ reset=dict(required=False, type="bool"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ aggressive = module.params["aggressive"]
+ msg_time = module.params["msg_time"]
+ reset = module.params["reset"]
+ state = module.params["state"]
+
+ if reset and state == "absent":
+ module.fail_json(msg="state must be present when using reset flag.")
+
+ args = dict(aggressive=aggressive, msg_time=msg_time, reset=reset)
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+
+ existing = get_udld_global(module)
+ end_state = existing
+
+ delta = set(proposed.items()).difference(existing.items())
+ changed = False
+
+ commands = []
+ if state == "present":
+ if delta:
+ command = get_commands_config_udld_global(dict(delta), reset, existing)
+ commands.append(command)
+
+ elif state == "absent":
+ command = get_commands_remove_udld_global(existing)
+ if command:
+ commands.append(command)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_udld_global(module)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["end_state"] = end_state
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_udld_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld_interface.py
new file mode 100644
index 00000000..b78ed5c7
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_udld_interface.py
@@ -0,0 +1,301 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_udld_interface
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages UDLD interface configuration params.
+description:
+- Manages UDLD interface configuration params.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Feature UDLD must be enabled on the device to use this module.
+options:
+ mode:
+ description:
+ - Manages UDLD mode for an interface.
+ required: true
+ choices:
+ - enabled
+ - disabled
+ - aggressive
+ type: str
+ interface:
+ description:
+ - FULL name of the interface, i.e. Ethernet1/1-
+ required: true
+ type: str
+ state:
+ description:
+ - Manage the state of the resource.
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+EXAMPLES = """
+# ensure Ethernet1/1 is configured to be in aggressive mode
+- cisco.nxos.nxos_udld_interface:
+ interface: Ethernet1/1
+ mode: aggressive
+ state: present
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+
+# Remove the aggressive config only if it's currently in aggressive mode and then disable udld (switch default)
+- cisco.nxos.nxos_udld_interface:
+ interface: Ethernet1/1
+ mode: aggressive
+ state: absent
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+
+# ensure Ethernet1/1 has aggressive mode enabled
+- cisco.nxos.nxos_udld_interface:
+ interface: Ethernet1/1
+ mode: enabled
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"mode": "enabled"}
+existing:
+ description:
+ - k/v pairs of existing configuration
+ returned: always
+ type: dict
+ sample: {"mode": "aggressive"}
+end_state:
+ description: k/v pairs of configuration after module execution
+ returned: always
+ type: dict
+ sample: {"mode": "enabled"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["interface ethernet1/33",
+ "no udld aggressive ; no udld disable"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_udld_interface(module, interface):
+ command = "show run udld all | section " + interface.title() + "$"
+ interface_udld = {}
+ mode = None
+ mode_str = None
+ try:
+ body = run_commands(module, [{"command": command, "output": "text"}])[0]
+ if "aggressive" in body:
+ mode = "aggressive"
+ mode_str = "aggressive"
+ elif "no udld enable" in body:
+ mode = "disabled"
+ mode_str = "no udld enable"
+ elif "no udld disable" in body:
+ mode = "enabled"
+ mode_str = "no udld disable"
+ elif "udld disable" in body:
+ mode = "disabled"
+ mode_str = "udld disable"
+ elif "udld enable" in body:
+ mode = "enabled"
+ mode_str = "udld enable"
+ interface_udld["mode"] = mode
+
+ except (KeyError, AttributeError, IndexError):
+ interface_udld = {}
+
+ return interface_udld, mode_str
+
+
+def get_commands_config_udld_interface1(delta, interface, module, existing):
+ commands = []
+ mode = delta["mode"]
+ if mode == "aggressive":
+ commands.append("udld aggressive")
+ else:
+ commands.append("no udld aggressive")
+ commands.insert(0, "interface {0}".format(interface))
+
+ return commands
+
+
+def get_commands_config_udld_interface2(delta, interface, module, existing):
+ commands = []
+ existing, mode_str = get_udld_interface(module, interface)
+ mode = delta["mode"]
+ if mode == "enabled":
+ if mode_str == "no udld enable":
+ command = "udld enable"
+ else:
+ command = "no udld disable"
+ else:
+ if mode_str == "no udld disable":
+ command = "udld disable"
+ else:
+ command = "no udld enable"
+ if command:
+ commands.append(command)
+ commands.insert(0, "interface {0}".format(interface))
+
+ return commands
+
+
+def get_commands_remove_udld_interface(delta, interface, module, existing):
+ commands = []
+ existing, mode_str = get_udld_interface(module, interface)
+
+ mode = delta["mode"]
+ if mode == "aggressive":
+ command = "no udld aggressive"
+ else:
+ if mode == "enabled":
+ if mode_str == "udld enable":
+ command = "no udld enable"
+ else:
+ command = "udld disable"
+ elif mode == "disabled":
+ if mode_str == "no udld disable":
+ command = "udld disable"
+ else:
+ command = "no udld enable"
+ if command:
+ commands.append(command)
+ commands.insert(0, "interface {0}".format(interface))
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ mode=dict(choices=["enabled", "disabled", "aggressive"], required=True),
+ interface=dict(type="str", required=True),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ interface = module.params["interface"].lower()
+ mode = module.params["mode"]
+ state = module.params["state"]
+
+ proposed = dict(mode=mode)
+ existing, mode_str = get_udld_interface(module, interface)
+ end_state = existing
+
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ changed = False
+ commands = []
+ cmds = []
+ if state == "present":
+ if delta:
+ command = get_commands_config_udld_interface1(delta, interface, module, existing)
+ commands.append(command)
+ cmds = flatten_list(commands)
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+
+ if delta["mode"] == "enabled" or delta["mode"] == "disabled":
+ commands = []
+ command = get_commands_config_udld_interface2(delta, interface, module, existing)
+ commands.append(command)
+ cmds = flatten_list(commands)
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ load_config(module, cmds)
+
+ else:
+ common = set(proposed.items()).intersection(existing.items())
+ if common:
+ command = get_commands_remove_udld_interface(dict(common), interface, module, existing)
+ cmds = flatten_list(commands)
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+
+ if not module.check_mode:
+ end_state, mode_str = get_udld_interface(module, interface)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["end_state"] = end_state
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_user.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_user.py
new file mode 100644
index 00000000..83c22a76
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_user.py
@@ -0,0 +1,473 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_user
+extends_documentation_fragment:
+- cisco.nxos.nxos
+author: Peter Sprygada (@privateip)
+notes:
+- Limited Support for Cisco MDS
+short_description: Manage the collection of local users on Nexus devices
+description:
+- This module provides declarative management of the local usernames configured on
+ Cisco Nexus devices. It allows playbooks to manage either individual usernames
+ or the collection of usernames in the current running config. It also supports
+ purging usernames from the configuration that are not explicitly defined.
+version_added: 1.0.0
+options:
+ aggregate:
+ description:
+ - The set of username objects to be configured on the remote Cisco Nexus device. The
+ list entries can either be the username or a hash of username and properties. This
+ argument is mutually exclusive with the C(name) argument.
+ aliases:
+ - users
+ - collection
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - The username to be configured on the remote Cisco Nexus device. This argument
+ accepts a string value and is mutually exclusive with the C(aggregate) argument.
+ type: str
+ configured_password:
+ description:
+ - The password to be configured on the network device. The password needs to be
+ provided in cleartext and it will be encrypted on the device.
+ type: str
+ update_password:
+ description:
+ - Since passwords are encrypted in the device running config, this argument will
+ instruct the module when to change the password. When set to C(always), the
+ password will always be updated in the device and when set to C(on_create) the
+ password will be updated only if the username is created.
+ choices:
+ - on_create
+ - always
+ type: str
+ roles:
+ description:
+ - The C(role) argument configures the role for the username in the device running
+ configuration. The argument accepts a string value defining the role name. This
+ argument does not check if the role has been configured on the device.
+ aliases:
+ - role
+ type: list
+ elements: str
+ sshkey:
+ description:
+ - The C(sshkey) argument defines the SSH public key to configure for the username. This
+ argument accepts a valid SSH key value.
+ type: str
+ state:
+ description:
+ - The C(state) argument configures the state of the username definition as it
+ relates to the device operational configuration. When set to I(present), the
+ username(s) should be configured in the device active configuration and when
+ set to I(absent) the username(s) should not be in the device active configuration
+ choices:
+ - present
+ - absent
+ type: str
+ name:
+ description:
+ - The username to be configured on the remote Cisco Nexus device. This argument
+ accepts a string value and is mutually exclusive with the C(aggregate) argument.
+ type: str
+ configured_password:
+ description:
+ - The password to be configured on the network device. The password needs to be
+ provided in cleartext and it will be encrypted on the device.
+ type: str
+ update_password:
+ description:
+ - Since passwords are encrypted in the device running config, this argument will
+ instruct the module when to change the password. When set to C(always), the
+ password will always be updated in the device and when set to C(on_create) the
+ password will be updated only if the username is created.
+ default: always
+ choices:
+ - on_create
+ - always
+ type: str
+ roles:
+ description:
+ - The C(role) argument configures the role for the username in the device running
+ configuration. The argument accepts a string value defining the role name. This
+ argument does not check if the role has been configured on the device.
+ aliases:
+ - role
+ type: list
+ elements: str
+ sshkey:
+ description:
+ - The C(sshkey) argument defines the SSH public key to configure for the username. This
+ argument accepts a valid SSH key value.
+ type: str
+ purge:
+ description:
+ - The C(purge) argument instructs the module to consider the resource definition
+ absolute. It will remove any previously configured usernames on the device
+ with the exception of the `admin` user which cannot be deleted per nxos constraints.
+ type: bool
+ default: no
+ state:
+ description:
+ - The C(state) argument configures the state of the username definition as it
+ relates to the device operational configuration. When set to I(present), the
+ username(s) should be configured in the device active configuration and when
+ set to I(absent) the username(s) should not be in the device active configuration
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: create a new user
+ cisco.nxos.nxos_user:
+ name: ansible
+ sshkey: "{{ lookup('file', '~/.ssh/id_rsa.pub') }}"
+ state: present
+
+- name: remove all users except admin
+ cisco.nxos.nxos_user:
+ purge: yes
+
+- name: set multiple users role
+ cisco.nxos.nxos_user:
+ aggregate:
+ - name: netop
+ - name: netend
+ role: network-operator
+ state: present
+"""
+
+RETURN = """
+commands:
+ description: The list of configuration mode commands to send to the device
+ returned: always
+ type: list
+ sample:
+ - name ansible
+ - name ansible password password
+"""
+import re
+
+from copy import deepcopy
+from functools import partial
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.six import iteritems
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_default_spec,
+ to_list,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+ run_commands,
+)
+
+
+BUILTIN_ROLES = [
+ "network-admin",
+ "network-operator",
+ "vdc-admin",
+ "vdc-operator",
+ "priv-15",
+ "priv-14",
+ "priv-13",
+ "priv-12",
+ "priv-11",
+ "priv-10",
+ "priv-9",
+ "priv-8",
+ "priv-7",
+ "priv-6",
+ "priv-5",
+ "priv-4",
+ "priv-3",
+ "priv-2",
+ "priv-1",
+ "priv-0",
+]
+
+
+def get_custom_roles(module):
+ return re.findall(
+ r"^role name (\S+)",
+ get_config(module, flags=["| include '^role name'"]),
+ re.M,
+ )
+
+
+def validate_roles(value, module):
+ valid_roles = BUILTIN_ROLES + get_custom_roles(module)
+ for item in value:
+ if item not in valid_roles:
+ module.fail_json(msg="invalid role specified")
+
+
+def map_obj_to_commands(updates, module):
+ commands = list()
+ update_password = module.params["update_password"]
+
+ for update in updates:
+ want, have = update
+
+ def needs_update(x):
+ return want.get(x) and (want.get(x) != have.get(x))
+
+ def add(x):
+ return commands.append("username %s %s" % (want["name"], x))
+
+ def remove(x):
+ return commands.append("no username %s %s" % (want["name"], x))
+
+ def configure_roles():
+ if want["roles"]:
+ if have:
+ for item in set(have["roles"]).difference(want["roles"]):
+ remove("role %s" % item)
+
+ for item in set(want["roles"]).difference(have["roles"]):
+ add("role %s" % item)
+ else:
+ for item in want["roles"]:
+ add("role %s" % item)
+
+ return True
+ return False
+
+ if want["state"] == "absent":
+ commands.append("no username %s" % want["name"])
+ continue
+
+ roles_configured = False
+ if want["state"] == "present" and not have:
+ roles_configured = configure_roles()
+ if not roles_configured:
+ commands.append("username %s" % want["name"])
+
+ if needs_update("configured_password"):
+ if update_password == "always" or not have:
+ add("password %s" % want["configured_password"])
+
+ if needs_update("sshkey"):
+ add("sshkey %s" % want["sshkey"])
+
+ if not roles_configured:
+ configure_roles()
+
+ return commands
+
+
+def parse_password(data):
+ if not data.get("remote_login"):
+ return "<PASSWORD>"
+
+
+def parse_roles(data):
+ configured_roles = None
+ if "TABLE_role" in data:
+ configured_roles = data.get("TABLE_role")["ROW_role"]
+
+ roles = list()
+ if configured_roles:
+ for item in to_list(configured_roles):
+ roles.append(item["role"])
+ return roles
+
+
+def map_config_to_obj(module):
+ out = run_commands(module, [{"command": "show user-account", "output": "json"}])
+ data = out[0]
+
+ objects = list()
+
+ for item in to_list(data["TABLE_template"]["ROW_template"]):
+ objects.append(
+ {
+ "name": item["usr_name"],
+ "configured_password": parse_password(item),
+ "sshkey": item.get("sshkey_info"),
+ "roles": parse_roles(item),
+ "state": "present",
+ },
+ )
+ return objects
+
+
+def get_param_value(key, item, module):
+ # if key doesn't exist in the item, get it from module.params
+ if not item.get(key):
+ value = module.params[key]
+
+ # if key does exist, do a type check on it to validate it
+ else:
+ value_type = module.argument_spec[key].get("type", "str")
+ type_checker = module._CHECK_ARGUMENT_TYPES_DISPATCHER[value_type]
+ type_checker(item[key])
+ value = item[key]
+
+ return value
+
+
+def map_params_to_obj(module):
+ aggregate = module.params["aggregate"]
+ if not aggregate:
+ if not module.params["name"] and module.params["purge"]:
+ return list()
+ elif not module.params["name"]:
+ module.fail_json(msg="username is required")
+ else:
+ collection = [{"name": module.params["name"]}]
+ else:
+ collection = list()
+ for item in aggregate:
+ if not isinstance(item, dict):
+ collection.append({"name": item})
+ elif "name" not in item:
+ module.fail_json(msg="name is required")
+ else:
+ collection.append(item)
+
+ objects = list()
+
+ for item in collection:
+ get_value = partial(get_param_value, item=item, module=module)
+ item.update(
+ {
+ "configured_password": get_value("configured_password"),
+ "sshkey": get_value("sshkey"),
+ "roles": get_value("roles"),
+ "state": get_value("state"),
+ },
+ )
+
+ for key, value in iteritems(item):
+ if value:
+ # validate the param value (if validator func exists)
+ validator = globals().get("validate_%s" % key)
+ if all((value, validator)):
+ validator(value, module)
+
+ objects.append(item)
+
+ return objects
+
+
+def update_objects(want, have):
+ updates = list()
+ for entry in want:
+ item = next((i for i in have if i["name"] == entry["name"]), None)
+ if all((item is None, entry["state"] == "present")):
+ updates.append((entry, {}))
+ elif item:
+ for key, value in iteritems(entry):
+ if value and value != item[key]:
+ updates.append((entry, item))
+ return updates
+
+
+def main():
+ """main entry point for module execution"""
+ element_spec = dict(
+ name=dict(),
+ configured_password=dict(no_log=True),
+ update_password=dict(default="always", choices=["on_create", "always"]),
+ roles=dict(type="list", aliases=["role"], elements="str"),
+ sshkey=dict(no_log=False),
+ state=dict(default="present", choices=["present", "absent"]),
+ )
+
+ aggregate_spec = deepcopy(element_spec)
+
+ # remove default in aggregate spec, to handle common arguments
+ remove_default_spec(aggregate_spec)
+
+ argument_spec = dict(
+ aggregate=dict(
+ type="list",
+ elements="dict",
+ options=aggregate_spec,
+ aliases=["collection", "users"],
+ ),
+ purge=dict(type="bool", default=False),
+ )
+
+ argument_spec.update(element_spec)
+
+ mutually_exclusive = [("name", "aggregate")]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ result = {"changed": False, "warnings": []}
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(module)
+
+ commands = map_obj_to_commands(update_objects(want, have), module)
+
+ if module.params["purge"]:
+ want_users = [x["name"] for x in want]
+ have_users = [x["name"] for x in have]
+ for item in set(have_users).difference(want_users):
+ if item != "admin":
+ item = item.replace("\\", "\\\\")
+ commands.append("no username %s" % item)
+
+ result["commands"] = commands
+
+ # the nxos cli prevents this by rule so capture it and display
+ # a nice failure message
+ if "no username admin" in commands:
+ module.fail_json(msg="cannot delete the `admin` account")
+
+ if commands:
+ if not module.check_mode:
+ responses = load_config(module, commands)
+ for resp in responses:
+ if resp.lower().startswith("wrong password"):
+ module.fail_json(msg=resp)
+ else:
+ result["warnings"].extend(
+ [x[9:] for x in resp.splitlines() if x.startswith("WARNING: ")],
+ )
+
+ result["changed"] = True
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vlans.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vlans.py
new file mode 100644
index 00000000..cac276b6
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vlans.py
@@ -0,0 +1,439 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# Copyright 2019 Red Hat
+# GNU General Public License v3.0+
+# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+#############################################
+# WARNING #
+#############################################
+#
+# This file is auto generated by the resource
+# module builder playbook.
+#
+# Do not edit this file manually.
+#
+# Changes to this file will be over written
+# by the resource module builder.
+#
+# Changes should be made in the model used to
+# generate this file or in the resource module
+# builder template.
+#
+#############################################
+
+"""
+The module file for nxos_vlans
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vlans
+short_description: VLANs resource module
+description: This module creates and manages VLAN configurations on Cisco NX-OS.
+version_added: 1.0.0
+author: Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOS 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+options:
+ running_config:
+ description:
+ - This option is used only with state I(parsed).
+ - The value of this option should be the output received from the NX-OS device
+ by executing the commands B(show vlans | json-pretty) and B(show running-config
+ | section ^vlan) in order and delimited by a line.
+ - The state I(parsed) reads the configuration from C(running_config) option and
+ transforms it into Ansible structured data as per the resource module's argspec
+ and the value is then returned in the I(parsed) key within the result.
+ type: str
+ config:
+ description: A dictionary of Vlan options
+ type: list
+ elements: dict
+ suboptions:
+ vlan_id:
+ description:
+ - Vlan ID.
+ type: int
+ required: true
+ name:
+ description:
+ - Name of VLAN.
+ type: str
+ state:
+ description:
+ - Manage operational state of the vlan.
+ type: str
+ choices:
+ - active
+ - suspend
+ enabled:
+ description:
+ - Manage administrative state of the vlan.
+ type: bool
+ mode:
+ description:
+ - Set vlan mode to classical ethernet or fabricpath. This is a valid option
+ for Nexus 5000, 6000 and 7000 series.
+ type: str
+ choices:
+ - ce
+ - fabricpath
+ mapped_vni:
+ description:
+ - The Virtual Network Identifier (VNI) ID that is mapped to the VLAN.
+ type: int
+ state:
+ description:
+ - The state of the configuration after module completion.
+ - The state I(overridden) would override the configuration of all the
+ VLANs on the device (including VLAN 1) with the provided configuration in
+ the task. Use caution with this state.
+ type: str
+ choices:
+ - merged
+ - replaced
+ - overridden
+ - deleted
+ - gathered
+ - rendered
+ - parsed
+ default: merged
+
+"""
+EXAMPLES = """
+# Using merged
+
+# Before state:
+# -------------
+# vlan 1
+
+- name: Merge provided configuration with device configuration.
+ cisco.nxos.nxos_vlans:
+ config:
+ - vlan_id: 5
+ name: test-vlan5
+ - vlan_id: 10
+ enabled: false
+ state: merged
+
+# After state:
+# ------------
+# vlan 5
+# name test-vlan5
+# state active
+# no shutdown
+# vlan 10
+# state active
+# shutdown
+
+
+# Using replaced
+
+# Before state:
+# -------------
+# vlan 1
+# vlan 5
+# name test-vlan5
+# vlan 10
+# shutdown
+
+- name: Replace device configuration of specified vlan with provided configuration.
+ cisco.nxos.nxos_vlans:
+ config:
+ - vlan_id: 5
+ name: test-vlan
+ enabled: false
+ - vlan_id: 10
+ enabled: false
+ state: replaced
+
+# After state:
+# ------------
+# vlan 1
+# vlan 5
+# name test-vlan
+# state active
+# shutdown
+# vlan 10
+# state active
+# shutdown
+
+
+# Using overridden
+
+# Before state:
+# -------------
+# vlan 1
+# vlan 3
+# name testing
+# vlan 5
+# name test-vlan5
+# shutdown
+# vlan 10
+# shutdown
+
+- name: Override device configuration of all vlans with provided configuration.
+ cisco.nxos.nxos_vlans:
+ config:
+ - vlan_id: 5
+ name: test-vlan
+ - vlan_id: 10
+ state: active
+ state: overridden
+
+# After state:
+# ------------
+# vlan 5
+# name test-vlan
+# state active
+# no shutdown
+# vlan 10
+# state active
+# no shutdown
+
+
+# Using deleted
+
+# Before state:
+# -------------
+# vlan 1
+# vlan 5
+# vlan 10
+
+- name: Delete vlans.
+ cisco.nxos.nxos_vlans:
+ config:
+ - vlan_id: 5
+ - vlan_id: 10
+ state: deleted
+
+# After state:
+# ------------
+#
+
+# Using rendered
+
+- name: Use rendered state to convert task input to device specific commands
+ cisco.nxos.nxos_vlans:
+ config:
+ - vlan_id: 5
+ name: vlan5
+ mapped_vni: 100
+
+ - vlan_id: 6
+ name: vlan6
+ state: suspend
+ state: rendered
+
+# Task Output (redacted)
+# -----------------------
+
+# rendered:
+# - vlan 5
+# - name vlan5
+# - vn-segment 100
+# - vlan 6
+# - name vlan6
+# - state suspend
+
+# Using parsed
+
+# parsed.cfg
+# ------------
+# {
+# "TABLE_vlanbrief": {
+# "ROW_vlanbrief": [
+# {
+# "vlanshowbr-vlanid": "1",
+# "vlanshowbr-vlanid-utf": "1",
+# "vlanshowbr-vlanname": "default",
+# "vlanshowbr-vlanstate": "active",
+# "vlanshowbr-shutstate": "noshutdown"
+# },
+# {
+# "vlanshowbr-vlanid": "5",
+# "vlanshowbr-vlanid-utf": "5",
+# "vlanshowbr-vlanname": "vlan5",
+# "vlanshowbr-vlanstate": "suspend",
+# "vlanshowbr-shutstate": "noshutdown"
+# },
+# {
+# "vlanshowbr-vlanid": "6",
+# "vlanshowbr-vlanid-utf": "6",
+# "vlanshowbr-vlanname": "VLAN0006",
+# "vlanshowbr-vlanstate": "active",
+# "vlanshowbr-shutstate": "noshutdown"
+# },
+# {
+# "vlanshowbr-vlanid": "7",
+# "vlanshowbr-vlanid-utf": "7",
+# "vlanshowbr-vlanname": "vlan7",
+# "vlanshowbr-vlanstate": "active",
+# "vlanshowbr-shutstate": "noshutdown"
+# }
+# ]
+# },
+# "TABLE_mtuinfo": {
+# "ROW_mtuinfo": [
+# {
+# "vlanshowinfo-vlanid": "1",
+# "vlanshowinfo-media-type": "enet",
+# "vlanshowinfo-vlanmode": "ce-vlan"
+# },
+# {
+# "vlanshowinfo-vlanid": "5",
+# "vlanshowinfo-media-type": "enet",
+# "vlanshowinfo-vlanmode": "ce-vlan"
+# },
+# {
+# "vlanshowinfo-vlanid": "6",
+# "vlanshowinfo-media-type": "enet",
+# "vlanshowinfo-vlanmode": "ce-vlan"
+# },
+# {
+# "vlanshowinfo-vlanid": "7",
+# "vlanshowinfo-media-type": "enet",
+# "vlanshowinfo-vlanmode": "ce-vlan"
+# }
+# ]
+# }
+# }
+#
+# vlan 1,5-7
+# vlan 5
+# state suspend
+# name vlan5
+# vlan 7
+# name vlan7
+# vn-segment 100
+
+- name: Use parsed state to convert externally supplied config to structured format
+ cisco.nxos.nxos_vlans:
+ running_config: "{{ lookup('file', 'parsed.cfg') }}"
+ state: parsed
+
+# Task output (redacted)
+# -----------------------
+
+# parsed:
+# - vlan_id: 5
+# enabled: True
+# mode: "ce"
+# name: "vlan5"
+# state: suspend
+#
+# - vlan_id: 6
+# enabled: True
+# mode: "ce"
+# state: active
+#
+# - vlan_id: 7
+# enabled: True
+# mode: "ce"
+# name: "vlan7"
+# state: active
+# mapped_vni: 100
+
+# Using gathered
+
+# Existing device config state
+# -------------------------------
+# nxos-9k# show vlan | json
+# {"TABLE_vlanbrief": {"ROW_vlanbrief": [{"vlanshowbr-vlanid": "1", "vlanshowbr-vlanid-utf": "1", "vlanshowbr-vlanname": "default", "vlanshowbr-vlanstate
+# ": "active", "vlanshowbr-shutstate": "noshutdown"}, {"vlanshowbr-vlanid": "5", "vlanshowbr-vlanid-utf": "5", "vlanshowbr-vlanname": "vlan5", "vlanshowb
+# r-vlanstate": "suspend", "vlanshowbr-shutstate": "noshutdown"}, {"vlanshowbr-vlanid": "6", "vlanshowbr-vlanid-utf": "6", "vlanshowbr-vlanname": "VLAN00
+# 06", "vlanshowbr-vlanstate": "active", "vlanshowbr-shutstate": "noshutdown"}, {"vlanshowbr-vlanid": "7", "vlanshowbr-vlanid-utf": "7", "vlanshowbr-vlan
+# name": "vlan7", "vlanshowbr-vlanstate": "active", "vlanshowbr-shutstate": "shutdown"}]}, "TABLE_mtuinfo": {"ROW_mtuinfo": [{"vlanshowinfo-vlanid": "1",
+# "vlanshowinfo-media-type": "enet", "vlanshowinfo-vlanmode": "ce-vlan"}, {"vlanshowinfo-vlanid": "5", "vlanshowinfo-media-type": "enet", "vlanshowinfo-
+# vlanmode": "ce-vlan"}, {"vlanshowinfo-vlanid": "6", "vlanshowinfo-media-type": "enet", "vlanshowinfo-vlanmode": "ce-vlan"}, {"vlanshowinfo-vlanid": "7"
+# , "vlanshowinfo-media-type": "enet", "vlanshowinfo-vlanmode": "ce-vlan"}]}}
+#
+# nxos-9k# show running-config | section ^vlan
+# vlan 1,5-7
+# vlan 5
+# state suspend
+# name vlan5
+# vlan 7
+# shutdown
+# name vlan7
+# vn-segment 190
+
+- name: Gather vlans facts from the device using nxos_vlans
+ cisco.nxos.nxos_vlans:
+ state: gathered
+
+# Task output (redacted)
+# -----------------------
+# gathered:
+# - vlan_id: 5
+# enabled: True
+# mode: "ce"
+# name: "vlan5"
+# state: suspend
+#
+# - vlan_id: 6
+# enabled: True
+# mode: "ce"
+# state: active
+#
+# - vlan_id: 7
+# enabled: False
+# mode: "ce"
+# name: "vlan7"
+# state: active
+# mapped_vni: 190
+"""
+RETURN = """
+before:
+ description: The configuration as structured data prior to module invocation.
+ returned: always
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+after:
+ description: The configuration as structured data after module completion.
+ returned: when changed
+ type: list
+ sample: >
+ The configuration returned will always be in the same format
+ of the parameters above.
+commands:
+ description: The set of commands pushed to the remote device.
+ returned: always
+ type: list
+ sample: ['vlan 5', 'name test-vlan5', 'state suspend']
+"""
+
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.argspec.vlans.vlans import (
+ VlansArgs,
+)
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.config.vlans.vlans import (
+ Vlans,
+)
+
+
+def main():
+ """
+ Main entry point for module execution
+
+ :returns: the result form module invocation
+ """
+ module = AnsibleModule(argument_spec=VlansArgs.argument_spec, supports_check_mode=True)
+
+ result = Vlans(module).execute_module()
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc.py
new file mode 100644
index 00000000..f660efc1
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc.py
@@ -0,0 +1,470 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vpc
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages global VPC configuration
+description:
+- Manages global VPC configuration
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- The feature vpc must be enabled before this module can be used
+- If not using management vrf, vrf must be globally on the device before using in
+ the pkl config
+- Although source IP isn't required on the command line it is required when using
+ this module. The PKL VRF must also be configured prior to using this module.
+- Both pkl_src and pkl_dest are needed when changing PKL VRF.
+options:
+ domain:
+ description:
+ - VPC domain
+ required: true
+ type: str
+ role_priority:
+ description:
+ - Role priority for device. Remember lower is better.
+ type: str
+ system_priority:
+ description:
+ - System priority device. Remember they must match between peers.
+ type: str
+ pkl_src:
+ description:
+ - Source IP address used for peer keepalive link
+ type: str
+ pkl_dest:
+ description:
+ - Destination (remote) IP address used for peer keepalive link
+ - pkl_dest is required whenever pkl options are used.
+ type: str
+ pkl_vrf:
+ description:
+ - VRF used for peer keepalive link
+ - The VRF must exist on the device before using pkl_vrf.
+ - "(Note) 'default' is an overloaded term: Default vrf context for pkl_vrf is
+ 'management'; 'pkl_vrf: default' refers to the literal 'default' rib."
+ type: str
+ peer_gw:
+ description:
+ - Enables/Disables peer gateway
+ type: bool
+ auto_recovery:
+ description:
+ - Enables/Disables auto recovery on platforms that support disable
+ - timers are not modifiable with this attribute
+ - mutually exclusive with auto_recovery_reload_delay
+ type: bool
+ auto_recovery_reload_delay:
+ description:
+ - Manages auto-recovery reload-delay timer in seconds
+ - mutually exclusive with auto_recovery
+ type: str
+ delay_restore:
+ description:
+ - manages delay restore command and config value in seconds
+ type: str
+ delay_restore_interface_vlan:
+ description:
+ - manages delay restore interface-vlan command and config value in seconds
+ - not supported on all platforms
+ type: str
+ delay_restore_orphan_port:
+ description:
+ - manages delay restore orphan-port command and config value in seconds
+ - not supported on all platforms
+ type: str
+ state:
+ description:
+ - Manages desired state of the resource
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: configure a simple asn
+ cisco.nxos.nxos_vpc:
+ domain: 100
+ role_priority: 1000
+ system_priority: 2000
+ pkl_dest: 192.168.100.4
+ pkl_src: 10.1.100.20
+ peer_gw: true
+ auto_recovery: true
+
+- name: configure
+ cisco.nxos.nxos_vpc:
+ domain: 100
+ role_priority: 32667
+ system_priority: 2000
+ peer_gw: true
+ pkl_src: 10.1.100.2
+ pkl_dest: 192.168.100.4
+ auto_recovery: true
+
+- name: Configure VPC with delay restore and existing keepalive VRF
+ cisco.nxos.nxos_vpc:
+ domain: 10
+ role_priority: 28672
+ system_priority: 2000
+ delay_restore: 180
+ peer_gw: true
+ pkl_src: 1.1.1.2
+ pkl_dest: 1.1.1.1
+ pkl_vrf: vpckeepalive
+ auto_recovery: true
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["vpc domain 100",
+ "peer-keepalive destination 192.168.100.4 source 10.1.100.20 vrf management",
+ "auto-recovery", "peer-gateway"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+ run_commands,
+)
+
+
+CONFIG_ARGS = {
+ "role_priority": "role priority {role_priority}",
+ "system_priority": "system-priority {system_priority}",
+ "delay_restore": "delay restore {delay_restore}",
+ "delay_restore_interface_vlan": "delay restore interface-vlan {delay_restore_interface_vlan}",
+ "delay_restore_orphan_port": "delay restore orphan-port {delay_restore_orphan_port}",
+ "peer_gw": "{peer_gw} peer-gateway",
+ "auto_recovery": "{auto_recovery} auto-recovery",
+ "auto_recovery_reload_delay": "auto-recovery reload-delay {auto_recovery_reload_delay}",
+}
+
+PARAM_TO_DEFAULT_KEYMAP = {
+ "delay_restore": "60",
+ "delay_restore_interface_vlan": "10",
+ "delay_restore_orphan_port": "0",
+ "role_priority": "32667",
+ "system_priority": "32667",
+ "peer_gw": False,
+ "auto_recovery_reload_delay": 240,
+}
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_vrf_list(module):
+ try:
+ body = run_commands(module, ["show vrf all | json"])[0]
+ vrf_table = body["TABLE_vrf"]["ROW_vrf"]
+ except (KeyError, AttributeError):
+ return []
+
+ vrf_list = []
+ if vrf_table:
+ for each in vrf_table:
+ vrf_list.append(str(each["vrf_name"].lower()))
+
+ return vrf_list
+
+
+def get_auto_recovery_default(module):
+ auto = False
+ data = run_commands(module, ["show inventory | json"])[0]
+ pid = data["TABLE_inv"]["ROW_inv"][0]["productid"]
+ if re.search(r"N7K", pid):
+ auto = True
+ elif re.search(r"N9K", pid):
+ data = run_commands(module, ["show hardware | json"])[0]
+ ver = data["kickstart_ver_str"]
+ if re.search(r"7.0\(3\)F", ver):
+ auto = True
+
+ return auto
+
+
+def get_vpc(module):
+ body = run_commands(module, ["show vpc | json"])[0]
+ if body:
+ domain = str(body["vpc-domain-id"])
+ else:
+ body = run_commands(module, ["show run vpc | inc domain"])[0]
+ if body:
+ domain = body.split()[2]
+ else:
+ domain = "not configured"
+
+ vpc = {}
+ if domain != "not configured":
+ run = get_config(module, flags=["vpc all"])
+ if run:
+ vpc["domain"] = domain
+ for key in PARAM_TO_DEFAULT_KEYMAP.keys():
+ vpc[key] = PARAM_TO_DEFAULT_KEYMAP.get(key)
+ vpc["auto_recovery"] = get_auto_recovery_default(module)
+ vpc_list = run.split("\n")
+ for each in vpc_list:
+ if "role priority" in each:
+ line = each.split()
+ vpc["role_priority"] = line[-1]
+ if "system-priority" in each:
+ line = each.split()
+ vpc["system_priority"] = line[-1]
+ if re.search(r"delay restore \d+", each):
+ line = each.split()
+ vpc["delay_restore"] = line[-1]
+ if "delay restore interface-vlan" in each:
+ line = each.split()
+ vpc["delay_restore_interface_vlan"] = line[-1]
+ if "delay restore orphan-port" in each:
+ line = each.split()
+ vpc["delay_restore_orphan_port"] = line[-1]
+ if "auto-recovery" in each:
+ vpc["auto_recovery"] = False if "no " in each else True
+ line = each.split()
+ vpc["auto_recovery_reload_delay"] = line[-1]
+ if "peer-gateway" in each:
+ vpc["peer_gw"] = False if "no " in each else True
+ if "peer-keepalive destination" in each:
+ # destination is reqd; src & vrf are optional
+ m = re.search(
+ r"destination (?P<pkl_dest>[\d.]+)"
+ r"(?:.* source (?P<pkl_src>[\d.]+))*"
+ r"(?:.* vrf (?P<pkl_vrf>\S+))*",
+ each,
+ )
+ if m:
+ for pkl in m.groupdict().keys():
+ if m.group(pkl):
+ vpc[pkl] = m.group(pkl)
+ return vpc
+
+
+def pkl_dependencies(module, delta, existing):
+ """peer-keepalive dependency checking.
+ 1. 'destination' is required with all pkl configs.
+ 2. If delta has optional pkl keywords present, then all optional pkl
+ keywords in existing must be added to delta, otherwise the device cli
+ will remove those values when the new config string is issued.
+ 3. The desired behavior for this set of properties is to merge changes;
+ therefore if an optional pkl property exists on the device but not
+ in the playbook, then that existing property should be retained.
+ Example:
+ CLI: peer-keepalive dest 10.1.1.1 source 10.1.1.2 vrf orange
+ Playbook: {pkl_dest: 10.1.1.1, pkl_vrf: blue}
+ Result: peer-keepalive dest 10.1.1.1 source 10.1.1.2 vrf blue
+ """
+ pkl_existing = [i for i in existing.keys() if i.startswith("pkl")]
+ for pkl in pkl_existing:
+ param = module.params.get(pkl)
+ if not delta.get(pkl):
+ if param and param == existing[pkl]:
+ # delta is missing this param because it's idempotent;
+ # however another pkl command has changed; therefore
+ # explicitly add it to delta so that the cli retains it.
+ delta[pkl] = existing[pkl]
+ elif param is None and existing[pkl]:
+ # retain existing pkl commands even if not in playbook
+ delta[pkl] = existing[pkl]
+
+
+def get_commands_to_config_vpc(module, vpc, domain, existing):
+ vpc = dict(vpc)
+
+ domain_only = vpc.get("domain")
+
+ commands = []
+ if "pkl_dest" in vpc:
+ pkl_command = "peer-keepalive destination {pkl_dest}".format(**vpc)
+ if "pkl_src" in vpc:
+ pkl_command += " source {pkl_src}".format(**vpc)
+ if "pkl_vrf" in vpc:
+ pkl_command += " vrf {pkl_vrf}".format(**vpc)
+ commands.append(pkl_command)
+
+ if "auto_recovery" in vpc:
+ if not vpc.get("auto_recovery"):
+ vpc["auto_recovery"] = "no"
+ else:
+ vpc["auto_recovery"] = ""
+
+ if "peer_gw" in vpc:
+ if not vpc.get("peer_gw"):
+ vpc["peer_gw"] = "no"
+ else:
+ vpc["peer_gw"] = ""
+
+ for param in vpc:
+ command = CONFIG_ARGS.get(param)
+ if command is not None:
+ command = command.format(**vpc).strip()
+ if "peer-gateway" in command:
+ commands.append("terminal dont-ask")
+ commands.append(command)
+
+ if commands or domain_only:
+ commands.insert(0, "vpc domain {0}".format(domain))
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ domain=dict(required=True, type="str"),
+ role_priority=dict(required=False, type="str"),
+ system_priority=dict(required=False, type="str"),
+ pkl_src=dict(required=False),
+ pkl_dest=dict(required=False),
+ pkl_vrf=dict(required=False),
+ peer_gw=dict(required=False, type="bool"),
+ auto_recovery=dict(required=False, type="bool"),
+ auto_recovery_reload_delay=dict(required=False, type="str"),
+ delay_restore=dict(required=False, type="str"),
+ delay_restore_interface_vlan=dict(required=False, type="str"),
+ delay_restore_orphan_port=dict(required=False, type="str"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ mutually_exclusive = [("auto_recovery", "auto_recovery_reload_delay")]
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ results = {"changed": False, "warnings": warnings}
+
+ domain = module.params["domain"]
+ role_priority = module.params["role_priority"]
+ system_priority = module.params["system_priority"]
+ pkl_src = module.params["pkl_src"]
+ pkl_dest = module.params["pkl_dest"]
+ pkl_vrf = module.params["pkl_vrf"]
+ peer_gw = module.params["peer_gw"]
+ auto_recovery = module.params["auto_recovery"]
+ auto_recovery_reload_delay = module.params["auto_recovery_reload_delay"]
+ delay_restore = module.params["delay_restore"]
+ delay_restore_interface_vlan = module.params["delay_restore_interface_vlan"]
+ delay_restore_orphan_port = module.params["delay_restore_orphan_port"]
+ state = module.params["state"]
+
+ args = dict(
+ domain=domain,
+ role_priority=role_priority,
+ system_priority=system_priority,
+ pkl_src=pkl_src,
+ pkl_dest=pkl_dest,
+ pkl_vrf=pkl_vrf,
+ peer_gw=peer_gw,
+ auto_recovery=auto_recovery,
+ auto_recovery_reload_delay=auto_recovery_reload_delay,
+ delay_restore=delay_restore,
+ delay_restore_interface_vlan=delay_restore_interface_vlan,
+ delay_restore_orphan_port=delay_restore_orphan_port,
+ )
+
+ if not pkl_dest:
+ if pkl_src:
+ module.fail_json(msg="dest IP for peer-keepalive is required" " when src IP is present")
+ elif pkl_vrf:
+ if pkl_vrf != "management":
+ module.fail_json(
+ msg="dest and src IP for peer-keepalive are required" " when vrf is present",
+ )
+ else:
+ module.fail_json(
+ msg="dest IP for peer-keepalive is required" " when vrf is present",
+ )
+ if pkl_vrf:
+ if pkl_vrf.lower() not in get_vrf_list(module):
+ module.fail_json(
+ msg="The VRF you are trying to use for the peer "
+ "keepalive link is not on device yet. Add it"
+ " first, please.",
+ )
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ existing = get_vpc(module)
+
+ commands = []
+ if state == "present":
+ delta = {}
+ for key, value in proposed.items():
+ if str(value).lower() == "default" and key != "pkl_vrf":
+ # 'default' is a reserved word for vrf
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key)
+ if existing.get(key) != value:
+ delta[key] = value
+
+ if delta:
+ pkl_dependencies(module, delta, existing)
+ command = get_commands_to_config_vpc(module, delta, domain, existing)
+ commands.append(command)
+ elif state == "absent":
+ if existing:
+ if domain != existing["domain"]:
+ module.fail_json(
+ msg="You are trying to remove a domain that " "does not exist on the device",
+ )
+ else:
+ commands.append("terminal dont-ask")
+ commands.append("no vpc domain {0}".format(domain))
+
+ cmds = flatten_list(commands)
+ results["commands"] = cmds
+
+ if cmds:
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, cmds)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc_interface.py
new file mode 100644
index 00000000..5e318457
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vpc_interface.py
@@ -0,0 +1,342 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vpc_interface
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages interface VPC configuration
+description:
+- Manages interface VPC configuration
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Either vpc or peer_link param is required, but not both.
+- C(state=absent) removes whatever VPC config is on a port-channel if one exists.
+- Re-assigning a vpc or peerlink from one portchannel to another is not supported. The
+ module will force the user to unconfigure an existing vpc/pl before configuring
+ the same value on a new portchannel
+options:
+ portchannel:
+ description:
+ - Group number of the portchannel that will be configured.
+ required: true
+ type: str
+ vpc:
+ description:
+ - VPC group/id that will be configured on associated portchannel.
+ type: str
+ peer_link:
+ description:
+ - Set to true/false for peer link config on associated portchannel.
+ type: bool
+ state:
+ description:
+ - Manages desired state of the resource.
+ choices:
+ - present
+ - absent
+ default: present
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_vpc_interface:
+ portchannel: 10
+ vpc: 100
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface port-channel100", "vpc 10"]
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+ run_commands,
+)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_portchannel_list(module):
+ portchannels = []
+ pc_list = []
+
+ try:
+ body = run_commands(module, ["show port-channel summary | json"])[0]
+ pc_list = body["TABLE_channel"]["ROW_channel"]
+ except (KeyError, AttributeError, TypeError):
+ return portchannels
+
+ if pc_list:
+ if isinstance(pc_list, dict):
+ pc_list = [pc_list]
+
+ for pc in pc_list:
+ portchannels.append(pc["group"])
+
+ return portchannels
+
+
+def get_existing_portchannel_to_vpc_mappings(module):
+ pc_vpc_mapping = {}
+
+ try:
+ body = run_commands(module, ["show vpc brief | json"])[0]
+ vpc_table = body["TABLE_vpc"]["ROW_vpc"]
+ except (KeyError, AttributeError, TypeError):
+ vpc_table = None
+
+ if vpc_table:
+ if isinstance(vpc_table, dict):
+ vpc_table = [vpc_table]
+
+ for vpc in vpc_table:
+ pc_vpc_mapping[str(vpc["vpc-id"])] = str(vpc["vpc-ifindex"])
+
+ return pc_vpc_mapping
+
+
+def peer_link_exists(module):
+ found = False
+ run = get_config(module, flags=["vpc"])
+
+ vpc_list = run.split("\n")
+ for each in vpc_list:
+ if "peer-link" in each:
+ found = True
+ return found
+
+
+def get_active_vpc_peer_link(module):
+ peer_link = None
+
+ try:
+ body = run_commands(module, ["show vpc brief | json"])[0]
+ peer_link = body["TABLE_peerlink"]["ROW_peerlink"]["peerlink-ifindex"]
+ except (KeyError, AttributeError, TypeError):
+ return peer_link
+
+ return peer_link
+
+
+def get_portchannel_vpc_config(module, portchannel):
+ peer_link_pc = None
+ peer_link = False
+ vpc = ""
+ pc = ""
+ config = {}
+
+ try:
+ body = run_commands(module, ["show vpc brief | json"])[0]
+ table = body["TABLE_peerlink"]["ROW_peerlink"]
+ except (KeyError, AttributeError, TypeError):
+ table = {}
+
+ if table:
+ peer_link_pc = table.get("peerlink-ifindex", None)
+
+ if peer_link_pc:
+ plpc = str(peer_link_pc[2:])
+ if portchannel == plpc:
+ config["portchannel"] = portchannel
+ config["peer-link"] = True
+ config["vpc"] = vpc
+
+ mapping = get_existing_portchannel_to_vpc_mappings(module)
+
+ for existing_vpc, port_channel in mapping.items():
+ port_ch = str(port_channel[2:])
+ if port_ch == portchannel:
+ pc = port_ch
+ vpc = str(existing_vpc)
+
+ config["portchannel"] = pc
+ config["peer-link"] = peer_link
+ config["vpc"] = vpc
+
+ return config
+
+
+def get_commands_to_config_vpc_interface(portchannel, delta, config_value, existing):
+ commands = []
+
+ if not delta.get("peer-link") and existing.get("peer-link"):
+ commands.append("no vpc peer-link")
+ commands.insert(0, "interface port-channel{0}".format(portchannel))
+
+ elif delta.get("peer-link") and not existing.get("peer-link"):
+ commands.append("vpc peer-link")
+ commands.insert(0, "interface port-channel{0}".format(portchannel))
+
+ elif delta.get("vpc") and not existing.get("vpc"):
+ command = "vpc {0}".format(config_value)
+ commands.append(command)
+ commands.insert(0, "interface port-channel{0}".format(portchannel))
+
+ return commands
+
+
+def state_present(portchannel, delta, config_value, existing):
+ commands = []
+
+ command = get_commands_to_config_vpc_interface(portchannel, delta, config_value, existing)
+ commands.append(command)
+
+ return commands
+
+
+def state_absent(portchannel, existing):
+ commands = []
+ if existing.get("vpc"):
+ command = "no vpc"
+ commands.append(command)
+ elif existing.get("peer-link"):
+ command = "no vpc peer-link"
+ commands.append(command)
+ if commands:
+ commands.insert(0, "interface port-channel{0}".format(portchannel))
+
+ return commands
+
+
+def main():
+ argument_spec = dict(
+ portchannel=dict(required=True, type="str"),
+ vpc=dict(required=False, type="str"),
+ peer_link=dict(required=False, type="bool"),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=[["vpc", "peer_link"]],
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ commands = []
+ results = {"changed": False, "warnings": warnings}
+
+ portchannel = module.params["portchannel"]
+ vpc = module.params["vpc"]
+ peer_link = module.params["peer_link"]
+ state = module.params["state"]
+
+ args = {"portchannel": portchannel, "vpc": vpc, "peer-link": peer_link}
+ active_peer_link = None
+
+ if portchannel not in get_portchannel_list(module):
+ if not portchannel.isdigit() or int(portchannel) not in get_portchannel_list(module):
+ module.fail_json(
+ msg="The portchannel you are trying to make a"
+ " VPC or PL is not created yet. "
+ "Create it first!",
+ )
+ if vpc:
+ mapping = get_existing_portchannel_to_vpc_mappings(module)
+
+ if vpc in mapping and portchannel != mapping[vpc].strip("Po"):
+ module.fail_json(
+ msg="This vpc is already configured on "
+ "another portchannel. Remove it first "
+ "before trying to assign it here. ",
+ existing_portchannel=mapping[vpc],
+ )
+
+ for vpcid, existing_pc in mapping.items():
+ if portchannel == existing_pc.strip("Po") and vpcid != vpc:
+ module.fail_json(
+ msg="This portchannel already has another"
+ " VPC configured. Remove it first "
+ "before assigning this one",
+ existing_vpc=vpcid,
+ )
+
+ if peer_link_exists(module):
+ active_peer_link = get_active_vpc_peer_link(module)
+ if active_peer_link[-2:] == portchannel:
+ module.fail_json(
+ msg="That port channel is the current "
+ "PEER LINK. Remove it if you want it"
+ " to be a VPC",
+ )
+ config_value = vpc
+
+ elif peer_link is not None:
+ if peer_link_exists(module):
+ active_peer_link = get_active_vpc_peer_link(module)[2::]
+ if active_peer_link != portchannel:
+ if peer_link:
+ module.fail_json(
+ msg="A peer link already exists on" " the device. Remove it first",
+ current_peer_link="Po{0}".format(active_peer_link),
+ )
+ config_value = "peer-link"
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ existing = get_portchannel_vpc_config(module, portchannel)
+
+ if state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ commands = state_present(portchannel, delta, config_value, existing)
+
+ elif state == "absent" and existing:
+ commands = state_absent(portchannel, existing)
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ load_config(module, cmds)
+ results["changed"] = True
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results["commands"] = cmds
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf.py
new file mode 100644
index 00000000..66fa43ab
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf.py
@@ -0,0 +1,616 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vrf
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages global VRF configuration.
+description:
+- This module provides declarative management of VRFs on CISCO NXOS network devices.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+- Trishna Guha (@trishnaguha)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Cisco NX-OS creates the default VRF by itself. Therefore, you're not allowed to
+ use default as I(vrf) name in this module.
+- C(vrf) name must be shorter than 32 chars.
+- VRF names are not case sensible in NX-OS. Anyway, the name is stored just like it's
+ inserted by the user and it'll not be changed again unless the VRF is removed and
+ re-created. i.e. C(vrf=NTC) will create a VRF named NTC, but running it again with
+ C(vrf=ntc) will not cause a configuration change.
+options:
+ name:
+ description:
+ - Name of VRF to be managed.
+ aliases:
+ - vrf
+ type: str
+ admin_state:
+ description:
+ - Administrative state of the VRF.
+ default: up
+ choices:
+ - up
+ - down
+ type: str
+ vni:
+ description:
+ - Specify virtual network identifier. Valid values are Integer or keyword 'default'.
+ type: str
+ rd:
+ description:
+ - VPN Route Distinguisher (RD). Valid values are a string in one of the route-distinguisher
+ formats (ASN2:NN, ASN4:NN, or IPV4:NN); the keyword 'auto', or the keyword 'default'.
+ type: str
+ interfaces:
+ description:
+ - List of interfaces to check the VRF has been configured correctly or keyword
+ 'default'.
+ type: list
+ elements: str
+ associated_interfaces:
+ description:
+ - This is a intent option and checks the operational state of the for given vrf
+ C(name) for associated interfaces. If the value in the C(associated_interfaces)
+ does not match with the operational state of vrf interfaces on device it will
+ result in failure.
+ type: list
+ elements: str
+ aggregate:
+ description: List of VRFs definitions.
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of VRF to be managed.
+ aliases:
+ - vrf
+ type: str
+ admin_state:
+ description:
+ - Administrative state of the VRF.
+ choices:
+ - up
+ - down
+ type: str
+ vni:
+ description:
+ - Specify virtual network identifier. Valid values are Integer or keyword 'default'.
+ type: str
+ rd:
+ description:
+ - VPN Route Distinguisher (RD). Valid values are a string in one of the route-distinguisher
+ formats (ASN2:NN, ASN4:NN, or IPV4:NN); the keyword 'auto', or the keyword 'default'.
+ type: str
+ interfaces:
+ description:
+ - List of interfaces to check the VRF has been configured correctly or keyword
+ 'default'.
+ type: list
+ elements: str
+ associated_interfaces:
+ description:
+ - This is a intent option and checks the operational state of the for given vrf
+ C(name) for associated interfaces. If the value in the C(associated_interfaces)
+ does not match with the operational state of vrf interfaces on device it will
+ result in failure.
+ type: list
+ elements: str
+ state:
+ description:
+ - Manages desired state of the resource.
+ choices:
+ - present
+ - absent
+ type: str
+ description:
+ description:
+ - Description of the VRF or keyword 'default'.
+ type: str
+ delay:
+ description:
+ - Time in seconds to wait before checking for the operational state on remote
+ device. This wait is applicable for operational state arguments.
+ type: int
+ purge:
+ description:
+ - Purge VRFs not defined in the I(aggregate) parameter.
+ type: bool
+ default: no
+ state:
+ description:
+ - Manages desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ description:
+ description:
+ - Description of the VRF or keyword 'default'.
+ type: str
+ delay:
+ description:
+ - Time in seconds to wait before checking for the operational state on remote
+ device. This wait is applicable for operational state arguments.
+ default: 10
+ type: int
+"""
+
+EXAMPLES = """
+- name: Ensure ntc VRF exists on switch
+ cisco.nxos.nxos_vrf:
+ name: ntc
+ description: testing
+ state: present
+
+- name: Aggregate definition of VRFs
+ cisco.nxos.nxos_vrf:
+ aggregate:
+ - {name: test1, description: Testing, admin_state: down}
+ - {name: test2, interfaces: Ethernet1/2}
+
+- name: Aggregate definitions of VRFs with Purge
+ cisco.nxos.nxos_vrf:
+ aggregate:
+ - {name: ntc1, description: purge test1}
+ - {name: ntc2, description: purge test2}
+ state: present
+ purge: yes
+
+- name: Delete VRFs exist on switch
+ cisco.nxos.nxos_vrf:
+ aggregate:
+ - {name: ntc1}
+ - {name: ntc2}
+ state: absent
+
+- name: Assign interfaces to VRF declaratively
+ cisco.nxos.nxos_vrf:
+ name: test1
+ interfaces:
+ - Ethernet2/3
+ - Ethernet2/5
+
+- name: Check interfaces assigned to VRF
+ cisco.nxos.nxos_vrf:
+ name: test1
+ associated_interfaces:
+ - Ethernet2/3
+ - Ethernet2/5
+
+- name: Ensure VRF is tagged with interface Ethernet2/5 only (Removes from Ethernet2/3)
+ cisco.nxos.nxos_vrf:
+ name: test1
+ interfaces:
+ - Ethernet2/5
+
+- name: Delete VRF
+ cisco.nxos.nxos_vrf:
+ name: ntc
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - vrf context ntc
+ - no shutdown
+ - interface Ethernet1/2
+ - no switchport
+ - vrf member test2
+"""
+
+import re
+import time
+
+from copy import deepcopy
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import (
+ remove_default_spec,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_interface_type,
+ load_config,
+ run_commands,
+)
+
+
+def search_obj_in_list(name, lst):
+ for o in lst:
+ if o["name"] == name:
+ return o
+
+
+def execute_show_command(command, module):
+ if "show run" not in command:
+ output = "json"
+ else:
+ output = "text"
+ cmds = [{"command": command, "output": output}]
+ body = run_commands(module, cmds)
+ return body
+
+
+def get_existing_vrfs(module):
+ objs = list()
+ command = "show vrf all"
+ try:
+ body = execute_show_command(command, module)[0]
+ except IndexError:
+ return list()
+ try:
+ vrf_table = body["TABLE_vrf"]["ROW_vrf"]
+ except (TypeError, IndexError, KeyError):
+ return list()
+
+ if isinstance(vrf_table, list):
+ for vrf in vrf_table:
+ obj = {}
+ obj["name"] = vrf["vrf_name"]
+ objs.append(obj)
+
+ elif isinstance(vrf_table, dict):
+ obj = {}
+ obj["name"] = vrf_table["vrf_name"]
+ objs.append(obj)
+
+ return objs
+
+
+def map_obj_to_commands(updates, module):
+ commands = list()
+ want, have = updates
+ state = module.params["state"]
+ purge = module.params["purge"]
+
+ args = ("rd", "description", "vni")
+
+ for w in want:
+ name = w["name"]
+ admin_state = w["admin_state"]
+ vni = w["vni"]
+ interfaces = w.get("interfaces") or []
+ if purge:
+ state = "absent"
+ else:
+ state = w["state"]
+ del w["state"]
+
+ obj_in_have = search_obj_in_list(name, have)
+ if state == "absent" and obj_in_have:
+ commands.append("no vrf context {0}".format(name))
+
+ elif state == "present":
+ if not obj_in_have:
+ commands.append("vrf context {0}".format(name))
+ for item in args:
+ candidate = w.get(item)
+ if candidate and candidate != "default":
+ cmd = item + " " + str(candidate)
+ commands.append(cmd)
+ if admin_state == "up":
+ commands.append("no shutdown")
+ elif admin_state == "down":
+ commands.append("shutdown")
+ commands.append("exit")
+
+ if interfaces and interfaces[0] != "default":
+ for i in interfaces:
+ commands.append("interface {0}".format(i))
+ if get_interface_type(i) in (
+ "ethernet",
+ "portchannel",
+ ):
+ commands.append("no switchport")
+ commands.append("vrf member {0}".format(name))
+
+ else:
+ # If vni is already configured on vrf, unconfigure it first.
+ if vni:
+ if obj_in_have.get("vni") and vni != obj_in_have.get("vni"):
+ commands.append("no vni {0}".format(obj_in_have.get("vni")))
+
+ for item in args:
+ candidate = w.get(item)
+ if candidate == "default":
+ if obj_in_have.get(item):
+ cmd = "no " + item + " " + obj_in_have.get(item)
+ commands.append(cmd)
+ elif candidate and candidate != obj_in_have.get(item):
+ cmd = item + " " + str(candidate)
+ commands.append(cmd)
+ if admin_state and admin_state != obj_in_have.get("admin_state"):
+ if admin_state == "up":
+ commands.append("no shutdown")
+ elif admin_state == "down":
+ commands.append("shutdown")
+
+ if commands:
+ commands.insert(0, "vrf context {0}".format(name))
+ commands.append("exit")
+
+ if interfaces and interfaces[0] != "default":
+ if not obj_in_have["interfaces"]:
+ for i in interfaces:
+ commands.append("vrf context {0}".format(name))
+ commands.append("exit")
+ commands.append("interface {0}".format(i))
+ if get_interface_type(i) in (
+ "ethernet",
+ "portchannel",
+ ):
+ commands.append("no switchport")
+ commands.append("vrf member {0}".format(name))
+
+ elif set(interfaces) != set(obj_in_have["interfaces"]):
+ missing_interfaces = list(set(interfaces) - set(obj_in_have["interfaces"]))
+ for i in missing_interfaces:
+ commands.append("vrf context {0}".format(name))
+ commands.append("exit")
+ commands.append("interface {0}".format(i))
+ if get_interface_type(i) in (
+ "ethernet",
+ "portchannel",
+ ):
+ commands.append("no switchport")
+ commands.append("vrf member {0}".format(name))
+
+ superfluous_interfaces = list(
+ set(obj_in_have["interfaces"]) - set(interfaces),
+ )
+ for i in superfluous_interfaces:
+ commands.append("vrf context {0}".format(name))
+ commands.append("exit")
+ commands.append("interface {0}".format(i))
+ if get_interface_type(i) in (
+ "ethernet",
+ "portchannel",
+ ):
+ commands.append("no switchport")
+ commands.append("no vrf member {0}".format(name))
+ elif interfaces and interfaces[0] == "default":
+ if obj_in_have["interfaces"]:
+ for i in obj_in_have["interfaces"]:
+ commands.append("vrf context {0}".format(name))
+ commands.append("exit")
+ commands.append("interface {0}".format(i))
+ if get_interface_type(i) in (
+ "ethernet",
+ "portchannel",
+ ):
+ commands.append("no switchport")
+ commands.append("no vrf member {0}".format(name))
+
+ if purge:
+ existing = get_existing_vrfs(module)
+ if existing:
+ for h in existing:
+ if h["name"] in ("default", "management"):
+ pass
+ else:
+ obj_in_want = search_obj_in_list(h["name"], want)
+ if not obj_in_want:
+ commands.append("no vrf context {0}".format(h["name"]))
+
+ return commands
+
+
+def validate_vrf(name, module):
+ if name:
+ name = name.strip()
+ if name == "default":
+ module.fail_json(msg="cannot use default as name of a VRF")
+ elif len(name) > 32:
+ module.fail_json(msg="VRF name exceeded max length of 32", name=name)
+ else:
+ return name
+
+
+def map_params_to_obj(module):
+ obj = []
+ aggregate = module.params.get("aggregate")
+ if aggregate:
+ for item in aggregate:
+ for key in item:
+ if item.get(key) is None:
+ item[key] = module.params[key]
+
+ d = item.copy()
+ d["name"] = validate_vrf(d["name"], module)
+ obj.append(d)
+ else:
+ obj.append(
+ {
+ "name": validate_vrf(module.params["name"], module),
+ "description": module.params["description"],
+ "vni": module.params["vni"],
+ "rd": module.params["rd"],
+ "admin_state": module.params["admin_state"],
+ "state": module.params["state"],
+ "interfaces": module.params["interfaces"],
+ "associated_interfaces": module.params["associated_interfaces"],
+ },
+ )
+ return obj
+
+
+def get_value(arg, config, module):
+ extra_arg_regex = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(arg), re.M)
+ value = ""
+ if arg in config:
+ value = extra_arg_regex.search(config).group("value")
+ return value
+
+
+def map_config_to_obj(want, element_spec, module):
+ objs = list()
+
+ for w in want:
+ obj = deepcopy(element_spec)
+ del obj["delay"]
+ del obj["state"]
+
+ command = "show vrf {0}".format(w["name"])
+ try:
+ body = execute_show_command(command, module)[0]
+ vrf_table = body["TABLE_vrf"]["ROW_vrf"]
+ except (TypeError, IndexError):
+ return list()
+
+ name = vrf_table["vrf_name"]
+ obj["name"] = name
+ obj["admin_state"] = vrf_table["vrf_state"].lower()
+
+ command = "show run all | section vrf.context.{0}".format(name)
+ body = execute_show_command(command, module)[0]
+ extra_params = ["vni", "rd", "description"]
+ for param in extra_params:
+ obj[param] = get_value(param, body, module)
+
+ obj["interfaces"] = []
+ command = "show vrf {0} interface".format(name)
+ try:
+ body = execute_show_command(command, module)[0]
+ vrf_int = body["TABLE_if"]["ROW_if"]
+ except (TypeError, IndexError):
+ vrf_int = None
+
+ if vrf_int:
+ if isinstance(vrf_int, list):
+ for i in vrf_int:
+ intf = i["if_name"]
+ obj["interfaces"].append(intf)
+ elif isinstance(vrf_int, dict):
+ intf = vrf_int["if_name"]
+ obj["interfaces"].append(intf)
+
+ objs.append(obj)
+ return objs
+
+
+def check_declarative_intent_params(want, module, element_spec, result):
+ have = None
+ is_delay = False
+
+ for w in want:
+ if w.get("associated_interfaces") is None:
+ continue
+
+ if result["changed"] and not is_delay:
+ time.sleep(module.params["delay"])
+ is_delay = True
+
+ if have is None:
+ have = map_config_to_obj(want, element_spec, module)
+
+ for i in w["associated_interfaces"]:
+ obj_in_have = search_obj_in_list(w["name"], have)
+
+ if obj_in_have:
+ interfaces = obj_in_have.get("interfaces")
+ if interfaces is not None and i not in interfaces:
+ module.fail_json(msg="Interface %s not configured on vrf %s" % (i, w["name"]))
+
+
+def vrf_error_check(module, commands, responses):
+ """Checks for VRF config errors and executes a retry in some circumstances."""
+ pattern = "ERROR: Deletion of VRF .* in progress"
+ if re.search(pattern, str(responses)):
+ # Allow delay/retry for VRF changes
+ time.sleep(15)
+ responses = load_config(module, commands, opts={"catch_clierror": True})
+ if re.search(pattern, str(responses)):
+ module.fail_json(msg="VRF config (and retry) failure: %s " % responses)
+ module.warn("VRF config delayed by VRF deletion - passed on retry")
+
+
+def main():
+ """main entry point for module execution"""
+ element_spec = dict(
+ name=dict(type="str", aliases=["vrf"]),
+ description=dict(type="str"),
+ vni=dict(type="str"),
+ rd=dict(type="str"),
+ admin_state=dict(type="str", default="up", choices=["up", "down"]),
+ interfaces=dict(type="list", elements="str"),
+ associated_interfaces=dict(type="list", elements="str"),
+ delay=dict(type="int", default=10),
+ state=dict(type="str", default="present", choices=["present", "absent"]),
+ )
+
+ aggregate_spec = deepcopy(element_spec)
+
+ # remove default in aggregate spec, to handle common arguments
+ remove_default_spec(aggregate_spec)
+
+ argument_spec = dict(
+ aggregate=dict(type="list", elements="dict", options=aggregate_spec),
+ purge=dict(type="bool", default=False),
+ )
+
+ argument_spec.update(element_spec)
+
+ required_one_of = [["name", "aggregate"]]
+ mutually_exclusive = [["name", "aggregate"]]
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_one_of=required_one_of,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = {"changed": False}
+ if warnings:
+ result["warnings"] = warnings
+
+ want = map_params_to_obj(module)
+ have = map_config_to_obj(want, element_spec, module)
+
+ commands = map_obj_to_commands((want, have), module)
+ result["commands"] = commands
+
+ if commands and not module.check_mode:
+ responses = load_config(module, commands, opts={"catch_clierror": True})
+ vrf_error_check(module, commands, responses)
+ result["changed"] = True
+
+ check_declarative_intent_params(want, module, element_spec, result)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_af.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_af.py
new file mode 100644
index 00000000..bf155ce8
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_af.py
@@ -0,0 +1,274 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vrf_af
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VRF AF.
+description:
+- Manages VRF AF
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- Default, where supported, restores params default value.
+- In case of C(state=absent) the address-family configuration will be absent. Therefore
+ the options C(route_target_both_auto_evpn) and C(route_targets) are ignored.
+options:
+ vrf:
+ description:
+ - Name of the VRF.
+ required: true
+ type: str
+ afi:
+ description:
+ - Address-Family Identifier (AFI).
+ required: true
+ choices:
+ - ipv4
+ - ipv6
+ type: str
+ route_target_both_auto_evpn:
+ description:
+ - Enable/Disable the EVPN route-target 'auto' setting for both import and export
+ target communities.
+ type: bool
+ route_targets:
+ description:
+ - Specify the route-targets which should be imported and/or exported under the
+ AF. This argument accepts a list of dicts that specify the route-target, the
+ direction (import|export|both) and state of each route-target. Default direction
+ is C(direction=both). See examples.
+ suboptions:
+ rt:
+ description:
+ - Defines the route-target itself
+ required: true
+ type: str
+ direction:
+ description:
+ - Indicates the direction of the route-target (import|export|both)
+ choices:
+ - import
+ - export
+ - both
+ default: both
+ type: str
+ state:
+ description:
+ - Determines whether the route-target with the given direction should be present
+ or not on the device.
+ choices:
+ - present
+ - absent
+ default: present
+ type: str
+ elements: dict
+ type: list
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_target_both_auto_evpn: true
+ state: present
+
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_targets:
+ - rt: 65000:1000
+ direction: import
+ - rt: 65001:1000
+ direction: import
+
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_targets:
+ - rt: 65000:1000
+ direction: import
+ - rt: 65001:1000
+ state: absent
+
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_targets:
+ - rt: 65000:1000
+ direction: export
+ - rt: 65001:1000
+ direction: export
+
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_targets:
+ - rt: 65000:1000
+ direction: export
+ state: absent
+
+- cisco.nxos.nxos_vrf_af:
+ vrf: ntc
+ afi: ipv4
+ route_targets:
+ - rt: 65000:1000
+ direction: both
+ state: present
+ - rt: 65001:1000
+ direction: import
+ state: present
+ - rt: 65002:1000
+ direction: both
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["vrf context ntc", "address-family ipv4 unicast"]
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ NetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_config,
+ load_config,
+)
+
+
+def match_current_rt(rt, direction, current, rt_commands):
+ command = "route-target %s %s" % (direction, rt.get("rt"))
+ match = re.findall(command, current, re.M)
+ want = bool(rt.get("state") != "absent")
+ if not match and want:
+ rt_commands.append(command)
+ elif match and not want:
+ rt_commands.append("no %s" % command)
+ return rt_commands
+
+
+def main():
+ argument_spec = dict(
+ vrf=dict(required=True),
+ afi=dict(required=True, choices=["ipv4", "ipv6"]),
+ route_target_both_auto_evpn=dict(required=False, type="bool"),
+ state=dict(choices=["present", "absent"], default="present"),
+ route_targets=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ rt=dict(type="str", required=True),
+ direction=dict(choices=["import", "export", "both"], default="both"),
+ state=dict(choices=["present", "absent"], default="present"),
+ ),
+ ),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ result = {"changed": False, "warnings": warnings}
+
+ config_text = get_config(module)
+ config = NetworkConfig(indent=2, contents=config_text)
+
+ path = [
+ "vrf context %s" % module.params["vrf"],
+ "address-family %s unicast" % module.params["afi"],
+ ]
+
+ try:
+ current = config.get_block_config(path)
+ except ValueError:
+ current = None
+
+ commands = list()
+ if current and module.params["state"] == "absent":
+ commands.append("no address-family %s unicast" % module.params["afi"])
+
+ elif module.params["state"] == "present":
+ rt_commands = list()
+
+ if not current:
+ commands.append("address-family %s unicast" % module.params["afi"])
+ current = ""
+
+ have_auto_evpn = "route-target both auto evpn" in current
+ if module.params["route_target_both_auto_evpn"] is not None:
+ want_auto_evpn = bool(module.params["route_target_both_auto_evpn"])
+ if want_auto_evpn and not have_auto_evpn:
+ commands.append("route-target both auto evpn")
+ elif have_auto_evpn and not want_auto_evpn:
+ commands.append("no route-target both auto evpn")
+
+ if module.params["route_targets"] is not None:
+ for rt in module.params["route_targets"]:
+ if rt.get("direction") == "both" or not rt.get("direction"):
+ platform = get_capabilities(module)["device_info"]["network_os_platform"]
+ if platform.startswith("N9K") and rt.get("rt") == "auto":
+ rt_commands = match_current_rt(rt, "both", current, rt_commands)
+ else:
+ rt_commands = match_current_rt(rt, "import", current, rt_commands)
+ rt_commands = match_current_rt(rt, "export", current, rt_commands)
+ else:
+ rt_commands = match_current_rt(rt, rt.get("direction"), current, rt_commands)
+
+ if rt_commands:
+ commands.extend(rt_commands)
+
+ if commands and current:
+ commands.insert(0, "address-family %s unicast" % module.params["afi"])
+
+ if commands:
+ commands.insert(0, "vrf context %s" % module.params["vrf"])
+ if not module.check_mode:
+ load_config(module, commands)
+ result["changed"] = True
+
+ result["commands"] = commands
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_interface.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_interface.py
new file mode 100644
index 00000000..9e9a0cef
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrf_interface.py
@@ -0,0 +1,267 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vrf_interface
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages interface specific VRF configuration.
+description:
+- Manages interface specific VRF configuration.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- VRF needs to be added globally with M(cisco.nxos.nxos_vrf) before adding a VRF to an interface.
+- Remove a VRF from an interface will still remove all L3 attributes just as it does
+ from CLI.
+- VRF is not read from an interface until IP address is configured on that interface.
+options:
+ vrf:
+ description:
+ - Name of VRF to be managed.
+ required: true
+ type: str
+ interface:
+ description:
+ - Full name of interface to be managed, i.e. Ethernet1/1.
+ required: true
+ type: str
+ state:
+ description:
+ - Manages desired state of the resource.
+ required: false
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: Ensure vrf ntc exists on Eth1/1
+ cisco.nxos.nxos_vrf_interface:
+ vrf: ntc
+ interface: Ethernet1/1
+ state: present
+
+- name: Ensure ntc VRF does not exist on Eth1/1
+ cisco.nxos.nxos_vrf_interface:
+ vrf: ntc
+ interface: Ethernet1/1
+ state: absent
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface loopback16", "vrf member ntc"]
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_interface_type,
+ load_config,
+ normalize_interface,
+ run_commands,
+)
+
+
+def execute_show_command(command, module):
+ if "show run" not in command:
+ output = "json"
+ else:
+ output = "text"
+ cmds = [{"command": command, "output": output}]
+ return run_commands(module, cmds)[0]
+
+
+def get_interface_mode(interface, intf_type, module):
+ command = "show interface {0}".format(interface)
+ interface = {}
+ mode = "unknown"
+
+ if intf_type in ["ethernet", "portchannel"]:
+ body = execute_show_command(command, module)
+ try:
+ interface_table = body["TABLE_interface"]["ROW_interface"]
+ except KeyError:
+ return mode
+
+ if interface_table and isinstance(interface_table, dict):
+ mode = str(interface_table.get("eth_mode", "layer3"))
+ if mode == "access" or mode == "trunk":
+ mode = "layer2"
+ else:
+ return mode
+
+ elif intf_type == "loopback" or intf_type == "svi":
+ mode = "layer3"
+ return mode
+
+
+def get_vrf_list(module):
+ command = "show vrf all"
+ vrf_list = []
+ body = execute_show_command(command, module)
+
+ try:
+ vrf_table = body["TABLE_vrf"]["ROW_vrf"]
+ except (KeyError, AttributeError):
+ return vrf_list
+
+ for each in vrf_table:
+ vrf_list.append(str(each["vrf_name"]))
+
+ return vrf_list
+
+
+def get_interface_info(interface, module):
+ if not interface.startswith("loopback"):
+ interface = interface.capitalize()
+
+ command = "show run interface {0}".format(interface)
+ vrf_regex = r".*vrf\s+member\s+(?P<vrf>\S+).*"
+
+ try:
+ body = execute_show_command(command, module)
+ match_vrf = re.match(vrf_regex, body, re.DOTALL)
+ group_vrf = match_vrf.groupdict()
+ vrf = group_vrf["vrf"]
+ except (AttributeError, TypeError):
+ return ""
+
+ return vrf
+
+
+def is_default(interface, module):
+ command = "show run interface {0}".format(interface)
+
+ try:
+ body = execute_show_command(command, module)
+ raw_list = body.split("\n")
+ if raw_list[-1].startswith("interface"):
+ return True
+ else:
+ return False
+
+ except (KeyError, IndexError):
+ return "DNE"
+
+
+def main():
+ argument_spec = dict(
+ vrf=dict(required=True),
+ interface=dict(type="str", required=True),
+ state=dict(default="present", choices=["present", "absent"], required=False),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ vrf = module.params["vrf"]
+ interface = normalize_interface(module.params["interface"])
+ state = module.params["state"]
+
+ device_info = get_capabilities(module)
+ network_api = device_info.get("network_api", "nxapi")
+
+ current_vrfs = get_vrf_list(module)
+ if vrf not in current_vrfs:
+ warnings.append("The VRF is not present/active on the device. Use nxos_vrf to fix this.")
+
+ intf_type = get_interface_type(interface)
+ if intf_type != "ethernet" and network_api == "cliconf":
+ if is_default(interface, module) == "DNE":
+ module.fail_json(
+ msg="interface does not exist on switch. Verify "
+ "switch platform or create it first with "
+ "nxos_interfaces if it's a logical interface",
+ )
+
+ mode = get_interface_mode(interface, intf_type, module)
+ if mode == "layer2":
+ module.fail_json(
+ msg="Ensure interface is a Layer 3 port before "
+ "configuring a VRF on an interface. You can "
+ "use nxos_interfaces",
+ )
+
+ current_vrf = get_interface_info(interface, module)
+ existing = dict(interface=interface, vrf=current_vrf)
+ changed = False
+
+ if not existing["vrf"]:
+ pass
+ elif vrf != existing["vrf"] and state == "absent":
+ module.fail_json(
+ msg="The VRF you are trying to remove "
+ "from the interface does not exist "
+ "on that interface.",
+ interface=interface,
+ proposed_vrf=vrf,
+ existing_vrf=existing["vrf"],
+ )
+
+ commands = []
+ if existing:
+ if state == "absent":
+ if existing and vrf == existing["vrf"]:
+ command = "no vrf member {0}".format(vrf)
+ commands.append(command)
+
+ elif state == "present":
+ if existing["vrf"] != vrf:
+ command = "vrf member {0}".format(vrf)
+ commands.append(command)
+
+ if commands:
+ commands.insert(0, "interface {0}".format(interface))
+
+ if commands:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=commands)
+ else:
+ load_config(module, commands)
+ changed = True
+ if "configure" in commands:
+ commands.pop(0)
+
+ results["commands"] = commands
+ results["changed"] = changed
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vrrp.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrrp.py
new file mode 100644
index 00000000..b5a026c4
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vrrp.py
@@ -0,0 +1,432 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vrrp
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VRRP configuration on NX-OS switches.
+description:
+- Manages VRRP configuration on NX-OS switches.
+version_added: 1.0.0
+author:
+- Jason Edelman (@jedelman8)
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- VRRP feature needs to be enabled first on the system.
+- SVIs must exist before using this module.
+- Interface must be a L3 port before using this module.
+- C(state=absent) removes the VRRP group if it exists on the device.
+- VRRP cannot be configured on loopback interfaces.
+options:
+ group:
+ description:
+ - VRRP group number.
+ required: true
+ type: str
+ interface:
+ description:
+ - Full name of interface that is being managed for VRRP.
+ required: true
+ type: str
+ interval:
+ description:
+ - Time interval between advertisement or 'default' keyword
+ required: false
+ type: str
+ priority:
+ description:
+ - VRRP priority or 'default' keyword
+ type: str
+ preempt:
+ description:
+ - Enable/Disable preempt.
+ type: bool
+ vip:
+ description:
+ - VRRP virtual IP address or 'default' keyword
+ type: str
+ authentication:
+ description:
+ - Clear text authentication string or 'default' keyword
+ type: str
+ admin_state:
+ description:
+ - Used to enable or disable the VRRP process.
+ choices:
+ - shutdown
+ - no shutdown
+ - default
+ default: shutdown
+ type: str
+ state:
+ description:
+ - Specify desired state of the resource.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+- name: Ensure vrrp group 100 and vip 10.1.100.1 is on vlan10
+ cisco.nxos.nxos_vrrp:
+ interface: vlan10
+ group: 100
+ vip: 10.1.100.1
+
+- name: Ensure removal of the vrrp group config
+ cisco.nxos.nxos_vrrp:
+ interface: vlan10
+ group: 100
+ vip: 10.1.100.1
+ state: absent
+
+- name: Re-config with more params
+ cisco.nxos.nxos_vrrp:
+ interface: vlan10
+ group: 100
+ vip: 10.1.100.1
+ preempt: false
+ priority: 130
+ authentication: AUTHKEY
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface vlan10", "vrrp 150", "address 10.1.15.1",
+ "authentication text testing", "no shutdown"]
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ get_interface_type,
+ load_config,
+ run_commands,
+)
+
+
+PARAM_TO_DEFAULT_KEYMAP = {
+ "priority": "100",
+ "interval": "1",
+ "vip": "0.0.0.0",
+ "admin_state": "shutdown",
+}
+
+
+def execute_show_command(command, module):
+ if "show run" not in command:
+ output = "json"
+ else:
+ output = "text"
+
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)[0]
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ if value:
+ new_dict[new_key] = str(value)
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def is_default(interface, module):
+ command = "show run interface {0}".format(interface)
+
+ try:
+ body = execute_show_command(command, module)
+ if "invalid" in body.lower():
+ return "DNE"
+ else:
+ raw_list = body.split("\n")
+ if raw_list[-1].startswith("interface"):
+ return True
+ else:
+ return False
+ except KeyError:
+ return "DNE"
+
+
+def get_interface_mode(interface, intf_type, module):
+ command = "show interface {0}".format(interface)
+ interface = {}
+ mode = "unknown"
+ body = execute_show_command(command, module)
+ interface_table = body["TABLE_interface"]["ROW_interface"]
+ name = interface_table.get("interface")
+
+ if intf_type in ["ethernet", "portchannel"]:
+ mode = str(interface_table.get("eth_mode", "layer3"))
+
+ if mode == "access" or mode == "trunk":
+ mode = "layer2"
+ elif intf_type == "svi":
+ mode = "layer3"
+
+ return mode, name
+
+
+def get_vrr_status(group, module, interface):
+ command = "show run all | section interface.{0}$".format(interface)
+ body = execute_show_command(command, module)
+ vrf_index = None
+ admin_state = "shutdown"
+
+ if body:
+ splitted_body = body.splitlines()
+ for index in range(0, len(splitted_body) - 1):
+ if splitted_body[index].strip() == "vrrp {0}".format(group):
+ vrf_index = index
+ vrf_section = splitted_body[vrf_index::]
+
+ for line in vrf_section:
+ if line.strip() == "no shutdown":
+ admin_state = "no shutdown"
+ break
+
+ return admin_state
+
+
+def get_existing_vrrp(interface, group, module, name):
+ command = "show vrrp detail interface {0}".format(interface)
+ body = execute_show_command(command, module)
+ vrrp = {}
+
+ vrrp_key = {
+ "sh_group_id": "group",
+ "sh_vip_addr": "vip",
+ "sh_priority": "priority",
+ "sh_group_preempt": "preempt",
+ "sh_auth_text": "authentication",
+ "sh_adv_interval": "interval",
+ }
+
+ try:
+ vrrp_table = body["TABLE_vrrp_group"]
+ except (AttributeError, IndexError, TypeError):
+ return {}
+
+ if isinstance(vrrp_table, dict):
+ vrrp_table = [vrrp_table]
+
+ for each_vrrp in vrrp_table:
+ vrrp_row = each_vrrp["ROW_vrrp_group"]
+ parsed_vrrp = apply_key_map(vrrp_key, vrrp_row)
+
+ if parsed_vrrp["preempt"] == "Disable":
+ parsed_vrrp["preempt"] = False
+ elif parsed_vrrp["preempt"] == "Enable":
+ parsed_vrrp["preempt"] = True
+
+ if parsed_vrrp["group"] == group:
+ parsed_vrrp["admin_state"] = get_vrr_status(group, module, name)
+ return parsed_vrrp
+
+ return vrrp
+
+
+def get_commands_config_vrrp(delta, existing, group):
+ commands = []
+
+ CMDS = {
+ "priority": "priority {0}",
+ "preempt": "preempt",
+ "vip": "address {0}",
+ "interval": "advertisement-interval {0}",
+ "auth": "authentication text {0}",
+ "admin_state": "{0}",
+ }
+
+ for arg in ["vip", "priority", "interval", "admin_state"]:
+ val = delta.get(arg)
+ if val == "default":
+ val = PARAM_TO_DEFAULT_KEYMAP.get(arg)
+ if val != existing.get(arg):
+ commands.append((CMDS.get(arg)).format(val))
+ elif val:
+ commands.append((CMDS.get(arg)).format(val))
+
+ preempt = delta.get("preempt")
+ auth = delta.get("authentication")
+
+ if preempt:
+ commands.append(CMDS.get("preempt"))
+ elif preempt is False:
+ commands.append("no " + CMDS.get("preempt"))
+ if auth:
+ if auth != "default":
+ commands.append((CMDS.get("auth")).format(auth))
+ elif existing.get("authentication"):
+ commands.append("no authentication")
+
+ if commands:
+ commands.insert(0, "vrrp {0}".format(group))
+
+ return commands
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def validate_params(param, module):
+ value = module.params[param]
+
+ if param == "group":
+ try:
+ if int(value) < 1 or int(value) > 255:
+ raise ValueError
+ except ValueError:
+ module.fail_json(
+ msg="Warning! 'group' must be an integer between" " 1 and 255",
+ group=value,
+ )
+ elif param == "priority":
+ try:
+ if int(value) < 1 or int(value) > 254:
+ raise ValueError
+ except ValueError:
+ module.fail_json(
+ msg="Warning! 'priority' must be an integer " "between 1 and 254",
+ priority=value,
+ )
+
+
+def main():
+ argument_spec = dict(
+ group=dict(required=True, type="str"),
+ interface=dict(required=True),
+ interval=dict(required=False, type="str"),
+ priority=dict(required=False, type="str"),
+ preempt=dict(required=False, type="bool"),
+ vip=dict(required=False, type="str"),
+ admin_state=dict(
+ required=False,
+ type="str",
+ choices=["shutdown", "no shutdown", "default"],
+ default="shutdown",
+ ),
+ authentication=dict(required=False, type="str", no_log=True),
+ state=dict(choices=["absent", "present"], required=False, default="present"),
+ )
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ results = {"changed": False, "commands": [], "warnings": warnings}
+
+ state = module.params["state"]
+ interface = module.params["interface"].lower()
+ group = module.params["group"]
+ priority = module.params["priority"]
+ interval = module.params["interval"]
+ preempt = module.params["preempt"]
+ vip = module.params["vip"]
+ authentication = module.params["authentication"]
+ admin_state = module.params["admin_state"]
+
+ device_info = get_capabilities(module)
+ network_api = device_info.get("network_api", "nxapi")
+
+ if state == "present" and not vip:
+ module.fail_json(msg='the "vip" param is required when state=present')
+
+ intf_type = get_interface_type(interface)
+ if intf_type != "ethernet" and network_api == "cliconf":
+ if is_default(interface, module) == "DNE":
+ module.fail_json(
+ msg="That interface does not exist yet. Create " "it first.",
+ interface=interface,
+ )
+ if intf_type == "loopback":
+ module.fail_json(
+ msg="Loopback interfaces don't support VRRP.",
+ interface=interface,
+ )
+
+ mode, name = get_interface_mode(interface, intf_type, module)
+ if mode == "layer2":
+ module.fail_json(
+ msg="That interface is a layer2 port.\nMake it " "a layer 3 port first.",
+ interface=interface,
+ )
+
+ args = dict(
+ group=group,
+ priority=priority,
+ preempt=preempt,
+ vip=vip,
+ authentication=authentication,
+ interval=interval,
+ admin_state=admin_state,
+ )
+
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ existing = get_existing_vrrp(interface, group, module, name)
+
+ commands = []
+
+ if state == "present":
+ delta = dict(set(proposed.items()).difference(existing.items()))
+ if delta:
+ command = get_commands_config_vrrp(delta, existing, group)
+ if command:
+ commands.append(command)
+ elif state == "absent":
+ if existing:
+ commands.append(["no vrrp {0}".format(group)])
+
+ if commands:
+ commands.insert(0, ["interface {0}".format(interface)])
+ commands = flatten_list(commands)
+ results["commands"] = commands
+ results["changed"] = True
+ if not module.check_mode:
+ load_config(module, commands)
+ if "configure" in commands:
+ commands.pop(0)
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vsan.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vsan.py
new file mode 100644
index 00000000..d95d95a9
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vsan.py
@@ -0,0 +1,354 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_vsan
+short_description: Configuration of vsan for Cisco NXOS MDS Switches.
+description:
+- Configuration of vsan for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ vsan:
+ description:
+ - List of vsan details to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ id:
+ description:
+ - Vsan id
+ required: true
+ type: int
+ name:
+ description:
+ - Name of the vsan
+ type: str
+ suspend:
+ description:
+ - suspend the vsan if True
+ type: bool
+ remove:
+ description:
+ - Removes the vsan if True
+ type: bool
+ interface:
+ description:
+ - List of vsan's interfaces to be added
+ type: list
+ elements: str
+"""
+
+EXAMPLES = """
+- name: Test that vsan module works
+ cisco.nxos.nxos_vsan:
+ vsan:
+ - id: 922
+ interface:
+ - fc1/1
+ - fc1/2
+ - port-channel 1
+ name: vsan-SAN-A
+ remove: false
+ suspend: false
+ - id: 923
+ interface:
+ - fc1/11
+ - fc1/21
+ - port-channel 2
+ name: vsan-SAN-B
+ remove: false
+ suspend: true
+ - id: 1923
+ name: vsan-SAN-Old
+ remove: true
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - vsan database
+ - vsan 922 interface fc1/40
+ - vsan 922 interface port-channel 155
+ - no terminal dont-ask
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+
+class Vsan(object):
+ def __init__(self, vsanid):
+ self.vsanid = vsanid
+ self.vsanname = None
+ self.vsanstate = None
+ self.vsanoperstate = None
+ self.vsaninterfaces = []
+
+
+class GetVsanInfoFromSwitch(object):
+ """docstring for GetVsanInfoFromSwitch"""
+
+ def __init__(self, module):
+ self.module = module
+ self.vsaninfo = {}
+ self.processShowVsan()
+ self.processShowVsanMembership()
+
+ def execute_show_vsan_cmd(self):
+ output = execute_show_command("show vsan", self.module)[0]
+ return output
+
+ def execute_show_vsan_mem_cmd(self):
+ output = execute_show_command("show vsan membership", self.module)[0]
+ return output
+
+ def processShowVsan(self):
+ patv = r"^vsan\s+(\d+)\s+information"
+ patnamestate = "name:(.*)state:(.*)"
+ patoperstate = "operational state:(.*)"
+
+ output = self.execute_show_vsan_cmd().split("\n")
+ for o in output:
+ z = re.match(patv, o.strip())
+ if z:
+ v = z.group(1).strip()
+ self.vsaninfo[v] = Vsan(v)
+
+ z1 = re.match(patnamestate, o.strip())
+ if z1:
+ n = z1.group(1).strip()
+ s = z1.group(2).strip()
+ self.vsaninfo[v].vsanname = n
+ self.vsaninfo[v].vsanstate = s
+
+ z2 = re.match(patoperstate, o.strip())
+ if z2:
+ oper = z2.group(1).strip()
+ self.vsaninfo[v].vsanoperstate = oper
+
+ # 4094/4079 vsan is always present
+ self.vsaninfo["4079"] = Vsan("4079")
+ self.vsaninfo["4094"] = Vsan("4094")
+
+ def processShowVsanMembership(self):
+ patv = r"^vsan\s+(\d+).*"
+ output = self.execute_show_vsan_mem_cmd().split("\n")
+ memlist = []
+ v = None
+ for o in output:
+ z = re.match(patv, o.strip())
+ if z:
+ if v is not None:
+ self.vsaninfo[v].vsaninterfaces = memlist
+ memlist = []
+ v = z.group(1)
+ if "interfaces" not in o:
+ llist = o.strip().split()
+ memlist = memlist + llist
+ self.vsaninfo[v].vsaninterfaces = memlist
+
+ def getVsanInfoObjects(self):
+ return self.vsaninfo
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def main():
+ vsan_element_spec = dict(
+ id=dict(required=True, type="int"),
+ name=dict(type="str"),
+ remove=dict(type="bool"),
+ suspend=dict(type="bool"),
+ interface=dict(type="list", elements="str"),
+ )
+
+ argument_spec = dict(vsan=dict(type="list", elements="dict", options=vsan_element_spec))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ warnings = list()
+ messages = list()
+ commands_executed = list()
+ result = {"changed": False}
+
+ obj = GetVsanInfoFromSwitch(module)
+ dictSwVsanObjs = obj.getVsanInfoObjects()
+
+ commands = []
+ vsan_list = module.params["vsan"]
+
+ for eachvsan in vsan_list:
+ vsanid = str(eachvsan["id"])
+ vsanname = eachvsan["name"]
+ vsanremove = eachvsan["remove"]
+ vsansuspend = eachvsan["suspend"]
+ vsaninterface_list = eachvsan["interface"]
+
+ if int(vsanid) < 1 or int(vsanid) >= 4095:
+ module.fail_json(
+ msg=vsanid + " - This is an invalid vsan. Supported vsan range is 1-4094",
+ )
+
+ if vsanid in dictSwVsanObjs.keys():
+ sw_vsanid = vsanid
+ sw_vsanname = dictSwVsanObjs[vsanid].vsanname
+ sw_vsanstate = dictSwVsanObjs[vsanid].vsanstate
+ sw_vsaninterfaces = dictSwVsanObjs[vsanid].vsaninterfaces
+ else:
+ sw_vsanid = None
+ sw_vsanname = None
+ sw_vsanstate = None
+ sw_vsaninterfaces = []
+
+ if vsanremove:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, hence cannot be removed")
+ continue
+ if vsanid == sw_vsanid:
+ commands.append("no vsan " + str(vsanid))
+ messages.append("deleting the vsan " + str(vsanid))
+ else:
+ messages.append(
+ "There is no vsan "
+ + str(vsanid)
+ + " present in the switch. Hence there is nothing to delete",
+ )
+ continue
+ else:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(
+ str(vsanid) + " is a reserved vsan, and always present on the switch",
+ )
+ else:
+ if vsanid == sw_vsanid:
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch. Hence there is nothing to configure",
+ )
+ else:
+ commands.append("vsan " + str(vsanid))
+ messages.append("creating vsan " + str(vsanid))
+
+ if vsanname is not None:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, and cannot be renamed")
+ else:
+ if vsanname == sw_vsanname:
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which has the name "
+ + vsanname
+ + " Hence there is nothing to configure",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " name " + vsanname)
+ messages.append("setting vsan name to " + vsanname + " for vsan " + str(vsanid))
+
+ if vsansuspend:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, and cannot be suspended")
+ else:
+ if sw_vsanstate == "suspended":
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which is in suspended state ",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " suspend")
+ messages.append("suspending the vsan " + str(vsanid))
+ else:
+ if sw_vsanstate == "active":
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which is in active state ",
+ )
+ else:
+ commands.append("no vsan " + str(vsanid) + " suspend")
+ messages.append("no suspending the vsan " + str(vsanid))
+
+ if vsaninterface_list is not None:
+ for each_interface_name in vsaninterface_list:
+ # For fcip,port-channel,vfc-port-channel need to remove the
+ # extra space to compare
+ temp = re.sub(" +", "", each_interface_name)
+ if temp in sw_vsaninterfaces:
+ messages.append(
+ each_interface_name
+ + " is already present in the vsan "
+ + str(vsanid)
+ + " interface list",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " interface " + each_interface_name)
+ messages.append(
+ "adding interface " + each_interface_name + " to vsan " + str(vsanid),
+ )
+
+ if len(commands) != 0:
+ commands = ["terminal dont-ask"] + ["vsan database"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+ commands_executed = cmds
+
+ if commands_executed:
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=commands_executed,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+ else:
+ result["changed"] = True
+ load_config(module, commands_executed)
+
+ result["messages"] = messages
+ result["commands"] = commands_executed
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_domain.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_domain.py
new file mode 100644
index 00000000..d1e3c39f
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_domain.py
@@ -0,0 +1,215 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vtp_domain
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VTP domain configuration.
+description:
+- Manages VTP domain configuration.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- VTP feature must be active on the device to use this module.
+- This module is used to manage only VTP domain names.
+- VTP domain names are case-sensible.
+- If it's never been configured before, VTP version is set to 1 by default. Otherwise,
+ it leaves the previous configured version untouched. Use M(cisco.nxos.nxos_vtp_version)
+ to change it.
+- Use this in combination with M(cisco.nxos.nxos_vtp_password) and M(cisco.nxos.nxos_vtp_version)
+ to fully manage VTP operations.
+options:
+ domain:
+ description:
+ - VTP domain name.
+ required: true
+ type: str
+"""
+
+EXAMPLES = """
+# ENSURE VTP DOMAIN IS CONFIGURED
+- cisco.nxos.nxos_vtp_domain:
+ domain: ntc
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"domain": "ntc"}
+existing:
+ description:
+ - k/v pairs of existing vtp domain
+ returned: always
+ type: dict
+ sample: {"domain": "testing", "version": "2", "vtp_password": "password"}
+end_state:
+ description: k/v pairs of vtp domain after module execution
+ returned: always
+ type: dict
+ sample: {"domain": "ntc", "version": "2", "vtp_password": "password"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["vtp domain ntc"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, output="json"):
+ cmds = [{"command": command, "output": output}]
+ body = run_commands(module, cmds)
+ return body
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_vtp_config(module):
+ command = "show vtp status"
+ body = execute_show_command(command, module, "text")[0]
+ vtp_parsed = {}
+
+ if body:
+ version_regex = r".*VTP version running\s+:\s+(?P<version>\d).*"
+ domain_regex = r".*VTP Domain Name\s+:\s+(?P<domain>\S+).*"
+
+ try:
+ match_version = re.match(version_regex, body, re.DOTALL)
+ version = match_version.groupdict()["version"]
+ except AttributeError:
+ version = ""
+
+ try:
+ match_domain = re.match(domain_regex, body, re.DOTALL)
+ domain = match_domain.groupdict()["domain"]
+ except AttributeError:
+ domain = ""
+
+ if domain and version:
+ vtp_parsed["domain"] = domain
+ vtp_parsed["version"] = version
+ vtp_parsed["vtp_password"] = get_vtp_password(module)
+
+ return vtp_parsed
+
+
+def get_vtp_password(module):
+ command = "show vtp password"
+ output = "json"
+ cap = get_capabilities(module)["device_info"]["network_os_model"]
+ if re.search(r"Nexus 6", cap):
+ output = "text"
+
+ body = execute_show_command(command, module, output)[0]
+
+ if output == "json":
+ password = body.get("passwd", "")
+ else:
+ password = ""
+ rp = r"VTP Password: (\S+)"
+ mo = re.search(rp, body)
+ if mo:
+ password = mo.group(1)
+
+ return str(password)
+
+
+def main():
+ argument_spec = dict(domain=dict(type="str", required=True))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ domain = module.params["domain"]
+
+ existing = get_vtp_config(module)
+ end_state = existing
+
+ args = dict(domain=domain)
+
+ changed = False
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ commands = []
+ if delta:
+ commands.append(["vtp domain {0}".format(domain)])
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_vtp_config(module)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["end_state"] = end_state
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_password.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_password.py
new file mode 100644
index 00000000..c6e9cabd
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_password.py
@@ -0,0 +1,277 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vtp_password
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VTP password configuration.
+description:
+- Manages VTP password configuration.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- VTP feature must be active on the device to use this module.
+- This module is used to manage only VTP passwords.
+- Use this in combination with M(cisco.nxos.nxos_vtp_domain) and M(cisco.nxos.nxos_vtp_version) to fully
+ manage VTP operations.
+- You can set/remove password only if a VTP domain already exist.
+- If C(state=absent) and no C(vtp_password) is provided, it remove the current VTP
+ password.
+- If C(state=absent) and C(vtp_password) is provided, the proposed C(vtp_password)
+ has to match the existing one in order to remove it.
+options:
+ vtp_password:
+ description:
+ - VTP password
+ type: str
+ state:
+ description:
+ - Manage the state of the resource
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+"""
+
+EXAMPLES = """
+# ENSURE VTP PASSWORD IS SET
+- cisco.nxos.nxos_vtp_password:
+ state: present
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+
+# ENSURE VTP PASSWORD IS REMOVED
+- cisco.nxos.nxos_vtp_password:
+ state: absent
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"vtp_password": "new_ntc"}
+existing:
+ description:
+ - k/v pairs of existing vtp
+ returned: always
+ type: dict
+ sample: {"domain": "ntc", "version": "1", "vtp_password": "ntc"}
+end_state:
+ description: k/v pairs of vtp after module execution
+ returned: always
+ type: dict
+ sample: {"domain": "ntc", "version": "1", "vtp_password": "new_ntc"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["vtp password new_ntc"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, output="json"):
+ cmds = [{"command": command, "output": output}]
+ body = run_commands(module, cmds)
+ return body
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = str(value)
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def get_vtp_config(module):
+ command = "show vtp status"
+
+ body = execute_show_command(command, module, "text")[0]
+ vtp_parsed = {}
+
+ if body:
+ version_regex = r".*VTP version running\s+:\s+(?P<version>\d).*"
+ domain_regex = r".*VTP Domain Name\s+:\s+(?P<domain>\S+).*"
+
+ try:
+ match_version = re.match(version_regex, body, re.DOTALL)
+ version = match_version.groupdict()["version"]
+ except AttributeError:
+ version = ""
+
+ try:
+ match_domain = re.match(domain_regex, body, re.DOTALL)
+ domain = match_domain.groupdict()["domain"]
+ except AttributeError:
+ domain = ""
+
+ if domain and version:
+ vtp_parsed["domain"] = domain
+ vtp_parsed["version"] = version
+ vtp_parsed["vtp_password"] = get_vtp_password(module)
+
+ return vtp_parsed
+
+
+def get_vtp_password(module):
+ command = "show vtp password"
+ output = "json"
+ cap = get_capabilities(module)["device_info"]["network_os_model"]
+ if re.search(r"Nexus 6", cap):
+ output = "text"
+
+ body = execute_show_command(command, module, output)[0]
+
+ if output == "json":
+ password = body.get("passwd", "")
+ else:
+ password = ""
+ rp = r"VTP Password: (\S+)"
+ mo = re.search(rp, body)
+ if mo:
+ password = mo.group(1)
+
+ return str(password)
+
+
+def main():
+ argument_spec = dict(
+ vtp_password=dict(type="str", no_log=True),
+ state=dict(choices=["absent", "present"], default="present"),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ vtp_password = module.params["vtp_password"] or None
+ state = module.params["state"]
+
+ existing = get_vtp_config(module)
+ end_state = existing
+
+ args = dict(vtp_password=vtp_password)
+
+ changed = False
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ commands = []
+ if state == "absent":
+ # if vtp_password is not set, some devices returns '\\' or the string 'None'
+ if (
+ not existing["vtp_password"]
+ or existing["vtp_password"] == "\\"
+ or existing["vtp_password"] == "None"
+ ):
+ pass
+ elif vtp_password is not None:
+ if existing["vtp_password"] == proposed["vtp_password"]:
+ commands.append(["no vtp password"])
+ else:
+ module.fail_json(
+ msg="Proposed vtp password doesn't match "
+ "current vtp password. It cannot be "
+ "removed when state=absent. If you are "
+ "trying to change the vtp password, use "
+ "state=present.",
+ )
+ else:
+ if not existing.get("domain"):
+ module.fail_json(msg="Cannot remove a vtp password " "before vtp domain is set.")
+
+ elif existing["vtp_password"] != ("\\"):
+ commands.append(["no vtp password"])
+
+ elif state == "present":
+ if delta:
+ if not existing.get("domain"):
+ module.fail_json(msg="Cannot set vtp password " "before vtp domain is set.")
+
+ else:
+ commands.append(["vtp password {0}".format(vtp_password)])
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_vtp_config(module)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["end_state"] = end_state
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_version.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_version.py
new file mode 100644
index 00000000..8c5305a0
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vtp_version.py
@@ -0,0 +1,210 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vtp_version
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VTP version configuration.
+description:
+- Manages VTP version configuration.
+version_added: 1.0.0
+author:
+- Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- VTP feature must be active on the device to use this module.
+- This module is used to manage only VTP version.
+- Use this in combination with M(cisco.nxos.nxos_vtp_password) and M(cisco.nxos.nxos_vtp_version)
+ to fully manage VTP operations.
+options:
+ version:
+ description:
+ - VTP version number.
+ required: true
+ choices:
+ - '1'
+ - '2'
+ type: str
+"""
+EXAMPLES = """
+# ENSURE VTP VERSION IS 2
+- cisco.nxos.nxos_vtp_version:
+ version: 2
+ host: '{{ inventory_hostname }}'
+ username: '{{ un }}'
+ password: '{{ pwd }}'
+"""
+
+RETURN = """
+proposed:
+ description: k/v pairs of parameters passed into module
+ returned: always
+ type: dict
+ sample: {"version": "2"}
+existing:
+ description:
+ - k/v pairs of existing vtp
+ returned: always
+ type: dict
+ sample: {"domain": "testing", "version": "1", "vtp_password": "password"}
+end_state:
+ description: k/v pairs of vtp after module execution
+ returned: always
+ type: dict
+ sample: {"domain": "testing", "version": "2", "vtp_password": "password"}
+updates:
+ description: command sent to the device
+ returned: always
+ type: list
+ sample: ["vtp version 2"]
+changed:
+ description: check to see if a change was made on the device
+ returned: always
+ type: bool
+ sample: true
+"""
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_capabilities,
+ load_config,
+ run_commands,
+)
+
+
+def execute_show_command(command, module, output="json"):
+ cmds = [{"command": command, "output": output}]
+ body = run_commands(module, cmds)
+ return body
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def get_vtp_config(module):
+ command = "show vtp status"
+ body = execute_show_command(command, module, "text")[0]
+ vtp_parsed = {}
+
+ if body:
+ version_regex = r".*VTP version running\s+:\s+(?P<version>\d).*"
+ domain_regex = r".*VTP Domain Name\s+:\s+(?P<domain>\S+).*"
+
+ try:
+ match_version = re.match(version_regex, body, re.DOTALL)
+ version = match_version.groupdict()["version"]
+ except AttributeError:
+ version = ""
+
+ try:
+ match_domain = re.match(domain_regex, body, re.DOTALL)
+ domain = match_domain.groupdict()["domain"]
+ except AttributeError:
+ domain = ""
+
+ if domain and version:
+ vtp_parsed["domain"] = domain
+ vtp_parsed["version"] = version
+ vtp_parsed["vtp_password"] = get_vtp_password(module)
+
+ return vtp_parsed
+
+
+def get_vtp_password(module):
+ command = "show vtp password"
+ output = "json"
+ cap = get_capabilities(module)["device_info"]["network_os_model"]
+ if re.search(r"Nexus 6", cap):
+ output = "text"
+
+ body = execute_show_command(command, module, output)[0]
+
+ if output == "json":
+ password = body.get("passwd", "")
+ else:
+ password = ""
+ rp = r"VTP Password: (\S+)"
+ mo = re.search(rp, body)
+ if mo:
+ password = mo.group(1)
+
+ return str(password)
+
+
+def main():
+ argument_spec = dict(version=dict(type="str", choices=["1", "2"], required=True))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+
+ version = module.params["version"]
+
+ existing = get_vtp_config(module)
+ end_state = existing
+
+ args = dict(version=version)
+
+ changed = False
+ proposed = dict((k, v) for k, v in args.items() if v is not None)
+ delta = dict(set(proposed.items()).difference(existing.items()))
+
+ commands = []
+ if delta:
+ commands.append(["vtp version {0}".format(version)])
+
+ cmds = flatten_list(commands)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(changed=True, commands=cmds)
+ else:
+ changed = True
+ load_config(module, cmds)
+ end_state = get_vtp_config(module)
+ if "configure" in cmds:
+ cmds.pop(0)
+
+ results = {}
+ results["proposed"] = proposed
+ results["existing"] = existing
+ results["end_state"] = end_state
+ results["updates"] = cmds
+ results["changed"] = changed
+ results["warnings"] = warnings
+
+ module.exit_json(**results)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep.py
new file mode 100644
index 00000000..ce311388
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep.py
@@ -0,0 +1,458 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vxlan_vtep
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Manages VXLAN Network Virtualization Endpoint (NVE).
+description:
+- Manages VXLAN Network Virtualization Endpoint (NVE) overlay interface that terminates
+ VXLAN tunnels.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- The module is used to manage NVE properties, not to create NVE interfaces. Use M(cisco.nxos.nxos_interfaces)
+ if you wish to do so.
+- C(state=absent) removes the interface.
+- Default, where supported, restores params default value.
+options:
+ interface:
+ description:
+ - Interface name for the VXLAN Network Virtualization Endpoint.
+ required: true
+ type: str
+ description:
+ description:
+ - Description of the NVE interface.
+ type: str
+ host_reachability:
+ description:
+ - Specify mechanism for host reachability advertisement. A Boolean value of 'true'
+ indicates that BGP will be used for host reachability advertisement. A Boolean
+ value of 'false' indicates that no protocol is used for host reachability advertisement.
+ Other host reachability advertisement protocols (e.g. OpenFlow, controller, etc.) are not
+ supported.
+ type: bool
+ shutdown:
+ description:
+ - Administratively shutdown the NVE interface.
+ type: bool
+ source_interface:
+ description:
+ - Specify the loopback interface whose IP address should be used for the NVE interface.
+ type: str
+ source_interface_hold_down_time:
+ description:
+ - Suppresses advertisement of the NVE loopback address until the overlay has converged.
+ type: str
+ global_mcast_group_L3:
+ description:
+ - Global multicast IP prefix for L3 VNIs or the keyword 'default'. This is available on
+ Nexus 9000 series switches running NX-OS software release 9.2(x) or higher.
+ type: str
+ global_mcast_group_L2:
+ description:
+ - Global multicast IP prefix for L2 VNIs or the keyword 'default'. This is available on
+ Nexus 9000 series switches running NX-OS software release 9.2(x) or higher.
+ type: str
+ global_suppress_arp:
+ description:
+ - Enables ARP suppression for all VNIs. This is available on NX-OS 9K series running
+ 9.2.x or higher.
+ type: bool
+ global_ingress_replication_bgp:
+ description:
+ - Configures ingress replication protocol as bgp for all VNIs. This is available on Nexus
+ 9000 series switches running NX-OS software release 9.2(x) or higher.
+ type: bool
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ multisite_border_gateway_interface:
+ description:
+ - Specify the loopback interface whose IP address should be used for the NVE
+ Multisite Border-gateway Interface. This is available on specific Nexus 9000
+ series switches running NX-OS 7.0(3)I7(x) or higher. Specify "default" to remove
+ an existing gateway config.
+ type: str
+ version_added: 1.1.0
+"""
+EXAMPLES = """
+- cisco.nxos.nxos_vxlan_vtep:
+ interface: nve1
+ description: default
+ host_reachability: true
+ source_interface: Loopback0
+ source_interface_hold_down_time: 30
+ shutdown: default
+ multisite_border_gateway_interface: Loopback0
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface nve1", "source-interface loopback0",
+ "source-interface hold-down-time 30", "description simple description",
+ "shutdown", "host-reachability protocol bgp",
+ "multisite border-gateway interface loopback0"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+ run_commands,
+)
+
+
+BOOL_PARAMS = [
+ "shutdown",
+ "host_reachability",
+ "global_ingress_replication_bgp",
+ "global_suppress_arp",
+]
+PARAM_TO_COMMAND_KEYMAP = {
+ "description": "description",
+ "global_suppress_arp": "global suppress-arp",
+ "global_ingress_replication_bgp": "global ingress-replication protocol bgp",
+ "global_mcast_group_L3": "global mcast-group L3",
+ "global_mcast_group_L2": "global mcast-group L2",
+ "host_reachability": "host-reachability protocol bgp",
+ "interface": "interface",
+ "shutdown": "shutdown",
+ "source_interface": "source-interface",
+ "source_interface_hold_down_time": "source-interface hold-down-time",
+ "multisite_border_gateway_interface": "multisite border-gateway interface",
+}
+PARAM_TO_DEFAULT_KEYMAP = {
+ "description": False,
+ "shutdown": True,
+ "source_interface_hold_down_time": "180",
+}
+
+
+def get_value(arg, config, module):
+ if arg in BOOL_PARAMS:
+ REGEX = re.compile(r"\s+{0}\s*$".format(PARAM_TO_COMMAND_KEYMAP[arg]), re.M)
+ NO_SHUT_REGEX = re.compile(r"\s+no shutdown\s*$", re.M)
+ value = False
+ if arg == "shutdown":
+ try:
+ if NO_SHUT_REGEX.search(config):
+ value = False
+ elif REGEX.search(config):
+ value = True
+ except TypeError:
+ value = False
+ else:
+ try:
+ if REGEX.search(config):
+ value = True
+ except TypeError:
+ value = False
+ else:
+ REGEX = re.compile(
+ r"(?:{0}\s)(?P<value>.*)$".format(PARAM_TO_COMMAND_KEYMAP[arg]),
+ re.M,
+ )
+ NO_DESC_REGEX = re.compile(r"\s+{0}\s*$".format("no description"), re.M)
+ SOURCE_INTF_REGEX = re.compile(
+ r"(?:{0}\s)(?P<value>\S+)$".format(PARAM_TO_COMMAND_KEYMAP[arg]),
+ re.M,
+ )
+ value = ""
+ if arg == "description":
+ if NO_DESC_REGEX.search(config):
+ value = False
+ elif PARAM_TO_COMMAND_KEYMAP[arg] in config:
+ value = REGEX.search(config).group("value").strip()
+ elif arg == "source_interface":
+ for line in config.splitlines():
+ try:
+ if PARAM_TO_COMMAND_KEYMAP[arg] in config:
+ value = SOURCE_INTF_REGEX.search(config).group("value").strip()
+ break
+ except AttributeError:
+ value = ""
+ elif arg == "global_mcast_group_L2":
+ for line in config.splitlines():
+ try:
+ if "global mcast-group" in line and "L2" in line:
+ value = line.split()[2].strip()
+ break
+ except AttributeError:
+ value = ""
+ elif arg == "global_mcast_group_L3":
+ for line in config.splitlines():
+ try:
+ if "global mcast-group" in line and "L3" in line:
+ value = line.split()[2].strip()
+ break
+ except AttributeError:
+ value = ""
+ elif arg == "multisite_border_gateway_interface":
+ for line in config.splitlines():
+ try:
+ if PARAM_TO_COMMAND_KEYMAP[arg] in config:
+ value = SOURCE_INTF_REGEX.search(config).group("value").strip()
+ break
+ except AttributeError:
+ value = ""
+ else:
+ if PARAM_TO_COMMAND_KEYMAP[arg] in config:
+ value = REGEX.search(config).group("value").strip()
+ return value
+
+
+def get_existing(module, args):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module, flags=["all"]))
+
+ interface_string = "interface {0}".format(module.params["interface"].lower())
+ parents = [interface_string]
+ config = netcfg.get_section(parents)
+
+ if config:
+ for arg in args:
+ existing[arg] = get_value(arg, config, module)
+
+ existing["interface"] = module.params["interface"].lower()
+ else:
+ if interface_string in str(netcfg):
+ existing["interface"] = module.params["interface"].lower()
+ for arg in args:
+ existing[arg] = ""
+ return existing
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ value = table.get(key)
+ if value:
+ new_dict[new_key] = value
+ else:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def fix_commands(commands, module):
+ source_interface_command = ""
+ no_source_interface_command = ""
+ no_host_reachability_command = ""
+ host_reachability_command = ""
+
+ for command in commands:
+ if "no source-interface hold-down-time" in command:
+ pass
+ elif "source-interface hold-down-time" in command:
+ pass
+ elif "no source-interface" in command:
+ no_source_interface_command = command
+ elif "source-interface" in command:
+ source_interface_command = command
+ elif "no host-reachability" in command:
+ no_host_reachability_command = command
+ elif "host-reachability" in command:
+ host_reachability_command = command
+
+ if host_reachability_command:
+ commands.pop(commands.index(host_reachability_command))
+ commands.insert(0, host_reachability_command)
+
+ if source_interface_command:
+ commands.pop(commands.index(source_interface_command))
+ commands.insert(0, source_interface_command)
+
+ if no_host_reachability_command:
+ commands.pop(commands.index(no_host_reachability_command))
+ commands.append(no_host_reachability_command)
+
+ if no_source_interface_command:
+ commands.pop(commands.index(no_source_interface_command))
+ commands.append(no_source_interface_command)
+
+ commands.insert(0, "terminal dont-ask")
+ return commands
+
+
+def gsa_tcam_check(module):
+ """
+ global_suppress_arp is an N9k-only command that requires TCAM resources.
+ This method checks the current TCAM allocation.
+ Note that changing tcam_size requires a switch reboot to take effect.
+ """
+ cmds = [{"command": "show hardware access-list tcam region", "output": "json"}]
+ body = run_commands(module, cmds)
+ if body:
+ tcam_region = body[0]["TCAM_Region"]["TABLE_Sizes"]["ROW_Sizes"]
+ if bool(
+ [
+ i
+ for i in tcam_region
+ if i["type"].startswith("Ingress ARP-Ether ACL") and i["tcam_size"] == "0"
+ ],
+ ):
+ msg = (
+ "'show hardware access-list tcam region' indicates 'ARP-Ether' tcam size is 0 (no allocated resources). "
+ + "'global_suppress_arp' will be rejected by device."
+ )
+ module.fail_json(msg=msg)
+
+
+def state_present(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+ for key, value in proposed_commands.items():
+ if value is True:
+ commands.append(key)
+
+ elif value is False:
+ commands.append("no {0}".format(key))
+
+ elif value == "default":
+ if existing_commands.get(key):
+ existing_value = existing_commands.get(key)
+ if "global mcast-group" in key:
+ commands.append("no {0}".format(key))
+ else:
+ commands.append("no {0} {1}".format(key, existing_value))
+ else:
+ if key.replace(" ", "_").replace("-", "_") in BOOL_PARAMS:
+ commands.append("no {0}".format(key.lower()))
+ module.exit_json(commands=commands)
+ else:
+ if "L2" in key:
+ commands.append("global mcast-group " + value + " L2")
+ elif "L3" in key:
+ commands.append("global mcast-group " + value + " L3")
+ else:
+ command = "{0} {1}".format(key, value.lower())
+ commands.append(command)
+
+ if commands:
+ commands = fix_commands(commands, module)
+ parents = ["interface {0}".format(module.params["interface"].lower())]
+ candidate.add(commands, parents=parents)
+ else:
+ if not existing and module.params["interface"]:
+ commands = ["interface {0}".format(module.params["interface"].lower())]
+ candidate.add(commands, parents=[])
+
+
+def state_absent(module, existing, proposed, candidate):
+ commands = ["no interface {0}".format(module.params["interface"].lower())]
+ candidate.add(commands, parents=[])
+
+
+def main():
+ argument_spec = dict(
+ interface=dict(required=True, type="str"),
+ description=dict(required=False, type="str"),
+ host_reachability=dict(required=False, type="bool"),
+ global_ingress_replication_bgp=dict(required=False, type="bool"),
+ global_suppress_arp=dict(required=False, type="bool"),
+ global_mcast_group_L2=dict(required=False, type="str"),
+ global_mcast_group_L3=dict(required=False, type="str"),
+ shutdown=dict(required=False, type="bool"),
+ source_interface=dict(required=False, type="str"),
+ source_interface_hold_down_time=dict(required=False, type="str"),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ multisite_border_gateway_interface=dict(required=False, type="str"),
+ )
+
+ mutually_exclusive = [("global_ingress_replication_bgp", "global_mcast_group_L2")]
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = {"changed": False, "commands": [], "warnings": warnings}
+
+ state = module.params["state"]
+
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+
+ existing = get_existing(module, args)
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key != "interface":
+ if str(value).lower() == "default":
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key)
+ if value is None:
+ if key in BOOL_PARAMS:
+ value = False
+ else:
+ value = "default"
+ if str(existing.get(key)).lower() != str(value).lower():
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+
+ if proposed.get("global_suppress_arp"):
+ gsa_tcam_check(module)
+ if state == "present":
+ if not existing:
+ warnings.append(
+ "The proposed NVE interface did not exist. "
+ "It's recommended to use nxos_interfaces to create "
+ "all logical interfaces.",
+ )
+ state_present(module, existing, proposed, candidate)
+ elif state == "absent" and existing:
+ state_absent(module, existing, proposed, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ result["commands"] = candidate
+ result["changed"] = True
+ load_config(module, candidate)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep_vni.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep_vni.py
new file mode 100644
index 00000000..d58bd6c9
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_vxlan_vtep_vni.py
@@ -0,0 +1,452 @@
+#!/usr/bin/python
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+module: nxos_vxlan_vtep_vni
+extends_documentation_fragment:
+- cisco.nxos.nxos
+short_description: Creates a Virtual Network Identifier member (VNI)
+description:
+- Creates a Virtual Network Identifier member (VNI) for an NVE overlay interface.
+version_added: 1.0.0
+author: Gabriele Gerbino (@GGabriele)
+notes:
+- Tested against NXOSv 7.3.(0)D1(1) on VIRL
+- Unsupported for Cisco MDS
+- default, where supported, restores params default value.
+options:
+ interface:
+ description:
+ - Interface name for the VXLAN Network Virtualization Endpoint.
+ required: true
+ type: str
+ vni:
+ description:
+ - ID of the Virtual Network Identifier.
+ required: true
+ type: str
+ assoc_vrf:
+ description:
+ - This attribute is used to identify and separate processing VNIs that are associated
+ with a VRF and used for routing. The VRF and VNI specified with this command
+ must match the configuration of the VNI under the VRF.
+ type: bool
+ ingress_replication:
+ description:
+ - Specifies mechanism for host reachability advertisement.
+ choices:
+ - bgp
+ - static
+ - default
+ type: str
+ multicast_group:
+ description:
+ - The multicast group (range) of the VNI. Valid values are string and keyword
+ 'default'.
+ type: str
+ peer_list:
+ description:
+ - Set the ingress-replication static peer list. Valid values are an array, a space-separated
+ string of ip addresses, or the keyword 'default'.
+ type: list
+ elements: str
+ suppress_arp:
+ description:
+ - Suppress arp under layer 2 VNI.
+ type: bool
+ suppress_arp_disable:
+ description:
+ - Overrides the global ARP suppression config. This is available on NX-OS 9K series
+ running 9.2.x or higher.
+ type: bool
+ state:
+ description:
+ - Determines whether the config should be present or not on the device.
+ default: present
+ choices:
+ - present
+ - absent
+ type: str
+ multisite_ingress_replication:
+ description:
+ - Enables multisite ingress replication.
+ choices:
+ - disable
+ - enable
+ - optimized
+ type: str
+ version_added: 1.1.0
+"""
+EXAMPLES = """
+- cisco.nxos.nxos_vxlan_vtep_vni:
+ interface: nve1
+ vni: 6000
+ ingress_replication: default
+ multisite_ingress_replication: enable
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample: ["interface nve1", "member vni 6000", "multisite ingress-replication"]
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.config import (
+ CustomNetworkConfig,
+)
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ get_config,
+ load_config,
+)
+
+
+BOOL_PARAMS = ["assoc_vrf", "suppress_arp", "suppress_arp_disable"]
+PARAM_TO_DEFAULT_KEYMAP = {
+ "multicast_group": "",
+ "peer_list": [],
+ "ingress_replication": "",
+}
+PARAM_TO_COMMAND_KEYMAP = {
+ "assoc_vrf": "associate-vrf",
+ "interface": "interface",
+ "vni": "member vni",
+ "ingress_replication": "ingress-replication protocol",
+ "multicast_group": "mcast-group",
+ "peer_list": "peer-ip",
+ "suppress_arp": "suppress-arp",
+ "suppress_arp_disable": "suppress-arp disable",
+ "multisite_ingress_replication": "multisite ingress-replication",
+}
+
+
+def get_value(arg, config, module):
+ command = PARAM_TO_COMMAND_KEYMAP[arg]
+ command_val_re = re.compile(r"(?:{0}\s)(?P<value>.*)$".format(command), re.M)
+
+ if arg in BOOL_PARAMS:
+ command_re = re.compile(r"\s+{0}\s*$".format(command), re.M)
+ value = False
+ if command_re.search(config):
+ value = True
+ elif arg == "peer_list":
+ has_command_val = command_val_re.findall(config, re.M)
+ value = []
+ if has_command_val:
+ value = has_command_val
+ elif arg == "multisite_ingress_replication":
+ has_command = re.search(r"^\s+{0}$".format(command), config, re.M)
+ has_command_val = command_val_re.search(config, re.M)
+ value = "disable"
+ if has_command:
+ value = "enable"
+ elif has_command_val:
+ value = "optimized"
+ else:
+ value = ""
+ has_command_val = command_val_re.search(config, re.M)
+ if has_command_val:
+ value = has_command_val.group("value")
+ return value
+
+
+def check_interface(module, netcfg):
+ config = str(netcfg)
+
+ has_interface = re.search(r"(?:interface nve)(?P<value>.*)$", config, re.M)
+ value = ""
+ if has_interface:
+ value = "nve{0}".format(has_interface.group("value"))
+
+ return value
+
+
+def get_existing(module, args):
+ existing = {}
+ netcfg = CustomNetworkConfig(indent=2, contents=get_config(module))
+
+ interface_exist = check_interface(module, netcfg)
+ if interface_exist:
+ parents = ["interface {0}".format(interface_exist)]
+ temp_config = netcfg.get_section(parents)
+
+ if "member vni {0} associate-vrf".format(module.params["vni"]) in temp_config:
+ parents.append("member vni {0} associate-vrf".format(module.params["vni"]))
+ config = netcfg.get_section(parents)
+ elif "member vni {0}".format(module.params["vni"]) in temp_config:
+ parents.append("member vni {0}".format(module.params["vni"]))
+ config = netcfg.get_section(parents)
+ else:
+ config = {}
+
+ if config:
+ for arg in args:
+ if arg not in ["interface", "vni"]:
+ existing[arg] = get_value(arg, config, module)
+ existing["interface"] = interface_exist
+ existing["vni"] = module.params["vni"]
+
+ return existing, interface_exist
+
+
+def apply_key_map(key_map, table):
+ new_dict = {}
+ for key, value in table.items():
+ new_key = key_map.get(key)
+ if new_key:
+ new_dict[new_key] = value
+ return new_dict
+
+
+def state_present(module, existing, proposed, candidate):
+ commands = list()
+ proposed_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, proposed)
+ existing_commands = apply_key_map(PARAM_TO_COMMAND_KEYMAP, existing)
+
+ for key, value in proposed_commands.items():
+ if key == "associate-vrf":
+ command = "member vni {0} {1}".format(module.params["vni"], key)
+ if not value:
+ command = "no {0}".format(command)
+ commands.append(command)
+
+ elif key == "peer-ip" and value != []:
+ for peer in value:
+ commands.append("{0} {1}".format(key, peer))
+
+ elif key == "mcast-group" and value != existing_commands.get(key):
+ commands.append("no {0}".format(key))
+ vni_command = "member vni {0}".format(module.params["vni"])
+ if vni_command not in commands:
+ commands.append("member vni {0}".format(module.params["vni"]))
+ if value != PARAM_TO_DEFAULT_KEYMAP.get("multicast_group", "default"):
+ commands.append("{0} {1}".format(key, value))
+
+ elif key == "ingress-replication protocol" and value != existing_commands.get(key):
+ evalue = existing_commands.get(key)
+ dvalue = PARAM_TO_DEFAULT_KEYMAP.get("ingress_replication", "default")
+ if value != dvalue:
+ if evalue and evalue != dvalue:
+ commands.append("no {0} {1}".format(key, evalue))
+ commands.append("{0} {1}".format(key, value))
+ else:
+ if evalue:
+ commands.append("no {0} {1}".format(key, evalue))
+
+ elif value is True:
+ commands.append(key)
+ elif value is False:
+ commands.append("no {0}".format(key))
+ elif value == "default" or value == []:
+ if existing_commands.get(key):
+ existing_value = existing_commands.get(key)
+ if key == "peer-ip":
+ for peer in existing_value:
+ commands.append("no {0} {1}".format(key, peer))
+ else:
+ commands.append("no {0} {1}".format(key, existing_value))
+ else:
+ if key.replace(" ", "_").replace("-", "_") in BOOL_PARAMS:
+ commands.append("no {0}".format(key.lower()))
+ elif key == "multisite ingress-replication" and value != existing_commands.get(key):
+ vni_command = "member vni {0}".format(module.params["vni"])
+ if vni_command not in commands:
+ commands.append("member vni {0}".format(module.params["vni"]))
+ if value == "disable":
+ command = "no {0}".format(key)
+ commands.append(command)
+ elif value == "enable":
+ command = "{0}".format(key)
+ commands.append(command)
+ elif value == "optimized":
+ command = "{0} {1}".format(key, value)
+ commands.append(command)
+ else:
+ command = "{0} {1}".format(key, value.lower())
+ commands.append(command)
+
+ if commands:
+ vni_command = "member vni {0}".format(module.params["vni"])
+ ingress_replications_command = "ingress-replication protocol static"
+ ingress_replicationb_command = "ingress-replication protocol bgp"
+ ingress_replicationns_command = "no ingress-replication protocol static"
+ ingress_replicationnb_command = "no ingress-replication protocol bgp"
+ interface_command = "interface {0}".format(module.params["interface"])
+
+ if any(
+ c in commands
+ for c in (
+ ingress_replications_command,
+ ingress_replicationb_command,
+ ingress_replicationnb_command,
+ ingress_replicationns_command,
+ )
+ ):
+ static_level_cmds = [cmd for cmd in commands if "peer" in cmd]
+ parents = [interface_command, vni_command]
+ commands = [cmd for cmd in commands if "peer" not in cmd]
+ for cmd in commands:
+ parents.append(cmd)
+ candidate.add(static_level_cmds, parents=parents)
+
+ elif "peer-ip" in commands[0]:
+ static_level_cmds = list(commands)
+ parents = [
+ interface_command,
+ vni_command,
+ ingress_replications_command,
+ ]
+ candidate.add(static_level_cmds, parents=parents)
+
+ if vni_command in commands:
+ parents = [interface_command]
+ commands.remove(vni_command)
+ if module.params["assoc_vrf"] is None:
+ parents.append(vni_command)
+ candidate.add(commands, parents=parents)
+
+
+def state_absent(module, existing, proposed, candidate):
+ if existing["assoc_vrf"]:
+ commands = ["no member vni {0} associate-vrf".format(module.params["vni"])]
+ else:
+ commands = ["no member vni {0}".format(module.params["vni"])]
+ parents = ["interface {0}".format(module.params["interface"])]
+ candidate.add(commands, parents=parents)
+
+
+def main():
+ argument_spec = dict(
+ interface=dict(required=True, type="str"),
+ vni=dict(required=True, type="str"),
+ assoc_vrf=dict(required=False, type="bool"),
+ multicast_group=dict(required=False, type="str"),
+ peer_list=dict(required=False, type="list", elements="str"),
+ suppress_arp=dict(required=False, type="bool"),
+ suppress_arp_disable=dict(required=False, type="bool"),
+ ingress_replication=dict(required=False, type="str", choices=["bgp", "static", "default"]),
+ state=dict(choices=["present", "absent"], default="present", required=False),
+ multisite_ingress_replication=dict(
+ required=False,
+ type="str",
+ choices=["enable", "optimized", "disable"],
+ ),
+ )
+
+ mutually_exclusive = [
+ ("suppress_arp", "suppress_arp_disable"),
+ ("assoc_vrf", "multicast_group"),
+ ("assoc_vrf", "suppress_arp"),
+ ("assoc_vrf", "suppress_arp_disable"),
+ ("assoc_vrf", "ingress_replication"),
+ ]
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ mutually_exclusive=mutually_exclusive,
+ supports_check_mode=True,
+ )
+
+ warnings = list()
+ result = {"changed": False, "commands": [], "warnings": warnings}
+
+ if module.params["peer_list"]:
+ if (
+ module.params["peer_list"][0] != "default"
+ and module.params["ingress_replication"] != "static"
+ ):
+ module.fail_json(
+ msg="ingress_replication=static is required " "when using peer_list param",
+ )
+ else:
+ peer_list = module.params["peer_list"]
+ if peer_list[0] == "default":
+ module.params["peer_list"] = "default"
+ else:
+ stripped_peer_list = list(map(str.strip, peer_list))
+ module.params["peer_list"] = stripped_peer_list
+
+ if (
+ module.params["multisite_ingress_replication"] == "enable"
+ or module.params["multisite_ingress_replication"] == "optimized"
+ ):
+ if module.params["ingress_replication"] == "static":
+ module.fail_json(
+ msg="ingress_replication=static is not allowed "
+ "when using multisite_ingress_replication",
+ )
+
+ state = module.params["state"]
+ args = PARAM_TO_COMMAND_KEYMAP.keys()
+ existing, interface_exist = get_existing(module, args)
+
+ if state == "present":
+ if not interface_exist:
+ module.fail_json(
+ msg="The proposed NVE interface does not exist. Use nxos_interface to create it first.",
+ )
+ elif interface_exist != module.params["interface"]:
+ module.fail_json(msg="Only 1 NVE interface is allowed on the switch.")
+ elif state == "absent":
+ if interface_exist != module.params["interface"]:
+ module.exit_json(**result)
+ elif existing and existing["vni"] != module.params["vni"]:
+ module.fail_json(
+ msg="ERROR: VNI delete failed: Could not find vni node for {0}".format(
+ module.params["vni"],
+ ),
+ existing_vni=existing["vni"],
+ )
+
+ proposed_args = dict((k, v) for k, v in module.params.items() if v is not None and k in args)
+
+ proposed = {}
+ for key, value in proposed_args.items():
+ if key in ["multicast_group", "peer_list", "ingress_replication"]:
+ if str(value).lower() == "default":
+ value = PARAM_TO_DEFAULT_KEYMAP.get(key, "default")
+ if key != "interface" and existing.get(key) != value:
+ proposed[key] = value
+
+ candidate = CustomNetworkConfig(indent=3)
+ if state == "present":
+ state_present(module, existing, proposed, candidate)
+ elif existing and state == "absent":
+ state_absent(module, existing, proposed, candidate)
+
+ if candidate:
+ candidate = candidate.items_text()
+ result["changed"] = True
+ result["commands"] = candidate
+ if not module.check_mode:
+ load_config(module, candidate)
+
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/nxos_zone_zoneset.py b/ansible_collections/cisco/nxos/plugins/modules/nxos_zone_zoneset.py
new file mode 100644
index 00000000..7c9fba30
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/nxos_zone_zoneset.py
@@ -0,0 +1,888 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_zone_zoneset
+short_description: Configuration of zone/zoneset for Cisco NXOS MDS Switches.
+description:
+- Configuration of zone/zoneset for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ zone_zoneset_details:
+ description:
+ - List of zone/zoneset details to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ vsan:
+ description:
+ - vsan id
+ required: true
+ type: int
+ mode:
+ description:
+ - mode of the zone for the vsan
+ choices:
+ - enhanced
+ - basic
+ type: str
+ default_zone:
+ description:
+ - default zone behaviour for the vsan
+ choices:
+ - permit
+ - deny
+ type: str
+ smart_zoning:
+ description:
+ - Removes the vsan if True
+ type: bool
+ zone:
+ description:
+ - List of zone options for that vsan
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zone
+ required: true
+ type: str
+ remove:
+ description:
+ - Deletes the zone if True
+ type: bool
+ default: false
+ members:
+ description:
+ - Members of the zone that needs to be removed or added
+ type: list
+ elements: dict
+ suboptions:
+ pwwn:
+ description:
+ - pwwn member of the zone, use alias 'device_alias' as option for
+ device_alias member
+ aliases:
+ - device_alias
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes member from the zone if True
+ type: bool
+ default: false
+ devtype:
+ description:
+ - devtype of the zone member used along with Smart zoning config
+ choices:
+ - initiator
+ - target
+ - both
+ type: str
+ zoneset:
+ description:
+ - List of zoneset options for the vsan
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zoneset
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes zoneset if True
+ type: bool
+ default: false
+ action:
+ description:
+ - activates/de-activates the zoneset
+ choices:
+ - activate
+ - deactivate
+ type: str
+ members:
+ description:
+ - Members of the zoneset that needs to be removed or added
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zone that needs to be added to the zoneset or removed
+ from the zoneset
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes zone member from the zoneset
+ type: bool
+ default: false
+"""
+
+EXAMPLES = """
+- name: Test that zone/zoneset module works
+ cisco.nxos.nxos_zone_zoneset:
+ zone_zoneset_details:
+ - mode: enhanced
+ vsan: 22
+ zone:
+ - members:
+ - pwwn: 11:11:11:11:11:11:11:11
+ - device_alias: test123
+ - pwwn: 61:61:62:62:12:12:12:12
+ remove: true
+ name: zoneA
+ - members:
+ - pwwn: 10:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:21:21:21:21
+ name: zoneB
+ - name: zoneC
+ remove: true
+ zoneset:
+ - action: activate
+ members:
+ - name: zoneA
+ - name: zoneB
+ - name: zoneC
+ remove: true
+ name: zsetname1
+ - action: deactivate
+ name: zsetTestExtra
+ remove: true
+ - mode: basic
+ smart_zoning: true
+ vsan: 21
+ zone:
+ - members:
+ - devtype: both
+ pwwn: 11:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:12:12:12:12
+ - devtype: both
+ pwwn: 92:62:62:62:12:12:1a:1a
+ remove: true
+ name: zone21A
+ - members:
+ - pwwn: 10:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:21:21:21:21
+ name: zone21B
+ zoneset:
+ - action: activate
+ members:
+ - name: zone21A
+ - name: zone21B
+ name: zsetname212
+
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - zone name zoneA vsan 923
+ - member pwwn 11:11:11:11:11:11:11:11
+ - no member device-alias test123
+ - zone commit vsan 923
+ - no terminal dont-ask
+messages:
+ description: debug messages
+ returned: always
+ type: list
+ sample:
+ - "zone mode is already enhanced ,no change in zone mode configuration for vsan 922"
+ - "zone member '11:11:11:11:11:11:11:11' is already present in zone 'zoneA' in vsan 922 hence nothing to add"
+ - "zone member 'test123' is already present in zone 'zoneA' in vsan 922 hence nothing to add"
+ - "zone member '61:61:62:62:12:12:12:12' is not present in zone 'zoneA' in vsan 922 hence nothing to remove"
+ - "zone member '10:11:11:11:11:11:11:11' is already present in zone 'zoneB' in vsan 922 hence nothing to add"
+ - "zone member '62:62:62:62:21:21:21:21' is already present in zone 'zoneB' in vsan 922 hence nothing to add"
+ - "zone 'zoneC' is not present in vsan 922 , so nothing to remove"
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+
+class ShowZonesetActive(object):
+ """docstring for ShowZonesetActive"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.activeZSName = None
+ self.parseCmdOutput()
+
+ def execute_show_zoneset_active_cmd(self):
+ command = "show zoneset active vsan " + str(self.vsan) + " | grep zoneset"
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zoneset_active_cmd().split("\n")
+ if len(output) == 0:
+ return
+ else:
+ for line in output:
+ line = line.strip()
+ mzs = re.match(patZoneset, line.strip())
+ if mzs:
+ self.activeZSName = mzs.group(1).strip()
+ return
+
+ def isZonesetActive(self, zsname):
+ if zsname == self.activeZSName:
+ return True
+ return False
+
+
+class ShowZoneset(object):
+ """docstring for ShowZoneset"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.zsDetails = {}
+ self.parseCmdOutput()
+
+ def execute_show_zoneset_cmd(self):
+ command = "show zoneset vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan)
+ patZone = r"zone name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zoneset_cmd().split("\n")
+ for line in output:
+ line = line.strip()
+ mzs = re.match(patZoneset, line.strip())
+ mz = re.match(patZone, line.strip())
+ if mzs:
+ zonesetname = mzs.group(1).strip()
+ self.zsDetails[zonesetname] = []
+ continue
+ elif mz:
+ zonename = mz.group(1).strip()
+ v = self.zsDetails[zonesetname]
+ v.append(zonename)
+ self.zsDetails[zonesetname] = v
+
+ def isZonesetPresent(self, zsname):
+ return zsname in self.zsDetails.keys()
+
+ def isZonePresentInZoneset(self, zsname, zname):
+ if zsname in self.zsDetails.keys():
+ return zname in self.zsDetails[zsname]
+ return False
+
+
+class ShowZone(object):
+ """docstring for ShowZone"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.zDetails = {}
+ self.parseCmdOutput()
+
+ def execute_show_zone_vsan_cmd(self):
+ command = "show zone vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZone = r"zone name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zone_vsan_cmd().split("\n")
+ for line in output:
+ line = re.sub(r"[\[].*?[\]]", "", line)
+ line = " ".join(line.strip().split())
+ if "init" in line:
+ line = line.replace("init", "initiator")
+ m = re.match(patZone, line)
+ if m:
+ zonename = m.group(1).strip()
+ self.zDetails[zonename] = []
+ continue
+ else:
+ # For now we support only pwwn and device-alias under zone
+ # Ideally should use 'supported_choices'....but maybe next
+ # time.
+ if "pwwn" in line or "device-alias" in line:
+ v = self.zDetails[zonename]
+ v.append(line)
+ self.zDetails[zonename] = v
+
+ def isZonePresent(self, zname):
+ return zname in self.zDetails.keys()
+
+ def isZoneMemberPresent(self, zname, cmd):
+ if zname in self.zDetails.keys():
+ zonememlist = self.zDetails[zname]
+ for eachline in zonememlist:
+ if cmd in eachline:
+ return True
+ return False
+
+ def get_zDetails(self):
+ return self.zDetails
+
+
+class ShowZoneStatus(object):
+ """docstring for ShowZoneStatus"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.vsanAbsent = False
+ self.module = module
+ self.default_zone = ""
+ self.mode = ""
+ self.session = ""
+ self.sz = ""
+ self.locked = False
+ self.update()
+
+ def execute_show_zone_status_cmd(self):
+ command = "show zone status vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def update(self):
+ output = self.execute_show_zone_status_cmd().split("\n")
+
+ patfordefzone = "VSAN: " + str(self.vsan) + r" default-zone:\s+(\S+).*"
+ patformode = r".*mode:\s+(\S+).*"
+ patforsession = r"^session:\s+(\S+).*"
+ patforsz = r".*smart-zoning:\s+(\S+).*"
+ for line in output:
+ if "is not configured" in line:
+ self.vsanAbsent = True
+ break
+ mdefz = re.match(patfordefzone, line.strip())
+ mmode = re.match(patformode, line.strip())
+ msession = re.match(patforsession, line.strip())
+ msz = re.match(patforsz, line.strip())
+
+ if mdefz:
+ self.default_zone = mdefz.group(1)
+ if mmode:
+ self.mode = mmode.group(1)
+ if msession:
+ self.session = msession.group(1)
+ if self.session != "none":
+ self.locked = True
+ if msz:
+ self.sz = msz.group(1)
+
+ def isLocked(self):
+ return self.locked
+
+ def getDefaultZone(self):
+ return self.default_zone
+
+ def getMode(self):
+ return self.mode
+
+ def getSmartZoningStatus(self):
+ return self.sz
+
+ def isVsanAbsent(self):
+ return self.vsanAbsent
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def getMemType(supported_choices, allmemkeys, default="pwwn"):
+ for eachchoice in supported_choices:
+ if eachchoice in allmemkeys:
+ return eachchoice
+ return default
+
+
+def main():
+ supported_choices = ["device_alias"]
+ zone_member_spec = dict(
+ pwwn=dict(required=True, type="str", aliases=["device_alias"]),
+ devtype=dict(type="str", choices=["initiator", "target", "both"]),
+ remove=dict(type="bool", default=False),
+ )
+
+ zone_spec = dict(
+ name=dict(required=True, type="str"),
+ members=dict(type="list", elements="dict", options=zone_member_spec),
+ remove=dict(type="bool", default=False),
+ )
+
+ zoneset_member_spec = dict(
+ name=dict(required=True, type="str"),
+ remove=dict(type="bool", default=False),
+ )
+
+ zoneset_spec = dict(
+ name=dict(type="str", required=True),
+ members=dict(type="list", elements="dict", options=zoneset_member_spec),
+ remove=dict(type="bool", default=False),
+ action=dict(type="str", choices=["activate", "deactivate"]),
+ )
+
+ zonedetails_spec = dict(
+ vsan=dict(required=True, type="int"),
+ mode=dict(type="str", choices=["enhanced", "basic"]),
+ default_zone=dict(type="str", choices=["permit", "deny"]),
+ smart_zoning=dict(type="bool"),
+ zone=dict(type="list", elements="dict", options=zone_spec),
+ zoneset=dict(type="list", elements="dict", options=zoneset_spec),
+ )
+
+ argument_spec = dict(
+ zone_zoneset_details=dict(type="list", elements="dict", options=zonedetails_spec),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ messages = list()
+ commands = list()
+ result = {"changed": False}
+
+ commands_executed = []
+ listOfZoneDetails = module.params["zone_zoneset_details"]
+ for eachZoneZonesetDetail in listOfZoneDetails:
+ vsan = eachZoneZonesetDetail["vsan"]
+ op_mode = eachZoneZonesetDetail["mode"]
+ op_default_zone = eachZoneZonesetDetail["default_zone"]
+ op_smart_zoning = eachZoneZonesetDetail["smart_zoning"]
+ op_zone = eachZoneZonesetDetail["zone"]
+ op_zoneset = eachZoneZonesetDetail["zoneset"]
+
+ # Step1: execute show zone status and get
+ shZoneStatusObj = ShowZoneStatus(module, vsan)
+ sw_default_zone = shZoneStatusObj.getDefaultZone()
+ sw_mode = shZoneStatusObj.getMode()
+ sw_smart_zoning = shZoneStatusObj.getSmartZoningStatus()
+
+ if sw_smart_zoning.lower() == "Enabled".lower():
+ sw_smart_zoning_bool = True
+ else:
+ sw_smart_zoning_bool = False
+
+ if shZoneStatusObj.isVsanAbsent():
+ module.fail_json(
+ msg="Vsan " + str(vsan) + " is not present in the switch. Hence cannot procced.",
+ )
+
+ if shZoneStatusObj.isLocked():
+ module.fail_json(
+ msg="zone has acquired lock on the switch for vsan "
+ + str(vsan)
+ + ". Hence cannot procced.",
+ )
+
+ # Process zone default zone options
+ if op_default_zone is not None:
+ if op_default_zone != sw_default_zone:
+ if op_default_zone == "permit":
+ commands_executed.append("zone default-zone permit vsan " + str(vsan))
+ messages.append(
+ "default zone configuration changed from deny to permit for vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append("no zone default-zone permit vsan " + str(vsan))
+ messages.append(
+ "default zone configuration changed from permit to deny for vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "default zone is already "
+ + op_default_zone
+ + " ,no change in default zone configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone mode options
+ if op_mode is not None:
+ if op_mode != sw_mode:
+ if op_mode == "enhanced":
+ commands_executed.append("zone mode enhanced vsan " + str(vsan))
+ messages.append(
+ "zone mode configuration changed from basic to enhanced for vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append("no zone mode enhanced vsan " + str(vsan))
+ messages.append(
+ "zone mode configuration changed from enhanced to basic for vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "zone mode is already "
+ + op_mode
+ + " ,no change in zone mode configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone smart-zone options
+ if op_smart_zoning is not None:
+ if op_smart_zoning != sw_smart_zoning_bool:
+ if op_smart_zoning:
+ commands_executed.append("zone smart-zoning enable vsan " + str(vsan))
+ messages.append("smart-zoning enabled for vsan " + str(vsan))
+ else:
+ commands_executed.append("no zone smart-zoning enable vsan " + str(vsan))
+ messages.append("smart-zoning disabled for vsan " + str(vsan))
+ else:
+ messages.append(
+ "smart-zoning is already set to "
+ + sw_smart_zoning
+ + " , no change in smart-zoning configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone member options
+ # TODO: Obviously this needs to be cleaned up properly, as there are a lot of ifelse statements which is bad
+ # Will take it up later becoz of time constraints
+ if op_zone is not None:
+ shZoneObj = ShowZone(module, vsan)
+ for eachzone in op_zone:
+ zname = eachzone["name"]
+ zmembers = eachzone["members"]
+ removeflag = eachzone["remove"]
+ if removeflag:
+ if shZoneObj.isZonePresent(zname):
+ messages.append("zone '" + zname + "' is removed from vsan " + str(vsan))
+ commands_executed.append("no zone name " + zname + " vsan " + str(vsan))
+ else:
+ messages.append(
+ "zone '"
+ + zname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " , so nothing to remove",
+ )
+ else:
+ if zmembers is None:
+ if shZoneObj.isZonePresent(zname):
+ messages.append(
+ "zone '" + zname + "' is already present in vsan " + str(vsan),
+ )
+ else:
+ commands_executed.append("zone name " + zname + " vsan " + str(vsan))
+ messages.append("zone '" + zname + "' is created in vsan " + str(vsan))
+ else:
+ cmdmemlist = []
+ for eachmem in zmembers:
+ memtype = getMemType(supported_choices, eachmem.keys())
+ cmd = memtype.replace("_", "-") + " " + eachmem[memtype]
+ if op_smart_zoning or sw_smart_zoning_bool:
+ if eachmem["devtype"] is not None:
+ cmd = cmd + " " + eachmem["devtype"]
+ if eachmem["remove"]:
+ if shZoneObj.isZonePresent(zname):
+ if shZoneObj.isZoneMemberPresent(zname, cmd):
+ cmd = "no member " + cmd
+ cmdmemlist.append(cmd)
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "removing zone member '"
+ + eachmem[memtype]
+ + " of device type '"
+ + eachmem["devtype"]
+ + "' from zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "removing zone member '"
+ + eachmem[memtype]
+ + "' from zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' is not present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to remove",
+ )
+ else:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' is not present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to remove",
+ )
+ else:
+ messages.append(
+ "zone '"
+ + zname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " , hence cannot remove the members",
+ )
+
+ else:
+ if shZoneObj.isZoneMemberPresent(zname, cmd):
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' is already present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to add",
+ )
+ else:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' is already present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to add",
+ )
+ else:
+ cmd = "member " + cmd
+ cmdmemlist.append(cmd)
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "adding zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' to zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "adding zone member '"
+ + eachmem[memtype]
+ + "' to zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ if len(cmdmemlist) != 0:
+ commands_executed.append("zone name " + zname + " vsan " + str(vsan))
+ commands_executed = commands_executed + cmdmemlist
+
+ # Process zoneset member options
+ if op_zoneset is not None:
+ dactcmd = []
+ actcmd = []
+ shZonesetObj = ShowZoneset(module, vsan)
+ shZonesetActiveObj = ShowZonesetActive(module, vsan)
+ for eachzoneset in op_zoneset:
+ zsetname = eachzoneset["name"]
+ zsetmembers = eachzoneset["members"]
+ removeflag = eachzoneset["remove"]
+ actionflag = eachzoneset["action"]
+ if removeflag:
+ if shZonesetObj.isZonesetPresent(zsetname):
+ messages.append(
+ "zoneset '" + zsetname + "' is removed from vsan " + str(vsan),
+ )
+ commands_executed.append(
+ "no zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to remove",
+ )
+ else:
+ if zsetmembers is not None:
+ cmdmemlist = []
+ for eachzsmem in zsetmembers:
+ zsetmem_name = eachzsmem["name"]
+ zsetmem_removeflag = eachzsmem["remove"]
+ if zsetmem_removeflag:
+ if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name):
+ cmd = "no member " + zsetmem_name
+ cmdmemlist.append(cmd)
+ messages.append(
+ "removing zoneset member '"
+ + zsetmem_name
+ + "' from zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset member '"
+ + zsetmem_name
+ + "' is not present in zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to remove",
+ )
+ else:
+ if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name):
+ messages.append(
+ "zoneset member '"
+ + zsetmem_name
+ + "' is already present in zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to add",
+ )
+ else:
+ cmd = "member " + zsetmem_name
+ cmdmemlist.append(cmd)
+ messages.append(
+ "adding zoneset member '"
+ + zsetmem_name
+ + "' to zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan),
+ )
+ if len(cmdmemlist) != 0:
+ commands_executed.append(
+ "zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ commands_executed = commands_executed + cmdmemlist
+ else:
+ if shZonesetObj.isZonesetPresent(zsetname):
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' is already present in vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append(
+ "zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ messages.append(
+ "zoneset '" + zsetname + "' is created in vsan " + str(vsan),
+ )
+
+ # Process zoneset activate options
+ if actionflag == "deactivate":
+ if shZonesetActiveObj.isZonesetActive(zsetname):
+ messages.append(
+ "deactivating zoneset '" + zsetname + "' in vsan " + str(vsan),
+ )
+ dactcmd.append(
+ "no zoneset activate name " + zsetname + " vsan " + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " is not activated, hence cannot deactivate",
+ )
+ elif actionflag == "activate":
+ if commands_executed:
+ messages.append(
+ "activating zoneset '" + zsetname + "' in vsan " + str(vsan),
+ )
+ actcmd.append("zoneset activate name " + zsetname + " vsan " + str(vsan))
+ else:
+ messages.append(
+ "no changes to existing zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " hence activate action is ignored",
+ )
+ commands_executed = commands_executed + dactcmd + actcmd
+
+ if commands_executed:
+ if op_mode == "enhanced":
+ commands_executed.append("zone commit vsan " + str(vsan))
+ elif op_mode is None:
+ if sw_mode == "enhanced":
+ commands_executed.append("zone commit vsan " + str(vsan))
+
+ if commands_executed:
+ commands_executed = ["terminal dont-ask"] + commands_executed + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands_executed)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=cmds,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+ else:
+ result["changed"] = True
+ commands = commands + cmds
+ load_config(module, cmds)
+
+ result["messages"] = messages
+ result["commands"] = commands_executed
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/__init__.py b/ansible_collections/cisco/nxos/plugins/modules/storage/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/storage/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_devicealias.py b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_devicealias.py
new file mode 100644
index 00000000..71d4ebb6
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_devicealias.py
@@ -0,0 +1,550 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_devicealias
+short_description: Configuration of device alias for Cisco NXOS MDS Switches.
+description:
+- Configuration of device alias for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ distribute:
+ description:
+ - Enable/Disable device-alias distribution
+ type: bool
+ mode:
+ description:
+ - Mode of devices-alias, basic or enhanced
+ choices:
+ - basic
+ - enhanced
+ type: str
+ da:
+ description:
+ - List of device-alias to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - Name of the device-alias to be added or removed
+ required: true
+ type: str
+ pwwn:
+ description:
+ - pwwn to which the name needs to be associated with
+ type: str
+ remove:
+ description:
+ - Removes the device-alias if set to True
+ type: bool
+ default: false
+ rename:
+ description:
+ - List of device-alias to be renamed
+ type: list
+ elements: dict
+ suboptions:
+ old_name:
+ description:
+ - Old name of the device-alias that needs to be renamed
+ required: true
+ type: str
+ new_name:
+ description:
+ - New name of the device-alias
+ required: true
+ type: str
+"""
+
+EXAMPLES = """
+- name: Test that device alias module works
+ cisco.nxos.nxos_devicealias:
+ da:
+ - name: test1_add
+ pwwn: 56:2:22:11:22:88:11:67
+ - name: test2_add
+ pwwn: 65:22:22:11:22:22:11:d
+ - name: dev1
+ remove: true
+ - name: dev2
+ remove: true
+ distribute: true
+ mode: enhanced
+ rename:
+ - new_name: bcd
+ old_name: abc
+ - new_name: bcd1
+ old_name: abc1
+
+
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - device-alias database
+ - device-alias name somename pwwn 10:00:00:00:89:a1:01:03
+ - device-alias name somename1 pwwn 10:00:00:00:89:a1:02:03
+ - device-alias commit
+ - no terminal dont-ask
+"""
+
+import string
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+VALID_DA_CHARS = ("-", "_", "$", "^")
+
+
+class showDeviceAliasStatus(object):
+ """docstring for showDeviceAliasStatus"""
+
+ def __init__(self, module):
+ self.module = module
+ self.distribute = ""
+ self.mode = ""
+ self.locked = False
+ self.update()
+
+ def execute_show_cmd(self, cmd):
+ output = execute_show_command(cmd, self.module)[0]
+ return output
+
+ def update(self):
+ command = "show device-alias status"
+ output = self.execute_show_cmd(command).split("\n")
+ for o in output:
+ if "Fabric Distribution" in o:
+ self.distribute = o.split(":")[1].strip().lower()
+ if "Mode" in o:
+ self.mode = o.split("Mode:")[1].strip().lower()
+ if "Locked" in o:
+ self.locked = True
+
+ def isLocked(self):
+ return self.locked
+
+ def getDistribute(self):
+ return self.distribute
+
+ def getMode(self):
+ return self.mode
+
+
+class showDeviceAliasDatabase(object):
+ """docstring for showDeviceAliasDatabase"""
+
+ def __init__(self, module):
+ self.module = module
+ self.da_dict = {}
+ self.update()
+
+ def execute_show_cmd(self, cmd):
+ output = execute_show_command(cmd, self.module)[0]
+ return output
+
+ def update(self):
+ command = "show device-alias database"
+ # output = execute_show_command(command, self.module)[0].split("\n")
+ output = self.execute_show_cmd(command)
+ self.da_list = output.split("\n")
+ for eachline in self.da_list:
+ if "device-alias" in eachline:
+ sv = eachline.strip().split()
+ self.da_dict[sv[2]] = sv[4]
+
+ def isNameInDaDatabase(self, name):
+ return name in self.da_dict.keys()
+
+ def isPwwnInDaDatabase(self, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ return newpwwn in self.da_dict.values()
+
+ def isNamePwwnPresentInDatabase(self, name, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ if name in self.da_dict.keys():
+ if newpwwn == self.da_dict[name]:
+ return True
+ return False
+
+ def getPwwnByName(self, name):
+ if name in self.da_dict.keys():
+ return self.da_dict[name]
+ else:
+ return None
+
+ def getNameByPwwn(self, pwwn):
+ newpwwn = ":".join(["0" + str(ep) if len(ep) == 1 else ep for ep in pwwn.split(":")])
+ for n, p in self.da_dict.items():
+ if p == newpwwn:
+ return n
+ return None
+
+
+def isPwwnValid(pwwn):
+ pwwnsplit = pwwn.split(":")
+ if len(pwwnsplit) != 8:
+ return False
+ for eachpwwnsplit in pwwnsplit:
+ if len(eachpwwnsplit) > 2 or len(eachpwwnsplit) < 1:
+ return False
+ if not all(c in string.hexdigits for c in eachpwwnsplit):
+ return False
+ return True
+
+
+def isNameValid(name):
+ if not name[0].isalpha():
+ # Illegal first character. Name must start with a letter
+ return False
+ if len(name) > 64:
+ return False
+ for character in name:
+ if not character.isalnum() and character not in VALID_DA_CHARS:
+ return False
+ return True
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ out = run_commands(module, commands)
+ return out
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def main():
+ element_spec = dict(
+ name=dict(required=True, type="str"),
+ pwwn=dict(type="str"),
+ remove=dict(type="bool", default=False),
+ )
+
+ element_spec_rename = dict(
+ old_name=dict(required=True, type="str"),
+ new_name=dict(required=True, type="str"),
+ )
+
+ argument_spec = dict(
+ distribute=dict(type="bool"),
+ mode=dict(type="str", choices=["enhanced", "basic"]),
+ da=dict(type="list", elements="dict", options=element_spec),
+ rename=dict(type="list", elements="dict", options=element_spec_rename),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ messages = list()
+ commands_to_execute = list()
+ result = {"changed": False}
+
+ distribute = module.params["distribute"]
+ mode = module.params["mode"]
+ da = module.params["da"]
+ rename = module.params["rename"]
+
+ # Step 0.0: Validate syntax of name and pwwn
+ # Also validate syntax of rename arguments
+ if da is not None:
+ for eachdict in da:
+ name = eachdict["name"]
+ pwwn = eachdict["pwwn"]
+ remove = eachdict["remove"]
+ if pwwn is not None:
+ pwwn = pwwn.lower()
+ if not remove:
+ if pwwn is None:
+ module.fail_json(
+ msg="This device alias name "
+ + str(name)
+ + " which needs to be added, does not have pwwn specified. Please specify a valid pwwn",
+ )
+ if not isNameValid(name):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(name)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+ if not isPwwnValid(pwwn):
+ module.fail_json(
+ msg="This pwwn is invalid : "
+ + str(pwwn)
+ + ". Please check that its a valid pwwn",
+ )
+ if rename is not None:
+ for eachdict in rename:
+ oldname = eachdict["old_name"]
+ newname = eachdict["new_name"]
+ if not isNameValid(oldname):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(oldname)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+ if not isNameValid(newname):
+ module.fail_json(
+ msg="This pwwn name is invalid : "
+ + str(newname)
+ + ". Note that name cannot be more than 64 alphanumeric chars, "
+ + "it must start with a letter, and can only contain these characters: "
+ + ", ".join(["'{0}'".format(c) for c in VALID_DA_CHARS]),
+ )
+
+ # Step 0.1: Check DA status
+ shDAStausObj = showDeviceAliasStatus(module)
+ d = shDAStausObj.getDistribute()
+ m = shDAStausObj.getMode()
+ if shDAStausObj.isLocked():
+ module.fail_json(msg="device-alias has acquired lock on the switch. Hence cannot procced.")
+
+ # Step 1: Process distribute
+ commands = []
+ if distribute is not None:
+ if distribute:
+ # playbook has distribute as True(enabled)
+ if d == "disabled":
+ # but switch distribute is disabled(false), so set it to
+ # true(enabled)
+ commands.append("device-alias distribute")
+ messages.append("device-alias distribute changed from disabled to enabled")
+ else:
+ messages.append(
+ "device-alias distribute remains unchanged. current distribution mode is enabled",
+ )
+ else:
+ # playbook has distribute as False(disabled)
+ if d == "enabled":
+ # but switch distribute is enabled(true), so set it to
+ # false(disabled)
+ commands.append("no device-alias distribute")
+ messages.append("device-alias distribute changed from enabled to disabled")
+ else:
+ messages.append(
+ "device-alias distribute remains unchanged. current distribution mode is disabled",
+ )
+
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the da_add/da_remove stage
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step 2: Process mode
+ commands = []
+ if mode is not None:
+ if mode == "basic":
+ # playbook has mode as basic
+ if m == "enhanced":
+ # but switch mode is enhanced, so set it to basic
+ commands.append("no device-alias mode enhanced")
+ messages.append("device-alias mode changed from enhanced to basic")
+ else:
+ messages.append("device-alias mode remains unchanged. current mode is basic")
+
+ else:
+ # playbook has mode as enhanced
+ if m == "basic":
+ # but switch mode is basic, so set it to enhanced
+ commands.append("device-alias mode enhanced")
+ messages.append("device-alias mode changed from basic to enhanced")
+ else:
+ messages.append("device-alias mode remains unchanged. current mode is enhanced")
+
+ if commands:
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step 3: Process da
+ commands = []
+ shDADatabaseObj = showDeviceAliasDatabase(module)
+ if da is not None:
+ da_remove_list = []
+ da_add_list = []
+ for eachdict in da:
+ name = eachdict["name"]
+ pwwn = eachdict["pwwn"]
+ remove = eachdict["remove"]
+ if pwwn is not None:
+ pwwn = pwwn.lower()
+ if remove:
+ if shDADatabaseObj.isNameInDaDatabase(name):
+ commands.append("no device-alias name " + name)
+ da_remove_list.append(name)
+ else:
+ messages.append(
+ name
+ + " - This device alias name is not in switch device-alias database, hence cannot be removed.",
+ )
+ else:
+ if shDADatabaseObj.isNamePwwnPresentInDatabase(name, pwwn):
+ messages.append(
+ name
+ + " : "
+ + pwwn
+ + " - This device alias name,pwwn is already in switch device-alias database, hence nothing to configure",
+ )
+ else:
+ if shDADatabaseObj.isNameInDaDatabase(name):
+ module.fail_json(
+ msg=name
+ + " - This device alias name is already present in switch device-alias database but assigned to another pwwn ("
+ + shDADatabaseObj.getPwwnByName(name)
+ + ") hence cannot be added",
+ )
+
+ elif shDADatabaseObj.isPwwnInDaDatabase(pwwn):
+ module.fail_json(
+ msg=pwwn
+ + " - This device alias pwwn is already present in switch device-alias database but assigned to another name ("
+ + shDADatabaseObj.getNameByPwwn(pwwn)
+ + ") hence cannot be added",
+ )
+
+ else:
+ commands.append("device-alias name " + name + " pwwn " + pwwn)
+ da_add_list.append(name)
+
+ if len(da_add_list) != 0 or len(da_remove_list) != 0:
+ commands = ["device-alias database"] + commands
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+ if len(da_remove_list) != 0:
+ messages.append(
+ "the required device-alias were removed. " + ",".join(da_remove_list),
+ )
+ if len(da_add_list) != 0:
+ messages.append(
+ "the required device-alias were added. " + ",".join(da_add_list),
+ )
+
+ # Step 5: Process rename
+ commands = []
+ if rename is not None:
+ for eachdict in rename:
+ oldname = eachdict["old_name"]
+ newname = eachdict["new_name"]
+ if shDADatabaseObj.isNameInDaDatabase(newname):
+ module.fail_json(
+ changed=False,
+ commands=cmds,
+ msg=newname
+ + " - this name is already present in the device-alias database, hence we cannot rename "
+ + oldname
+ + " with this one",
+ )
+ if shDADatabaseObj.isNameInDaDatabase(oldname):
+ commands.append("device-alias rename " + oldname + " " + newname)
+ else:
+ module.fail_json(
+ changed=False,
+ commands=cmds,
+ msg=oldname
+ + " - this name is not present in the device-alias database, hence we cannot rename.",
+ )
+
+ if len(commands) != 0:
+ commands = ["device-alias database"] + commands
+ if distribute:
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ else:
+ if distribute is None and d == "enabled":
+ commands.append("device-alias commit")
+ commands = ["terminal dont-ask"] + commands + ["no terminal dont-ask"]
+ cmds = flatten_list(commands)
+ if cmds:
+ commands_to_execute = commands_to_execute + cmds
+ if module.check_mode:
+ # Check mode implemented at the end
+ pass
+ else:
+ result["changed"] = True
+ load_config(module, cmds)
+
+ # Step END: check for 'check' mode
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=commands_to_execute,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+
+ result["messages"] = messages
+ result["commands"] = commands_to_execute
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_vsan.py b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_vsan.py
new file mode 100644
index 00000000..d95d95a9
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_vsan.py
@@ -0,0 +1,354 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_vsan
+short_description: Configuration of vsan for Cisco NXOS MDS Switches.
+description:
+- Configuration of vsan for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ vsan:
+ description:
+ - List of vsan details to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ id:
+ description:
+ - Vsan id
+ required: true
+ type: int
+ name:
+ description:
+ - Name of the vsan
+ type: str
+ suspend:
+ description:
+ - suspend the vsan if True
+ type: bool
+ remove:
+ description:
+ - Removes the vsan if True
+ type: bool
+ interface:
+ description:
+ - List of vsan's interfaces to be added
+ type: list
+ elements: str
+"""
+
+EXAMPLES = """
+- name: Test that vsan module works
+ cisco.nxos.nxos_vsan:
+ vsan:
+ - id: 922
+ interface:
+ - fc1/1
+ - fc1/2
+ - port-channel 1
+ name: vsan-SAN-A
+ remove: false
+ suspend: false
+ - id: 923
+ interface:
+ - fc1/11
+ - fc1/21
+ - port-channel 2
+ name: vsan-SAN-B
+ remove: false
+ suspend: true
+ - id: 1923
+ name: vsan-SAN-Old
+ remove: true
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - vsan database
+ - vsan 922 interface fc1/40
+ - vsan 922 interface port-channel 155
+ - no terminal dont-ask
+"""
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+
+class Vsan(object):
+ def __init__(self, vsanid):
+ self.vsanid = vsanid
+ self.vsanname = None
+ self.vsanstate = None
+ self.vsanoperstate = None
+ self.vsaninterfaces = []
+
+
+class GetVsanInfoFromSwitch(object):
+ """docstring for GetVsanInfoFromSwitch"""
+
+ def __init__(self, module):
+ self.module = module
+ self.vsaninfo = {}
+ self.processShowVsan()
+ self.processShowVsanMembership()
+
+ def execute_show_vsan_cmd(self):
+ output = execute_show_command("show vsan", self.module)[0]
+ return output
+
+ def execute_show_vsan_mem_cmd(self):
+ output = execute_show_command("show vsan membership", self.module)[0]
+ return output
+
+ def processShowVsan(self):
+ patv = r"^vsan\s+(\d+)\s+information"
+ patnamestate = "name:(.*)state:(.*)"
+ patoperstate = "operational state:(.*)"
+
+ output = self.execute_show_vsan_cmd().split("\n")
+ for o in output:
+ z = re.match(patv, o.strip())
+ if z:
+ v = z.group(1).strip()
+ self.vsaninfo[v] = Vsan(v)
+
+ z1 = re.match(patnamestate, o.strip())
+ if z1:
+ n = z1.group(1).strip()
+ s = z1.group(2).strip()
+ self.vsaninfo[v].vsanname = n
+ self.vsaninfo[v].vsanstate = s
+
+ z2 = re.match(patoperstate, o.strip())
+ if z2:
+ oper = z2.group(1).strip()
+ self.vsaninfo[v].vsanoperstate = oper
+
+ # 4094/4079 vsan is always present
+ self.vsaninfo["4079"] = Vsan("4079")
+ self.vsaninfo["4094"] = Vsan("4094")
+
+ def processShowVsanMembership(self):
+ patv = r"^vsan\s+(\d+).*"
+ output = self.execute_show_vsan_mem_cmd().split("\n")
+ memlist = []
+ v = None
+ for o in output:
+ z = re.match(patv, o.strip())
+ if z:
+ if v is not None:
+ self.vsaninfo[v].vsaninterfaces = memlist
+ memlist = []
+ v = z.group(1)
+ if "interfaces" not in o:
+ llist = o.strip().split()
+ memlist = memlist + llist
+ self.vsaninfo[v].vsaninterfaces = memlist
+
+ def getVsanInfoObjects(self):
+ return self.vsaninfo
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def main():
+ vsan_element_spec = dict(
+ id=dict(required=True, type="int"),
+ name=dict(type="str"),
+ remove=dict(type="bool"),
+ suspend=dict(type="bool"),
+ interface=dict(type="list", elements="str"),
+ )
+
+ argument_spec = dict(vsan=dict(type="list", elements="dict", options=vsan_element_spec))
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+ warnings = list()
+ messages = list()
+ commands_executed = list()
+ result = {"changed": False}
+
+ obj = GetVsanInfoFromSwitch(module)
+ dictSwVsanObjs = obj.getVsanInfoObjects()
+
+ commands = []
+ vsan_list = module.params["vsan"]
+
+ for eachvsan in vsan_list:
+ vsanid = str(eachvsan["id"])
+ vsanname = eachvsan["name"]
+ vsanremove = eachvsan["remove"]
+ vsansuspend = eachvsan["suspend"]
+ vsaninterface_list = eachvsan["interface"]
+
+ if int(vsanid) < 1 or int(vsanid) >= 4095:
+ module.fail_json(
+ msg=vsanid + " - This is an invalid vsan. Supported vsan range is 1-4094",
+ )
+
+ if vsanid in dictSwVsanObjs.keys():
+ sw_vsanid = vsanid
+ sw_vsanname = dictSwVsanObjs[vsanid].vsanname
+ sw_vsanstate = dictSwVsanObjs[vsanid].vsanstate
+ sw_vsaninterfaces = dictSwVsanObjs[vsanid].vsaninterfaces
+ else:
+ sw_vsanid = None
+ sw_vsanname = None
+ sw_vsanstate = None
+ sw_vsaninterfaces = []
+
+ if vsanremove:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, hence cannot be removed")
+ continue
+ if vsanid == sw_vsanid:
+ commands.append("no vsan " + str(vsanid))
+ messages.append("deleting the vsan " + str(vsanid))
+ else:
+ messages.append(
+ "There is no vsan "
+ + str(vsanid)
+ + " present in the switch. Hence there is nothing to delete",
+ )
+ continue
+ else:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(
+ str(vsanid) + " is a reserved vsan, and always present on the switch",
+ )
+ else:
+ if vsanid == sw_vsanid:
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch. Hence there is nothing to configure",
+ )
+ else:
+ commands.append("vsan " + str(vsanid))
+ messages.append("creating vsan " + str(vsanid))
+
+ if vsanname is not None:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, and cannot be renamed")
+ else:
+ if vsanname == sw_vsanname:
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which has the name "
+ + vsanname
+ + " Hence there is nothing to configure",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " name " + vsanname)
+ messages.append("setting vsan name to " + vsanname + " for vsan " + str(vsanid))
+
+ if vsansuspend:
+ # Negative case:
+ if vsanid == "4079" or vsanid == "4094":
+ messages.append(str(vsanid) + " is a reserved vsan, and cannot be suspended")
+ else:
+ if sw_vsanstate == "suspended":
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which is in suspended state ",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " suspend")
+ messages.append("suspending the vsan " + str(vsanid))
+ else:
+ if sw_vsanstate == "active":
+ messages.append(
+ "There is already a vsan "
+ + str(vsanid)
+ + " present in the switch, which is in active state ",
+ )
+ else:
+ commands.append("no vsan " + str(vsanid) + " suspend")
+ messages.append("no suspending the vsan " + str(vsanid))
+
+ if vsaninterface_list is not None:
+ for each_interface_name in vsaninterface_list:
+ # For fcip,port-channel,vfc-port-channel need to remove the
+ # extra space to compare
+ temp = re.sub(" +", "", each_interface_name)
+ if temp in sw_vsaninterfaces:
+ messages.append(
+ each_interface_name
+ + " is already present in the vsan "
+ + str(vsanid)
+ + " interface list",
+ )
+ else:
+ commands.append("vsan " + str(vsanid) + " interface " + each_interface_name)
+ messages.append(
+ "adding interface " + each_interface_name + " to vsan " + str(vsanid),
+ )
+
+ if len(commands) != 0:
+ commands = ["terminal dont-ask"] + ["vsan database"] + commands + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands)
+ commands_executed = cmds
+
+ if commands_executed:
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=commands_executed,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+ else:
+ result["changed"] = True
+ load_config(module, commands_executed)
+
+ result["messages"] = messages
+ result["commands"] = commands_executed
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_zone_zoneset.py b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_zone_zoneset.py
new file mode 100644
index 00000000..7c9fba30
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/modules/storage/nxos_zone_zoneset.py
@@ -0,0 +1,888 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = """
+module: nxos_zone_zoneset
+short_description: Configuration of zone/zoneset for Cisco NXOS MDS Switches.
+description:
+- Configuration of zone/zoneset for Cisco MDS NXOS.
+version_added: 1.0.0
+author:
+- Suhas Bharadwaj (@srbharadwaj) (subharad@cisco.com)
+notes:
+- Tested against Cisco MDS NX-OS 8.4(1)
+options:
+ zone_zoneset_details:
+ description:
+ - List of zone/zoneset details to be added or removed
+ type: list
+ elements: dict
+ suboptions:
+ vsan:
+ description:
+ - vsan id
+ required: true
+ type: int
+ mode:
+ description:
+ - mode of the zone for the vsan
+ choices:
+ - enhanced
+ - basic
+ type: str
+ default_zone:
+ description:
+ - default zone behaviour for the vsan
+ choices:
+ - permit
+ - deny
+ type: str
+ smart_zoning:
+ description:
+ - Removes the vsan if True
+ type: bool
+ zone:
+ description:
+ - List of zone options for that vsan
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zone
+ required: true
+ type: str
+ remove:
+ description:
+ - Deletes the zone if True
+ type: bool
+ default: false
+ members:
+ description:
+ - Members of the zone that needs to be removed or added
+ type: list
+ elements: dict
+ suboptions:
+ pwwn:
+ description:
+ - pwwn member of the zone, use alias 'device_alias' as option for
+ device_alias member
+ aliases:
+ - device_alias
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes member from the zone if True
+ type: bool
+ default: false
+ devtype:
+ description:
+ - devtype of the zone member used along with Smart zoning config
+ choices:
+ - initiator
+ - target
+ - both
+ type: str
+ zoneset:
+ description:
+ - List of zoneset options for the vsan
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zoneset
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes zoneset if True
+ type: bool
+ default: false
+ action:
+ description:
+ - activates/de-activates the zoneset
+ choices:
+ - activate
+ - deactivate
+ type: str
+ members:
+ description:
+ - Members of the zoneset that needs to be removed or added
+ type: list
+ elements: dict
+ suboptions:
+ name:
+ description:
+ - name of the zone that needs to be added to the zoneset or removed
+ from the zoneset
+ required: true
+ type: str
+ remove:
+ description:
+ - Removes zone member from the zoneset
+ type: bool
+ default: false
+"""
+
+EXAMPLES = """
+- name: Test that zone/zoneset module works
+ cisco.nxos.nxos_zone_zoneset:
+ zone_zoneset_details:
+ - mode: enhanced
+ vsan: 22
+ zone:
+ - members:
+ - pwwn: 11:11:11:11:11:11:11:11
+ - device_alias: test123
+ - pwwn: 61:61:62:62:12:12:12:12
+ remove: true
+ name: zoneA
+ - members:
+ - pwwn: 10:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:21:21:21:21
+ name: zoneB
+ - name: zoneC
+ remove: true
+ zoneset:
+ - action: activate
+ members:
+ - name: zoneA
+ - name: zoneB
+ - name: zoneC
+ remove: true
+ name: zsetname1
+ - action: deactivate
+ name: zsetTestExtra
+ remove: true
+ - mode: basic
+ smart_zoning: true
+ vsan: 21
+ zone:
+ - members:
+ - devtype: both
+ pwwn: 11:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:12:12:12:12
+ - devtype: both
+ pwwn: 92:62:62:62:12:12:1a:1a
+ remove: true
+ name: zone21A
+ - members:
+ - pwwn: 10:11:11:11:11:11:11:11
+ - pwwn: 62:62:62:62:21:21:21:21
+ name: zone21B
+ zoneset:
+ - action: activate
+ members:
+ - name: zone21A
+ - name: zone21B
+ name: zsetname212
+
+"""
+
+RETURN = """
+commands:
+ description: commands sent to the device
+ returned: always
+ type: list
+ sample:
+ - terminal dont-ask
+ - zone name zoneA vsan 923
+ - member pwwn 11:11:11:11:11:11:11:11
+ - no member device-alias test123
+ - zone commit vsan 923
+ - no terminal dont-ask
+messages:
+ description: debug messages
+ returned: always
+ type: list
+ sample:
+ - "zone mode is already enhanced ,no change in zone mode configuration for vsan 922"
+ - "zone member '11:11:11:11:11:11:11:11' is already present in zone 'zoneA' in vsan 922 hence nothing to add"
+ - "zone member 'test123' is already present in zone 'zoneA' in vsan 922 hence nothing to add"
+ - "zone member '61:61:62:62:12:12:12:12' is not present in zone 'zoneA' in vsan 922 hence nothing to remove"
+ - "zone member '10:11:11:11:11:11:11:11' is already present in zone 'zoneB' in vsan 922 hence nothing to add"
+ - "zone member '62:62:62:62:21:21:21:21' is already present in zone 'zoneB' in vsan 922 hence nothing to add"
+ - "zone 'zoneC' is not present in vsan 922 , so nothing to remove"
+"""
+
+
+import re
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ansible_collections.cisco.nxos.plugins.module_utils.network.nxos.nxos import (
+ load_config,
+ run_commands,
+)
+
+
+__metaclass__ = type
+
+
+class ShowZonesetActive(object):
+ """docstring for ShowZonesetActive"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.activeZSName = None
+ self.parseCmdOutput()
+
+ def execute_show_zoneset_active_cmd(self):
+ command = "show zoneset active vsan " + str(self.vsan) + " | grep zoneset"
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zoneset_active_cmd().split("\n")
+ if len(output) == 0:
+ return
+ else:
+ for line in output:
+ line = line.strip()
+ mzs = re.match(patZoneset, line.strip())
+ if mzs:
+ self.activeZSName = mzs.group(1).strip()
+ return
+
+ def isZonesetActive(self, zsname):
+ if zsname == self.activeZSName:
+ return True
+ return False
+
+
+class ShowZoneset(object):
+ """docstring for ShowZoneset"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.zsDetails = {}
+ self.parseCmdOutput()
+
+ def execute_show_zoneset_cmd(self):
+ command = "show zoneset vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZoneset = r"zoneset name (\S+) vsan " + str(self.vsan)
+ patZone = r"zone name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zoneset_cmd().split("\n")
+ for line in output:
+ line = line.strip()
+ mzs = re.match(patZoneset, line.strip())
+ mz = re.match(patZone, line.strip())
+ if mzs:
+ zonesetname = mzs.group(1).strip()
+ self.zsDetails[zonesetname] = []
+ continue
+ elif mz:
+ zonename = mz.group(1).strip()
+ v = self.zsDetails[zonesetname]
+ v.append(zonename)
+ self.zsDetails[zonesetname] = v
+
+ def isZonesetPresent(self, zsname):
+ return zsname in self.zsDetails.keys()
+
+ def isZonePresentInZoneset(self, zsname, zname):
+ if zsname in self.zsDetails.keys():
+ return zname in self.zsDetails[zsname]
+ return False
+
+
+class ShowZone(object):
+ """docstring for ShowZone"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.module = module
+ self.zDetails = {}
+ self.parseCmdOutput()
+
+ def execute_show_zone_vsan_cmd(self):
+ command = "show zone vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def parseCmdOutput(self):
+ patZone = r"zone name (\S+) vsan " + str(self.vsan)
+ output = self.execute_show_zone_vsan_cmd().split("\n")
+ for line in output:
+ line = re.sub(r"[\[].*?[\]]", "", line)
+ line = " ".join(line.strip().split())
+ if "init" in line:
+ line = line.replace("init", "initiator")
+ m = re.match(patZone, line)
+ if m:
+ zonename = m.group(1).strip()
+ self.zDetails[zonename] = []
+ continue
+ else:
+ # For now we support only pwwn and device-alias under zone
+ # Ideally should use 'supported_choices'....but maybe next
+ # time.
+ if "pwwn" in line or "device-alias" in line:
+ v = self.zDetails[zonename]
+ v.append(line)
+ self.zDetails[zonename] = v
+
+ def isZonePresent(self, zname):
+ return zname in self.zDetails.keys()
+
+ def isZoneMemberPresent(self, zname, cmd):
+ if zname in self.zDetails.keys():
+ zonememlist = self.zDetails[zname]
+ for eachline in zonememlist:
+ if cmd in eachline:
+ return True
+ return False
+
+ def get_zDetails(self):
+ return self.zDetails
+
+
+class ShowZoneStatus(object):
+ """docstring for ShowZoneStatus"""
+
+ def __init__(self, module, vsan):
+ self.vsan = vsan
+ self.vsanAbsent = False
+ self.module = module
+ self.default_zone = ""
+ self.mode = ""
+ self.session = ""
+ self.sz = ""
+ self.locked = False
+ self.update()
+
+ def execute_show_zone_status_cmd(self):
+ command = "show zone status vsan " + str(self.vsan)
+ output = execute_show_command(command, self.module)[0]
+ return output
+
+ def update(self):
+ output = self.execute_show_zone_status_cmd().split("\n")
+
+ patfordefzone = "VSAN: " + str(self.vsan) + r" default-zone:\s+(\S+).*"
+ patformode = r".*mode:\s+(\S+).*"
+ patforsession = r"^session:\s+(\S+).*"
+ patforsz = r".*smart-zoning:\s+(\S+).*"
+ for line in output:
+ if "is not configured" in line:
+ self.vsanAbsent = True
+ break
+ mdefz = re.match(patfordefzone, line.strip())
+ mmode = re.match(patformode, line.strip())
+ msession = re.match(patforsession, line.strip())
+ msz = re.match(patforsz, line.strip())
+
+ if mdefz:
+ self.default_zone = mdefz.group(1)
+ if mmode:
+ self.mode = mmode.group(1)
+ if msession:
+ self.session = msession.group(1)
+ if self.session != "none":
+ self.locked = True
+ if msz:
+ self.sz = msz.group(1)
+
+ def isLocked(self):
+ return self.locked
+
+ def getDefaultZone(self):
+ return self.default_zone
+
+ def getMode(self):
+ return self.mode
+
+ def getSmartZoningStatus(self):
+ return self.sz
+
+ def isVsanAbsent(self):
+ return self.vsanAbsent
+
+
+def execute_show_command(command, module, command_type="cli_show"):
+ output = "text"
+ commands = [{"command": command, "output": output}]
+ return run_commands(module, commands)
+
+
+def flatten_list(command_lists):
+ flat_command_list = []
+ for command in command_lists:
+ if isinstance(command, list):
+ flat_command_list.extend(command)
+ else:
+ flat_command_list.append(command)
+ return flat_command_list
+
+
+def getMemType(supported_choices, allmemkeys, default="pwwn"):
+ for eachchoice in supported_choices:
+ if eachchoice in allmemkeys:
+ return eachchoice
+ return default
+
+
+def main():
+ supported_choices = ["device_alias"]
+ zone_member_spec = dict(
+ pwwn=dict(required=True, type="str", aliases=["device_alias"]),
+ devtype=dict(type="str", choices=["initiator", "target", "both"]),
+ remove=dict(type="bool", default=False),
+ )
+
+ zone_spec = dict(
+ name=dict(required=True, type="str"),
+ members=dict(type="list", elements="dict", options=zone_member_spec),
+ remove=dict(type="bool", default=False),
+ )
+
+ zoneset_member_spec = dict(
+ name=dict(required=True, type="str"),
+ remove=dict(type="bool", default=False),
+ )
+
+ zoneset_spec = dict(
+ name=dict(type="str", required=True),
+ members=dict(type="list", elements="dict", options=zoneset_member_spec),
+ remove=dict(type="bool", default=False),
+ action=dict(type="str", choices=["activate", "deactivate"]),
+ )
+
+ zonedetails_spec = dict(
+ vsan=dict(required=True, type="int"),
+ mode=dict(type="str", choices=["enhanced", "basic"]),
+ default_zone=dict(type="str", choices=["permit", "deny"]),
+ smart_zoning=dict(type="bool"),
+ zone=dict(type="list", elements="dict", options=zone_spec),
+ zoneset=dict(type="list", elements="dict", options=zoneset_spec),
+ )
+
+ argument_spec = dict(
+ zone_zoneset_details=dict(type="list", elements="dict", options=zonedetails_spec),
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)
+
+ warnings = list()
+ messages = list()
+ commands = list()
+ result = {"changed": False}
+
+ commands_executed = []
+ listOfZoneDetails = module.params["zone_zoneset_details"]
+ for eachZoneZonesetDetail in listOfZoneDetails:
+ vsan = eachZoneZonesetDetail["vsan"]
+ op_mode = eachZoneZonesetDetail["mode"]
+ op_default_zone = eachZoneZonesetDetail["default_zone"]
+ op_smart_zoning = eachZoneZonesetDetail["smart_zoning"]
+ op_zone = eachZoneZonesetDetail["zone"]
+ op_zoneset = eachZoneZonesetDetail["zoneset"]
+
+ # Step1: execute show zone status and get
+ shZoneStatusObj = ShowZoneStatus(module, vsan)
+ sw_default_zone = shZoneStatusObj.getDefaultZone()
+ sw_mode = shZoneStatusObj.getMode()
+ sw_smart_zoning = shZoneStatusObj.getSmartZoningStatus()
+
+ if sw_smart_zoning.lower() == "Enabled".lower():
+ sw_smart_zoning_bool = True
+ else:
+ sw_smart_zoning_bool = False
+
+ if shZoneStatusObj.isVsanAbsent():
+ module.fail_json(
+ msg="Vsan " + str(vsan) + " is not present in the switch. Hence cannot procced.",
+ )
+
+ if shZoneStatusObj.isLocked():
+ module.fail_json(
+ msg="zone has acquired lock on the switch for vsan "
+ + str(vsan)
+ + ". Hence cannot procced.",
+ )
+
+ # Process zone default zone options
+ if op_default_zone is not None:
+ if op_default_zone != sw_default_zone:
+ if op_default_zone == "permit":
+ commands_executed.append("zone default-zone permit vsan " + str(vsan))
+ messages.append(
+ "default zone configuration changed from deny to permit for vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append("no zone default-zone permit vsan " + str(vsan))
+ messages.append(
+ "default zone configuration changed from permit to deny for vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "default zone is already "
+ + op_default_zone
+ + " ,no change in default zone configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone mode options
+ if op_mode is not None:
+ if op_mode != sw_mode:
+ if op_mode == "enhanced":
+ commands_executed.append("zone mode enhanced vsan " + str(vsan))
+ messages.append(
+ "zone mode configuration changed from basic to enhanced for vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append("no zone mode enhanced vsan " + str(vsan))
+ messages.append(
+ "zone mode configuration changed from enhanced to basic for vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "zone mode is already "
+ + op_mode
+ + " ,no change in zone mode configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone smart-zone options
+ if op_smart_zoning is not None:
+ if op_smart_zoning != sw_smart_zoning_bool:
+ if op_smart_zoning:
+ commands_executed.append("zone smart-zoning enable vsan " + str(vsan))
+ messages.append("smart-zoning enabled for vsan " + str(vsan))
+ else:
+ commands_executed.append("no zone smart-zoning enable vsan " + str(vsan))
+ messages.append("smart-zoning disabled for vsan " + str(vsan))
+ else:
+ messages.append(
+ "smart-zoning is already set to "
+ + sw_smart_zoning
+ + " , no change in smart-zoning configuration for vsan "
+ + str(vsan),
+ )
+
+ # Process zone member options
+ # TODO: Obviously this needs to be cleaned up properly, as there are a lot of ifelse statements which is bad
+ # Will take it up later becoz of time constraints
+ if op_zone is not None:
+ shZoneObj = ShowZone(module, vsan)
+ for eachzone in op_zone:
+ zname = eachzone["name"]
+ zmembers = eachzone["members"]
+ removeflag = eachzone["remove"]
+ if removeflag:
+ if shZoneObj.isZonePresent(zname):
+ messages.append("zone '" + zname + "' is removed from vsan " + str(vsan))
+ commands_executed.append("no zone name " + zname + " vsan " + str(vsan))
+ else:
+ messages.append(
+ "zone '"
+ + zname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " , so nothing to remove",
+ )
+ else:
+ if zmembers is None:
+ if shZoneObj.isZonePresent(zname):
+ messages.append(
+ "zone '" + zname + "' is already present in vsan " + str(vsan),
+ )
+ else:
+ commands_executed.append("zone name " + zname + " vsan " + str(vsan))
+ messages.append("zone '" + zname + "' is created in vsan " + str(vsan))
+ else:
+ cmdmemlist = []
+ for eachmem in zmembers:
+ memtype = getMemType(supported_choices, eachmem.keys())
+ cmd = memtype.replace("_", "-") + " " + eachmem[memtype]
+ if op_smart_zoning or sw_smart_zoning_bool:
+ if eachmem["devtype"] is not None:
+ cmd = cmd + " " + eachmem["devtype"]
+ if eachmem["remove"]:
+ if shZoneObj.isZonePresent(zname):
+ if shZoneObj.isZoneMemberPresent(zname, cmd):
+ cmd = "no member " + cmd
+ cmdmemlist.append(cmd)
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "removing zone member '"
+ + eachmem[memtype]
+ + " of device type '"
+ + eachmem["devtype"]
+ + "' from zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "removing zone member '"
+ + eachmem[memtype]
+ + "' from zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' is not present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to remove",
+ )
+ else:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' is not present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to remove",
+ )
+ else:
+ messages.append(
+ "zone '"
+ + zname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " , hence cannot remove the members",
+ )
+
+ else:
+ if shZoneObj.isZoneMemberPresent(zname, cmd):
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' is already present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to add",
+ )
+ else:
+ messages.append(
+ "zone member '"
+ + eachmem[memtype]
+ + "' is already present in zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan)
+ + " hence nothing to add",
+ )
+ else:
+ cmd = "member " + cmd
+ cmdmemlist.append(cmd)
+ if op_smart_zoning and eachmem["devtype"] is not None:
+ messages.append(
+ "adding zone member '"
+ + eachmem[memtype]
+ + "' of device type '"
+ + eachmem["devtype"]
+ + "' to zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "adding zone member '"
+ + eachmem[memtype]
+ + "' to zone '"
+ + zname
+ + "' in vsan "
+ + str(vsan),
+ )
+ if len(cmdmemlist) != 0:
+ commands_executed.append("zone name " + zname + " vsan " + str(vsan))
+ commands_executed = commands_executed + cmdmemlist
+
+ # Process zoneset member options
+ if op_zoneset is not None:
+ dactcmd = []
+ actcmd = []
+ shZonesetObj = ShowZoneset(module, vsan)
+ shZonesetActiveObj = ShowZonesetActive(module, vsan)
+ for eachzoneset in op_zoneset:
+ zsetname = eachzoneset["name"]
+ zsetmembers = eachzoneset["members"]
+ removeflag = eachzoneset["remove"]
+ actionflag = eachzoneset["action"]
+ if removeflag:
+ if shZonesetObj.isZonesetPresent(zsetname):
+ messages.append(
+ "zoneset '" + zsetname + "' is removed from vsan " + str(vsan),
+ )
+ commands_executed.append(
+ "no zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' is not present in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to remove",
+ )
+ else:
+ if zsetmembers is not None:
+ cmdmemlist = []
+ for eachzsmem in zsetmembers:
+ zsetmem_name = eachzsmem["name"]
+ zsetmem_removeflag = eachzsmem["remove"]
+ if zsetmem_removeflag:
+ if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name):
+ cmd = "no member " + zsetmem_name
+ cmdmemlist.append(cmd)
+ messages.append(
+ "removing zoneset member '"
+ + zsetmem_name
+ + "' from zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset member '"
+ + zsetmem_name
+ + "' is not present in zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to remove",
+ )
+ else:
+ if shZonesetObj.isZonePresentInZoneset(zsetname, zsetmem_name):
+ messages.append(
+ "zoneset member '"
+ + zsetmem_name
+ + "' is already present in zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " ,hence there is nothing to add",
+ )
+ else:
+ cmd = "member " + zsetmem_name
+ cmdmemlist.append(cmd)
+ messages.append(
+ "adding zoneset member '"
+ + zsetmem_name
+ + "' to zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan),
+ )
+ if len(cmdmemlist) != 0:
+ commands_executed.append(
+ "zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ commands_executed = commands_executed + cmdmemlist
+ else:
+ if shZonesetObj.isZonesetPresent(zsetname):
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' is already present in vsan "
+ + str(vsan),
+ )
+ else:
+ commands_executed.append(
+ "zoneset name " + zsetname + " vsan " + str(vsan),
+ )
+ messages.append(
+ "zoneset '" + zsetname + "' is created in vsan " + str(vsan),
+ )
+
+ # Process zoneset activate options
+ if actionflag == "deactivate":
+ if shZonesetActiveObj.isZonesetActive(zsetname):
+ messages.append(
+ "deactivating zoneset '" + zsetname + "' in vsan " + str(vsan),
+ )
+ dactcmd.append(
+ "no zoneset activate name " + zsetname + " vsan " + str(vsan),
+ )
+ else:
+ messages.append(
+ "zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " is not activated, hence cannot deactivate",
+ )
+ elif actionflag == "activate":
+ if commands_executed:
+ messages.append(
+ "activating zoneset '" + zsetname + "' in vsan " + str(vsan),
+ )
+ actcmd.append("zoneset activate name " + zsetname + " vsan " + str(vsan))
+ else:
+ messages.append(
+ "no changes to existing zoneset '"
+ + zsetname
+ + "' in vsan "
+ + str(vsan)
+ + " hence activate action is ignored",
+ )
+ commands_executed = commands_executed + dactcmd + actcmd
+
+ if commands_executed:
+ if op_mode == "enhanced":
+ commands_executed.append("zone commit vsan " + str(vsan))
+ elif op_mode is None:
+ if sw_mode == "enhanced":
+ commands_executed.append("zone commit vsan " + str(vsan))
+
+ if commands_executed:
+ commands_executed = ["terminal dont-ask"] + commands_executed + ["no terminal dont-ask"]
+
+ cmds = flatten_list(commands_executed)
+ if cmds:
+ if module.check_mode:
+ module.exit_json(
+ changed=False,
+ commands=cmds,
+ msg="Check Mode: No cmds issued to the hosts",
+ )
+ else:
+ result["changed"] = True
+ commands = commands + cmds
+ load_config(module, cmds)
+
+ result["messages"] = messages
+ result["commands"] = commands_executed
+ result["warnings"] = warnings
+ module.exit_json(**result)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/cisco/nxos/plugins/netconf/__init__.py b/ansible_collections/cisco/nxos/plugins/netconf/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/netconf/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/netconf/nxos.py b/ansible_collections/cisco/nxos/plugins/netconf/nxos.py
new file mode 100644
index 00000000..fcbee795
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/netconf/nxos.py
@@ -0,0 +1,46 @@
+#
+# (c) 2021 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+DOCUMENTATION = """
+author: Ansible Networking Team (@ansible-network)
+name: nxos
+short_description: Use nxos netconf plugin to run netconf commands on Cisco NX-OS platform.
+description:
+- This nxos plugin provides low level abstraction apis for sending and receiving
+ netconf commands from Cisco NX-OS network devices.
+version_added: 2.3.0
+options:
+ ncclient_device_handler:
+ type: str
+ default: nexus
+ description:
+ - Specifies the ncclient device handler name for Cisco NX-OS network os. To
+ identify the ncclient device handler name refer ncclient library documentation.
+"""
+
+from ansible.plugins.netconf import NetconfBase
+from ansible_collections.ansible.netcommon.plugins.plugin_utils.netconf_base import NetconfBase
+
+
+class Netconf(NetconfBase):
+ pass
diff --git a/ansible_collections/cisco/nxos/plugins/terminal/__init__.py b/ansible_collections/cisco/nxos/plugins/terminal/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/terminal/__init__.py
diff --git a/ansible_collections/cisco/nxos/plugins/terminal/nxos.py b/ansible_collections/cisco/nxos/plugins/terminal/nxos.py
new file mode 100644
index 00000000..ff0ada40
--- /dev/null
+++ b/ansible_collections/cisco/nxos/plugins/terminal/nxos.py
@@ -0,0 +1,139 @@
+#
+# (c) 2016 Red Hat Inc.
+#
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+#
+from __future__ import absolute_import, division, print_function
+
+
+__metaclass__ = type
+
+import json
+import re
+
+from ansible.errors import AnsibleConnectionFailure
+from ansible.module_utils._text import to_bytes, to_text
+from ansible_collections.ansible.netcommon.plugins.plugin_utils.terminal_base import TerminalBase
+
+
+class TerminalModule(TerminalBase):
+ terminal_stdout_re = [
+ re.compile(
+ rb"[\r\n](?!\s*<)?(\x1b\S+)*[a-zA-Z_0-9]{1}[a-zA-Z0-9-_.]*[>|#](?:\s*)(\x1b\S+)*$",
+ ),
+ re.compile(rb"[\r\n]?[a-zA-Z0-9]{1}[a-zA-Z0-9-_.]*\(.+\)#(?:\s*)$"),
+ ]
+
+ terminal_stderr_re = [
+ re.compile(rb"% ?Error"),
+ re.compile(rb"\nerror:(.*)", re.I),
+ re.compile(rb"^% \w+", re.M),
+ re.compile(rb"% ?Bad secret"),
+ re.compile(rb"invalid input", re.I),
+ re.compile(rb"(?:incomplete|ambiguous) command", re.I),
+ re.compile(rb"connection timed out", re.I),
+ re.compile(rb"[^\r\n] not found", re.I),
+ re.compile(rb"'[^']' +returned error code: ?\d+"),
+ re.compile(rb"syntax error"),
+ re.compile(rb"unknown command"),
+ re.compile(rb"user not present"),
+ re.compile(rb"invalid (.+?)at '\^' marker", re.I),
+ re.compile(rb"configuration not allowed .+ at '\^' marker"),
+ re.compile(
+ rb"[B|b]aud rate of console should be.* (\d*) to increase [a-z]* level",
+ re.I,
+ ),
+ re.compile(rb"cannot apply non-existing acl policy to interface", re.I),
+ re.compile(rb"Duplicate sequence number", re.I),
+ re.compile(
+ rb"Cannot apply ACL to an interface that is a port-channel member",
+ re.I,
+ ),
+ re.compile(rb"No corresponding (.+) configured", re.I),
+ re.compile(rb"(.+)please specify sequence number", re.I),
+ ]
+
+ terminal_config_prompt = re.compile(r"^.*\((?!maint-mode).*\)#$")
+
+ def on_become(self, passwd=None):
+ if self._get_prompt().strip().endswith(b"enable#"):
+ return
+
+ out = self._exec_cli_command("show privilege")
+ out = to_text(out, errors="surrogate_then_replace").strip()
+
+ # if already at privilege level 15 return
+ if "15" in out:
+ return
+
+ if self.validate_user_role():
+ return
+
+ if "Disabled" in out:
+ raise AnsibleConnectionFailure("Feature privilege is not enabled")
+
+ cmd = {"command": "enable"}
+ if passwd:
+ cmd["prompt"] = to_text(r"(?i)[\r\n]?Password: $", errors="surrogate_or_strict")
+ cmd["answer"] = passwd
+ cmd["prompt_retry_check"] = True
+
+ try:
+ self._exec_cli_command(to_bytes(json.dumps(cmd), errors="surrogate_or_strict"))
+ prompt = self._get_prompt()
+ if prompt is None or not prompt.strip().endswith(b"enable#"):
+ raise AnsibleConnectionFailure(
+ "failed to elevate privilege to enable mode still at prompt [%s]" % prompt,
+ )
+ except AnsibleConnectionFailure as e:
+ prompt = self._get_prompt()
+ raise AnsibleConnectionFailure(
+ "unable to elevate privilege to enable mode, at prompt [%s] with error: %s"
+ % (prompt, e.message),
+ )
+
+ def on_unbecome(self):
+ prompt = self._get_prompt()
+ if prompt is None:
+ # if prompt is None most likely the terminal is hung up at a prompt
+ return
+
+ if b"(config" in prompt:
+ self._exec_cli_command("end")
+ self._exec_cli_command("exit")
+
+ elif prompt.endswith(b"enable#"):
+ self._exec_cli_command("exit")
+
+ def on_open_shell(self):
+ try:
+ for cmd in ("terminal length 0", "terminal width 511"):
+ self._exec_cli_command(cmd)
+ except AnsibleConnectionFailure:
+ raise AnsibleConnectionFailure("unable to set terminal parameters")
+
+ def validate_user_role(self):
+ user = self._connection._play_context.remote_user
+
+ out = self._exec_cli_command("show user-account %s" % user)
+ out = to_text(out, errors="surrogate_then_replace").strip()
+
+ match = re.search(r"roles:(.+)$", out, re.M)
+ if match:
+ roles = match.group(1).split()
+ if "network-admin" in roles:
+ return True
+ return False