From 975f66f2eebe9dadba04f275774d4ab83f74cf25 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sat, 13 Apr 2024 14:04:41 +0200 Subject: Adding upstream version 7.7.0+dfsg. Signed-off-by: Daniel Baumann --- .../arista/eos/plugins/action/__init__.py | 0 .../arista/eos/plugins/action/acl_interfaces.py | 58 + .../arista/eos/plugins/action/acls.py | 58 + .../arista/eos/plugins/action/banner.py | 58 + .../arista/eos/plugins/action/bgp.py | 58 + .../eos/plugins/action/bgp_address_family.py | 58 + .../arista/eos/plugins/action/bgp_global.py | 58 + .../arista/eos/plugins/action/command.py | 58 + .../arista/eos/plugins/action/config.py | 58 + .../arista/eos/plugins/action/eapi.py | 58 + .../arista/eos/plugins/action/eos.py | 58 + .../arista/eos/plugins/action/facts.py | 58 + .../arista/eos/plugins/action/hostname.py | 58 + .../arista/eos/plugins/action/interface.py | 58 + .../arista/eos/plugins/action/interfaces.py | 58 + .../arista/eos/plugins/action/l2_interface.py | 58 + .../arista/eos/plugins/action/l2_interfaces.py | 58 + .../arista/eos/plugins/action/l3_interface.py | 58 + .../arista/eos/plugins/action/l3_interfaces.py | 58 + .../arista/eos/plugins/action/lacp.py | 58 + .../arista/eos/plugins/action/lacp_interfaces.py | 58 + .../arista/eos/plugins/action/lag_interfaces.py | 58 + .../arista/eos/plugins/action/linkagg.py | 58 + .../arista/eos/plugins/action/lldp.py | 58 + .../arista/eos/plugins/action/lldp_global.py | 58 + .../arista/eos/plugins/action/lldp_interfaces.py | 58 + .../arista/eos/plugins/action/logging.py | 58 + .../arista/eos/plugins/action/logging_global.py | 58 + .../arista/eos/plugins/action/ntp_global.py | 58 + .../arista/eos/plugins/action/ospf_interfaces.py | 58 + .../arista/eos/plugins/action/ospfv2.py | 58 + .../arista/eos/plugins/action/ospfv3.py | 58 + .../arista/eos/plugins/action/prefix_lists.py | 58 + .../arista/eos/plugins/action/route_maps.py | 58 + .../arista/eos/plugins/action/snmp_server.py | 58 + .../arista/eos/plugins/action/static_route.py | 58 + .../arista/eos/plugins/action/static_routes.py | 58 + .../arista/eos/plugins/action/system.py | 58 + .../arista/eos/plugins/action/user.py | 58 + .../arista/eos/plugins/action/vlan.py | 58 + .../arista/eos/plugins/action/vlans.py | 58 + .../arista/eos/plugins/action/vrf.py | 58 + .../arista/eos/plugins/cliconf/__init__.py | 0 .../arista/eos/plugins/cliconf/eos.py | 494 ++++ .../arista/eos/plugins/filter/__init__.py | 0 .../arista/eos/plugins/httpapi/__init__.py | 0 .../arista/eos/plugins/httpapi/eos.py | 217 ++ .../arista/eos/plugins/inventory/__init__.py | 0 .../arista/eos/plugins/module_utils/__init__.py | 0 .../eos/plugins/module_utils/network/__init__.py | 0 .../plugins/module_utils/network/eos/__init__.py | 0 .../module_utils/network/eos/argspec/__init__.py | 0 .../network/eos/argspec/acl_interfaces/__init__.py | 0 .../eos/argspec/acl_interfaces/acl_interfaces.py | 85 + .../network/eos/argspec/acls/__init__.py | 0 .../module_utils/network/eos/argspec/acls/acls.py | 416 +++ .../eos/argspec/bgp_address_family/__init__.py | 0 .../bgp_address_family/bgp_address_family.py | 206 ++ .../network/eos/argspec/bgp_global/__init__.py | 0 .../network/eos/argspec/bgp_global/bgp_global.py | 1033 +++++++ .../network/eos/argspec/facts/__init__.py | 0 .../network/eos/argspec/facts/facts.py | 24 + .../network/eos/argspec/hostname/__init__.py | 0 .../network/eos/argspec/hostname/hostname.py | 50 + .../network/eos/argspec/interfaces/__init__.py | 0 .../network/eos/argspec/interfaces/interfaces.py | 72 + .../network/eos/argspec/l2_interfaces/__init__.py | 0 .../eos/argspec/l2_interfaces/l2_interfaces.py | 77 + .../network/eos/argspec/l3_interfaces/__init__.py | 0 .../eos/argspec/l3_interfaces/l3_interfaces.py | 76 + .../network/eos/argspec/lacp/__init__.py | 0 .../module_utils/network/eos/argspec/lacp/lacp.py | 63 + .../eos/argspec/lacp_interfaces/__init__.py | 0 .../eos/argspec/lacp_interfaces/lacp_interfaces.py | 69 + .../network/eos/argspec/lag_interfaces/__init__.py | 0 .../eos/argspec/lag_interfaces/lag_interfaces.py | 73 + .../network/eos/argspec/lldp_global/__init__.py | 0 .../network/eos/argspec/lldp_global/lldp_global.py | 75 + .../eos/argspec/lldp_interfaces/__init__.py | 0 .../eos/argspec/lldp_interfaces/lldp_interfaces.py | 65 + .../network/eos/argspec/logging_global/__init__.py | 0 .../eos/argspec/logging_global/logging_global.py | 253 ++ .../network/eos/argspec/ntp_global/__init__.py | 0 .../network/eos/argspec/ntp_global/ntp_global.py | 123 + .../eos/argspec/ospf_interfaces/__init__.py | 0 .../eos/argspec/ospf_interfaces/ospf_interfaces.py | 180 ++ .../network/eos/argspec/ospfv2/__init__.py | 0 .../network/eos/argspec/ospfv2/ospfv2.py | 340 +++ .../network/eos/argspec/ospfv3/__init__.py | 0 .../network/eos/argspec/ospfv3/ospfv3.py | 532 ++++ .../network/eos/argspec/prefix_lists/__init__.py | 0 .../eos/argspec/prefix_lists/prefix_lists.py | 100 + .../network/eos/argspec/route_maps/__init__.py | 0 .../network/eos/argspec/route_maps/route_maps.py | 367 +++ .../network/eos/argspec/snmp_server/__init__.py | 0 .../network/eos/argspec/snmp_server/snmp_server.py | 389 +++ .../network/eos/argspec/static_routes/__init__.py | 0 .../eos/argspec/static_routes/static_routes.py | 97 + .../network/eos/argspec/vlans/__init__.py | 0 .../network/eos/argspec/vlans/vlans.py | 64 + .../module_utils/network/eos/config/__init__.py | 0 .../network/eos/config/acl_interfaces/__init__.py | 0 .../eos/config/acl_interfaces/acl_interfaces.py | 481 ++++ .../network/eos/config/acls/__init__.py | 0 .../module_utils/network/eos/config/acls/acls.py | 661 +++++ .../eos/config/bgp_address_family/__init__.py | 0 .../bgp_address_family/bgp_address_family.py | 293 ++ .../network/eos/config/bgp_global/__init__.py | 0 .../network/eos/config/bgp_global/bgp_global.py | 422 +++ .../network/eos/config/hostname/__init__.py | 0 .../network/eos/config/hostname/hostname.py | 76 + .../network/eos/config/interfaces/__init__.py | 0 .../network/eos/config/interfaces/interfaces.py | 309 ++ .../network/eos/config/l2_interfaces/__init__.py | 0 .../eos/config/l2_interfaces/l2_interfaces.py | 365 +++ .../network/eos/config/l3_interfaces/__init__.py | 0 .../eos/config/l3_interfaces/l3_interfaces.py | 351 +++ .../network/eos/config/lacp/__init__.py | 0 .../module_utils/network/eos/config/lacp/lacp.py | 201 ++ .../network/eos/config/lacp_interfaces/__init__.py | 0 .../eos/config/lacp_interfaces/lacp_interfaces.py | 262 ++ .../network/eos/config/lag_interfaces/__init__.py | 0 .../eos/config/lag_interfaces/lag_interfaces.py | 275 ++ .../network/eos/config/lldp_global/__init__.py | 0 .../network/eos/config/lldp_global/lldp_global.py | 215 ++ .../network/eos/config/lldp_interfaces/__init__.py | 0 .../eos/config/lldp_interfaces/lldp_interfaces.py | 264 ++ .../network/eos/config/logging_global/__init__.py | 0 .../eos/config/logging_global/logging_global.py | 190 ++ .../network/eos/config/ntp_global/__init__.py | 0 .../network/eos/config/ntp_global/ntp_global.py | 249 ++ .../network/eos/config/ospf_interfaces/__init__.py | 0 .../eos/config/ospf_interfaces/ospf_interfaces.py | 212 ++ .../network/eos/config/ospfv2/__init__.py | 0 .../network/eos/config/ospfv2/ospfv2.py | 840 ++++++ .../network/eos/config/ospfv3/__init__.py | 0 .../network/eos/config/ospfv3/ospfv3.py | 392 +++ .../network/eos/config/prefix_lists/__init__.py | 0 .../eos/config/prefix_lists/prefix_lists.py | 217 ++ .../network/eos/config/route_maps/__init__.py | 0 .../network/eos/config/route_maps/route_maps.py | 349 +++ .../network/eos/config/snmp_server/__init__.py | 0 .../network/eos/config/snmp_server/snmp_server.py | 243 ++ .../network/eos/config/static_routes/__init__.py | 0 .../eos/config/static_routes/static_routes.py | 369 +++ .../network/eos/config/vlans/__init__.py | 0 .../module_utils/network/eos/config/vlans/vlans.py | 262 ++ .../eos/plugins/module_utils/network/eos/eos.py | 568 ++++ .../module_utils/network/eos/facts/__init__.py | 0 .../network/eos/facts/acl_interfaces/__init__.py | 0 .../eos/facts/acl_interfaces/acl_interfaces.py | 149 + .../network/eos/facts/acls/__init__.py | 0 .../module_utils/network/eos/facts/acls/acls.py | 392 +++ .../eos/facts/bgp_address_family/__init__.py | 0 .../facts/bgp_address_family/bgp_address_family.py | 120 + .../network/eos/facts/bgp_global/__init__.py | 0 .../network/eos/facts/bgp_global/bgp_global.py | 136 + .../module_utils/network/eos/facts/facts.py | 160 ++ .../network/eos/facts/hostname/__init__.py | 0 .../network/eos/facts/hostname/hostname.py | 76 + .../network/eos/facts/interfaces/__init__.py | 0 .../network/eos/facts/interfaces/interfaces.py | 116 + .../network/eos/facts/l2_interfaces/__init__.py | 0 .../eos/facts/l2_interfaces/l2_interfaces.py | 120 + .../network/eos/facts/l3_interfaces/__init__.py | 0 .../eos/facts/l3_interfaces/l3_interfaces.py | 124 + .../network/eos/facts/lacp/__init__.py | 0 .../module_utils/network/eos/facts/lacp/lacp.py | 106 + .../network/eos/facts/lacp_interfaces/__init__.py | 0 .../eos/facts/lacp_interfaces/lacp_interfaces.py | 107 + .../network/eos/facts/lag_interfaces/__init__.py | 0 .../eos/facts/lag_interfaces/lag_interfaces.py | 124 + .../network/eos/facts/legacy/__init__.py | 0 .../module_utils/network/eos/facts/legacy/base.py | 180 ++ .../network/eos/facts/lldp_global/__init__.py | 0 .../network/eos/facts/lldp_global/lldp_global.py | 102 + .../network/eos/facts/lldp_interfaces/__init__.py | 0 .../eos/facts/lldp_interfaces/lldp_interfaces.py | 106 + .../network/eos/facts/logging_global/__init__.py | 0 .../eos/facts/logging_global/logging_global.py | 98 + .../network/eos/facts/ntp_global/__init__.py | 0 .../network/eos/facts/ntp_global/ntp_global.py | 97 + .../network/eos/facts/ospf_interfaces/__init__.py | 0 .../eos/facts/ospf_interfaces/ospf_interfaces.py | 107 + .../network/eos/facts/ospfv2/__init__.py | 0 .../network/eos/facts/ospfv2/ospfv2.py | 510 ++++ .../network/eos/facts/ospfv3/__init__.py | 0 .../network/eos/facts/ospfv3/ospfv3.py | 121 + .../network/eos/facts/prefix_lists/__init__.py | 0 .../network/eos/facts/prefix_lists/prefix_lists.py | 95 + .../network/eos/facts/route_maps/__init__.py | 0 .../network/eos/facts/route_maps/route_maps.py | 143 + .../network/eos/facts/snmp_server/__init__.py | 0 .../network/eos/facts/snmp_server/snmp_server.py | 117 + .../network/eos/facts/static_routes/__init__.py | 0 .../eos/facts/static_routes/static_routes.py | 243 ++ .../network/eos/facts/vlans/__init__.py | 0 .../module_utils/network/eos/facts/vlans/vlans.py | 125 + .../module_utils/network/eos/providers/__init__.py | 0 .../network/eos/providers/cli/__init__.py | 0 .../network/eos/providers/cli/config/__init__.py | 0 .../eos/providers/cli/config/bgp/__init__.py | 0 .../eos/providers/cli/config/bgp/address_family.py | 149 + .../eos/providers/cli/config/bgp/neighbors.py | 194 ++ .../eos/providers/cli/config/bgp/process.py | 186 ++ .../module_utils/network/eos/providers/module.py | 75 + .../network/eos/providers/providers.py | 127 + .../network/eos/rm_templates/__init__.py | 0 .../network/eos/rm_templates/bgp_address_family.py | 672 +++++ .../network/eos/rm_templates/bgp_global.py | 2965 ++++++++++++++++++++ .../network/eos/rm_templates/hostname.py | 48 + .../network/eos/rm_templates/logging_global.py | 475 ++++ .../network/eos/rm_templates/ntp_global.py | 273 ++ .../network/eos/rm_templates/ospf_interfaces.py | 1094 ++++++++ .../network/eos/rm_templates/ospfv3.py | 1091 +++++++ .../network/eos/rm_templates/prefix_lists.py | 166 ++ .../network/eos/rm_templates/route_maps.py | 1697 +++++++++++ .../network/eos/rm_templates/snmp_server.py | 1232 ++++++++ .../module_utils/network/eos/utils/__init__.py | 0 .../module_utils/network/eos/utils/utils.py | 84 + .../arista/eos/plugins/modules/__init__.py | 0 .../eos/plugins/modules/eos_acl_interfaces.py | 423 +++ .../arista/eos/plugins/modules/eos_acls.py | 904 ++++++ .../arista/eos/plugins/modules/eos_banner.py | 199 ++ .../arista/eos/plugins/modules/eos_bgp.py | 469 ++++ .../eos/plugins/modules/eos_bgp_address_family.py | 1351 +++++++++ .../arista/eos/plugins/modules/eos_bgp_global.py | 2353 ++++++++++++++++ .../arista/eos/plugins/modules/eos_command.py | 320 +++ .../arista/eos/plugins/modules/eos_config.py | 630 +++++ .../arista/eos/plugins/modules/eos_eapi.py | 437 +++ .../arista/eos/plugins/modules/eos_facts.py | 210 ++ .../arista/eos/plugins/modules/eos_hostname.py | 333 +++ .../arista/eos/plugins/modules/eos_interfaces.py | 415 +++ .../eos/plugins/modules/eos_l2_interfaces.py | 430 +++ .../eos/plugins/modules/eos_l3_interfaces.py | 412 +++ .../arista/eos/plugins/modules/eos_lacp.py | 247 ++ .../eos/plugins/modules/eos_lacp_interfaces.py | 340 +++ .../eos/plugins/modules/eos_lag_interfaces.py | 342 +++ .../arista/eos/plugins/modules/eos_lldp.py | 117 + .../arista/eos/plugins/modules/eos_lldp_global.py | 347 +++ .../eos/plugins/modules/eos_lldp_interfaces.py | 346 +++ .../arista/eos/plugins/modules/eos_logging.py | 505 ++++ .../eos/plugins/modules/eos_logging_global.py | 942 +++++++ .../arista/eos/plugins/modules/eos_ntp_global.py | 1053 +++++++ .../eos/plugins/modules/eos_ospf_interfaces.py | 1230 ++++++++ .../arista/eos/plugins/modules/eos_ospfv2.py | 1563 +++++++++++ .../arista/eos/plugins/modules/eos_ospfv3.py | 1583 +++++++++++ .../arista/eos/plugins/modules/eos_prefix_lists.py | 1197 ++++++++ .../arista/eos/plugins/modules/eos_route_maps.py | 1406 ++++++++++ .../arista/eos/plugins/modules/eos_snmp_server.py | 1522 ++++++++++ .../eos/plugins/modules/eos_static_routes.py | 969 +++++++ .../arista/eos/plugins/modules/eos_system.py | 378 +++ .../arista/eos/plugins/modules/eos_user.py | 491 ++++ .../arista/eos/plugins/modules/eos_vlans.py | 329 +++ .../arista/eos/plugins/modules/eos_vrf.py | 427 +++ .../arista/eos/plugins/terminal/__init__.py | 0 .../arista/eos/plugins/terminal/eos.py | 113 + 257 files changed, 54619 insertions(+) create mode 100644 ansible_collections/arista/eos/plugins/action/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/action/acl_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/action/acls.py create mode 100644 ansible_collections/arista/eos/plugins/action/banner.py create mode 100644 ansible_collections/arista/eos/plugins/action/bgp.py create mode 100644 ansible_collections/arista/eos/plugins/action/bgp_address_family.py create mode 100644 ansible_collections/arista/eos/plugins/action/bgp_global.py create mode 100644 ansible_collections/arista/eos/plugins/action/command.py create mode 100644 ansible_collections/arista/eos/plugins/action/config.py create mode 100644 ansible_collections/arista/eos/plugins/action/eapi.py create mode 100644 ansible_collections/arista/eos/plugins/action/eos.py create mode 100644 ansible_collections/arista/eos/plugins/action/facts.py create mode 100644 ansible_collections/arista/eos/plugins/action/hostname.py create mode 100644 ansible_collections/arista/eos/plugins/action/interface.py create mode 100644 ansible_collections/arista/eos/plugins/action/interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/action/l2_interface.py create mode 100644 ansible_collections/arista/eos/plugins/action/l2_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/action/l3_interface.py create mode 100644 ansible_collections/arista/eos/plugins/action/l3_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/action/lacp.py create mode 100644 ansible_collections/arista/eos/plugins/action/lacp_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/action/lag_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/action/linkagg.py create mode 100644 ansible_collections/arista/eos/plugins/action/lldp.py create mode 100644 ansible_collections/arista/eos/plugins/action/lldp_global.py create mode 100644 ansible_collections/arista/eos/plugins/action/lldp_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/action/logging.py create mode 100644 ansible_collections/arista/eos/plugins/action/logging_global.py create mode 100644 ansible_collections/arista/eos/plugins/action/ntp_global.py create mode 100644 ansible_collections/arista/eos/plugins/action/ospf_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/action/ospfv2.py create mode 100644 ansible_collections/arista/eos/plugins/action/ospfv3.py create mode 100644 ansible_collections/arista/eos/plugins/action/prefix_lists.py create mode 100644 ansible_collections/arista/eos/plugins/action/route_maps.py create mode 100644 ansible_collections/arista/eos/plugins/action/snmp_server.py create mode 100644 ansible_collections/arista/eos/plugins/action/static_route.py create mode 100644 ansible_collections/arista/eos/plugins/action/static_routes.py create mode 100644 ansible_collections/arista/eos/plugins/action/system.py create mode 100644 ansible_collections/arista/eos/plugins/action/user.py create mode 100644 ansible_collections/arista/eos/plugins/action/vlan.py create mode 100644 ansible_collections/arista/eos/plugins/action/vlans.py create mode 100644 ansible_collections/arista/eos/plugins/action/vrf.py create mode 100644 ansible_collections/arista/eos/plugins/cliconf/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/cliconf/eos.py create mode 100644 ansible_collections/arista/eos/plugins/filter/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/httpapi/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/httpapi/eos.py create mode 100644 ansible_collections/arista/eos/plugins/inventory/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acl_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acl_interfaces/acl_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acls/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acls/acls.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_address_family/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_address_family/bgp_address_family.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_global/bgp_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/facts/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/facts/facts.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/hostname/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/hostname/hostname.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/interfaces/interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l2_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l2_interfaces/l2_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l3_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l3_interfaces/l3_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp/lacp.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp_interfaces/lacp_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lag_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lag_interfaces/lag_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_global/lldp_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_interfaces/lldp_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/logging_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/logging_global/logging_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ntp_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ntp_global/ntp_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospf_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospf_interfaces/ospf_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv2/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv2/ospfv2.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv3/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv3/ospfv3.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/prefix_lists/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/prefix_lists/prefix_lists.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/route_maps/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/route_maps/route_maps.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/snmp_server/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/snmp_server/snmp_server.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/static_routes/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/static_routes/static_routes.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/vlans/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/vlans/vlans.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acl_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acl_interfaces/acl_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acls/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acls/acls.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_address_family/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_address_family/bgp_address_family.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_global/bgp_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/hostname/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/hostname/hostname.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/interfaces/interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l2_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l2_interfaces/l2_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l3_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l3_interfaces/l3_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp/lacp.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp_interfaces/lacp_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lag_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lag_interfaces/lag_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_global/lldp_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_interfaces/lldp_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/logging_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/logging_global/logging_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ntp_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ntp_global/ntp_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospf_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospf_interfaces/ospf_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv2/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv2/ospfv2.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv3/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv3/ospfv3.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/prefix_lists/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/prefix_lists/prefix_lists.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/route_maps/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/route_maps/route_maps.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/snmp_server/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/snmp_server/snmp_server.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/static_routes/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/static_routes/static_routes.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/vlans/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/config/vlans/vlans.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/eos.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acl_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acl_interfaces/acl_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acls/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acls/acls.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_address_family/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_address_family/bgp_address_family.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_global/bgp_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/facts.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/hostname/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/hostname/hostname.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/interfaces/interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l2_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l2_interfaces/l2_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l3_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l3_interfaces/l3_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp/lacp.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp_interfaces/lacp_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lag_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lag_interfaces/lag_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/legacy/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/legacy/base.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_global/lldp_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_interfaces/lldp_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/logging_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/logging_global/logging_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ntp_global/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ntp_global/ntp_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospf_interfaces/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospf_interfaces/ospf_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv2/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv2/ospfv2.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv3/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv3/ospfv3.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/prefix_lists/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/prefix_lists/prefix_lists.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/route_maps/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/route_maps/route_maps.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/snmp_server/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/snmp_server/snmp_server.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/static_routes/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/static_routes/static_routes.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/vlans/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/vlans/vlans.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/address_family.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/neighbors.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/process.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/module.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/providers.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/bgp_address_family.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/bgp_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/hostname.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/logging_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ntp_global.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ospf_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/ospfv3.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/prefix_lists.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/route_maps.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/snmp_server.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/utils/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/module_utils/network/eos/utils/utils.py create mode 100644 ansible_collections/arista/eos/plugins/modules/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_acl_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_acls.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_banner.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_bgp.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_bgp_address_family.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_bgp_global.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_command.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_config.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_eapi.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_facts.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_hostname.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_l2_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_l3_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_lacp.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_lacp_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_lag_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_lldp.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_lldp_global.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_lldp_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_logging.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_logging_global.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_ntp_global.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_ospf_interfaces.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_ospfv2.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_ospfv3.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_prefix_lists.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_route_maps.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_snmp_server.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_static_routes.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_system.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_user.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_vlans.py create mode 100644 ansible_collections/arista/eos/plugins/modules/eos_vrf.py create mode 100644 ansible_collections/arista/eos/plugins/terminal/__init__.py create mode 100644 ansible_collections/arista/eos/plugins/terminal/eos.py (limited to 'ansible_collections/arista/eos/plugins') diff --git a/ansible_collections/arista/eos/plugins/action/__init__.py b/ansible_collections/arista/eos/plugins/action/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/action/acl_interfaces.py b/ansible_collections/arista/eos/plugins/action/acl_interfaces.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/acl_interfaces.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/acls.py b/ansible_collections/arista/eos/plugins/action/acls.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/acls.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/banner.py b/ansible_collections/arista/eos/plugins/action/banner.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/banner.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/bgp.py b/ansible_collections/arista/eos/plugins/action/bgp.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/bgp.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/bgp_address_family.py b/ansible_collections/arista/eos/plugins/action/bgp_address_family.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/bgp_address_family.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/bgp_global.py b/ansible_collections/arista/eos/plugins/action/bgp_global.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/bgp_global.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/command.py b/ansible_collections/arista/eos/plugins/action/command.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/command.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/config.py b/ansible_collections/arista/eos/plugins/action/config.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/config.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/eapi.py b/ansible_collections/arista/eos/plugins/action/eapi.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/eapi.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/eos.py b/ansible_collections/arista/eos/plugins/action/eos.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/eos.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/facts.py b/ansible_collections/arista/eos/plugins/action/facts.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/facts.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/hostname.py b/ansible_collections/arista/eos/plugins/action/hostname.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/hostname.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/interface.py b/ansible_collections/arista/eos/plugins/action/interface.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/interface.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/interfaces.py b/ansible_collections/arista/eos/plugins/action/interfaces.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/interfaces.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/l2_interface.py b/ansible_collections/arista/eos/plugins/action/l2_interface.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/l2_interface.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/l2_interfaces.py b/ansible_collections/arista/eos/plugins/action/l2_interfaces.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/l2_interfaces.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/l3_interface.py b/ansible_collections/arista/eos/plugins/action/l3_interface.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/l3_interface.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/l3_interfaces.py b/ansible_collections/arista/eos/plugins/action/l3_interfaces.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/l3_interfaces.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/lacp.py b/ansible_collections/arista/eos/plugins/action/lacp.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/lacp.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/lacp_interfaces.py b/ansible_collections/arista/eos/plugins/action/lacp_interfaces.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/lacp_interfaces.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/lag_interfaces.py b/ansible_collections/arista/eos/plugins/action/lag_interfaces.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/lag_interfaces.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/linkagg.py b/ansible_collections/arista/eos/plugins/action/linkagg.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/linkagg.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/lldp.py b/ansible_collections/arista/eos/plugins/action/lldp.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/lldp.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/lldp_global.py b/ansible_collections/arista/eos/plugins/action/lldp_global.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/lldp_global.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/lldp_interfaces.py b/ansible_collections/arista/eos/plugins/action/lldp_interfaces.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/lldp_interfaces.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/logging.py b/ansible_collections/arista/eos/plugins/action/logging.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/logging.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/logging_global.py b/ansible_collections/arista/eos/plugins/action/logging_global.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/logging_global.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/ntp_global.py b/ansible_collections/arista/eos/plugins/action/ntp_global.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/ntp_global.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/ospf_interfaces.py b/ansible_collections/arista/eos/plugins/action/ospf_interfaces.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/ospf_interfaces.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/ospfv2.py b/ansible_collections/arista/eos/plugins/action/ospfv2.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/ospfv2.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/ospfv3.py b/ansible_collections/arista/eos/plugins/action/ospfv3.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/ospfv3.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/prefix_lists.py b/ansible_collections/arista/eos/plugins/action/prefix_lists.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/prefix_lists.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/route_maps.py b/ansible_collections/arista/eos/plugins/action/route_maps.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/route_maps.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/snmp_server.py b/ansible_collections/arista/eos/plugins/action/snmp_server.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/snmp_server.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/static_route.py b/ansible_collections/arista/eos/plugins/action/static_route.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/static_route.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/static_routes.py b/ansible_collections/arista/eos/plugins/action/static_routes.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/static_routes.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/system.py b/ansible_collections/arista/eos/plugins/action/system.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/system.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/user.py b/ansible_collections/arista/eos/plugins/action/user.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/user.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/vlan.py b/ansible_collections/arista/eos/plugins/action/vlan.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/vlan.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/vlans.py b/ansible_collections/arista/eos/plugins/action/vlans.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/vlans.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/action/vrf.py b/ansible_collections/arista/eos/plugins/action/vrf.py new file mode 100644 index 000000000..0afb7e88f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/action/vrf.py @@ -0,0 +1,58 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +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 ["eos_config", "config"] else False + ) + persistent_connection = self._play_context.connection.split(".")[-1] + warnings = [] + + if persistent_connection not in ("network_cli", "httpapi"): + 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/arista/eos/plugins/cliconf/__init__.py b/ansible_collections/arista/eos/plugins/cliconf/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/cliconf/eos.py b/ansible_collections/arista/eos/plugins/cliconf/eos.py new file mode 100644 index 000000000..d5347307d --- /dev/null +++ b/ansible_collections/arista/eos/plugins/cliconf/eos.py @@ -0,0 +1,494 @@ +# +# (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 . +# + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +DOCUMENTATION = """ +author: Ansible Networking Team (@ansible-network) +name: eos +short_description: Use eos cliconf to run command on Arista EOS platform +description: +- This eos plugin provides low level abstraction apis for sending and receiving CLI + commands from Arista EOS network devices. +version_added: 1.0.0 +options: + eos_use_sessions: + type: boolean + default: true + description: + - Specifies if sessions should be used on remote host or not + env: + - name: ANSIBLE_EOS_USE_SESSIONS + vars: + - name: ansible_eos_use_sessions + 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_eos_config_commands +""" + +import json +import re + +from ansible.errors import AnsibleConnectionFailure +from ansible.module_utils._text import to_text +from ansible.module_utils.common._collections_compat import Mapping +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, + enable_mode, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.eos import ( + session_name, +) + + +class Cliconf(CliconfBase): + __rpc__ = CliconfBase.__rpc__ + [ + "commit", + "discard_changes", + "get_diff", + "run_commands", + "supports_sessions", + ] + + def __init__(self, *args, **kwargs): + super(Cliconf, self).__init__(*args, **kwargs) + self._device_info = {} + self._session_support = None + + @enable_mode + def get_config(self, source="running", flags=None, format="text"): + 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 %s " % lookup[source] + if format and format != "text": + cmd += "| %s " % format + + cmd += " ".join(to_list(flags)) + cmd = cmd.strip() + return self.send_command(cmd) + + @enable_mode + def get_session_config( + self, + candidate=None, + commit=True, + replace=None, + comment=None, + ): + operations = self.get_device_operations() + self.check_edit_config_capability( + operations, + candidate, + commit, + replace, + comment, + ) + + if (commit is False) and (not self.supports_sessions()): + raise ValueError( + "check mode is not supported without configuration session", + ) + + resp = {} + session = None + if self.supports_sessions(): + session = session_name() + resp.update({"session": session}) + self.send_command("configure session %s" % session) + if replace: + self.send_command("rollback clean-config") + else: + self.send_command("configure") + + results = [] + requests = [] + multiline = False + for line in to_list(candidate): + if not isinstance(line, Mapping): + line = {"command": line} + + cmd = line["command"] + if cmd == "end": + continue + if cmd.startswith("banner") or multiline: + multiline = True + elif cmd == "EOF" and multiline: + multiline = False + + if multiline: + line["sendonly"] = True + + if cmd != "end" and not cmd.startswith("!"): + try: + results.append(self.send_command(**line)) + requests.append(cmd) + except AnsibleConnectionFailure as e: + self.discard_changes(session) + raise AnsibleConnectionFailure(e.message) + + resp["request"] = requests + resp["response"] = results + if self.supports_sessions(): + out = self.send_command("show session-config") + if out: + resp["diff"] = out.strip() + + if commit: + self.commit() + else: + self.discard_changes(session) + else: + self.send_command("end") + if resp.get("diff"): + return resp["diff"] + return resp + + @enable_mode + def edit_config( + self, + candidate=None, + commit=True, + replace=None, + comment=None, + ): + operations = self.get_device_operations() + self.check_edit_config_capability( + operations, + candidate, + commit, + replace, + comment, + ) + + if (commit is False) and (not self.supports_sessions()): + raise ValueError( + "check mode is not supported without configuration session", + ) + + resp = {} + session = None + if self.supports_sessions(): + session = session_name() + resp.update({"session": session}) + self.send_command("configure session %s" % session) + if replace: + self.send_command("rollback clean-config") + else: + self.send_command("configure") + + results = [] + requests = [] + multiline = False + for line in to_list(candidate): + if not isinstance(line, Mapping): + line = {"command": line} + + cmd = line["command"] + if cmd == "end": + continue + if cmd.startswith("banner") or multiline: + multiline = True + elif cmd == "EOF" and multiline: + multiline = False + + if multiline: + line["sendonly"] = True + + if cmd != "end" and not cmd.startswith("!"): + try: + results.append(self.send_command(**line)) + requests.append(cmd) + except AnsibleConnectionFailure as e: + self.discard_changes(session) + raise AnsibleConnectionFailure(e.message) + + resp["request"] = requests + resp["response"] = results + if self.supports_sessions(): + out = self.send_command("show session-config diffs") + if out: + resp["diff"] = out.strip() + + if commit: + self.commit() + else: + self.discard_changes(session) + else: + self.send_command("end") + return resp + + def get( + self, + command, + prompt=None, + answer=None, + sendonly=False, + newline=True, + output=None, + check_all=False, + version=None, + ): + if output: + command = self._get_command_with_output(command, output, version) + return self.send_command( + command=command, + prompt=prompt, + answer=answer, + sendonly=sendonly, + newline=newline, + check_all=check_all, + ) + + def commit(self): + self.send_command("commit") + + def discard_changes(self, session=None): + commands = ["end"] + if self.supports_sessions(): + # to close session gracefully execute abort in top level session prompt. + commands.extend(["configure session %s" % session, "abort"]) + + for cmd in commands: + self.send_command(cmd) + + 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) + version = cmd.pop("version", None) + if output: + cmd["command"] = self._get_command_with_output( + cmd["command"], + output, + version, + ) + + try: + out = self.send_command(**cmd) + except AnsibleConnectionFailure as e: + if check_rc: + raise + out = getattr(e, "err", e) + out = to_text(out, errors="surrogate_or_strict") + + if out is not None: + try: + out = json.loads(out) + except ValueError: + out = out.strip() + + responses.append(out) + return responses + + 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=3) + candidate_obj.load(candidate) + + if running and diff_match != "none" and diff_replace != "config": + # running configuration + running_obj = NetworkConfig( + indent=3, + 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 supports_sessions(self): + if not self.get_option("eos_use_sessions"): + self._session_support = False + else: + if self._session_support: + return self._session_support + + try: + self.get("show configuration sessions") + self._session_support = True + except AnsibleConnectionFailure: + self._session_support = False + + return self._session_support + + def get_device_info(self): + if not self._device_info: + device_info = {} + + device_info["network_os"] = "eos" + reply = self.get("show version | json") + data = json.loads(reply) + + device_info["network_os_version"] = data["version"] + device_info["network_os_model"] = data["modelName"] + + reply = self.get("show hostname | json") + data = json.loads(reply) + + device_info["network_os_hostname"] = data["hostname"] + + try: + reply = self.get("bash timeout 5 cat /mnt/flash/boot-config") + + match = re.search(r"SWI=(.+)$", reply, re.M) + if match: + device_info["network_os_image"] = match.group(1) + except AnsibleConnectionFailure: + # This requires enable mode to run + self._connection.queue_message( + "vvv", + "Unable to gather network_os_image without enable mode", + ) + + self._device_info = device_info + + return self._device_info + + def get_device_operations(self): + return { + "supports_diff_replace": True, + "supports_commit": bool(self.supports_sessions()), + "supports_rollback": False, + "supports_defaults": False, + "supports_onbox_diff": bool(self.supports_sessions()), + "supports_commit_comment": False, + "supports_multiline_delimiter": False, + "supports_diff_match": True, + "supports_diff_ignore_lines": True, + "supports_generate_diff": not bool(self.supports_sessions()), + "supports_replace": bool(self.supports_sessions()), + } + + 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["device_operations"] = self.get_device_operations() + result.update(self.get_option_values()) + + return json.dumps(result) + + def set_cli_prompt_context(self): + """ + Make sure we are in the operational cli mode + :return: None + """ + if self._connection.connected: + self._update_cli_prompt_context( + config_context="(config", + exit_command="abort", + ) + + def _get_command_with_output(self, command, output, version): + 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"): + cmd = "%s | json" % command + else: + cmd = command + if version != "latest" and "| json" in cmd: + cmd = "%s version %s" % (cmd, version) + return cmd diff --git a/ansible_collections/arista/eos/plugins/filter/__init__.py b/ansible_collections/arista/eos/plugins/filter/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/httpapi/__init__.py b/ansible_collections/arista/eos/plugins/httpapi/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/httpapi/eos.py b/ansible_collections/arista/eos/plugins/httpapi/eos.py new file mode 100644 index 000000000..9fa9dfd3e --- /dev/null +++ b/ansible_collections/arista/eos/plugins/httpapi/eos.py @@ -0,0 +1,217 @@ +# (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: eos +short_description: Use eAPI to run command on eos platform +description: +- This eos plugin provides low level abstraction api's for sending and receiving CLI + commands with eos network devices. +version_added: 1.0.0 +options: + eos_use_sessions: + type: bool + default: yes + description: + - Specifies if sessions should be used on remote host or not + env: + - name: ANSIBLE_EOS_USE_SESSIONS + vars: + - name: ansible_eos_use_sessions +""" + +import json + +from ansible.errors import AnsibleConnectionFailure +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, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.eos import ( + session_name, +) + + +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._session_support = None + + def supports_sessions(self): + if not self.get_option("eos_use_sessions"): + self._session_support = False + else: + if self._session_support: + return self._session_support + + try: + response = self.send_request("show configuration sessions") + self._session_support = "error" not in response + except AnsibleConnectionFailure: + self._session_support = False + + return self._session_support + + def send_request(self, data, **message_kwargs): + data = to_list(data) + become = self._become + if become: + self.connection.queue_message("vvvv", "firing event: on_become") + data.insert(0, {"cmd": "enable", "input": self._become_pass}) + + output = message_kwargs.get("output") or "text" + version = message_kwargs.get("version") or "latest" + request = request_builder(data, output, version) + headers = {"Content-Type": "application/json-rpc"} + + _response, response_data = self.connection.send( + "/command-api", + 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) + + if become: + results = results[1:] + if len(results) == 1: + results = results[0] + + return results + + def get_device_info(self): + if self._device_info: + return self._device_info + + device_info = {} + + device_info["network_os"] = "eos" + reply = self.send_request("show version", output="json") + data = json.loads(reply) + + device_info["network_os_version"] = data["version"] + device_info["network_os_model"] = data["modelName"] + + reply = self.send_request("show hostname", output="json") + data = json.loads(reply) + + device_info["network_os_hostname"] = data["hostname"] + + self._device_info = device_info + return self._device_info + + def get_device_operations(self): + return { + "supports_diff_replace": True, + "supports_commit": bool(self.supports_sessions()), + "supports_rollback": False, + "supports_defaults": False, + "supports_onbox_diff": bool(self.supports_sessions()), + "supports_commit_comment": False, + "supports_multiline_delimiter": False, + "supports_diff_match": True, + "supports_diff_ignore_lines": True, + "supports_generate_diff": not bool(self.supports_sessions()), + "supports_replace": bool(self.supports_sessions()), + } + + 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"] = "eapi" + + 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. + + session = None + if self.supports_sessions(): + session = session_name() + candidate = ["configure session %s" % session] + candidate + else: + candidate = ["configure"] + candidate + candidate.append("commit") + + try: + responses = self.send_request(candidate) + except ConnectionError: + if session: + self.send_request(["configure session %s" % session, "abort"]) + raise + + return [resp for resp in to_list(responses) if resp != "{}"] + + +def handle_response(response): + if "error" in response: + error = response["error"] + + error_text = [] + for data in error.get("data", []): + error_text.extend(data.get("errors", [])) + error_text = "\n".join(error_text) or error["message"] + + raise ConnectionError(error_text, code=error["code"]) + + results = [] + + for result in response["result"]: + if "messages" in result: + results.append(result["messages"][0]) + elif "output" in result: + results.append(result["output"].strip()) + else: + results.append(json.dumps(result)) + + return results + + +def request_builder(commands, output, version, reqid=None): + if version != "latest": + version = int(version) + params = dict(version=version, cmds=commands, format=output) + return json.dumps( + dict(jsonrpc="2.0", id=reqid, method="runCmds", params=params), + ) diff --git a/ansible_collections/arista/eos/plugins/inventory/__init__.py b/ansible_collections/arista/eos/plugins/inventory/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acl_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acl_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acl_interfaces/acl_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acl_interfaces/acl_interfaces.py new file mode 100644 index 000000000..f76152800 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acl_interfaces/acl_interfaces.py @@ -0,0 +1,85 @@ +# +# -*- 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 eos_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 eos_acl_interfaces module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "access_groups": { + "elements": "dict", + "options": { + "acls": { + "elements": "dict", + "options": { + "direction": { + "required": True, + "choices": ["in", "out"], + "type": "str", + }, + "name": {"required": True, "type": "str"}, + }, + "type": "list", + }, + "afi": { + "required": True, + "choices": ["ipv4", "ipv6"], + "type": "str", + }, + }, + "type": "list", + }, + "name": {"required": True, "type": "str"}, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "parsed", + "rendered", + ], + "default": "merged", + "type": "str", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acls/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acls/acls.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acls/acls.py new file mode 100644 index 000000000..6f010fe9f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/acls/acls.py @@ -0,0 +1,416 @@ +# +# -*- 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 eos_acls module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class AclsArgs(object): # pylint: disable=R0903 + """The arg spec for the eos_acls module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "acls": { + "elements": "dict", + "options": { + "aces": { + "elements": "dict", + "options": { + "destination": { + "mutually_exclusive": [ + [ + "address", + "subnet_address", + "any", + "host", + ], + [ + "wildcard_bits", + "subnet_address", + "any", + "host", + ], + ], + "options": { + "address": {"type": "str"}, + "any": {"type": "bool"}, + "host": {"type": "str"}, + "port_protocol": {"type": "dict"}, + "subnet_address": {"type": "str"}, + "wildcard_bits": {"type": "str"}, + }, + "required_together": [ + ["address", "wildcard_bits"], + ], + "type": "dict", + }, + "fragment_rules": {"type": "bool"}, + "fragments": {"type": "bool"}, + "grant": { + "choices": ["permit", "deny"], + "type": "str", + }, + "line": {"type": "str", "aliases": ["ace"]}, + "hop_limit": {"type": "dict"}, + "log": {"type": "bool"}, + "protocol": {"type": "str"}, + "protocol_options": { + "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"}, + "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_num": {"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": { + "options": { + "address_unreachable": { + "type": "bool", + }, + "beyond_scope": { + "type": "bool", + }, + "echo_reply": {"type": "bool"}, + "echo_request": { + "type": "bool", + }, + "erroneous_header": { + "type": "bool", + }, + "fragment_reassembly_exceeded": { + "type": "bool", + }, + "hop_limit_exceeded": { + "type": "bool", + }, + "neighbor_advertisement": { + "type": "bool", + }, + "neighbor_solicitation": { + "type": "bool", + }, + "no_admin": {"type": "bool"}, + "no_route": {"type": "bool"}, + "packet_too_big": { + "type": "bool", + }, + "parameter_problem": { + "type": "bool", + }, + "port_unreachable": { + "type": "bool", + }, + "redirect_message": { + "type": "bool", + }, + "reject_route": { + "type": "bool", + }, + "router_advertisement": { + "type": "bool", + }, + "router_solicitation": { + "type": "bool", + }, + "source_address_failed": { + "type": "bool", + }, + "source_routing_error": { + "type": "bool", + }, + "time_exceeded": { + "type": "bool", + }, + "unreachable": { + "type": "bool", + }, + "unrecognized_ipv6_option": { + "type": "bool", + }, + "unrecognized_next_header": { + "type": "bool", + }, + }, + "type": "dict", + }, + "ip": { + "options": { + "nexthop_group": { + "type": "str", + }, + }, + "type": "dict", + }, + "ipv6": { + "options": { + "nexthop_group": { + "type": "str", + }, + }, + "type": "dict", + }, + "tcp": { + "options": { + "flags": { + "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", + }, + }, + "type": "dict", + }, + "remark": {"type": "str"}, + "sequence": {"type": "int"}, + "source": { + "mutually_exclusive": [ + [ + "address", + "subnet_address", + "any", + "host", + ], + [ + "wildcard_bits", + "subnet_address", + "any", + "host", + ], + ], + "options": { + "address": {"type": "str"}, + "any": {"type": "bool"}, + "host": {"type": "str"}, + "port_protocol": {"type": "dict"}, + "subnet_address": {"type": "str"}, + "wildcard_bits": {"type": "str"}, + }, + "required_together": [ + ["address", "wildcard_bits"], + ], + "type": "dict", + }, + "tracked": {"type": "bool"}, + "ttl": { + "options": { + "eq": {"type": "int"}, + "gt": {"type": "int"}, + "lt": {"type": "int"}, + "neq": {"type": "int"}, + }, + "type": "dict", + }, + "vlan": {"type": "str"}, + }, + "type": "list", + }, + "name": {"required": True, "type": "str"}, + "standard": {"type": "bool"}, + }, + "type": "list", + }, + "afi": { + "choices": ["ipv4", "ipv6"], + "required": True, + "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/arista/eos/plugins/module_utils/network/eos/argspec/bgp_address_family/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_address_family/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_address_family/bgp_address_family.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_address_family/bgp_address_family.py new file mode 100644 index 000000000..c706afe18 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_address_family/bgp_address_family.py @@ -0,0 +1,206 @@ +# -*- 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 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 eos_bgp_address_family module +""" + + +class Bgp_afArgs(object): # pylint: disable=R0903 + """The arg spec for the eos_bgp_address_family module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "running_config": {"type": "str"}, + "state": { + "default": "merged", + "type": "str", + "choices": [ + "deleted", + "merged", + "overridden", + "replaced", + "gathered", + "rendered", + "parsed", + ], + }, + "config": { + "type": "dict", + "options": { + "as_number": {"type": "str"}, + "address_family": { + "elements": "dict", + "type": "list", + "options": { + "network": { + "type": "list", + "elements": "dict", + "options": { + "route_map": {"type": "str"}, + "address": {"type": "str"}, + }, + }, + "redistribute": { + "elements": "dict", + "type": "list", + "options": { + "ospf_route": { + "type": "str", + "choices": [ + "internal", + "external", + "nssa_external_1", + "nssa_external_2", + ], + }, + "route_map": {"type": "str"}, + "protocol": { + "type": "str", + "choices": ["isis", "ospfv3", "dhcp"], + }, + "isis_level": { + "type": "str", + "choices": [ + "level-1", + "level-2", + "level-1-2", + ], + }, + }, + }, + "route_target": { + "type": "dict", + "options": { + "action": { + "type": "str", + "choices": ["both", "import", "export"], + "aliases": ["mode"], + }, + "type": { + "type": "str", + "choices": [ + "evpn", + "vpn-ipv4", + "vpn-ipv6", + ], + }, + "route_map": {"type": "str"}, + "target": {"type": "str"}, + "imported_route": {"type": "bool"}, + }, + }, + "graceful_restart": {"type": "bool"}, + "bgp_params": { + "type": "dict", + "options": { + "next_hop_address_family": { + "type": "str", + "choices": ["ipv6"], + }, + "redistribute_internal": {"type": "bool"}, + "route": {"type": "str"}, + "additional_paths": { + "type": "str", + "choices": ["install", "send", "receive"], + }, + "next_hop_unchanged": {"type": "bool"}, + }, + }, + "safi": { + "type": "str", + "choices": ["labeled-unicast", "multicast"], + }, + "neighbor": { + "elements": "dict", + "type": "list", + "options": { + "activate": {"type": "bool"}, + "graceful_restart": {"type": "bool"}, + "weight": {"type": "int"}, + "default_originate": { + "type": "dict", + "options": { + "route_map": {"type": "str"}, + "always": {"type": "bool"}, + }, + }, + "route_map": { + "type": "dict", + "options": { + "direction": { + "type": "str", + "choices": ["in", "out"], + }, + "name": {"type": "str"}, + }, + }, + "next_hop_address_family": { + "type": "str", + "choices": ["ipv6"], + }, + "additional_paths": { + "type": "str", + "choices": ["send", "receive"], + }, + "peer": {"type": "str"}, + "encapsulation": { + "type": "dict", + "options": { + "transport": { + "type": "str", + "choices": ["mpls", "vxlan"], + }, + "source_interface": {"type": "str"}, + }, + }, + "prefix_list": { + "type": "dict", + "options": { + "direction": { + "type": "str", + "choices": ["in", "out"], + }, + "name": {"type": "str"}, + }, + }, + "next_hop_unchanged": {"type": "bool"}, + }, + }, + "afi": { + "type": "str", + "choices": ["ipv4", "ipv6", "evpn"], + }, + "vrf": {"type": "str"}, + }, + }, + }, + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_global/bgp_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_global/bgp_global.py new file mode 100644 index 000000000..96a055e43 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/bgp_global/bgp_global.py @@ -0,0 +1,1033 @@ +# -*- 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 eos_bgp_global module +""" + + +class Bgp_globalArgs(object): # pylint: disable=R0903 + """The arg spec for the eos_bgp_global module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "running_config": {"type": "str"}, + "state": { + "default": "merged", + "type": "str", + "choices": [ + "deleted", + "purged", + "merged", + "replaced", + "gathered", + "rendered", + "parsed", + ], + }, + "config": { + "type": "dict", + "options": { + "router_id": {"type": "str"}, + "as_number": {"type": "str"}, + "redistribute": { + "elements": "dict", + "type": "list", + "options": { + "ospf_route": { + "type": "str", + "choices": [ + "internal", + "external", + "nssa_external_1", + "nssa_external_2", + ], + }, + "route_map": {"type": "str"}, + "protocol": { + "type": "str", + "choices": [ + "isis", + "ospfv3", + "ospf", + "attached-host", + "connected", + "rip", + "static", + ], + }, + "isis_level": { + "type": "str", + "choices": ["level-1", "level-2", "level-1-2"], + }, + }, + }, + "monitoring": { + "type": "dict", + "options": { + "received": { + "type": "str", + "choices": ["post_policy", "pre_policy"], + }, + "station": {"type": "str"}, + "port": {"type": "int"}, + "timestamp": { + "type": "str", + "choices": ["none", "send_time"], + }, + }, + }, + "default_metric": {"type": "int"}, + "bgp_params": { + "type": "dict", + "options": { + "labeled_unicast": { + "type": "str", + "choices": ["ip", "tunnel"], + }, + "host_routes": {"type": "bool"}, + "transport": {"type": "int"}, + "next_hop_unchanged": {"type": "bool"}, + "missing_policy": { + "type": "dict", + "options": { + "action": { + "type": "str", + "choices": [ + "deny", + "permit", + "deny-in-out", + ], + }, + "direction": { + "type": "str", + "choices": ["in", "out"], + }, + }, + }, + "monitoring": {"type": "bool"}, + "allowas_in": { + "type": "dict", + "options": { + "count": {"type": "int"}, + "set": {"type": "bool"}, + }, + }, + "additional_paths": { + "type": "str", + "choices": ["install", "send", "receive"], + }, + "advertise_inactive": {"type": "bool"}, + "listen": { + "type": "dict", + "options": { + "range": { + "type": "dict", + "options": { + "peer_group": { + "type": "dict", + "options": { + "peer_filter": {"type": "str"}, + "remote_as": {"type": "str"}, + "name": {"type": "str"}, + }, + }, + "address": {"type": "str"}, + }, + }, + "limit": {"type": "int"}, + }, + }, + "route_reflector": { + "type": "dict", + "options": { + "preserve": {"type": "bool"}, + "set": {"type": "bool"}, + }, + }, + "always_compare_med": {"type": "bool"}, + "client_to_client": {"type": "bool"}, + "bestpath": { + "type": "dict", + "options": { + "ecmp_fast": {"type": "bool"}, + "tie_break": { + "type": "str", + "choices": [ + "cluster_list_length", + "router_id", + ], + }, + "skip": {"type": "bool"}, + "as_path": { + "type": "str", + "choices": ["ignore", "multipath_relax"], + }, + "med": { + "type": "dict", + "options": { + "confed": {"type": "bool"}, + "missing_as_worst": {"type": "bool"}, + }, + }, + }, + }, + "convergence": { + "type": "dict", + "options": { + "slow_peer": {"type": "bool"}, + "time": {"type": "int"}, + }, + }, + "log_neighbor_changes": {"type": "bool"}, + "asn": { + "type": "str", + "choices": ["asdot", "asplain"], + }, + "default": { + "type": "str", + "choices": ["ipv4_unicast", "ipv6_unicast"], + }, + "route": {"type": "str"}, + "enforce_first_as": {"type": "bool"}, + "auto_local_addr": {"type": "bool"}, + "redistribute_internal": {"type": "bool"}, + "cluster_id": {"type": "str"}, + "control_plane_filter": {"type": "bool"}, + "confederation": { + "type": "dict", + "options": { + "peers": {"type": "str"}, + "identifier": {"type": "str"}, + }, + }, + }, + }, + "vlan": {"type": "int"}, + "update": { + "type": "dict", + "options": { + "wait_for": { + "type": "str", + "choices": [ + "wait_for_convergence", + "wait_install", + ], + }, + "batch_size": {"type": "int"}, + }, + }, + "vlan_aware_bundle": {"type": "str"}, + "aggregate_address": { + "elements": "dict", + "type": "list", + "options": { + "advertise_only": {"type": "bool"}, + "match_map": {"type": "str"}, + "attribute_map": {"type": "str"}, + "as_set": {"type": "bool"}, + "summary_only": {"type": "bool"}, + "address": {"type": "str"}, + }, + }, + "neighbor": { + "elements": "dict", + "type": "list", + "aliases": ["neighbors"], + "options": { + "bfd": {"type": "str", "choices": ["c_bit", "enable"]}, + "weight": {"type": "int"}, + "default_originate": { + "type": "dict", + "options": { + "route_map": {"type": "str"}, + "always": {"type": "bool"}, + }, + }, + "next_hop_v6_address": {"type": "str"}, + "route_reflector_client": {"type": "bool"}, + "ttl": {"type": "int"}, + "remove_private_as": { + "type": "dict", + "options": { + "all": {"type": "bool"}, + "set": {"type": "bool"}, + "replace_as": {"type": "bool"}, + }, + }, + "local_v6_addr": {"type": "str"}, + "transport": { + "type": "dict", + "options": { + "connection_mode": {"type": "str"}, + "remote_port": {"type": "int"}, + }, + }, + "next_hop_unchanged": {"type": "bool"}, + "monitoring": {"type": "bool"}, + "ebgp_multihop": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "ttl": {"type": "int"}, + }, + }, + "shutdown": {"type": "bool"}, + "fall_over": {"type": "bool"}, + "idle_restart_timer": {"type": "int"}, + "allowas_in": { + "type": "dict", + "options": { + "count": {"type": "int"}, + "set": {"type": "bool"}, + }, + }, + "additional_paths": { + "type": "str", + "choices": ["send", "receive"], + }, + "peer_group": {"type": "str"}, + "out_delay": {"type": "int"}, + "import_localpref": {"type": "int"}, + "prefix_list": { + "type": "dict", + "options": { + "direction": { + "type": "str", + "choices": ["in", "out"], + }, + "name": {"type": "str"}, + }, + }, + "dont_capability_negotiate": {"type": "bool"}, + "update_source": {"type": "str"}, + "export_localpref": {"type": "int"}, + "local_as": { + "type": "dict", + "options": { + "as_number": {"type": "str"}, + "fallback": {"type": "bool"}, + }, + }, + "maximum_received_routes": { + "type": "dict", + "options": { + "count": {"type": "int"}, + "warning_limit": { + "type": "dict", + "options": { + "limit_count": {"type": "int"}, + "limit_percent": {"type": "int"}, + }, + }, + "warning_only": {"type": "bool"}, + }, + }, + "encryption_password": { + "type": "dict", + "no_log": True, + "options": { + "password": {"type": "str", "no_log": True}, + "type": {"type": "int", "choices": [0, 7]}, + }, + }, + "link_bandwidth": { + "type": "dict", + "options": { + "default": {"type": "str"}, + "auto": {"type": "bool"}, + "set": {"type": "bool"}, + "update_delay": {"type": "int"}, + }, + }, + "graceful_restart_helper": {"type": "bool"}, + "neighbor_address": { + "type": "str", + "aliases": ["peer"], + }, + "next_hop_self": {"type": "bool"}, + "route_to_peer": {"type": "bool"}, + "soft_recognition": { + "type": "str", + "choices": ["all", "None"], + }, + "graceful_restart": {"type": "bool"}, + "enforce_first_as": {"type": "bool"}, + "send_community": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "community_attribute": {"type": "str"}, + "sub_attribute": { + "type": "str", + "choices": [ + "extended", + "link-bandwidth", + "standard", + ], + }, + "speed": {"type": "str"}, + "divide": { + "type": "str", + "choices": ["equal", "ratio"], + }, + "link_bandwidth_attribute": { + "type": "str", + "choices": ["aggregate", "divide"], + }, + }, + }, + "description": {"type": "str"}, + "maximum_accepted_routes": { + "type": "dict", + "options": { + "count": {"type": "int"}, + "warning_limit": {"type": "int"}, + }, + }, + "auto_local_addr": {"type": "bool"}, + "metric_out": {"type": "int"}, + "timers": { + "type": "dict", + "options": { + "holdtime": {"type": "int"}, + "keepalive": {"type": "int"}, + }, + }, + "route_map": { + "type": "dict", + "options": { + "direction": { + "type": "str", + "choices": ["in", "out"], + }, + "name": {"type": "str"}, + }, + }, + "remote_as": {"type": "str"}, + }, + }, + "graceful_restart": { + "type": "dict", + "options": { + "stalepath_time": {"type": "int"}, + "restart_time": {"type": "int"}, + "set": {"type": "bool"}, + }, + }, + "timers": { + "type": "dict", + "options": { + "holdtime": {"type": "int"}, + "keepalive": {"type": "int"}, + }, + }, + "distance": { + "type": "dict", + "options": { + "internal": {"type": "int"}, + "local": {"type": "int"}, + "external": {"type": "int"}, + }, + }, + "route_target": { + "type": "dict", + "options": { + "action": { + "type": "str", + "choices": ["both", "import", "export"], + }, + "target": {"type": "str"}, + }, + }, + "vrfs": { + "elements": "dict", + "type": "list", + "options": { + "access_group": { + "elements": "dict", + "type": "list", + "options": { + "direction": {"type": "str"}, + "afi": { + "type": "str", + "choices": ["ipv4", "ipv6"], + }, + "acl_name": {"type": "str"}, + }, + }, + "router_id": {"type": "str"}, + "vrf": {"type": "str"}, + "route_target": { + "type": "dict", + "options": { + "action": { + "type": "str", + "choices": ["both", "import", "export"], + }, + "type": { + "type": "str", + "choices": [ + "evpn", + "vpn-ipv4", + "vpn-ipv6", + ], + }, + "route_map": {"type": "str"}, + "target": {"type": "str"}, + "imported_route": {"type": "bool"}, + }, + }, + "redistribute": { + "elements": "dict", + "type": "list", + "options": { + "ospf_route": { + "type": "str", + "choices": [ + "internal", + "external", + "nssa_external_1", + "nssa_external_2", + ], + }, + "route_map": {"type": "str"}, + "protocol": { + "type": "str", + "choices": [ + "isis", + "ospfv3", + "ospf", + "attached-host", + "connected", + "rip", + "static", + ], + }, + "isis_level": { + "type": "str", + "choices": [ + "level-1", + "level-2", + "level-1-2", + ], + }, + }, + }, + "distance": { + "type": "dict", + "options": { + "internal": {"type": "int"}, + "local": {"type": "int"}, + "external": {"type": "int"}, + }, + }, + "default_metric": {"type": "int"}, + "bgp_params": { + "type": "dict", + "options": { + "control_plane_filter": {"type": "bool"}, + "convergence": { + "type": "dict", + "options": { + "slow_peer": {"type": "bool"}, + "time": {"type": "int"}, + }, + }, + "host_routes": {"type": "bool"}, + "transport": {"type": "int"}, + "next_hop_unchanged": {"type": "bool"}, + "missing_policy": { + "type": "dict", + "options": { + "action": { + "type": "str", + "choices": [ + "deny", + "permit", + "deny-in-out", + ], + }, + "direction": { + "type": "str", + "choices": ["in", "out"], + }, + }, + }, + "monitoring": {"type": "bool"}, + "allowas_in": { + "type": "dict", + "options": { + "count": {"type": "int"}, + "set": {"type": "bool"}, + }, + }, + "additional_paths": { + "type": "str", + "choices": ["install", "send", "receive"], + }, + "advertise_inactive": {"type": "bool"}, + "listen": { + "type": "dict", + "options": { + "range": { + "type": "dict", + "options": { + "peer_group": { + "type": "dict", + "options": { + "peer_filter": { + "type": "str", + }, + "remote_as": { + "type": "str", + }, + "name": { + "type": "str", + }, + }, + }, + "address": {"type": "str"}, + }, + }, + "limit": {"type": "int"}, + }, + }, + "route_reflector": { + "type": "dict", + "options": { + "preserve": {"type": "bool"}, + "set": {"type": "bool"}, + }, + }, + "always_compare_med": {"type": "bool"}, + "client_to_client": {"type": "bool"}, + "bestpath": { + "type": "dict", + "options": { + "ecmp_fast": {"type": "bool"}, + "tie_break": { + "type": "str", + "choices": [ + "cluster_list_length", + "router_id", + ], + }, + "skip": {"type": "bool"}, + "as_path": { + "type": "str", + "choices": [ + "ignore", + "multipath_relax", + ], + }, + "med": { + "type": "dict", + "options": { + "confed": {"type": "bool"}, + "missing_as_worst": { + "type": "bool", + }, + }, + }, + }, + }, + "labeled_unicast": { + "type": "str", + "choices": ["ip", "tunnel"], + }, + "log_neighbor_changes": {"type": "bool"}, + "asn": { + "type": "str", + "choices": ["asdot", "asplain"], + }, + "default": { + "type": "str", + "choices": [ + "ipv4_unicast", + "ipv6_unicast", + ], + }, + "route": {"type": "str"}, + "enforce_first_as": {"type": "bool"}, + "auto_local_addr": {"type": "bool"}, + "redistribute_internal": {"type": "bool"}, + "cluster_id": {"type": "str"}, + "confederation": { + "type": "dict", + "options": { + "peers": {"type": "str"}, + "identifier": {"type": "str"}, + }, + }, + }, + }, + "update": { + "type": "dict", + "options": { + "wait_for": { + "type": "str", + "choices": [ + "wait_for_convergence", + "wait_install", + ], + }, + "batch_size": {"type": "int"}, + }, + }, + "aggregate_address": { + "elements": "dict", + "type": "list", + "options": { + "advertise_only": {"type": "bool"}, + "match_map": {"type": "str"}, + "attribute_map": {"type": "str"}, + "as_set": {"type": "bool"}, + "summary_only": {"type": "bool"}, + "address": {"type": "str"}, + }, + }, + "neighbor": { + "elements": "dict", + "aliases": ["neighbors"], + "type": "list", + "options": { + "bfd": { + "type": "str", + "choices": ["c_bit", "enable"], + }, + "weight": {"type": "int"}, + "default_originate": { + "type": "dict", + "options": { + "route_map": {"type": "str"}, + "always": {"type": "bool"}, + }, + }, + "next_hop_v6_address": {"type": "str"}, + "route_reflector_client": {"type": "bool"}, + "ttl": {"type": "int"}, + "remove_private_as": { + "type": "dict", + "options": { + "all": {"type": "bool"}, + "set": {"type": "bool"}, + "replace_as": {"type": "bool"}, + }, + }, + "local_v6_addr": {"type": "str"}, + "transport": { + "type": "dict", + "options": { + "connection_mode": {"type": "str"}, + "remote_port": {"type": "int"}, + }, + }, + "next_hop_unchanged": {"type": "bool"}, + "monitoring": {"type": "bool"}, + "ebgp_multihop": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "ttl": {"type": "int"}, + }, + }, + "shutdown": {"type": "bool"}, + "fall_over": {"type": "bool"}, + "idle_restart_timer": {"type": "int"}, + "allowas_in": { + "type": "dict", + "options": { + "count": {"type": "int"}, + "set": {"type": "bool"}, + }, + }, + "additional_paths": { + "type": "str", + "choices": ["send", "receive"], + }, + "peer_group": {"type": "str"}, + "out_delay": {"type": "int"}, + "import_localpref": {"type": "int"}, + "prefix_list": { + "type": "dict", + "options": { + "direction": { + "type": "str", + "choices": ["in", "out"], + }, + "name": {"type": "str"}, + }, + }, + "dont_capability_negotiate": {"type": "bool"}, + "update_source": {"type": "str"}, + "export_localpref": {"type": "int"}, + "local_as": { + "type": "dict", + "options": { + "as_number": {"type": "str"}, + "fallback": {"type": "bool"}, + }, + }, + "maximum_received_routes": { + "type": "dict", + "options": { + "count": {"type": "int"}, + "warning_limit": { + "type": "dict", + "options": { + "limit_count": {"type": "int"}, + "limit_percent": { + "type": "int", + }, + }, + }, + "warning_only": {"type": "bool"}, + }, + }, + "encryption_password": { + "type": "dict", + "no_log": True, + "options": { + "password": { + "type": "str", + "no_log": True, + }, + "type": { + "type": "int", + "choices": [0, 7], + }, + }, + }, + "link_bandwidth": { + "type": "dict", + "options": { + "default": {"type": "str"}, + "auto": {"type": "bool"}, + "set": {"type": "bool"}, + "update_delay": {"type": "int"}, + }, + }, + "graceful_restart_helper": {"type": "bool"}, + "neighbor_address": { + "type": "str", + "aliases": ["peer"], + }, + "next_hop_self": {"type": "bool"}, + "route_to_peer": {"type": "bool"}, + "soft_recognition": { + "type": "str", + "choices": ["all", "None"], + }, + "graceful_restart": {"type": "bool"}, + "enforce_first_as": {"type": "bool"}, + "send_community": { + "type": "dict", + "options": { + "community_attribute": {"type": "str"}, + "sub_attribute": { + "type": "str", + "choices": [ + "extended", + "link-bandwidth", + "standard", + ], + }, + "speed": {"type": "str"}, + "divide": { + "type": "str", + "choices": ["equal", "ratio"], + }, + "link_bandwidth_attribute": { + "type": "str", + "choices": ["aggregate", "divide"], + }, + }, + }, + "description": {"type": "str"}, + "maximum_accepted_routes": { + "type": "dict", + "options": { + "count": {"type": "int"}, + "warning_limit": {"type": "int"}, + }, + }, + "auto_local_addr": {"type": "bool"}, + "metric_out": {"type": "int"}, + "timers": { + "type": "dict", + "options": { + "holdtime": {"type": "int"}, + "keepalive": {"type": "int"}, + }, + }, + "route_map": { + "type": "dict", + "options": { + "direction": { + "type": "str", + "choices": ["in", "out"], + }, + "name": {"type": "str"}, + }, + }, + "remote_as": {"type": "str"}, + }, + }, + "graceful_restart": { + "type": "dict", + "options": { + "stalepath_time": {"type": "int"}, + "restart_time": {"type": "int"}, + "set": {"type": "bool"}, + }, + }, + "timers": { + "type": "dict", + "options": { + "holdtime": {"type": "int"}, + "keepalive": {"type": "int"}, + }, + }, + "shutdown": {"type": "bool"}, + "graceful_restart_helper": {"type": "bool"}, + "ucmp": { + "type": "dict", + "options": { + "link_bandwidth": { + "type": "dict", + "options": { + "update_delay": {"type": "int"}, + "mode": { + "type": "str", + "choices": [ + "encoding_weighted", + "recursive", + "update_delay", + ], + }, + }, + }, + "fec": { + "type": "dict", + "options": { + "clear": {"type": "int"}, + "trigger": {"type": "int"}, + }, + }, + "mode": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "nexthops": {"type": "int"}, + }, + }, + }, + }, + "maximum_paths": { + "type": "dict", + "options": { + "max_equal_cost_paths": {"type": "int"}, + "max_installed_ecmp_paths": {"type": "int"}, + }, + }, + "network": { + "type": "list", + "aliases": ["networks"], + "elements": "dict", + "options": { + "route_map": {"type": "str"}, + "address": {"type": "str"}, + }, + }, + }, + }, + "access_group": { + "elements": "dict", + "type": "list", + "options": { + "direction": {"type": "str"}, + "afi": {"type": "str", "choices": ["ipv4", "ipv6"]}, + "acl_name": {"type": "str"}, + }, + }, + "graceful_restart_helper": {"type": "bool"}, + "ucmp": { + "type": "dict", + "options": { + "link_bandwidth": { + "type": "dict", + "options": { + "update_delay": {"type": "int"}, + "mode": { + "type": "str", + "choices": [ + "encoding_weighted", + "recursive", + ], + }, + }, + }, + "fec": { + "type": "dict", + "options": { + "clear": {"type": "int"}, + "trigger": {"type": "int"}, + }, + }, + "mode": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "nexthops": {"type": "int"}, + }, + }, + }, + }, + "shutdown": {"type": "bool"}, + "maximum_paths": { + "type": "dict", + "options": { + "max_equal_cost_paths": {"type": "int"}, + "max_installed_ecmp_paths": {"type": "int"}, + }, + }, + "network": { + "type": "list", + "aliases": ["networks"], + "elements": "dict", + "options": { + "route_map": {"type": "str"}, + "address": {"type": "str"}, + }, + }, + }, + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/facts/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/facts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/facts/facts.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/facts/facts.py new file mode 100644 index 000000000..beef509a8 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/facts/facts.py @@ -0,0 +1,24 @@ +# -*- 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 arg spec for the eos facts module. +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class FactsArgs(object): + """The arg spec for the eos 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/arista/eos/plugins/module_utils/network/eos/argspec/hostname/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/hostname/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/hostname/hostname.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/hostname/hostname.py new file mode 100644 index 000000000..8e866d4dc --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/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 eos_hostname module +""" + + +class HostnameArgs(object): # pylint: disable=R0903 + """The arg spec for the eos_hostname module""" + + argument_spec = { + "config": {"type": "dict", "options": {"hostname": {"type": "str"}}}, + "running_config": {"type": "str"}, + "state": { + "type": "str", + "choices": [ + "deleted", + "merged", + "overridden", + "replaced", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/interfaces/interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/interfaces/interfaces.py new file mode 100644 index 000000000..bd861c6b5 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/interfaces/interfaces.py @@ -0,0 +1,72 @@ +# -*- 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 eos_interfaces module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class InterfacesArgs(object): + """The arg spec for the eos_interfaces module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "name": {"required": True, "type": "str"}, + "description": {"required": False, "type": "str"}, + "enabled": { + "default": True, + "required": False, + "type": "bool", + }, + "mtu": {"required": False, "type": "int"}, + "speed": {"required": False, "type": "str"}, + "duplex": {"required": False, "type": "str"}, + "mode": {"choices": ["layer2", "layer3"], "type": "str"}, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "default": "merged", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "required": False, + "type": "str", + }, + } diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l2_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l2_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l2_interfaces/l2_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l2_interfaces/l2_interfaces.py new file mode 100644 index 000000000..bdf7e7451 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l2_interfaces/l2_interfaces.py @@ -0,0 +1,77 @@ +# -*- 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 eos_l2_interfaces module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class L2_interfacesArgs(object): + """The arg spec for the eos_l2_interfaces module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "access": { + "options": {"vlan": {"type": "int"}}, + "type": "dict", + }, + "mode": {"type": "str", "choices": ["access", "trunk"]}, + "name": {"required": True, "type": "str"}, + "trunk": { + "options": { + "native_vlan": {"type": "int"}, + "trunk_allowed_vlans": { + "type": "list", + "elements": "str", + }, + }, + "type": "dict", + }, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "default": "merged", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "required": False, + "type": "str", + }, + } diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l3_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l3_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l3_interfaces/l3_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l3_interfaces/l3_interfaces.py new file mode 100644 index 000000000..a4ac42f63 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/l3_interfaces/l3_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 eos_l3_interfaces module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class L3_interfacesArgs(object): + """The arg spec for the eos_l3_interfaces module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "name": {"required": True, "type": "str"}, + "ipv4": { + "elements": "dict", + "options": { + "address": {"type": "str"}, + "secondary": {"type": "bool"}, + "virtual": {"type": "bool"}, + }, + "type": "list", + }, + "ipv6": { + "elements": "dict", + "options": {"address": {"type": "str"}}, + "type": "list", + }, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "default": "merged", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "type": "str", + }, + } diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp/lacp.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp/lacp.py new file mode 100644 index 000000000..bbb9df8db --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp/lacp.py @@ -0,0 +1,63 @@ +# -*- 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 eos_lacp module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class LacpArgs(object): + """The arg spec for the eos_lacp module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "options": { + "system": { + "options": {"priority": {"type": "int"}}, + "type": "dict", + }, + }, + "type": "dict", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp_interfaces/lacp_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 000000000..bea5a062b --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,69 @@ +# +# -*- 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 eos_lacp_interfaces module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class Lacp_interfacesArgs(object): + """The arg spec for the eos_lacp_interfaces module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "name": {"type": "str"}, + "port_priority": {"type": "int"}, + "timer": { + "choices": ["fast", "normal"], + "type": "str", + "aliases": ["rate"], + }, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lag_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lag_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lag_interfaces/lag_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lag_interfaces/lag_interfaces.py new file mode 100644 index 000000000..0f2aa73dd --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lag_interfaces/lag_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 eos_lag_interfaces module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class Lag_interfacesArgs(object): + """The arg spec for the eos_lag_interfaces module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "name": {"required": True, "type": "str"}, + "members": { + "elements": "dict", + "options": { + "member": {"type": "str"}, + "mode": { + "choices": ["active", "on", "passive"], + "type": "str", + }, + }, + "type": "list", + }, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "default": "merged", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "type": "str", + }, + } diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_global/lldp_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_global/lldp_global.py new file mode 100644 index 000000000..cc0ea3190 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_global/lldp_global.py @@ -0,0 +1,75 @@ +# +# -*- 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 eos_lldp_global module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class Lldp_globalArgs(object): + """The arg spec for the eos_lldp_global module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "options": { + "holdtime": {"type": "int"}, + "reinit": {"type": "int"}, + "timer": {"type": "int"}, + "tlv_select": { + "options": { + "link_aggregation": {"type": "bool"}, + "management_address": {"type": "bool"}, + "max_frame_size": {"type": "bool"}, + "port_description": {"type": "bool"}, + "system_capabilities": {"type": "bool"}, + "system_description": {"type": "bool"}, + "system_name": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_interfaces/lldp_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 000000000..fa0d409f0 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,65 @@ +# +# -*- 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 eos_lldp_interfaces module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class Lldp_interfacesArgs(object): + """The arg spec for the eos_lldp_interfaces module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "name": {"type": "str"}, + "receive": {"type": "bool"}, + "transmit": {"type": "bool"}, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/logging_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/logging_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/logging_global/logging_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/logging_global/logging_global.py new file mode 100644 index 000000000..7899a3933 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/logging_global/logging_global.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 + +############################################# +# 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 eos_logging_global module +""" + + +class Logging_globalArgs(object): # pylint: disable=R0903 + """The arg spec for the eos_logging_global module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "type": "dict", + "options": { + "buffered": { + "type": "dict", + "options": { + "severity": { + "type": "str", + "choices": [ + "alerts", + "critical", + "debugging", + "emergencies", + "errors", + "informational", + "notifications", + "warnings", + ], + }, + "buffer_size": {"type": "int"}, + }, + }, + "console": { + "type": "dict", + "options": { + "severity": { + "type": "str", + "choices": [ + "alerts", + "critical", + "debugging", + "emergencies", + "errors", + "informational", + "notifications", + "warnings", + ], + }, + }, + }, + "event": { + "type": "str", + "choices": [ + "link-status", + "port-channel", + "spanning-tree", + ], + }, + "facility": { + "type": "str", + "choices": [ + "auth", + "cron", + "daemon", + "kern", + "local0", + "local1", + "local2", + "local3", + "local4", + "local5", + "local6", + "local7", + "lpr", + "mail", + "news", + "sys10", + "sys11", + "sys12", + "sys13", + "sys14", + "sys9", + "syslog", + "user", + "uucp", + ], + }, + "format": { + "type": "dict", + "options": { + "hostname": {"type": "str"}, + "timestamp": { + "type": "dict", + "options": { + "high_resolution": {"type": "bool"}, + "traditional": { + "type": "dict", + "options": { + "state": { + "type": "str", + "choices": ["enabled", "disabled"], + }, + "timezone": {"type": "bool"}, + "year": {"type": "bool"}, + }, + }, + }, + }, + "sequence_numbers": {"type": "bool"}, + }, + }, + "hosts": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str"}, + "add": {"type": "bool"}, + "remove": {"type": "bool"}, + "protocol": {"type": "str", "choices": ["tcp", "udp"]}, + "port": {"type": "int"}, + }, + }, + "level": { + "type": "dict", + "options": { + "facility": {"type": "str"}, + "severity": { + "type": "str", + "choices": [ + "alerts", + "critical", + "debugging", + "emergencies", + "errors", + "informational", + "notifications", + "warnings", + ], + }, + }, + }, + "monitor": {"type": "str"}, + "turn_on": {"type": "bool"}, + "persistent": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "size": {"type": "int"}, + }, + }, + "policy": { + "type": "dict", + "options": { + "invert_result": {"type": "bool"}, + "match_list": {"type": "str"}, + }, + }, + "qos": {"type": "int"}, + "relogging_interval": {"type": "int"}, + "repeat_messages": {"type": "bool"}, + "source_interface": {"type": "str"}, + "synchronous": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "level": {"type": "str"}, + }, + }, + "trap": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "severity": { + "type": "str", + "choices": [ + "alerts", + "critical", + "debugging", + "emergencies", + "errors", + "informational", + "notifications", + "warnings", + ], + }, + }, + }, + "vrfs": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str"}, + "hosts": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str"}, + "add": {"type": "bool"}, + "remove": {"type": "bool"}, + "protocol": { + "type": "str", + "choices": ["tcp", "udp"], + }, + "port": {"type": "int"}, + }, + }, + "source_interface": {"type": "str"}, + }, + }, + }, + }, + "running_config": {"type": "str"}, + "state": { + "type": "str", + "choices": [ + "deleted", + "merged", + "overridden", + "replaced", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ntp_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ntp_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ntp_global/ntp_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ntp_global/ntp_global.py new file mode 100644 index 000000000..ad3a231dd --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ntp_global/ntp_global.py @@ -0,0 +1,123 @@ +# -*- 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 eos_ntp_global module +""" + + +class Ntp_globalArgs(object): # pylint: disable=R0903 + """The arg spec for the eos_ntp_global module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "type": "dict", + "options": { + "authenticate": { + "type": "dict", + "options": { + "enable": {"type": "bool"}, + "servers": {"type": "bool"}, + }, + }, + "authentication_keys": { + "type": "list", + "elements": "dict", + "no_log": False, + "options": { + "id": {"type": "int"}, + "algorithm": { + "type": "str", + "choices": ["md5", "sha1"], + }, + "encryption": {"type": "int", "choices": [0, 7]}, + "key": {"type": "str", "no_log": True}, + }, + }, + "local_interface": {"type": "str"}, + "qos_dscp": {"type": "int"}, + "serve": { + "type": "dict", + "options": { + "all": {"type": "bool"}, + "access_lists": { + "type": "list", + "elements": "dict", + "options": { + "afi": {"type": "str"}, + "acls": { + "type": "list", + "elements": "dict", + "options": { + "acl_name": {"type": "str"}, + "direction": { + "type": "str", + "choices": ["in", "out"], + }, + "vrf": {"type": "str"}, + }, + }, + }, + }, + }, + }, + "servers": { + "type": "list", + "elements": "dict", + "options": { + "vrf": {"type": "str"}, + "server": {"type": "str", "required": True}, + "burst": {"type": "bool"}, + "iburst": {"type": "bool"}, + "key_id": {"type": "int"}, + "local_interface": {"type": "str"}, + "source": {"type": "str"}, + "maxpoll": {"type": "int"}, + "minpoll": {"type": "int"}, + "prefer": {"type": "bool"}, + "version": {"type": "int"}, + }, + }, + "trusted_key": {"type": "str", "no_log": False}, + }, + }, + "running_config": {"type": "str"}, + "state": { + "type": "str", + "choices": [ + "deleted", + "merged", + "overridden", + "replaced", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospf_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospf_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospf_interfaces/ospf_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospf_interfaces/ospf_interfaces.py new file mode 100644 index 000000000..3e8dcbc9f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospf_interfaces/ospf_interfaces.py @@ -0,0 +1,180 @@ +# -*- 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 eos_ospf_interfaces module +""" + + +class Ospf_interfacesArgs(object): # pylint: disable=R0903 + """The arg spec for the eos_ospf_interfaces module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "state": { + "default": "merged", + "type": "str", + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "parsed", + "rendered", + ], + }, + "running_config": {"type": "str"}, + "config": { + "elements": "dict", + "type": "list", + "options": { + "name": {"type": "str"}, + "address_family": { + "elements": "dict", + "type": "list", + "options": { + "ip_params": { + "elements": "dict", + "type": "list", + "options": { + "retransmit_interval": {"type": "int"}, + "cost": {"type": "int"}, + "afi": { + "required": True, + "type": "str", + "choices": ["ipv4", "ipv6"], + }, + "area": { + "type": "dict", + "options": { + "area_id": { + "required": True, + "type": "str", + }, + }, + }, + "bfd": {"type": "bool"}, + "mtu_ignore": {"type": "bool"}, + "priority": {"type": "int"}, + "dead_interval": {"type": "int"}, + "hello_interval": {"type": "int"}, + "passive_interface": {"type": "bool"}, + "transmit_delay": {"type": "int"}, + "network": {"type": "str"}, + }, + }, + "encryption_v3": { + "type": "dict", + "options": { + "key": {"type": "str", "no_log": True}, + "algorithm": { + "type": "str", + "choices": ["md5", "sha1"], + }, + "encryption": { + "type": "str", + "choices": [ + "3des-cbc", + "aes-128-cbc", + "aes-192-cbc", + "aes-256-cbc", + "null", + ], + }, + "keytype": {"type": "str", "no_log": False}, + "spi": {"type": "int"}, + "passphrase": {"type": "str", "no_log": True}, + }, + }, + "cost": {"type": "int"}, + "afi": { + "required": True, + "type": "str", + "choices": ["ipv4", "ipv6"], + }, + "authentication_v2": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "message_digest": {"type": "bool"}, + }, + }, + "bfd": {"type": "bool"}, + "authentication_v3": { + "type": "dict", + "options": { + "key": {"type": "str", "no_log": True}, + "spi": {"type": "int"}, + "keytype": {"type": "str", "no_log": False}, + "passphrase": {"type": "str", "no_log": True}, + "algorithm": { + "type": "str", + "choices": ["md5", "sha1"], + }, + }, + }, + "retransmit_interval": {"type": "int"}, + "message_digest_key": { + "no_log": False, + "type": "dict", + "options": { + "key_id": {"type": "int"}, + "key": {"type": "str", "no_log": True}, + "encryption": {"type": "str"}, + }, + }, + "mtu_ignore": {"type": "bool"}, + "priority": {"type": "int"}, + "area": { + "type": "dict", + "options": { + "area_id": {"required": True, "type": "str"}, + }, + }, + "dead_interval": {"type": "int"}, + "shutdown": {"type": "bool"}, + "passive_interface": {"type": "bool"}, + "authentication_key": { + "type": "dict", + "no_log": False, + "options": { + "encryption": {"type": "str"}, + "key": {"type": "str", "no_log": True}, + }, + }, + "hello_interval": {"type": "int"}, + "transmit_delay": {"type": "int"}, + "network": {"type": "str"}, + }, + }, + }, + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv2/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv2/ospfv2.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv2/ospfv2.py new file mode 100644 index 000000000..42388d8d5 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv2/ospfv2.py @@ -0,0 +1,340 @@ +# +# -*- 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 + +############################################# +# 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 eos_ospfv2 module +""" + + +class Ospfv2Args(object): # pylint: disable=R0903 + """The arg spec for the eos_ospfv2 module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "options": { + "processes": { + "elements": "dict", + "options": { + "process_id": {"type": "int"}, + "vrf": {"type": "str"}, + "traffic_engineering": {"type": "bool"}, + "adjacency": { + "options": { + "exchange_start": { + "options": {"threshold": {"type": "int"}}, + "type": "dict", + }, + }, + "type": "dict", + }, + "areas": { + "elements": "dict", + "options": { + "default_cost": {"type": "int"}, + "filter": { + "options": { + "address": {"type": "str"}, + "prefix_list": {"type": "str"}, + "subnet_address": {"type": "str"}, + "subnet_mask": {"type": "str"}, + }, + "type": "dict", + }, + "not_so_stubby": { + "options": { + "default_information_originate": { + "options": { + "metric": {"type": "int"}, + "metric_type": {"type": "int"}, + "nssa_only": {"type": "bool"}, + }, + "type": "dict", + }, + "no_summary": {"type": "bool"}, + "nssa_only": {"type": "bool"}, + "lsa": {"type": "bool"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + "nssa": { + "options": { + "default_information_originate": { + "options": { + "metric": {"type": "int"}, + "metric_type": {"type": "int"}, + "nssa_only": {"type": "bool"}, + }, + "type": "dict", + }, + "no_summary": {"type": "bool"}, + "nssa_only": {"type": "bool"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + "area_id": {"type": "str"}, + "range": { + "options": { + "address": {"type": "str"}, + "advertise": {"type": "bool"}, + "cost": {"type": "int"}, + "subnet_address": {"type": "str"}, + "subnet_mask": {"type": "str"}, + }, + "type": "dict", + }, + "stub": { + "options": { + "no_summary": {"type": "bool"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "list", + }, + "auto_cost": { + "options": { + "reference_bandwidth": {"type": "int"}, + }, + "type": "dict", + }, + "bfd": { + "options": {"all_interfaces": {"type": "bool"}}, + "type": "dict", + }, + "default_information": { + "options": { + "always": {"type": "bool"}, + "metric": {"type": "int"}, + "metric_type": {"type": "int"}, + "originate": {"type": "bool"}, + "route_map": {"type": "str"}, + }, + "type": "dict", + }, + "default_metric": {"type": "int"}, + "distance": { + "options": { + "external": {"type": "int"}, + "inter_area": {"type": "int"}, + "intra_area": {"type": "int"}, + }, + "type": "dict", + }, + "distribute_list": { + "options": { + "prefix_list": {"type": "str"}, + "route_map": {"type": "str"}, + }, + "type": "dict", + }, + "dn_bit_ignore": {"type": "bool"}, + "fips_restrictions": {"type": "str"}, + "graceful_restart": { + "options": { + "grace_period": {"type": "int"}, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + "graceful_restart_helper": {"type": "bool"}, + "log_adjacency_changes": { + "options": {"detail": {"type": "bool"}}, + "type": "dict", + }, + "max_lsa": { + "options": { + "count": {"type": "int"}, + "ignore_count": {"type": "int"}, + "ignore_time": {"type": "int"}, + "reset_time": {"type": "int"}, + "threshold": {"type": "int"}, + "warning": {"type": "bool"}, + }, + "type": "dict", + }, + "max_metric": { + "options": { + "router_lsa": { + "options": { + "set": {"type": "bool"}, + "include_stub": {"type": "bool"}, + "on_startup": { + "options": { + "wait_period": {"type": "int"}, + }, + "type": "dict", + }, + "summary_lsa": { + "options": { + "max_metric_value": { + "type": "int", + }, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + "external_lsa": { + "options": { + "max_metric_value": { + "type": "int", + }, + "set": {"type": "bool"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "maximum_paths": {"type": "int"}, + "mpls_ldp": {"type": "bool"}, + "networks": { + "elements": "dict", + "options": { + "area": {"type": "str"}, + "mask": {"type": "str"}, + "network_address": {"type": "str"}, + "prefix": {"type": "str"}, + }, + "type": "list", + }, + "passive_interface": { + "type": "dict", + "options": { + "interface_list": {"type": "str"}, + "default": {"type": "bool"}, + }, + }, + "point_to_point": {"type": "bool"}, + "redistribute": { + "elements": "dict", + "options": { + "isis_level": {"type": "str"}, + "route_map": {"type": "str"}, + "routes": {"type": "str"}, + }, + "type": "list", + }, + "retransmission_threshold": {"type": "int"}, + "rfc1583compatibility": {"type": "bool"}, + "router_id": {"type": "str"}, + "shutdown": {"type": "bool"}, + "summary_address": { + "options": { + "address": {"type": "str"}, + "attribute_map": {"type": "str"}, + "mask": {"type": "str"}, + "not_advertise": {"type": "bool"}, + "prefix": {"type": "str"}, + "tag": {"type": "int"}, + }, + "type": "dict", + }, + "timers": { + "elements": "dict", + "options": { + "lsa": { + "options": { + "rx": { + "options": { + "min_interval": { + "type": "int", + }, + }, + "type": "dict", + }, + "tx": { + "options": { + "delay": { + "options": { + "initial": { + "type": "int", + }, + "max": {"type": "int"}, + "min": {"type": "int"}, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + }, + "type": "dict", + }, + "out_delay": {"type": "int"}, + "pacing": {"type": "int"}, + "spf": { + "options": { + "initial": {"type": "int"}, + "max": {"type": "int"}, + "min": {"type": "int"}, + "seconds": {"type": "int"}, + }, + "type": "dict", + }, + "throttle": { + "options": { + "attr": {"type": "str"}, + "initial": {"type": "int"}, + "max": {"type": "int"}, + "min": {"type": "int"}, + }, + "type": "dict", + }, + }, + "type": "list", + }, + }, + "type": "list", + }, + }, + "type": "dict", + }, + "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/arista/eos/plugins/module_utils/network/eos/argspec/ospfv3/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv3/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv3/ospfv3.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv3/ospfv3.py new file mode 100644 index 000000000..ece4db0cd --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/ospfv3/ospfv3.py @@ -0,0 +1,532 @@ +# -*- 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 eos_ospfv3 module +""" + + +class Ospfv3Args(object): # pylint: disable=R0903 + """The arg spec for the eos_ospfv3 module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "running_config": {"type": "str"}, + "state": { + "default": "merged", + "type": "str", + "choices": [ + "deleted", + "merged", + "overridden", + "replaced", + "gathered", + "rendered", + "parsed", + ], + }, + "config": { + "type": "dict", + "options": { + "processes": { + "elements": "dict", + "type": "list", + "options": { + "router_id": {"type": "str"}, + "shutdown": {"type": "bool"}, + "fips_restrictions": {"type": "bool"}, + "graceful_restart_helper": {"type": "bool"}, + "adjacency": { + "type": "dict", + "options": { + "exchange_start": { + "type": "dict", + "options": {"threshold": {"type": "int"}}, + }, + }, + }, + "max_metric": { + "type": "dict", + "options": { + "router_lsa": { + "type": "dict", + "options": { + "external_lsa": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "max_metric_value": { + "type": "int", + }, + }, + }, + "summary_lsa": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "max_metric_value": { + "type": "int", + }, + }, + }, + "set": {"type": "bool"}, + "on_startup": { + "type": "dict", + "options": { + "wait_for_bgp": { + "type": "bool", + }, + "wait_period": {"type": "int"}, + }, + }, + "include_stub": {"type": "bool"}, + }, + }, + }, + }, + "log_adjacency_changes": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "detail": {"type": "bool"}, + }, + }, + "graceful_restart": { + "type": "dict", + "options": { + "grace_period": {"type": "int"}, + "set": {"type": "bool"}, + }, + }, + "timers": { + "type": "dict", + "options": { + "throttle": { + "type": "dict", + "options": { + "max": {"type": "int"}, + "initial": {"type": "int"}, + "min": {"type": "int"}, + "spf": {"type": "bool"}, + "lsa": {"type": "bool"}, + }, + }, + "spf": { + "type": "dict", + "options": { + "max": {"type": "int"}, + "initial": {"type": "int"}, + "min": {"type": "int"}, + }, + }, + "lsa": { + "type": "raw", + "options": { + "max": {"type": "int"}, + "initial": {"type": "int"}, + "min": {"type": "int"}, + "direction": { + "type": "str", + "choices": ["rx", "tx"], + }, + }, + }, + "out_delay": {"type": "int"}, + "pacing": {"type": "int"}, + }, + }, + "vrf": {"type": "str"}, + "auto_cost": { + "type": "dict", + "options": { + "reference_bandwidth": {"type": "int"}, + }, + }, + "passive_interface": {"type": "bool"}, + "bfd": { + "type": "dict", + "options": {"all_interfaces": {"type": "bool"}}, + }, + "areas": { + "elements": "dict", + "type": "list", + "options": { + "area_id": {"type": "str"}, + "encryption": { + "type": "dict", + "options": { + "hidden_key": {"type": "bool"}, + "key": {"type": "str", "no_log": True}, + "algorithm": { + "type": "str", + "choices": ["sha1", "md5"], + }, + "encrypt_key": {"type": "bool"}, + "encryption": { + "type": "str", + "choices": [ + "3des-cbc", + "aes-128-cbc", + "aes-192-cbc", + "aes-256-cbc", + "null", + ], + }, + "spi": {"type": "int"}, + "passphrase": { + "type": "str", + "no_log": True, + }, + }, + }, + "nssa": { + "type": "dict", + "options": { + "translate": {"type": "bool"}, + "default_information_originate": { + "type": "dict", + "options": { + "metric_type": {"type": "int"}, + "metric": {"type": "int"}, + "nssa_only": {"type": "bool"}, + "set": {"type": "bool"}, + }, + }, + "nssa_only": {"type": "bool"}, + "set": {"type": "bool"}, + "no_summary": {"type": "bool"}, + }, + }, + "stub": { + "type": "dict", + "options": { + "summary_lsa": {"type": "bool"}, + "set": {"type": "bool"}, + }, + }, + "default_cost": {"type": "int"}, + "authentication": { + "type": "dict", + "options": { + "hidden_key": {"type": "bool"}, + "key": {"type": "str", "no_log": True}, + "algorithm": { + "type": "str", + "choices": ["md5", "sha1"], + }, + "encrypt_key": {"type": "bool"}, + "spi": {"type": "int"}, + "passphrase": { + "type": "str", + "no_log": True, + }, + }, + }, + }, + }, + "address_family": { + "elements": "dict", + "type": "list", + "options": { + "router_id": {"type": "str"}, + "distance": {"type": "int"}, + "redistribute": { + "elements": "dict", + "type": "list", + "options": { + "routes": { + "type": "str", + "choices": [ + "bgp", + "connected", + "static", + ], + }, + "route_map": {"type": "str"}, + }, + }, + "default_information": { + "type": "dict", + "options": { + "metric_type": {"type": "int"}, + "always": {"type": "bool"}, + "metric": {"type": "int"}, + "originate": {"type": "bool"}, + "route_map": {"type": "str"}, + }, + }, + "afi": { + "choices": ["ipv4", "ipv6"], + "type": "str", + }, + "fips_restrictions": {"type": "bool"}, + "default_metric": {"type": "int"}, + "maximum_paths": {"type": "int"}, + "adjacency": { + "type": "dict", + "options": { + "exchange_start": { + "type": "dict", + "options": { + "threshold": {"type": "int"}, + }, + }, + }, + }, + "max_metric": { + "type": "dict", + "options": { + "router_lsa": { + "type": "dict", + "options": { + "external_lsa": { + "type": "dict", + "options": { + "set": { + "type": "bool", + }, + "max_metric_value": { + "type": "int", + }, + }, + }, + "summary_lsa": { + "type": "dict", + "options": { + "set": { + "type": "bool", + }, + "max_metric_value": { + "type": "int", + }, + }, + }, + "set": {"type": "bool"}, + "on_startup": { + "type": "dict", + "options": { + "wait_for_bgp": { + "type": "bool", + }, + "wait_period": { + "type": "int", + }, + }, + }, + "include_stub": { + "type": "bool", + }, + }, + }, + }, + }, + "log_adjacency_changes": { + "type": "dict", + "options": { + "set": {"type": "bool"}, + "detail": {"type": "bool"}, + }, + }, + "timers": { + "type": "dict", + "options": { + "throttle": { + "type": "dict", + "options": { + "max": {"type": "int"}, + "initial": {"type": "int"}, + "min": {"type": "int"}, + "spf": {"type": "bool"}, + "lsa": {"type": "bool"}, + }, + }, + "spf": { + "type": "dict", + "options": { + "max": {"type": "int"}, + "initial": {"type": "int"}, + "min": {"type": "int"}, + }, + }, + "lsa": { + "type": "raw", + "options": { + "max": {"type": "int"}, + "initial": {"type": "int"}, + "min": {"type": "int"}, + "direction": { + "type": "str", + "choices": ["rx", "tx"], + }, + }, + }, + "out_delay": {"type": "int"}, + "pacing": {"type": "int"}, + }, + }, + "shutdown": {"type": "bool"}, + "auto_cost": { + "type": "dict", + "options": { + "reference_bandwidth": {"type": "int"}, + }, + }, + "graceful_restart_helper": {"type": "bool"}, + "passive_interface": {"type": "bool"}, + "bfd": { + "type": "dict", + "options": { + "all_interfaces": {"type": "bool"}, + }, + }, + "areas": { + "elements": "dict", + "type": "list", + "options": { + "ranges": { + "elements": "dict", + "type": "list", + "options": { + "subnet_mask": {"type": "str"}, + "advertise": {"type": "bool"}, + "cost": {"type": "int"}, + "subnet_address": { + "type": "str", + }, + "address": {"type": "str"}, + }, + }, + "area_id": {"type": "str"}, + "encryption": { + "type": "dict", + "options": { + "hidden_key": {"type": "bool"}, + "key": { + "type": "str", + "no_log": True, + }, + "algorithm": { + "type": "str", + "choices": ["sha1", "md5"], + }, + "encrypt_key": { + "type": "bool", + }, + "encryption": { + "type": "str", + "choices": [ + "3des-cbc", + "aes-128-cbc", + "aes-192-cbc", + "aes-256-cbc", + "null", + ], + }, + "spi": {"type": "int"}, + "passphrase": { + "type": "str", + "no_log": True, + }, + }, + }, + "nssa": { + "type": "dict", + "options": { + "translate": {"type": "bool"}, + "default_information_originate": { + "type": "dict", + "options": { + "metric_type": { + "type": "int", + }, + "metric": { + "type": "int", + }, + "nssa_only": { + "type": "bool", + }, + "set": { + "type": "bool", + }, + }, + }, + "nssa_only": {"type": "bool"}, + "set": {"type": "bool"}, + "no_summary": {"type": "bool"}, + }, + }, + "stub": { + "type": "dict", + "options": { + "summary_lsa": { + "type": "bool", + }, + "set": {"type": "bool"}, + }, + }, + "default_cost": {"type": "int"}, + "authentication": { + "type": "dict", + "options": { + "hidden_key": {"type": "bool"}, + "key": { + "type": "str", + "no_log": True, + }, + "algorithm": { + "type": "str", + "choices": ["md5", "sha1"], + }, + "encrypt_key": { + "type": "bool", + }, + "spi": {"type": "int"}, + "passphrase": { + "type": "str", + "no_log": True, + }, + }, + }, + }, + }, + "graceful_restart": { + "type": "dict", + "options": { + "grace_period": {"type": "int"}, + "set": {"type": "bool"}, + }, + }, + }, + }, + }, + }, + }, + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/prefix_lists/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/prefix_lists/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/prefix_lists/prefix_lists.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/prefix_lists/prefix_lists.py new file mode 100644 index 000000000..665c899a2 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/prefix_lists/prefix_lists.py @@ -0,0 +1,100 @@ +# -*- 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 eos_prefix_lists module +""" + + +class Prefix_listsArgs(object): # pylint: disable=R0903 + """The arg spec for the eos_prefix_lists module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "type": "list", + "elements": "dict", + "options": { + "afi": { + "type": "str", + "required": True, + "choices": ["ipv4", "ipv6"], + }, + "prefix_lists": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str", "required": True}, + "entries": { + "type": "list", + "elements": "dict", + "options": { + "action": { + "type": "str", + "choices": ["deny", "permit"], + }, + "address": {"type": "str"}, + "match": { + "type": "dict", + "options": { + "operator": { + "type": "str", + "choices": ["eq", "le", "ge"], + }, + "masklen": {"type": "int"}, + }, + }, + "sequence": {"type": "int"}, + "resequence": { + "type": "dict", + "options": { + "default": {"type": "bool"}, + "start_seq": {"type": "int"}, + "step": {"type": "int"}, + }, + }, + }, + }, + }, + }, + }, + }, + "running_config": {"type": "str"}, + "state": { + "type": "str", + "choices": [ + "deleted", + "merged", + "overridden", + "replaced", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/route_maps/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/route_maps/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/route_maps/route_maps.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/route_maps/route_maps.py new file mode 100644 index 000000000..ab6cb7574 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/route_maps/route_maps.py @@ -0,0 +1,367 @@ +# -*- 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 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 eos_route_maps module +""" + + +class Route_mapsArgs(object): # pylint: disable=R0903 + """The arg spec for the eos_route_maps module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "running_config": {"type": "str"}, + "state": { + "default": "merged", + "type": "str", + "choices": [ + "deleted", + "merged", + "overridden", + "replaced", + "gathered", + "rendered", + "parsed", + ], + }, + "config": { + "elements": "dict", + "type": "list", + "options": { + "route_map": {"type": "str"}, + "entries": { + "elements": "dict", + "type": "list", + "options": { + "set": { + "type": "dict", + "options": { + "extcommunity": { + "type": "dict", + "options": { + "rt": { + "type": "dict", + "options": { + "vpn": {"type": "str"}, + "additive": {"type": "bool"}, + "delete": {"type": "bool"}, + }, + }, + "none": {"type": "bool"}, + "soo": { + "type": "dict", + "options": { + "vpn": {"type": "str"}, + "additive": {"type": "bool"}, + "delete": {"type": "bool"}, + }, + }, + "lbw": { + "type": "dict", + "options": { + "aggregate": {"type": "bool"}, + "divide": { + "type": "str", + "choices": [ + "equal", + "ration", + ], + }, + "value": {"type": "str"}, + }, + }, + }, + }, + "origin": { + "type": "str", + "choices": ["egp", "igp", "incomplete"], + }, + "isis_level": {"type": "str"}, + "weight": {"type": "int"}, + "distance": {"type": "int"}, + "ip": { + "type": "dict", + "options": { + "peer_address": {"type": "bool"}, + "unchanged": {"type": "bool"}, + "address": {"type": "str"}, + }, + }, + "metric": { + "type": "dict", + "options": { + "add": { + "type": "str", + "choices": [ + "igp-metric", + "igp-nexthop-cost", + ], + }, + "igp_param": { + "type": "str", + "choices": [ + "igp-metric", + "igp-nexthop-cost", + ], + }, + "value": {"type": "str"}, + }, + }, + "nexthop": { + "type": "dict", + "options": { + "value": {"type": "int"}, + "max_metric": {"type": "bool"}, + }, + }, + "as_path": { + "type": "dict", + "options": { + "match": { + "type": "dict", + "options": { + "as_number": {"type": "str"}, + "none": {"type": "bool"}, + }, + }, + "prepend": { + "type": "dict", + "options": { + "last_as": {"type": "int"}, + "as_number": {"type": "str"}, + }, + }, + }, + }, + "community_attributes": { + "type": "dict", + "options": { + "none": {"type": "bool"}, + "graceful_shutdown": {"type": "bool"}, + "community": { + "type": "dict", + "options": { + "additive": {"type": "bool"}, + "local_as": {"type": "bool"}, + "no_export": {"type": "bool"}, + "list": {"type": "str"}, + "number": {"type": "str"}, + "no_advertise": { + "type": "bool", + }, + "internet": {"type": "bool"}, + "graceful_shutdown": { + "type": "bool", + }, + "delete": {"type": "bool"}, + }, + }, + }, + }, + "bgp": {"type": "int"}, + "tag": {"type": "int"}, + "local_preference": {"type": "int"}, + "segment_index": {"type": "int"}, + "ipv6": { + "type": "dict", + "options": { + "peer_address": {"type": "bool"}, + "unchanged": {"type": "bool"}, + "address": {"type": "str"}, + }, + }, + "metric_type": { + "type": "str", + "choices": ["type-1", "type-2"], + }, + "evpn": {"type": "bool"}, + }, + }, + "description": {"type": "str"}, + "sequence": {"type": "int"}, + "source": {"type": "dict"}, + "continue_sequence": {"type": "int"}, + "statement": {"type": "str"}, + "action": { + "type": "str", + "choices": ["deny", "permit"], + }, + "sub_route_map": { + "type": "dict", + "options": { + "name": {"type": "str"}, + "invert_result": {"type": "bool"}, + }, + }, + "match": { + "type": "dict", + "options": { + "extcommunity": { + "type": "dict", + "options": { + "community_list": {"type": "str"}, + "exact_match": {"type": "bool"}, + }, + }, + "router_id": {"type": "str"}, + "invert_result": { + "type": "dict", + "options": { + "extcommunity": { + "type": "dict", + "options": { + "community_list": { + "type": "str", + }, + "exact_match": { + "type": "bool", + }, + }, + }, + "large_community": { + "type": "dict", + "options": { + "community_list": { + "type": "str", + }, + "exact_match": { + "type": "bool", + }, + }, + }, + "aggregate_role": { + "type": "dict", + "options": { + "contributor": { + "type": "bool", + }, + "route_map": {"type": "str"}, + }, + }, + "as_path": { + "type": "dict", + "options": { + "path_list": {"type": "str"}, + "length": {"type": "str"}, + }, + }, + "community": { + "type": "dict", + "options": { + "community_list": { + "type": "str", + }, + "instances": {"type": "str"}, + "exact_match": { + "type": "bool", + }, + }, + }, + }, + }, + "large_community": { + "type": "dict", + "options": { + "community_list": {"type": "str"}, + "exact_match": {"type": "bool"}, + }, + }, + "ip": { + "type": "dict", + "options": { + "resolved_next_hop": {"type": "str"}, + "next_hop": {"type": "str"}, + "address": { + "type": "dict", + "options": { + "prefix_list": {"type": "str"}, + "dynamic": {"type": "bool"}, + "access_list": {"type": "str"}, + }, + }, + }, + }, + "aggregate_role": { + "type": "dict", + "options": { + "contributor": {"type": "bool"}, + "route_map": {"type": "str"}, + }, + }, + "isis_level": {"type": "str"}, + "community": { + "type": "dict", + "options": { + "community_list": {"type": "str"}, + "instances": {"type": "str"}, + "exact_match": {"type": "bool"}, + }, + }, + "as_path": { + "type": "dict", + "options": { + "path_list": {"type": "str"}, + "length": {"type": "str"}, + }, + }, + "route_type": {"type": "str"}, + "as": {"type": "int"}, + "tag": {"type": "int"}, + "local_preference": {"type": "int"}, + "ipv6": { + "type": "dict", + "options": { + "resolved_next_hop": {"type": "str"}, + "next_hop": {"type": "str"}, + "address": { + "type": "dict", + "options": { + "prefix_list": {"type": "str"}, + "dynamic": {"type": "bool"}, + "access_list": {"type": "str"}, + }, + }, + }, + }, + "metric_type": { + "type": "str", + "choices": ["type-1", "type-2"], + }, + "interface": {"type": "str"}, + "source_protocol": {"type": "str"}, + "metric": {"type": "int"}, + }, + }, + }, + }, + }, + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/snmp_server/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/snmp_server/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/snmp_server/snmp_server.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/snmp_server/snmp_server.py new file mode 100644 index 000000000..654201e44 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/snmp_server/snmp_server.py @@ -0,0 +1,389 @@ +# -*- 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 eos_snmp_server module +""" + + +class Snmp_serverArgs(object): # pylint: disable=R0903 + """The arg spec for the eos_snmp_server module""" + + argument_spec = { + "config": { + "type": "dict", + "options": { + "chassis_id": {"type": "str"}, + "communities": { + "type": "list", + "elements": "dict", + "options": { + "name": {"type": "str"}, + "acl_v4": {"type": "str"}, + "acl_v6": {"type": "str"}, + "ro": {"type": "bool"}, + "rw": {"type": "bool"}, + "view": {"type": "str"}, + }, + }, + "contact": {"type": "str"}, + "traps": { + "type": "dict", + "options": { + "bgp": { + "type": "dict", + "options": { + "arista_backward_transition": {"type": "bool"}, + "arista_established": {"type": "bool"}, + "backward_transition": {"type": "bool"}, + "established": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "bridge": { + "type": "dict", + "options": { + "arista_mac_age": {"type": "bool"}, + "arista_mac_learn": {"type": "bool"}, + "arista_mac_move": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "capacity": { + "type": "dict", + "options": { + "arista_hardware_utilization_alert": { + "type": "bool", + }, + "enabled": {"type": "bool"}, + }, + }, + "entity": { + "type": "dict", + "options": { + "arista_ent_sensor_alarm": {"type": "bool"}, + "ent_config_change": {"type": "bool"}, + "ent_state_oper": {"type": "bool"}, + "ent_state_oper_disabled": {"type": "bool"}, + "ent_state_oper_enabled": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "external_alarm": { + "type": "dict", + "options": { + "arista_external_alarm_asserted_notif": { + "type": "bool", + }, + "arista_external_alarm_deasserted_notif": { + "type": "bool", + }, + "enabled": {"type": "bool"}, + }, + }, + "isis": { + "type": "dict", + "options": { + "adjacency_change": {"type": "bool"}, + "area_mismatch": {"type": "bool"}, + "attempt_to_exceed_max_sequence": { + "type": "bool", + }, + "authentication_type_failure": { + "type": "bool", + }, + "database_overload": {"type": "bool"}, + "own_lsp_purge": {"type": "bool"}, + "rejected_adjacency": {"type": "bool"}, + "sequence_number_skip": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "lldp": { + "type": "dict", + "options": { + "rem_tables_change": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "mpls_ldp": { + "type": "dict", + "options": { + "mpls_ldp_session_down": {"type": "bool"}, + "mpls_ldp_session_up": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "msdp": { + "type": "dict", + "options": { + "backward_transition": {"type": "bool"}, + "established": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "ospf": { + "type": "dict", + "options": { + "if_config_error": {"type": "bool"}, + "if_auth_failure": {"type": "bool"}, + "if_state_change": {"type": "bool"}, + "nbr_state_change": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "ospfv3": { + "type": "dict", + "options": { + "if_config_error": {"type": "bool"}, + "if_rx_bad_packet": {"type": "bool"}, + "if_state_change": {"type": "bool"}, + "nbr_state_change": {"type": "bool"}, + "nbr_restart_helper_status_change": { + "type": "bool", + }, + "nssa_translator_status_change": { + "type": "bool", + }, + "restart_status_change": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "pim": { + "type": "dict", + "options": { + "neighbor_loss": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "snmp": { + "type": "dict", + "options": { + "authentication": {"type": "bool"}, + "link_down": {"type": "bool"}, + "link_up": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "snmpConfigManEvent": { + "type": "dict", + "options": { + "arista_config_man_event": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "switchover": { + "type": "dict", + "options": { + "arista_redundancy_switch_over_notif": { + "type": "bool", + }, + "enabled": {"type": "bool"}, + }, + }, + "test": { + "type": "dict", + "options": { + "arista_test_notification": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + "vrrp": { + "type": "dict", + "options": { + "trap_new_master": {"type": "bool"}, + "enabled": {"type": "bool"}, + }, + }, + }, + }, + "engineid": { + "type": "dict", + "options": { + "local": {"type": "str"}, + "remote": { + "type": "dict", + "options": { + "host": {"type": "str"}, + "udp_port": {"type": "int"}, + "id": {"type": "str"}, + }, + }, + }, + }, + "extension": { + "type": "dict", + "options": { + "root_oid": {"type": "str"}, + "script_location": {"type": "str"}, + "oneshot": {"type": "bool"}, + }, + }, + "groups": { + "type": "list", + "elements": "dict", + "options": { + "group": {"type": "str"}, + "version": { + "type": "str", + "choices": ["v1", "v3", "v2c"], + }, + "auth_privacy": { + "type": "str", + "choices": ["auth", "noauth", "priv"], + }, + "context": {"type": "str"}, + "notify": {"type": "str"}, + "read": {"type": "str"}, + "write": {"type": "str"}, + }, + }, + "hosts": { + "type": "list", + "elements": "dict", + "options": { + "host": {"type": "str"}, + "user": {"type": "str"}, + "udp_port": {"type": "int"}, + "informs": {"type": "bool"}, + "traps": {"type": "bool"}, + "version": { + "type": "str", + "choices": [ + "1", + "2c", + "3 auth", + "3 noauth", + "3 priv", + ], + }, + "vrf": {"type": "str"}, + }, + }, + "acls": { + "type": "list", + "elements": "dict", + "options": { + "afi": {"type": "str", "choices": ["ipv4", "ipv6"]}, + "acl": {"type": "str"}, + "vrf": {"type": "str"}, + }, + }, + "local_interface": {"type": "str"}, + "location": {"type": "str"}, + "notification": {"type": "int"}, + "objects": { + "type": "dict", + "options": { + "mac_address_tables": {"type": "bool"}, + "route_tables": {"type": "bool"}, + }, + }, + "qos": {"type": "int"}, + "qosmib": {"type": "int"}, + "transmit": {"type": "int"}, + "transport": {"type": "str"}, + "users": { + "type": "list", + "elements": "dict", + "options": { + "user": {"type": "str"}, + "group": {"type": "str"}, + "remote": {"type": "str"}, + "udp_port": {"type": "int"}, + "version": { + "type": "str", + "choices": ["v1", "v2c", "v3"], + }, + "auth": { + "type": "dict", + "options": { + "algorithm": {"type": "str"}, + "auth_passphrase": { + "type": "str", + "no_log": True, + }, + "encryption": {"type": "str"}, + "priv_passphrase": { + "type": "str", + "no_log": True, + }, + }, + }, + "localized": { + "type": "dict", + "options": { + "engineid": {"type": "str"}, + "algorithm": {"type": "str"}, + "auth_passphrase": { + "type": "str", + "no_log": True, + }, + "encryption": {"type": "str"}, + "priv_passphrase": { + "type": "str", + "no_log": True, + }, + }, + }, + }, + }, + "views": { + "type": "list", + "elements": "dict", + "options": { + "view": {"type": "str"}, + "mib": {"type": "str"}, + "action": { + "type": "str", + "choices": ["excluded", "included"], + }, + }, + }, + "vrfs": { + "type": "list", + "elements": "dict", + "options": { + "vrf": {"type": "str"}, + "local_interface": {"type": "str"}, + }, + }, + }, + }, + "running_config": {"type": "str"}, + "state": { + "type": "str", + "choices": [ + "deleted", + "merged", + "overridden", + "replaced", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + }, + } # pylint: disable=C0301 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/static_routes/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/static_routes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/static_routes/static_routes.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/static_routes/static_routes.py new file mode 100644 index 000000000..86d790606 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/static_routes/static_routes.py @@ -0,0 +1,97 @@ +# +# -*- 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 eos_static_routes module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class Static_routesArgs(object): + """The arg spec for the eos_static_routes module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "address_families": { + "elements": "dict", + "options": { + "afi": { + "choices": ["ipv4", "ipv6"], + "required": True, + "type": "str", + }, + "routes": { + "elements": "dict", + "options": { + "dest": {"required": True, "type": "str"}, + "next_hops": { + "elements": "dict", + "options": { + "admin_distance": {"type": "int"}, + "description": {"type": "str"}, + "forward_router_address": { + "type": "str", + }, + "interface": {"type": "str"}, + "nexthop_grp": {"type": "str"}, + "mpls_label": {"type": "int"}, + "tag": {"type": "int"}, + "track": {"type": "str"}, + "vrf": {"type": "str"}, + }, + "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/arista/eos/plugins/module_utils/network/eos/argspec/vlans/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/vlans/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/vlans/vlans.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/argspec/vlans/vlans.py new file mode 100644 index 000000000..5bdefddb0 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/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 eos_vlans module +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + + +class VlansArgs(object): + """The arg spec for the eos_vlans module""" + + def __init__(self, **kwargs): + pass + + argument_spec = { + "config": { + "elements": "dict", + "options": { + "vlan_id": {"required": True, "type": "int"}, + "name": {"type": "str"}, + "state": {"choices": ["active", "suspend"], "type": "str"}, + }, + "type": "list", + }, + "running_config": {"type": "str"}, + "state": { + "choices": [ + "merged", + "replaced", + "overridden", + "deleted", + "gathered", + "rendered", + "parsed", + ], + "default": "merged", + "type": "str", + }, + } diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acl_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acl_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acl_interfaces/acl_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acl_interfaces/acl_interfaces.py new file mode 100644 index 000000000..c76e79595 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acl_interfaces/acl_interfaces.py @@ -0,0 +1,481 @@ +# +# -*- 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 eos_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 + +import itertools + +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 ( + search_obj_in_list, + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) + + +class Acl_interfaces(ConfigBase): + """ + The eos_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 execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + warnings = list() + commands = list() + changed = False + + if self.state in self.ACTION_STATES: + existing_acl_interfaces_facts = self.get_acl_interfaces_facts() + else: + existing_acl_interfaces_facts = [] + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_acl_interfaces_facts)) + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + changed = True + if changed: + 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_acl_interfaces_facts = self.get_acl_interfaces_facts() + elif self.state == "rendered": + result["rendered"] = commands + elif self.state == "parsed": + if not self._module.params["running_config"]: + self._module.fail_json( + msg="Value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_acl_interfaces_facts( + data=self._module.params["running_config"], + ) + else: + changed_acl_interfaces_facts = [] + if self.state in self.ACTION_STATES: + result["before"] = existing_acl_interfaces_facts + if result["changed"]: + result["after"] = changed_acl_interfaces_facts + elif self.state == "gathered": + result["gathered"] = 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 + """ + want = self._module.params["config"] + 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 + """ + commands = [] + if self.state in ("merged", "replaced", "overridden") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + self.state, + ), + ) + 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 == "merged" or self.state == "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 + """ + commandset = [] + want_interface = [] + for w in want: + commands = [] + diff_access_group = [] + want_interface.append(w["name"]) + obj_in_have = search_obj_in_list(w["name"], have, "name") + if not obj_in_have or "access_groups" not in obj_in_have.keys(): + commands.append(add_commands(w["access_groups"], w["name"])) + else: + if "access_groups" in obj_in_have.keys(): + obj = self.get_acl_diff(obj_in_have, w) + if obj[0]: + to_delete = { + "access_groups": [{"acls": obj[0], "afi": "ipv4"}], + } + commands.append(remove_commands(to_delete, w["name"])) + if obj[1]: + to_delete = { + "access_groups": [{"acls": obj[1], "afi": "ipv6"}], + } + commands.append(remove_commands(to_delete, w["name"])) + diff = self.get_acl_diff(w, obj_in_have) + if diff[0]: + diff_access_group.append( + {"afi": "ipv4", "acls": diff[0]}, + ) + if diff[1]: + diff_access_group.append( + {"afi": "ipv6", "acls": diff[1]}, + ) + if diff_access_group: + commands.append( + add_commands(diff_access_group, w["name"]), + ) + if commands: + intf_command = ["interface " + w["name"]] + commands = list(itertools.chain(*commands)) + commandset.append(intf_command) + commandset.append(commands) + + if commandset: + commandset = list(itertools.chain(*commandset)) + return commandset + + 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 + """ + commandset = [] + want_interface = [] + for w in want: + commands = [] + diff_access_group = [] + want_interface.append(w["name"]) + obj_in_have = search_obj_in_list(w["name"], have, "name") + if not obj_in_have or "access_groups" not in obj_in_have.keys(): + commands.append(add_commands(w["access_groups"], w["name"])) + else: + if "access_groups" in obj_in_have.keys(): + obj = self.get_acl_diff(obj_in_have, w) + if obj[0]: + to_delete = { + "access_groups": [{"acls": obj[0], "afi": "ipv4"}], + } + commands.append(remove_commands(to_delete, w["name"])) + if obj[1]: + to_delete = { + "access_groups": [{"acls": obj[1], "afi": "ipv6"}], + } + commands.append(remove_commands(to_delete, w["name"])) + diff = self.get_acl_diff(w, obj_in_have) + if diff[0]: + diff_access_group.append( + {"afi": "ipv4", "acls": diff[0]}, + ) + if diff[1]: + diff_access_group.append( + {"afi": "ipv6", "acls": diff[1]}, + ) + if diff_access_group: + commands.append( + add_commands(diff_access_group, w["name"]), + ) + if commands: + intf_command = ["interface " + w["name"]] + commands = list(itertools.chain(*commands)) + commandset.append(intf_command) + commandset.append(commands) + for h in have: + commands = [] + if "access_groups" in h.keys() and h["access_groups"]: + if h["name"] not in want_interface: + for h_group in h["access_groups"]: + to_delete = {"access_groups": [h_group]} + commands.append(remove_commands(to_delete, h["name"])) + if commands: + intf_command = ["interface " + h["name"]] + commands = list(itertools.chain(*commands)) + commandset.append(intf_command) + commandset.append(commands) + + if commandset: + commandset = list(itertools.chain(*commandset)) + + return commandset + + 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 + """ + commandset = [] + for w in want: + commands = [] + diff_access_group = [] + obj_in_have = search_obj_in_list(w["name"], have, "name") + if not obj_in_have: + commands = add_commands(w["access_groups"], w["name"]) + else: + if "access_groups" in obj_in_have.keys(): + diff = self.get_acl_diff(w, obj_in_have) + if diff[0]: + diff_access_group.append( + {"afi": "ipv4", "acls": diff[0]}, + ) + if diff[1]: + diff_access_group.append( + {"afi": "ipv6", "acls": diff[1]}, + ) + if diff_access_group: + commands = add_commands(diff_access_group, w["name"]) + else: + commands = add_commands(w["access_groups"], w["name"]) + if commands: + intf_command = ["interface " + w["name"]] + commandset.append(intf_command) + commandset.append(commands) + if commandset: + commandset = list(itertools.chain(*commandset)) + return commandset + + 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 + """ + commandset = [] + for w in want: + commands = [] + intf_command = ["interface " + w["name"]] + obj_in_have = search_obj_in_list(w["name"], have, "name") + if "access_groups" not in w.keys() or not w["access_groups"]: + commands = remove_commands(obj_in_have, w["name"]) + if w["access_groups"]: + for w_grp in w["access_groups"]: + if "acls" not in w_grp.keys() or not w_grp["acls"]: + obj = self.get_acls_from_afi( + w["name"], + w_grp["afi"], + have, + ) + to_delete = { + "access_groups": [ + {"acls": obj, "afi": w_grp["afi"]}, + ], + } + commands = remove_commands(to_delete, w["name"]) + else: + if ( + "access_groups" not in obj_in_have.keys() + or not obj_in_have["access_groups"] + ): + continue + group = {"access_groups": [w_grp]} + obj = self.get_acl_diff(group, obj_in_have, True) + if obj[0]: + to_delete = { + "access_groups": [ + {"acls": obj[0], "afi": "ipv4"}, + ], + } + commands.append( + remove_commands(to_delete, w["name"]), + ) + if obj[1]: + to_delete = { + "access_groups": [ + {"acls": obj[1], "afi": "ipv6"}, + ], + } + commands.append( + remove_commands(to_delete, w["name"]), + ) + if commands: + commands = list(itertools.chain(*commands)) + if commands: + commandset.append(intf_command) + commandset.append(commands) + + if commandset: + commandset = list(itertools.chain(*commandset)) + return commandset + + def get_acl_diff(self, w, h, intersection=False): + diff_v4 = [] + diff_v6 = [] + w_acls_v4 = [] + w_acls_v6 = [] + h_acls_v4 = [] + h_acls_v6 = [] + for w_group in w["access_groups"]: + if w_group["afi"] == "ipv4": + w_acls_v4 = w_group["acls"] + if w_group["afi"] == "ipv6": + w_acls_v6 = w_group["acls"] + for h_group in h["access_groups"]: + if h_group["afi"] == "ipv4": + h_acls_v4 = h_group["acls"] + if h_group["afi"] == "ipv6": + h_acls_v6 = h_group["acls"] + for item in w_acls_v4: + match = list( + filter(lambda x: x["name"] == item["name"], h_acls_v4), + ) + if match: + if item["direction"] == match[0]["direction"]: + if intersection: + diff_v4.append(item) + else: + if not intersection: + diff_v4.append(item) + else: + if not intersection: + diff_v4.append(item) + for item in w_acls_v6: + match = list( + filter(lambda x: x["name"] == item["name"], h_acls_v6), + ) + if match: + if item["direction"] == match[0]["direction"]: + if intersection: + diff_v6.append(item) + else: + if not intersection: + diff_v6.append(item) + else: + if not intersection: + diff_v6.append(item) + return diff_v4, diff_v6 + + def get_acls_from_afi(self, interface, afi, have): + config = [] + for h in have: + if h["name"] == interface: + if "access_groups" not in h.keys() or not h["access_groups"]: + continue + if h["access_groups"]: + for h_grp in h["access_groups"]: + if h_grp["afi"] == afi: + config = h_grp["acls"] + return config + + +def add_commands(want, interface): + commands = [] + + for w in want: + # This module was verified on an ios device since vEOS doesnot support + # acl_interfaces cnfiguration. In ios, ipv6 acl is configured as + # traffic-filter and in eos it is access-group + + # a_cmd = "traffic-filter" if w['afi'] == 'ipv6' else "access-group" + a_cmd = "access-group" + afi = "ip" if w["afi"] == "ipv4" else w["afi"] + if "acls" in w.keys(): + for acl in w["acls"]: + commands.append( + afi + + " " + + a_cmd + + " " + + acl["name"] + + " " + + acl["direction"], + ) + return commands + + +def remove_commands(want, interface): + commands = [] + if "access_groups" not in want.keys() or not want["access_groups"]: + return commands + for w in want["access_groups"]: + # This module was verified on an ios device since vEOS doesnot support + # acl_interfaces cnfiguration. In ios, ipv6 acl is configured as + # traffic-filter and in eos it is access-group + + # a_cmd = "traffic-filter" if w['afi'] == 'ipv6' else "access-group" + a_cmd = "access-group" + + afi = "ip" if w["afi"] == "ipv4" else w["afi"] + if "acls" in w.keys(): + for acl in w["acls"]: + commands.append( + "no " + + afi + + " " + + a_cmd + + " " + + acl["name"] + + " " + + acl["direction"], + ) + return commands diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acls/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acls/acls.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acls/acls.py new file mode 100644 index 000000000..17fe6cfd3 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/acls/acls.py @@ -0,0 +1,661 @@ +# +# -*- 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 eos_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 itertools +import re +import socket + +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.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) + + +class Acls(ConfigBase): + """ + The eos_acls class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["acls"] + + def __init__(self, module): + super(Acls, self).__init__(module) + + 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 execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + warnings = list() + commands = list() + changed = False + + if self.state in self.ACTION_STATES: + existing_acls_facts = self.get_acls_facts() + else: + existing_acls_facts = [] + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_acls_facts)) + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + changed = True + if changed: + 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_acls_facts = self.get_acls_facts() + elif self.state == "rendered": + commands = list(itertools.chain(*commands)) + result["rendered"] = commands + elif self.state == "parsed": + if not self._module.params["running_config"]: + self._module.fail_json( + msg="Value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_acls_facts( + data=self._module.params["running_config"], + ) + else: + changed_acls_facts = [] + if self.state in self.ACTION_STATES: + result["before"] = existing_acls_facts + if result["changed"]: + result["after"] = changed_acls_facts + elif self.state == "gathered": + result["gathered"] = 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.get("config") + want = [] + onbox_configs = [] + for h in existing_acls_facts: + have_configs = add_commands(remove_empties(h)) + onbox_configs.append(have_configs) + if config: + for w in config: + want.append(remove_empties(w)) + have = existing_acls_facts + resp = self.set_state(want, have) + if self.state == "merged": + to_config = self.compare_configs(onbox_configs, to_list(resp)) + else: + to_config = resp + return to_config + + def compare_configs(self, have, want): + commands = [] + want = list(itertools.chain(*want)) + have = list(itertools.chain(*have)) + h_index = 0 + config = list(want) + for w in want: + access_list = re.findall(r"(ip.*) access-list (.*)", w) + if access_list: + if w in have: + h_index = have.index(w) + else: + for num, h in enumerate(have, start=h_index + 1): + if "access-list" not in h: + seq_num = re.search(r"(\d+) (.*)", w) + if seq_num: + have_seq_num = re.search(r"(\d+) (.*)", h) + if seq_num.group(1) == have_seq_num.group( + 1, + ) and have_seq_num.group(2) != seq_num.group(2): + negate_cmd = "no " + seq_num.group(1) + config.insert(config.index(w), negate_cmd) + if w in h: + config.pop(config.index(w)) + break + for c in config: + access_list = re.findall(r"(ip.*) access-list (.*)", c) + if access_list: + acl_index = config.index(c) + else: + if config[acl_index] not in commands: + commands.append(config[acl_index]) + commands.append(c) + return commands + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + if ( + self.state in ("merged", "replaced", "overridden", "rendered") + and not want + ): + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + self.state, + ), + ) + 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 == "merged" or self.state == "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 = [] + config_cmds = [] + remove_cmds = [] + ace_names = [] + diff = {} + if not have: + commands = set_commands(want, []) + for w in want: + afi = "ipv6" if w["afi"] == "ipv6" else "ipv4" + for acl in w["acls"]: + name = acl["name"] + want_ace = acl["aces"] + for h in have: + if h["afi"] == afi: + for h_acl in h["acls"]: + if h_acl["name"] == name: + if name not in ace_names: + ace_names.append(name) + h = {"afi": afi, "acls": [{"name": name}]} + for h_ace in h_acl.get("aces", []): + diff = get_ace_diff(h_ace, want_ace) + if diff: + h = { + "afi": afi, + "acls": [ + { + "name": name, + "aces": [h_ace], + }, + ], + } + remove_cmds.append( + del_commands(h, have), + ) + for w_ace in want_ace: + w_diff = get_ace_diff( + w_ace, + h_acl.get("aces", []), + ) + if w_diff: + w = [ + { + "afi": afi, + "acls": [ + { + "name": name, + "aces": [w_ace], + }, + ], + }, + ] + cmds = set_commands(w, have) + config_cmds.append( + list(itertools.chain(*cmds)), + ) + if name not in ace_names: + for w_ace in want_ace: + w = [ + { + "afi": afi, + "acls": [ + { + "name": name, + "aces": [w_ace], + }, + ], + }, + ] + cmds = set_commands(w, have) + config_cmds.append( + list(itertools.chain(*cmds)), + ) + + if remove_cmds: + remove_cmds = list(itertools.chain(*remove_cmds)) + commands.append(remove_cmds) + if config_cmds: + config_cmds = list(itertools.chain(*config_cmds)) + commands.append(config_cmds) + commands = list(itertools.chain(*commands)) + commandset = [] + [commandset.append(cmd) for cmd in commands if cmd not in commandset] + return commandset + + 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 = [] + ace_diff = {} + h_afi_list = [] + w_afi_list = [] + for h in have: + h_afi_list.append(h["afi"]) + for w in want: + w_afi_list.append(w["afi"]) + if not h_afi_list: + commands = set_commands(want, []) + for hafi in h_afi_list: + if hafi not in w_afi_list: + h = {"afi": hafi} + remove_cmds = del_commands(h, have) + commands.append(remove_cmds) + for w in want: + w_names = [] + for h in have: + h_names = [] + if w["afi"] == h["afi"]: + for w_acl in w["acls"]: + for h_acl in h["acls"]: + h_names.append(h_acl["name"]) + if h_acl["name"] == w_acl["name"]: + for h_ace in h_acl.get("aces", []): + ace_diff = get_ace_diff( + h_ace, + w_acl["aces"], + ) + if ace_diff: + h = { + "afi": h["afi"], + "acls": [ + { + "name": h_acl["name"], + "aces": [h_ace], + }, + ], + } + remove_cmds = del_commands(h, have) + commands.append(remove_cmds) + for w_ace in w_acl["aces"]: + if w_acl["name"] not in w_names: + w_ace_diff = get_ace_diff( + w_ace, + h_acl.get("aces", []), + ) + if w_ace_diff: + w_diff = [ + { + "afi": w["afi"], + "acls": [ + { + "name": w_acl["name"], + "aces": [w_ace], + }, + ], + }, + ] + config_cmds = set_commands( + w_diff, + have, + ) + config_cmds = list( + itertools.chain(*config_cmds), + ) + commands.append(config_cmds) + w_names.append(w_acl["name"]) + for hname in h_names: + if hname not in w_names: + h = {"afi": h["afi"], "acls": [{"name": hname}]} + remove_cmds = del_commands(h, have) + if remove_cmds not in commands: + commands.append(remove_cmds) + + if commands: + commands = list(itertools.chain(*commands)) + + commandset = [] + for c in commands: + access_list = re.findall(r"(ip.*) access-list (.*)", c) + if access_list and "no" in c: + commandset.append(c) + continue + if access_list: + acl_index = commands.index(c) + else: + if commands[acl_index] not in commandset: + commandset.append(commands[acl_index]) + commandset.append(c) + + return commandset + + 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 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 want: + for h in have: + return_command = add_commands(h) + for command in return_command: + command = "no " + command + commands.append(command) + else: + for w in want: + return_command = del_commands(w, have, True) + if return_command[:5] == "Warn ": + self._module.warn(return_command[5:]) + return commands + else: + commands.append(return_command) + commands = list(itertools.chain(*commands)) + return commands + + +def set_commands(want, have): + commands = [] + for w in want: + wace_updated = [] + for h in have: + if w["afi"] == h["afi"]: + for wacl in w["acls"]: + for hacl in h["acls"]: + if wacl["name"] == hacl["name"]: + want_aces = wacl["aces"] + for wace in wacl["aces"]: + for hace in hacl.get("aces", []): + if ( + "sequence" in wace.keys() + and "sequence" in hace.keys() + ): + if ( + wace["sequence"] + == hace["sequence"] + ): + wace_updated = get_updated_ace( + wace, + hace, + ) + if wace_updated: + want_aces.pop( + want_aces.index(wace), + ) + want_aces.append(wace_updated) + return_command = add_commands(w) + commands.append(return_command) + return commands + + +def get_updated_ace(w, h): + # gives the ace to be updated in case of merge update. + if not dict_diff(w, h): + return + w_updated = w.copy() + for hkey in h.keys(): + if hkey not in w.keys(): + w_updated.update({hkey: h[hkey]}) + else: + w_updated.update({hkey: w[hkey]}) + return w_updated + + +def add_commands(want): + commandset = [] + protocol_name = { + "51": "ahp", + "47": "gre", + "1": "icmp", + "2": "igmp", + "4": "ip", + "89": "ospf", + "103": "pim", + "6": "tcp", + "17": "udp", + "112": "vrrp", + } + if not want: + return commandset + command = "" + afi = "ip" if want["afi"] == "ipv4" else "ipv6" + for acl in want["acls"]: + if "standard" in acl.keys() and acl["standard"]: + command = afi + " access-list standard " + acl["name"] + else: + command = afi + " access-list " + acl["name"] + commandset.append(command) + if "aces" not in acl.keys(): + continue + for ace in acl["aces"]: + command = "" + if "sequence" in ace.keys(): + command = str(ace["sequence"]) + if "remark" in ace.keys(): + command = command + " remark " + ace["remark"] + if "fragment_rules" in ace.keys() and ace["fragment_rules"]: + command = command + " fragment-rules" + if "grant" in ace.keys(): + command = command + " " + ace["grant"] + if "vlan" in ace.keys(): + command = command + " vlan " + ace["vlan"] + if "protocol" in ace.keys(): + protocol = ace["protocol"] + if protocol.isdigit(): + if protocol in protocol_name.keys(): + protocol = protocol_name[protocol] + command = command + " " + protocol + if "source" in ace.keys(): + if "any" in ace["source"].keys(): + command = command + " any" + elif "subnet_address" in ace["source"].keys(): + command = command + " " + ace["source"]["subnet_address"] + elif "host" in ace["source"].keys(): + command = command + " host " + ace["source"]["host"] + elif "address" in ace["source"].keys(): + command = ( + command + + " " + + ace["source"]["address"] + + " " + + ace["source"]["wildcard_bits"] + ) + if "port_protocol" in ace["source"].keys(): + for op, val in ace["source"]["port_protocol"].items(): + if val.isdigit(): + val = socket.getservbyport(int(val)) + command = ( + command + " " + op + " " + val.replace("_", "-") + ) + if "destination" in ace.keys(): + if "any" in ace["destination"].keys(): + command = command + " any" + elif "subnet_address" in ace["destination"].keys(): + command = ( + command + " " + ace["destination"]["subnet_address"] + ) + elif "host" in ace["destination"].keys(): + command = command + " host " + ace["destination"]["host"] + elif "address" in ace["destination"].keys(): + command = ( + command + + " " + + ace["destination"]["address"] + + " " + + ace["destination"]["wildcard_bits"] + ) + if "port_protocol" in ace["destination"].keys(): + for op in ace["destination"]["port_protocol"].keys(): + command = ( + command + + " " + + op + + " " + + ace["destination"]["port_protocol"][op].replace( + "_", + "-", + ) + ) + if "protocol_options" in ace.keys(): + for proto in ace["protocol_options"].keys(): + if proto == "icmp" or proto == "icmpv6": + for icmp_msg in ace["protocol_options"][proto].keys(): + command = ( + command + " " + icmp_msg.replace("_", "-") + ) + elif proto == "ip" or proto == "ipv6": + command = ( + command + + " nexthop-group " + + ace["protocol_options"][proto]["nexthop_group"] + ) + elif proto == "tcp": + for flag, val in ace["protocol_options"][proto][ + "flags" + ].items(): + if val: + command = command + " " + flag + if "hop_limit" in ace.keys(): + for op, val in ace["hop_limit"].items(): + command = command + " hop-limit " + op + " " + val + if "tracked" in ace.keys() and ace["tracked"]: + command = command + " tracked" + if "ttl" in ace.keys(): + for op, val in ace["ttl"].items(): + command = command + " ttl " + op + " " + str(val) + if "fragments" in ace.keys(): + command = command + " fragments" + if "log" in ace.keys(): + command = command + " log" + commandset.append(command.strip()) + return commandset + + +def del_commands(want, have, name_only=False): + commandset = [] + command = "" + have_command = [] + for h in have: + have_configs = add_commands(h) + have_command.append(have_configs) + have_command = list(itertools.chain(*have_command)) + afi = "ip" if want["afi"] == "ipv4" else "ipv6" + if "acls" not in want.keys(): + for have_cmd in have_command: + access_list = re.search(r"(ip.*)\s+access-list .*", have_cmd) + if access_list and access_list.group(1) == afi: + commandset.append("no " + have_cmd) + return commandset + + for acl in want["acls"]: + ace_present = True + if "standard" in acl.keys() and acl["standard"]: + command = afi + " access-list standard " + acl["name"] + else: + command = afi + " access-list " + acl["name"] + if "aces" not in acl.keys(): + ace_present = False + commandset.append("no " + command) + if ace_present: + if name_only: + msg = "Deleted operation allows deletion of access-list only and not the entries !!" + return "Warn " + msg + return_command = add_commands(want) + for cmd in return_command: + if "access-list" in cmd: + commandset.append(cmd) + continue + seq = re.search( + r"(\d+) (permit|deny|fragment-rules|remark) .*", + cmd, + ) + if seq: + commandset.append("no " + seq.group(1)) + else: + commandset.append("no " + cmd) + return commandset + + +def get_ace_diff(want_ace, have_ace): + # gives the diff of the aces passed. + if not have_ace: + return dict_diff({}, want_ace) + for h_a in have_ace: + d = dict_diff(want_ace, h_a) + if not d: + break + return d diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_address_family/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_address_family/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_address_family/bgp_address_family.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_address_family/bgp_address_family.py new file mode 100644 index 000000000..021074805 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_address_family/bgp_address_family.py @@ -0,0 +1,293 @@ +# +# -*- 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 eos_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. +""" + +import re + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.bgp_address_family import ( + Bgp_afTemplate, +) + + +class Bgp_af(ResourceModule): + """ + The eos_bgp_address_family config class + """ + + def __init__(self, module): + super(Bgp_af, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="bgp_address_family", + tmplt=Bgp_afTemplate(), + ) + self.parsers = [ + "router", + "address_family", + "bgp_params_additional_paths", + "bgp_params.nexthop_address_family", + "bgp_params.nexthop_unchanged", + "bgp_params.redistribute_internal", + "bgp_params.route", + "graceful_restart", + "neighbor.activate", + "neighbor.additional_paths", + "neighbor.default_originate", + "neighbor.graceful_restart", + "neighbor.next_hop_unchanged", + "neighbor.next_hop_address_family", + "neighbor.prefix_list", + "neighbor.route_map", + "neighbor.weight", + "neighbor.encapsulation", + "network", + "redistribute", + "route_target", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = {} + haved = {} + + if self.want: + wantd = {self.want["as_number"]: self.want} + if self.have: + haved = {self.have["as_number"]: self.have} + + # turn all lists of dicts into dicts prior to merge + for entry in wantd, haved: + self._bgp_af_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 len(wantd.keys()) > 1: + self._module.fail_json( + msg="Only one bgp instance is allowed per device", + ) + wantd = {} + # if state is deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + h_del = {} + for k, v in iteritems(haved): + if k in wantd or not wantd: + h_del.update({k: v}) + haved = h_del + for wk, wv in iteritems(wantd): + self._compare(want=wv, have=haved.pop(wk, {})) + + wantd = {} + + # remove superfluous config for overridden + if self.state == "overridden": + 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 _delete_af(self, want, have): + waf = want.get("address_family", {}) + haf = have.get("address_family", {}) + for hkey, entry in iteritems(haf): + if hkey in waf.keys(): + af_no_command = self._tmplt.render( + entry, + "address_family", + True, + ).split("\n") + if re.search(r"\S+_\S+", hkey): + af_no_command[0] = af_no_command[0][3:] + af_no_command[1] = "no " + af_no_command[1] + for cmd in af_no_command: + self.commands.append(cmd) + else: + self.addcmd(entry, "address_family", True) + 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_af network resource. + """ + for name, entry in iteritems(want): + if name != "as_number": + if self.state == "deleted": + self._delete_af(want, have) + else: + self._compare_af({name: entry}, {name: have.get(name, {})}) + + if self.commands and "router bgp" not in self.commands[0]: + self.commands.insert( + 0, + self._tmplt.render( + {"as_number": want.get("as_number") or have["as_number"]}, + "router", + False, + ), + ) + + def _compare_af(self, want, have): + waf = want.get("address_family", {}) + haf = have.get("address_family", {}) + for name, entry in iteritems(waf): + begin = len(self.commands) + self._compare_lists(entry, have=haf.get(name, {})) + self._compare_neighbor(entry, have=haf.get(name, {})) + # Removing the alias key + if "route_target" in entry.keys(): + entry["route_target"].pop("mode", "") + self.compare( + parsers=self.parsers, + want=entry, + have=haf.pop(name, {}), + ) + if len(self.commands) != begin: + af_command = self._tmplt.render( + entry, + "address_family", + False, + ).split("\n") + for cmd in af_command: + self.commands.insert(begin, cmd) + self.commands.append("exit") + begin += 1 + for name, entry in iteritems(haf): + # skip superfluous configs for replaced + if self.state in ["replaced"]: + if name in waf.keys(): + self.addcmd(entry, "address_family", True) + else: + # overridden + # check if want has vrf or not + # if want doesnot have vrf, device's vrf config will not + # be touched. + vrf_present = False + for w_key in waf.keys(): + if re.search(r"\S+_\S+", w_key): + vrf_present = True + break + if vrf_present: + if re.search(r"\S+_\S+", name): + af_no_command = self._tmplt.render( + entry, + "address_family", + True, + ).split("\n") + if name not in waf.keys(): + af_no_command[0] = af_no_command[0][3:] + af_no_command[1] = "no " + af_no_command[1] + for cmd in af_no_command: + self.commands.append(cmd) + else: + self.addcmd(entry, "address_family", True) + else: + if not re.search(r"\S+_\S+", name): + self.addcmd(entry, "address_family", True) + + def _compare_neighbor(self, want, have): + parsers = [ + "neighbor.activate", + "neighbor.additional_paths", + "neighbor.default_originate", + "neighbor.graceful_restart", + "neighbor.next_hop_unchanged", + "neighbor.next_hop_address_family", + "neighbor.prefix_list", + "neighbor.route_map", + "neighbor.weight", + "neighbor.encapsulation", + ] + wneigh = want.get("neighbor", {}) + hneigh = have.get("neighbor", {}) + for name, entry in iteritems(wneigh): + self.compare( + parsers=parsers, + want={"neighbor": entry}, + have={"neighbor": hneigh.pop(name, {})}, + ) + for name, entry in iteritems(hneigh): + self.compare(parsers=parsers, want={}, have={"neighbor": entry}) + + def _compare_lists(self, want, have): + for attrib in ["redistribute", "network"]: + wdict = want.pop(attrib, {}) + hdict = have.pop(attrib, {}) + 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 _bgp_af_list_to_dict(self, entry): + for name, proc in iteritems(entry): + if "address_family" in proc: + addr_dict = {} + for entry in proc.get("address_family", []): + addr_dict.update( + {entry["afi"] + "_" + entry.get("vrf", ""): entry}, + ) + proc["address_family"] = addr_dict + self._bgp_af_list_to_dict(proc["address_family"]) + + if "neighbor" in proc: + neigh_dict = {} + for entry in proc.get("neighbor", []): + neigh_dict.update({entry["peer"]: entry}) + proc["neighbor"] = neigh_dict + + if "network" in proc: + network_dict = {} + for entry in proc.get("network", []): + network_dict.update({entry["address"]: entry}) + proc["network"] = network_dict + + if "redistribute" in proc: + redis_dict = {} + for entry in proc.get("redistribute", []): + redis_dict.update({entry["protocol"]: entry}) + proc["redistribute"] = redis_dict diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_global/bgp_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_global/bgp_global.py new file mode 100644 index 000000000..4658ced75 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/bgp_global/bgp_global.py @@ -0,0 +1,422 @@ +# +# -*- 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 eos_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.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.bgp_global import ( + Bgp_globalTemplate, +) + + +class Bgp_global(ResourceModule): + """ + The eos_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(), + ) + self.parsers = [ + "router", + "vrf", + "default_metric", + "distance", + "graceful_restart", + "graceful_restart_helper", + "maximum_paths", + "monitoring", + "route_target", + "router_id", + "shutdown", + "timers", + "ucmp_fec", + "ucmp_link_bandwidth", + "ucmp_mode", + "update", + "vlan", + "vlan_aware_bundle", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = {} + haved = {} + if ( + self.want.get("as_number") == self.have.get("as_number") + or not self.have + ): + if self.want: + wantd = {self.want["as_number"]: self.want} + if self.have: + haved = {self.have["as_number"]: self.have} + else: + self._module.fail_json( + msg="Only one bgp instance is allowed per device", + ) + + # turn all lists of dicts into dicts prior to merge + for entry in wantd, haved: + self._bgp_global_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 in ["deleted", "purged"]: + h_del = {} + for k, v in iteritems(haved): + if k in wantd or not wantd: + h_del.update({k: v}) + wantd = {} + haved = h_del + + if self.state == "deleted": + self._compare(want={}, have=self.have) + + if self.state == "purged": + for num, entry in iteritems(haved): + self.commands.append( + self._tmplt.render({"as_number": num}, "router", 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 Bgp_global network resource. + """ + self._compare_vrfs(want, have) + self._compare_neighbor(want, have) + self._compare_lists(want, have) + self._compare_bgp_params(want, have) + for name, entry in iteritems(want): + if name != "as_number": + self.compare( + parsers=self.parsers, + want={name: entry}, + have={name: have.pop(name, {})}, + ) + for name, entry in iteritems(have): + if name != "as_number": + self.compare( + parsers=self.parsers, + want={}, + have={name: have.get(name)}, + ) + + if self.commands and "router bgp" not in self.commands[0]: + self.commands.insert( + 0, + self._tmplt.render(want or have, "router", False), + ) + + def _compare_bgp_params(self, want, have): + parsers = [ + "bgp_params_additional_paths", + "bgp_params_advertise_inactive", + "bgp_params_allowas_in", + "bgp_params_always_compare_med", + "bgp_params_asn", + "bgp_params_auto_local_addr", + "bgp_params_bestpath_as_path", + "bgp_params_bestpath_ecmp_fast", + "bgp_params_bestpath_med", + "bgp_params_bestpath_skip", + "bgp_params_tie_break", + "bgp_params_client_to_client", + "bgp_params_cluster_id", + "bgp_params_confederation", + "bgp_params_control_plane_filter", + "bgp_params_convergence", + "bgp_params_default", + "bgp_params.enforce_first_as", + "bgp_params.host_routes", + "bgp_params.labelled_unicast", + "bgp_params.listen_limit", + "bgp_params.listen_range", + "bgp_params.log_neighbor_changes", + "bgp_params.missing_policy", + "bgp_params.monitoring", + "bgp_params.nexthop_unchanged", + "bgp_params.redistribute_internal", + "bgp_params.route", + "bgp_params.route_reflector", + "bgp_params.transport", + ] + wbgp = want.pop("bgp_params", {}) + hbgp = have.pop("bgp_params", {}) + for name, entry in iteritems(wbgp): + if name == "bestpath": + for k, v in iteritems(entry): + h = {} + if hbgp.get(name): + h = {name: hbgp[name].pop(v, {})} + self.compare( + parsers=parsers, + want={"bgp_params": {name: {k: v}}}, + have={"bgp_params": h}, + ) + hbgp.pop(name, {}) + continue + self.compare( + parsers=parsers, + want={"bgp_params": {name: entry}}, + have={"bgp_params": {name: hbgp.pop(name, {})}}, + ) + for name, entry in iteritems(hbgp): + self.compare( + parsers=parsers, + want={}, + have={"bgp_params": {name: entry}}, + ) + + def _compare_vrfs(self, want, have): + wvrf = want.pop("vrfs", {}) + hvrf = have.pop("vrfs", {}) + begin = len(self.commands) + for name, entry in iteritems(wvrf): + self._compare_neighbor(entry, hvrf.get(name, {})) + self._compare_lists(entry, hvrf.get(name, {})) + self._compare_bgp_params(entry, hvrf.get(name, {})) + for k, v in entry.items(): + if hvrf.get(name): + h = {k: hvrf[name].pop(k, {})} + else: + h = {} + if k != "vrf": + self.compare(parsers=self.parsers, want={k: v}, have=h) + + if len(self.commands) != begin: + self.commands.insert( + begin, + self._tmplt.render({"vrf": name}, "vrf", False), + ) + self.commands.append("exit") + begin_negate = len(self.commands) + for name, entry in iteritems(hvrf): + if name not in wvrf.keys(): + if self._check_af(name): + self._module.fail_json( + msg="Use the _bgp_address_family module to delete the address_family under vrf, before replacing/deleting the vrf.", + ) + else: + self.commands.append( + self._tmplt.render({"vrf": name}, "vrf", True), + ) + continue + self.compare(parsers=self.parsers, want={}, have=entry) + after_negate = len(self.commands) + if after_negate != begin_negate: + if "vrf " + name in self.commands: + index = self.commands.index("vrf " + name) + i = begin_negate + while i < after_negate: + cmd = self.commands.pop(i) + if cmd != "exit": + self.commands.insert(index + 1, cmd) + i += 1 + else: + self.commands.insert( + begin_negate, + self._tmplt.render({"vrf": name}, "vrf", False), + ) + self.commands.append("exit") + + def _get_config(self, connection): + return connection.get("show running-config | section bgp ") + + def _check_af(self, vrf): + af_present = False + if self._connection: + config_lines = self._get_config(self._connection).splitlines() + index = [i + 1 for i, el in enumerate(config_lines) if vrf in el] + if index: + # had to do this to escape flake8 and black errors + ind = index[0] + for line in config_lines[ind:]: + if "vrf" in line: + break + if "address-family" in line: + af_present = True + break + return af_present + + def _compare_neighbor(self, want, have): + parsers = [ + "neighbor.additional_paths", + "neighbor.allowas_in", + "neighbor.auto_local_addr", + "neighbor.bfd", + "neighbor.default_originate", + "neighbor.description", + "neighbor.dont_capability_negotiate", + "neighbor.ebgp_multihop", + "neighbor.encryption_password", + "neighbor.enforce_first_as", + "neighbor.export_localpref", + "neighbor.fall_over", + "neighbor.graceful_restart", + "neighbor.graceful_restart_helper", + "neighbor.idle_restart_timer", + "neighbor.import_localpref", + "neighbor.link_bandwidth", + "neighbor.local_as", + "neighbor.local_v6_addr", + "neighbor.maximum_accepted_routes", + "neighbor.maximum_received_routes", + "neighbor.metric_out", + "neighbor.monitoring", + "neighbor.next_hop_self", + "neighbor.next_hop_unchanged", + "neighbor.next_hop_v6_addr", + "neighbor.out_delay", + "neighbor.remote_as", + "neighbor.remove_private_as", + "neighbor.peer_group", + "neighbor.prefix_list", + "neighbor.route_map", + "neighbor.route_reflector_client", + "neighbor.route_to_peer", + "neighbor.send_community", + "neighbor.shutdown", + "neighbor.soft_reconfiguration", + "neighbor.transport", + "neighbor.timers", + "neighbor.ttl", + "neighbor.update_source", + "neighbor.weight", + ] + wneigh = want.pop("neighbor", {}) + hneigh = have.pop("neighbor", {}) + for name, entry in iteritems(wneigh): + for k, v in entry.items(): + if entry.get("peer"): + peer = entry["peer"] + else: + peer = entry["neighbor_address"] + if hneigh.get(name): + h = {"neighbor_address": peer, k: hneigh[name].pop(k, {})} + else: + h = {} + self.compare( + parsers=parsers, + want={"neighbor": {"neighbor_address": peer, k: v}}, + have={"neighbor": h}, + ) + for name, entry in iteritems(hneigh): + if name not in wneigh.keys() and "peer_group" not in entry.keys(): + self.commands.append("no neighbor " + name) + continue + for k, v in entry.items(): + self.compare( + parsers=parsers, + want={}, + have={"neighbor": {"neighbor_address": name, k: v}}, + ) + + def _compare_lists(self, want, have): + for attrib in [ + "redistribute", + "network", + "aggregate_address", + "access_group", + ]: + wdict = want.pop(attrib, {}) + hdict = have.pop(attrib, {}) + 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 _bgp_global_list_to_dict(self, entry): + for name, proc in iteritems(entry): + if "neighbor" in proc: + neigh_dict = {} + for entry in proc.get("neighbor", []): + if entry.get("peer"): + peer = entry["peer"] + else: + peer = entry["neighbor_address"] + neigh_dict.update({peer: entry}) + proc["neighbor"] = neigh_dict + + if "network" in proc: + network_dict = {} + for entry in proc.get("network", []): + network_dict.update({entry["address"]: entry}) + proc["network"] = network_dict + + if "aggregate_address" in proc: + agg_dict = {} + for entry in proc.get("aggregate_address", []): + agg_dict.update({entry["address"]: entry}) + proc["aggregate_address"] = agg_dict + + if "access_group" in proc: + access_dict = {} + for entry in proc.get("access_group", []): + access_dict.update({entry["afi"]: entry}) + proc["access_group"] = access_dict + + if "redistribute" in proc: + redis_dict = {} + for entry in proc.get("redistribute", []): + redis_dict.update({entry["protocol"]: entry}) + proc["redistribute"] = redis_dict + + if "vrfs" in proc: + vrf_dict = {} + for entry in proc.get("vrfs", []): + vrf_dict.update({entry["vrf"]: entry}) + proc["vrfs"] = vrf_dict + self._bgp_global_list_to_dict(proc["vrfs"]) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/hostname/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/hostname/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/hostname/hostname.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/hostname/hostname.py new file mode 100644 index 000000000..026e1ae19 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/hostname/hostname.py @@ -0,0 +1,76 @@ +# +# -*- 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 eos_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.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.hostname import ( + HostnameTemplate, +) + + +class Hostname(ResourceModule): + """ + The eos_hostname config class + """ + + def __init__(self, module): + super(Hostname, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="hostname", + tmplt=HostnameTemplate(), + ) + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = self.want + haved = self.have + + if 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 Hostname network resource. + """ + self.compare(parsers="hostname", want=want, have=have) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/interfaces/interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/interfaces/interfaces.py new file mode 100644 index 000000000..fdaa9e979 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/interfaces/interfaces.py @@ -0,0 +1,309 @@ +# -*- 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 eos_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, + param_list_to_dict, + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.utils.utils import ( + normalize_interface, +) + + +class Interfaces(ConfigBase): + """ + The eos_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["interfaces"] + + def get_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, + ) + interfaces_facts = facts["ansible_network_resources"].get("interfaces") + if not interfaces_facts: + return [] + return 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_interfaces_facts = self.get_interfaces_facts() + else: + existing_interfaces_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_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_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 + """ + want = self._module.params["config"] + have = 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 ("merged", "replaced", "overridden", "rendered") + and not want + ): + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + state, + ), + ) + want = param_list_to_dict(want) + have = param_list_to_dict(have) + commands = [] + if state == "overridden": + commands = self._state_overridden(want, have) + elif state == "deleted": + commands = self._state_deleted(want, have) + elif state == "merged" or state == "rendered": + commands = self._state_merged(want, have) + elif state == "replaced": + commands = self._state_replaced(want, have) + 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 = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict() + + add_config = dict_diff(extant, desired) + del_config = dict_diff(desired, extant) + + if ( + "speed" in add_config.keys() + and "duplex" not in add_config.keys() + ): + add_config.update({"duplex": desired.get("duplex")}) + + commands.extend(generate_commands(key, add_config, del_config)) + + return commands + + @staticmethod + def _state_overridden(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 key, extant in have.items(): + if key in want: + desired = want[key] + else: + desired = dict() + + add_config = dict_diff(extant, desired) + del_config = dict_diff(desired, extant) + + if ( + "speed" in add_config.keys() + and "duplex" not in add_config.keys() + ): + add_config.update({"duplex": desired.get("duplex")}) + + commands.extend(generate_commands(key, add_config, del_config)) + + return commands + + @staticmethod + def _state_merged(want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict() + + add_config = dict_diff(extant, desired) + if ( + "speed" in add_config.keys() + and "duplex" not in add_config.keys() + ): + add_config.update({"duplex": desired.get("duplex")}) + commands.extend(generate_commands(key, add_config, {})) + + return 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 = [] + for key in want: + desired = dict() + if key in have: + extant = have[key] + else: + continue + + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(key, {}, del_config)) + + return commands + + +def generate_commands(interface, to_set, to_remove): + commands = [] + for key, value in to_set.items(): + if value is None: + continue + + if key == "enabled": + commands.append("{0}shutdown".format("no " if value else "")) + elif key == "speed": + if value == "auto": + commands.append("{0} {1}".format(key, value)) + else: + commands.append("speed {0}{1}".format(value, to_set["duplex"])) + elif key == "duplex": + # duplex is handled with speed + continue + elif key == "mode": + if not re.search(r"(M|m)anagement.*", interface): + if value == "layer3": + # switching from default (layer2) mode to layer3 + commands.append("no switchport") + else: + # setting to default (layer 2) mode + commands.append("switchport") + else: + commands.append("{0} {1}".format(key, value)) + + # Don't try to also remove the same key, if present in to_remove + to_remove.pop(key, None) + + for key in to_remove.keys(): + if key == "enabled": + commands.append("no shutdown") + elif key == "speed": + commands.append("speed auto") + elif key == "duplex": + # duplex is handled with speed + continue + elif key == "mode": + if not re.search(r"(M|m)anagement.*", interface): + commands.append("switchport") + else: + commands.append("no {0}".format(key)) + + if commands: + commands.insert(0, "interface {0}".format(interface)) + + return commands diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l2_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l2_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l2_interfaces/l2_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l2_interfaces/l2_interfaces.py new file mode 100644 index 000000000..a34c1614a --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l2_interfaces/l2_interfaces.py @@ -0,0 +1,365 @@ +# -*- 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 eos_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 + +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 ( + param_list_to_dict, + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.utils.utils import ( + normalize_interface, + vlan_range_to_list, +) + + +class L2_interfaces(ConfigBase): + """ + The eos_l2_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["l2_interfaces"] + + 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 = list() + warnings = list() + + 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 + """ + want = self._module.params["config"] + have = existing_l2_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 ("merged", "replaced", "overridden", "rendered") + and not want + ): + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + state, + ), + ) + want = param_list_to_dict(want) + have = param_list_to_dict(have) + commands = [] + if state == "overridden": + commands = self._state_overridden(want, have) + elif state == "deleted": + commands = self._state_deleted(want, have) + elif state == "merged" or state == "rendered": + commands = self._state_merged(want, have) + elif state == "replaced": + commands = self._state_replaced(want, have) + 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 = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict() + + intf_commands = set_interface(desired, extant) + intf_commands.extend(clear_interface(desired, extant)) + + if intf_commands: + commands.append("interface {0}".format(interface_name)) + commands.extend(intf_commands) + + return commands + + @staticmethod + def _state_overridden(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 key, extant in have.items(): + if key in want: + desired = want[key] + else: + desired = dict() + + intf_commands = set_interface(desired, extant) + intf_commands.extend(clear_interface(desired, extant)) + + if intf_commands: + commands.append("interface {0}".format(key)) + commands.extend(intf_commands) + + return commands + + @staticmethod + def _state_merged(want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict() + + intf_commands = set_interface(desired, extant) + + if intf_commands: + commands.append("interface {0}".format(interface_name)) + commands.extend(intf_commands) + + return 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 = [] + for key in want: + desired = dict() + if key in have: + extant = have[key] + else: + continue + intf_commands = clear_interface(desired, extant) + + if intf_commands: + commands.append("interface {0}".format(key)) + commands.extend(intf_commands) + + return commands + + +def set_interface(want, have): + commands = [] + + want_mode = want.get("mode") + if want_mode and want_mode != have.get("mode"): + commands.append("switchport mode {0}".format(want_mode)) + + wants_access = want.get("access") + if wants_access: + access_vlan = wants_access.get("vlan") + if access_vlan and access_vlan != have.get("access", {}).get("vlan"): + commands.append("switchport access vlan {0}".format(access_vlan)) + + wants_trunk = want.get("trunk") + if wants_trunk: + allowed_vlans = [] + has_allowed_vlans = {} + want_allowed_vlans = {} + has_trunk = have.get("trunk", {}) + native_vlan = wants_trunk.get("native_vlan") + if native_vlan and native_vlan != has_trunk.get("native_vlan"): + commands.append( + "switchport trunk native vlan {0}".format(native_vlan), + ) + for con in [want, have]: + expand_trunk_allowed_vlans(con) + want_allowed_vlans = want["trunk"].get("trunk_allowed_vlans") + if has_trunk: + has_allowed_vlans = has_trunk.get("trunk_allowed_vlans") + + if want_allowed_vlans and has_allowed_vlans: + allowed_vlans = list( + set(want_allowed_vlans.split(",")) + - set(has_allowed_vlans.split(",")), + ) + elif want_allowed_vlans: + allowed_vlans = want_allowed_vlans.split(",") + if allowed_vlans: + allowed_vlans.sort() + allowed_vlans = ",".join( + ["{0}".format(vlan) for vlan in allowed_vlans], + ) + if has_allowed_vlans: + commands.append( + "switchport trunk allowed vlan add {0}".format( + allowed_vlans, + ), + ) + else: + commands.append( + "switchport trunk allowed vlan {0}".format(allowed_vlans), + ) + return commands + + +def expand_trunk_allowed_vlans(want): + if not want: + return None + if want.get("trunk"): + if "trunk_allowed_vlans" in want["trunk"]: + allowed_vlans = vlan_range_to_list( + want["trunk"]["trunk_allowed_vlans"], + ) + vlans_list = [str(num) for num in sorted(allowed_vlans)] + want["trunk"]["trunk_allowed_vlans"] = ",".join(vlans_list) + + +def clear_interface(want, have): + commands = [] + + if "mode" in have and want.get("mode") is None: + commands.append("no switchport mode") + + if "access" in have and not want.get("access"): + commands.append("no switchport access vlan") + + has_trunk = have.get("trunk") or {} + wants_trunk = want.get("trunk") or {} + if ( + "trunk_allowed_vlans" in has_trunk + and "trunk_allowed_vlans" not in wants_trunk + ): + commands.append("no switchport trunk allowed vlan") + if ( + "trunk_allowed_vlans" in has_trunk + and "trunk_allowed_vlans" in wants_trunk + ): + for con in [want, have]: + expand_trunk_allowed_vlans(con) + want_allowed_vlans = want["trunk"].get("trunk_allowed_vlans") + has_allowed_vlans = has_trunk.get("trunk_allowed_vlans") + allowed_vlans = list( + set(has_allowed_vlans.split(",")) + - set(want_allowed_vlans.split(",")), + ) + if allowed_vlans: + allowed_vlans = ",".join( + ["{0}".format(vlan) for vlan in allowed_vlans], + ) + + commands.append( + "switchport trunk allowed vlan remove {0}".format( + allowed_vlans, + ), + ) + + if "native_vlan" in has_trunk and "native_vlan" not in wants_trunk: + commands.append("no switchport trunk native vlan") + return commands diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l3_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l3_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l3_interfaces/l3_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l3_interfaces/l3_interfaces.py new file mode 100644 index 000000000..a34e23619 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/l3_interfaces/l3_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 eos_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 + +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 ( + param_list_to_dict, + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.utils.utils import ( + normalize_interface, +) + + +class L3_interfaces(ConfigBase): + """ + The eos_l3_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["l3_interfaces"] + + def get_l3_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, + ) + l3_interfaces_facts = facts["ansible_network_resources"].get( + "l3_interfaces", + ) + if not l3_interfaces_facts: + return [] + return l3_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_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._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_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 + """ + want = self._module.params["config"] + have = existing_l3_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 ("merged", "replaced", "overridden", "rendered") + and not want + ): + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + state, + ), + ) + want = param_list_to_dict(want) + have = param_list_to_dict(have) + commands = [] + if state == "overridden": + commands = self._state_overridden(want, have) + elif state == "deleted": + commands = self._state_deleted(want, have) + elif state == "merged" or state == "rendered": + commands = self._state_merged(want, have) + elif state == "replaced": + commands = self._state_replaced(want, have) + 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 = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict() + intf_commands = set_interface(desired, extant) + intf_commands.extend(clear_interface(desired, extant)) + + if intf_commands: + commands.append("interface {0}".format(interface_name)) + commands.extend(intf_commands) + return commands + + @staticmethod + def _state_overridden(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 key, extant in have.items(): + interface_name = normalize_interface(key) + desired = dict() + if "Management" in interface_name: + continue + for k in want.keys(): + k_want = normalize_interface(k) + if key == k_want: + desired = want[k] + break + if desired.get("ipv4"): + for ipv4 in desired["ipv4"]: + for k in ["secondary", "virtual"]: + if ipv4[k] is None: + del ipv4[k] + intf_commands = set_interface(desired, extant) + intf_commands.extend(clear_interface(desired, extant)) + + if intf_commands: + commands.append("interface {0}".format(interface_name)) + commands.extend(intf_commands) + + # new interfaces in want + new_intf_commands = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name not in have: + extant = dict() + new_intf_commands = set_interface(desired, extant) + + if new_intf_commands: + commands.append("interface {0}".format(interface_name)) + commands.extend(new_intf_commands) + + return commands + + @staticmethod + def _state_merged(want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict() + + intf_commands = set_interface(desired, extant) + if intf_commands: + commands.append("interface {0}".format(interface_name)) + commands.extend(intf_commands) + + return 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 = [] + for key in want: + interface_name = normalize_interface(key) + desired = dict() + if interface_name in have: + extant = have[interface_name] + else: + continue + + intf_commands = clear_interface(desired, extant) + + if intf_commands: + commands.append("interface {0}".format(interface_name)) + commands.extend(intf_commands) + + return commands + + +def set_interface(want, have): + commands = [] + want_ipv4 = set( + tuple(sorted(address.items())) for address in want.get("ipv4") or [] + ) + have_ipv4 = set( + tuple(sorted(address.items())) for address in have.get("ipv4") or [] + ) + for address in want_ipv4 - have_ipv4: + address = dict(address) + for param in ["secondary", "virtual"]: + if param in address and not address[param]: + del address[param] + if tuple(sorted(address.items())) in have_ipv4: + continue + + address_cmd = "ip address {0}".format(address["address"]) + if address.get("secondary"): + address_cmd += " secondary" + if address.get("virtual"): + address_cmd = "ip address virtual {0}".format(address["address"]) + commands.append(address_cmd) + + want_ipv6 = set( + tuple(sorted(address.items())) for address in want.get("ipv6") or [] + ) + have_ipv6 = set( + tuple(sorted(address.items())) for address in have.get("ipv6") or [] + ) + for address in want_ipv6 - have_ipv6: + address = dict(address) + commands.append("ipv6 address {0}".format(address["address"])) + return commands + + +def clear_interface(want, have): + commands = [] + want_ipv4 = set( + tuple(sorted(address.items())) for address in want.get("ipv4") or [] + ) + have_ipv4 = set( + tuple(sorted(address.items())) for address in have.get("ipv4") or [] + ) + if not want_ipv4 and have_ipv4: + commands.append("no ip address") + else: + for address in have_ipv4 - want_ipv4: + address = dict(address) + for param in ["secondary", "virtual"]: + if param not in address: + address[param] = None + if tuple(sorted(address.items())) in want_ipv4: + continue + + if address.get("secondary"): + commands.append( + "no ip address {0} secondary".format(address["address"]), + ) + if address.get("virtual"): + commands.append( + "no ip address virtual {0}".format(address["address"]), + ) + + if "secondary" not in address: + # Removing non-secondary removes all other interfaces + break + + want_ipv6 = set( + tuple(sorted(address.items())) for address in want.get("ipv6") or [] + ) + have_ipv6 = set( + tuple(sorted(address.items())) for address in have.get("ipv6") or [] + ) + for address in have_ipv6 - want_ipv6: + address = dict(address) + commands.append("no ipv6 address {0}".format(address["address"])) + return commands diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp/lacp.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp/lacp.py new file mode 100644 index 000000000..0d7ba850f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp/lacp.py @@ -0,0 +1,201 @@ +# -*- 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 eos_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, + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) + + +class Lacp(ConfigBase): + """ + The eos_lacp class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["lacp"] + + 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") + if not lacp_facts: + return {} + return lacp_facts + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + warnings = list() + commands = list() + + if self.state in self.ACTION_STATES: + existing_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 = self._module.params["config"] or {} + 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, + ), + ) + if state == "deleted": + commands = self._state_deleted(want, have) + elif state == "merged" or state == "rendered": + commands = self._state_merged(want, have) + elif state == "replaced": + commands = self._state_replaced(want, have) + 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 = [] + to_set = dict_diff(have, want) + if "system" in to_set: + system = to_set["system"] + if "priority" in system: + commands.append( + "lacp system-priority {0}".format(system["priority"]), + ) + + to_del = dict_diff(want, have) + if "system" in to_del: + system = to_del["system"] + system_set = to_set.get("system", {}) + if "priority" in system and "priority" not in system_set: + commands.append("no lacp system-priority") + + return commands + + @staticmethod + def _state_merged(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 = [] + to_set = dict_diff(have, want) + if "system" in to_set: + system = to_set["system"] + if "priority" in system: + commands.append( + "lacp system-priority {0}".format(system["priority"]), + ) + + return 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 = [] + to_del = dict_diff(want, have) + if "system" in to_del: + system = to_del["system"] + if "priority" in system: + commands.append("no lacp system-priority") + + return commands diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp_interfaces/lacp_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 000000000..64746f880 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lacp_interfaces/lacp_interfaces.py @@ -0,0 +1,262 @@ +# -*- 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 eos_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, + param_list_to_dict, + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.utils.utils import ( + normalize_interface, +) + + +class Lacp_interfaces(ConfigBase): + """ + The eos_lacp_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["lacp_interfaces"] + + 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} + warnings = list() + commands = 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 + """ + want = self._module.params["config"] + 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 ("merged", "replaced", "overridden", "rendered") + and not want + ): + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + state, + ), + ) + want = param_list_to_dict(want) + have = param_list_to_dict(have) + if state == "overridden": + commands = self._state_overridden(want, have) + elif state == "deleted": + commands = self._state_deleted(want, have) + elif state == "merged" or state == "rendered": + commands = self._state_merged(want, have) + elif state == "replaced": + commands = self._state_replaced(want, have) + 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 = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict() + + add_config = dict_diff(extant, desired) + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(key, add_config, del_config)) + + return commands + + @staticmethod + def _state_overridden(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 key, extant in have.items(): + if key in want: + desired = want[key] + else: + desired = dict() + + add_config = dict_diff(extant, desired) + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(key, add_config, del_config)) + + return commands + + @staticmethod + def _state_merged(want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict() + + add_config = dict_diff(extant, desired) + + commands.extend(generate_commands(key, add_config, {})) + + return 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 = [] + for key in want: + desired = dict() + if key in have: + extant = have[key] + else: + continue + + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(key, {}, del_config)) + + return commands + + +def generate_commands(interface, to_set, to_remove): + commands = [] + for key in to_remove.keys(): + if key == "rate": + continue + commands.append("no lacp {0}".format(key.replace("_", "-"))) + + for key, value in to_set.items(): + if key == "rate": + continue + if value is None: + continue + + commands.append("lacp {0} {1}".format(key.replace("_", "-"), value)) + + if commands: + commands.insert(0, "interface {0}".format(interface)) + + return commands diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lag_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lag_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lag_interfaces/lag_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lag_interfaces/lag_interfaces.py new file mode 100644 index 000000000..b69ae175f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lag_interfaces/lag_interfaces.py @@ -0,0 +1,275 @@ +# -*- 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 eos_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 + +import re + + +__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.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.utils.utils import ( + normalize_interface, +) + + +class Lag_interfaces(ConfigBase): + """ + The eos_lag_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["lag_interfaces"] + + 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: + 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_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["config"] + 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 ("merged", "replaced", "overridden", "rendered") + and not want + ): + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + state, + ), + ) + if state == "overridden": + commands = self._state_overridden(want, have) + elif state == "deleted": + commands = self._state_deleted(want, have) + elif state == "merged" or state == "rendered": + commands = self._state_merged(want, have) + elif state == "replaced": + commands = self._state_replaced(want, have) + 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 = [] + for interface in want: + interface_name = normalize_interface(interface["name"]) + for extant in have: + if extant["name"] == interface_name: + break + else: + extant = dict(name=interface_name) + + commands.extend(set_config(interface, extant)) + commands.extend(remove_config(interface, extant)) + + return commands + + @staticmethod + def _state_overridden(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 extant in have: + for interface in want: + if normalize_interface(interface["name"]) == extant["name"]: + break + else: + interface = dict(name=extant["name"]) + commands.extend(remove_config(interface, extant)) + + for interface in want: + interface_name = normalize_interface(interface["name"]) + for extant in have: + if extant["name"] == interface_name: + break + else: + extant = dict(name=interface_name) + commands.extend(set_config(interface, extant)) + + return commands + + @staticmethod + def _state_merged(want, have): + """The command generator when state is merged + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + for interface in want: + interface_name = normalize_interface(interface["name"]) + for extant in have: + if extant["name"] == interface_name: + break + else: + extant = dict(name=interface_name) + + commands.extend(set_config(interface, extant)) + + return 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 = [] + for interface in want: + interface_name = normalize_interface(interface["name"]) + for extant in have: + if extant["name"] == interface_name: + break + else: + extant = dict(name=interface_name) + + # Clearing all args, send empty dictionary + interface = dict(name=interface_name) + commands.extend(remove_config(interface, extant)) + + return commands + + +def set_config(want, have): + commands = [] + to_set = dict_diff(have, want) + for member in to_set.get("members", []): + channel_id = re.search(r"\d.*", want["name"]) + if channel_id: + commands.extend( + [ + "interface {0}".format(member["member"]), + "channel-group {0} mode {1}".format( + channel_id.group(0), + member["mode"], + ), + ], + ) + + return commands + + +def remove_config(want, have): + commands = [] + if not want.get("members"): + return ["no interface {0}".format(want["name"])] + + to_remove = dict_diff(want, have) + for member in to_remove.get("members", []): + commands.extend( + ["interface {0}".format(member["member"]), "no channel-group"], + ) + + return commands diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_global/lldp_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_global/lldp_global.py new file mode 100644 index 000000000..f42471ff9 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_global/lldp_global.py @@ -0,0 +1,215 @@ +# +# -*- 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 eos_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, + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) + + +class Lldp_global(ConfigBase): + """ + The eos_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} + warnings = list() + commands = 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"] or {} + have = existing_lldp_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"] + if ( + state in ("merged", "replaced", "overridden", "rendered") + and not want + ): + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + state, + ), + ) + if state == "deleted": + commands = state_deleted(want, have) + elif state == "merged" or state == "rendered": + commands = state_merged(want, have) + elif state == "replaced": + commands = state_replaced(want, have) + return commands + + +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 = set() + # merged and deleted are likely to emit duplicate tlv-select commands + commands.update(state_merged(want, have)) + commands.update(state_deleted(want, have)) + + return list(commands) + + +def state_merged(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 = [] + to_set = dict_diff(have, want) + tlv_options = to_set.pop("tlv_select", {}) + for key, value in to_set.items(): + if key == "holdtime": + key = "hold-time" + if key == "reinit": + key = "timer reinitialization" + commands.append("lldp {0} {1}".format(key, value)) + for key, value in tlv_options.items(): + device_option = key.replace("_", "-") + if value is True: + commands.append("lldp tlv transmit {0}".format(device_option)) + elif value is False: + commands.append("no lldp tlv transmit {0}".format(device_option)) + + return commands + + +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 = [] + to_remove = dict_diff(want, have) + tlv_options = to_remove.pop("tlv_select", {}) + for key in to_remove: + if key == "holdtime": + key = "hold-time" + if key == "reinit": + key = "timer reinitialization" + commands.append("no lldp {0}".format(key)) + for key, value in tlv_options.items(): + device_option = key.replace("_", "-") + if value is False: + commands.append("lldp tlv transmit {0}".format(device_option)) + elif value is True: + commands.append("no lldp tlv transmit {0}".format(device_option)) + + return commands diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_interfaces/lldp_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 000000000..c0325102b --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,264 @@ +# -*- 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 eos_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, + param_list_to_dict, + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.utils.utils import ( + normalize_interface, +) + + +class Lldp_interfaces(ConfigBase): + """ + The eos_lldp_interfaces class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["lldp_interfaces"] + + 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 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_lldp_interfaces_facts = self.get_lldp_interfaces_facts() + else: + existing_lldp_interfaces_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_lldp_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_lldp_interfaces_facts = self.get_lldp_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_lldp_interfaces_facts( + data=running_config, + ) + + if self.state in self.ACTION_STATES: + result["before"] = existing_lldp_interfaces_facts + if result["changed"]: + result["after"] = changed_lldp_interfaces_facts + + elif self.state == "gathered": + result["gathered"] = 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 + """ + want = self._module.params["config"] + 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 + """ + state = self._module.params["state"] + if ( + state in ("merged", "replaced", "overridden", "rendered") + and not want + ): + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + state, + ), + ) + want = param_list_to_dict(want, remove_key=False) + have = param_list_to_dict(have, remove_key=False) + if state == "overridden": + commands = self._state_overridden(want, have) + elif state == "deleted": + commands = self._state_deleted(want, have) + elif state == "merged" or state == "rendered": + commands = self._state_merged(want, have) + elif state == "replaced": + commands = self._state_replaced(want, have) + 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 = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict(name=interface_name) + + add_config = dict_diff(extant, desired) + del_config = dict_diff(desired, extant) + + commands.extend( + generate_commands(interface_name, add_config, del_config), + ) + + return commands + + @staticmethod + def _state_overridden(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 key, extant in have.items(): + if key in want: + desired = want[key] + else: + desired = dict(name=key) + + add_config = dict_diff(extant, desired) + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(key, add_config, del_config)) + + return commands + + @staticmethod + def _state_merged(want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + for key, desired in want.items(): + interface_name = normalize_interface(key) + if interface_name in have: + extant = have[interface_name] + else: + extant = dict(name=interface_name) + + add_config = dict_diff(extant, desired) + + commands.extend(generate_commands(interface_name, add_config, {})) + + return 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 = [] + for key in want.keys(): + interface_name = normalize_interface(key) + desired = dict(name=interface_name) + if interface_name in have: + extant = have[interface_name] + else: + continue + + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(interface_name, {}, del_config)) + + return commands + + +def generate_commands(name, to_set, to_remove): + commands = [] + for key, value in to_set.items(): + if value is None: + continue + + prefix = "" if value else "no " + commands.append("{0}lldp {1}".format(prefix, key)) + + for key in to_remove: + commands.append("lldp {0}".format(key)) + + if commands: + commands.insert(0, "interface {0}".format(name)) + + return commands diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/logging_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/logging_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/logging_global/logging_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/logging_global/logging_global.py new file mode 100644 index 000000000..8016b29d7 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/logging_global/logging_global.py @@ -0,0 +1,190 @@ +# +# -*- 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 eos_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 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.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.logging_global import ( + Logging_globalTemplate, +) + + +class Logging_global(ResourceModule): + """ + The eos_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.parsers = [ + "buffered", + "event", + "facility", + "console", + "format", + "format.timestamp.traditional", + "format.timestamp.highresolution", + "level", + "monitor", + "on", + "persistent", + "policy", + "relogging_interval", + "repeat_messages", + "src_interface", + "synchronous", + "trap", + ] + + 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 = {"logging_global": self.want} + haved = {"logging_global": self.have} + # turn all lists of dicts into dicts prior to merge + for entry in wantd["logging_global"], haved["logging_global"]: + self._logging_global_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": + wantd = {} + for k, have in iteritems(haved): + 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 Logging_global network resource. + """ + self._hosts_compare(want=want, have=have) + self._vrfs_compare(want=want, have=have) + self.compare(parsers=self.parsers, want=want, have=have) + + def _hosts_compare(self, want, have): + host_want = want.pop("hosts", {}) + host_have = have.pop("hosts", {}) + for name, entry in iteritems(host_want): + h = {} + if host_have: + h = {"hosts": host_have.pop(name, {})} + self.compare(parsers="host", want={"hosts": entry}, have=h) + for name, entry in iteritems(host_have): + self.compare(parsers="host", want={}, have={"hosts": entry}) + + def _vrfs_hosts_compare(self, vrf, want, have): + host_want = want.pop("hosts", {}) + host_have = have.pop("hosts", {}) + for name, entry in iteritems(host_want): + h = {} + if host_have: + h = {"vrfs": {"name": vrf, "hosts": host_have.pop(name, {})}} + w = {"vrfs": {"name": vrf, "hosts": entry}} + self.compare(parsers="vrf.host", want=w, have=h) + for name, entry in iteritems(host_have): + self.compare( + parsers="vrf.host", + want={}, + have={"vrfs": {"name": vrf, "hosts": entry}}, + ) + + def _vrfs_compare(self, want, have): + vrf_want = want.pop("vrfs", {}) + vrf_have = have.pop("vrfs", {}) + for name, entry in iteritems(vrf_want): + self._vrfs_hosts_compare( + name, + want=entry, + have=vrf_have.get(name, {}), + ) + if entry.get("source_interface"): + h = {} + if vrf_have.get(name): + h = { + "vrfs": { + "name": name, + "source_interface": vrf_have[name].pop( + "source_interface", + "", + ), + }, + } + w = { + "vrfs": { + "name": name, + "source_interface": entry["source_interface"], + }, + } + self.compare(parsers="vrf.source_interface", want=w, have=h) + for name, entry in iteritems(vrf_have): + self._vrfs_hosts_compare(name, want={}, have=entry) + self.compare( + parsers="vrf.source_interface", + want={}, + have={"vrfs": entry}, + ) + + def _logging_global_list_to_dict(self, entry): + if "hosts" in entry: + hosts_dict = {} + for el in entry["hosts"]: + hosts_dict.update({el["name"]: el}) + entry["hosts"] = hosts_dict + + if "vrfs" in entry: + vrf_dict = {} + for el in entry["vrfs"]: + vrf_dict.update({el["name"]: el}) + entry["vrfs"] = vrf_dict + for k, v in iteritems(entry["vrfs"]): + self._logging_global_list_to_dict(v) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ntp_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ntp_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ntp_global/ntp_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ntp_global/ntp_global.py new file mode 100644 index 000000000..0b8323d15 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ntp_global/ntp_global.py @@ -0,0 +1,249 @@ +# +# -*- 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 eos_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 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.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.ntp_global import ( + Ntp_globalTemplate, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.utils.utils import ( + normalize_interface, +) + + +class Ntp_global(ResourceModule): + """ + The eos_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 = [ + "authenticate", + "local_interface", + "qos_dscp", + "trusted_key", + ] + + 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 = {"ntp_global": self.want} + haved = {"ntp_global": self.have} + # turn all lists of dicts into dicts prior to merge + for entry in wantd["ntp_global"], haved["ntp_global"]: + self._ntp_global_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": + wantd = {} + 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 Ntp_global network resource. + """ + self._serve_compare(want=want, have=have) + self._authentication_keys_compare(want=want, have=have) + self._servers_compare(want=want, have=have) + self.compare(parsers=self.parsers, want=want, have=have) + add_cmd = [] + del_cmd = [] + if self.commands: + for cmd in self.commands: + if "no ntp" in cmd: + del_cmd.append(cmd) + else: + add_cmd.append(cmd) + self.commands = del_cmd + add_cmd + + def _authentication_keys_compare(self, want, have): + w = want.pop("authentication_keys", {}) + h = have.pop("authentication_keys", {}) + for name, entry in iteritems(w): + h_key = {} + if h.get(name): + h_key = {"authentication_keys": h.pop(name)} + self.compare( + parsers="authentication_keys", + want={"authentication_keys": entry}, + have=h_key, + ) + for name, entry in iteritems(h): + self.compare( + parsers="authentication_keys", + want={}, + have={"authentication_keys": entry}, + ) + + def _servers_compare(self, want, have): + w = want.pop("servers", {}) + h = have.pop("servers", {}) + for name, entry in iteritems(w): + if entry.get("source"): + entry["source"] = normalize_interface(entry["source"]) + h_key = {} + if h.get(name): + h_key = {"servers": h.pop(name)} + self.compare( + parsers="servers", + want={"servers": entry}, + have=h_key, + ) + for name, entry in iteritems(h): + self.compare(parsers="servers", want={}, have={"servers": entry}) + + def _serve_compare(self, want, have): + serve_want = want.pop("serve", {}) + serve_have = have.pop("serve", {}) + for name, entry in iteritems(serve_want): + if name == "all" and entry: + w = {"serve": {"all": True}} + self.compare( + parsers="serve_all", + want=w, + have={"serve": {"all": serve_have.pop("all", False)}}, + ) + else: + for k_afi, v_afi in iteritems(entry): + for k, v in iteritems(v_afi): + afi = v_afi["afi"] + if k == "afi": + continue + h = {} + if k == "acls": + for ace, ace_entry in iteritems(v): + if serve_have.get("access_lists"): + for hk, hv in iteritems( + serve_have["access_lists"], + ): + for h_k, h_v in iteritems(hv): + h_afi = hv["afi"] + if h_k == "afi": + continue + if h_afi == afi: + if ace in h_v: + h_acc = { + "afi": h_afi, + "acls": h_v.pop(ace), + } + h = { + "serve": { + "access_lists": h_acc, + }, + } + w = { + "serve": { + "access_lists": { + "afi": afi, + "acls": ace_entry, + }, + }, + } + self.compare(parsers="serve", want=w, have=h) + for k, v in iteritems(serve_have): + if k == "all" and v: + h = {"serve": {"all": True}} + self.compare(parsers="serve_all", want={}, have=h) + else: + for k_afi, v_afi in iteritems(v): + for k, v in iteritems(v_afi): + hafi = v_afi["afi"] + if k == "afi": + continue + for k_acl, v_acl in iteritems(v): + h = { + "serve": { + "access_lists": { + "afi": hafi, + "acls": v_acl, + }, + }, + } + self.compare(parsers="serve", want={}, have=h) + + def _ntp_global_list_to_dict(self, entry): + if "authentication_keys" in entry: + key_dict = {} + for el in entry["authentication_keys"]: + key_dict.update({el["id"]: el}) + entry["authentication_keys"] = key_dict + + if "servers" in entry: + server_dict = {} + for el in entry["servers"]: + server_dict.update({el["server"]: el}) + entry["servers"] = server_dict + + if "serve" in entry: + serve_dict = {} + main_dict = {} + if entry["serve"].get("all"): + main_dict.update({"all": entry["serve"]["all"]}) + if entry["serve"].get("access_lists"): + for el in entry["serve"].get("access_lists"): + if "acls" in el: + acl_dict = {} + for acl in el["acls"]: + acl_dict.update({acl["acl_name"]: acl}) + el["acls"] = acl_dict + serve_dict.update({el["afi"]: el}) + if serve_dict: + main_dict.update({"access_lists": serve_dict}) + if serve_dict: + entry["serve"] = main_dict diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospf_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospf_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospf_interfaces/ospf_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospf_interfaces/ospf_interfaces.py new file mode 100644 index 000000000..38d46bd7c --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospf_interfaces/ospf_interfaces.py @@ -0,0 +1,212 @@ +# +# -*- 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 eos_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.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.ospf_interfaces import ( + Ospf_interfacesTemplate, +) + + +class Ospf_interfaces(ResourceModule): + """ + The eos_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 = [ + "interfaces", + "area", + "authentication_v2", + "authentication_v3", + "authentication_key", + "deadinterval", + "encryption", + "hellointerval", + "bfd", + "cost", + "ip_params_area", + "ip_params_bfd", + "ip_params_cost", + "ip_params_dead_interval", + "ip_params_hello_interval", + "ip_params_mtu_ignore", + "ip_params_network", + "ip_params_priority", + "ip_params_passive_interface", + "ip_params_retransmit_interval", + "ip_params_transmit_delay", + "mtu_ignore", + "network", + "priority", + "passive_interface", + "retransmit_interval", + "transmit_delay", + "message_digest_key", + ] + + 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. + """ + + # convert list of dicts to dicts of dicts + wantd = {} + haved = {} + for entry in self.want: + wantd.update({entry["name"]: entry}) + for entry in self.have: + haved.update({entry["name"]: entry}) + + # turn all lists of dicts into dicts prior to merge + for entry in wantd, haved: + self._ospf_int_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": + h_del = {} + for k, v in iteritems(haved): + if k in wantd or not wantd: + h_del.update({k: v}) + haved = h_del + for k, have in iteritems(haved): + self._compare(want={}, have=have) + wantd = {} + + # remove superfluous config for overridden + if self.state == "overridden": + 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_addr_family(want=want, have=have) + if len(self.commands) != begin: + tmp = want or have + tmp.pop("address_family", {}) + self.commands.insert( + begin, + self._tmplt.render(tmp, "interfaces", False), + ) + + def _compare_addr_family(self, want, have): + wdict = want.get("address_family", {}) + hdict = have.get("address_family", {}) + for afi in ["ipv4", "ipv6"]: + w_family = wdict.pop(afi, {}) + h_family = hdict.pop(afi, {}) + for k in w_family.keys(): + if k == "afi": + continue + w = {"afi": afi, k: w_family[k]} + h = {"afi": afi, k: h_family.pop(k, {})} + if k == "ip_params": + self._compare_ip_params(want=w, have=h) + self.compare(parsers=self.parsers, want=w, have=h) + for k in h_family.keys(): + if k in ["afi"]: + continue + w = {"afi": afi, k: None} + h = {"afi": afi, k: h_family[k]} + if k == "ip_params": + w = {"afi": afi, k: {}} + self._compare_ip_params(want=w, have=h) + self.compare(parsers=self.parsers, want=w, have=h) + + def _compare_ip_params(self, want, have): + w_params = want.get("ip_params", {}) + h_params = have.get("ip_params", {}) + for afi in ["ipv4", "ipv6"]: + w_p = w_params.pop(afi, {}) + h_p = h_params.pop(afi, {}) + for k, params in iteritems(w_p): + if k == "afi": + continue + w = {"afi": afi, k: params} + h = {"afi": afi, k: h_p.pop(k, None)} + self.compare( + parsers=self.parsers, + want={"ip_params": w}, + have={"ip_params": h}, + ) + for k, params in iteritems(h_p): + if k == "afi": + continue + w = {"afi": afi, k: None} + h = {"afi": afi, k: params} + self.compare( + parsers=self.parsers, + want={"ip_params": w}, + have={"ip_params": h}, + ) + + def _ospf_int_list_to_dict(self, entry): + for name, family in iteritems(entry): + if family.get("ip_params"): + family_dict = {} + for entry in family["ip_params"]: + family_dict.update({entry["afi"]: entry}) + family["ip_params"] = family_dict + + if "address_family" in family: + addr_dict = {} + for entry in family.get("address_family", []): + addr_dict.update({entry["afi"]: entry}) + family["address_family"] = addr_dict + self._ospf_int_list_to_dict(family["address_family"]) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv2/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv2/ospfv2.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv2/ospfv2.py new file mode 100644 index 000000000..f7e85d4a2 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv2/ospfv2.py @@ -0,0 +1,840 @@ +# +# -*- 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 eos_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 __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.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) + + +class Ospfv2(ConfigBase): + """ + The eos_ospfv2 class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["ospfv2"] + + def __init__(self, module): + super(Ospfv2, self).__init__(module) + + def get_ospfv2_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, + ) + + ospfv2_facts = facts["ansible_network_resources"].get("ospfv2") + if not ospfv2_facts: + return [] + return ospfv2_facts + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + warnings = list() + commands = list() + + if self.state in self.ACTION_STATES: + existing_ospfv2_facts = self.get_ospfv2_facts() + else: + existing_ospfv2_facts = [] + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_ospfv2_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_ospfv2_facts = self.get_ospfv2_facts() + elif self.state == "rendered": + result["rendered"] = commands + elif self.state == "parsed": + if not self._module.params["running_config"]: + self._module.fail_json( + msg="value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_ospfv2_facts( + data=self._module.params["running_config"], + ) + else: + changed_ospfv2_facts = self.get_ospfv2_facts() + if self.state in self.ACTION_STATES: + result["before"] = existing_ospfv2_facts + if result["changed"]: + result["after"] = changed_ospfv2_facts + elif self.state == "gathered": + result["gathered"] = changed_ospfv2_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_ospfv2_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_ospfv2_facts + resp = self.set_state(want, have) + return to_list(resp) + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + if ( + self.state in ("merged", "replaced", "overridden", "rendered") + and not want + ): + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + self.state, + ), + ) + if self.state == "overridden": + commands = self._state_overridden(want, have) + elif self.state == "deleted": + commands = self._state_deleted(want, have) + elif self.state == "merged" or self.state == "rendered": + commands = self._state_merged(want, have) + elif self.state == "replaced": + commands = self._state_replaced(want, have) + return commands + + def _get_os_version(self): + os_version = "4.20" + if self._connection(): + os_version = self._connection.get_device_info()[ + "network_os_version" + ] + return os_version + + 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 = [] + for w in want["processes"]: + del_cmds = w.copy() + add_cmds = {} + for h in have["processes"]: + if h["process_id"] != w["process_id"]: + continue + if w.get("vrf"): + if w["vrf"] != h["vrf"]: + self._module.fail_json( + msg="Value of vrf and process_id does not match the config present in the device", + ) + break + del_instance_list = self.compare_dicts(h, w) + if del_instance_list: + del_cmds = {"processes": del_instance_list} + add_instance_list = self.compare_dicts(w, h) + if add_instance_list: + add_cmds = {"processes": add_instance_list} + + return_command = self.del_commands(del_cmds, have) + for command in return_command: + if "exit" not in command: + commands.append(command) + return_command = self.add_commands(add_cmds, have) + for command in return_command: + if "router ospf" in command: + if command not in commands: + commands.append(command) + else: + commands.append(command) + commandset = [] + if commands: + commandset.append(commands[0]) + for cmd in commands[1::]: + if "router ospf" in cmd and commandset[-1] != "exit": + commandset.append("exit") + commandset.append(cmd) + if commandset[-1] != "exit": + commandset.append("exit") + return commandset + + 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["processes"]: + present = False + for w in want["processes"]: + if h["process_id"] == w["process_id"]: + present = True + break + if not present: + commands.append("no router ospf " + str(h["process_id"])) + replace_cmds = self._state_replaced(want, have) + for cmd in replace_cmds: + commands.append(cmd) + 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 = [] + return_command = self.del_commands(want, have) + if return_command: + for cmd in return_command: + if "no exit" in cmd: + cmd = "exit" + commands.append(cmd) + return commands + + def set_commands(self, want, have): + commands = [] + instance_list = [] + for w in want["processes"]: + present = False + c = [] + if have and not have.get("processes"): + instance_list = want["processes"] + break + if have: + for h in have["processes"]: + if w["process_id"] == h["process_id"]: + if w.get("vrf"): + if w["vrf"] != h["vrf"]: + self._module.fail_json( + msg="Value of vrf and process_id does not match the config present in the device", + ) + continue + present = True + c = self.compare_dicts(w, h) + break + if c: + instance_list.append(c[0]) + if not present: + if w["vrf"] in _get_vrf_list(have): + self._module.fail_json( + msg="Value of vrf and process_id does not match the config present in the device", + ) + instance_list.append(w) + instance_dict = {"processes": instance_list} + return_command = self.add_commands(instance_dict, have) + for command in return_command: + commands.append(command) + return commands + + def compare_dicts(self, want_inst, have_inst): + want_dict = remove_empties(want_inst) + have = have_inst + ospf_list = [] + return_ospf_dict = {} + for w_key in want_dict.keys(): + if not have.get(w_key): + return_ospf_dict.update({w_key: want_dict[w_key]}) + elif ( + isinstance(want_dict[w_key], str) + or isinstance(want_dict[w_key], bool) + or isinstance(want_dict[w_key], int) + ): + if want_dict[w_key] != have[w_key]: + return_ospf_dict.update({w_key: want_dict[w_key]}) + elif isinstance(want_dict[w_key], dict): + diff = dict_diff(have.get(w_key, {}), want_dict[w_key]) + if diff: + return_ospf_dict.update({w_key: diff}) + elif isinstance(want_dict[w_key], list): + if have.get(w_key): + compare_list = self.compare_ospf_list( + want_dict[w_key], + have.get(w_key), + w_key, + ) + if compare_list: + return_ospf_dict.update({w_key: compare_list}) + else: + if want_dict[w_key] != have.get(w_key): + return_ospf_dict.update({w_key: want_dict[w_key]}) + + if return_ospf_dict: + if want_dict.get("vrf"): + return_ospf_dict.update( + { + "process_id": want_dict["process_id"], + "vrf": want_dict["vrf"], + }, + ) + else: + return_ospf_dict.update( + {"process_id": want_dict["process_id"]}, + ) + ospf_list.append(return_ospf_dict) + return ospf_list + + def compare_ospf_list(self, w_list, h_list, l_key): + return_list = [] + for w in w_list: + present = False + for h in h_list: + diff = dict_diff(h, w) + if not diff: + present = True + break + if not present: + return_list.append(w) + return return_list + + def add_commands(self, want, have): + commands = [] + if not want: + return commands + for ospf_params in want["processes"]: + commands.append(_get_router_command(ospf_params)) + if ospf_params.get("traffic_engineering"): + commands.append("traffic-engineering") + if ospf_params.get("adjacency"): + threshold = ospf_params["adjacency"]["exchange_start"][ + "threshold" + ] + commands.append( + "adjacency exchange-start threshold " + str(threshold), + ) + if ospf_params.get("areas"): + command_list = _parse_areas(ospf_params["areas"]) + for c in command_list: + commands.append(c) + if ospf_params.get("auto_cost"): + commands.append( + "auto-cost reference-bandwidth " + + ospf_params["auto_cost"], + ) + if ospf_params.get("bfd"): + os_version = self._get_os_version() + if os_version < "4.23": + commands.append("bfd all-interfaces") + else: + commands.append("bfd default") + if ospf_params.get("default_information"): + commands.append( + _parse_default_information( + ospf_params["default_information"], + ), + ) + if ospf_params.get("default_metric"): + commands.append( + "default-metric" + + " " + + str(ospf_params["default_metric"]), + ) + if ospf_params.get("distance"): + for k, v in ospf_params["distance"].items(): + if v: + k = re.sub(r"_", "-", k) + commands.append("distance ospf " + k + " " + str(v)) + if ospf_params.get("distribute_list"): + commands.append( + "distribute-list " + + ospf_params["distribute_list"].keys()[0] + + " " + + ospf_params["distribute_list"].values()[0] + + " in", + ) + if ospf_params.get("dn_bit_ignore"): + commands.append("dn-bit-ignore") + if ospf_params.get("graceful_restart"): + if ospf_params["graceful_restart"].get("set"): + commands.append("graceful-restart") + else: + commands.append( + "graceful-restart grace-period " + + str( + ospf_params["graceful_restart"].get( + "grace_period", + ), + ), + ) + if ospf_params.get("graceful_restart_helper"): + commands.append("graceful-restart-helper") + if ospf_params.get("log_adjacency_changes"): + cmd = "log-adjacency-changes" + if ospf_params["log_adjacency_changes"].get("detail"): + cmd = cmd + " detail" + commands.append(cmd) + if ospf_params.get("max_lsa"): + commands.append(_parse_max_lsa(ospf_params["max_lsa"])) + if ospf_params.get("max_metric"): + commands.append(_parse_max_metric(ospf_params["max_metric"])) + if ospf_params.get("maximum_paths"): + commands.append( + "maximum-paths " + str(ospf_params["maximum_paths"]), + ) + if ospf_params.get("mpls_ldp"): + commands.append("mpls ldp sync default") + if ospf_params.get("networks"): + command_list = _parse_networks(ospf_params["networks"]) + for c in command_list: + commands.append(c) + if ospf_params.get("passive_interface"): + if "interface_list" in ospf_params["passive_interface"].keys(): + commands.append( + "passive-interface " + + ospf_params["passive_interface"]["interface_list"], + ) + else: + commands.append("passive-interface default") + if ospf_params.get("point_to_point"): + commands.append("point-to-point routes") + if ospf_params.get("redistribute"): + command_list = _parse_redistribute(ospf_params["redistribute"]) + for c in command_list: + commands.append(c) + if ospf_params.get("retransmission_threshold"): + commands.append( + "retransmission-threshold lsa " + + str(ospf_params["retransmission_threshold"]), + ) + if ospf_params.get("rfc1583compatibility"): + commands.append("compatible rfc1583") + if ospf_params.get("router_id"): + commands.append("router-id " + ospf_params.get("router_id")) + if ospf_params.get("summary_address"): + commands.append( + _parse_summary_address(ospf_params["summary_address"]), + ) + if ospf_params.get("timers"): + os_version = self._get_os_version() + command_list = _parse_timers(ospf_params["timers"], os_version) + for c in command_list: + commands.append(c) + commands.append("exit") + commandset = [] + for command in commands: + commandset.append(command.strip()) + return commandset + + def del_commands(self, want, have): + commands = [] + other_commands = 0 + want = remove_empties(want) + if want.get("processes"): + for w_inst in want["processes"]: + router_context = 0 + d_cmds = [] + instance_list = [] + if have.get("processes"): + for h_inst in have["processes"]: + if h_inst["process_id"] == w_inst["process_id"]: + if w_inst.get("vrf") and w_inst.get( + "vrf", + ) == h_inst.get("vrf"): + if list(w_inst.keys()) == [ + "process_id", + "vrf", + ]: + commands.append( + "no router ospf " + + str(w_inst["process_id"]) + + " vrf " + + w_inst["vrf"], + ) + router_context = 1 + if len(w_inst.keys()) == 1 and list( + w_inst.keys(), + ) == ["process_id"]: + commands.append( + "no router ospf " + + str(w_inst["process_id"]), + ) + router_context = 1 + if not router_context: + instance_list = self.compare_dicts( + w_inst, + h_inst, + ) + if not instance_list: + del_want = {"processes": [w_inst]} + d_cmds = self.add_commands(del_want, have) + for cmd in d_cmds: + if "router ospf" in cmd: + other_commands = 0 + if cmd not in commands: + commands.append(cmd) + else: + cmd = "no " + cmd + if cmd not in commands: + commands.append(cmd) + other_commands += 1 + if ( + not other_commands + and len(commands) == 1 + and not router_context + ): + if ( + "no" not in commands[0] + and "router ospf" in commands[0] + ): + commands[0] = "no " + commands[0] + return commands + + +def _get_router_command(inst): + command = "" + if inst.get("vrf") and inst.get("vrf") != "default": + command = ( + "router ospf " + str(inst["process_id"]) + " vrf " + inst["vrf"] + ) + else: + command = "router ospf " + str(inst["process_id"]) + return command + + +def _get_vrf_list(want): + vrf_list = [] + if not want: + return vrf_list + for w in want["processes"]: + if w.get("vrf"): + vrf_list.append(w["vrf"]) + return vrf_list + + +def _parse_areas(areas): + command = [] + for area in areas: + area_cmd = "area " + area["area_id"] + if area.get("default_cost"): + command.append( + area_cmd + " default-cost " + str(area.get("default_cost")), + ) + elif area.get("filter"): + command.append( + area_cmd + " " + _parse_areas_filter(area["filter"]), + ) + elif area.get("not_so_stubby"): + command.append( + area_cmd + + " " + + _parse_areas_filter_notsostubby(area["not_so_stubby"]), + ) + elif area.get("nssa"): + command.append( + area_cmd + " " + _parse_areas_filter_nssa(area["nssa"]), + ) + elif area.get("range"): + command.append(area_cmd + " " + _parse_areas_range(area["range"])) + return command + + +def _parse_areas_filter(filter_dict): + filter_cmd = "filter " + if filter_dict.get("prefix_list"): + filter_cmd = filter_cmd + filter_dict.get("filter") + elif filter_dict.get("address"): + filter_cmd = filter_cmd + filter_dict.get("address") + else: + filter_cmd = ( + filter_cmd + + filter_dict.get("subnet_address") + + " " + + filter_dict.get("subnet_mask") + ) + return filter_cmd + + +def _parse_areas_filter_notsostubby(nss_dict): + nss_cmd = "not-so-stubby " + if nss_dict.get("default_information_originate"): + nss_cmd = nss_cmd + "default-information-originate " + for def_keys in nss_dict["default_information_originate"].keys(): + if ( + def_keys == "nssa_only" + and nss_dict["default_information_originate"]["nssa_only"] + ): + nss_cmd = nss_cmd + " nssa-only " + elif nss_dict["default_information_originate"].get(def_keys): + nss_cmd = ( + nss_cmd + + def_keys + + " " + + nss_dict["default_information_originate"][def_keys] + ) + elif "lsa" in nss_dict.keys() and nss_dict.get("lsa"): + nss_cmd = nss_cmd + " lsa type-7 convert type-5" + elif "no_summary" in nss_dict.keys() and nss_dict.get("no_summary"): + nss_cmd = nss_cmd + " no-summary" + elif "nssa_only" in nss_dict.keys() and nss_dict.get("nssa_only"): + nss_cmd = nss_cmd + " nssa-only" + return nss_cmd + + +def _parse_areas_filter_nssa(nss_dict): + nss_cmd = "nssa " + if nss_dict.get("default_information_originate"): + nss_cmd = nss_cmd + "default-information-originate " + for def_keys in nss_dict["default_information_originate"].keys(): + if ( + def_keys == "nssa_only" + and nss_dict["default_information_originate"]["nssa_only"] + ): + nss_cmd = nss_cmd + " nssa-only " + elif nss_dict["default_information_originate"].get(def_keys): + nss_cmd = ( + nss_cmd + + def_keys + + " " + + nss_dict["default_information_originate"][def_keys] + ) + elif "no_summary" in nss_dict.keys() and nss_dict.get("no_summary"): + nss_cmd = nss_cmd + " no-summary" + elif "nssa_only" in nss_dict.keys() and nss_dict.get("nssa_only"): + nss_cmd = nss_cmd + " nssa-only" + return nss_cmd + + +def _parse_areas_range(range_dict): + range_cmd = " range " + if range_dict.get("address"): + range_cmd = range_cmd + range_dict["address"] + if range_dict.get("subnet_address"): + range_cmd = ( + range_cmd + + range_dict["subnet_address"] + + " " + + range_dict["subnet_mask"] + ) + if range_dict.get("advertise") is not None: + if range_dict["advertise"]: + range_cmd = range_cmd + " advertise " + else: + range_cmd = range_cmd + " not-advertise " + if range_dict.get("cost"): + range_cmd = range_cmd + " cost " + str(range_dict["cost"]) + return range_cmd + + +def _parse_default_information(default_dict): + def_cmd = "default-information originate" + for def_key in sorted(default_dict.keys()): + if def_key == "always": + if default_dict.get(def_key): + def_cmd = def_cmd + " " + def_key + elif def_key in ["metric", "metric_type", "route_map"]: + if default_dict.get(def_key): + k = re.sub(r"_", "-", def_key) + def_cmd = def_cmd + " " + k + " " + str(default_dict[def_key]) + return def_cmd + + +def _parse_max_lsa(max_lsa_dict): + max_lsa_cmd = "max-lsa " + if max_lsa_dict.get("count"): + max_lsa_cmd = max_lsa_cmd + " " + str(max_lsa_dict["count"]) + if max_lsa_dict.get("threshold"): + max_lsa_cmd = max_lsa_cmd + " " + str(max_lsa_dict["threshold"]) + for lsa_key, lsa_val in sorted(max_lsa_dict.items()): + if lsa_key == "warning" and lsa_val: + max_lsa_cmd = max_lsa_cmd + " warning-only" + elif lsa_key in ["ignore_count", "reset_time", "ignore_time"]: + if lsa_val: + k = re.sub(r"_", "-", lsa_key) + max_lsa_cmd = max_lsa_cmd + " " + k + " " + str(lsa_val) + " " + return max_lsa_cmd + + +def _parse_max_metric(max_metric_dict): + metric_cmd = "max-metric router-lsa " + for k, v in max_metric_dict["router_lsa"].items(): + if not v: + continue + if k == "include_stub" and v: + metric_cmd = metric_cmd + " include-stub" + elif k == "on_startup": + metric_cmd = metric_cmd + " on-startup " + str(v["wait_period"]) + elif k in ["summary_lsa", "external_lsa"]: + k = re.sub(r"_", "-", k) + if v.get("set"): + metric_cmd = metric_cmd + " " + k + else: + metric_cmd = ( + metric_cmd + " " + k + " " + str(v.get("max_metric_value")) + ) + return metric_cmd + + +def _parse_networks(net_list): + network_cmd = [] + for net_dict in net_list: + net_cmd = "network " + if net_dict.get("prefix"): + net_cmd = net_cmd + net_dict.get("prefix") + else: + net_cmd = ( + net_cmd + + net_dict.get("network_address") + + " " + + net_dict.get("mask") + ) + if net_dict.get("area"): + net_cmd = net_cmd + " area " + net_dict.get("area") + network_cmd.append(net_cmd) + return network_cmd + + +def _parse_redistribute(r_list): + rcmd_list = [] + for r_dict in r_list: + r_cmd = "redistribute " + r_cmd = r_cmd + r_dict["routes"] + if r_dict.get("isis_level"): + k = re.sub(r"_", "-", r_dict["isis_level"]) + r_cmd = r_cmd + " " + k + if r_dict.get("route_map"): + r_cmd = r_cmd + " route-map " + r_dict["route_map"] + rcmd_list.append(r_cmd) + return rcmd_list + + +def _parse_summary_address(addr_dict): + sum_cmd = "summary-address " + if addr_dict.get("prefix"): + sum_cmd = sum_cmd + addr_dict.get("prefix") + else: + sum_cmd = ( + sum_cmd + addr_dict.get("address") + " " + addr_dict.get("mask") + ) + if "attribute_map" in addr_dict.keys(): + sum_cmd = sum_cmd + " attribute-map " + addr_dict["attribute_map"] + elif addr_dict.get("not_advertise"): + sum_cmd = sum_cmd + " not-advertise " + elif "tag" in addr_dict.keys(): + sum_cmd = sum_cmd + " tag " + addr_dict["tag"] + return sum_cmd + + +def _parse_timers(timers_list, os_version="4.20"): + timers_cmd = [] + for t_dict in timers_list: + t_cmd = "timers " + for t_key in t_dict.keys(): + if not t_dict.get(t_key): + break + if t_key == "lsa": + if t_dict["lsa"].get("rx"): + if os_version < "4.23": + t_cmd = ( + t_cmd + + "lsa arrival " + + str(t_dict["lsa"]["rx"]["min_interval"]) + ) + else: + t_cmd = ( + t_cmd + + "lsa rx min interval " + + str(t_dict["lsa"]["rx"]["min_interval"]) + ) + else: + t_cmd = ( + t_cmd + + "lsa tx delay initial " + + str(t_dict["lsa"]["tx"]["delay"]["initial"]) + + " " + + str(t_dict["lsa"]["tx"]["delay"]["min"]) + + " " + + str(t_dict["lsa"]["tx"]["delay"]["max"]) + ) + elif t_key == "out_delay": + t_cmd = t_cmd + " out-delay " + str(t_dict["out_delay"]) + elif t_key == "pacing": + t_cmd = t_cmd + " pacing flood " + str(t_dict["pacing"]) + elif t_key == "spf": + if "seconds" in t_dict["spf"].keys(): + t_cmd = t_cmd + " spf " + str(t_dict["spf"]["seconds"]) + else: + t_cmd = ( + t_cmd + + "spf delay initial " + + str(t_dict["spf"]["initial"]) + + " " + + str(t_dict["spf"]["max"]) + + " " + + str(t_dict["spf"]["min"]) + ) + elif t_key == "throttle": + if t_dict["throttle"]["attr"] == "lsa": + t_cmd = t_cmd + "throttle lsa all " + else: + t_cmd = t_cmd + "throttle spf " + t_cmd = ( + t_cmd + + str(t_dict["throttle"]["initial"]) + + " " + + str(t_dict["throttle"]["min"]) + + " " + + str(t_dict["throttle"]["max"]) + ) + timers_cmd.append(t_cmd) + return timers_cmd diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv3/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv3/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv3/ospfv3.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv3/ospfv3.py new file mode 100644 index 000000000..a16d163e5 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/ospfv3/ospfv3.py @@ -0,0 +1,392 @@ +# +# -*- 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 eos_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. +""" + +import re + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, + get_from_dict, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.ospfv3 import ( + Ospfv3Template, +) + + +class Ospfv3(ResourceModule): + """ + The eos_ospfv3 config class + """ + + def __init__(self, module): + super(Ospfv3, self).__init__( + empty_fact_val={}, + facts_module=Facts(module), + module=module, + resource="ospfv3", + tmplt=Ospfv3Template(module=module), + ) + self.parsers = [ + "vrf", + "address_family", + "adjacency", + "auto_cost", + "area.default_cost", + "area.authentication", + "area.encryption", + "area.nssa", + "area.ranges", + "area.stub", + "bfd", + "default_information", + "default_metric", + "distance", + "fips_restrictions", + "graceful_restart", + "graceful_restart_period", + "graceful_restart_helper", + "log_adjacency_changes", + "max_metric", + "maximum_paths", + "passive_interface", + "redistribute", + "router_id", + "shutdown", + "timers.out_delay", + "timers.pacing", + "timers.lsa", + "timers.spf", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = {} + haved = {} + for entry in self.want.get("processes", []): + wantd.update({entry["vrf"]: entry}) + for entry in self.have.get("processes", []): + haved.update({entry["vrf"]: entry}) + + # 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": + h_del = {} + for k, v in iteritems(haved): + if k in wantd or not wantd: + h_del.update({k: v}) + wantd = {} + haved = h_del + + # remove superfluous config for overridden and deleted + if self.state in ["overridden", "deleted"]: + for k, have in iteritems(haved): + if k not in wantd and have.get("vrf") == k: + self.commands.append(self._tmplt.render(have, "vrf", 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._af_compare(want=want, have=have) + self._global_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", False), + ) + self.commands.append("exit") + + def _global_compare(self, want, have): + for name, entry in iteritems(want): + if name == "timers": + if entry.get("throttle"): + throttle = entry.pop("throttle") + modified = {} + if throttle.get("lsa"): + modified["lsa"] = { + "max": throttle["max"], + "min": throttle["min"], + "initial": throttle["initial"], + "direction": "tx", + } + if throttle.get("spf"): + modified["spf"] = { + "max": throttle["max"], + "min": throttle["min"], + "initial": throttle["initial"], + } + entry.update(modified) + self._module.warn( + " ** The 'timers' argument has been changed to have separate 'lsa' and 'spf' keys and 'throttle' has been deprecated. ** " + " \n** Your task has been modified to use {0}. ** " + " \n** timers.throttle will be removed after '2024-01-01' ** ".format( + entry, + ), + ) + if entry.get("lsa") and not isinstance(entry["lsa"], dict): + modified = {} + if not isinstance(entry["lsa"], int): + # if neither old or new format, fail ! + self._module.fail_json( + msg="The lsa key takes a dictionary of arguments. Please consult the documentation for more details", + ) + modified = { + "timers": { + "lsa": {"direction": "rx", "min": entry["lsa"]}, + }, + } + self._module.warn( + " ** 'timers lsa arrival' has changed to 'timers lsa rx min interval' from eos 4.23 onwards. ** " + " \n** Your task has been modified to use {0}. ** " + " \n** timers.lsa of type int will be removed after '2024-01-01' ** ".format( + modified, + ), + ) + entry["lsa"] = modified["timers"]["lsa"] + if name in ["vrf", "address_family"]: + continue + if not isinstance(entry, dict) and name != "areas": + self.compare( + parsers=self.parsers, + want={name: entry}, + have={name: have.pop(name, None)}, + ) + else: + if name == "areas" and entry: + self._areas_compare( + want={name: entry}, + have={name: have.get(name, {})}, + ) + else: + # passing dict without vrf, inorder to avoid no router ospfv3 command + h = {} + for i in have: + if i != "vrf": + h.update({i: have[i]}) + self.compare( + parsers=self.parsers, + want={name: entry}, + have={name: h.pop(name, {})}, + ) + # remove remaining items in have for replaced + for name, entry in iteritems(have): + if name in ["vrf", "address_family"]: + continue + if not isinstance(entry, dict): + self.compare( + parsers=self.parsers, + want={name: want.pop(name, None)}, + have={name: entry}, + ) + else: + # passing dict without vrf, inorder to avoid no router ospfv3 command + self.compare( + parsers=self.parsers, + want={name: want.pop(name, {})}, + have={name: entry}, + ) + + def _af_compare(self, want, have): + wafs = want.get("address_family", {}) + hafs = have.get("address_family", {}) + for name, entry in iteritems(wafs): + begin = len(self.commands) + if "timers" in entry: + if entry["timers"].get("throttle"): + throttle = entry["timers"].pop("throttle") + modified = {} + if throttle.get("lsa"): + modified["lsa"] = { + "max": throttle["max"], + "min": throttle["min"], + "initial": throttle["initial"], + "direction": "tx", + } + if throttle.get("spf"): + modified["spf"] = { + "max": throttle["max"], + "min": throttle["min"], + "initial": throttle["initial"], + } + entry["timers"].update(modified) + self._module.warn( + " ** The 'timers' argument has been changed to have separate 'lsa' and 'spf' keys and 'throttle' has been deprecated. ** " + " \n** Your task has been modified to use {0}. ** " + " \n** timers.throttle will be removed after '2024-01-01' ** ".format( + entry["timers"], + ), + ) + if entry["timers"].get("lsa") and not isinstance( + entry["timers"]["lsa"], + dict, + ): + if not isinstance(entry["timers"]["lsa"], int): + # It doesn't match the new format or the old format, fail here + self._module.fail_json( + msg="The lsa key takes a dictionary of arguments. Please consult the documentation for more details", + ) + modified = { + "timers": { + "lsa": { + "direction": "rx", + "min": entry["timers"]["lsa"], + }, + }, + } + self._module.warn( + " ** 'timers lsa arrival' has changed to 'timers lsa rx min interval' from eos 4.23 onwards. ** " + " \n** Your task has been modified to use {0}. ** " + " \n** timers.lsa of type int will be removed after '2024-01-01' ** ".format( + modified, + ), + ) + entry["timers"]["lsa"] = modified["timers"]["lsa"] + self._compare_lists(want=entry, have=hafs.get(name, {})) + self._areas_compare(want=entry, have=hafs.get(name, {})) + self.compare( + parsers=self.parsers, + want=entry, + have=hafs.pop(name, {}), + ) + if ( + len(self.commands) != begin + and "afi" in entry + and entry["afi"] != "router" + ): + self._rotate_commands(begin=begin) + self.commands.insert( + begin, + self._tmplt.render(entry, "address_family", False), + ) + self.commands.append("exit") + for name, entry in iteritems(hafs): + self.addcmd(entry, "address_family", True) + + def _rotate_commands(self, begin=0): + # move negate commands to beginning + for cmd in self.commands[begin::]: + negate = re.match(r"^no .*", cmd) + if negate: + self.commands.insert( + begin, + self.commands.pop(self.commands.index(cmd)), + ) + begin += 1 + + 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.encryption", + "area.authentication", + "area.nssa", + "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 ["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 ["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 _ospf_list_to_dict(self, entry): + for name, proc in iteritems(entry): + for area in proc.get("areas", []): + if "ranges" in area: + range_dict = {} + for entry in area.get("ranges", []): + range_dict.update({entry["address"]: entry}) + area["ranges"] = range_dict + areas_dict = {} + for entry in proc.get("areas", []): + areas_dict.update({entry["area_id"]: entry}) + proc["areas"] = areas_dict + + redis_dict = {} + for entry in proc.get("redistribute", []): + redis_dict.update({entry["routes"]: entry}) + proc["redistribute"] = redis_dict + + if "address_family" in proc: + addr_dict = {} + for entry in proc.get("address_family", []): + addr_dict.update({entry["afi"]: entry}) + proc["address_family"] = addr_dict + self._ospf_list_to_dict(proc["address_family"]) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/prefix_lists/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/prefix_lists/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/prefix_lists/prefix_lists.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/prefix_lists/prefix_lists.py new file mode 100644 index 000000000..347f8d426 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/prefix_lists/prefix_lists.py @@ -0,0 +1,217 @@ +# +# -*- 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 eos_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_diff, + dict_merge, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.prefix_lists import ( + Prefix_listsTemplate, +) + + +class Prefix_lists(ResourceModule): + """ + The eos_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 = {} + haved = {} + + for entry in self.want: + wantd.update({entry["afi"]: entry}) + for entry in self.have: + haved.update({entry["afi"]: entry}) + + # turn all lists of dicts into dicts prior to merge + for entry in wantd, haved: + self._prefix_lists_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": + h_del = {} + for k, v in iteritems(haved): + if k in wantd or not wantd: + h_del.update({k: v}) + haved = h_del + 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 Prefix_lists network resource. + """ + for k, v in iteritems(want): + if k == "afi": + continue + afi = want["afi"] + for pk, pv in iteritems(v): + begin = len(self.commands) + w_parent = {"afi": afi, "prefix_lists": {"name": pk}} + if pv.get("entries"): + self._compare_prefix_lists(afi, pk, pv, have) + if have.get("prefix_lists"): + if have["prefix_lists"].get(pk): + h_parent = {"afi": afi, "prefix_lists": {"name": pk}} + if begin != len(self.commands): + self.commands.insert( + begin, + self._tmplt.render( + w_parent or h_parent, + "prefixlist.name", + False, + ), + ) + for hk, hv in iteritems(have): + if hk == "afi": + continue + h_afi = have["afi"] + for hpk, hpv in iteritems(hv): + self.commands.append( + self._tmplt.render( + {"afi": h_afi, "prefix_lists": {"name": hpk}}, + "prefixlist.name", + True, + ), + ) + + def _compare_prefix_lists(self, afi, pk, w_list, have): + parser = ["prefixlist.entry", "prefixlist.resequence"] + for ek, ev in iteritems(w_list): + if ek == "name": + continue + h_child = {} + if have.get("prefix_lists"): + if have["prefix_lists"].get(pk): + if have["prefix_lists"][pk].get(ek): + self._compare_seq( + afi, + w_list["entries"], + have["prefix_lists"][pk][ek], + ) + for seq, seq_val in iteritems( + have["prefix_lists"][pk][ek], + ): + h_child = { + "afi": afi, + "prefix_lists": {"entries": {seq: seq_val}}, + } + self.compare(parsers=parser, want={}, have=h_child) + have["prefix_lists"].pop(pk) + else: + self._compare_seq(afi, w_list["entries"], {}) + else: + self._compare_seq(afi, w_list["entries"], {}) + + def _compare_seq(self, afi, w, h): + wl_child = {} + hl_child = {} + parser = ["prefixlist.entry", "prefixlist.resequence"] + for seq, ent in iteritems(w): + seq_diff = {} + wl_child = {"afi": afi, "prefix_lists": {"entries": {seq: ent}}} + if h.get(seq): + hl_child = { + "afi": afi, + "prefix_lists": {"entries": {seq: h.pop(seq)}}, + } + seq_diff = dict_diff( + hl_child["prefix_lists"]["entries"][seq], + wl_child["prefix_lists"]["entries"][seq], + ) + if seq_diff: + if self.state == "merged": + self._module.fail_json( + msg="Sequence number " + + str(seq) + + " is already present. Use replaced/overridden operation to change the configuration", + ) + + self.compare( + parsers="prefixlist.entry", + want={}, + have=hl_child, + ) + self.compare(parsers=parser, want=wl_child, have=hl_child) + + def _prefix_lists_list_to_dict(self, entry): + for afi, plist in iteritems(entry): + if "prefix_lists" in plist: + pl_dict = {} + for el in plist["prefix_lists"]: + if "entries" in el: + ent_dict = {} + for en in el["entries"]: + if "sequence" not in en.keys(): + num = "seq" + else: + num = en["sequence"] + ent_dict.update({num: en}) + el["entries"] = ent_dict + for el in plist["prefix_lists"]: + pl_dict.update({el["name"]: el}) + plist["prefix_lists"] = pl_dict diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/route_maps/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/route_maps/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/route_maps/route_maps.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/route_maps/route_maps.py new file mode 100644 index 000000000..7f799d793 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/route_maps/route_maps.py @@ -0,0 +1,349 @@ +# +# -*- 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 eos_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, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.route_maps import ( + Route_mapsTemplate, +) + + +class Route_maps(ResourceModule): + """ + The eos_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.parsers = [ + "continue", + "route_map.copy", + "route_map.rename", + "description", + "sub_route_map", + ] + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + if self.state not in ["parsed", "gathered"]: + self.generate_commands() + self.run_commands() + return self.result + + def generate_commands(self): + """Generate configuration commands to send based on + want, have and desired state. + """ + wantd = {} + haved = {} + + for entry in self.want: + wantd.update({entry["route_map"]: entry}) + for entry in self.have: + haved.update({entry["route_map"]: entry}) + + # turn all lists of dicts into dicts prior to merge + for entry in wantd, haved: + self._route_maps_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": + h_del = {} + for k, v in iteritems(haved): + if k in wantd or not wantd: + h_del.update({k: v}) + haved = h_del + for rmap, val in iteritems(haved): + self.addcmd({"route_map": rmap}, "route_map.name", True) + wantd = {} + + # remove superfluous config for overridden + if self.state in ["overridden"]: + for k, have in iteritems(haved): + for entry, val in iteritems(have.get("entries", {})): + if not wantd.get(k) or entry not in wantd[k].get( + "entries", + {}, + ): + self._compare_maps(want={}, have={entry: val}) + + 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 Route_maps network resource. + """ + self._compare_entries(want=want, have=have) + + def _compare_entries(self, want, have): + w_entries = want.get("entries", {}) + h_entries = have.get("entries", {}) + # overridden + if not w_entries: + for k, v in iteritems(h_entries): + self._compare_maps({}, {k: v}) + for k, v in iteritems(w_entries): + before_maps = len(self.commands) + self._compare_maps({k: v}, {k: h_entries.get(k, {})}) + after_maps = len(self.commands) + self._compare_match(v, h_entries.get(k, {})) + self._comapre_set(v, h_entries.get(k, {})) + for entry_k, entry_v in iteritems(v): + h = {} + if h_entries.get(k): + h = {"entries": {entry_k: h_entries[k].pop(entry_k, {})}} + self.compare( + parsers=self.parsers, + want={"entries": {entry_k: entry_v}}, + have=h, + ) + for h_k, h_v in iteritems(h_entries.pop(k, {})): + self.compare( + parsers=self.parsers, + have={"entries": {h_k: h_v}}, + want={}, + ) + + parent_present = False + for c in self.commands[before_maps::]: + if c.startswith("route-map"): + parent_present = True + break + if ( + before_maps == after_maps and len(self.commands) > after_maps + ) or (not parent_present and len(self.commands) > after_maps): + self._compare_maps({k: v}, {}) + self.commands.insert(after_maps, self.commands.pop(-1)) + + def _compare_maps(self, want, have): + map_in_want = [] + for k, v in iteritems(want): + map_name = k.split(" ")[0] + map_in_want.append(map_name) + w = {} + h = {} + h_entry = {} + for entry_k, entry_v in iteritems(v): + if entry_k not in [ + "continue_sequence", + "sub_route_map", + "description", + "match", + "set", + ]: + w.update({entry_k: entry_v}) + if have.get(k): + h.update({entry_k: have[k].pop(entry_k, {})}) + if h: + h_entry = {"route_map": map_name, "entries": h} + parser = self._select_parser(w) + self.compare( + parsers=parser, + want={"route_map": map_name, "entries": w}, + have=h_entry, + ) + for k, v in iteritems(have): + map_name = k.split(" ")[0] + if k not in want.keys() and self.state in [ + "replaced", + "overridden", + ]: + w_negate = {} + if map_name not in map_in_want and self.state == "replaced": + continue + parser = self._select_parser(v) + w_negate.update({"route_map": map_name, "entries": v}) + self.addcmd(w_negate, parser, True) + + def _select_parser(self, w): + parser = "" + if ( + "statement" in w.keys() + and "action" in w.keys() + and "sequence" in w.keys() + ): + parser = "route_map.statement.entries" + elif "statement" in w.keys() and "action" in w.keys(): + parser = "route_map.statement.action" + elif "statement" in w.keys(): + parser = "route_map.statement.name" + elif "action" in w.keys() and "sequence" in w.keys(): + parser = "route_map.entries" + elif "action" in w.keys(): + parser = "route_map.action" + else: + parser = "route_map.name" + return parser + + def _compare_match(self, want, have): + parsers = [ + "match.aggregate_role", + "match.as", + "match.as_path", + "match.community.instances", + "match.community.list", + "match.extcommunity", + "match.invert.aggregate_role", + "match.invert.as_path", + "match.invert.community.instances", + "match.invert.community.list", + "match.invert.extcommunity", + "match.interface", + "match.ip", + "match.ipaddress", + "match.ipv6", + "match.ipv6address", + "match.largecommunity", + "match.isis", + "match.local_pref", + "match.metric", + "match.metric_type", + "match.route_type", + "match.routerid", + "match.source_protocol", + "match.tag", + ] + w_match = want.pop("match", {}) + h_match = have.pop("match", {}) + for k, v in iteritems(w_match): + if k in ["ip", "ipv6"]: + for k_ip, v_ip in iteritems(v): + if h_match.get(k): + h = {k_ip: h_match[k].pop(k_ip, {})} + else: + h = {} + self.compare( + parsers=[ + "match.ip", + "match.ipaddress", + "match.ipv6address", + "match.ipv6", + ], + want={"entries": {"match": {k: {k_ip: v_ip}}}}, + have={"entries": {"match": {k: h}}}, + ) + h_match.pop(k, {}) + continue + self.compare( + parsers=parsers, + want={"entries": {"match": {k: v}}}, + have={"entries": {"match": {k: h_match.pop(k, {})}}}, + ) + for k, v in iteritems(h_match): + if k in ["ip", "ipv6"]: + for hk, hv in iteritems(v): + self.compare( + parsers=[ + "match.ip", + "match.ipaddress", + "match.ipv6address", + "match.ipv6", + ], + want={}, + have={"entries": {"match": {k: {hk: hv}}}}, + ) + continue + self.compare( + parsers=parsers, + want={}, + have={"entries": {"match": {k: v}}}, + ) + + def _comapre_set(self, want, have): + parsers = [ + "set.as_path.prepend", + "set.as_path.match", + "set.bgp", + "set.community.graceful_shutdown", + "set.community.none", + "set.community.number", + "set.community.list", + "set.community.internet", + "set.distance", + "set.evpn", + "set.extcommunity.lbw", + "set.extcommunity.none", + "set.extcommunity.rt", + "set.extcommunity.soo", + "set.ip", + "set.ipv6", + "set.isis", + "set.local_pref", + "set.metric.value", + "set.metric_type", + "set.nexthop", + "set.origin", + "set.segment_index", + "set.tag", + "set.weight", + ] + + w_set = want.pop("set", {}) + h_set = have.pop("set", {}) + for k, v in iteritems(w_set): + self.compare( + parsers=parsers, + want={"entries": {"set": {k: v}}}, + have={"entries": {"set": {k: h_set.pop(k, {})}}}, + ) + for k, v in iteritems(h_set): + self.compare( + parsers=parsers, + want={}, + have={"entries": {"set": {k: v}}}, + ) + + def _route_maps_list_to_dict(self, entry): + for name, r_map in iteritems(entry): + if r_map.get("entries"): + map_dict = {} + for entry in r_map["entries"]: + if entry.get("sequence"): + seq = entry["sequence"] + else: + seq = "seq" + mapkey = name + " " + str(seq) + map_dict.update({mapkey: entry}) + r_map["entries"] = map_dict diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/snmp_server/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/snmp_server/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/snmp_server/snmp_server.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/snmp_server/snmp_server.py new file mode 100644 index 000000000..7d850fea2 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/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 eos_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. +""" + +import re + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.rm_base.resource_module import ( + ResourceModule, +) +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + dict_merge, + get_from_dict, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.snmp_server import ( + Snmp_serverTemplate, +) + + +class Snmp_server(ResourceModule): + """ + The eos_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 = [ + "chassis_id", + "contact", + "traps.bgp", + "traps.bridge", + "traps.capacity", + "traps.entity", + "traps.external_alarm", + "traps.isis", + "traps.lldp", + "traps.mpls_ldp", + "traps.msdp", + "traps.ospf", + "traps.ospfv3", + "traps.pim", + "traps.snmp", + "traps.snmpConfigManEvent", + "traps.switchover", + "traps.test", + "traps.vrrp", + "engineid", + "extension", + "local_interface", + "location", + "notification", + "objects.mac", + "objects.route", + "qos", + "qosmib", + "transmit", + "transport", + ] + + 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 = {"snmp_server": self.want} + haved = {"snmp_server": self.have} + + # turn all lists of dicts into dicts prior to merge + for entry in wantd["snmp_server"], haved["snmp_server"]: + self._snmp_server_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 deleted, empty out wantd and set haved to wantd + if self.state == "deleted": + wantd = {} + for k, have in iteritems(haved): + 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 Snmp_server network resource. + """ + self._compare_hosts(want, have) + self._compare_lists(want, have) + for name, entry in iteritems(want): + self.compare( + parsers=self.parsers, + want={name: entry}, + have={name: have.pop(name, {})}, + ) + for name, entry in iteritems(have): + self.compare(parsers=self.parsers, want={}, have={name: entry}) + + self._modify_traps_negate() + + def _modify_traps_negate(self): + command_set = [] + for cmd in self.commands: + if re.search("no snmp-server enable traps", cmd): + command_set.append(cmd.replace("no ", "default ")) + else: + command_set.append(cmd) + self.commands = command_set + + def _compare_lists(self, want, have): + parsers = [ + "communities_ipv6_acl", + "communities_ipv4_acl", + "groups", + "acls", + "views", + "users.auth", + "users.localized", + "vrfs", + ] + for attrib in [ + "communities", + "groups", + "acls", + "users", + "views", + "vrfs", + ]: + wdict = get_from_dict(want, attrib) or {} + hdict = get_from_dict(have, attrib) or {} + for key, entry in iteritems(wdict): + # self.addcmd(entry, attrib, False) + self.compare( + parsers=parsers, + want={attrib: entry}, + have={attrib: hdict.pop(key, {})}, + ) + # remove remaining items in have for replaced + for entry in hdict.values(): + self.compare(parsers=parsers, want={}, have={attrib: entry}) + + def _compare_hosts(self, want, have): + wdict = get_from_dict(want, "hosts") or {} + hdict = get_from_dict(have, "hosts") or {} + for key, entry in iteritems(wdict): + # self.addcmd(entry, attrib, False) + self.compare( + parsers="hosts", + want={"hosts": {key: entry}}, + have={"hosts": {key: hdict.pop(key, {})}}, + ) + # remove remaining items in have for replaced + for key, entry in iteritems(hdict): + self.compare( + parsers="hosts", + want={}, + have={"hosts": {key: entry}}, + ) + + def _snmp_server_list_to_dict(self, entry): + param_dict = { + "communities": "name", + "groups": "group", + "acls": "afi", + "users": "user", + "views": "view", + "vrfs": "vrf", + } + for k, v in iteritems(param_dict): + if k in entry: + a_dict = {} + for el in entry[k]: + a_dict.update({el[v]: el}) + entry[k] = a_dict + if "hosts" in entry: + host_dict = {} + for el in entry["hosts"]: + tr = "" + inf = "" + if el.get("traps"): + tr = "traps" + if el.get("informs"): + inf = "informs" + host_dict.update( + { + ( + el.get("host"), + el.get("user"), + el.get("version"), + inf, + tr, + el.get("udp_port"), + ): el, + }, + ) + entry["hosts"] = host_dict diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/static_routes/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/static_routes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/static_routes/static_routes.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/static_routes/static_routes.py new file mode 100644 index 000000000..0c4198edf --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/static_routes/static_routes.py @@ -0,0 +1,369 @@ +# +# -*- 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 eos_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 + +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 ( + remove_empties, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) + + +class Static_routes(ConfigBase): + """ + The eos_static_routes class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["static_routes"] + + def __init__(self, module): + super(Static_routes, self).__init__(module) + + def get_static_routes_facts(self, data=None): + """Get the 'facts' (the current configuration) + + :rtype: A dictionary + :returns: The current configuration as a dictionary + """ + facts, _warnings = Facts(self._module).get_facts( + self.gather_subset, + self.gather_network_resources, + data=data, + ) + static_routes_facts = facts["ansible_network_resources"].get( + "static_routes", + ) + if not static_routes_facts: + return [] + return static_routes_facts + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + warnings = list() + commands = list() + if self.state in self.ACTION_STATES: + existing_static_routes_facts = self.get_static_routes_facts() + else: + existing_static_routes_facts = [] + + if self.state in self.ACTION_STATES or self.state == "rendered": + commands.extend(self.set_config(existing_static_routes_facts)) + + if commands and self.state in self.ACTION_STATES: + if not self._module.check_mode: + self._connection.edit_config(commands) + result["changed"] = True + if self.state in self.ACTION_STATES: + result["commands"] = commands + if self.state in self.ACTION_STATES or self.state == "gathered": + changed_static_routes_facts = self.get_static_routes_facts() + elif self.state == "rendered": + result["rendered"] = commands + elif self.state == "parsed": + if not self._module.params["running_config"]: + self._module.fail_json( + msg="Value of running_config parameter must not be empty for state parsed", + ) + result["parsed"] = self.get_static_routes_facts( + data=self._module.params["running_config"], + ) + else: + changed_static_routes_facts = [] + + if self.state in self.ACTION_STATES: + result["before"] = existing_static_routes_facts + if result["changed"]: + result["after"] = changed_static_routes_facts + elif self.state == "gathered": + result["gathered"] = changed_static_routes_facts + + result["warnings"] = warnings + return result + + def set_config(self, existing_static_routes_facts): + """Collect the configuration from the args passed to the module, + collect the current configuration (as a dict from facts) + + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + onbox_configs = [] + for h in existing_static_routes_facts: + return_command = add_commands(h) + for command in return_command: + onbox_configs.append(command) + config = self._module.params.get("config") + want = [] + if config: + for w in config: + want.append(remove_empties(w)) + have = existing_static_routes_facts + resp = self.set_state(want, have) + for want_config in resp: + if want_config not in onbox_configs: + commands.append(want_config) + return commands + + def set_state(self, want, have): + """Select the appropriate function based on the state provided + + :param want: the desired configuration as a dictionary + :param have: the current configuration as a dictionary + :rtype: A list + :returns: the commands necessary to migrate the current configuration + to the desired configuration + """ + commands = [] + if self.state in ("merged", "replaced", "overridden") and not want: + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + self.state, + ), + ) + 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 == "merged" or self.state == "rendered": + commands = self._state_merged(want, have) + elif state == "replaced": + commands = self._state_replaced(want, have) + 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 = [] + haveconfigs = [] + vrf = get_vrf(want) + dest = get_dest(want) + for h in have: + return_command = add_commands(h) + for command in return_command: + for d in dest: + if d in command: + if vrf is None: + if "vrf" not in command: + haveconfigs.append(command) + else: + if vrf in command: + haveconfigs.append(command) + wantconfigs = set_commands(want, have) + + removeconfigs = list(set(haveconfigs) - set(wantconfigs)) + for command in removeconfigs: + commands.append("no " + command) + for wantcmd in wantconfigs: + commands.append(wantcmd) + return commands + + @staticmethod + def _state_overridden(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 = [] + haveconfigs = [] + for h in have: + return_command = add_commands(h) + for command in return_command: + haveconfigs.append(command) + wantconfigs = set_commands(want, have) + idempotentconfigs = list(set(haveconfigs) - set(wantconfigs)) + if not idempotentconfigs: + return idempotentconfigs + removeconfigs = list(set(haveconfigs) - set(wantconfigs)) + for command in removeconfigs: + commands.append("no " + command) + for wantcmd in wantconfigs: + commands.append(wantcmd) + return commands + + @staticmethod + def _state_merged(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 set_commands(want, have) + + @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 not want: + for h in have: + return_command = add_commands(h) + for command in return_command: + command = "no " + command + commands.append(command) + else: + for w in want: + return_command = del_commands(w, have) + for command in return_command: + commands.append(command) + return commands + + +def set_commands(want, have): + commands = [] + for w in want: + return_command = add_commands(w) + for command in return_command: + commands.append(command) + return commands + + +def add_commands(want): + commandset = [] + if not want: + return commandset + vrf = ( + want["vrf"] + if "vrf" in want.keys() and want["vrf"] is not None + else None + ) + for address_family in want["address_families"]: + for route in address_family["routes"]: + for next_hop in route["next_hops"]: + commands = [] + if address_family["afi"] == "ipv4": + commands.append("ip route") + else: + commands.append("ipv6 route") + if vrf: + commands.append(" vrf " + vrf) + if not re.search(r"/", route["dest"]): + mask = route["dest"].split()[1] + cidr = get_net_size(mask) + commands.append( + " " + route["dest"].split()[0] + "/" + cidr, + ) + else: + commands.append(" " + route["dest"]) + if "interface" in next_hop.keys(): + commands.append(" " + next_hop["interface"]) + if "nexthop_grp" in next_hop.keys(): + commands.append( + " Nexthop-Group" + " " + next_hop["nexthop_grp"], + ) + if "forward_router_address" in next_hop.keys(): + commands.append(" " + next_hop["forward_router_address"]) + if "mpls_label" in next_hop.keys(): + commands.append(" label " + str(next_hop["mpls_label"])) + if "track" in next_hop.keys(): + commands.append(" track " + next_hop["track"]) + if "admin_distance" in next_hop.keys(): + commands.append(" " + str(next_hop["admin_distance"])) + if "description" in next_hop.keys(): + commands.append(" name " + str(next_hop["description"])) + if "tag" in next_hop.keys(): + commands.append(" tag " + str(next_hop["tag"])) + + config_commands = "".join(commands) + commandset.append(config_commands) + return commandset + + +def del_commands(want, have): + commandset = [] + haveconfigs = [] + for h in have: + return_command = add_commands(h) + for command in return_command: + command = "no " + command + haveconfigs.append(command) + if want is None or "address_families" not in want.keys(): + commandset = haveconfigs + if "address_families" not in want.keys() and "vrf" in want.keys(): + commandset = [] + for command in haveconfigs: + if want["vrf"] in command: + commandset.append(command) + elif ( + want is not None + and "vrf" not in want.keys() + and "address_families" not in want.keys() + ): + commandset = [] + for command in haveconfigs: + if "vrf" not in command: + commandset.append(command) + + elif want["address_families"]: + for address_family in want["address_families"]: + for command in haveconfigs: + afi = "ip " if address_family["afi"] == "ipv4" else "ipv6" + if afi in command: + commandset.append(command) + return commandset + + +def get_net_size(netmask): + binary_str = "" + netmask = netmask.split(".") + for octet in netmask: + binary_str += bin(int(octet))[2:].zfill(8) + return str(len(binary_str.rstrip("0"))) + + +def get_vrf(config): + vrf = "" + for c in config: + vrf = c["vrf"] if "vrf" in c.keys() and c["vrf"] else None + return vrf + + +def get_dest(config): + dest = [] + for c in config: + for address_family in c["address_families"]: + for route in address_family["routes"]: + dest.append(route["dest"]) + return dest diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/vlans/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/vlans/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/vlans/vlans.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/vlans/vlans.py new file mode 100644 index 000000000..664216e6d --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/config/vlans/vlans.py @@ -0,0 +1,262 @@ +# -*- 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 eos_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 + +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, + param_list_to_dict, + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.facts import ( + Facts, +) + + +class Vlans(ConfigBase): + """ + The eos_vlans class + """ + + gather_subset = ["!all", "!min"] + + gather_network_resources = ["vlans"] + + def get_vlans_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, + ) + vlans_facts = facts["ansible_network_resources"].get("vlans") + if not vlans_facts: + return [] + return vlans_facts + + def execute_module(self): + """Execute the module + + :rtype: A dictionary + :returns: The result from module execution + """ + result = {"changed": False} + warnings = list() + commands = list() + + if self.state in self.ACTION_STATES: + existing_vlans_facts = self.get_vlans_facts() + else: + existing_vlans_facts = {} + + 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._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_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["config"] + have = existing_vlans_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", "overridden", "rendered") + and not want + ): + self._module.fail_json( + msg="value of config parameter must not be empty for state {0}".format( + state, + ), + ) + want = param_list_to_dict(want, "vlan_id", remove_key=False) + have = param_list_to_dict(have, "vlan_id", remove_key=False) + if state == "overridden": + commands = self._state_overridden(want, have) + elif state == "deleted": + commands = self._state_deleted(want, have) + elif state == "merged" or state == "rendered": + commands = self._state_merged(want, have) + elif state == "replaced": + commands = self._state_replaced(want, have) + 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 = [] + for vlan_id, desired in want.items(): + if vlan_id in have: + extant = have[vlan_id] + else: + extant = dict() + + add_config = dict_diff(extant, desired) + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(vlan_id, add_config, del_config)) + + return commands + + @staticmethod + def _state_overridden(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 vlan_id, extant in have.items(): + if vlan_id in want: + desired = want[vlan_id] + else: + desired = dict() + + add_config = dict_diff(extant, desired) + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(vlan_id, add_config, del_config)) + + # Handle vlans not already in config + new_vlans = [vlan_id for vlan_id in want if vlan_id not in have] + for vlan_id in new_vlans: + desired = want[vlan_id] + extant = dict(vlan_id=vlan_id) + add_config = dict_diff(extant, desired) + + commands.extend(generate_commands(vlan_id, add_config, {})) + + return commands + + @staticmethod + def _state_merged(want, have): + """The command generator when state is merged + + :rtype: A list + :returns: the commands necessary to merge the provided into + the current configuration + """ + commands = [] + for vlan_id, desired in want.items(): + if vlan_id in have: + extant = have[vlan_id] + else: + extant = dict() + + add_config = dict_diff(extant, desired) + + commands.extend(generate_commands(vlan_id, add_config, {})) + + return 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 = [] + for vlan_id in want: + desired = dict() + if vlan_id in have: + extant = have[vlan_id] + else: + continue + + del_config = dict_diff(desired, extant) + + commands.extend(generate_commands(vlan_id, {}, del_config)) + + return commands + + +def generate_commands(vlan_id, to_set, to_remove): + commands = [] + if "vlan_id" in to_remove: + return ["no vlan {0}".format(vlan_id)] + + for key in to_remove: + if key in to_set.keys(): + continue + commands.append("no {0}".format(key)) + + for key, value in to_set.items(): + if key == "vlan_id" or value is None: + continue + + commands.append("{0} {1}".format(key, value)) + + if commands: + commands.insert(0, "vlan {0}".format(vlan_id)) + return commands diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/eos.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/eos.py new file mode 100644 index 000000000..292ad274f --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/eos.py @@ -0,0 +1,568 @@ +# +# 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. +# +# (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 os +import time + +from ansible.module_utils._text import to_text +from ansible.module_utils.connection import Connection, 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 ( + ComplexList, + to_list, +) + + +_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"] == "eapi": + conn = HttpApi(module) + _DEVICE_CONNECTION = conn + return _DEVICE_CONNECTION + + +def transform_commands(module): + transform = ComplexList( + dict( + command=dict(key=True), + output=dict(), + 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), + version=dict( + type="str", + default="latest", + choices=["latest", "1"], + ), + ), + module, + ) + + return transform(module.params["commands"]) + + +def session_name(): + """Generate a unique string to be used as a configuration session name.""" + return "ansible_%d" % (time.time() * 100) + + +class Cli: + def __init__(self, module): + self._module = module + self._device_configs = {} + self._session_support = None + self._connection = None + + @property + def supports_sessions(self): + if self._session_support is None: + self._session_support = self._get_connection().supports_sessions() + return self._session_support + + 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: + conn = self._get_connection() + try: + out = conn.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() + 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: + response = connection.run_commands( + commands=commands, + check_rc=check_rc, + ) + except ConnectionError as exc: + self._module.fail_json( + msg=to_text(exc, errors="surrogate_then_replace"), + ) + return response + + def get_session_config(self, commands, commit=False, replace=False): + """Loads the config commands onto the remote device""" + conn = self._get_connection() + try: + response = conn.get_session_config(commands, commit, replace) + except ConnectionError as exc: + message = getattr(exc, "err", to_text(exc)) + if ( + "check mode is not supported without configuration session" + in message + ): + self._module.warn( + "EOS can not check config without config session", + ) + response = {"changed": True} + else: + self._module.fail_json( + msg="%s" % message, + data=to_text(message, errors="surrogate_then_replace"), + ) + + return response + + def load_config(self, commands, commit=False, replace=False): + """Loads the config commands onto the remote device""" + conn = self._get_connection() + try: + response = conn.edit_config(commands, commit, replace) + except ConnectionError as exc: + message = getattr(exc, "err", to_text(exc)) + if ( + "check mode is not supported without configuration session" + in message + ): + self._module.warn( + "EOS can not check config without config session", + ) + response = {"changed": True} + else: + self._module.fail_json( + msg="%s" % message, + data=to_text(message, errors="surrogate_then_replace"), + ) + + return response + + 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: + diff = 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 diff + + 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 + + +class HttpApi: + def __init__(self, module): + self._module = module + self._device_configs = {} + self._session_support = None + 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 + + @property + def supports_sessions(self): + if self._session_support is None: + self._session_support = self._connection.supports_sessions() + return self._session_support + + def run_commands(self, commands, check_rc=True): + """Runs list of commands on remote device and returns results""" + output = None + queue = list() + responses = list() + + def run_queue(queue, output, version): + try: + response = to_list( + self._connection.send_request( + queue, + output=output, + version=version, + ), + ) + except ConnectionError as exc: + if check_rc: + raise + return to_list(to_text(exc)) + + if output == "json": + response = [json.loads(item) for item in response] + return response + + for item in to_list(commands): + cmd_output = "text" + if isinstance(item, dict): + command = item["command"] + if "output" in item: + cmd_output = item["output"] + if "version" in item: + version = item["version"] + else: + command = item + + # Emulate '| json' from CLI + if is_json(command): + command = command.rsplit("|", 1)[0] + cmd_output = "json" + + if output and output != cmd_output: + responses.extend(run_queue(queue, output, version)) + queue = list() + + output = cmd_output + queue.append(command) + + if queue: + responses.extend(run_queue(queue, output, version)) + + return responses + + 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=3) + candidate_obj.load(candidate) + + if running and diff_match != "none" and diff_replace != "config": + # running configuration + running_obj = NetworkConfig( + indent=3, + 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, config, commit=False, replace=False): + """Loads the configuration onto the remote devices + + If the device doesn't support configuration sessions, this will + fallback to using configure() to load the commands. If that happens, + there will be no returned diff or session values + """ + return self.edit_config(config, commit, replace) + + def edit_config(self, config, commit=False, replace=False): + """Loads the configuration onto the remote devices + + If the device doesn't support configuration sessions, this will + fallback to using configure() to load the commands. If that happens, + there will be no returned diff or session values + """ + session = session_name() + result = {"session": session} + banner_cmd = None + banner_input = [] + + commands = ["configure session %s" % session] + if replace: + commands.append("rollback clean-config") + + for command in config: + if command.startswith("banner"): + banner_cmd = command + banner_input = [] + elif banner_cmd: + if command == "EOF": + command = { + "cmd": banner_cmd, + "input": "\n".join(banner_input), + } + banner_cmd = None + commands.append(command) + else: + banner_input.append(command) + continue + else: + commands.append(command) + + try: + response = self._connection.send_request(commands) + except Exception: + commands = ["configure session %s" % session, "abort"] + response = self._connection.send_request(commands, output="text") + raise + + commands = [ + "configure session %s" % session, + "show session-config diffs", + ] + if commit: + commands.append("commit") + else: + commands.append("abort") + + response = self._connection.send_request(commands, output="text") + diff = response[1].strip() + if diff: + result["diff"] = diff + + return result + + def get_session_config(self, config, commit=False, replace=False): + """Loads the configuration onto the remote devices + + If the device doesn't support configuration sessions, this will + fallback to using configure() to load the commands. If that happens, + there will be no returned diff or session values + """ + resp = "" + use_session = os.getenv("ANSIBLE_EOS_USE_SESSIONS", True) + try: + use_session = int(use_session) + except ValueError: + pass + + if not all((bool(use_session), self.supports_sessions)): + if commit: + return self.configure(config) + else: + self._module.warn( + "EOS can not check config without config session", + ) + result = {"changed": True} + return result + session = session_name() + result = {"session": session} + commands = ["configure session %s" % session] + + if replace: + commands.append("rollback clean-config") + + commands.extend(config) + response = self._connection.send_request(commands) + if "error" in response: + commands = ["configure session %s" % session, "abort"] + self._connection.send_request(commands) + err = response["error"] + error_text = [] + for data in err["data"]: + error_text.extend(data.get("errors", [])) + error_text = "\n".join(error_text) or err["message"] + self._module.fail_json(msg=error_text, code=err["code"]) + + commands = [ + "configure session %s" % session, + "show session-config", + ] + if commit: + commands.append("commit") + else: + commands.append("abort") + response = self._connection.send_request(commands, output="text") + for out in response: + if out: + resp += out + "" + return resp.rstrip() + + 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 is_json(cmd): + return to_text(cmd, errors="surrogate_then_replace").endswith("| json") + + +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), + version=dict(type="str", default="latest"), + ), + module, + ) + + return transform(to_list(commands)) + + +def get_config(module, flags=None): + flags = None if flags is None else flags + + conn = get_connection(module) + return conn.get_config(flags) + + +def run_commands(module, commands, check_rc=True): + conn = get_connection(module) + return conn.run_commands(to_command(module, commands), check_rc=check_rc) + + +def load_config(module, config, commit=False, replace=False): + conn = get_connection(module) + return conn.load_config(config, commit, replace) + + +def get_session_config(module, config, commit=False, replace=False): + conn = get_connection(module) + return conn.get_session_config(config, commit, replace) + + +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 get_capabilities(module): + conn = get_connection(module) + return conn.get_capabilities() diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acl_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acl_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acl_interfaces/acl_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acl_interfaces/acl_interfaces.py new file mode 100644 index 000000000..2e53931de --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acl_interfaces/acl_interfaces.py @@ -0,0 +1,149 @@ +# +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.acl_interfaces.acl_interfaces import ( + Acl_interfacesArgs, +) + + +class Acl_interfacesFacts(object): + """The eos 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 | include interface | access-group | traffic-filter", + ) + + 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) + # split the config into instances of the resource + resource_delim = "interface" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [ + p.strip() for p in re.findall(find_pattern, data, re.DOTALL) + ] + 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}, + ) + facts["acl_interfaces"] = [ + utils.remove_empties(cfg) for cfg in 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) + access_group_list = [] + access_group_v6_list = [] + acls_list = [] + group_list = [] + group_dict = {} + config["name"] = utils.parse_conf_arg(conf, "^interface") + conf_lines = conf.split("\n") + for line in conf_lines: + if config["name"] in line: + continue + access_group = utils.parse_conf_arg(line, "^ip access-group") + # This module was verified on an ios device since vEOS doesnot support + # acl_interfaces cnfiguration. In ios, ipv6 acl is configured as + # traffic-filter and in eos it is access-group + + # access_group_v6 = utils.parse_conf_arg(line, 'ipv6 traffic-filter') + access_group_v6 = utils.parse_conf_arg(line, "^ipv6 access-group") + if access_group: + access_group_list.append(access_group) + if access_group_v6: + access_group_v6_list.append(access_group_v6) + if access_group_list: + for acl in access_group_list: + a_name = acl.split()[0] + a_dir = acl.split()[1] + acls_dict = {"name": a_name, "direction": a_dir} + acls_list.append(acls_dict) + group_dict = {"afi": "ipv4", "acls": acls_list} + group_list.append(group_dict) + acls_list = [] + if group_list: + config["access_groups"] = group_list + if access_group_v6_list: + for acl in access_group_v6_list: + a_name = acl.split()[0] + a_dir = acl.split()[1] + acls_dict = {"name": a_name, "direction": a_dir} + acls_list.append(acls_dict) + group_dict = {"acls": acls_list, "afi": "ipv6"} + group_list.append(group_dict) + acls_list = [] + if group_list: + config["access_groups"] = group_list + return utils.remove_empties(config) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acls/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acls/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acls/acls.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acls/acls.py new file mode 100644 index 000000000..567fca8ba --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/acls/acls.py @@ -0,0 +1,392 @@ +# +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.acls.acls import ( + AclsArgs, +) + + +class AclsFacts(object): + """The eos 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): + return connection.get("show running-config | section access-list") + + 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) + + # split the config into instances of the resource + find_pattern = r"(?:^|\n)(?:ip|ipv6) access\-list.*?(?=(?:^|\n)(?:ip|ipv6) access\-list|$)" + resources = [] + for p in re.findall(find_pattern, data, re.DOTALL): + resources.append(p) + + objs = [] + ipv4list = [] + ipv6list = [] + for resource in resources: + if "ipv6" in resource: + ipv6list.append(resource) + else: + ipv4list.append(resource) + ipv4list = ["\n".join(ipv4list)] + ipv6list = ["\n".join(ipv6list)] + for resource in ipv4list: + if resource: + obj = self.render_config(self.generated_spec, resource) + if obj: + objs.append(obj) + for resource in ipv6list: + 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: + facts["acls"] = [] + params = utils.validate_config( + self.argument_spec, + {"config": objs}, + ) + for cfg in params["config"]: + facts["acls"].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) + afi_list = [] + acls_list = [] + name_dict = {} + standard = 0 + operator = ["eq", "lt", "neq", "range", "gt"] + flags = ["ack", "established", "fin", "psh", "rst", "syn", "urg"] + others = ["hop_limit", "log", "ttl", "fragments", "tracked"] + for dev_config in conf.split("\n"): + ace_dict = {} + if not dev_config: + continue + if dev_config == "!": + continue + dev_config = dev_config.strip() + matches = re.findall(r"(ip.*?) access-list (.*)", dev_config) + if matches: + afi = "ipv4" if matches[0][0] == "ip" else "ipv6" + ace_list = [] + if bool(name_dict): + acls_list.append(name_dict.copy()) + name_dict = {} + if afi not in afi_list: + afi_list.append(afi) + config.update({"afi": afi}) + if "standard" in matches[0][1]: + standard = 1 + name = matches[0][1].split() + name_dict.update({"name": name[1]}) + name_dict.update({"standard": True}) + else: + name_dict.update({"name": matches[0][1]}) + else: + source_dict = {} + dest_dict = {} + dev_config = re.sub("-", "_", dev_config) + dev_config_remainder = dev_config.split() + if "fragment_rules" in dev_config: + ace_dict.update({"sequence": dev_config_remainder.pop(0)}) + ace_dict.update({"fragment_rules": True}) + if "remark" in dev_config: + ace_dict.update({"sequence": dev_config_remainder.pop(0)}) + ace_dict.update( + {"remark": " ".join(dev_config_remainder[1:])}, + ) + seq = re.search(r"\d+ (permit|deny) .*", dev_config) + if seq: + ace_dict.update({"sequence": dev_config_remainder.pop(0)}) + ace_dict.update({"grant": dev_config_remainder.pop(0)}) + if ( + dev_config_remainder + and dev_config_remainder[0] == "vlan" + ): + vlan_str = "" + dev_config_remainder.pop(0) + if ( + dev_config_remainder + and dev_config_remainder[0] == "inner" + ): + vlan_str = dev_config_remainder.pop(0) + " " + vlan_str = ( + dev_config_remainder.pop(0) + + " " + + dev_config_remainder.pop(0) + ) + ace_dict.update({"vlan": vlan_str}) + if not standard: + protocol = dev_config_remainder[0] + ace_dict.update( + {"protocol": dev_config_remainder.pop(0)}, + ) + src_prefix = re.search(r"/", dev_config_remainder[0]) + src_address = re.search( + r"[a-z\d:\.]+", + dev_config_remainder[0], + ) + if ( + dev_config_remainder + and dev_config_remainder[0] == "host" + ): + source_dict.update( + {"host": dev_config_remainder.pop(1)}, + ) + dev_config_remainder.pop(0) + elif ( + dev_config_remainder + and dev_config_remainder[0] == "any" + ): + source_dict.update({"any": True}) + dev_config_remainder.pop(0) + elif src_prefix: + source_dict.update( + {"subnet_address": dev_config_remainder.pop(0)}, + ) + elif src_address: + source_dict.update( + {"address": dev_config_remainder.pop(0)}, + ) + source_dict.update( + {"wildcard_bits": dev_config_remainder.pop(0)}, + ) + if dev_config_remainder: + if ( + dev_config_remainder + and dev_config_remainder[0] in operator + ): + port_dict = {} + src_port = "" + src_opr = dev_config_remainder.pop(0) + portlist = dev_config_remainder[:] + for config_remainder in portlist: + addr = re.search(r"[\.\:]", config_remainder) + if ( + config_remainder == "any" + or config_remainder == "host" + or addr + ): + break + src_port = src_port + " " + config_remainder + dev_config_remainder.pop(0) + src_port = src_port.strip() + port_dict.update({src_opr: src_port}) + source_dict.update({"port_protocol": port_dict}) + ace_dict.update({"source": source_dict}) + if not dev_config_remainder or standard: + if ( + dev_config_remainder + and "log" in dev_config_remainder + ): + ace_dict.update({"log": True}) + if bool(ace_dict): + ace_list.append(ace_dict.copy()) + if len(ace_list): + name_dict = name_dict.copy() + name_dict.update({"aces": ace_list[:]}) + # acls_list.append(name_dict) + continue + dest_prefix = re.search(r"/", dev_config_remainder[0]) + dest_address = re.search( + r"[a-z\d:\.]+", + dev_config_remainder[0], + ) + if ( + dev_config_remainder + and dev_config_remainder[0] == "host" + ): + dest_dict.update({"host": dev_config_remainder.pop(1)}) + dev_config_remainder.pop(0) + elif ( + dev_config_remainder + and dev_config_remainder[0] == "any" + ): + dest_dict.update({"any": True}) + dev_config_remainder.pop(0) + elif dest_prefix: + dest_dict.update( + {"subnet_address": dev_config_remainder.pop(0)}, + ) + elif dest_address: + dest_dict.update( + {"address": dev_config_remainder.pop(0)}, + ) + dest_dict.update( + {"wildcard_bits": dev_config_remainder.pop(0)}, + ) + if dev_config_remainder: + if dev_config_remainder[0] in operator: + port_dict = {} + dest_port = "" + dest_opr = dev_config_remainder.pop(0) + portlist = dev_config_remainder[:] + for config_remainder in portlist: + if ( + config_remainder in operator + or config_remainder in others + ): + break + dest_port = dest_port + " " + config_remainder + dev_config_remainder.pop(0) + dest_port = dest_port.strip() + port_dict.update({dest_opr: dest_port}) + dest_dict.update({"port_protocol": port_dict}) + ace_dict.update({"destination": dest_dict}) + protocol_option_dict = {} + tcp_dict = {} + icmp_dict = {} + ip_dict = {} + if not dev_config_remainder: + if bool(ace_dict): + ace_list.append(ace_dict.copy()) + if len(ace_list): + name_dict = name_dict.copy() + name_dict.update({"aces": ace_list[:]}) + # acls_list.append(name_dict) + continue + if protocol in ["tcp", "6"]: + protocol = "tcp" + flags_dict = {} + if ( + dev_config_remainder + and dev_config_remainder[0] in flags + ): + flaglist = dev_config_remainder[:] + for config_remainder in flaglist: + if config_remainder not in flags: + break + flags_dict.update({config_remainder: True}) + dev_config_remainder.pop(0) + if bool(flags_dict): + tcp_dict.update({"flags": flags_dict}) + if bool(tcp_dict): + protocol_option_dict.update({"tcp": tcp_dict}) + if ( + protocol == "icmp" + or protocol == "icmpv6" + or protocol == "1" + or protocol == "58" + ): + if protocol == "1": + protocol = "icmp" + elif protocol == "58": + protocol = "icmpv6" + if ( + dev_config_remainder + and dev_config_remainder[0] not in others + ): + icmp_dict.update({dev_config_remainder[0]: True}) + dev_config_remainder.pop(0) + if bool(icmp_dict): + protocol_option_dict.update({protocol: icmp_dict}) + if protocol in ["ip", "ipv6"]: + if ( + dev_config_remainder + and dev_config_remainder[0] == "nexthop_group" + ): + dev_config_remainder.pop(0) + ip_dict.update( + {"nexthop_group": dev_config_remainder.pop(0)}, + ) + if bool(ip_dict): + protocol_option_dict.update({protocol: ip_dict}) + if bool(protocol_option_dict): + ace_dict.update( + {"protocol_options": protocol_option_dict}, + ) + if ( + dev_config_remainder + and dev_config_remainder[0] == "ttl" + ): + dev_config_remainder.pop(0) + op = dev_config_remainder.pop(0) + ttl_dict = {op: dev_config_remainder.pop(0)} + ace_dict.update({"ttl": ttl_dict}) + for config_remainder in dev_config_remainder: + if config_remainder in others: + if config_remainder == "hop_limit": + hop_index = dev_config_remainder.index( + config_remainder, + ) + hoplimit_dict = { + dev_config_remainder[ + hop_index + 1 + ]: dev_config_remainder[hop_index + 2], + } + ace_dict.update({"hop_limit": hoplimit_dict}) + dev_config_remainder.pop(0) + continue + ace_dict.update({config_remainder: True}) + dev_config_remainder.pop(0) + if dev_config_remainder: + config.update({"line": dev_config}) + return utils.remove_empties(config) + if bool(ace_dict): + ace_list.append(ace_dict.copy()) + if len(ace_list): + name_dict = name_dict.copy() + name_dict.update({"aces": ace_list[:]}) + acls_list.append(name_dict.copy()) + config.update({"acls": acls_list}) + return utils.remove_empties(config) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_address_family/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_address_family/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_address_family/bgp_address_family.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_address_family/bgp_address_family.py new file mode 100644 index 000000000..649e5c6eb --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_address_family/bgp_address_family.py @@ -0,0 +1,120 @@ +# -*- 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 eos 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. +""" + +import re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.bgp_address_family.bgp_address_family import ( + Bgp_afArgs, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.bgp_address_family import ( + Bgp_afTemplate, +) + + +class Bgp_afFacts(object): + """The eos bgp_address_family facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Bgp_afArgs.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\\sbgp ") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for Bgp_af 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) + + # remove global configs from bgp_address_family + bgp_af_config = [] + vrf_set = "" + start = False + for bgp_line in data.splitlines(): + if "router bgp" in bgp_line: + bgp_af_config.append(bgp_line) + vrf_present = re.search(r"vrf\s\S+", bgp_line) + if vrf_present: + vrf_set = vrf_present.group(0) + if start: + bgp_af_config.append(bgp_line) + if "address-family" in bgp_line: + af_line = vrf_set + bgp_line + bgp_af_config.append(af_line) + start = True + if start and "!" in bgp_line: + start = False + + # parse native config using the Bgp_af template + bgp_af_parser = Bgp_afTemplate(lines=bgp_af_config) + objs = bgp_af_parser.parse() + if objs: + if "address_family" in objs: + objs["address_family"] = list(objs["address_family"].values()) + for af in objs["address_family"]: + if "neighbor" in af: + af["neighbor"] = list(af["neighbor"].values()) + if "network" in af: + af["network"] = list(af["network"].values()) + af["network"] = sorted( + af["network"], + key=lambda k: k["address"], + ) + + 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 diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_global/bgp_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_global/bgp_global.py new file mode 100644 index 000000000..9cb785a3c --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/bgp_global/bgp_global.py @@ -0,0 +1,136 @@ +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.bgp_global.bgp_global import ( + Bgp_globalArgs, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.bgp_global import ( + Bgp_globalTemplate, +) + + +class Bgp_globalFacts(object): + """The eos 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\\sbgp ") + + 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 = {} + objs = [] + if not data: + data = self.get_config(connection) + + # remove address_family configs from bgp_global + bgp_global_config = [] + start = False + self._af = False + not_bgp = False + for bgp_line in data.splitlines(): + if "router " in bgp_line: + # Skip other protocol configs like router ospf etc + if "router bgp" not in bgp_line: + not_bgp = True + continue + not_bgp = False + if not start and not not_bgp: + bgp_global_config.append(bgp_line) + if "address-family" in bgp_line: + start = True + self._af = True + if start and "!" in bgp_line: + start = False + + # parse native config using the Bgp_global template + bgp_global_parser = Bgp_globalTemplate( + lines=bgp_global_config, + module=self._module, + ) + objs = bgp_global_parser.parse() + + if objs: + global_vals = objs.get("vrfs", {}).pop("vrf_", {}) + for key, value in iteritems(global_vals): + objs[key] = value + + if "vrfs" in objs: + objs["vrfs"] = list(objs["vrfs"].values()) + for vrf in objs["vrfs"]: + if "neighbor" in vrf: + vrf["neighbor"] = list(vrf["neighbor"].values()) + if "network" in vrf: + vrf["network"] = list(vrf["network"].values()) + vrf["network"] = sorted( + vrf["network"], + key=lambda k: k["address"], + ) + if "aggregate_address" in vrf: + vrf["aggregate_address"] = sorted( + vrf["aggregate_address"], + key=lambda k: k["address"], + ) + + if "neighbor" in objs: + objs["neighbor"] = list(objs["neighbor"].values()) + + if "network" in objs: + objs["network"] = list(objs["network"].values()) + objs["network"] = sorted( + objs["network"], + key=lambda k: k["address"], + ) + if "aggregate_address" in objs: + objs["aggregate_address"] = sorted( + objs["aggregate_address"], + key=lambda k: k["address"], + ) + + ansible_facts["ansible_network_resources"].pop("bgp_global", None) + + params = utils.remove_empties( + bgp_global_parser.validate_config( + self.argument_spec, + {"config": objs}, + redact=True, + ), + ) + + facts["bgp_global"] = params.get("config", []) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/facts.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/facts.py new file mode 100644 index 000000000..87fd402eb --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/facts.py @@ -0,0 +1,160 @@ +# -*- 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 facts class for eos +this file validates each subset of facts and selectively +calls the appropriate facts gathering function +""" + +from __future__ import absolute_import, division, print_function + + +__metaclass__ = type + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.facts.facts import ( + FactsBase, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.acl_interfaces.acl_interfaces import ( + Acl_interfacesFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.acls.acls import ( + AclsFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.bgp_address_family.bgp_address_family import ( + Bgp_afFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.bgp_global.bgp_global import ( + Bgp_globalFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.hostname.hostname import ( + HostnameFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.interfaces.interfaces import ( + InterfacesFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.l2_interfaces.l2_interfaces import ( + L2_interfacesFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.l3_interfaces.l3_interfaces import ( + L3_interfacesFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.lacp.lacp import ( + LacpFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.lacp_interfaces.lacp_interfaces import ( + Lacp_interfacesFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.lag_interfaces.lag_interfaces import ( + Lag_interfacesFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.legacy.base import ( + Config, + Default, + Hardware, + Interfaces, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.lldp_global.lldp_global import ( + Lldp_globalFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.lldp_interfaces.lldp_interfaces import ( + Lldp_interfacesFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.logging_global.logging_global import ( + Logging_globalFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.ntp_global.ntp_global import ( + Ntp_globalFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.ospf_interfaces.ospf_interfaces import ( + Ospf_interfacesFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.ospfv2.ospfv2 import ( + Ospfv2Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.ospfv3.ospfv3 import ( + Ospfv3Facts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.prefix_lists.prefix_lists import ( + Prefix_listsFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.route_maps.route_maps import ( + Route_mapsFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.snmp_server.snmp_server import ( + Snmp_serverFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.static_routes.static_routes import ( + Static_routesFacts, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.facts.vlans.vlans import ( + VlansFacts, +) + + +FACT_LEGACY_SUBSETS = dict( + default=Default, + hardware=Hardware, + interfaces=Interfaces, + config=Config, +) +FACT_RESOURCE_SUBSETS = dict( + interfaces=InterfacesFacts, + l2_interfaces=L2_interfacesFacts, + l3_interfaces=L3_interfacesFacts, + lacp=LacpFacts, + lacp_interfaces=Lacp_interfacesFacts, + lag_interfaces=Lag_interfacesFacts, + lldp_global=Lldp_globalFacts, + lldp_interfaces=Lldp_interfacesFacts, + vlans=VlansFacts, + acl_interfaces=Acl_interfacesFacts, + acls=AclsFacts, + static_routes=Static_routesFacts, + ospfv2=Ospfv2Facts, + ospfv3=Ospfv3Facts, + ospf_interfaces=Ospf_interfacesFacts, + bgp_address_family=Bgp_afFacts, + bgp_global=Bgp_globalFacts, + route_maps=Route_mapsFacts, + prefix_lists=Prefix_listsFacts, + logging_global=Logging_globalFacts, + ntp_global=Ntp_globalFacts, + snmp_server=Snmp_serverFacts, + hostname=HostnameFacts, +) + + +class Facts(FactsBase): + """The fact class for eos""" + + VALID_LEGACY_GATHER_SUBSETS = frozenset(FACT_LEGACY_SUBSETS.keys()) + VALID_RESOURCE_SUBSETS = frozenset(FACT_RESOURCE_SUBSETS.keys()) + + def get_facts( + self, + legacy_facts_type=None, + resource_facts_type=None, + data=None, + ): + """Collect the facts for eos + :param legacy_facts_type: List of legacy facts types + :param resource_facts_type: List of resource fact types + :param data: previously collected conf + :rtype: dict + :return: the facts gathered + """ + if self.VALID_RESOURCE_SUBSETS: + self.get_network_resources_facts( + FACT_RESOURCE_SUBSETS, + resource_facts_type, + data, + ) + + if self.VALID_LEGACY_GATHER_SUBSETS: + self.get_network_legacy_facts( + FACT_LEGACY_SUBSETS, + legacy_facts_type, + ) + return self.ansible_facts, self._warnings diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/hostname/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/hostname/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/hostname/hostname.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/hostname/hostname.py new file mode 100644 index 000000000..1c95fe576 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/hostname/hostname.py @@ -0,0 +1,76 @@ +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.hostname.hostname import ( + HostnameArgs, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.hostname import ( + HostnameTemplate, +) + + +class HostnameFacts(object): + """The eos hostname facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = HostnameArgs.argument_spec + + def get_config(self, connection): + 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/arista/eos/plugins/module_utils/network/eos/facts/interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/interfaces/interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/interfaces/interfaces.py new file mode 100644 index 000000000..f4d77637e --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/interfaces/interfaces.py @@ -0,0 +1,116 @@ +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.interfaces.interfaces import ( + InterfacesArgs, +) + + +class InterfacesFacts(object): + """The eos 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 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 interfaces + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected configuration + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_device_data(connection) + + # operate on a collection of resource x + config = ("\n" + data).split("\ninterface ") + objs = [] + for conf in config: + if conf: + obj = self.render_config(self.generated_spec, conf) + if obj: + objs.append(obj) + 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) + + # populate the facts from the configuration + config["name"] = re.match(r"(\S+)", conf).group(1) + description = utils.parse_conf_arg(conf, "description") + if description is not None: + config["description"] = description.replace('"', "") + shutdown = utils.parse_conf_cmd_arg(conf, "shutdown", False) + config["enabled"] = shutdown if shutdown is False else True + config["mtu"] = utils.parse_conf_arg(conf, "mtu") + config["mode"] = utils.parse_conf_cmd_arg( + conf, + "switchport", + "layer2", + "layer3", + ) + + state = utils.parse_conf_arg(conf, "speed") + if state: + if state == "auto": + config["duplex"] = state + else: + # remaining options are all e.g., 10half or 40gfull + config["speed"] = state[:-4] + config["duplex"] = state[-4:] + return utils.remove_empties(config) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l2_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l2_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l2_interfaces/l2_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l2_interfaces/l2_interfaces.py new file mode 100644 index 000000000..b77b1a611 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l2_interfaces/l2_interfaces.py @@ -0,0 +1,120 @@ +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.l2_interfaces.l2_interfaces import ( + L2_interfacesArgs, +) + + +class L2_interfacesFacts(object): + """The eos 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 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 l2_interfaces + + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected configuration + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_device_data(connection) + + # operate on a collection of resource x + config = ("\n" + data).split("\ninterface ") + objs = [] + for conf in config: + if conf: + obj = self.render_config(self.generated_spec, conf) + if obj: + objs.append(obj) + facts = {} + if objs: + params = utils.validate_config( + self.argument_spec, + {"config": objs}, + ) + facts["l2_interfaces"] = [ + utils.remove_empties(cfg) for cfg in 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) + + # populate the facts from the configuration + config["name"] = re.match(r"(\S+)", conf).group(1).replace('"', "") + has_mode = re.search(r"switchport mode (\S+)", conf) + if has_mode: + config["mode"] = has_mode.group(1) + + has_access = re.search(r"switchport access vlan (\d+)", conf) + if has_access: + config["access"] = {"vlan": int(has_access.group(1))} + + has_trunk = re.findall(r"switchport trunk (.+)", conf) + if has_trunk: + trunk = {} + for match in has_trunk: + has_native = re.match(r"native vlan (\d+)", match) + if has_native: + trunk["native_vlan"] = int(has_native.group(1)) + continue + + has_allowed = re.match(r"allowed vlan (\S+)", match) + if has_allowed: + # TODO: listify? + trunk["trunk_allowed_vlans"] = has_allowed.group(1) + continue + config["trunk"] = trunk + + return utils.remove_empties(config) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l3_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l3_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l3_interfaces/l3_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l3_interfaces/l3_interfaces.py new file mode 100644 index 000000000..3a617507e --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/l3_interfaces/l3_interfaces.py @@ -0,0 +1,124 @@ +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.l3_interfaces.l3_interfaces import ( + L3_interfacesArgs, +) + + +class L3_interfacesFacts(object): + """The eos 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 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 l3_interfaces + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected configuration + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_device_data(connection) + + # split the config into instances of the resource + resource_delim = "interface" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [ + p.strip() for p in re.findall(find_pattern, data, re.DOTALL) + ] + + objs = [] + for resource in resources: + if resource: + obj = self.render_config(self.generated_spec, resource) + if obj: + objs.append(obj) + facts = {} + if objs: + params = utils.validate_config( + self.argument_spec, + {"config": objs}, + ) + facts["l3_interfaces"] = [ + utils.remove_empties(cfg) for cfg in 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"] = utils.parse_conf_arg(conf, "interface") + + matches = re.findall(r".*ip address (.+)$", conf, re.MULTILINE) + if matches: + config["ipv4"] = [] + for match in matches: + address, dummy, remainder = match.partition(" ") + if address == "virtual": + ipv4 = {"virtual": True, "address": remainder} + else: + ipv4 = {"address": address} + if remainder == "secondary": + ipv4["secondary"] = True + config["ipv4"].append(ipv4) + + matches = re.findall(r".*ipv6 address (.+)$", conf, re.MULTILINE) + if matches: + config["ipv6"] = [] + for match in matches: + address, dummy, remainder = match.partition(" ") + ipv6 = {"address": address} + config["ipv6"].append(ipv6) + + return utils.remove_empties(config) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp/lacp.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp/lacp.py new file mode 100644 index 000000000..28ba476b8 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp/lacp.py @@ -0,0 +1,106 @@ +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.lacp.lacp import ( + LacpArgs, +) + + +class LacpFacts(object): + """The eos 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 get_device_data(self, connection): + return connection.get("show running-config | section ^lacp") + + 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 configuration + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_device_data(connection) + + # split the config into instances of the resource + resource_delim = "lacp" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [ + p.strip() for p in re.findall(find_pattern, data, re.DOTALL) + ] + + objs = {} + for resource in resources: + if resource: + obj = self.render_config(self.generated_spec, resource) + if obj: + objs.update(obj) + + ansible_facts["ansible_network_resources"].pop("lacp", None) + facts = {"lacp": {}} + 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) + config["system"]["priority"] = utils.parse_conf_arg( + conf, + "system-priority", + ) + + return utils.remove_empties(config) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp_interfaces/lacp_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp_interfaces/lacp_interfaces.py new file mode 100644 index 000000000..6961ca1b6 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lacp_interfaces/lacp_interfaces.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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.lacp_interfaces.lacp_interfaces import ( + Lacp_interfacesArgs, +) + + +class Lacp_interfacesFacts(object): + """The eos 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 get_device_data(self, connection): + return connection.get("show running-config | section lacp") + + 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 configuration + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_device_data(connection) + + # split the config into instances of the resource + resource_delim = "interface" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [ + p.strip() for p in re.findall(find_pattern, data, re.DOTALL) + ] + + 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("lacp_interfaces", None) + facts = {} + if objs: + params = utils.validate_config( + self.argument_spec, + {"config": objs}, + ) + facts["lacp_interfaces"] = [ + utils.remove_empties(cfg) for cfg in 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"] = utils.parse_conf_arg(conf, "interface") + config["port_priority"] = utils.parse_conf_arg(conf, "port-priority") + config["timer"] = utils.parse_conf_arg(conf, "timer") + + return utils.remove_empties(config) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lag_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lag_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lag_interfaces/lag_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lag_interfaces/lag_interfaces.py new file mode 100644 index 000000000..73b6be3bb --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lag_interfaces/lag_interfaces.py @@ -0,0 +1,124 @@ +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.lag_interfaces.lag_interfaces import ( + Lag_interfacesArgs, +) + + +class Lag_interfacesFacts(object): + """The eos 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 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 lag_interfaces + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected configuration + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_device_data(connection) + + # split the config into instances of the resource + resource_delim = "interface" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [ + p.strip() for p in re.findall(find_pattern, data, re.DOTALL) + ] + + objs = {} + for resource in resources: + if resource: + obj = self.render_config(self.generated_spec, resource) + if obj: + group_name = obj["name"] + if group_name in objs and "members" in obj: + config = objs[group_name] + if "members" not in config: + config["members"] = [] + objs[group_name]["members"].extend(obj["members"]) + else: + objs[group_name] = obj + objs = list(objs.values()) + facts = {"lag_interfaces": []} + if objs: + params = utils.validate_config( + self.argument_spec, + {"config": objs}, + ) + facts["lag_interfaces"] = [ + utils.remove_empties(cfg) for cfg in 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) + interface_name = utils.parse_conf_arg(conf, "interface") + if interface_name.startswith("Port-Channel"): + config["name"] = interface_name + return utils.remove_empties(config) + + interface = {"member": interface_name} + match = re.match( + r".*channel-group (\d+) mode (\S+)", + conf, + re.MULTILINE | re.DOTALL, + ) + if match: + config["name"], interface["mode"] = match.groups() + config["name"] = "Port-Channel" + config["name"] + config["members"] = [interface] + + return utils.remove_empties(config) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/legacy/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/legacy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/legacy/base.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/legacy/base.py new file mode 100644 index 000000000..6e0424f15 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/legacy/base.py @@ -0,0 +1,180 @@ +# -*- 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.arista.eos.plugins.module_utils.network.eos.eos import ( + get_capabilities, + run_commands, +) + + +class FactsBase(object): + COMMANDS = frozenset() + + def __init__(self, module): + self.module = module + self.warnings = list() + self.facts = dict() + self.responses = None + + def populate(self): + self.responses = run_commands( + self.module, + list(self.COMMANDS), + check_rc=False, + ) + + +class Default(FactsBase): + SYSTEM_MAP = {"serialNumber": "serialnum"} + + COMMANDS = ["show version | json", "show hostname | json"] + + def populate(self): + super(Default, self).populate() + data = self.responses[0] + for key, value in iteritems(self.SYSTEM_MAP): + if key in data: + self.facts[value] = data[key] + + self.facts.update(self.responses[1]) + self.facts.update(self.platform_facts()) + + def platform_facts(self): + platform_facts = {} + + resp = get_capabilities(self.module) + 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 + + +class Hardware(FactsBase): + COMMANDS = ["dir all-filesystems", "show version | json"] + + def populate(self): + super(Hardware, self).populate() + self.facts.update(self.populate_filesystems()) + self.facts.update(self.populate_memory()) + + def populate_filesystems(self): + data = self.responses[0] + + if isinstance(data, dict): + data = data["messages"][0] + + fs = re.findall(r"^Directory of (.+)/", data, re.M) + return dict(filesystems=fs) + + def populate_memory(self): + values = self.responses[1] + return dict( + memfree_mb=int(values["memFree"]) / 1024, + memtotal_mb=int(values["memTotal"]) / 1024, + ) + + +class Config(FactsBase): + COMMANDS = ["show running-config"] + + def populate(self): + super(Config, self).populate() + self.facts["config"] = self.responses[0] + + +class Interfaces(FactsBase): + INTERFACE_MAP = { + "description": "description", + "physicalAddress": "macaddress", + "mtu": "mtu", + "bandwidth": "bandwidth", + "duplex": "duplex", + "lineProtocolStatus": "lineprotocol", + "interfaceStatus": "operstatus", + "forwardingModel": "type", + } + + COMMANDS = ["show interfaces | json", "show lldp neighbors | json"] + + def populate(self): + super(Interfaces, self).populate() + + self.facts["all_ipv4_addresses"] = list() + self.facts["all_ipv6_addresses"] = list() + + data = self.responses[0] + if data and "LLDP is not enabled" not in data: + self.facts["interfaces"] = self.populate_interfaces(data) + + if len(self.responses) > 1: + data = self.responses[1] + if data: + self.facts["neighbors"] = self.populate_neighbors( + data["lldpNeighbors"], + ) + + def populate_interfaces(self, data): + facts = dict() + for key, value in iteritems(data["interfaces"]): + intf = dict() + + for remote, local in iteritems(self.INTERFACE_MAP): + if remote in value: + intf[local] = value[remote] + + if "interfaceAddress" in value: + intf["ipv4"] = dict() + for entry in value["interfaceAddress"]: + intf["ipv4"]["address"] = entry["primaryIp"]["address"] + intf["ipv4"]["masklen"] = entry["primaryIp"]["maskLen"] + self.add_ip_address(entry["primaryIp"]["address"], "ipv4") + + if "interfaceAddressIp6" in value: + intf["ipv6"] = dict() + for entry in value["interfaceAddressIp6"]["globalUnicastIp6s"]: + intf["ipv6"]["address"] = entry["address"] + intf["ipv6"]["subnet"] = entry["subnet"] + self.add_ip_address(entry["address"], "ipv6") + + facts[key] = intf + + return facts + + def add_ip_address(self, address, family): + if family == "ipv4": + self.facts["all_ipv4_addresses"].append(address) + else: + self.facts["all_ipv6_addresses"].append(address) + + def populate_neighbors(self, neighbors): + facts = dict() + for value in neighbors: + port = value["port"] + if port not in facts: + facts[port] = list() + lldp = dict() + lldp["host"] = value["neighborDevice"] + lldp["port"] = value["neighborPort"] + facts[port].append(lldp) + return facts diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_global/lldp_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_global/lldp_global.py new file mode 100644 index 000000000..d59be7c54 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_global/lldp_global.py @@ -0,0 +1,102 @@ +# +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.lldp_global.lldp_global import ( + Lldp_globalArgs, +) + + +class Lldp_globalFacts(object): + """The eos 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 get_device_data(self, connection): + return connection.get("show running-config | section lldp") + + 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 = self.get_device_data(connection) + + obj = {} + if data: + obj.update(self.render_config(self.generated_spec, data)) + + ansible_facts["ansible_network_resources"].pop("lldp_global", None) + facts = {} + if obj: + params = utils.validate_config(self.argument_spec, {"config": obj}) + facts["lldp_global"] = utils.remove_empties(params["config"]) + else: + facts["lldp_global"] = {} + + 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["holdtime"] = utils.parse_conf_arg(conf, "hold-time") + config["reinit"] = utils.parse_conf_arg(conf, "timer reinitialization") + config["timer"] = utils.parse_conf_arg(conf, "timer") + if config.get("timer") and "reinitialization" in config["timer"]: + config["timer"] = None + + for match in re.findall( + r"^(no)? lldp tlv transmit (\S+)", + conf, + re.MULTILINE, + ): + tlv_option = match[1].replace("-", "_") + config["tlv_select"][tlv_option] = bool(match[0] != "no") + + return utils.remove_empties(config) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_interfaces/lldp_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_interfaces/lldp_interfaces.py new file mode 100644 index 000000000..569986ef2 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/lldp_interfaces/lldp_interfaces.py @@ -0,0 +1,106 @@ +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.lldp_interfaces.lldp_interfaces import ( + Lldp_interfacesArgs, +) + + +class Lldp_interfacesFacts(object): + """The eos 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 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 configuration + :rtype: dictionary + :returns: facts + """ + if not data: + data = connection.get("show running-config | section lldp") + + # split the config into instances of the resource + resource_delim = "interface" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [ + p.strip() for p in re.findall(find_pattern, data, re.DOTALL) + ] + + 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("lldp_interfaces", None) + facts = {} + if objs: + params = utils.validate_config( + self.argument_spec, + {"config": objs}, + ) + facts["lldp_interfaces"] = [ + utils.remove_empties(cfg) for cfg in 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"] = utils.parse_conf_arg(conf, "interface") + + matches = re.findall(r"(no )?lldp (\S+)", conf) + for match in matches: + config[match[1]] = not bool(match[0]) + + return utils.remove_empties(config) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/logging_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/logging_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/logging_global/logging_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/logging_global/logging_global.py new file mode 100644 index 000000000..e2e0e2cfd --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/logging_global/logging_global.py @@ -0,0 +1,98 @@ +# -*- 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 eos 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.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.logging_global.logging_global import ( + Logging_globalArgs, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.logging_global import ( + Logging_globalTemplate, +) + + +class Logging_globalFacts(object): + """The eos 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 | section 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 = {} + + if not data: + data = self.get_config(connection) + + # parse native config using the Prefix_lists template + logging_parser = Logging_globalTemplate( + lines=data.splitlines(), + module=self._module, + ) + objs = logging_parser.parse() + if objs: + if "hosts" in objs: + objs["hosts"] = sorted( + list(objs["hosts"].values()), + key=lambda k, sk="name": k[sk], + ) + if "vrfs" in objs: + for k, v in iteritems(objs["vrfs"]): + if "hosts" in v: + v["hosts"] = sorted( + list(v["hosts"].values()), + key=lambda k, sk="name": k[sk], + ) + objs["vrfs"] = sorted( + list(objs["vrfs"].values()), + key=lambda k, sk="name": k[sk], + ) + else: + objs = {} + ansible_facts["ansible_network_resources"].pop("logging_global", None) + + params = utils.remove_empties( + logging_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/arista/eos/plugins/module_utils/network/eos/facts/ntp_global/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ntp_global/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ntp_global/ntp_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ntp_global/ntp_global.py new file mode 100644 index 000000000..96c2321c7 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ntp_global/ntp_global.py @@ -0,0 +1,97 @@ +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.ntp_global.ntp_global import ( + Ntp_globalArgs, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.ntp_global import ( + Ntp_globalTemplate, +) + + +class Ntp_globalFacts(object): + """The eos 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 | section 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 objs: + if "authentication_keys" in objs: + objs["authentication_keys"] = sorted( + list(objs["authentication_keys"].values()), + key=lambda k, sk="id": k[sk], + ) + if "serve" in objs: + if "access_lists" in objs["serve"]: + objs["serve"]["access_lists"] = sorted( + list(objs["serve"]["access_lists"].values()), + key=lambda k, sk="afi": k[sk], + ) + if "servers" in objs: + objs["servers"] = sorted( + list(objs["servers"].values()), + key=lambda k, sk="server": k[sk], + ) + else: + objs = {} + 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/arista/eos/plugins/module_utils/network/eos/facts/ospf_interfaces/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospf_interfaces/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospf_interfaces/ospf_interfaces.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospf_interfaces/ospf_interfaces.py new file mode 100644 index 000000000..981b6bca7 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospf_interfaces/ospf_interfaces.py @@ -0,0 +1,107 @@ +# -*- 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 eos 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. +""" + +import re + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.ospf_interfaces.ospf_interfaces import ( + Ospf_interfacesArgs, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.ospf_interfaces import ( + Ospf_interfacesTemplate, +) + + +class Ospf_interfacesFacts(object): + """The eos 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 = {} + + if not data: + data = self.get_config(connection) + + resource_delim = "interface" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [ + p.strip() for p in re.findall(find_pattern, data, re.DOTALL) + ] + # parse native config using the Ospf_interfaces template + ospf_interfaces_facts = [] + for resource in resources: + ospf_interfaces_parser = Ospf_interfacesTemplate( + lines=resource.splitlines(), + module=self._module, + ) + entry = ospf_interfaces_parser.parse() + if entry: + if "address_family" in entry and entry["address_family"]: + entry["address_family"] = sorted( + list(entry["address_family"].values()), + key=lambda k, sk="afi": k[sk], + ) + if entry: + if entry.get("address_family"): + for addr in entry["address_family"]: + if "ip_params" in addr: + addr["ip_params"] = sorted( + list(addr["ip_params"].values()), + key=lambda k, sk="afi": k[sk], + ) + ospf_interfaces_facts.append(entry) + + ansible_facts["ansible_network_resources"].pop("ospf_interfaces", None) + facts = {"ospf_interfaces": []} + params = utils.remove_empties( + ospf_interfaces_parser.validate_config( + self.argument_spec, + {"config": ospf_interfaces_facts}, + redact=True, + ), + ) + + if params.get("config"): + for cfg in params["config"]: + facts["ospf_interfaces"].append(utils.remove_empties(cfg)) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv2/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv2/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv2/ospfv2.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv2/ospfv2.py new file mode 100644 index 000000000..f5f35541a --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv2/ospfv2.py @@ -0,0 +1,510 @@ +# +# -*- 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 eos ospfv2 fact class +It is in this file the configuration is collected from the device +for a given resource, parsed, and the facts tree is populated +based on the configuration. +""" +import re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.ospfv2.ospfv2 import ( + Ospfv2Args, +) + + +class Ospfv2Facts(object): + """The eos ospfv2 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_device_data(self, connection): + return connection.get("show running-config | section ospf") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for ospfv2 + :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) + + # split the config into instances of the resource + resource_delim = "router ospf" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [ + p.strip() for p in re.findall(find_pattern, data, re.DOTALL) + ] + objs_list = [] + objs = {} + for resource in resources: + if resource and "router ospfv3" not in resource: + obj = self.render_config(self.generated_spec, resource) + if obj: + objs_list.append(obj) + objs = {"processes": objs_list} + ansible_facts["ansible_network_resources"].pop("ospfv2", None) + + facts = {} + if objs: + facts["ospfv2"] = {} + params = utils.validate_config( + self.argument_spec, + {"config": objs}, + ) + facts["ospfv2"].update(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 + """ + instance_list = [] + ospf_params_dict = {} + areas_list = [] + distance_dict = {} + network_list = [] + redistribute_list = [] + timers_list = [] + areas_list = [] + for dev_config in conf.split("\n"): + if not dev_config: + continue + network_dict = {} + redistribute_dict = {} + dev_config = dev_config.strip() + dev_config = re.sub(r"-", "_", dev_config).strip() + matches = re.findall(r"router (ospf) (.*)", dev_config) + if matches: + if ospf_params_dict: + instance_list.append(ospf_params_dict) + ospf_params_dict = {} + instance = matches[0][1].split() + ospf_params_dict.update({"process_id": str(instance[0])}) + if "vrf" in dev_config: + vrf_name = instance[-1] + else: + vrf_name = None + ospf_params_dict.update({"vrf": vrf_name}) + if "traffic_engineering" in dev_config: + ospf_params_dict.update({"traffic_engineering": True}) + config_params = dev_config.split() + if config_params[0] == "adjacency": + threshold = config_params[-1] + adjacency_dict = {"exchange_start": {"threshold": threshold}} + ospf_params_dict.update({"adjacency": adjacency_dict}) + elif "auto_cost" in dev_config: + bw = config_params[-1] + ospf_params_dict.update( + {"auto_cost": {"reference_bandwidth": bw}}, + ) + elif "bfd" in dev_config: + ospf_params_dict.update({"bfd": {"all_interfaces": True}}) + elif config_params[0] == "default_information": + def_dict = {"originate": True} + for i, val in enumerate(config_params[2::]): + if val == "always": + def_dict.update({"always": True}) + elif val in ["route_map", "metric", "metric_type"]: + def_dict.update({val: config_params[i + 3]}) + ospf_params_dict.update({"default_information": def_dict}) + elif "default_metric" in dev_config: + ospf_params_dict.update({"default_metric": config_params[-1]}) + elif "distance" in dev_config: + distance_dict.update({config_params[-2]: config_params[-1]}) + ospf_params_dict.update({"distance": distance_dict}) + elif "distribute_list" in dev_config: + ospf_params_dict.update( + {"distribute_list": {config_params[1]: config_params[2]}}, + ) + elif "dn_bit_ignore" in dev_config: + ospf_params_dict.update({"dn_bit_ignore": True}) + elif "fips_restrictions" in dev_config: + ospf_params_dict.update({"fips_restrictions": True}) + elif "graceful_restart" in dev_config: + if "grace_period" in dev_config: + ospf_params_dict.update( + { + "graceful_restart": { + "grace_period": config_params[-1], + }, + }, + ) + else: + ospf_params_dict.update( + {"graceful_restart": {"set": True}}, + ) + elif "graceful_restart_helper" in dev_config: + ospf_params_dict.update({"graceful_restart_helper": True}) + elif "log_adjacency_changes" in dev_config: + detail = True if "detail" in dev_config else False + ospf_params_dict.update( + {"log_adjacency_changes": {"detail": detail}}, + ) + elif "max_lsa" in dev_config: + max_lsa_dict = {} + config_params.pop(0) + max_lsa_dict.update({"count": config_params.pop(0)}) + if config_params: + if config_params[0].isdigit(): + max_lsa_dict.update( + {"threshold": config_params.pop(0)}, + ) + for i, el in enumerate(config_params): + if el == "warning_only": + max_lsa_dict.update({"warning": True}) + if el in ["ignore_count", "ignore_time", "reset_time"]: + max_lsa_dict.update({el: config_params[i + 1]}) + ospf_params_dict.update({"max_lsa": max_lsa_dict}) + elif "maximum_paths" in dev_config: + ospf_params_dict.update({"maximum_paths": config_params[1]}) + elif "mpls ldp sync default" in dev_config: + ospf_params_dict.update({"mpls_ldp": True}) + elif config_params[0] == "network": + config_params.pop(0) + prefix = re.search(r"\/", config_params[0]) + if prefix: + network_dict.update({"prefix": config_params.pop(0)}) + else: + network_dict.update( + {"network_address": config_params.pop(0)}, + ) + network_dict.update({"mask": config_params.pop(0)}) + network_dict.update({"area": config_params[-1]}) + network_list.append(network_dict) + ospf_params_dict.update({"networks": network_list}) + elif "passive_interface" in dev_config: + if config_params[1] == "default": + ospf_params_dict.update( + {"passive_interface": {"default": True}}, + ) + else: + ospf_params_dict.update( + { + "passive_interface": { + "interface_list": config_params[1], + }, + }, + ) + elif "point_to_point" in dev_config: + ospf_params_dict.update({"point_to_point": True}) + elif "redistribute" in dev_config: + redistribute_dict.update({"routes": config_params[1]}) + if config_params[1] == "isis": + if "level" in config_params[2]: + k = re.sub(r"_", "-", config_params[2]) + redistribute_dict.update({"isis_level": k}) + if "route_map" in dev_config: + redistribute_dict.update({"route_map": config_params[-1]}) + redistribute_list.append(redistribute_dict) + ospf_params_dict.update({"redistribute": redistribute_list}) + elif "router_id" in dev_config: + ospf_params_dict.update({"router_id": config_params[-1]}) + elif "retransmission_threshold" in dev_config: + ospf_params_dict.update( + {"retransmission_threshold": config_params[-1]}, + ) + elif config_params[0] == "compatible": + ospf_params_dict.update({"rfc1583compatibility": True}) + elif "shutdown" in dev_config: + ospf_params_dict.update({"shutdown": True}) + elif "summary_address" in dev_config: + summary_address_dict = {} + config_params.pop(0) + prefix = re.search(r"\/", config_params[0]) + if prefix: + summary_address_dict.update( + {"prefix": config_params.pop(0)}, + ) + else: + summary_address_dict.update( + {"address": config_params.pop(0)}, + ) + summary_address_dict.update({"mask": config_params.pop(0)}) + if "not_advertise" in dev_config: + summary_address_dict.update({"not_advertise": True}) + config_params.pop(0) + else: + if config_params: + summary_address_dict.update( + {config_params[0]: config_params[1]}, + ) + ospf_params_dict.update( + {"summary_address": summary_address_dict}, + ) + elif "timers" in dev_config: + timers_dict = {} + if config_params[1] == "lsa": + if config_params[2] in ["rx", "arrival"]: + timers_dict.update( + { + "lsa": { + "rx": {"min_interval": config_params[-1]}, + }, + }, + ) + else: + timers_dict.update( + { + "lsa": { + "tx": { + "delay": { + "initial": config_params[-3], + "min": config_params[-2], + "max": config_params[-1], + }, + }, + }, + }, + ) + elif config_params[1] == "out_delay": + timers_dict.update({"out_delay": config_params[-1]}) + elif config_params[1] == "pacing": + timers_dict.update({"pacing": config_params[-1]}) + elif config_params[1] == "spf": + if config_params[2] == "delay": + timers_dict.update( + { + "spf": { + "tx": { + "delay": { + "initial": config_params[-3], + "min": config_params[-2], + "max": config_params[-1], + }, + }, + }, + }, + ) + else: + timers_dict.update( + {"spf": {"seconds": config_params[-1]}}, + ) + elif config_params[1] == "throttle": + timers_dict.update( + { + "throttle": { + "attr": config_params[2], + "initial": config_params[-3], + "min": config_params[-2], + "max": config_params[-1], + }, + }, + ) + timers_list.append(timers_dict) + ospf_params_dict.update({"timers": timers_list}) + elif config_params[0] == "area": + areas_dict = {} + areas_dict.update({"area_id": config_params[1]}) + if config_params[2] == "default_cost": + areas_dict.update({"default_cost": config_params[-1]}) + elif config_params[2] == "filter": + prefix = re.search(r"\/", config_params[3]) + if prefix: + areas_dict.update( + {"filter": {"address": config_params[3]}}, + ) + elif config_params[3] == "prefix_list": + areas_dict.update( + {"filter": {"prefix_list": config_params[-1]}}, + ) + else: + areas_dict.update( + {"filter": {"subnet_address": config_params[3]}}, + ) + areas_dict.update( + {"filter": {"subnet_mask": config_params[4]}}, + ) + elif config_params[2] == "not_so_stubby": + if len(config_params) == 3: + areas_dict.update({"not_so_stubby": {"set": True}}) + continue + if config_params[3] == "lsa": + areas_dict.update({"not_so_stubby": {"lsa": True}}) + elif config_params[3] == "default_information_originate": + default_dict = {} + for i, val in enumerate(config_params): + if val == "nssa_only": + default_dict.update({"nssa_only": True}) + if val == "metric_type": + default_dict.update( + {"metric_type": config_params[i + 1]}, + ) + if val == "metric": + default_dict.update( + {"metric": config_params[i + 1]}, + ) + areas_dict.update( + { + "not_so_stubby": { + "default_information_originate": default_dict, + }, + }, + ) + elif config_params[3] == "no_summary": + areas_dict.update( + {"not_so_stubby": {"no_summary": True}}, + ) + elif config_params[3] == "nssa_only": + areas_dict.update( + {"not_so_stubby": {"nssa_only": True}}, + ) + elif config_params[2] == "nssa": + if len(config_params) == 3: + areas_dict.update({"nssa": {"set": True}}) + continue + if config_params[3] == "default_information_originate": + default_dict = {} + for i, val in enumerate(config_params): + if val == "nssa_only": + default_dict.update({"nssa_only": True}) + if val == "metric_type": + default_dict.update( + {"metric_type": config_params[i + 1]}, + ) + if val == "metric": + default_dict.update( + {"metric": config_params[i + 1]}, + ) + areas_dict.update( + { + "nssa": { + "default_information_originate": default_dict, + }, + }, + ) + elif config_params[3] == "no_summary": + areas_dict.update({"nssa": {"no_summary": True}}) + elif config_params[3] == "nssa_only": + areas_dict.update({"nssa": {"nssa_only": True}}) + elif config_params[2] == "range": + prefix = re.search(r"\/", config_params[3]) + range_dict = {} + if prefix: + range_dict.update({"address": config_params[3]}) + else: + range_dict.update({"subnet_address": config_params[3]}) + range_dict.update({"subnet_mask": config_params[4]}) + if "advertise" in dev_config: + range_dict.update({"advertise": True}) + if "not_advertise" in dev_config: + range_dict.update({"advertise": False}) + if "cost" in dev_config: + range_dict.update({"cost": config_params[-1]}) + areas_dict.update({"range": range_dict}) + elif config_params[2] == "stub": + if "no_summary" in dev_config: + areas_dict.update({"stub": {"no_summary": True}}) + else: + areas_dict.update({"stub": {"set": True}}) + areas_list.append(areas_dict) + ospf_params_dict.update({"areas": areas_list}) + elif config_params[0] == "max_metric": + config_params.pop(0) + router_lsa_dict = {} + config_params.pop(0) + if not config_params: + ospf_params_dict.update( + {"max_metric": {"router_lsa": {"set": True}}}, + ) + else: + for i, val in enumerate(config_params): + if val == "include_stub": + router_lsa_dict.update({"include_stub": True}) + elif val == "on_startup": + if config_params[i + 1] == "wait_for_bgp": + router_lsa_dict.update( + {"on_startup": {"wait_for_bgp": True}}, + ) + else: + router_lsa_dict.update( + { + "on_startup": { + "time": config_params[i + 1], + }, + }, + ) + elif val == "external_lsa": + if ( + i < len(config_params) + and config_params[i + 1].isdigit() + ): + router_lsa_dict.update( + { + "external_lsa": { + "max_metric_value": config_params[ + i + 1 + ], + }, + }, + ) + else: + router_lsa_dict.update( + {"external_lsa": {"set": True}}, + ) + elif val == "summary_lsa": + if ( + i < len(config_params) - 1 + and config_params[i + 1].isdigit() + ): + router_lsa_dict.update( + { + "summary_lsa": { + "max_metric_value": config_params[ + i + 1 + ], + }, + }, + ) + else: + router_lsa_dict.update( + {"summary_lsa": {"set": True}}, + ) + ospf_params_dict.update( + {"max_metric": {"router_lsa": router_lsa_dict}}, + ) + # instance_list.append(ospf_params_dict) + # config.update({"ospf_version": "v2", "ospf_processes": instance_list}) + return utils.remove_empties(ospf_params_dict) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv3/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv3/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv3/ospfv3.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv3/ospfv3.py new file mode 100644 index 000000000..3efa27cdf --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/ospfv3/ospfv3.py @@ -0,0 +1,121 @@ +# -*- 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 eos 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. +""" + +import re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.ospfv3.ospfv3 import ( + Ospfv3Args, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.ospfv3 import ( + Ospfv3Template, +) + + +class Ospfv3Facts(object): + """The eos ospfv3 facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Ospfv3Args.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 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 + """ + facts = {} + objs = [] + ospfv3_parser = Ospfv3Template(lines=[], module=self._module) + + if not data: + data = self.get_config(connection) + + # split the config into instances of the resource + resource_delim = "router ospfv3" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [ + p.strip() for p in re.findall(find_pattern, data, re.DOTALL) + ] + + # parse native config using the Ospfv3 template + ospfv3_facts = {"processes": []} + for resource in resources: + ospfv3_parser = Ospfv3Template( + lines=resource.splitlines(), + module=self._module, + ) + objs = ospfv3_parser.parse() + for key, sortv in [("address_family", "afi")]: + if key in objs["processes"] and objs["processes"][key]: + objs["processes"][key] = list( + objs["processes"][key].values(), + ) + + for addr_family in objs["processes"]["address_family"]: + if "areas" in addr_family: + addr_family["areas"] = list(addr_family["areas"].values()) + + for addr_family in objs["processes"]["address_family"]: + if not addr_family.get("afi"): + # global vars + objs["processes"].update(addr_family) + objs["processes"]["address_family"].remove(addr_family) + + ospfv3_facts["processes"].append(objs["processes"]) + + ansible_facts["ansible_network_resources"].pop("ospfv3", None) + params = ospfv3_parser.validate_config( + self.argument_spec, + {"config": ospfv3_facts}, + redact=True, + ) + 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/arista/eos/plugins/module_utils/network/eos/facts/prefix_lists/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/prefix_lists/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/prefix_lists/prefix_lists.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/prefix_lists/prefix_lists.py new file mode 100644 index 000000000..aa1f81b84 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/prefix_lists/prefix_lists.py @@ -0,0 +1,95 @@ +# -*- 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 eos 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.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.prefix_lists.prefix_lists import ( + Prefix_listsArgs, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.prefix_lists import ( + Prefix_listsTemplate, +) + + +class Prefix_listsFacts(object): + """The eos prefix_lists facts class""" + + def __init__(self, module, subspec="config", options="options"): + 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 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 = {} + + 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 = prefix_lists_parser.parse() + if objs: + for afi, pl in iteritems(objs): + if "prefix_lists" in pl: + pl["prefix_lists"] = sorted( + list(pl["prefix_lists"].values()), + key=lambda k, sk="name": k[sk], + ) + for plist in pl["prefix_lists"]: + for en in plist: + if "entries" in en: + plist["entries"] = sorted( + list(plist["entries"].values()), + key=lambda k, sk="sequence": k[sk], + ) + objs = sorted(list(objs.values()), key=lambda k, sk="afi": k[sk]) + else: + objs = [] + 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/arista/eos/plugins/module_utils/network/eos/facts/route_maps/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/route_maps/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/route_maps/route_maps.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/route_maps/route_maps.py new file mode 100644 index 000000000..e5ca11162 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/route_maps/route_maps.py @@ -0,0 +1,143 @@ +# -*- 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 eos 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. +""" + +import re + +from copy import deepcopy + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.route_maps.route_maps import ( + Route_mapsArgs, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.route_maps import ( + Route_mapsTemplate, +) + + +class Route_mapsFacts(object): + """The eos route_maps facts class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Route_mapsArgs.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 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) + + resource_delim = "route-map" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [ + p.strip() for p in re.findall(find_pattern, data, re.DOTALL) + ] + # parse native config using the Ospf_interfaces template + route_maps_facts = [] + # parse native config using the Route_maps template + for resource in resources: + route_maps_parser = Route_mapsTemplate(lines=resource.splitlines()) + objs = route_maps_parser.parse() + if objs: + dict_update = {} + for k, v in iteritems(objs): + if k == "entries": + e_list = [] + match_dict = {} + match_ip = {} + match_ipv6 = {} + set_dict = {} + for el in v: + for entry_k, entry_v in iteritems(el): + if entry_k == "match": + if "ip" in entry_v or "ipv6" in entry_v: + for ipk, ipv in iteritems(entry_v): + if "ip" in entry_v: + match_ip.update(ipv) + if "ipv6" in entry_v: + match_ipv6.update(ipv) + matchv = { + "ip": match_ip, + "ipv6": match_ipv6, + } + else: + matchv = entry_v + match_dict.update(matchv) + elif entry_k == "set": + set_dict.update(entry_v) + else: + dict_update.update(el) + dict_update.update( + {"match": match_dict, "set": set_dict}, + ) + e_list.append(dict_update) + objs.update({"entries": e_list}) + route_maps_facts.append(objs) + maps = [] + r_facts = [] + for r_map in route_maps_facts: + if r_map["route_map"] in maps: + for r_f in r_facts: + if r_f["route_map"] == r_map["route_map"]: + r_f["entries"].extend(r_map["entries"]) + else: + maps.append(r_map["route_map"]) + r_facts.append(r_map) + ansible_facts["ansible_network_resources"].pop("route_maps", None) + facts = {"route_maps": []} + params = utils.remove_empties( + utils.validate_config(self.argument_spec, {"config": r_facts}), + ) + if params.get("config"): + for cfg in params["config"]: + facts["route_maps"].append(utils.remove_empties(cfg)) + ansible_facts["ansible_network_resources"].update(facts) + + return ansible_facts diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/snmp_server/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/snmp_server/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/snmp_server/snmp_server.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/snmp_server/snmp_server.py new file mode 100644 index 000000000..3746be94c --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/snmp_server/snmp_server.py @@ -0,0 +1,117 @@ +# -*- 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 eos 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_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.snmp_server.snmp_server import ( + Snmp_serverArgs, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.rm_templates.snmp_server import ( + Snmp_serverTemplate, +) + + +class Snmp_serverFacts(object): + """The eos snmp_server facts class""" + + def __init__(self, module, subspec="config", options="options"): + 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 objs: + if "communities" in objs: + objs["communities"] = sorted( + list(objs["communities"].values()), + key=lambda k, sk="name": k[sk], + ) + if "groups" in objs: + objs["groups"] = sorted( + list(objs["groups"].values()), + key=lambda k, sk="group": k[sk], + ) + if "hosts" in objs: + objs["hosts"] = sorted( + list(objs["hosts"].values()), + key=lambda k, sk="host": k[sk], + ) + if "acls" in objs: + objs["acls"] = sorted( + list(objs["acls"].values()), + key=lambda k, sk="afi": k[sk], + ) + if "users" in objs: + objs["users"] = sorted( + list(objs["users"].values()), + key=lambda k, sk="user": k[sk], + ) + if "views" in objs: + objs["views"] = sorted( + list(objs["views"].values()), + key=lambda k, sk="view": k[sk], + ) + if "vrfs" in objs: + objs["vrfs"] = sorted( + list(objs["vrfs"].values()), + key=lambda k, sk="vrf": k[sk], + ) + else: + objs = {} + + 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/arista/eos/plugins/module_utils/network/eos/facts/static_routes/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/static_routes/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/static_routes/static_routes.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/static_routes/static_routes.py new file mode 100644 index 000000000..85b0e7475 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/static_routes/static_routes.py @@ -0,0 +1,243 @@ +# +# -*- 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 eos 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.arista.eos.plugins.module_utils.network.eos.argspec.static_routes.static_routes import ( + Static_routesArgs, +) + + +class Static_routesFacts(object): + """The eos static_routes fact class""" + + def __init__(self, module, subspec="config", options="options"): + self._module = module + self.argument_spec = Static_routesArgs.argument_spec + spec = deepcopy(self.argument_spec) + if subspec: + if options: + facts_argument_spec = spec[subspec][options] + else: + facts_argument_spec = spec[subspec] + else: + facts_argument_spec = spec + + self.generated_spec = utils.generate_dict(facts_argument_spec) + + def get_device_data(self, connection): + return connection.get("show running-config | grep route") + + def populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for static_routes + :param connection: the device connection + :param ansible_facts: Facts dictionary + :param data: previously collected conf + :rtype: dictionary + :returns: facts + """ + if not data: + data = self.get_device_data(connection) + + # split the config into instances of the resource + resource_delim = "ip.* route" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [p.strip() for p in re.findall(find_pattern, data)] + resources_without_vrf = [] + resource_vrf = {} + for resource in resources: + if resource and "vrf" not in resource: + resources_without_vrf.append(resource) + else: + vrf = re.search(r"ip(v6)* route vrf (.*?) .*", resource) + if vrf.group(2) in resource_vrf.keys(): + vrf_val = resource_vrf[vrf.group(2)] + vrf_val.append(resource) + resource_vrf.update({vrf.group(2): vrf_val}) + else: + resource_vrf.update({vrf.group(2): [resource]}) + resources_without_vrf = ["\n".join(resources_without_vrf)] + for vrf in resource_vrf.keys(): + vrflist = ["\n".join(resource_vrf[vrf])] + resource_vrf.update({vrf: vrflist}) + objs = [] + for resource in resources_without_vrf: + if resource: + obj = self.render_config(self.generated_spec, resource) + if obj: + objs.append(obj) + for resource in resource_vrf.keys(): + if resource: + obj = self.render_config( + self.generated_spec, + resource_vrf[resource][0], + ) + if obj: + objs.append(obj) + ansible_facts["ansible_network_resources"].pop("static_routes", None) + facts = {} + if objs: + facts["static_routes"] = [] + params = utils.validate_config( + self.argument_spec, + {"config": objs}, + ) + for cfg in params["config"]: + facts["static_routes"].append(utils.remove_empties(cfg)) + ansible_facts["ansible_network_resources"].update(facts) + return ansible_facts + + def render_config(self, 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) + address_family_dict = {} + route_dict = {} + dest_list = [] + afi_list = [] + vrf_list = [] + routes = [] + config["address_families"] = [] + next_hops = {} + interface_list = [ + "Ethernet", + "Loopback", + "Management", + "Port-Channel", + "Tunnel", + "Vlan", + "Vxlan", + "vtep", + ] + conf_list = conf.split("\n") + for conf_elem in conf_list: + matches = re.findall( + r"(ip|ipv6) route ([\d\.\/:]+|vrf) (.+)$", + conf_elem, + ) + if matches: + remainder = matches[0][2].split() + route_update = False + if matches[0][1] == "vrf": + vrf = remainder.pop(0) + # new vrf + if vrf not in vrf_list and vrf_list: + route_dict.update({"next_hops": next_hops}) + routes.append(route_dict) + address_family_dict.update({"routes": routes}) + config["address_families"].append(address_family_dict) + route_update = True + config.update({"vrf": vrf}) + vrf_list.append(vrf) + dest = remainder.pop(0) + else: + config["vrf"] = None + dest = matches[0][1] + afi = "ipv4" if matches[0][0] == "ip" else "ipv6" + if afi not in afi_list: + if afi_list and not route_update: + # new afi and not the first updating all prev configs + route_dict.update({"next_hops": next_hops}) + routes.append(route_dict) + address_family_dict.update({"routes": routes}) + config["address_families"].append(address_family_dict) + route_update = True + address_family_dict = {} + address_family_dict.update({"afi": afi}) + routes = [] + afi_list.append(afi) + # To check the format of the dest + prefix = re.search(r"/", dest) + if not prefix: + dest = dest + " " + remainder.pop(0) + if dest not in dest_list: + # For new dest and not the first dest + if dest_list and not route_update: + route_dict.update({"next_hops": next_hops}) + routes.append(route_dict) + dest_list.append(dest) + next_hops = [] + route_dict = {} + route_dict.update({"dest": dest}) + nexthops = {} + nxthop_addr = re.search(r"[\.\:]", remainder[0]) + if nxthop_addr: + nexthops.update({"interface": remainder.pop(0)}) + if remainder and remainder[0] == "label": + nexthops.update({"mpls_label": remainder.pop(1)}) + remainder.pop(0) + elif re.search(r"Nexthop-Group", remainder[0], re.IGNORECASE): + nexthops.update({"nexthop_grp": remainder.pop(1)}) + remainder.pop(0) + else: + interface = remainder.pop(0) + if interface in interface_list: + interface = interface + " " + remainder.pop(0) + nexthops.update({"interface": interface}) + for attribute in remainder: + forward_addr = re.search( + r"([\dA-Fa-f]+[:\.]+)+[\dA-Fa-f]+", + attribute, + ) + if forward_addr: + nexthops.update( + { + "forward_router_address": remainder.pop( + remainder.index(attribute), + ), + }, + ) + for attribute in remainder: + for params in ["tag", "name", "track"]: + if attribute == params: + keyname = params + if attribute == "name": + keyname = "description" + nexthops.update( + { + keyname: remainder.pop( + remainder.index(attribute) + 1, + ), + }, + ) + remainder.pop(remainder.index(attribute)) + if remainder: + metric = re.search(r"\d+", remainder[0]) + if metric: + nexthops.update({"admin_distance": remainder.pop(0)}) + next_hops.append(nexthops) + route_dict.update({"next_hops": next_hops}) + routes.append(route_dict) + address_family_dict.update({"routes": routes}) + config["address_families"].append(address_family_dict) + return utils.remove_empties(config) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/vlans/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/vlans/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/vlans/vlans.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/vlans/vlans.py new file mode 100644 index 000000000..876891253 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/facts/vlans/vlans.py @@ -0,0 +1,125 @@ +# -*- 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 eos 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 re + +from copy import deepcopy + +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import ( + utils, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.argspec.vlans.vlans import ( + VlansArgs, +) + + +class VlansFacts(object): + """The eos 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 populate_facts(self, connection, ansible_facts, data=None): + """Populate the facts for vlans + :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 | section ^vlan") + + # split the config into instances of the resource + resource_delim = "vlan" + find_pattern = r"(?:^|\n)%s.*?(?=(?:^|\n)%s|$)" % ( + resource_delim, + resource_delim, + ) + resources = [ + p.strip() for p in re.findall(find_pattern, data, re.DOTALL) + ] + + objs = [] + for resource in resources: + if resource: + obj = self.render_config(self.generated_spec, resource) + if obj: + objs.extend(obj) + + ansible_facts["ansible_network_resources"].pop("vlans", None) + facts = {} + if objs: + params = utils.validate_config( + self.argument_spec, + {"config": objs}, + ) + facts["vlans"] = [ + utils.remove_empties(cfg) for cfg in 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) + vlans = [] + + parse_result = utils.parse_conf_arg(conf, "vlan") + if not parse_result.split(" ")[0].isalpha(): + vlan_list = vlan_to_list(parse_result) + for vlan in vlan_list: + config["vlan_id"] = vlan + config["name"] = utils.parse_conf_arg(conf, "name") + config["state"] = utils.parse_conf_arg(conf, "state") + if config["state"] is None: + config["state"] = "active" + vlans.append(utils.remove_empties(config)) + + return vlans + + +def vlan_to_list(vlan_str): + vlans = [] + for vlan in vlan_str.split(","): + if "-" in vlan: + start, stop = vlan.split("-") + vlans.extend(range(int(start), int(stop) + 1)) + else: + vlans.append(int(vlan)) + + return vlans diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/address_family.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/address_family.py new file mode 100644 index 000000000..dcbf45a4a --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/address_family.py @@ -0,0 +1,149 @@ +# +# (c) 2019, Ansible by 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 +import re + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.providers.cli.config.bgp.neighbors import ( + AFNeighbors, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.providers.providers import ( + CliProvider, +) + + +class AddressFamily(CliProvider): + def render(self, config=None): + commands = list() + safe_list = list() + + router_context = "router bgp %s" % self.get_value("config.bgp_as") + context_config = None + + for item in self.get_value("config.address_family"): + context = "address-family %s" % item["afi"] + context_commands = list() + + if config: + context_path = [router_context, context] + context_config = self.get_config_context( + config, + context_path, + indent=2, + ) + + for key, value in iteritems(item): + if value is not None: + meth = getattr(self, "_render_%s" % key, None) + if meth: + resp = meth(item, context_config) + if resp: + context_commands.extend(to_list(resp)) + + if context_commands: + commands.append(context) + commands.extend(context_commands) + commands.append("exit") + + safe_list.append(context) + + if self.params["operation"] == "replace": + if config: + resp = self._negate_config(config, safe_list) + commands.extend(resp) + + return commands + + def _negate_config(self, config, safe_list=None): + commands = list() + matches = re.findall(r"(address-family .+)$", config, re.M) + for item in set(matches).difference(safe_list): + commands.append("no %s" % item) + return commands + + def _render_auto_summary(self, item, config=None): + cmd = "auto-summary" + if item["auto_summary"] is False: + cmd = "no %s" % cmd + if not config or cmd not in config: + return cmd + + def _render_synchronization(self, item, config=None): + cmd = "synchronization" + if item["synchronization"] is False: + cmd = "no %s" % cmd + if not config or cmd not in config: + return cmd + + def _render_networks(self, item, config=None): + commands = list() + safe_list = list() + + for entry in item["networks"]: + network = entry["prefix"] + if entry["masklen"]: + network = "%s/%s" % (entry["prefix"], entry["masklen"]) + safe_list.append(network) + + cmd = "network %s" % network + + if entry["route_map"]: + cmd += " route-map %s" % entry["route_map"] + + if not config or cmd not in config: + commands.append(cmd) + + if self.params["operation"] == "replace": + if config: + matches = re.findall(r"network (\S+)", config, re.M) + for entry in set(matches).difference(safe_list): + commands.append("no network %s" % entry) + + return commands + + def _render_redistribute(self, item, config=None): + commands = list() + safe_list = list() + + for entry in item["redistribute"]: + option = entry["protocol"] + + cmd = "redistribute %s" % entry["protocol"] + + if entry["route_map"]: + cmd += " route-map %s" % entry["route_map"] + + if not config or cmd not in config: + commands.append(cmd) + + safe_list.append(option) + + if self.params["operation"] == "replace": + if config: + matches = re.findall( + r"redistribute (\S+)(?:\s*)(\d*)", + config, + re.M, + ) + for i in range(0, len(matches)): + matches[i] = " ".join(matches[i]).strip() + for entry in set(matches).difference(safe_list): + commands.append("no redistribute %s" % entry) + + return commands + + def _render_neighbors(self, item, config): + """generate bgp neighbor configuration""" + return AFNeighbors(self.params).render( + config, + nbr_list=item["neighbors"], + ) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/neighbors.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/neighbors.py new file mode 100644 index 000000000..760827a93 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/neighbors.py @@ -0,0 +1,194 @@ +# +# (c) 2019, Ansible by 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 +import re + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.providers.providers import ( + CliProvider, +) + + +class Neighbors(CliProvider): + def render(self, config=None, nbr_list=None): + commands = list() + safe_list = list() + if not nbr_list: + nbr_list = self.get_value("config.neighbors") + + for item in nbr_list: + neighbor_commands = list() + context = "neighbor %s" % item["neighbor"] + cmd = "%s remote-as %s" % (context, item["remote_as"]) + + if not config or cmd not in config: + neighbor_commands.append(cmd) + + for key, value in iteritems(item): + if value is not None: + meth = getattr(self, "_render_%s" % key, None) + if meth: + resp = meth(item, config) + if resp: + neighbor_commands.extend(to_list(resp)) + commands.extend(neighbor_commands) + safe_list.append(context) + + if self.params["operation"] == "replace": + if config and safe_list: + commands.extend(self._negate_config(config, safe_list)) + + return commands + + def _negate_config(self, config, safe_list=None): + commands = list() + matches = re.findall(r"(neighbor \S+)", config, re.M) + for item in set(matches).difference(safe_list): + commands.append("no %s" % item) + return commands + + def _render_description(self, item, config=None): + cmd = "neighbor %s description %s" % ( + item["neighbor"], + item["description"], + ) + if not config or cmd not in config: + return cmd + + def _render_enabled(self, item, config=None): + cmd = "neighbor %s shutdown" % item["neighbor"] + if item["enabled"] is True: + if not config or cmd in config: + cmd = "no %s" % cmd + return cmd + elif not config or cmd not in config: + return cmd + + def _render_update_source(self, item, config=None): + cmd = "neighbor %s update-source %s" % ( + item["neighbor"], + item["update_source"], + ) + if not config or cmd not in config: + return cmd + + def _render_password(self, item, config=None): + cmd = "neighbor %s password %s" % (item["neighbor"], item["password"]) + if not config or cmd not in config: + return cmd + + def _render_ebgp_multihop(self, item, config=None): + cmd = "neighbor %s ebgp-multihop %s" % ( + item["neighbor"], + item["ebgp_multihop"], + ) + if not config or cmd not in config: + return cmd + + def _render_peer_group(self, item, config=None): + cmd = "neighbor %s peer group %s" % ( + item["neighbor"], + item["peer_group"], + ) + if not config or cmd not in config: + return cmd + + def _render_route_reflector_client(self, item, config=None): + cmd = "neighbor %s route-reflector-client" % item["neighbor"] + if item["route_reflector_client"] is False: + if not config or cmd in config: + cmd = "no %s" % cmd + return cmd + elif not config or cmd not in config: + return cmd + + def _render_maximum_prefix(self, item, config=None): + cmd = "neighbor %s maximum-routes %s" % ( + item["neighbor"], + item["maximum_prefix"], + ) + if not config or cmd not in config: + return cmd + + def _render_remove_private_as(self, item, config=None): + cmd = "neighbor %s remove-private-AS" % item["neighbor"] + if item["remove_private_as"] is False: + if not config or cmd in config: + cmd = "no %s" % cmd + return cmd + elif not config or cmd not in config: + return cmd + + def _render_timers(self, item, config): + """generate bgp timer related configuration""" + keepalive = item["timers"]["keepalive"] + holdtime = item["timers"]["holdtime"] + neighbor = item["neighbor"] + + if keepalive and holdtime: + cmd = "neighbor %s timers %s %s" % (neighbor, keepalive, holdtime) + if not config or cmd not in config: + return cmd + + +class AFNeighbors(CliProvider): + def render(self, config=None, nbr_list=None): + commands = list() + if not nbr_list: + return + + for item in nbr_list: + neighbor_commands = list() + for key, value in iteritems(item): + if value is not None: + meth = getattr(self, "_render_%s" % key, None) + if meth: + resp = meth(item, config) + if resp: + neighbor_commands.extend(to_list(resp)) + + commands.extend(neighbor_commands) + + return commands + + def _render_activate(self, item, config=None): + cmd = "neighbor %s activate" % item["neighbor"] + if item["activate"] is False: + if not config or cmd in config: + cmd = "no %s" % cmd + return cmd + elif not config or cmd not in config: + return cmd + + def _render_default_originate(self, item, config=None): + cmd = "neighbor %s default-originate" % item["neighbor"] + if item["default_originate"] is False: + if not config or cmd in config: + cmd = "no %s" % cmd + return cmd + elif not config or cmd not in config: + return cmd + + def _render_graceful_restart(self, item, config=None): + cmd = "neighbor %s graceful-restart" % item["neighbor"] + if item["graceful_restart"] is False: + cmd = "no " + cmd + if config: + config_el = [x.strip() for x in config.split("\n")] + if cmd in config_el: + return + return cmd + + def _render_weight(self, item, config=None): + cmd = "neighbor %s weight %s" % (item["neighbor"], item["weight"]) + if not config or cmd not in config: + return cmd diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/process.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/process.py new file mode 100644 index 000000000..79cecb29c --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/cli/config/bgp/process.py @@ -0,0 +1,186 @@ +# +# (c) 2019, Ansible by 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 +import re + +from ansible.module_utils.six import iteritems +from ansible_collections.ansible.netcommon.plugins.module_utils.network.common.utils import ( + to_list, +) + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.providers.cli.config.bgp.address_family import ( + AddressFamily, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.providers.cli.config.bgp.neighbors import ( + Neighbors, +) +from ansible_collections.arista.eos.plugins.module_utils.network.eos.providers.providers import ( + CliProvider, + register_provider, +) + + +REDISTRIBUTE_PROTOCOLS = frozenset( + ["ospf", "ospfv3", "rip", "isis", "static", "connected"], +) + + +@register_provider("eos", "eos_bgp") +class Provider(CliProvider): + def render(self, config=None): + commands = list() + + existing_as = None + if config: + match = re.search(r"router bgp (\d+)", config, re.M) + if match: + existing_as = match.group(1) + + operation = self.params["operation"] + + context = None + if self.params["config"]: + context = "router bgp %s" % self.get_value("config.bgp_as") + + if operation == "delete": + if existing_as: + commands.append("no router bgp %s" % existing_as) + elif context: + commands.append("no %s" % context) + + else: + self._validate_input(config) + if operation == "replace": + if existing_as and int(existing_as) != self.get_value( + "config.bgp_as", + ): + commands.append("no router bgp %s" % existing_as) + config = None + + elif operation == "override": + if existing_as: + commands.append("no router bgp %s" % existing_as) + config = None + + context_commands = list() + + for key, value in iteritems(self.get_value("config")): + if value is not None: + meth = getattr(self, "_render_%s" % key, None) + if meth: + resp = meth(config) + if resp: + context_commands.extend(to_list(resp)) + + if context and context_commands: + commands.append(context) + commands.extend(context_commands) + commands.append("exit") + return commands + + def _render_router_id(self, config=None): + cmd = "router-id %s" % self.get_value("config.router_id") + if not config or cmd not in config: + return cmd + + def _render_log_neighbor_changes(self, config=None): + cmd = "bgp log-neighbor-changes" + log_neighbor_changes = self.get_value("config.log_neighbor_changes") + if log_neighbor_changes is True: + if not config or cmd not in config: + return cmd + elif log_neighbor_changes is False: + if config and cmd in config: + return "no %s" % cmd + + def _render_networks(self, config=None): + commands = list() + safe_list = list() + + for entry in self.get_value("config.networks"): + network = entry["prefix"] + if entry["masklen"]: + network = "%s/%s" % (entry["prefix"], entry["masklen"]) + safe_list.append(network) + + cmd = "network %s" % network + + if entry["route_map"]: + cmd += " route-map %s" % entry["route_map"] + + if not config or cmd not in config: + commands.append(cmd) + + if self.params["operation"] == "replace": + if config: + matches = re.findall(r"network (\S+)", config, re.M) + for entry in set(matches).difference(safe_list): + commands.append("no network %s" % entry) + + return commands + + def _render_redistribute(self, config=None): + commands = list() + safe_list = list() + + for entry in self.get_value("config.redistribute"): + option = entry["protocol"] + + cmd = "redistribute %s" % entry["protocol"] + + if entry["route_map"]: + cmd += " route-map %s" % entry["route_map"] + + if not config or cmd not in config: + commands.append(cmd) + + safe_list.append(option) + + if self.params["operation"] == "replace": + if config: + matches = re.findall( + r"redistribute (\S+)(?:\s*)(\d*)", + config, + re.M, + ) + for i in range(0, len(matches)): + matches[i] = " ".join(matches[i]).strip() + for entry in set(matches).difference(safe_list): + commands.append("no redistribute %s" % entry) + + return commands + + def _render_neighbors(self, config): + """generate bgp neighbor configuration""" + return Neighbors(self.params).render(config) + + def _render_address_family(self, config): + """generate address-family configuration""" + return AddressFamily(self.params).render(config) + + def _validate_input(self, config): + def device_has_AF(config): + return re.search(r"address-family (?:.*)", config) + + address_family = self.get_value("config.address_family") + root_networks = self.get_value("config.networks") + operation = self.params["operation"] + + if operation == "replace" and root_networks: + if address_family: + for item in address_family: + if item["networks"]: + raise ValueError( + "operation is replace but provided both root level networks and networks under %s address family" + % item["afi"], + ) + + if config and device_has_AF(config): + raise ValueError( + "operation is replace and device has one or more address family activated but root level network(s) provided", + ) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/module.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/module.py new file mode 100644 index 000000000..12d606b70 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/module.py @@ -0,0 +1,75 @@ +# +# (c) 2019, Ansible by 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 +from ansible.module_utils._text import to_text +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.connection import Connection + +from ansible_collections.arista.eos.plugins.module_utils.network.eos.providers import ( + providers, +) + + +class NetworkModule(AnsibleModule): + fail_on_missing_provider = True + + def __init__(self, connection=None, *args, **kwargs): + super(NetworkModule, self).__init__(*args, **kwargs) + + if connection is None: + connection = Connection(self._socket_path) + + self.connection = connection + + @property + def provider(self): + if not hasattr(self, "_provider"): + capabilities = self.from_json(self.connection.get_capabilities()) + + network_os = capabilities["device_info"]["network_os"] + network_api = capabilities["network_api"] + + if network_api == "cliconf": + connection_type = "network_cli" + + cls = providers.get( + network_os, + self._name.split(".")[-1], + connection_type, + ) + + if not cls: + msg = ( + "unable to find suitable provider for network os %s" + % network_os + ) + if self.fail_on_missing_provider: + self.fail_json(msg=msg) + else: + self.warn(msg) + + obj = cls(self.params, self.connection, self.check_mode) + + setattr(self, "_provider", obj) + + return getattr(self, "_provider") + + def get_facts(self, subset=None): + try: + self.provider.get_facts(subset) + except Exception as exc: + self.fail_json(msg=to_text(exc)) + + def edit_config(self, config_filter=None): + current_config = self.connection.get_config(flags=config_filter) + try: + commands = self.provider.edit_config(current_config) + changed = bool(commands) + return {"commands": commands, "changed": changed} + except Exception as exc: + self.fail_json(msg=to_text(exc)) diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/providers.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/providers.py new file mode 100644 index 000000000..31adec628 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/providers/providers.py @@ -0,0 +1,127 @@ +# +# (c) 2019, Ansible by 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 +import json + +from threading import RLock + +from ansible.module_utils.six import itervalues +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 ( + to_list, +) + + +_registered_providers = {} +_provider_lock = RLock() + + +def register_provider(network_os, module_name): + def wrapper(cls): + _provider_lock.acquire() + try: + if network_os not in _registered_providers: + _registered_providers[network_os] = {} + for ct in cls.supported_connections: + if ct not in _registered_providers[network_os]: + _registered_providers[network_os][ct] = {} + for item in to_list(module_name): + for entry in itervalues(_registered_providers[network_os]): + entry[item] = cls + finally: + _provider_lock.release() + return cls + + return wrapper + + +def get(network_os, module_name, connection_type): + network_os_providers = _registered_providers.get(network_os) + if network_os_providers is None: + raise ValueError("unable to find a suitable provider for this module") + if connection_type not in network_os_providers: + raise ValueError("provider does not support this connection type") + elif module_name not in network_os_providers[connection_type]: + raise ValueError("could not find a suitable provider for this module") + return network_os_providers[connection_type][module_name] + + +class ProviderBase(object): + supported_connections = () + + def __init__(self, params, connection=None, check_mode=False): + self.params = params + self.connection = connection + self.check_mode = check_mode + + @property + def capabilities(self): + if not hasattr(self, "_capabilities"): + resp = self.from_json(self.connection.get_capabilities()) + setattr(self, "_capabilities", resp) + return getattr(self, "_capabilities") + + def get_value(self, path): + params = self.params.copy() + for key in path.split("."): + params = params[key] + return params + + def get_facts(self, subset=None): + raise NotImplementedError(self.__class__.__name__) + + def edit_config(self): + raise NotImplementedError(self.__class__.__name__) + + +class CliProvider(ProviderBase): + supported_connections = ("network_cli",) + + @property + def capabilities(self): + if not hasattr(self, "_capabilities"): + resp = self.from_json(self.connection.get_capabilities()) + setattr(self, "_capabilities", resp) + return getattr(self, "_capabilities") + + def get_config_context(self, config, path, indent=2): + if config is not None: + netcfg = NetworkConfig(indent=indent, contents=config) + try: + config = netcfg.get_block_config(to_list(path)) + except ValueError: + config = None + return config + + def render(self, config=None): + raise NotImplementedError(self.__class__.__name__) + + def cli(self, command): + try: + if not hasattr(self, "_command_output"): + setattr(self, "_command_output", {}) + return self._command_output[command] + except KeyError: + out = self.connection.get(command) + try: + out = json.loads(out) + except ValueError: + pass + self._command_output[command] = out + return out + + def get_facts(self, subset=None): + return self.populate() + + def edit_config(self, config=None): + commands = self.render(config) + if commands and self.check_mode is False: + self.connection.edit_config(commands) + return commands diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/__init__.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/bgp_address_family.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/bgp_address_family.py new file mode 100644 index 000000000..555ffd30e --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/bgp_address_family.py @@ -0,0 +1,672 @@ +# -*- 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 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_router_bgp_cmd(config_data): + command = "router bgp {as_number}".format(**config_data) + return command + + +def _tmplt_bgp_address_family(config_data): + command = "" + if config_data.get("vrf"): + command = "vrf {vrf}\n".format(**config_data) + command += "address-family {afi}".format(**config_data) + if config_data.get("safi"): + command += " {safi}".format(**config_data) + return command + + +def _tmplt_bgp_params(config_data): + command = "bgp" + if config_data["bgp_params"].get("additional_paths"): + command += " additional-paths {additional_paths}".format( + **config_data["bgp_params"] + ) + if config_data["bgp_params"]["additional_paths"] == "send": + command += " any" + elif config_data["bgp_params"].get("next_hop_address_family"): + command += " next-hop address-family ipv6" + elif config_data["bgp_params"].get("next_hop_unchanged"): + command += " next-hop-unchanged" + elif config_data["bgp_params"].get("redistribute_internal"): + command += " redistribute-internal" + elif config_data["bgp_params"].get("route"): + command += " route install-map {route}".format( + **config_data["bgp_params"] + ) + return command + + +def _tmplt_bgp_graceful_restart(config_data): + command = "graceful-restart" + return command + + +def _tmplt_bgp_neighbor(config_data): + command = "neighbor {peer}".format(**config_data["neighbor"]) + if config_data["neighbor"].get("additional_paths"): + command += " additional-paths {additional_paths}".format( + **config_data["neighbor"] + ) + if config_data["neighbor"]["additional_paths"] == "send": + command += "any" + elif config_data["neighbor"].get("activate"): + command += " activate" + elif config_data["neighbor"].get("default_originate"): + command += " default-originate" + if config_data["neighbor"]["default_originate"].get("route_map"): + command += " route-map {route_map}".format( + **config_data["neighbor"]["default_originate"] + ) + if config_data["neighbor"]["default_originate"].get("always"): + command += " always" + elif config_data["neighbor"].get("graceful_restart"): + command += " graceful-restart" + elif config_data["neighbor"].get("next_hop_unchanged"): + command += " next-hop-unchanged" + elif config_data["neighbor"].get("next_hop_address_family"): + command += " next-hop addres-family ipv6" + elif config_data["neighbor"].get("prefix_list"): + command += " prefix-list {name} {direction}".format( + **config_data["neighbor"]["prefix_list"] + ) + elif config_data["neighbor"].get("route_map"): + command += " route-map {name} {direction}".format( + **config_data["neighbor"]["route_map"] + ) + elif config_data["neighbor"].get("weight"): + command += " weight {weight}".format(**config_data["neighbor"]) + elif config_data["neighbor"].get("encapsulation"): + command += " encapsulation {transport}".format( + **config_data["neighbor"] + ) + if config_data["neighbor"]["encapsulation"].get("source_interface"): + command += ( + " next-hop-self source-interface {source_interface}".format( + **config_data["neighbor"] + ) + ) + return command + + +def _tmplt_bgp_network(config_data): + command = "network {address}".format(**config_data) + if config_data.get("route_map"): + command += " route-map {route_map}".format(**config_data) + return command + + +def _tmplt_bgp_redistribute(config_data): + command = "redistribute {protocol}".format(**config_data) + if config_data.get("isis_level"): + command += " {isis_level}".format(**config_data) + if config_data.get("ospf_route"): + command += " match {ospf_route}".format(**config_data) + if config_data.get("route_map"): + command += " route-map {route_map}".format(**config_data) + return command + + +def _tmplt_bgp_route_target(config_data): + command = "route-target {action}".format(**config_data["route_target"]) + if config_data["route_target"].get("type"): + command += " {type}".format(**config_data["route_target"]) + if config_data["route_target"].get("route_map"): + command += " {route_map}".format(**config_data["route_target"]) + if config_data["route_target"].get("target"): + command += " {target}".format(**config_data["route_target"]) + return command + + +class Bgp_afTemplate(NetworkTemplate): + def __init__(self, lines=None): + super(Bgp_afTemplate, self).__init__(lines=lines, tmplt=self) + + # fmt: off + PARSERS = [ + { + "name": "router", + "getval": re.compile( + r""" + ^router\s + bgp + \s(?P\S+) + $""", + re.VERBOSE, + ), + "setval": _tmplt_router_bgp_cmd, + "compval": "as_number", + "result": {"as_number": "{{ as_num }}"}, + "shared": True, + }, + { + "name": "address_family", + "getval": re.compile( + r""" + \s*(?Pvrf\s\S+)* + \s*address-family + \s(?Pipv4|ipv6|evpn) + \s*(?P\S+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_address_family, + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "afi": "{{ afi }}", + "safi": "{{ type }}", + "vrf": "{{ vrf.split(" ")[1] }}", + }, + }, + }, + "shared": True, + }, + { + "name": "bgp_params_additional_paths", + "getval": re.compile( + r""" + \s*bgp + \s+additional-paths + \s+(?P\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.additional_paths", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "bgp_params": { + "additional_paths": "{{ action }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params.nexthop_address_family", + "getval": re.compile( + r""" + \s*bgp + \s+next-hop + \s+address-family + \s+ipv6 + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.next_hop_address_family", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "bgp_params": { + "next_hop_unchanged": "{{ 'ipv6' }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params.nexthop_unchanged", + "getval": re.compile( + r""" + \s*bgp + \s+next-hop-unchanged + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.next_hop_unchanged", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "bgp_params": { + "next_hop_unchanged": "{{ True }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params.redistribute_internal", + "getval": re.compile( + r""" + \s*bgp + \s+redistribute-internal + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.redistribute_internal", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "bgp_params": { + "redistribute_internal": "{{ True }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params.route", + "getval": re.compile( + r""" + \s*bgp + \s+route + \s+install-map + \s+(?P\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.route", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "bgp_params": { + "route": "{{ route }}", + }, + }, + }, + }, + }, + { + "name": "graceful_restart", + "getval": re.compile( + r""" + \s*graceful-restart + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_graceful_restart, + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "graceful_restart": "{{ True }}", + }, + }, + }, + }, + { + "name": "neighbor.activate", + "getval": re.compile( + r""" + \s*neighbor + \s+(?P\S+) + \s+activate + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_neighbor, + "compval": "neighbor.activate", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "neighbor": { + "{{ peer }}": { + "peer": "{{ peer }}", + "activate": "{{ True }}", + }, + }, + }, + }, + }, + }, + { + "name": "neighbor.additional_paths", + "getval": re.compile( + r""" + \s*neighbor + \s+(?P\S+) + \s+additional-paths + \s+(?P\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_neighbor, + "compval": "neighbor.additional_paths", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "neighbor": { + "{{ peer }}": { + "peer": "{{ peer }}", + "additional_paths": "{{ action }}", + }, + }, + }, + }, + }, + }, + { + "name": "neighbor.default_originate", + "getval": re.compile( + r""" + \s*neighbor + \s+(?P\S+) + \s+default-originate + \s*(?Proute-map\s\S+)* + \s*(?Palways)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_neighbor, + "compval": "neighbor.default_originate", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "neighbor": { + "{{ peer }}": { + "peer": "{{ peer }}", + "default_originate": { + "route_map": "{{ route_map.split(" ")[1] }}", + "always": "{{ True if always is defined }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "neighbor.graceful_restart", + "getval": re.compile( + r""" + \s*neighbor + \s+(?P\S+) + \s+graceful-restart + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_neighbor, + "compval": "neighbor.graceful_restart", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "neighbor": { + "{{ peer }}": { + "peer": "{{ peer }}", + "graceful_restart": "{{ True }}", + }, + }, + }, + }, + }, + }, + { + "name": "neighbor.next_hop_unchanged", + "getval": re.compile( + r""" + \s*neighbor + \s+(?P\S+) + \s+next-hop-unchanged + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_neighbor, + "compval": "neighbor.next_hop_unchanged", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "neighbor": { + "{{ peer }}": { + "peer": "{{ peer }}", + "next_hop_unchanged": "{{ True }}", + }, + }, + }, + }, + }, + }, + { + "name": "neighbor.next_hop_address_family", + "getval": re.compile( + r""" + \s*neighbor + \s+(?P\S+) + \s+next-hop + \s+address-family + \s+ipv6 + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_neighbor, + "compval": "neighbor.next_hop_address_family", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "neighbor": { + "{{ peer }}": { + "peer": "{{ peer }}", + "next_hop_address_family": "{{ 'ipv6' }}", + }, + }, + }, + }, + }, + }, + { + "name": "neighbor.prefix_list", + "getval": re.compile( + r""" + \s*neighbor + \s+(?P\S+) + \s+prefix-list + \s+(?P\S+) + \s+(?Pin|out) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_neighbor, + "compval": "neighbor.prefix_list", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "neighbor": { + "{{ peer }}": { + "peer": "{{ peer }}", + "prefix_list": { + "name": "{{ name }}", + "direction": "{{ dir }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "neighbor.route_map", + "getval": re.compile( + r""" + \s*neighbor + \s+(?P\S+) + \s+route-map + \s+(?P\S+) + \s+(?Pin|out) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_neighbor, + "compval": "neighbor.route_map", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "neighbor": { + "{{ peer }}": { + "peer": "{{ peer }}", + "route_map": { + "name": "{{ name }}", + "direction": "{{ dir }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "neighbor.weight", + "getval": re.compile( + r""" + \s*neighbor + \s+(?P\S+) + \s+weight + \s+(?P\d+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_neighbor, + "compval": "neighbor.weight", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "neighbor": { + "{{ peer }}": { + "peer": "{{ peer }}", + "weight": "{{ weight }}", + }, + }, + }, + }, + }, + }, + { + "name": "neighbor.encapsulation", + "getval": re.compile( + r""" + \s*neighbor + \s+default + \s+encapsulation + \s+(?Pmpls|vxlan) + \s*(next-hop-self)* + \s*(source-interface)* + \s*(?P\S+\s\S+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_neighbor, + "compval": "neighbor.encapsulation", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "neighbor": { + "{{ peer }}": { + "peer": "{{ peer }}", + "encapsulation": { + "transport": "{{ type }}", + "source_interface": "{{ interface }}", + }, + }, + }, + }, + }, + }, + }, + { + "name": "network", + "getval": re.compile( + r""" + \s*network + \s+(?P
\S+) + \s*(route-map)* + \s*(?P\S+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_network, + "compval": "network", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "network": { + "{{ address }}": { + "address": "{{ address }}", + "route_map": "{{ route_map }}", + }, + }, + }, + }, + }, + }, + { + "name": "redistribute", + "getval": re.compile( + r""" + \s*redistribute + \s+(?P\S+) + \s*(?Plevel-1|level-2|level-1-2)* + \s*(?Pmatch\s\S+)* + \s*(?Proute-map\s\S+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_redistribute, + "compval": "redistribute", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "redistribute": [ + { + "protocol": "{{ route }}", + "route_map": "{{ route_map.split(" ")[1] }}", + "isis_level": "{{ level }}", + "ospf_route": "{{ match.split(" ")[1] }}", + }, + ], + }, + }, + }, + }, + { + "name": "route_target", + "getval": re.compile( + r""" + \s*route-target + \s+(?P\S+) + \s*(?Pevpn|vpn-ipv4|vpn-ipv6)* + \s*(?Proute-map\s\S+)* + \s+(?P\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_route_target, + "compval": "route_target", + "result": { + "address_family": { + '{{ afi + "_" + vrf|d() }}': { + "route_target": { + "action": "{{ action }}", + "type": "{{ type }}", + "route_map": "{{ map.split(" ")[1] }}", + "target": "{{ target }}", + }, + }, + }, + }, + }, + ] + # fmt: on diff --git a/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/bgp_global.py b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/bgp_global.py new file mode 100644 index 000000000..a1fcee400 --- /dev/null +++ b/ansible_collections/arista/eos/plugins/module_utils/network/eos/rm_templates/bgp_global.py @@ -0,0 +1,2965 @@ +# -*- 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 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_router_bgp_cmd(config_data): + command = "router bgp {as_number}".format(**config_data) + return command + + +def _tmplt_bgp_vrf(config_data): + command = "vrf {vrf}".format(**config_data) + return command + + +def _tmplt_bgp_aggregate_address(config_data): + command = "aggregate-address {address}".format(**config_data) + if config_data.get("as_set"): + command += " as-set" + if config_data.get("summary_only"): + command += " summary-only" + if config_data.get("attribute_map"): + command += " attribute-map {attribute_map}".format(**config_data) + if config_data.get("match_map"): + command += " match-map {match_map}".format(**config_data) + if config_data.get("advertise_only"): + command += " advertise-only" + return command + + +def _tmplt_bgp_params(config_data): + command = "bgp" + if config_data["bgp_params"].get("additional_paths"): + command += ( + " additional-paths " + + config_data["bgp_params"]["additional_paths"] + ) + if config_data["bgp_params"]["additional_paths"] == "send": + command += " any" + elif config_data["bgp_params"].get("advertise_inactive"): + command += " advertise-inactive" + elif config_data["bgp_params"].get("allowas_in"): + command += " allowas-in" + if config_data["bgp_params"]["allowas_in"].get("count"): + command += " {count}".format( + **config_data["bgp_params"]["allowas_in"] + ) + elif config_data["bgp_params"].get("always_compare_med"): + command += " always-comapre-med" + elif config_data["bgp_params"].get("asn"): + command += " asn notaion {asn}".format(**config_data["bgp_params"]) + elif config_data["bgp_params"].get("auto_local_addr"): + command += " auto-local-addr" + elif config_data["bgp_params"].get("bestpath"): + if config_data["bgp_params"]["bestpath"].get("as_path"): + command += " bestpath as-path {as_path}".format( + **config_data["bgp_params"]["bestpath"] + ) + elif config_data["bgp_params"]["bestpath"].get("ecmp_fast"): + command += " bestpath ecmp-fast" + elif config_data["bgp_params"]["bestpath"].get("med"): + command += " bestpath med" + if config_data["bgp_params"]["bestpath"]["med"].get("confed"): + command += " confed" + else: + command += " missing-as-worst" + elif config_data["bgp_params"]["bestpath"].get("skip"): + command += " bestpath skip next-hop igp-cost" + elif config_data["bgp_params"]["bestpath"].get("tie_break"): + tie = re.sub( + r"_", + r"-", + config_data["bgp_params"]["bestpath"]["tie_break"], + ) + command += " tie-break " + tie + elif config_data["bgp_params"].get("client_to_client"): + command += " client-to-client" + elif config_data["bgp_params"].get("cluster_id"): + command += " cluster-id {cluster_id}".format( + **config_data["bgp_params"] + ) + elif config_data["bgp_params"].get("confederation"): + command += " confederation" + if config_data["bgp_params"]["confederation"].get("identifier"): + command += " identifier {identifier}".format( + **config_data["bgp_params"]["confederation"] + ) + else: + command += " peers {peers}".format( + **config_data["bgp_params"]["confederation"] + ) + elif config_data["bgp_params"].get("control_plane_filter"): + command += " control-plane-filter default-allow" + elif config_data["bgp_params"].get("convergence"): + command += " convergence" + if config_data["bgp_params"]["convergence"].get("slow_peer"): + command += " slow-peer" + command += " time {time}".format( + **config_data["bgp_params"]["convergence"] + ) + elif config_data["bgp_params"].get("default"): + command += " default {default}".format(**config_data["bgp_params"]) + elif config_data["bgp_params"].get("enforce_first_as"): + command += " enforce-first-as" + elif config_data["bgp_params"].get("host_routes"): + command += " host-routes fib direct-install" + elif config_data["bgp_params"].get("labeled_unicast"): + command += " labeled-unicast rib {labeled_unicast}".format( + **config_data["bgp_params"] + ) + elif config_data["bgp_params"].get("listen"): + # from eos 4.23 , 'bgp listen limit ' is replaced by 'dynamic peer max'. + command = "dynamic peer max " + if config_data["bgp_params"]["listen"].get("limit"): + command += "{limit}".format(**config_data["bgp_params"]["listen"]) + else: + command += " range {address} peer group".format( + **config_data["bgp_params"]["listen"]["range"] + ) + if config_data["bgp_params"]["listen"]["range"]["peer_group"].get( + "peer_filter", + ): + command += " {name} peer-filter {peer_filter}".format( + **config_data["bgp_params"]["listen"]["range"][ + "peer_group" + ] + ) + else: + command += " {name} remote-as {remote_as}".format( + **config_data["bgp_params"]["listen"]["range"][ + "peer_group" + ] + ) + elif config_data["bgp_params"].get("log_neighbor_changes"): + command += " log-neighbor-changes" + elif config_data["bgp_params"].get("missing_policy"): + command += ( + " missing-policy direction {direction} action {action}".format( + **config_data["bgp_params"]["missing_policy"] + ) + ) + elif config_data["bgp_params"].get("monitoring"): + command += " monitoring" + elif config_data["bgp_params"].get("next_hop_unchanged"): + command += " next-hop-unchanged" + elif config_data["bgp_params"].get("redistribute_internal"): + command += " redistribute-internal" + elif config_data["bgp_params"].get("route"): + command += " route install-map {route}".format( + **config_data["bgp_params"] + ) + elif config_data["bgp_params"].get("route_reflector"): + command += " route-reflector preserve-attributes" + if config_data["bgp_params"]["route_reflector"].get("preserve"): + command += " always" + elif config_data["bgp_params"].get("transport"): + command += " transport listen-port {transport}".format( + **config_data["bgp_params"] + ) + return command + + +def _tmplt_bgp_redistribute(config_data): + command = "redistribute {protocol}".format(**config_data) + if config_data.get("isis_level"): + command += " {isis_level}".format(**config_data) + if config_data.get("ospf_route"): + if config_data["ospf_route"] == "nssa_external_2": + route = "nssa-external 2" + elif config_data["ospf_route"] == "nssa_external_1": + route = "nssa-external 1" + else: + route = config_data["ospf_route"] + command += " match " + route + if config_data.get("route_map"): + command += " route-map {route_map}".format(**config_data) + return command + + +def _tmplt_bgp_default_metric(config_data): + command = "default-metric {default_metric}".format(**config_data) + return command + + +def _tmplt_bgp_distance(config_data): + command = "distance bgp" + if config_data["distance"].get("external"): + command += " {external}".format(**config_data["distance"]) + if config_data["distance"].get("internal"): + command += " {internal}".format(**config_data["distance"]) + if config_data["distance"].get("local"): + command += " {local}".format(**config_data["distance"]) + return command + + +def _tmplt_bgp_graceful_restart(config_data): + command = "graceful-restart" + if config_data.get("restart_time"): + command += " restart-time {restart_time}".format(**config_data) + if config_data.get("stalepath_time"): + command += " stalepath-time {stalepath_time}".format(**config_data) + return command + + +def _tmplt_bgp_graceful_restart_helper(config_data): + command = "graceful-restart-helper" + return command + + +def _tmplt_bgp_access_group(config_data): + if config_data.get("afi") == "ipv4": + afi = "ip" + else: + afi = "ipv6" + command = afi + " access-group {acl_name}".format(**config_data) + if config_data.get("direction"): + command += " {direction}".format(**config_data) + return command + + +def _tmplt_bgp_maximum_paths(config_data): + command = "maximum-paths {max_equal_cost_paths}".format( + **config_data["maximum_paths"] + ) + if config_data["maximum_paths"].get("max_installed_ecmp_paths"): + command += " ecmp {max_installed_ecmp_paths}".format( + **config_data["maximum_paths"] + ) + return command + + +def _tmplt_bgp_monitoring(config_data): + cmd = "monitoring" + command = "" + if config_data.get("timestamp"): + command = cmd + " timestamp {timestamp}".format(**config_data) + if config_data.get("port"): + command = cmd + " port {port}".format(**config_data) + if config_data.get("received"): + command = cmd + " received routes {received}".format(**config_data) + if config_data.get("station"): + command = cmd + " station {station}".format(**config_data) + return command + + +def _tmplt_bgp_neighbor(config_data): + command = "neighbor {neighbor_address}".format(**config_data["neighbor"]) + if config_data["neighbor"].get("additional_paths"): + command += " additional-paths {additional_paths}".format( + **config_data["neighbor"] + ) + if config_data["neighbor"]["additional_paths"] == "send": + command += "any" + elif config_data["neighbor"].get("peer_group"): + command += " peer group" + if ( + config_data["neighbor"]["peer_group"] + != config_data["neighbor"]["peer_group"] + ): + command += config_data["neighbor"]["peer_group"] + elif config_data["neighbor"].get("allowas_in"): + command += " allowas-in" + if config_data["neighbor"]["allowas_in"].get("count"): + command += " {count}".format( + **config_data["neighbor"]["allowas_in"] + ) + elif config_data["neighbor"].get("auto_local_addr"): + command += " auto-local-addr" + elif config_data["neighbor"].get("bfd"): + command += " bfd" + if config_data["neighbor"]["bfd"] == "c_bit": + command += " c-bit" + elif config_data["neighbor"].get("default_originate"): + command += " default-originate" + if config_data["neighbor"]["default_originate"].get("route_map"): + command += " route-map {route_map}".format( + **config_data["neighbor"]["default_originate"] + ) + if config_data["neighbor"]["default_originate"].get("always"): + command += " always" + elif config_data["neighbor"].get("description"): + command += " description {description}".format( + **config_data["neighbor"] + ) + elif config_data["neighbor"].get("dont_capability_negotiate"): + command += " dont-capability-negotiate" + elif config_data["neighbor"].get("ebgp_multihop"): + command += " ebgp-multiphop" + if config_data["neighbor"]["ebgp_multihop"].get("ttl"): + command += " {ttl}".format( + **config_data["neighbor"]["ebgp_multihop"] + ) + elif config_data["neighbor"].get("encryption_password"): + command += " password {type} {password}".format( + **config_data["neighbor"]["encryption_password"] + ) + elif config_data["neighbor"].get("enforce_first_as"): + command += " enforce-first-as" + elif config_data["neighbor"].get("export_localpref"): + command += " export-localpref {export_localpref}".format( + **config_data["neighbor"] + ) + elif config_data["neighbor"].get("fall_over"): + command += " fall-over bfd" + elif config_data["neighbor"].get("graceful_restart"): + command += " graceful-restart" + elif config_data["neighbor"].get("graceful_restart_helper"): + command += " graceful-restart-helper" + elif config_data["neighbor"].get("idle_restart_timer"): + command += " idle-restart-timer {idle_restart_timer}".format( + **config_data["neighbor"] + ) + elif config_data["neighbor"].get("import_localpref"): + command += " import-localpref {import_localpref}".format( + **config_data["neighbor"] + ) + elif config_data["neighbor"].get("link_bandwidth"): + command += " link-bandwidth" + if config_data["neighbor"]["link_bandwidth"].get("auto"): + command += " auto" + if config_data["neighbor"]["link_bandwidth"].get("default"): + command += " default {default}".format( + **config_data["neighbor"]["link_bandwidth"] + ) + if config_data["neighbor"]["link_bandwidth"].get("update_delay"): + command += " update-delay {update_delay}".format( + **config_data["neighbor"]["link_bandwidth"] + ) + elif config_data["neighbor"].get("local_as"): + command += " local-as {as_number} no-prepend replace-as".format( + **config_data["neighbor"]["local_as"] + ) + if config_data["neighbor"]["local_as"].get("fallback"): + command += " fallback" + elif config_data["neighbor"].get("local_v6_addr"): + command += " local-v6-addr {local_v6_addr}".format( + **config_data["neighbor"] + ) + elif config_data["neighbor"].get("maximum_accepted_routes"): + command += " maximum-accepted-routes {count}".format( + **config_data["neighbor"]["maximum_accepted_routes"] + ) + if config_data["neighbor"]["maximum_accepted_routes"].get( + "warning_limit", + ): + command += " warning-limit {warning_limit}".format( + **config_data["neighbor"]["maximum_accepted_routes"] + ) + elif config_data["neighbor"].get("maximum_received_routes"): + command += " maximum-routes {count}".format( + **config_data["neighbor"]["maximum_received_routes"] + ) + if config_data["neighbor"]["maximum_received_routes"].get( + "warning_limit", + ): + if config_data["neighbor"]["maximum_received_routes"][ + "warning_limit" + ].get("limit_count"): + command += " warning-limit {limit_count}".format( + **config_data["neighbor"]["maximum_received_routes"][ + "warning_limit" + ] + ) + if config_data["neighbor"]["maximum_received_routes"][ + "warning_limit" + ].get("limit_percent"): + command += ( + " warning-limit " + + str( + config_data["neighbor"]["maximum_received_routes"][ + "warning_limit" + ]["limit_percent"], + ) + + " percent" + ) + if config_data["neighbor"]["maximum_received_routes"].get( + "warning_only", + ): + command += " warning-only" + elif config_data["neighbor"].get("metric_out"): + command += " metric-out {metric_out}".format(**config_data["neighbor"]) + elif config_data["neighbor"].get("monitoring"): + command += " monitoring" + elif config_data["neighbor"].get("next_hop_self"): + command += " next-hop-self" + elif config_data["neighbor"].get("next_hop_unchanged"): + command += " next-hop-unchanged" + elif config_data["neighbor"].get("next_hop_v6_address"): + command += " next-hop-v6-addr {next_hop_v6_address} in".format( + **config_data["neighbor"] + ) + elif config_data["neighbor"].get("out_delay"): + command += " out-delay {out_delay}".format(**config_data["neighbor"]) + elif config_data["neighbor"].get("remote_as"): + command += " remote-as {remote_as}".format(**config_data["neighbor"]) + elif config_data["neighbor"].get("remove_private_as"): + command += " remove-private-as" + if config_data["neighbor"]["remove_private_as"].get("all"): + command += " all" + if config_data["neighbor"]["remove_private_as"].get("replace_as"): + command += " replace-as" + elif config_data["neighbor"].get("peer_as"): + command += " peer-as {peer_as}".format(**config_data["neighbor"]) + elif config_data["neighbor"].get("prefix_list"): + command += " prefix-list {name} {direction}".format( + **config_data["neighbor"]["prefix_list"] + ) + elif config_data["neighbor"].get("route_map"): + command += " route-map {name} {direction}".format( + **config_data["neighbor"]["route_map"] + ) + elif config_data["neighbor"].get("route_reflector_client"): + command += " route-reflector-client" + elif config_data["neighbor"].get("route_to_peer"): + command += " route-to-peer" + elif config_data["neighbor"].get("send_community"): + command += " send-community" + if config_data["neighbor"]["send_community"].get( + "community_attribute", + ): + command += ( + " " + + config_data["neighbor"]["send_community"][ + "community_attribute" + ] + ) + if config_data["neighbor"]["send_community"].get("sub_attribute"): + command += ( + " " + + config_data["neighbor"]["send_community"]["sub_attribute"] + ) + if config_data["neighbor"]["send_community"].get( + "link_bandwidth_attribute", + ): + command += ( + " " + + config_data["neighbor"]["send_community"][ + "link_bandwidth_attribute" + ] + ) + if config_data["neighbor"]["send_community"].get("speed"): + command += " " + config_data["neighbor"]["send_community"]["speed"] + if config_data["neighbor"]["send_community"].get("divide"): + command += ( + " " + config_data["neighbor"]["send_community"]["divide"] + ) + elif config_data["neighbor"].get("shutdown"): + command += " shutdown" + elif config_data["neighbor"].get("soft_reconfiguration"): + command += " soft-reconfiguration inbound" + if config_data["neighbor"]["soft_reconfiguration"] == "all": + command += " all" + elif config_data["neighbor"].get("transport"): + command += " transport" + if config_data["neighbor"]["transport"].get("connection_mode"): + command += " connection-mode passive" + else: + command += " remote-port {remote_port}".format( + **config_data["neighbor"]["transport"] + ) + elif config_data["neighbor"].get("timers"): + command += " timers {keepalive} {holdtime}".format( + **config_data["neighbor"]["timers"] + ) + elif config_data["neighbor"].get("ttl"): + command += " ttl maximum-hops {ttl}".format(**config_data["neighbor"]) + elif config_data["neighbor"].get("update_source"): + command += " update-source {update_source}".format( + **config_data["neighbor"] + ) + elif config_data["neighbor"].get("weight"): + command += " weight {weight}".format(**config_data["neighbor"]) + return command + + +def _tmplt_bgp_network(config_data): + command = "network {address}".format(**config_data) + if config_data.get("route_map"): + command += " route-map {route_map}".format(**config_data) + return command + + +def _tmplt_bgp_route_target(config_data): + command = "route-target {action}".format(**config_data["route_target"]) + if config_data["route_target"].get("type"): + command += " {type}".format(**config_data["route_target"]) + if config_data["route_target"].get("route_map"): + command += " {route_map}".format(**config_data["route_target"]) + if config_data["route_target"].get("imported_route"): + command += " imported-route" + if config_data["route_target"].get("target"): + command += " {target}".format(**config_data["route_target"]) + + return command + + +def _tmplt_bgp_router_id(config_data): + command = "router-id {router_id}".format(**config_data) + return command + + +def _tmplt_bgp_shutdown(config_data): + return "shutdown" + + +def _tmplt_bgp_timers(config_data): + command = "timers bgp {keepalive} {holdtime}".format( + **config_data["timers"] + ) + return command + + +def _tmplt_bgp_ucmp(config_data): + command = "ucmp" + if "fec" in config_data["ucmp"]: + command += " fec threshold trigger" + command += " {trigger} clear {clear} warning-only".format( + **config_data["ucmp"]["fec"] + ) + if "link_bandwidth" in config_data["ucmp"]: + command += " link-bandwidth {mode}".format( + **config_data["ucmp"]["link_bandwidth"] + ) + if config_data["ucmp"]["link_bandwidth"].get("mode") == "update_delay": + command += " {update_delay}".format( + **config_data["ucmp"]["link_bandwidth"] + ) + if "mode" in config_data["ucmp"]: + command += " mode 1" + if config_data["ucmp"]["mode"].get("nexthops"): + command += " {nexthops}".format(**config_data["ucmp"]["mode"]) + return command + + +def _tmplt_bgp_update(config_data): + command = "update {wait_for}".format(**config_data["update"]) + if config_data["update"].get("batch_size"): + command += " {batch_size}".format(**config_data["update"]) + return command + + +def _tmplt_bgp_vlan(config_data): + command = "vlan {vlan}".format(**config_data) + return command + + +def _tmplt_bgp_vlan_aware_bundle(config_data): + command = "vlan-aware-bundle " + config_data["vlan_aware_bundle"] + return command + + +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": "router", + "getval": re.compile( + r""" + ^router\s + bgp + \s(?P\S+) + $""", + re.VERBOSE, + ), + "setval": _tmplt_router_bgp_cmd, + "compval": "as_number", + "result": {"as_number": "{{ as_num }}"}, + }, + { + "name": "vrf", + "getval": re.compile( + r""" + \s*vrf + \s(?P\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_vrf, + "compval": "vrfs.vrf", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "vrf": "{{ vrf }}", + }, + }, + }, + "shared": True, + }, + { + "name": "aggregate_address", + "getval": re.compile( + r""" + \s*aggregate-address + \s+(?P
\S+) + \s*(?Pas-set)* + \s*(?Psummary-only)* + \s*(?Pattribute-map\s\S+)* + \s*(?Pmatch-map\s\S+)* + \s*(?Padvertise-only)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_aggregate_address, + "compval": "aggregate_address", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "aggregate_address": [ + { + "address": "{{ address }}", + "advertise_only": "{{ True if advertise_only is defined }}", + "as_set": "{{ True if as_set is defined }}", + "summary_only": "{{ True if summary_only is defined }}", + "attribute_map": "{{ attribute_map.split(" ")[1] }}", + "match_map": "{{ match_map.split(" ")[1] }}", + }, + ], + }, + }, + }, + }, + { + "name": "bgp_params_additional_paths", + "getval": re.compile( + r""" + \s*bgp + \s+additional-paths + \s+(?P\S+) + \s*(any) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.additional_paths", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "additional_paths": "{{ action }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params_advertise_inactive", + "getval": re.compile( + r""" + \s*bgp + \s+advertise-inactive + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.advertise_inactive", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "advertise_inactive": "{{ True }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params_allowas_in", + "getval": re.compile( + r""" + \s*bgp + \s+allowas-in + \s*(?P\d+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.allowas_in", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "allowas_in": { + "set": "{{ True if count is undefined }}", + "count": "{{ count }}", + }, + }, + }, + }, + }, + }, + { + "name": "bgp_params_always_compare_med", + "getval": re.compile( + r""" + \s*bgp + \s+always-compare-med + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.always_compare_med", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "always_compare_med": "{{ True }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params_asn", + "getval": re.compile( + r""" + \s*bgp + \s+asn + \s+notation + \s+(?Pasdot|asplain) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.asn", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "asn": "{{ notation }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params_auto_local_addr", + "getval": re.compile( + r""" + \s*bgp + \s+auto-local-addr + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.auto_local_addr", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "auto_local_addr": "{{ True }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params_bestpath_as_path", + "getval": re.compile( + r""" + \s*bgp + \s+bestpath + \s*(?Pas-path\s\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.bestpath.as_path", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "bestpath": { + "as_path": "{{ as_path.split(" ")[1] }}", + }, + }, + }, + }, + }, + }, + { + "name": "bgp_params_bestpath_ecmp_fast", + "getval": re.compile( + r""" + \s*bgp + \s+bestpath + \s+ecmp-fast + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.bestpath.ecmp_fast", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "bestpath": { + "ecmp_fast": "{{ True }}", + }, + }, + }, + }, + }, + }, + { + "name": "bgp_params_bestpath_med", + "getval": re.compile( + r""" + \s*bgp + \s+bestpath + \s+med + \s*(?Pconfed)* + \s*(?Pmissing-as-worst)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.bestpath.med", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "med": { + "confed": "{{ True if confed is defined }}", + "missing_as_worst": "{{ True if missing is defined }}", + }, + }, + }, + }, + }, + }, + { + "name": "bgp_params_bestpath_skip", + "getval": re.compile( + r""" + \s*bgp + \s+bestpath + \s+(?Pskip) + \s+next-hop + \s+igp-cost + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.bestpath.skip", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "bestpath": { + "skip": "{{ True }}", + }, + }, + }, + }, + }, + }, + { + "name": "bgp_params_tie_break", + "getval": re.compile( + r""" + \s*bgp + \s+tie-break + \s+(?P\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.bestpath.tie_break", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "tie_break": "{{ tie }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params_client_to_client", + "getval": re.compile( + r""" + \s*bgp + \s+client-to-client + \s+reflection + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.client_to_client", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "client_to_client": "{{ True }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params_cluster_id", + "getval": re.compile( + r""" + \s*bgp + \s+cluster-id + \s+(?P
\S+) + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.cluster_id", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "cluster_id": "{{ address }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params_confederation", + "getval": re.compile( + r""" + \s*bgp + \s+confederation + \s*(?Pidentifier\s.+)* + \s*(?Ppeers\s.+)* + $""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.confederation", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "confederation": { + "identifier": "{{ identifier.split(" ")[1] }}", + "peers": "{{ peers.split(" ")[1] }}", + }, + }, + }, + }, + }, + }, + { + "name": "bgp_params_control_plane_filter", + "getval": re.compile( + r""" + \s*bgp + \s+control-plane-filter + \s+default-allow + *$""", + re.VERBOSE, + ), + "setval": _tmplt_bgp_params, + "compval": "bgp_params.control_plane_filter", + "result": { + "vrfs": { + '{{ "vrf_" + vrf|d() }}': { + "bgp_params": { + "control_plane_filter": "{{ True }}", + }, + }, + }, + }, + }, + { + "name": "bgp_params_convergence", + "getval": re.compile( + r""" + \s*bgp + \s+convergence + \s*(?Pslow-peer)* + \s+(?P